diff options
Diffstat (limited to 'lib/private')
266 files changed, 1813 insertions, 846 deletions
diff --git a/lib/private/Accounts/Hooks.php b/lib/private/Accounts/Hooks.php index 0235879e8e7..12f2b4777f8 100644 --- a/lib/private/Accounts/Hooks.php +++ b/lib/private/Accounts/Hooks.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Activity/Event.php b/lib/private/Activity/Event.php index 54274acea6b..39cdc12b3fb 100644 --- a/lib/private/Activity/Event.php +++ b/lib/private/Activity/Event.php @@ -415,8 +415,7 @@ class Event implements IEvent { public function isValid(): bool { return $this->isValidCommon() - && - $this->getSubject() !== '' + && $this->getSubject() !== '' ; } @@ -443,20 +442,16 @@ class Event implements IEvent { return $this->isValidCommon() - && - $this->getParsedSubject() !== '' + && $this->getParsedSubject() !== '' ; } protected function isValidCommon(): bool { return $this->getApp() !== '' - && - $this->getType() !== '' - && - $this->getAffectedUser() !== '' - && - $this->getTimestamp() !== 0 + && $this->getType() !== '' + && $this->getAffectedUser() !== '' + && $this->getTimestamp() !== 0 /** * Disabled for BC with old activities * && diff --git a/lib/private/Activity/EventMerger.php b/lib/private/Activity/EventMerger.php index 504f9088f24..0e7d318103a 100644 --- a/lib/private/Activity/EventMerger.php +++ b/lib/private/Activity/EventMerger.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php index 5c306fe6399..4e10f8a0c1a 100644 --- a/lib/private/Activity/Manager.php +++ b/lib/private/Activity/Manager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index 72af6c960a5..8a6bb5a4723 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index f6494fa946d..1911bce12bf 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,6 +9,7 @@ namespace OC\App; use OC\AppConfig; use OC\AppFramework\Bootstrap\Coordinator; +use OC\Config\ConfigManager; use OCP\Activity\IManager as IActivityManager; use OCP\App\AppPathNotFoundException; use OCP\App\Events\AppDisableEvent; @@ -18,6 +20,7 @@ use OCP\Collaboration\AutoComplete\IManager as IAutoCompleteManager; use OCP\Collaboration\Collaborators\ISearch as ICollaboratorSearch; use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IAppConfig; use OCP\ICacheFactory; use OCP\IConfig; use OCP\IGroup; @@ -26,6 +29,7 @@ use OCP\INavigationManager; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserSession; +use OCP\Server; use OCP\ServerVersion; use OCP\Settings\IManager as ISettingsManager; use Psr\Log\LoggerInterface; @@ -81,12 +85,13 @@ class AppManager implements IAppManager { private IEventDispatcher $dispatcher, private LoggerInterface $logger, private ServerVersion $serverVersion, + private ConfigManager $configManager, ) { } private function getNavigationManager(): INavigationManager { if ($this->navigationManager === null) { - $this->navigationManager = \OCP\Server::get(INavigationManager::class); + $this->navigationManager = Server::get(INavigationManager::class); } return $this->navigationManager; } @@ -112,7 +117,7 @@ class AppManager implements IAppManager { if (!$this->config->getSystemValueBool('installed', false)) { throw new \Exception('Nextcloud is not installed yet, AppConfig is not available'); } - $this->appConfig = \OCP\Server::get(AppConfig::class); + $this->appConfig = Server::get(AppConfig::class); return $this->appConfig; } @@ -123,7 +128,7 @@ class AppManager implements IAppManager { if (!$this->config->getSystemValueBool('installed', false)) { throw new \Exception('Nextcloud is not installed yet, AppConfig is not available'); } - $this->urlGenerator = \OCP\Server::get(IURLGenerator::class); + $this->urlGenerator = Server::get(IURLGenerator::class); return $this->urlGenerator; } @@ -134,7 +139,8 @@ class AppManager implements IAppManager { */ private function getEnabledAppsValues(): array { if (!$this->enabledAppsCache) { - $values = $this->getAppConfig()->getValues(false, 'enabled'); + /** @var array<string,string> */ + $values = $this->getAppConfig()->searchValues('enabled', false, IAppConfig::VALUE_STRING); $alwaysEnabledApps = $this->getAlwaysEnabledApps(); foreach ($alwaysEnabledApps as $appId) { @@ -185,9 +191,9 @@ class AppManager implements IAppManager { if (is_resource($dh)) { while (($file = readdir($dh)) !== false) { if ( - $file[0] != '.' && - is_dir($apps_dir['path'] . '/' . $file) && - is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml') + $file[0] != '.' + && is_dir($apps_dir['path'] . '/' . $file) + && is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml') ) { $apps[] = $file; } @@ -202,7 +208,7 @@ class AppManager implements IAppManager { * List all apps enabled for a user * * @param \OCP\IUser $user - * @return string[] + * @return list<string> */ public function getEnabledAppsForUser(IUser $user) { $apps = $this->getEnabledAppsValues(); @@ -457,7 +463,7 @@ class AppManager implements IAppManager { ]); } - $coordinator = \OCP\Server::get(Coordinator::class); + $coordinator = Server::get(Coordinator::class); $coordinator->bootApp($app); $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it"); @@ -507,8 +513,8 @@ class AppManager implements IAppManager { if (!empty($info['collaboration']['plugins'])) { // deal with one or many plugin entries - $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ? - [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin']; + $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) + ? [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin']; $collaboratorSearch = null; $autoCompleteManager = null; foreach ($plugins as $plugin) { @@ -545,11 +551,16 @@ class AppManager implements IAppManager { * @param string $appId * @param bool $forceEnable * @throws AppPathNotFoundException + * @throws \InvalidArgumentException if the application is not installed yet */ public function enableApp(string $appId, bool $forceEnable = false): void { // Check if app exists $this->getAppPath($appId); + if ($this->config->getAppValue($appId, 'installed_version', '') === '') { + throw new \InvalidArgumentException("$appId is not installed, cannot be enabled."); + } + if ($forceEnable) { $this->overwriteNextcloudRequirement($appId); } @@ -561,6 +572,8 @@ class AppManager implements IAppManager { ManagerEvent::EVENT_APP_ENABLE, $appId )); $this->clearAppsCache(); + + $this->configManager->migrateConfigLexiconKeys($appId); } /** @@ -596,6 +609,10 @@ class AppManager implements IAppManager { throw new \InvalidArgumentException("$appId can't be enabled for groups."); } + if ($this->config->getAppValue($appId, 'installed_version', '') === '') { + throw new \InvalidArgumentException("$appId is not installed, cannot be enabled."); + } + if ($forceEnable) { $this->overwriteNextcloudRequirement($appId); } @@ -615,6 +632,8 @@ class AppManager implements IAppManager { ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups )); $this->clearAppsCache(); + + $this->configManager->migrateConfigLexiconKeys($appId); } /** @@ -775,8 +794,8 @@ class AppManager implements IAppManager { * * @return array<string, string> */ - public function getAppInstalledVersions(): array { - return $this->getAppConfig()->getAppInstalledVersions(); + public function getAppInstalledVersions(bool $onlyEnabled = false): array { + return $this->getAppConfig()->getAppInstalledVersions($onlyEnabled); } /** @@ -812,6 +831,10 @@ class AppManager implements IAppManager { } private function isAlwaysEnabled(string $appId): bool { + if ($appId === 'core') { + return true; + } + $alwaysEnabled = $this->getAlwaysEnabledApps(); return in_array($appId, $alwaysEnabled, true); } diff --git a/lib/private/App/AppStore/AppNotFoundException.php b/lib/private/App/AppStore/AppNotFoundException.php new file mode 100644 index 00000000000..79ceebb4423 --- /dev/null +++ b/lib/private/App/AppStore/AppNotFoundException.php @@ -0,0 +1,13 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\App\AppStore; + +class AppNotFoundException extends \Exception { +} diff --git a/lib/private/App/AppStore/Bundles/Bundle.php b/lib/private/App/AppStore/Bundles/Bundle.php index 62f09b82f79..1443be81e92 100644 --- a/lib/private/App/AppStore/Bundles/Bundle.php +++ b/lib/private/App/AppStore/Bundles/Bundle.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Bundles/BundleFetcher.php b/lib/private/App/AppStore/Bundles/BundleFetcher.php index 01325699e2c..4ff53b0c70b 100644 --- a/lib/private/App/AppStore/Bundles/BundleFetcher.php +++ b/lib/private/App/AppStore/Bundles/BundleFetcher.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Bundles/EducationBundle.php b/lib/private/App/AppStore/Bundles/EducationBundle.php index 6770d4a7091..23681ec7416 100644 --- a/lib/private/App/AppStore/Bundles/EducationBundle.php +++ b/lib/private/App/AppStore/Bundles/EducationBundle.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Bundles/EnterpriseBundle.php b/lib/private/App/AppStore/Bundles/EnterpriseBundle.php index 88a9ec60a00..fc2d43e0388 100644 --- a/lib/private/App/AppStore/Bundles/EnterpriseBundle.php +++ b/lib/private/App/AppStore/Bundles/EnterpriseBundle.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Bundles/GroupwareBundle.php b/lib/private/App/AppStore/Bundles/GroupwareBundle.php index 7c7b74ff53d..93fa70268cd 100644 --- a/lib/private/App/AppStore/Bundles/GroupwareBundle.php +++ b/lib/private/App/AppStore/Bundles/GroupwareBundle.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Bundles/PublicSectorBundle.php b/lib/private/App/AppStore/Bundles/PublicSectorBundle.php index 158d525bdcc..106a6353029 100644 --- a/lib/private/App/AppStore/Bundles/PublicSectorBundle.php +++ b/lib/private/App/AppStore/Bundles/PublicSectorBundle.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Bundles/SocialSharingBundle.php b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php index 7f925688ca3..40f0fb15977 100644 --- a/lib/private/App/AppStore/Bundles/SocialSharingBundle.php +++ b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Fetcher/AppDiscoverFetcher.php b/lib/private/App/AppStore/Fetcher/AppDiscoverFetcher.php index 2537d1dcec9..8389e525750 100644 --- a/lib/private/App/AppStore/Fetcher/AppDiscoverFetcher.php +++ b/lib/private/App/AppStore/Fetcher/AppDiscoverFetcher.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php index 352646c3f5e..bbf4b00245b 100644 --- a/lib/private/App/AppStore/Fetcher/AppFetcher.php +++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -78,8 +79,8 @@ class AppFetcher extends Fetcher { $minServerVersion = $serverVersion->getMinimumVersion(); $maxServerVersion = $serverVersion->getMaximumVersion(); $minFulfilled = $this->compareVersion->isCompatible($ncVersion, $minServerVersion, '>='); - $maxFulfilled = $maxServerVersion !== '' && - $this->compareVersion->isCompatible($ncVersion, $maxServerVersion, '<='); + $maxFulfilled = $maxServerVersion !== '' + && $this->compareVersion->isCompatible($ncVersion, $maxServerVersion, '<='); $isPhpCompatible = true; if (($release['rawPhpVersionSpec'] ?? '*') !== '*') { $phpVersion = $versionParser->getVersion($release['rawPhpVersionSpec']); diff --git a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php index d72f8fa7e24..d7857d41bee 100644 --- a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php +++ b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php index f998c9e2023..2e949fedb51 100644 --- a/lib/private/App/AppStore/Fetcher/Fetcher.php +++ b/lib/private/App/AppStore/Fetcher/Fetcher.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Version/Version.php b/lib/private/App/AppStore/Version/Version.php index b7e679d250a..2d169a291f1 100644 --- a/lib/private/App/AppStore/Version/Version.php +++ b/lib/private/App/AppStore/Version/Version.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/AppStore/Version/VersionParser.php b/lib/private/App/AppStore/Version/VersionParser.php index 59715c8d385..8976f28837f 100644 --- a/lib/private/App/AppStore/Version/VersionParser.php +++ b/lib/private/App/AppStore/Version/VersionParser.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php index 72b38ca12c7..1e56612132b 100644 --- a/lib/private/App/DependencyAnalyzer.php +++ b/lib/private/App/DependencyAnalyzer.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php index 6610121f446..e7a75afdf0b 100644 --- a/lib/private/App/InfoParser.php +++ b/lib/private/App/InfoParser.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/App/Platform.php b/lib/private/App/Platform.php index c2c059220c8..80e4cefed64 100644 --- a/lib/private/App/Platform.php +++ b/lib/private/App/Platform.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/App/PlatformRepository.php b/lib/private/App/PlatformRepository.php index 6d9a9cd011a..faed8b07feb 100644 --- a/lib/private/App/PlatformRepository.php +++ b/lib/private/App/PlatformRepository.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index a8a6f689ffa..476adbb93e0 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -15,6 +15,7 @@ use NCU\Config\Lexicon\ConfigLexiconEntry; use NCU\Config\Lexicon\ConfigLexiconStrictness; use NCU\Config\Lexicon\IConfigLexicon; use OC\AppFramework\Bootstrap\Coordinator; +use OC\Config\ConfigManager; use OCP\DB\Exception as DBException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Exceptions\AppConfigIncorrectTypeException; @@ -24,6 +25,7 @@ use OCP\IAppConfig; use OCP\IConfig; use OCP\IDBConnection; use OCP\Security\ICrypto; +use OCP\Server; use Psr\Log\LoggerInterface; /** @@ -59,8 +61,9 @@ class AppConfig implements IAppConfig { private array $valueTypes = []; // type for all config values private bool $fastLoaded = false; private bool $lazyLoaded = false; - /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + /** @var array<string, array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ private array $configLexiconDetails = []; + private bool $ignoreLexiconAliases = false; /** @var ?array<string, string> */ private ?array $appVersionsCache = null; @@ -117,6 +120,7 @@ class AppConfig implements IAppConfig { public function hasKey(string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($app, $key); $this->loadConfig($app, $lazy); + $this->matchAndApplyLexiconDefinition($app, $key); if ($lazy === null) { $appCache = $this->getAllValues($app); @@ -142,6 +146,7 @@ class AppConfig implements IAppConfig { public function isSensitive(string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($app, $key); $this->loadConfig(null, $lazy); + $this->matchAndApplyLexiconDefinition($app, $key); if (!isset($this->valueTypes[$app][$key])) { throw new AppConfigUnknownKeyException('unknown config key'); @@ -162,6 +167,9 @@ class AppConfig implements IAppConfig { * @since 29.0.0 */ public function isLazy(string $app, string $key): bool { + $this->assertParams($app, $key); + $this->matchAndApplyLexiconDefinition($app, $key); + // there is a huge probability the non-lazy config are already loaded if ($this->hasKey($app, $key, false)) { return false; @@ -284,7 +292,7 @@ class AppConfig implements IAppConfig { ): string { try { $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy; - } catch (AppConfigUnknownKeyException $e) { + } catch (AppConfigUnknownKeyException) { return $default; } @@ -429,6 +437,7 @@ class AppConfig implements IAppConfig { int $type, ): string { $this->assertParams($app, $key, valueType: $type); + $origKey = $key; if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) { return $default; // returns default if strictness of lexicon is set to WARNING (block and report) } @@ -469,6 +478,14 @@ class AppConfig implements IAppConfig { $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH)); } + // in case the key was modified while running matchAndApplyLexiconDefinition() we are + // interested to check options in case a modification of the value is needed + // ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN + if ($origKey !== $key && $type === self::VALUE_BOOL) { + $configManager = Server::get(ConfigManager::class); + $value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0'; + } + return $value; } @@ -798,8 +815,8 @@ class AppConfig implements IAppConfig { * we only accept a different type from the one stored in database * if the one stored in database is not-defined (VALUE_MIXED) */ - if (!$this->isTyped(self::VALUE_MIXED, $currType) && - ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) { + if (!$this->isTyped(self::VALUE_MIXED, $currType) + && ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) { try { $currType = $this->convertTypeToString($currType); $type = $this->convertTypeToString($type); @@ -863,7 +880,8 @@ class AppConfig implements IAppConfig { public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool { $this->assertParams($app, $key); $this->loadConfigAll(); - $lazy = $this->isLazy($app, $key); + $this->matchAndApplyLexiconDefinition($app, $key); + $this->isLazy($app, $key); // confirm key exists // type can only be one type if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) { @@ -905,6 +923,7 @@ class AppConfig implements IAppConfig { public function updateSensitive(string $app, string $key, bool $sensitive): bool { $this->assertParams($app, $key); $this->loadConfigAll(); + $this->matchAndApplyLexiconDefinition($app, $key); try { if ($sensitive === $this->isSensitive($app, $key, null)) { @@ -964,6 +983,7 @@ class AppConfig implements IAppConfig { public function updateLazy(string $app, string $key, bool $lazy): bool { $this->assertParams($app, $key); $this->loadConfigAll(); + $this->matchAndApplyLexiconDefinition($app, $key); try { if ($lazy === $this->isLazy($app, $key)) { @@ -999,6 +1019,7 @@ class AppConfig implements IAppConfig { public function getDetails(string $app, string $key): array { $this->assertParams($app, $key); $this->loadConfigAll(); + $this->matchAndApplyLexiconDefinition($app, $key); $lazy = $this->isLazy($app, $key); if ($lazy) { @@ -1086,6 +1107,8 @@ class AppConfig implements IAppConfig { */ public function deleteKey(string $app, string $key): void { $this->assertParams($app, $key); + $this->matchAndApplyLexiconDefinition($app, $key); + $qb = $this->connection->getQueryBuilder(); $qb->delete('appconfig') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app))) @@ -1293,6 +1316,7 @@ class AppConfig implements IAppConfig { */ public function getValue($app, $key, $default = null) { $this->loadConfig($app); + $this->matchAndApplyLexiconDefinition($app, $key); return $this->fastCache[$app][$key] ?? $default; } @@ -1372,7 +1396,7 @@ class AppConfig implements IAppConfig { foreach ($values as $key => $value) { try { $type = $this->getValueType($app, $key, $lazy); - } catch (AppConfigUnknownKeyException $e) { + } catch (AppConfigUnknownKeyException) { continue; } @@ -1556,7 +1580,8 @@ class AppConfig implements IAppConfig { } /** - * match and apply current use of config values with defined lexicon + * Match and apply current use of config values with defined lexicon. + * Set $lazy to NULL only if only interested into checking that $key is alias. * * @throws AppConfigUnknownKeyException * @throws AppConfigTypeConflictException @@ -1564,9 +1589,9 @@ class AppConfig implements IAppConfig { */ private function matchAndApplyLexiconDefinition( string $app, - string $key, - bool &$lazy, - int &$type, + string &$key, + ?bool &$lazy = null, + int &$type = self::VALUE_MIXED, string &$default = '', ): bool { if (in_array($key, @@ -1578,11 +1603,18 @@ class AppConfig implements IAppConfig { return true; // we don't break stuff for this list of config keys. } $configDetails = $this->getConfigDetailsFromLexicon($app); + if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) { + // in case '$rename' is set in ConfigLexiconEntry, we use the new config key + $key = $configDetails['aliases'][$key]; + } + if (!array_key_exists($key, $configDetails['entries'])) { - return $this->applyLexiconStrictness( - $configDetails['strictness'], - 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon' - ); + return $this->applyLexiconStrictness($configDetails['strictness'], 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon'); + } + + // if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon + if ($lazy === null) { + return true; } /** @var ConfigLexiconEntry $configValue */ @@ -1644,20 +1676,25 @@ class AppConfig implements IAppConfig { * extract details from registered $appId's config lexicon * * @param string $appId + * @internal * - * @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness} + * @return array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness} */ - private function getConfigDetailsFromLexicon(string $appId): array { + public function getConfigDetailsFromLexicon(string $appId): array { if (!array_key_exists($appId, $this->configLexiconDetails)) { - $entries = []; + $entries = $aliases = []; $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) { $entries[$configEntry->getKey()] = $configEntry; + if ($configEntry->getRename() !== null) { + $aliases[$configEntry->getRename()] = $configEntry->getKey(); + } } $this->configLexiconDetails[$appId] = [ 'entries' => $entries, + 'aliases' => $aliases, 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE ]; } @@ -1665,16 +1702,36 @@ class AppConfig implements IAppConfig { return $this->configLexiconDetails[$appId]; } + private function getLexiconEntry(string $appId, string $key): ?ConfigLexiconEntry { + return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null; + } + + /** + * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class + * + * @internal + */ + public function ignoreLexiconAliases(bool $ignore): void { + $this->ignoreLexiconAliases = $ignore; + } + /** * Returns the installed versions of all apps * * @return array<string, string> */ - public function getAppInstalledVersions(): array { + public function getAppInstalledVersions(bool $onlyEnabled = false): array { if ($this->appVersionsCache === null) { /** @var array<string, string> */ $this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING); } + if ($onlyEnabled) { + return array_filter( + $this->appVersionsCache, + fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no', + ARRAY_FILTER_USE_KEY + ); + } return $this->appVersionsCache; } } diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 4e613703dec..64e3dbfd928 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -20,6 +20,7 @@ use OCP\Dashboard\IManager; use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; use OCP\IServerContainer; +use Psr\Container\ContainerExceptionInterface; use Psr\Log\LoggerInterface; use Throwable; use function class_exists; @@ -69,19 +70,24 @@ class Coordinator { */ try { $path = $this->appManager->getAppPath($appId); + OC_App::registerAutoloading($appId, $path); } catch (AppPathNotFoundException) { // Ignore continue; } - OC_App::registerAutoloading($appId, $path); $this->eventLogger->end("bootstrap:register_app:$appId:autoloader"); /* * Next we check if there is an application class, and it implements * the \OCP\AppFramework\Bootstrap\IBootstrap interface */ - $appNameSpace = App::buildAppNamespace($appId); + if ($appId === 'core') { + $appNameSpace = 'OC\\Core'; + } else { + $appNameSpace = App::buildAppNamespace($appId); + } $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; + try { if (class_exists($applicationClassName) && is_a($applicationClassName, IBootstrap::class, true)) { $this->eventLogger->start("bootstrap:register_app:$appId:application", "Load `Application` instance for $appId"); @@ -89,7 +95,7 @@ class Coordinator { /** @var IBootstrap&App $application */ $application = $this->serverContainer->query($applicationClassName); $apps[$appId] = $application; - } catch (QueryException $e) { + } catch (ContainerExceptionInterface $e) { // Weird, but ok $this->eventLogger->end("bootstrap:register_app:$appId"); continue; @@ -172,7 +178,7 @@ class Coordinator { public function isBootable(string $appId) { $appNameSpace = App::buildAppNamespace($appId); $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; - return class_exists($applicationClassName) && - in_array(IBootstrap::class, class_implements($applicationClassName), true); + return class_exists($applicationClassName) + && in_array(IBootstrap::class, class_implements($applicationClassName), true); } } diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index c3b829825c2..95ad129c466 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -157,7 +157,7 @@ class RegistrationContext { /** @var ServiceRegistration<\OCP\Files\Conversion\IConversionProvider>[] */ private array $fileConversionProviders = []; - + /** @var ServiceRegistration<IMailProvider>[] */ private $mailProviders = []; diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index b6e2df4ce7b..87361a9d1ea 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -440,7 +441,7 @@ class DIContainer extends SimpleContainer implements IAppContainer { return parent::query($name); } - throw new QueryException('Could not resolve ' . $name . '!' . - ' Class can not be instantiated', 1); + throw new QueryException('Could not resolve ' . $name . '!' + . ' Class can not be instantiated', 1); } } diff --git a/lib/private/AppFramework/Http.php b/lib/private/AppFramework/Http.php index 3c3e19692bf..08d6259c2a2 100644 --- a/lib/private/AppFramework/Http.php +++ b/lib/private/AppFramework/Http.php @@ -102,7 +102,7 @@ class Http extends BaseHttp { $status = self::STATUS_FOUND; } - return $this->protocolVersion . ' ' . $status . ' ' . - $this->headers[$status]; + return $this->protocolVersion . ' ' . $status . ' ' + . $this->headers[$status]; } } diff --git a/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php b/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php index c30855a0e98..e4571dfc50e 100644 --- a/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php +++ b/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php @@ -33,8 +33,8 @@ class FlowV2EphemeralSessionsMiddleware extends Middleware { } if ( - $controller instanceof ClientFlowLoginV2Controller && - ($methodName === 'grantPage' || $methodName === 'generateAppPassword') + $controller instanceof ClientFlowLoginV2Controller + && ($methodName === 'grantPage' || $methodName === 'generateAppPassword') ) { return; } diff --git a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php index 17b423164f6..08b30092155 100644 --- a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php +++ b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php @@ -29,7 +29,7 @@ class NotModifiedMiddleware extends Middleware { } $modifiedSinceHeader = $this->request->getHeader('IF_MODIFIED_SINCE'); - if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTimeInterface::RFC2822)) { + if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTimeInterface::RFC7231)) { $response->setStatus(Http::STATUS_NOT_MODIFIED); return $response; } diff --git a/lib/private/AppFramework/Middleware/OCSMiddleware.php b/lib/private/AppFramework/Middleware/OCSMiddleware.php index 46612bf0d29..64f4b0054de 100644 --- a/lib/private/AppFramework/Middleware/OCSMiddleware.php +++ b/lib/private/AppFramework/Middleware/OCSMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php index c80d06c90ae..5df4009b094 100644 --- a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php +++ b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php index b3040673d0f..83e799e3d3b 100644 --- a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php +++ b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php index 40af67739d6..4453f5a7d4b 100644 --- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -68,8 +68,8 @@ class CORSMiddleware extends Middleware { // ensure that @CORS annotated API routes are not used in conjunction // with session authentication since this enables CSRF attack vectors - if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) && - (!$this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { + if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) + && (!$this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { $user = array_key_exists('PHP_AUTH_USER', $this->request->server) ? $this->request->server['PHP_AUTH_USER'] : null; $pass = array_key_exists('PHP_AUTH_PW', $this->request->server) ? $this->request->server['PHP_AUTH_PW'] : null; @@ -134,10 +134,10 @@ class CORSMiddleware extends Middleware { // allow credentials headers must not be true or CSRF is possible // otherwise foreach ($response->getHeaders() as $header => $value) { - if (strtolower($header) === 'access-control-allow-credentials' && - strtolower(trim($value)) === 'true') { - $msg = 'Access-Control-Allow-Credentials must not be ' . - 'set to true in order to prevent CSRF'; + if (strtolower($header) === 'access-control-allow-credentials' + && strtolower(trim($value)) === 'true') { + $msg = 'Access-Control-Allow-Credentials must not be ' + . 'set to true in order to prevent CSRF'; throw new SecurityException($msg); } } diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php index 646a5240bfc..53fbaaf5ed2 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php index 91f1dba874d..0380c6781aa 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php index 7e950f2c976..ca30f736fbc 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -14,7 +15,7 @@ use OCP\AppFramework\Http; * @package OC\AppFramework\Middleware\Security\Exceptions */ class NotConfirmedException extends SecurityException { - public function __construct() { - parent::__construct('Password confirmation is required', Http::STATUS_FORBIDDEN); + public function __construct(string $message = 'Password confirmation is required') { + parent::__construct($message, Http::STATUS_FORBIDDEN); } } diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php index d00840084a3..0facbffe504 100644 --- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -79,6 +80,9 @@ class PasswordConfirmationMiddleware extends Middleware { if ($this->isPasswordConfirmationStrict($reflectionMethod)) { $authHeader = $this->request->getHeader('Authorization'); + if (!str_starts_with(strtolower($authHeader), 'basic ')) { + throw new NotConfirmedException('Required authorization header missing'); + } [, $password] = explode(':', base64_decode(substr($authHeader, 6)), 2); $loginName = $this->session->get('loginname'); $loginResult = $this->userManager->checkPassword($loginName, $password); diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php index efe56e0b124..ed3bb232023 100644 --- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 6b054b03f44..e3a293e0fd9 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -184,8 +184,8 @@ class SecurityMiddleware extends Middleware { } // Check for strict cookie requirement - if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) || - !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { + if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) + || !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { if (!$this->request->passesStrictCookieCheck()) { throw new StrictCookieMissingException(); } diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php index 5929a3993ec..05ce133db24 100644 --- a/lib/private/AppFramework/OCS/BaseResponse.php +++ b/lib/private/AppFramework/OCS/BaseResponse.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -83,9 +84,9 @@ abstract class BaseResponse extends Response { */ protected function renderResult(array $meta): string { $status = $this->getStatus(); - if ($status === Http::STATUS_NO_CONTENT || - $status === Http::STATUS_NOT_MODIFIED || - ($status >= 100 && $status <= 199)) { + if ($status === Http::STATUS_NO_CONTENT + || $status === Http::STATUS_NOT_MODIFIED + || ($status >= 100 && $status <= 199)) { // Those status codes are not supposed to have a body: // https://stackoverflow.com/q/8628725 return ''; diff --git a/lib/private/AppFramework/OCS/V1Response.php b/lib/private/AppFramework/OCS/V1Response.php index 131ca22ff24..1c2c25f5cb0 100644 --- a/lib/private/AppFramework/OCS/V1Response.php +++ b/lib/private/AppFramework/OCS/V1Response.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/OCS/V2Response.php b/lib/private/AppFramework/OCS/V2Response.php index 47cf0f60200..efc9348eb37 100644 --- a/lib/private/AppFramework/OCS/V2Response.php +++ b/lib/private/AppFramework/OCS/V2Response.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Services/AppConfig.php b/lib/private/AppFramework/Services/AppConfig.php index 77c5ea4de0c..04d97738483 100644 --- a/lib/private/AppFramework/Services/AppConfig.php +++ b/lib/private/AppFramework/Services/AppConfig.php @@ -343,7 +343,7 @@ class AppConfig implements IAppConfig { * * @return array<string, string> */ - public function getAppInstalledVersions(): array { - return $this->appConfig->getAppInstalledVersions(); + public function getAppInstalledVersions(bool $onlyEnabled = false): array { + return $this->appConfig->getAppInstalledVersions($onlyEnabled); } } diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 9af65a37ab8..1d77c277b02 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -12,6 +12,7 @@ use Closure; use OCP\AppFramework\QueryException; use OCP\IContainer; use Pimple\Container; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use ReflectionClass; use ReflectionException; @@ -23,8 +24,9 @@ use function class_exists; * SimpleContainer is a simple implementation of a container on basis of Pimple */ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { - /** @var Container */ - private $container; + public static bool $useLazyObjects = false; + + private Container $container; public function __construct() { $this->container = new Container(); @@ -49,16 +51,29 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { /** * @param ReflectionClass $class the class to instantiate - * @return \stdClass the created class + * @return object the created class * @suppress PhanUndeclaredClassInstanceof */ - private function buildClass(ReflectionClass $class) { + private function buildClass(ReflectionClass $class): object { $constructor = $class->getConstructor(); if ($constructor === null) { + /* No constructor, return a instance directly */ return $class->newInstance(); } + if (PHP_VERSION_ID >= 80400 && self::$useLazyObjects) { + /* For PHP>=8.4, use a lazy ghost to delay constructor and dependency resolving */ + /** @psalm-suppress UndefinedMethod */ + return $class->newLazyGhost(function (object $object) use ($constructor): void { + /** @psalm-suppress DirectConstructorCall For lazy ghosts we have to call the constructor directly */ + $object->__construct(...$this->buildClassConstructorParameters($constructor)); + }); + } else { + return $class->newInstanceArgs($this->buildClassConstructorParameters($constructor)); + } + } - return $class->newInstanceArgs(array_map(function (ReflectionParameter $parameter) { + private function buildClassConstructorParameters(\ReflectionMethod $constructor): array { + return array_map(function (ReflectionParameter $parameter) { $parameterType = $parameter->getType(); $resolveName = $parameter->getName(); @@ -69,10 +84,10 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { } try { - $builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType) - && $parameter->getType()->isBuiltin(); + $builtIn = $parameterType !== null && ($parameterType instanceof ReflectionNamedType) + && $parameterType->isBuiltin(); return $this->query($resolveName, !$builtIn); - } catch (QueryException $e) { + } catch (ContainerExceptionInterface $e) { // Service not found, use the default value when available if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); @@ -82,7 +97,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { $resolveName = $parameter->getName(); try { return $this->query($resolveName); - } catch (QueryException $e2) { + } catch (ContainerExceptionInterface $e2) { // Pass null if typed and nullable if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) { return null; @@ -95,7 +110,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { throw $e; } - }, $constructor->getParameters())); + }, $constructor->getParameters()); } public function resolve($name) { @@ -105,8 +120,8 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { if ($class->isInstantiable()) { return $this->buildClass($class); } else { - throw new QueryException($baseMsg . - ' Class can not be instantiated'); + throw new QueryException($baseMsg + . ' Class can not be instantiated'); } } catch (ReflectionException $e) { // Class does not exist diff --git a/lib/private/AppScriptDependency.php b/lib/private/AppScriptDependency.php index 380816f21ac..cc5ded0d011 100644 --- a/lib/private/AppScriptDependency.php +++ b/lib/private/AppScriptDependency.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppScriptSort.php b/lib/private/AppScriptSort.php index eda41e97445..134dad100dc 100644 --- a/lib/private/AppScriptSort.php +++ b/lib/private/AppScriptSort.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Authentication/LoginCredentials/Credentials.php b/lib/private/Authentication/LoginCredentials/Credentials.php index 2d7ed3adfd0..3414034b33c 100644 --- a/lib/private/Authentication/LoginCredentials/Credentials.php +++ b/lib/private/Authentication/LoginCredentials/Credentials.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Authentication/Token/TokenCleanupJob.php b/lib/private/Authentication/Token/TokenCleanupJob.php index 041d2e8a5e2..e6d1e69e9b4 100644 --- a/lib/private/Authentication/Token/TokenCleanupJob.php +++ b/lib/private/Authentication/Token/TokenCleanupJob.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-only diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php index 1b22300e317..07aa98610ed 100644 --- a/lib/private/Authentication/TwoFactorAuth/Manager.php +++ b/lib/private/Authentication/TwoFactorAuth/Manager.php @@ -308,8 +308,8 @@ class Manager { // First check if the session tells us we should do 2FA (99% case) if (!$this->session->exists(self::SESSION_UID_KEY)) { // Check if the session tells us it is 2FA authenticated already - if ($this->session->exists(self::SESSION_UID_DONE) && - $this->session->get(self::SESSION_UID_DONE) === $user->getUID()) { + if ($this->session->exists(self::SESSION_UID_DONE) + && $this->session->get(self::SESSION_UID_DONE) === $user->getUID()) { return false; } diff --git a/lib/private/Avatar/UserAvatar.php b/lib/private/Avatar/UserAvatar.php index 51c44b23a55..bef0a20e7b8 100644 --- a/lib/private/Avatar/UserAvatar.php +++ b/lib/private/Avatar/UserAvatar.php @@ -82,8 +82,8 @@ class UserAvatar extends Avatar { $img = new \OCP\Image(); if ( - (is_resource($data) && get_resource_type($data) === 'gd') || - (is_object($data) && get_class($data) === \GdImage::class) + (is_resource($data) && get_resource_type($data) === 'gd') + || (is_object($data) && get_class($data) === \GdImage::class) ) { $img->setResource($data); } elseif (is_resource($data)) { diff --git a/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php b/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php index 982693bcfe8..8faf4627251 100644 --- a/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php +++ b/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php @@ -68,6 +68,11 @@ class GenerateBlurhashMetadata implements IEventListener { return; } + // Preview are disabled, so we skip generating the blurhash. + if (!$this->preview->isAvailable($file)) { + return; + } + $preview = $this->preview->getPreview($file, 64, 64, cacheResult: false); $image = @imagecreatefromstring($preview->getContent()); diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php index 21370e74d54..0e2a3f5f679 100644 --- a/lib/private/Calendar/Manager.php +++ b/lib/private/Calendar/Manager.php @@ -236,7 +236,7 @@ class Manager implements IManager { $this->logger->warning('iMip message could not be processed because user has no calendars'); return false; } - + try { /** @var VCalendar $vObject|null */ $calendarObject = Reader::read($calendarData); diff --git a/lib/private/Collaboration/AutoComplete/Manager.php b/lib/private/Collaboration/AutoComplete/Manager.php index c4dd4093e69..cc5df78beea 100644 --- a/lib/private/Collaboration/AutoComplete/Manager.php +++ b/lib/private/Collaboration/AutoComplete/Manager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php index a7b84b72199..a59d5981825 100644 --- a/lib/private/Collaboration/Collaborators/GroupPlugin.php +++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Collaboration/Collaborators/LookupPlugin.php b/lib/private/Collaboration/Collaborators/LookupPlugin.php index 2c8dc845458..fb6b9f2e0e8 100644 --- a/lib/private/Collaboration/Collaborators/LookupPlugin.php +++ b/lib/private/Collaboration/Collaborators/LookupPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index 278ba19aa5a..55e3945ace2 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -228,8 +229,8 @@ class MailPlugin implements ISearchPlugin { $reachedEnd = true; if ($this->shareeEnumeration) { - $reachedEnd = (count($result['wide']) < $offset + $limit) && - (count($userResults['wide']) < $offset + $limit); + $reachedEnd = (count($result['wide']) < $offset + $limit) + && (count($userResults['wide']) < $offset + $limit); $result['wide'] = array_slice($result['wide'], $offset, $limit); $userResults['wide'] = array_slice($userResults['wide'], $offset, $limit); diff --git a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php index 89d5c4e4f79..f4c1793ea0a 100644 --- a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php +++ b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php index 788ece70cb9..037c6f6cbea 100644 --- a/lib/private/Collaboration/Collaborators/RemotePlugin.php +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Collaboration/Collaborators/Search.php b/lib/private/Collaboration/Collaborators/Search.php index 78b57b52400..32c70549a7b 100644 --- a/lib/private/Collaboration/Collaborators/Search.php +++ b/lib/private/Collaboration/Collaborators/Search.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Collaboration/Collaborators/SearchResult.php b/lib/private/Collaboration/Collaborators/SearchResult.php index 73c0fed41e0..c9c2f032f36 100644 --- a/lib/private/Collaboration/Collaborators/SearchResult.php +++ b/lib/private/Collaboration/Collaborators/SearchResult.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index d196abae042..671181aea35 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -147,11 +148,11 @@ class UserPlugin implements ISearchPlugin { if ( - $this->shareeEnumerationFullMatch && - $lowerSearch !== '' && (strtolower($uid) === $lowerSearch || - strtolower($userDisplayName) === $lowerSearch || - ($this->shareeEnumerationFullMatchIgnoreSecondDisplayName && trim(strtolower(preg_replace('/ \(.*\)$/', '', $userDisplayName))) === $lowerSearch) || - ($this->shareeEnumerationFullMatchEmail && strtolower($userEmail ?? '') === $lowerSearch)) + $this->shareeEnumerationFullMatch + && $lowerSearch !== '' && (strtolower($uid) === $lowerSearch + || strtolower($userDisplayName) === $lowerSearch + || ($this->shareeEnumerationFullMatchIgnoreSecondDisplayName && trim(strtolower(preg_replace('/ \(.*\)$/', '', $userDisplayName))) === $lowerSearch) + || ($this->shareeEnumerationFullMatchEmail && strtolower($userEmail ?? '') === $lowerSearch)) ) { if (strtolower($uid) === $lowerSearch) { $foundUserById = true; @@ -169,8 +170,8 @@ class UserPlugin implements ISearchPlugin { ]; } else { $addToWideResults = false; - if ($this->shareeEnumeration && - !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone)) { + if ($this->shareeEnumeration + && !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone)) { $addToWideResults = true; } diff --git a/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php b/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php index e468ad4eb4c..9c18531c8e7 100644 --- a/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php +++ b/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php @@ -15,6 +15,7 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; use OCP\Files\Events\Node\NodeDeletedEvent; +use OCP\Files\Events\Node\NodeRenamedEvent; use OCP\Share\Events\ShareCreatedEvent; use OCP\Share\Events\ShareDeletedEvent; @@ -27,6 +28,7 @@ class FileReferenceEventListener implements IEventListener { public static function register(IEventDispatcher $eventDispatcher): void { $eventDispatcher->addServiceListener(NodeDeletedEvent::class, FileReferenceEventListener::class); + $eventDispatcher->addServiceListener(NodeRenamedEvent::class, FileReferenceEventListener::class); $eventDispatcher->addServiceListener(ShareDeletedEvent::class, FileReferenceEventListener::class); $eventDispatcher->addServiceListener(ShareCreatedEvent::class, FileReferenceEventListener::class); } @@ -42,6 +44,9 @@ class FileReferenceEventListener implements IEventListener { $this->manager->invalidateCache((string)$event->getNode()->getId()); } + if ($event instanceof NodeRenamedEvent) { + $this->manager->invalidateCache((string)$event->getTarget()->getId()); + } if ($event instanceof ShareDeletedEvent) { $this->manager->invalidateCache((string)$event->getShare()->getNodeId()); } diff --git a/lib/private/Collaboration/Resources/Collection.php b/lib/private/Collaboration/Resources/Collection.php index cf5f7740ced..2481a3e9a09 100644 --- a/lib/private/Collaboration/Resources/Collection.php +++ b/lib/private/Collaboration/Resources/Collection.php @@ -164,8 +164,8 @@ class Collection implements ICollection { } protected function isSameResource(IResource $resource1, IResource $resource2): bool { - return $resource1->getType() === $resource2->getType() && - $resource1->getId() === $resource2->getId(); + return $resource1->getType() === $resource2->getType() + && $resource1->getId() === $resource2->getId(); } protected function removeCollection(): void { diff --git a/lib/private/Color.php b/lib/private/Color.php index e96d6fd23bd..d97c519e552 100644 --- a/lib/private/Color.php +++ b/lib/private/Color.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Command/CronBus.php b/lib/private/Command/CronBus.php index 1ff9bb7099a..a12520469a9 100644 --- a/lib/private/Command/CronBus.php +++ b/lib/private/Command/CronBus.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Config/ConfigManager.php b/lib/private/Config/ConfigManager.php new file mode 100644 index 00000000000..1980269e2ca --- /dev/null +++ b/lib/private/Config/ConfigManager.php @@ -0,0 +1,250 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Config; + +use JsonException; +use NCU\Config\Exceptions\TypeConflictException; +use NCU\Config\IUserConfig; +use NCU\Config\Lexicon\ConfigLexiconEntry; +use NCU\Config\ValueType; +use OC\AppConfig; +use OCP\App\IAppManager; +use OCP\IAppConfig; +use OCP\Server; +use Psr\Log\LoggerInterface; + +/** + * tools to maintains configurations + * + * @since 32.0.0 + */ +class ConfigManager { + /** @var AppConfig|null $appConfig */ + private ?IAppConfig $appConfig = null; + /** @var UserConfig|null $userConfig */ + private ?IUserConfig $userConfig = null; + + public function __construct( + private readonly LoggerInterface $logger, + ) { + } + + /** + * Use the rename values from the list of ConfigLexiconEntry defined in each app ConfigLexicon + * to migrate config value to a new config key. + * Migration will only occur if new config key has no value in database. + * The previous value from the key set in rename will be deleted from the database when migration + * is over. + * + * This method should be mainly called during a new upgrade or when a new app is enabled. + * + * @see ConfigLexiconEntry + * @internal + * @since 32.0.0 + * @param string|null $appId when set to NULL the method will be executed for all enabled apps of the instance + */ + public function migrateConfigLexiconKeys(?string $appId = null): void { + if ($appId === null) { + $this->migrateConfigLexiconKeys('core'); + $appManager = Server::get(IAppManager::class); + foreach ($appManager->getEnabledApps() as $app) { + $this->migrateConfigLexiconKeys($app); + } + + return; + } + + $this->loadConfigServices(); + + // it is required to ignore aliases when moving config values + $this->appConfig->ignoreLexiconAliases(true); + $this->userConfig->ignoreLexiconAliases(true); + + $this->migrateAppConfigKeys($appId); + $this->migrateUserConfigKeys($appId); + + // switch back to normal behavior + $this->appConfig->ignoreLexiconAliases(false); + $this->userConfig->ignoreLexiconAliases(false); + } + + /** + * config services cannot be load at __construct() or install will fail + */ + private function loadConfigServices(): void { + if ($this->appConfig === null) { + $this->appConfig = Server::get(IAppConfig::class); + } + if ($this->userConfig === null) { + $this->userConfig = Server::get(IUserConfig::class); + } + } + + /** + * Get details from lexicon related to AppConfig and search for entries with rename to initiate + * a migration to new config key + */ + private function migrateAppConfigKeys(string $appId): void { + $lexicon = $this->appConfig->getConfigDetailsFromLexicon($appId); + foreach ($lexicon['entries'] as $entry) { + // only interested in entries with rename set + if ($entry->getRename() === null) { + continue; + } + + // only migrate if rename config key has a value and the new config key hasn't + if ($this->appConfig->hasKey($appId, $entry->getRename()) + && !$this->appConfig->hasKey($appId, $entry->getKey())) { + try { + $this->migrateAppConfigValue($appId, $entry); + } catch (TypeConflictException $e) { + $this->logger->error('could not migrate AppConfig value', ['appId' => $appId, 'entry' => $entry, 'exception' => $e]); + continue; + } + } + + // we only delete previous config value if migration went fine. + $this->appConfig->deleteKey($appId, $entry->getRename()); + } + } + + /** + * Get details from lexicon related to UserConfig and search for entries with rename to initiate + * a migration to new config key + */ + private function migrateUserConfigKeys(string $appId): void { + $lexicon = $this->userConfig->getConfigDetailsFromLexicon($appId); + foreach ($lexicon['entries'] as $entry) { + // only interested in keys with rename set + if ($entry->getRename() === null) { + continue; + } + + foreach ($this->userConfig->getValuesByUsers($appId, $entry->getRename()) as $userId => $value) { + if ($this->userConfig->hasKey($userId, $appId, $entry->getKey())) { + continue; + } + + try { + $this->migrateUserConfigValue($userId, $appId, $entry); + } catch (TypeConflictException $e) { + $this->logger->error('could not migrate UserConfig value', ['userId' => $userId, 'appId' => $appId, 'entry' => $entry, 'exception' => $e]); + continue; + } + + $this->userConfig->deleteUserConfig($userId, $appId, $entry->getRename()); + } + } + } + + + /** + * converting value from rename to the new key + * + * @throws TypeConflictException if previous value does not fit the expected type + */ + private function migrateAppConfigValue(string $appId, ConfigLexiconEntry $entry): void { + $value = $this->appConfig->getValueMixed($appId, $entry->getRename(), lazy: null); + switch ($entry->getValueType()) { + case ValueType::STRING: + $this->appConfig->setValueString($appId, $entry->getKey(), $value); + return; + + case ValueType::INT: + $this->appConfig->setValueInt($appId, $entry->getKey(), $this->convertToInt($value)); + return; + + case ValueType::FLOAT: + $this->appConfig->setValueFloat($appId, $entry->getKey(), $this->convertToFloat($value)); + return; + + case ValueType::BOOL: + $this->appConfig->setValueBool($appId, $entry->getKey(), $this->convertToBool($value, $entry)); + return; + + case ValueType::ARRAY: + $this->appConfig->setValueArray($appId, $entry->getKey(), $this->convertToArray($value)); + return; + } + } + + /** + * converting value from rename to the new key + * + * @throws TypeConflictException if previous value does not fit the expected type + */ + private function migrateUserConfigValue(string $userId, string $appId, ConfigLexiconEntry $entry): void { + $value = $this->userConfig->getValueMixed($userId, $appId, $entry->getRename(), lazy: null); + switch ($entry->getValueType()) { + case ValueType::STRING: + $this->userConfig->setValueString($userId, $appId, $entry->getKey(), $value); + return; + + case ValueType::INT: + $this->userConfig->setValueInt($userId, $appId, $entry->getKey(), $this->convertToInt($value)); + return; + + case ValueType::FLOAT: + $this->userConfig->setValueFloat($userId, $appId, $entry->getKey(), $this->convertToFloat($value)); + return; + + case ValueType::BOOL: + $this->userConfig->setValueBool($userId, $appId, $entry->getKey(), $this->convertToBool($value, $entry)); + return; + + case ValueType::ARRAY: + $this->userConfig->setValueArray($userId, $appId, $entry->getKey(), $this->convertToArray($value)); + return; + } + } + + public function convertToInt(string $value): int { + if (!is_numeric($value) || (float)$value <> (int)$value) { + throw new TypeConflictException('Value is not an integer'); + } + + return (int)$value; + } + + public function convertToFloat(string $value): float { + if (!is_numeric($value)) { + throw new TypeConflictException('Value is not a float'); + } + + return (float)$value; + } + + public function convertToBool(string $value, ?ConfigLexiconEntry $entry = null): bool { + if (in_array(strtolower($value), ['true', '1', 'on', 'yes'])) { + $valueBool = true; + } elseif (in_array(strtolower($value), ['false', '0', 'off', 'no'])) { + $valueBool = false; + } else { + throw new TypeConflictException('Value cannot be converted to boolean'); + } + if ($entry?->hasOption(ConfigLexiconEntry::RENAME_INVERT_BOOLEAN) === true) { + $valueBool = !$valueBool; + } + + return $valueBool; + } + + public function convertToArray(string $value): array { + try { + $valueArray = json_decode($value, true, flags: JSON_THROW_ON_ERROR); + } catch (JsonException) { + throw new TypeConflictException('Value is not a valid json'); + } + if (!is_array($valueArray)) { + throw new TypeConflictException('Value is not an array'); + } + + return $valueArray; + } +} diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index 77a86a5e1c7..7848f1728e3 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -25,6 +25,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; use OCP\Security\ICrypto; +use OCP\Server; use Psr\Log\LoggerInterface; /** @@ -62,8 +63,9 @@ class UserConfig implements IUserConfig { private array $fastLoaded = []; /** @var array<string, boolean> ['user_id' => bool] */ private array $lazyLoaded = []; - /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + /** @var array<string, array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ private array $configLexiconDetails = []; + private bool $ignoreLexiconAliases = false; public function __construct( protected IDBConnection $connection, @@ -150,6 +152,7 @@ class UserConfig implements IUserConfig { public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if ($lazy === null) { $appCache = $this->getValues($userId, $app); @@ -178,6 +181,7 @@ class UserConfig implements IUserConfig { public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key])) { throw new UnknownKeyException('unknown config key'); @@ -201,6 +205,7 @@ class UserConfig implements IUserConfig { public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key])) { throw new UnknownKeyException('unknown config key'); @@ -222,6 +227,8 @@ class UserConfig implements IUserConfig { * @since 31.0.0 */ public function isLazy(string $userId, string $app, string $key): bool { + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + // there is a huge probability the non-lazy config are already loaded // meaning that we can start by only checking if a current non-lazy key exists if ($this->hasKey($userId, $app, $key, false)) { @@ -349,6 +356,7 @@ class UserConfig implements IUserConfig { ?array $userIds = null, ): array { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); $qb = $this->connection->getQueryBuilder(); $qb->select('userid', 'configvalue', 'type') @@ -464,6 +472,7 @@ class UserConfig implements IUserConfig { */ private function searchUsersByTypedValue(string $app, string $key, string|array $value, bool $caseInsensitive = false): Generator { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); $qb = $this->connection->getQueryBuilder(); $qb->from('preferences'); @@ -541,6 +550,7 @@ class UserConfig implements IUserConfig { string $default = '', ?bool $lazy = false, ): string { + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { $lazy ??= $this->isLazy($userId, $app, $key); } catch (UnknownKeyException) { @@ -710,6 +720,7 @@ class UserConfig implements IUserConfig { ValueType $type, ): string { $this->assertParams($userId, $app, $key); + $origKey = $key; if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, default: $default)) { // returns default if strictness of lexicon is set to WARNING (block and report) return $default; @@ -746,6 +757,15 @@ class UserConfig implements IUserConfig { } $this->decryptSensitiveValue($userId, $app, $key, $value); + + // in case the key was modified while running matchAndApplyLexiconDefinition() we are + // interested to check options in case a modification of the value is needed + // ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN + if ($origKey !== $key && $type === ValueType::BOOL) { + $configManager = Server::get(ConfigManager::class); + $value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0'; + } + return $value; } @@ -764,6 +784,7 @@ class UserConfig implements IUserConfig { public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key]['type'])) { throw new UnknownKeyException('unknown config key'); @@ -788,6 +809,7 @@ class UserConfig implements IUserConfig { public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int { $this->assertParams($userId, $app, $key); $this->loadConfig($userId, $lazy); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); if (!isset($this->valueDetails[$userId][$app][$key])) { throw new UnknownKeyException('unknown config key'); @@ -1045,6 +1067,11 @@ class UserConfig implements IUserConfig { int $flags, ValueType $type, ): bool { + // Primary email addresses are always(!) expected to be lowercase + if ($app === 'settings' && $key === 'email') { + $value = strtolower($value); + } + $this->assertParams($userId, $app, $key); if (!$this->matchAndApplyLexiconDefinition($userId, $app, $key, $lazy, $type, $flags)) { // returns false as database is not updated @@ -1129,8 +1156,8 @@ class UserConfig implements IUserConfig { * we only accept a different type from the one stored in database * if the one stored in database is not-defined (VALUE_MIXED) */ - if ($currType !== ValueType::MIXED && - $currType !== $type) { + if ($currType !== ValueType::MIXED + && $currType !== $type) { try { $currTypeDef = $currType->getDefinition(); $typeDef = $type->getDefinition(); @@ -1197,8 +1224,8 @@ class UserConfig implements IUserConfig { public function updateType(string $userId, string $app, string $key, ValueType $type = ValueType::MIXED): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); - // confirm key exists - $this->isLazy($userId, $app, $key); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + $this->isLazy($userId, $app, $key); // confirm key exists $update = $this->connection->getQueryBuilder(); $update->update('preferences') @@ -1227,6 +1254,7 @@ class UserConfig implements IUserConfig { public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { if ($sensitive === $this->isSensitive($userId, $app, $key, null)) { @@ -1282,6 +1310,8 @@ class UserConfig implements IUserConfig { */ public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); + foreach (array_keys($this->getValuesByUsers($app, $key)) as $userId) { try { $this->updateSensitive($userId, $app, $key, $sensitive); @@ -1311,6 +1341,7 @@ class UserConfig implements IUserConfig { public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { if ($indexed === $this->isIndexed($userId, $app, $key, null)) { @@ -1366,6 +1397,8 @@ class UserConfig implements IUserConfig { */ public function updateGlobalIndexed(string $app, string $key, bool $indexed): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); + foreach (array_keys($this->getValuesByUsers($app, $key)) as $userId) { try { $this->updateIndexed($userId, $app, $key, $indexed); @@ -1392,6 +1425,7 @@ class UserConfig implements IUserConfig { public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); try { if ($lazy === $this->isLazy($userId, $app, $key)) { @@ -1426,6 +1460,7 @@ class UserConfig implements IUserConfig { */ public function updateGlobalLazy(string $app, string $key, bool $lazy): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); $update = $this->connection->getQueryBuilder(); $update->update('preferences') @@ -1451,6 +1486,8 @@ class UserConfig implements IUserConfig { public function getDetails(string $userId, string $app, string $key): array { $this->assertParams($userId, $app, $key); $this->loadConfigAll($userId); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + $lazy = $this->isLazy($userId, $app, $key); if ($lazy) { @@ -1498,6 +1535,8 @@ class UserConfig implements IUserConfig { */ public function deleteUserConfig(string $userId, string $app, string $key): void { $this->assertParams($userId, $app, $key); + $this->matchAndApplyLexiconDefinition($userId, $app, $key); + $qb = $this->connection->getQueryBuilder(); $qb->delete('preferences') ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId))) @@ -1520,6 +1559,8 @@ class UserConfig implements IUserConfig { */ public function deleteKey(string $app, string $key): void { $this->assertParams('', $app, $key, allowEmptyUser: true); + $this->matchAndApplyLexiconDefinition('', $app, $key); + $qb = $this->connection->getQueryBuilder(); $qb->delete('preferences') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app))) @@ -1538,6 +1579,7 @@ class UserConfig implements IUserConfig { */ public function deleteApp(string $app): void { $this->assertParams('', $app, allowEmptyUser: true); + $qb = $this->connection->getQueryBuilder(); $qb->delete('preferences') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app))); @@ -1830,7 +1872,8 @@ class UserConfig implements IUserConfig { } /** - * match and apply current use of config values with defined lexicon + * Match and apply current use of config values with defined lexicon. + * Set $lazy to NULL only if only interested into checking that $key is alias. * * @throws UnknownKeyException * @throws TypeConflictException @@ -1839,17 +1882,27 @@ class UserConfig implements IUserConfig { private function matchAndApplyLexiconDefinition( string $userId, string $app, - string $key, - bool &$lazy, - ValueType &$type, + string &$key, + ?bool &$lazy = null, + ValueType &$type = ValueType::MIXED, int &$flags = 0, string &$default = '', ): bool { $configDetails = $this->getConfigDetailsFromLexicon($app); + if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) { + // in case '$rename' is set in ConfigLexiconEntry, we use the new config key + $key = $configDetails['aliases'][$key]; + } + if (!array_key_exists($key, $configDetails['entries'])) { return $this->applyLexiconStrictness($configDetails['strictness'], 'The user config key ' . $app . '/' . $key . ' is not defined in the config lexicon'); } + // if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon + if ($lazy === null) { + return true; + } + /** @var ConfigLexiconEntry $configValue */ $configValue = $configDetails['entries'][$key]; if ($type === ValueType::MIXED) { @@ -1934,24 +1987,42 @@ class UserConfig implements IUserConfig { * extract details from registered $appId's config lexicon * * @param string $appId + * @internal * - * @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness} + * @return array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness} */ - private function getConfigDetailsFromLexicon(string $appId): array { + public function getConfigDetailsFromLexicon(string $appId): array { if (!array_key_exists($appId, $this->configLexiconDetails)) { - $entries = []; + $entries = $aliases = []; $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); foreach ($configLexicon?->getUserConfigs() ?? [] as $configEntry) { $entries[$configEntry->getKey()] = $configEntry; + if ($configEntry->getRename() !== null) { + $aliases[$configEntry->getRename()] = $configEntry->getKey(); + } } $this->configLexiconDetails[$appId] = [ 'entries' => $entries, + 'aliases' => $aliases, 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE ]; } return $this->configLexiconDetails[$appId]; } + + private function getLexiconEntry(string $appId, string $key): ?ConfigLexiconEntry { + return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null; + } + + /** + * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class + * + * @internal + */ + public function ignoreLexiconAliases(bool $ignore): void { + $this->ignoreLexiconAliases = $ignore; + } } diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index f896c0abebe..4cf1e0da8ca 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -74,8 +74,8 @@ class Application { if ($this->memoryInfo->isMemoryLimitSufficient() === false) { $output->getErrorOutput()->writeln( - '<comment>The current PHP memory limit ' . - 'is below the recommended value of 512MB.</comment>' + '<comment>The current PHP memory limit ' + . 'is below the recommended value of 512MB.</comment>' ); } diff --git a/lib/private/Contacts/ContactsMenu/ActionFactory.php b/lib/private/Contacts/ContactsMenu/ActionFactory.php index 71ebe575fdd..40037598d49 100644 --- a/lib/private/Contacts/ContactsMenu/ActionFactory.php +++ b/lib/private/Contacts/ContactsMenu/ActionFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php b/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php index 0d4cc9b9b01..cdaf9308bfc 100644 --- a/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php +++ b/lib/private/Contacts/ContactsMenu/Actions/LinkAction.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php index 65a2c4469ea..f8def45421b 100644 --- a/lib/private/Contacts/ContactsMenu/Manager.php +++ b/lib/private/Contacts/ContactsMenu/Manager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php b/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php index c852fc90b9e..266125f5ed5 100644 --- a/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php +++ b/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/DB/Adapter.php b/lib/private/DB/Adapter.php index edd8c1bf023..8f1b8e6d75f 100644 --- a/lib/private/DB/Adapter.php +++ b/lib/private/DB/Adapter.php @@ -28,25 +28,11 @@ class Adapter { /** * @param string $table name * - * @return int id of last insert statement, 0 in case there was no INSERT before or it failed to get the ID + * @return int id of last insert statement * @throws Exception */ - public function lastInsertId($table, bool $allowRetry = true): int { - $return = $this->conn->realLastInsertId($table); - if ($return === 0 && $allowRetry) { - /** - * During a reconnect we are losing the connection and when the - * realLastInsertId call is the one triggering the reconnect, it - * does not return the ID. But inside the reconnect, we were able - * to save the last insert id, so calling it a second time is going - * to be successful. - * We can not return the result on the initial call, as we are already - * way deeper in the stack performing the actual database query on - * the doctrine driver. - */ - return $this->lastInsertId($table, false); - } - return $return; + public function lastInsertId($table) { + return (int)$this->conn->realLastInsertId($table); } /** diff --git a/lib/private/DB/AdapterOCI8.php b/lib/private/DB/AdapterOCI8.php index f5ad9f7c934..0a509090bca 100644 --- a/lib/private/DB/AdapterOCI8.php +++ b/lib/private/DB/AdapterOCI8.php @@ -8,7 +8,7 @@ namespace OC\DB; class AdapterOCI8 extends Adapter { - public function lastInsertId($table, bool $allowRetry = true): int { + public function lastInsertId($table) { if (is_null($table)) { throw new \InvalidArgumentException('Oracle requires a table name to be passed into lastInsertId()'); } diff --git a/lib/private/DB/AdapterPgSql.php b/lib/private/DB/AdapterPgSql.php index b321fcf4715..db48c81c2c5 100644 --- a/lib/private/DB/AdapterPgSql.php +++ b/lib/private/DB/AdapterPgSql.php @@ -9,7 +9,7 @@ namespace OC\DB; class AdapterPgSql extends Adapter { - public function lastInsertId($table, bool $allowRetry = true): int { + public function lastInsertId($table) { $result = $this->conn->executeQuery('SELECT lastval()'); $val = $result->fetchOne(); $result->free(); diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 4ba2d2a341d..88bdc377e2b 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -92,8 +92,6 @@ class Connection extends PrimaryReadReplicaConnection { protected ShardConnectionManager $shardConnectionManager; protected AutoIncrementHandler $autoIncrementHandler; protected bool $isShardingEnabled; - protected bool $disableReconnect = false; - protected int $lastInsertId = 0; public const SHARD_PRESETS = [ 'filecache' => [ @@ -512,9 +510,9 @@ class Connection extends PrimaryReadReplicaConnection { * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY * columns or sequences. * - * @param ?string $name Name of the sequence object from which the ID should be returned. + * @param string $seqName Name of the sequence object from which the ID should be returned. * - * @return int the last inserted ID, 0 in case there was no INSERT before or it failed to get the ID + * @return int the last inserted ID. * @throws Exception */ public function lastInsertId($name = null): int { @@ -528,13 +526,8 @@ class Connection extends PrimaryReadReplicaConnection { * @internal * @throws Exception */ - public function realLastInsertId($seqName = null): int { - if ($this->lastInsertId !== 0) { - $lastInsertId = $this->lastInsertId; - $this->lastInsertId = 0; - return $lastInsertId; - } - return (int)parent::lastInsertId($seqName); + public function realLastInsertId($seqName = null) { + return parent::lastInsertId($seqName); } /** @@ -901,25 +894,13 @@ class Connection extends PrimaryReadReplicaConnection { private function reconnectIfNeeded(): void { if ( - !isset($this->lastConnectionCheck[$this->getConnectionName()]) || - time() <= $this->lastConnectionCheck[$this->getConnectionName()] + 30 || - $this->isTransactionActive() || - $this->disableReconnect + !isset($this->lastConnectionCheck[$this->getConnectionName()]) + || time() <= $this->lastConnectionCheck[$this->getConnectionName()] + 30 + || $this->isTransactionActive() ) { return; } - if ($this->getDatabaseProvider() === IDBConnection::PLATFORM_MYSQL) { - /** - * Before reconnecting we save the lastInsertId, so that if the reconnect - * happens between the INSERT executeStatement() and the getLastInsertId call - * we are able to return the correct result after all. - */ - $this->disableReconnect = true; - $this->lastInsertId = (int)parent::lastInsertId(); - $this->disableReconnect = false; - } - try { $this->_conn->query($this->getDriver()->getDatabasePlatform()->getDummySelectSQL()); $this->lastConnectionCheck[$this->getConnectionName()] = time(); diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 2f4c8131d9f..40579c7a898 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2017 ownCloud GmbH diff --git a/lib/private/DB/MySqlTools.php b/lib/private/DB/MySqlTools.php index cd6b812be61..3413be43417 100644 --- a/lib/private/DB/MySqlTools.php +++ b/lib/private/DB/MySqlTools.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 ownCloud GmbH * SPDX-License-Identifier: AGPL-3.0-only @@ -45,7 +46,7 @@ class MySqlTools { return false; } - return str_contains($row, 'maria') && version_compare($row, '10.3', '>=') || - !str_contains($row, 'maria') && version_compare($row, '8.0', '>='); + return str_contains($row, 'maria') && version_compare($row, '10.3', '>=') + || !str_contains($row, 'maria') && version_compare($row, '8.0', '>='); } } diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php index 559c29df208..52f82db2232 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php index 2466493c1fa..48dc1da6330 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php index 6791430b1b0..8fae6275916 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php index ee430a6bd71..354a2b126d7 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/PgSqlFunctionBuilder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php index 956b2123f2c..53aa530054b 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/DB/QueryBuilder/Partitioned/PartitionedQueryBuilder.php b/lib/private/DB/QueryBuilder/Partitioned/PartitionedQueryBuilder.php index 2942eeccdf7..d748c791321 100644 --- a/lib/private/DB/QueryBuilder/Partitioned/PartitionedQueryBuilder.php +++ b/lib/private/DB/QueryBuilder/Partitioned/PartitionedQueryBuilder.php @@ -126,8 +126,8 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder { $selectPartition = null; } if ( - ($select === $checkColumn || $select === '*') && - $selectPartition === $partition + ($select === $checkColumn || $select === '*') + && $selectPartition === $partition ) { return; } @@ -151,8 +151,8 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder { foreach ($this->selects as $select) { foreach ($this->partitions as $partition) { if (is_string($select['select']) && ( - $select['select'] === '*' || - $partition->isColumnInPartition($select['select'])) + $select['select'] === '*' + || $partition->isColumnInPartition($select['select'])) ) { if (isset($this->splitQueries[$partition->name])) { if ($select['alias']) { @@ -444,4 +444,19 @@ class PartitionedQueryBuilder extends ShardedQueryBuilder { public function getPartitionCount(): int { return count($this->splitQueries) + 1; } + + public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self { + if (str_contains($column, '.')) { + [$alias, $column] = explode('.', $column); + $partition = $this->getPartition($alias); + if ($partition) { + $this->splitQueries[$partition->name]->query->hintShardKey($column, $value, $overwrite); + } else { + parent::hintShardKey($column, $value, $overwrite); + } + } else { + parent::hintShardKey($column, $value, $overwrite); + } + return $this; + } } diff --git a/lib/private/DB/SchemaWrapper.php b/lib/private/DB/SchemaWrapper.php index 473c0009237..0d5b2040513 100644 --- a/lib/private/DB/SchemaWrapper.php +++ b/lib/private/DB/SchemaWrapper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Diagnostics/Event.php b/lib/private/Diagnostics/Event.php index cf36bf9f82a..11d6505a888 100644 --- a/lib/private/Diagnostics/Event.php +++ b/lib/private/Diagnostics/Event.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Diagnostics/EventLogger.php b/lib/private/Diagnostics/EventLogger.php index 40cbd3e9e5d..3ec35a5e9ce 100644 --- a/lib/private/Diagnostics/EventLogger.php +++ b/lib/private/Diagnostics/EventLogger.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Diagnostics/Query.php b/lib/private/Diagnostics/Query.php index 81610074709..020fc4bb512 100644 --- a/lib/private/Diagnostics/Query.php +++ b/lib/private/Diagnostics/Query.php @@ -56,7 +56,7 @@ class Query implements IQuery { public function getStart() { return $this->start; } - + /** * @return float */ diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php index 4823b6b4456..154002ef340 100644 --- a/lib/private/DirectEditing/Manager.php +++ b/lib/private/DirectEditing/Manager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/DirectEditing/Token.php b/lib/private/DirectEditing/Token.php index 12ad9411216..ca01265f9df 100644 --- a/lib/private/DirectEditing/Token.php +++ b/lib/private/DirectEditing/Token.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/EmojiHelper.php b/lib/private/EmojiHelper.php index 514973b2959..52ab441e73a 100644 --- a/lib/private/EmojiHelper.php +++ b/lib/private/EmojiHelper.php @@ -19,8 +19,8 @@ class EmojiHelper implements IEmojiHelper { } public function doesPlatformSupportEmoji(): bool { - return $this->db->supports4ByteText() && - \class_exists(\IntlBreakIterator::class); + return $this->db->supports4ByteText() + && \class_exists(\IntlBreakIterator::class); } public function isValidSingleEmoji(string $emoji): bool { @@ -48,17 +48,17 @@ class EmojiHelper implements IEmojiHelper { if (strlen($emoji) >= 2) { // If the current code-point is an emoji or a modifier (like a skin-tone) // just continue and check the next character - if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL || - $codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER || - $codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL || - $codePointType === \IntlChar::CHAR_CATEGORY_FORMAT_CHAR || // i.e. 🏴 🏴 - $codePointType === \IntlChar::CHAR_CATEGORY_OTHER_PUNCTUATION || // i.e. ‼️ ⁉️ #⃣ - $codePointType === \IntlChar::CHAR_CATEGORY_LOWERCASE_LETTER || // i.e. ℹ️ - $codePointType === \IntlChar::CHAR_CATEGORY_MATH_SYMBOL || // i.e. ↔️ ◻️ ⤴️ ⤵️ - $codePointType === \IntlChar::CHAR_CATEGORY_ENCLOSING_MARK || // i.e. 0⃣..9⃣ - $codePointType === \IntlChar::CHAR_CATEGORY_DECIMAL_DIGIT_NUMBER || // i.e. 0⃣..9⃣ - $codePointType === \IntlChar::CHAR_CATEGORY_DASH_PUNCTUATION || // i.e. 〰️ - $codePointType === \IntlChar::CHAR_CATEGORY_GENERAL_OTHER_TYPES + if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL + || $codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER + || $codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL + || $codePointType === \IntlChar::CHAR_CATEGORY_FORMAT_CHAR // i.e. 🏴 🏴 + || $codePointType === \IntlChar::CHAR_CATEGORY_OTHER_PUNCTUATION // i.e. ‼️ ⁉️ #⃣ + || $codePointType === \IntlChar::CHAR_CATEGORY_LOWERCASE_LETTER // i.e. ℹ️ + || $codePointType === \IntlChar::CHAR_CATEGORY_MATH_SYMBOL // i.e. ↔️ ◻️ ⤴️ ⤵️ + || $codePointType === \IntlChar::CHAR_CATEGORY_ENCLOSING_MARK // i.e. 0⃣..9⃣ + || $codePointType === \IntlChar::CHAR_CATEGORY_DECIMAL_DIGIT_NUMBER // i.e. 0⃣..9⃣ + || $codePointType === \IntlChar::CHAR_CATEGORY_DASH_PUNCTUATION // i.e. 〰️ + || $codePointType === \IntlChar::CHAR_CATEGORY_GENERAL_OTHER_TYPES ) { continue; } diff --git a/lib/private/Encryption/EncryptionEventListener.php b/lib/private/Encryption/EncryptionEventListener.php index 59ac0dea932..d51b4b0d531 100644 --- a/lib/private/Encryption/EncryptionEventListener.php +++ b/lib/private/Encryption/EncryptionEventListener.php @@ -17,7 +17,9 @@ use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\NotFoundException; use OCP\IUser; +use OCP\IUserManager; use OCP\IUserSession; use OCP\Share\Events\ShareCreatedEvent; use OCP\Share\Events\ShareDeletedEvent; @@ -31,6 +33,7 @@ class EncryptionEventListener implements IEventListener { private IUserSession $userSession, private SetupManager $setupManager, private Manager $encryptionManager, + private IUserManager $userManager, ) { } @@ -50,10 +53,14 @@ class EncryptionEventListener implements IEventListener { } elseif ($event instanceof ShareCreatedEvent) { $this->getUpdate()->postShared($event->getShare()->getNode()); } elseif ($event instanceof ShareDeletedEvent) { - // In case the unsharing happens in a background job, we don't have - // a session and we load instead the user from the UserManager - $owner = $event->getShare()->getNode()->getOwner(); - $this->getUpdate($owner)->postUnshared($event->getShare()->getNode()); + try { + // In case the unsharing happens in a background job, we don't have + // a session and we load instead the user from the UserManager + $owner = $this->userManager->get($event->getShare()->getShareOwner()); + $this->getUpdate($owner)->postUnshared($event->getShare()->getNode()); + } catch (NotFoundException $e) { + /* The node was deleted already, nothing to update */ + } } elseif ($event instanceof NodeRestoredEvent) { $this->getUpdate()->postRestore($event->getTarget()); } @@ -78,7 +85,7 @@ class EncryptionEventListener implements IEventListener { $this->updater = new Update( new Util( new View(), - \OC::$server->getUserManager(), + $this->userManager, \OC::$server->getGroupManager(), \OC::$server->getConfig()), \OC::$server->getEncryptionManager(), diff --git a/lib/private/Encryption/Keys/Storage.php b/lib/private/Encryption/Keys/Storage.php index 2c0ce9e5ef3..cce22b9138a 100644 --- a/lib/private/Encryption/Keys/Storage.php +++ b/lib/private/Encryption/Keys/Storage.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Encryption/Util.php b/lib/private/Encryption/Util.php index 0566ab9a760..2d7bc28129b 100644 --- a/lib/private/Encryption/Util.php +++ b/lib/private/Encryption/Util.php @@ -290,8 +290,8 @@ class Util { if (count($root) > 1) { // detect alternative key storage root $rootDir = $this->getKeyStorageRoot(); - if ($rootDir !== '' && - str_starts_with(Filesystem::normalizePath($path), Filesystem::normalizePath($rootDir)) + if ($rootDir !== '' + && str_starts_with(Filesystem::normalizePath($path), Filesystem::normalizePath($rootDir)) ) { return true; } diff --git a/lib/private/Federation/CloudFederationFactory.php b/lib/private/Federation/CloudFederationFactory.php index f5f25d14ea1..d06de0f2f58 100644 --- a/lib/private/Federation/CloudFederationFactory.php +++ b/lib/private/Federation/CloudFederationFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Federation/CloudFederationNotification.php b/lib/private/Federation/CloudFederationNotification.php index 855580843ba..6ae805df1d9 100644 --- a/lib/private/Federation/CloudFederationNotification.php +++ b/lib/private/Federation/CloudFederationNotification.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Federation/CloudFederationProviderManager.php b/lib/private/Federation/CloudFederationProviderManager.php index e9354294351..81b5d717a56 100644 --- a/lib/private/Federation/CloudFederationProviderManager.php +++ b/lib/private/Federation/CloudFederationProviderManager.php @@ -227,8 +227,8 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager private function prepareOcmPayload(string $uri, string $payload): array { $payload = array_merge($this->getDefaultRequestOptions(), ['body' => $payload]); - if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true) && - $this->signatoryManager->getRemoteSignatory($this->signatureManager->extractIdentityFromUri($uri)) === null) { + if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true) + && $this->signatoryManager->getRemoteSignatory($this->signatureManager->extractIdentityFromUri($uri)) === null) { return $payload; } diff --git a/lib/private/Federation/CloudFederationShare.php b/lib/private/Federation/CloudFederationShare.php index 3ec53d89ed3..2eb06b3acea 100644 --- a/lib/private/Federation/CloudFederationShare.php +++ b/lib/private/Federation/CloudFederationShare.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index cb160115851..329466e682d 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -668,8 +668,8 @@ class Cache implements ICache { $shardDefinition = $this->connection->getShardDefinition('filecache'); if ( - $shardDefinition && - $shardDefinition->getShardForKey($sourceCache->getNumericStorageId()) !== $shardDefinition->getShardForKey($this->getNumericStorageId()) + $shardDefinition + && $shardDefinition->getShardForKey($sourceCache->getNumericStorageId()) !== $shardDefinition->getShardForKey($this->getNumericStorageId()) ) { $this->moveFromStorageSharded($shardDefinition, $sourceCache, $sourceData, $targetPath); return; diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index ff2d6766893..3ddcf1ca4e6 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php index 6a0cba7f1f2..e1d3c42a8a2 100644 --- a/lib/private/Files/Cache/SearchBuilder.php +++ b/lib/private/Files/Cache/SearchBuilder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 2b49e65f0b4..1a3bda58e6a 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -213,6 +213,7 @@ class Storage { $query = $db->getQueryBuilder(); $query->delete('filecache') ->where($query->expr()->in('storage', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY))); + $query->runAcrossAllShards(); $query->executeStatement(); $query = $db->getQueryBuilder(); diff --git a/lib/private/Files/Cache/Wrapper/JailPropagator.php b/lib/private/Files/Cache/Wrapper/JailPropagator.php index 19ca4a13ece..d6409b7875e 100644 --- a/lib/private/Files/Cache/Wrapper/JailPropagator.php +++ b/lib/private/Files/Cache/Wrapper/JailPropagator.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Config/CachedMountFileInfo.php b/lib/private/Files/Config/CachedMountFileInfo.php index 90a6b47f9d8..69bd4e9301e 100644 --- a/lib/private/Files/Config/CachedMountFileInfo.php +++ b/lib/private/Files/Config/CachedMountFileInfo.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index 6a5407934c8..9d63184e05f 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -24,60 +24,43 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { use EmitterTrait; /** - * @var \OCP\Files\Config\IHomeMountProvider[] + * @var list<IHomeMountProvider> */ - private $homeProviders = []; + private array $homeProviders = []; /** - * @var \OCP\Files\Config\IMountProvider[] + * @var list<IMountProvider> */ - private $providers = []; + private array $providers = []; - /** @var \OCP\Files\Config\IRootMountProvider[] */ - private $rootProviders = []; + /** @var list<IRootMountProvider> */ + private array $rootProviders = []; - /** - * @var \OCP\Files\Storage\IStorageFactory - */ - private $loader; - - /** - * @var \OCP\Files\Config\IUserMountCache - */ - private $mountCache; - - /** @var callable[] */ - private $mountFilters = []; + /** @var list<callable> */ + private array $mountFilters = []; - private IEventLogger $eventLogger; - - /** - * @param \OCP\Files\Storage\IStorageFactory $loader - * @param IUserMountCache $mountCache - */ public function __construct( - IStorageFactory $loader, - IUserMountCache $mountCache, - IEventLogger $eventLogger, + private IStorageFactory $loader, + private IUserMountCache $mountCache, + private IEventLogger $eventLogger, ) { - $this->loader = $loader; - $this->mountCache = $mountCache; - $this->eventLogger = $eventLogger; } + /** + * @return list<IMountPoint> + */ private function getMountsFromProvider(IMountProvider $provider, IUser $user, IStorageFactory $loader): array { $class = str_replace('\\', '_', get_class($provider)); $uid = $user->getUID(); $this->eventLogger->start('fs:setup:provider:' . $class, "Getting mounts from $class for $uid"); $mounts = $provider->getMountsForUser($user, $loader) ?? []; $this->eventLogger->end('fs:setup:provider:' . $class); - return $mounts; + return array_values($mounts); } /** - * @param IUser $user - * @param IMountProvider[] $providers - * @return IMountPoint[] + * @param list<IMountProvider> $providers + * @return list<IMountPoint> */ private function getUserMountsForProviders(IUser $user, array $providers): array { $loader = $this->loader; @@ -90,10 +73,16 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { return $this->filterMounts($user, $mounts); } + /** + * @return list<IMountPoint> + */ public function getMountsForUser(IUser $user): array { return $this->getUserMountsForProviders($user, $this->providers); } + /** + * @return list<IMountPoint> + */ public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array { $providers = array_filter( $this->providers, @@ -102,7 +91,10 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { return $this->getUserMountsForProviders($user, $providers); } - public function addMountForUser(IUser $user, IMountManager $mountManager, ?callable $providerFilter = null) { + /** + * @return list<IMountPoint> + */ + public function addMountForUser(IUser $user, IMountManager $mountManager, ?callable $providerFilter = null): array { // shared mount provider gets to go last since it needs to know existing files // to check for name collisions $firstMounts = []; @@ -135,18 +127,15 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { array_walk($lateMounts, [$mountManager, 'addMount']); $this->eventLogger->end('fs:setup:add-mounts'); - return array_merge($lateMounts, $firstMounts); + return array_values(array_merge($lateMounts, $firstMounts)); } /** * Get the configured home mount for this user * - * @param \OCP\IUser $user - * @return \OCP\Files\Mount\IMountPoint * @since 9.1.0 */ - public function getHomeMountForUser(IUser $user) { - /** @var \OCP\Files\Config\IHomeMountProvider[] $providers */ + public function getHomeMountForUser(IUser $user): IMountPoint { $providers = array_reverse($this->homeProviders); // call the latest registered provider first to give apps an opportunity to overwrite builtin foreach ($providers as $homeProvider) { if ($mount = $homeProvider->getHomeMountForUser($user, $this->loader)) { @@ -159,34 +148,36 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { /** * Add a provider for mount points - * - * @param \OCP\Files\Config\IMountProvider $provider */ - public function registerProvider(IMountProvider $provider) { + public function registerProvider(IMountProvider $provider): void { $this->providers[] = $provider; $this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]); } - public function registerMountFilter(callable $filter) { + public function registerMountFilter(callable $filter): void { $this->mountFilters[] = $filter; } - private function filterMounts(IUser $user, array $mountPoints) { - return array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) { + /** + * @param list<IMountPoint> $mountPoints + * @return list<IMountPoint> + */ + private function filterMounts(IUser $user, array $mountPoints): array { + return array_values(array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) { foreach ($this->mountFilters as $filter) { if ($filter($mountPoint, $user) === false) { return false; } } return true; - }); + })); } /** * Add a provider for home mount points * - * @param \OCP\Files\Config\IHomeMountProvider $provider + * @param IHomeMountProvider $provider * @since 9.1.0 */ public function registerHomeProvider(IHomeMountProvider $provider) { @@ -196,21 +187,19 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { /** * Get the mount cache which can be used to search for mounts without setting up the filesystem - * - * @return IUserMountCache */ - public function getMountCache() { + public function getMountCache(): IUserMountCache { return $this->mountCache; } - public function registerRootProvider(IRootMountProvider $provider) { + public function registerRootProvider(IRootMountProvider $provider): void { $this->rootProviders[] = $provider; } /** * Get all root mountpoints * - * @return \OCP\Files\Mount\IMountPoint[] + * @return list<IMountPoint> * @since 20.0.0 */ public function getRootMounts(): array { @@ -226,16 +215,33 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { throw new \Exception('No root mounts provided by any provider'); } - return $mounts; + return array_values($mounts); } - public function clearProviders() { + public function clearProviders(): void { $this->providers = []; $this->homeProviders = []; $this->rootProviders = []; } + /** + * @return list<IMountProvider> + */ public function getProviders(): array { return $this->providers; } + + /** + * @return list<IHomeMountProvider> + */ + public function getHomeProviders(): array { + return $this->homeProviders; + } + + /** + * @return list<IRootMountProvider> + */ + public function getRootProviders(): array { + return $this->rootProviders; + } } diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index 09ac6d1416a..3e53a67a044 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -151,9 +151,9 @@ class UserMountCache implements IUserMountCache { if (isset($newMounts[$key])) { $newMount = $newMounts[$key]; if ( - $newMount->getStorageId() !== $cachedMount->getStorageId() || - $newMount->getMountId() !== $cachedMount->getMountId() || - $newMount->getMountProvider() !== $cachedMount->getMountProvider() + $newMount->getStorageId() !== $cachedMount->getStorageId() + || $newMount->getMountId() !== $cachedMount->getMountId() + || $newMount->getMountProvider() !== $cachedMount->getMountProvider() ) { $changed[] = [$cachedMount, $newMount]; } diff --git a/lib/private/Files/Conversion/ConversionManager.php b/lib/private/Files/Conversion/ConversionManager.php index cf1085f66f0..2c98a4c6404 100644 --- a/lib/private/Files/Conversion/ConversionManager.php +++ b/lib/private/Files/Conversion/ConversionManager.php @@ -129,7 +129,7 @@ class ConversionManager implements IConversionManager { $this->preferredProviders[$class] = $this->serverContainer->get($class); continue; } - + $this->providers[$class] = $this->serverContainer->get($class); } catch (NotFoundExceptionInterface|ContainerExceptionInterface|Throwable $e) { $this->logger->error('Failed to load file conversion provider ' . $class, [ @@ -175,7 +175,7 @@ class ConversionManager implements IConversionManager { } } } - + return null; } } diff --git a/lib/private/Files/FilenameValidator.php b/lib/private/Files/FilenameValidator.php index 57a62b0b219..a78c6d3cc3c 100644 --- a/lib/private/Files/FilenameValidator.php +++ b/lib/private/Files/FilenameValidator.php @@ -232,7 +232,7 @@ class FilenameValidator implements IFilenameValidator { $forbiddenCharacters = $this->getForbiddenCharacters(); if ($charReplacement === null) { - $charReplacement = array_diff([' ', '_', '-'], $forbiddenCharacters); + $charReplacement = array_diff(['_', '-', ' '], $forbiddenCharacters); $charReplacement = reset($charReplacement) ?: ''; } if (mb_strlen($charReplacement) !== 1) { diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php index 99c52108fa8..4b088f2c808 100644 --- a/lib/private/Files/Mount/ObjectHomeMountProvider.php +++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php @@ -7,117 +7,39 @@ */ namespace OC\Files\Mount; +use OC\Files\ObjectStore\HomeObjectStoreStorage; +use OC\Files\ObjectStore\PrimaryObjectStoreConfig; use OCP\Files\Config\IHomeMountProvider; +use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage\IStorageFactory; -use OCP\IConfig; use OCP\IUser; -use Psr\Log\LoggerInterface; /** * Mount provider for object store home storages */ class ObjectHomeMountProvider implements IHomeMountProvider { - /** - * @var IConfig - */ - private $config; - - /** - * ObjectStoreHomeMountProvider constructor. - * - * @param IConfig $config - */ - public function __construct(IConfig $config) { - $this->config = $config; + public function __construct( + private PrimaryObjectStoreConfig $objectStoreConfig, + ) { } /** - * Get the cache mount for a user + * Get the home mount for a user * * @param IUser $user * @param IStorageFactory $loader - * @return \OCP\Files\Mount\IMountPoint + * @return ?IMountPoint */ - public function getHomeMountForUser(IUser $user, IStorageFactory $loader) { - $config = $this->getMultiBucketObjectStoreConfig($user); - if ($config === null) { - $config = $this->getSingleBucketObjectStoreConfig($user); - } - - if ($config === null) { + public function getHomeMountForUser(IUser $user, IStorageFactory $loader): ?IMountPoint { + $objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForUser($user); + if ($objectStoreConfig === null) { return null; } + $arguments = array_merge($objectStoreConfig['arguments'], [ + 'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig), + 'user' => $user, + ]); - return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class); - } - - /** - * @param IUser $user - * @return array|null - */ - private function getSingleBucketObjectStoreConfig(IUser $user) { - $config = $this->config->getSystemValue('objectstore'); - if (!is_array($config)) { - return null; - } - - // sanity checks - if (empty($config['class'])) { - \OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']); - } - if (!isset($config['arguments'])) { - $config['arguments'] = []; - } - // instantiate object store implementation - $config['arguments']['objectstore'] = new $config['class']($config['arguments']); - - $config['arguments']['user'] = $user; - - return $config; - } - - /** - * @param IUser $user - * @return array|null - */ - private function getMultiBucketObjectStoreConfig(IUser $user) { - $config = $this->config->getSystemValue('objectstore_multibucket'); - if (!is_array($config)) { - return null; - } - - // sanity checks - if (empty($config['class'])) { - \OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']); - } - if (!isset($config['arguments'])) { - $config['arguments'] = []; - } - - $bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null); - - if ($bucket === null) { - /* - * Use any provided bucket argument as prefix - * and add the mapping from username => bucket - */ - if (!isset($config['arguments']['bucket'])) { - $config['arguments']['bucket'] = ''; - } - $mapper = new \OC\Files\ObjectStore\Mapper($user, $this->config); - $numBuckets = $config['arguments']['num_buckets'] ?? 64; - $config['arguments']['bucket'] .= $mapper->getBucket($numBuckets); - - $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $config['arguments']['bucket']); - } else { - $config['arguments']['bucket'] = $bucket; - } - - // instantiate object store implementation - $config['arguments']['objectstore'] = new $config['class']($config['arguments']); - - $config['arguments']['user'] = $user; - - return $config; + return new HomeMountPoint($user, HomeObjectStoreStorage::class, '/' . $user->getUID(), $arguments, $loader, null, null, self::class); } } diff --git a/lib/private/Files/Mount/RootMountProvider.php b/lib/private/Files/Mount/RootMountProvider.php index 86f8188978f..5e0c924ad38 100644 --- a/lib/private/Files/Mount/RootMountProvider.php +++ b/lib/private/Files/Mount/RootMountProvider.php @@ -10,79 +10,41 @@ namespace OC\Files\Mount; use OC; use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\ObjectStore\PrimaryObjectStoreConfig; use OC\Files\Storage\LocalRootStorage; -use OC_App; use OCP\Files\Config\IRootMountProvider; use OCP\Files\Storage\IStorageFactory; use OCP\IConfig; -use Psr\Log\LoggerInterface; class RootMountProvider implements IRootMountProvider { + private PrimaryObjectStoreConfig $objectStoreConfig; private IConfig $config; - private LoggerInterface $logger; - public function __construct(IConfig $config, LoggerInterface $logger) { + public function __construct(PrimaryObjectStoreConfig $objectStoreConfig, IConfig $config) { + $this->objectStoreConfig = $objectStoreConfig; $this->config = $config; - $this->logger = $logger; } public function getRootMounts(IStorageFactory $loader): array { - $objectStore = $this->config->getSystemValue('objectstore', null); - $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null); + $objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForRoot(); - if ($objectStoreMultiBucket) { - return [$this->getMultiBucketStoreRootMount($loader, $objectStoreMultiBucket)]; - } elseif ($objectStore) { - return [$this->getObjectStoreRootMount($loader, $objectStore)]; + if ($objectStoreConfig) { + return [$this->getObjectStoreRootMount($loader, $objectStoreConfig)]; } else { return [$this->getLocalRootMount($loader)]; } } - private function validateObjectStoreConfig(array &$config) { - if (empty($config['class'])) { - $this->logger->error('No class given for objectstore', ['app' => 'files']); - } - if (!isset($config['arguments'])) { - $config['arguments'] = []; - } - - // instantiate object store implementation - $name = $config['class']; - if (str_starts_with($name, 'OCA\\') && substr_count($name, '\\') >= 2) { - $segments = explode('\\', $name); - OC_App::loadApp(strtolower($segments[1])); - } - } - private function getLocalRootMount(IStorageFactory $loader): MountPoint { $configDataDirectory = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data'); return new MountPoint(LocalRootStorage::class, '/', ['datadir' => $configDataDirectory], $loader, null, null, self::class); } - private function getObjectStoreRootMount(IStorageFactory $loader, array $config): MountPoint { - $this->validateObjectStoreConfig($config); - - $config['arguments']['objectstore'] = new $config['class']($config['arguments']); - // mount with plain / root object store implementation - $config['class'] = ObjectStoreStorage::class; - - return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class); - } - - private function getMultiBucketStoreRootMount(IStorageFactory $loader, array $config): MountPoint { - $this->validateObjectStoreConfig($config); - - if (!isset($config['arguments']['bucket'])) { - $config['arguments']['bucket'] = ''; - } - // put the root FS always in first bucket for multibucket configuration - $config['arguments']['bucket'] .= '0'; - - $config['arguments']['objectstore'] = new $config['class']($config['arguments']); - // mount with plain / root object store implementation - $config['class'] = ObjectStoreStorage::class; + private function getObjectStoreRootMount(IStorageFactory $loader, array $objectStoreConfig): MountPoint { + $arguments = array_merge($objectStoreConfig['arguments'], [ + 'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig), + ]); - return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class); + return new MountPoint(ObjectStoreStorage::class, '/', $arguments, $loader, null, null, self::class); } } diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 16365948031..7453b553119 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -460,4 +461,12 @@ class Folder extends Node implements \OCP\Files\Folder { return $this->search($query); } + + public function verifyPath($fileName, $readonly = false): void { + $this->view->verifyPath( + $this->getPath(), + $fileName, + $readonly, + ); + } } diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 5879748d951..37b1efa0fad 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -561,4 +561,8 @@ class LazyFolder implements Folder { public function getMetadata(): array { return $this->data['metadata'] ?? $this->__call(__FUNCTION__, func_get_args()); } + + public function verifyPath($fileName, $readonly = false): void { + $this->__call(__FUNCTION__, func_get_args()); + } } diff --git a/lib/private/Files/Notify/Change.php b/lib/private/Files/Notify/Change.php index d2448e5deec..c8eccd11ae2 100644 --- a/lib/private/Files/Notify/Change.php +++ b/lib/private/Files/Notify/Change.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Notify/RenameChange.php b/lib/private/Files/Notify/RenameChange.php index 98fd7099a58..28554ceaa26 100644 --- a/lib/private/Files/Notify/RenameChange.php +++ b/lib/private/Files/Notify/RenameChange.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/ObjectStore/Azure.php b/lib/private/Files/ObjectStore/Azure.php index 575cc336ba8..2729bb3c037 100644 --- a/lib/private/Files/ObjectStore/Azure.php +++ b/lib/private/Files/ObjectStore/Azure.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index ebe87399ab4..10ee6aec167 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -67,7 +67,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil $this->logger = \OCP\Server::get(LoggerInterface::class); } - public function mkdir(string $path, bool $force = false): bool { + public function mkdir(string $path, bool $force = false, array $metadata = []): bool { $path = $this->normalizePath($path); if (!$force && $this->file_exists($path)) { $this->logger->warning("Tried to create an object store folder that already exists: $path"); @@ -77,7 +77,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil $mTime = time(); $data = [ 'mimetype' => 'httpd/unix-directory', - 'size' => 0, + 'size' => $metadata['size'] ?? 0, 'mtime' => $mTime, 'storage_mtime' => $mTime, 'permissions' => \OCP\Constants::PERMISSION_ALL, @@ -413,16 +413,6 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil //create a empty file, need to have at least on char to make it // work with all object storage implementations $this->file_put_contents($path, ' '); - $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); - $stat = [ - 'etag' => $this->getETag($path), - 'mimetype' => $mimeType, - 'size' => 0, - 'mtime' => $mtime, - 'storage_mtime' => $mtime, - 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, - ]; - $this->getCache()->put($path, $stat); } catch (\Exception $ex) { $this->logger->error( 'Could not create object for ' . $path, @@ -607,8 +597,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, ?ICacheEntry $sourceCacheEntry = null): bool { $sourceCache = $sourceStorage->getCache(); if ( - $sourceStorage->instanceOfStorage(ObjectStoreStorage::class) && - $sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId() + $sourceStorage->instanceOfStorage(ObjectStoreStorage::class) + && $sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId() ) { if ($this->getCache()->get($targetInternalPath)) { $this->unlink($targetInternalPath); @@ -709,7 +699,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil if ($cache->inCache($to)) { $cache->remove($to); } - $this->mkdir($to); + $this->mkdir($to, false, ['size' => $sourceEntry->getSize()]); foreach ($sourceCache->getFolderContentsById($sourceEntry->getId()) as $child) { $this->copyInner($sourceCache, $child, $to . '/' . $child->getName()); diff --git a/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php new file mode 100644 index 00000000000..fdfe989addc --- /dev/null +++ b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php @@ -0,0 +1,140 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OC\Files\ObjectStore; + +use OCP\App\IAppManager; +use OCP\Files\ObjectStore\IObjectStore; +use OCP\IConfig; +use OCP\IUser; + +/** + * @psalm-type ObjectStoreConfig array{class: class-string<IObjectStore>, arguments: array{multibucket: bool, ...}} + */ +class PrimaryObjectStoreConfig { + public function __construct( + private readonly IConfig $config, + private readonly IAppManager $appManager, + ) { + } + + /** + * @param ObjectStoreConfig $config + */ + public function buildObjectStore(array $config): IObjectStore { + return new $config['class']($config['arguments']); + } + + /** + * @return ?ObjectStoreConfig + */ + public function getObjectStoreConfigForRoot(): ?array { + $config = $this->getObjectStoreConfig(); + + if ($config && $config['arguments']['multibucket']) { + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + + // put the root FS always in first bucket for multibucket configuration + $config['arguments']['bucket'] .= '0'; + } + return $config; + } + + /** + * @return ?ObjectStoreConfig + */ + public function getObjectStoreConfigForUser(IUser $user): ?array { + $config = $this->getObjectStoreConfig(); + + if ($config && $config['arguments']['multibucket']) { + $config['arguments']['bucket'] = $this->getBucketForUser($user, $config); + } + return $config; + } + + /** + * @return ?ObjectStoreConfig + */ + private function getObjectStoreConfig(): ?array { + $objectStore = $this->config->getSystemValue('objectstore', null); + $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null); + + // new-style multibucket config uses the same 'objectstore' key but sets `'multibucket' => true`, transparently upgrade older style config + if ($objectStoreMultiBucket) { + $objectStoreMultiBucket['arguments']['multibucket'] = true; + return $this->validateObjectStoreConfig($objectStoreMultiBucket); + } elseif ($objectStore) { + return $this->validateObjectStoreConfig($objectStore); + } else { + return null; + } + } + + /** + * @return ObjectStoreConfig + */ + private function validateObjectStoreConfig(array $config) { + if (!isset($config['class'])) { + throw new \Exception('No class configured for object store'); + } + if (!isset($config['arguments'])) { + $config['arguments'] = []; + } + $class = $config['class']; + $arguments = $config['arguments']; + if (!is_array($arguments)) { + throw new \Exception('Configured object store arguments are not an array'); + } + if (!isset($arguments['multibucket'])) { + $arguments['multibucket'] = false; + } + if (!is_bool($arguments['multibucket'])) { + throw new \Exception('arguments.multibucket must be a boolean in object store configuration'); + } + + if (!is_string($class)) { + throw new \Exception('Configured class for object store is not a string'); + } + + if (str_starts_with($class, 'OCA\\') && substr_count($class, '\\') >= 2) { + [$appId] = explode('\\', $class); + $this->appManager->loadApp(strtolower($appId)); + } + + if (!is_a($class, IObjectStore::class, true)) { + throw new \Exception('Configured class for object store is not an object store'); + } + return [ + 'class' => $class, + 'arguments' => $arguments, + ]; + } + + private function getBucketForUser(IUser $user, array $config): string { + $bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null); + + if ($bucket === null) { + /* + * Use any provided bucket argument as prefix + * and add the mapping from username => bucket + */ + if (!isset($config['arguments']['bucket'])) { + $config['arguments']['bucket'] = ''; + } + $mapper = new Mapper($user, $this->config); + $numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64; + $bucket = $config['arguments']['bucket'] . $mapper->getBucket($numBuckets); + + $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $bucket); + } + + return $bucket; + } +} diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index 23c061db174..72e1751e23d 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/ObjectStore/S3ConfigTrait.php b/lib/private/Files/ObjectStore/S3ConfigTrait.php index 63f14ac2d00..5b086db8f77 100644 --- a/lib/private/Files/ObjectStore/S3ConfigTrait.php +++ b/lib/private/Files/ObjectStore/S3ConfigTrait.php @@ -18,6 +18,10 @@ trait S3ConfigTrait { /** Maximum number of concurrent multipart uploads */ protected int $concurrency; + /** Timeout, in seconds, for the connection to S3 server, not for the + * request. */ + protected float $connectTimeout; + protected int $timeout; protected string|false $proxy; diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index b7017583dc2..67b82a44ab7 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -39,6 +40,7 @@ trait S3ConnectionTrait { // Default to 5 like the S3 SDK does $this->concurrency = $params['concurrency'] ?? 5; $this->proxy = $params['proxy'] ?? false; + $this->connectTimeout = $params['connect_timeout'] ?? 5; $this->timeout = $params['timeout'] ?? 15; $this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD'; $this->uploadPartSize = $params['uploadPartSize'] ?? 524288000; @@ -102,8 +104,7 @@ trait S3ConnectionTrait { 'use_arn_region' => false, 'http' => [ 'verify' => $this->getCertificateBundlePath(), - // Timeout for the connection to S3 server, not for the request. - 'connect_timeout' => 5 + 'connect_timeout' => $this->connectTimeout, ], 'use_aws_shared_config_files' => false, 'retries' => [ diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index 61e8158b863..5e6dcf88a42 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -119,28 +119,54 @@ trait S3ObjectTrait { protected function writeMultiPart(string $urn, StreamInterface $stream, array $metaData): void { $mimetype = $metaData['mimetype'] ?? null; unset($metaData['mimetype']); - $uploader = new MultipartUploader($this->getConnection(), $stream, [ - 'bucket' => $this->bucket, - 'concurrency' => $this->concurrency, - 'key' => $urn, - 'part_size' => $this->uploadPartSize, - 'params' => [ - 'ContentType' => $mimetype, - 'Metadata' => $this->buildS3Metadata($metaData), - 'StorageClass' => $this->storageClass, - ] + $this->getSSECParameters(), - ]); - try { - $uploader->upload(); - } catch (S3MultipartUploadException $e) { + $attempts = 0; + $uploaded = false; + $concurrency = $this->concurrency; + $exception = null; + $state = null; + + // retry multipart upload once with concurrency at half on failure + while (!$uploaded && $attempts <= 1) { + $uploader = new MultipartUploader($this->getConnection(), $stream, [ + 'bucket' => $this->bucket, + 'concurrency' => $concurrency, + 'key' => $urn, + 'part_size' => $this->uploadPartSize, + 'state' => $state, + 'params' => [ + 'ContentType' => $mimetype, + 'Metadata' => $this->buildS3Metadata($metaData), + 'StorageClass' => $this->storageClass, + ] + $this->getSSECParameters(), + ]); + + try { + $uploader->upload(); + $uploaded = true; + } catch (S3MultipartUploadException $e) { + $exception = $e; + $attempts++; + + if ($concurrency > 1) { + $concurrency = round($concurrency / 2); + } + + if ($stream->isSeekable()) { + $stream->rewind(); + } + } + } + + if (!$uploaded) { // if anything goes wrong with multipart, make sure that you don´t poison and // slow down s3 bucket with orphaned fragments - $uploadInfo = $e->getState()->getId(); - if ($e->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) { + $uploadInfo = $exception->getState()->getId(); + if ($exception->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) { $this->getConnection()->abortMultipartUpload($uploadInfo); } - throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $e); + + throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $exception); } } diff --git a/lib/private/Files/ObjectStore/S3Signature.php b/lib/private/Files/ObjectStore/S3Signature.php index 994d65cc219..b80382ff67d 100644 --- a/lib/private/Files/ObjectStore/S3Signature.php +++ b/lib/private/Files/ObjectStore/S3Signature.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/ObjectStore/StorageObjectStore.php b/lib/private/Files/ObjectStore/StorageObjectStore.php index 4361795ec45..888602a62e4 100644 --- a/lib/private/Files/ObjectStore/StorageObjectStore.php +++ b/lib/private/Files/ObjectStore/StorageObjectStore.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php b/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php index 2bde6f0bbdb..bb7bef2ed63 100644 --- a/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php +++ b/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php @@ -14,8 +14,8 @@ class FlattenNestedBool extends QueryOptimizerStep { public function processOperator(ISearchOperator &$operator) { if ( $operator instanceof SearchBinaryOperator && ( - $operator->getType() === ISearchBinaryOperator::OPERATOR_OR || - $operator->getType() === ISearchBinaryOperator::OPERATOR_AND + $operator->getType() === ISearchBinaryOperator::OPERATOR_OR + || $operator->getType() === ISearchBinaryOperator::OPERATOR_AND ) ) { $newArguments = []; diff --git a/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php b/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php index cb04351534a..7e99c04f197 100644 --- a/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php +++ b/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php @@ -16,11 +16,11 @@ class FlattenSingleArgumentBinaryOperation extends ReplacingOptimizerStep { public function processOperator(ISearchOperator &$operator): bool { parent::processOperator($operator); if ( - $operator instanceof ISearchBinaryOperator && - count($operator->getArguments()) === 1 && - ( - $operator->getType() === ISearchBinaryOperator::OPERATOR_OR || - $operator->getType() === ISearchBinaryOperator::OPERATOR_AND + $operator instanceof ISearchBinaryOperator + && count($operator->getArguments()) === 1 + && ( + $operator->getType() === ISearchBinaryOperator::OPERATOR_OR + || $operator->getType() === ISearchBinaryOperator::OPERATOR_AND ) ) { $operator = $operator->getArguments()[0]; diff --git a/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php b/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php index 42d960cc22a..6df35c9c9a2 100644 --- a/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php +++ b/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php @@ -18,8 +18,8 @@ use OCP\Files\Search\ISearchOperator; class OrEqualsToIn extends ReplacingOptimizerStep { public function processOperator(ISearchOperator &$operator): bool { if ( - $operator instanceof ISearchBinaryOperator && - $operator->getType() === ISearchBinaryOperator::OPERATOR_OR + $operator instanceof ISearchBinaryOperator + && $operator->getType() === ISearchBinaryOperator::OPERATOR_OR ) { $groups = $this->groupEqualsComparisonsByField($operator->getArguments()); $newParts = array_map(function (array $group) { diff --git a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php index 9d50746d5d1..2994a9365a7 100644 --- a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php +++ b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php @@ -52,9 +52,9 @@ class PathPrefixOptimizer extends QueryOptimizerStep { private function operatorPairIsPathPrefix(ISearchOperator $like, ISearchOperator $equal): bool { return ( - $like instanceof ISearchComparison && $equal instanceof ISearchComparison && - !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path' && - $like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL + $like instanceof ISearchComparison && $equal instanceof ISearchComparison + && !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path' + && $like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL && $like->getValue() === SearchComparison::escapeLikeParameter($equal->getValue()) . '/%' ); } diff --git a/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php b/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php index 3e5258a715f..8aee1975708 100644 --- a/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php +++ b/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php @@ -18,9 +18,9 @@ use OCP\Files\Search\ISearchOperator; class SplitLargeIn extends ReplacingOptimizerStep { public function processOperator(ISearchOperator &$operator): bool { if ( - $operator instanceof ISearchComparison && - $operator->getType() === ISearchComparison::COMPARE_IN && - count($operator->getValue()) > 1000 + $operator instanceof ISearchComparison + && $operator->getType() === ISearchComparison::COMPARE_IN + && count($operator->getValue()) > 1000 ) { $chunks = array_chunk($operator->getValue(), 1000); $chunkComparisons = array_map(function (array $values) use ($operator) { diff --git a/lib/private/Files/Search/SearchBinaryOperator.php b/lib/private/Files/Search/SearchBinaryOperator.php index 59a1ba2dfaf..49f599933f4 100644 --- a/lib/private/Files/Search/SearchBinaryOperator.php +++ b/lib/private/Files/Search/SearchBinaryOperator.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php index 3dcbc02bc1a..5a036653f4e 100644 --- a/lib/private/Files/Search/SearchOrder.php +++ b/lib/private/Files/Search/SearchOrder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Search/SearchQuery.php b/lib/private/Files/Search/SearchQuery.php index 1793e42d349..592749cf4a0 100644 --- a/lib/private/Files/Search/SearchQuery.php +++ b/lib/private/Files/Search/SearchQuery.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 2b8121a529d..d6260c38fb0 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -23,7 +23,6 @@ use OC\Share\Share; use OC\Share20\ShareDisableChecker; use OC_App; use OC_Hook; -use OC_Util; use OCA\Files_External\Config\ExternalMountPoint; use OCA\Files_Sharing\External\Mount; use OCA\Files_Sharing\ISharedMountPoint; @@ -34,6 +33,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\ICachedMountInfo; use OCP\Files\Config\IHomeMountProvider; use OCP\Files\Config\IMountProvider; +use OCP\Files\Config\IRootMountProvider; use OCP\Files\Config\IUserMountCache; use OCP\Files\Events\BeforeFileSystemSetupEvent; use OCP\Files\Events\InvalidateMountCacheEvent; @@ -156,7 +156,7 @@ class SetupManager { if ($mount instanceof HomeMountPoint) { $user = $mount->getUser(); return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) { - return OC_Util::getUserQuota($user); + return $user->getQuotaBytes(); }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]); } @@ -171,9 +171,9 @@ class SetupManager { return new PermissionsMask([ 'storage' => $storage, 'mask' => Constants::PERMISSION_ALL & ~( - Constants::PERMISSION_UPDATE | - Constants::PERMISSION_CREATE | - Constants::PERMISSION_DELETE + Constants::PERMISSION_UPDATE + | Constants::PERMISSION_CREATE + | Constants::PERMISSION_DELETE ), ]); } @@ -280,9 +280,13 @@ class SetupManager { $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) { return str_starts_with($mount->getMountPoint(), $userRoot); }); - $allProviders = array_map(function (IMountProvider $provider) { + $allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) { return get_class($provider); - }, $this->mountProviderCollection->getProviders()); + }, array_merge( + $this->mountProviderCollection->getProviders(), + $this->mountProviderCollection->getHomeProviders(), + $this->mountProviderCollection->getRootProviders(), + )); $newProviders = array_diff($allProviders, $previouslySetupProviders); $mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) { return !in_array($mount->getMountProvider(), $previouslySetupProviders); diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php index 0bfaea21788..d9c1b47d2f1 100644 --- a/lib/private/Files/SimpleFS/SimpleFile.php +++ b/lib/private/Files/SimpleFS/SimpleFile.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php index 0da552e402b..62f3db25e9b 100644 --- a/lib/private/Files/SimpleFS/SimpleFolder.php +++ b/lib/private/Files/SimpleFS/SimpleFolder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index b1e02cf2e6c..2dc359169d7 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -550,8 +550,8 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage, public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { if ( - !$sourceStorage->instanceOfStorage(Encryption::class) && - $this->isSameStorage($sourceStorage) + !$sourceStorage->instanceOfStorage(Encryption::class) + && $this->isSameStorage($sourceStorage) ) { // resolve any jailed paths while ($sourceStorage->instanceOfStorage(Jail::class)) { diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php index 10670d6331a..afd8f87e2de 100644 --- a/lib/private/Files/Storage/DAV.php +++ b/lib/private/Files/Storage/DAV.php @@ -350,7 +350,13 @@ class DAV extends Common { } } - return $response->getBody(); + $content = $response->getBody(); + + if ($content === null || is_string($content)) { + return false; + } + + return $content; case 'w': case 'wb': case 'a': @@ -390,6 +396,8 @@ class DAV extends Common { $this->writeBack($tmpFile, $path); }); } + + return false; } public function writeBack(string $tmpFile, string $path): void { diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 4d38d2d37aa..51a5f99908d 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -185,11 +185,11 @@ class Encryption extends Wrapper { public function rename(string $source, string $target): bool { $result = $this->storage->rename($source, $target); - if ($result && + if ($result // versions always use the keys from the original file, so we can skip // this step for versions - $this->isVersion($target) === false && - $this->encryptionManager->isEnabled()) { + && $this->isVersion($target) === false + && $this->encryptionManager->isEnabled()) { $sourcePath = $this->getFullPath($source); if (!$this->util->isExcluded($sourcePath)) { $targetPath = $this->getFullPath($target); @@ -210,9 +210,9 @@ class Encryption extends Wrapper { public function rmdir(string $path): bool { $result = $this->storage->rmdir($path); $fullPath = $this->getFullPath($path); - if ($result && - $this->util->isExcluded($fullPath) === false && - $this->encryptionManager->isEnabled() + if ($result + && $this->util->isExcluded($fullPath) === false + && $this->encryptionManager->isEnabled() ) { $this->keyStorage->deleteAllFileKeys($fullPath); } @@ -225,9 +225,9 @@ class Encryption extends Wrapper { $metaData = $this->getMetaData($path); if ( - !$this->is_dir($path) && - isset($metaData['encrypted']) && - $metaData['encrypted'] === true + !$this->is_dir($path) + && isset($metaData['encrypted']) + && $metaData['encrypted'] === true ) { $fullPath = $this->getFullPath($path); $module = $this->getEncryptionModule($path); @@ -384,9 +384,9 @@ class Encryption extends Wrapper { $size = $this->storage->filesize($path); $result = $unencryptedSize; - if ($unencryptedSize < 0 || - ($size > 0 && $unencryptedSize === $size) || - $unencryptedSize > $size + if ($unencryptedSize < 0 + || ($size > 0 && $unencryptedSize === $size) + || $unencryptedSize > $size ) { // check if we already calculate the unencrypted size for the // given path to avoid recursions @@ -634,8 +634,8 @@ class Encryption extends Wrapper { ): bool { // for versions we have nothing to do, because versions should always use the // key from the original file. Just create a 1:1 copy and done - if ($this->isVersion($targetInternalPath) || - $this->isVersion($sourceInternalPath)) { + if ($this->isVersion($targetInternalPath) + || $this->isVersion($sourceInternalPath)) { // remember that we try to create a version so that we can detect it during // fopen($sourceInternalPath) and by-pass the encryption in order to // create a 1:1 copy of the file diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index 0d55385820c..ef147ec421f 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -312,8 +312,8 @@ class Encryption extends Wrapper { // for seekable streams the pointer is moved back to the beginning of the encrypted block // flush will start writing there when the position moves to another block - $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) * - $this->util->getBlockSize() + $this->headerSize; + $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) + * $this->util->getBlockSize() + $this->headerSize; $resultFseek = $this->parentStreamSeek($positionInFile); // only allow writes on seekable streams, or at the end of the encrypted stream @@ -336,8 +336,8 @@ class Encryption extends Wrapper { // if $data doesn't fit the current block, the fill the current block and reiterate // after the block is filled, it is flushed and $data is updatedxxx } else { - $this->cache = substr($this->cache, 0, $blockPosition) . - substr($data, 0, $this->unencryptedBlockSize - $blockPosition); + $this->cache = substr($this->cache, 0, $blockPosition) + . substr($data, 0, $this->unencryptedBlockSize - $blockPosition); $this->flush(); $this->position += ($this->unencryptedBlockSize - $blockPosition); $length += ($this->unencryptedBlockSize - $blockPosition); diff --git a/lib/private/Files/Stream/SeekableHttpStream.php b/lib/private/Files/Stream/SeekableHttpStream.php index 5ed04ed9066..6ce0a880e8d 100644 --- a/lib/private/Files/Stream/SeekableHttpStream.php +++ b/lib/private/Files/Stream/SeekableHttpStream.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 6832b4e1551..63eecf5e1d6 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -938,7 +938,7 @@ class View { try { $exists = $this->file_exists($target); - if ($this->shouldEmitHooks()) { + if ($this->shouldEmitHooks($target)) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_copy, @@ -978,7 +978,7 @@ class View { $this->changeLock($target, ILockingProvider::LOCK_SHARED); $lockTypePath2 = ILockingProvider::LOCK_SHARED; - if ($this->shouldEmitHooks() && $result !== false) { + if ($this->shouldEmitHooks($target) && $result !== false) { \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_copy, diff --git a/lib/private/FullTextSearch/FullTextSearchManager.php b/lib/private/FullTextSearch/FullTextSearchManager.php index 3ef8547ad3f..989da8d6bae 100644 --- a/lib/private/FullTextSearch/FullTextSearchManager.php +++ b/lib/private/FullTextSearch/FullTextSearchManager.php @@ -53,9 +53,9 @@ class FullTextSearchManager implements IFullTextSearchManager { * @since 16.0.0 */ public function isAvailable(): bool { - if ($this->indexService === null || - $this->providerService === null || - $this->searchService === null) { + if ($this->indexService === null + || $this->providerService === null + || $this->searchService === null) { return false; } diff --git a/lib/private/FullTextSearch/Model/IndexDocument.php b/lib/private/FullTextSearch/Model/IndexDocument.php index 8bd20bad1e0..a51447393ed 100644 --- a/lib/private/FullTextSearch/Model/IndexDocument.php +++ b/lib/private/FullTextSearch/Model/IndexDocument.php @@ -512,8 +512,8 @@ class IndexDocument implements IIndexDocument, JsonSerializable { * @since 16.0.0 */ final public function addExcerpt(string $source, string $excerpt): IIndexDocument { - $this->excerpts[] = - [ + $this->excerpts[] + = [ 'source' => $source, 'excerpt' => $this->cleanExcerpt($excerpt) ]; diff --git a/lib/private/GlobalScale/Config.php b/lib/private/GlobalScale/Config.php index 02acc1ab50a..2f729c2702e 100644 --- a/lib/private/GlobalScale/Config.php +++ b/lib/private/GlobalScale/Config.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php index 147c5baf543..6e42fad8b9f 100644 --- a/lib/private/Group/Group.php +++ b/lib/private/Group/Group.php @@ -377,7 +377,7 @@ class Group implements IGroup { */ public function hideFromCollaboration(): bool { return array_reduce($this->backends, function (bool $hide, GroupInterface $backend) { - return $hide | ($backend instanceof IHideFromCollaborationBackend && $backend->hideGroup($this->gid)); + return $hide || ($backend instanceof IHideFromCollaborationBackend && $backend->hideGroup($this->gid)); }, false); } } diff --git a/lib/private/Hooks/BasicEmitter.php b/lib/private/Hooks/BasicEmitter.php index c9444b40473..091334b71c8 100644 --- a/lib/private/Hooks/BasicEmitter.php +++ b/lib/private/Hooks/BasicEmitter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Hooks/Emitter.php b/lib/private/Hooks/Emitter.php index 8a63ac9ed3a..86eb410860c 100644 --- a/lib/private/Hooks/Emitter.php +++ b/lib/private/Hooks/Emitter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Hooks/EmitterTrait.php b/lib/private/Hooks/EmitterTrait.php index 8bffb6f7c3b..7b2ec4ad7fe 100644 --- a/lib/private/Hooks/EmitterTrait.php +++ b/lib/private/Hooks/EmitterTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Hooks/PublicEmitter.php b/lib/private/Hooks/PublicEmitter.php index 042b616e264..77cb2bf30dd 100644 --- a/lib/private/Hooks/PublicEmitter.php +++ b/lib/private/Hooks/PublicEmitter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index c3f8f589827..553a8921a80 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -149,8 +149,8 @@ class Client implements IClient { } private function isLocalAddressAllowed(array $options) : bool { - if (($options['nextcloud']['allow_local_address'] ?? false) || - $this->config->getSystemValueBool('allow_local_remote_servers', false)) { + if (($options['nextcloud']['allow_local_address'] ?? false) + || $this->config->getSystemValueBool('allow_local_remote_servers', false)) { return true; } diff --git a/lib/private/Http/Client/Response.php b/lib/private/Http/Client/Response.php index adf83306d07..1e4cb3b8fa2 100644 --- a/lib/private/Http/Client/Response.php +++ b/lib/private/Http/Client/Response.php @@ -11,49 +11,25 @@ namespace OC\Http\Client; use OCP\Http\Client\IResponse; use Psr\Http\Message\ResponseInterface; -/** - * Class Response - * - * @package OC\Http - */ class Response implements IResponse { - /** @var ResponseInterface */ - private $response; - - /** - * @var bool - */ - private $stream; + private ResponseInterface $response; + private bool $stream; - /** - * @param ResponseInterface $response - * @param bool $stream - */ - public function __construct(ResponseInterface $response, $stream = false) { + public function __construct(ResponseInterface $response, bool $stream = false) { $this->response = $response; $this->stream = $stream; } - /** - * @return string|resource - */ public function getBody() { - return $this->stream ? - $this->response->getBody()->detach(): - $this->response->getBody()->getContents(); + return $this->stream + ? $this->response->getBody()->detach() + :$this->response->getBody()->getContents(); } - /** - * @return int - */ public function getStatusCode(): int { return $this->response->getStatusCode(); } - /** - * @param string $key - * @return string - */ public function getHeader(string $key): string { $headers = $this->response->getHeader($key); @@ -64,9 +40,6 @@ class Response implements IResponse { return $headers[0]; } - /** - * @return array - */ public function getHeaders(): array { return $this->response->getHeaders(); } diff --git a/lib/private/Image.php b/lib/private/Image.php index 96699a0046b..3b8cb79c853 100644 --- a/lib/private/Image.php +++ b/lib/private/Image.php @@ -699,11 +699,11 @@ class Image implements IImage { fclose($fp); unset($fp); - $headerFormat = 'A4Riff/' . // get n string - 'I1Filesize/' . // get integer (file size but not actual size) - 'A4Webp/' . // get n string - 'A4Vp/' . // get n string - 'A74Chunk'; + $headerFormat = 'A4Riff/' // get n string + . 'I1Filesize/' // get integer (file size but not actual size) + . 'A4Webp/' // get n string + . 'A4Vp/' // get n string + . 'A74Chunk'; $header = unpack($headerFormat, $data); unset($data, $headerFormat); diff --git a/lib/private/Installer.php b/lib/private/Installer.php index f32b0e5919a..3bbef3252f4 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace OC; use Doctrine\DBAL\Exception\TableExistsException; +use OC\App\AppStore\AppNotFoundException; use OC\App\AppStore\Bundles\Bundle; use OC\App\AppStore\Fetcher\AppFetcher; use OC\AppFramework\Bootstrap\Coordinator; @@ -174,6 +175,7 @@ class Installer { * @param string $appId * @param bool [$allowUnstable] * + * @throws AppNotFoundException If the app is not found on the appstore * @throws \Exception If the installation was not successful */ public function downloadApp(string $appId, bool $allowUnstable = false): void { @@ -341,6 +343,9 @@ class Installer { // otherwise we just copy the outer directory $this->copyRecursive($extractDir, $baseDir); Files::rmdirr($extractDir); + if (function_exists('opcache_reset')) { + opcache_reset(); + } return; } // Signature does not match @@ -353,9 +358,9 @@ class Installer { } } - throw new \Exception( + throw new AppNotFoundException( sprintf( - 'Could not download app %s', + 'Could not download app %s, it was not found on the appstore', $appId ) ); diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php index 5645693f8d9..6a747744829 100644 --- a/lib/private/L10N/Factory.php +++ b/lib/private/L10N/Factory.php @@ -437,8 +437,8 @@ class Factory implements IFactory { } // Use language from request - if ($this->userSession->getUser() instanceof IUser && - $user->getUID() === $this->userSession->getUser()->getUID()) { + if ($this->userSession->getUser() instanceof IUser + && $user->getUID() === $this->userSession->getUser()->getUID()) { try { return $this->getLanguageFromRequest(); } catch (LanguageNotFoundException $e) { @@ -517,10 +517,10 @@ class Factory implements IFactory { // use formal version of german ("Sie" instead of "Du") if the default // language is set to 'de_DE' if possible if ( - is_string($defaultLanguage) && - strtolower($lang) === 'de' && - strtolower($defaultLanguage) === 'de_de' && - $this->languageExists($app, 'de_DE') + is_string($defaultLanguage) + && strtolower($lang) === 'de' + && strtolower($defaultLanguage) === 'de_de' + && $this->languageExists($app, 'de_DE') ) { $result = 'de_DE'; } diff --git a/lib/private/L10N/LanguageNotFoundException.php b/lib/private/L10N/LanguageNotFoundException.php index 31b08b2ec14..087a384e00e 100644 --- a/lib/private/L10N/LanguageNotFoundException.php +++ b/lib/private/L10N/LanguageNotFoundException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/LargeFileHelper.php b/lib/private/LargeFileHelper.php index 238fb0790b8..fa4c72da756 100755 --- a/lib/private/LargeFileHelper.php +++ b/lib/private/LargeFileHelper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Lockdown/Filesystem/NullCache.php b/lib/private/Lockdown/Filesystem/NullCache.php index e84ff40e00c..5a27c5d5c6e 100644 --- a/lib/private/Lockdown/Filesystem/NullCache.php +++ b/lib/private/Lockdown/Filesystem/NullCache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -21,20 +22,23 @@ class NullCache implements ICache { } public function get($file) { - return $file !== '' ? null : - new CacheEntry([ - 'fileid' => -1, - 'parent' => -1, - 'name' => '', - 'path' => '', - 'size' => '0', - 'mtime' => time(), - 'storage_mtime' => time(), - 'etag' => '', - 'mimetype' => FileInfo::MIMETYPE_FOLDER, - 'mimepart' => 'httpd', - 'permissions' => Constants::PERMISSION_READ - ]); + if ($file !== '') { + return false; + } + + return new CacheEntry([ + 'fileid' => -1, + 'parent' => -1, + 'name' => '', + 'path' => '', + 'size' => '0', + 'mtime' => time(), + 'storage_mtime' => time(), + 'etag' => '', + 'mimetype' => FileInfo::MIMETYPE_FOLDER, + 'mimepart' => 'httpd', + 'permissions' => Constants::PERMISSION_READ + ]); } public function getFolderContents($folder) { diff --git a/lib/private/Lockdown/Filesystem/NullStorage.php b/lib/private/Lockdown/Filesystem/NullStorage.php index 967f8b5108e..71a40d8da1e 100644 --- a/lib/private/Lockdown/Filesystem/NullStorage.php +++ b/lib/private/Lockdown/Filesystem/NullStorage.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Lockdown/LockdownManager.php b/lib/private/Lockdown/LockdownManager.php index 3b45709d5c9..4f351812bad 100644 --- a/lib/private/Lockdown/LockdownManager.php +++ b/lib/private/Lockdown/LockdownManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Log.php b/lib/private/Log.php index 746e4d75b91..301a25d12c1 100644 --- a/lib/private/Log.php +++ b/lib/private/Log.php @@ -306,9 +306,9 @@ class Log implements ILogger, IDataLogger { protected function checkLogSecret(string $conditionSecret): bool { $request = \OCP\Server::get(IRequest::class); - if ($request->getMethod() === 'PUT' && - !str_contains($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded') && - !str_contains($request->getHeader('Content-Type'), 'application/json')) { + if ($request->getMethod() === 'PUT' + && !str_contains($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded') + && !str_contains($request->getHeader('Content-Type'), 'application/json')) { return hash_equals($conditionSecret, ''); } diff --git a/lib/private/Log/ErrorHandler.php b/lib/private/Log/ErrorHandler.php index e1faf336118..6597274a868 100644 --- a/lib/private/Log/ErrorHandler.php +++ b/lib/private/Log/ErrorHandler.php @@ -72,9 +72,9 @@ class ErrorHandler { private static function errnoToLogLevel(int $errno): int { return match ($errno) { - E_USER_WARNING => ILogger::WARN, + E_WARNING, E_USER_WARNING => ILogger::WARN, E_DEPRECATED, E_USER_DEPRECATED => ILogger::DEBUG, - E_USER_NOTICE => ILogger::INFO, + E_NOTICE, E_USER_NOTICE => ILogger::INFO, default => ILogger::ERROR, }; } diff --git a/lib/private/Log/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php index 6d94bf51f45..af7c9e48435 100644 --- a/lib/private/Log/ExceptionSerializer.php +++ b/lib/private/Log/ExceptionSerializer.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Log/LogDetails.php b/lib/private/Log/LogDetails.php index 8c1efaea20d..6063b25cef9 100644 --- a/lib/private/Log/LogDetails.php +++ b/lib/private/Log/LogDetails.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Log/LogFactory.php b/lib/private/Log/LogFactory.php index bbe77de198c..ee6054b81f8 100644 --- a/lib/private/Log/LogFactory.php +++ b/lib/private/Log/LogFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Log/Rotate.php b/lib/private/Log/Rotate.php index 839c40726b7..ee1593b87ac 100644 --- a/lib/private/Log/Rotate.php +++ b/lib/private/Log/Rotate.php @@ -7,6 +7,8 @@ */ namespace OC\Log; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\TimedJob; use OCP\IConfig; use OCP\Log\RotationTrait; use Psr\Log\LoggerInterface; @@ -17,9 +19,15 @@ use Psr\Log\LoggerInterface; * For more professional log management set the 'logfile' config to a different * location and manage that with your own tools. */ -class Rotate extends \OCP\BackgroundJob\Job { +class Rotate extends TimedJob { use RotationTrait; + public function __construct(ITimeFactory $time) { + parent::__construct($time); + + $this->setInterval(3600); + } + public function run($argument): void { $config = \OCP\Server::get(IConfig::class); $this->filePath = $config->getSystemValueString('logfile', $config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/nextcloud.log'); diff --git a/lib/private/Log/Systemdlog.php b/lib/private/Log/Systemdlog.php index 46de0f7db97..ffea0511732 100644 --- a/lib/private/Log/Systemdlog.php +++ b/lib/private/Log/Systemdlog.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Mail/Message.php b/lib/private/Mail/Message.php index faeba469e20..523a4836760 100644 --- a/lib/private/Mail/Message.php +++ b/lib/private/Mail/Message.php @@ -316,7 +316,7 @@ class Message implements IMessage { public function getAutoSubmitted(): string { $headers = $this->symfonyEmail->getHeaders(); - return $headers->has(AutoSubmitted::HEADER) ? - $headers->get(AutoSubmitted::HEADER)->getBodyAsString() : AutoSubmitted::VALUE_NO; + return $headers->has(AutoSubmitted::HEADER) + ? $headers->get(AutoSubmitted::HEADER)->getBodyAsString() : AutoSubmitted::VALUE_NO; } } diff --git a/lib/private/Mail/Provider/Manager.php b/lib/private/Mail/Provider/Manager.php index 61791620198..f162d30b834 100644 --- a/lib/private/Mail/Provider/Manager.php +++ b/lib/private/Mail/Provider/Manager.php @@ -17,7 +17,7 @@ use Psr\Log\LoggerInterface; use Throwable; class Manager implements IManager { - + protected ?array $providersCollection = null; public function __construct( @@ -63,7 +63,7 @@ class Manager implements IManager { * @return array<string,string> collection of provider id and label ['jmap' => 'JMap Connector'] */ public function types(): array { - + // construct types collection $types = []; // extract id and name from providers collection @@ -72,7 +72,7 @@ class Manager implements IManager { } // return types collection return $types; - + } /** @@ -131,7 +131,7 @@ class Manager implements IManager { if (!is_array($this->providersCollection)) { $this->providers(); } - + if (isset($this->providersCollection[$providerId])) { return $this->providersCollection[$providerId]; } @@ -150,7 +150,7 @@ class Manager implements IManager { * @return array<string,array<string,IService>> collection of provider id, service id and object ['jmap' => ['Service1' => IServiceObject]] */ public function services(string $userId): array { - + // initilize collection $services = []; // retrieve and iterate through mail providers @@ -164,7 +164,7 @@ class Manager implements IManager { } // return collection return $services; - + } /** @@ -179,7 +179,7 @@ class Manager implements IManager { * @return IService|null returns service object or null if none found */ public function findServiceById(string $userId, string $serviceId, ?string $providerId = null): ?IService { - + // evaluate if provider id was specified if ($providerId !== null) { // find provider @@ -204,7 +204,7 @@ class Manager implements IManager { } } } - + // return null if no match was found return null; @@ -223,7 +223,7 @@ class Manager implements IManager { * @return IService|null returns service object or null if none found */ public function findServiceByAddress(string $userId, string $address, ?string $providerId = null): ?IService { - + // evaluate if provider id was specified if ($providerId !== null) { // find provider diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php index 024462d227b..937f8a863ab 100644 --- a/lib/private/Memcache/APCu.php +++ b/lib/private/Memcache/APCu.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php index 4cac60c272c..9b3540b771f 100644 --- a/lib/private/Memcache/ArrayCache.php +++ b/lib/private/Memcache/ArrayCache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/CADTrait.php b/lib/private/Memcache/CADTrait.php index 3bf94246338..d0f6611c4f3 100644 --- a/lib/private/Memcache/CADTrait.php +++ b/lib/private/Memcache/CADTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/CASTrait.php b/lib/private/Memcache/CASTrait.php index 945f1539f99..8c2d2a46b19 100644 --- a/lib/private/Memcache/CASTrait.php +++ b/lib/private/Memcache/CASTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/Cache.php b/lib/private/Memcache/Cache.php index 2a2a6e2a23f..774769b25fe 100644 --- a/lib/private/Memcache/Cache.php +++ b/lib/private/Memcache/Cache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php index a44f0127651..b54189937fc 100644 --- a/lib/private/Memcache/Factory.php +++ b/lib/private/Memcache/Factory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -134,8 +135,8 @@ class Factory implements ICacheFactory { $this->profiler->add($cache); } - if ($this->lockingCacheClass === Redis::class && - $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + if ($this->lockingCacheClass === Redis::class + && $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { $cache = new LoggerWrapperCache($cache, $this->logFile); } return $cache; diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php index 620013feda6..d8b624a978a 100644 --- a/lib/private/Memcache/Memcached.php +++ b/lib/private/Memcache/Memcached.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -48,8 +49,8 @@ class Memcached extends Cache implements IMemcache { * @psalm-suppress TypeDoesNotContainType */ if (\Memcached::HAVE_IGBINARY) { - $defaultOptions[\Memcached::OPT_SERIALIZER] = - \Memcached::SERIALIZER_IGBINARY; + $defaultOptions[\Memcached::OPT_SERIALIZER] + = \Memcached::SERIALIZER_IGBINARY; } $options = \OC::$server->getConfig()->getSystemValue('memcached_options', []); if (is_array($options)) { diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php index b667869bf0d..eac1e6ddadc 100644 --- a/lib/private/Memcache/NullCache.php +++ b/lib/private/Memcache/NullCache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index 711531e0ac2..f8c51570c4f 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Migration/ConsoleOutput.php b/lib/private/Migration/ConsoleOutput.php index 7ccc4e7825a..31412bf4ff0 100644 --- a/lib/private/Migration/ConsoleOutput.php +++ b/lib/private/Migration/ConsoleOutput.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2015 ownCloud GmbH diff --git a/lib/private/Migration/NullOutput.php b/lib/private/Migration/NullOutput.php index 3f4cc38dba8..8db7b950af8 100644 --- a/lib/private/Migration/NullOutput.php +++ b/lib/private/Migration/NullOutput.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-only diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php index 31420d49932..b7a07cc6ff2 100644 --- a/lib/private/Migration/SimpleOutput.php +++ b/lib/private/Migration/SimpleOutput.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2015 ownCloud GmbH diff --git a/lib/private/Notification/Manager.php b/lib/private/Notification/Manager.php index b75e52deacb..8c457db8beb 100644 --- a/lib/private/Notification/Manager.php +++ b/lib/private/Notification/Manager.php @@ -217,7 +217,9 @@ class Manager implements IManager { * @since 8.2.0 */ public function hasNotifiers(): bool { - return !empty($this->notifiers) || !empty($this->notifierClasses); + return !empty($this->notifiers) + || !empty($this->notifierClasses) + || (!$this->parsedRegistrationContext && !empty($this->coordinator->getRegistrationContext()->getNotifierServices())); } /** diff --git a/lib/private/Notification/Notification.php b/lib/private/Notification/Notification.php index f8f1e247854..fcce7fd0020 100644 --- a/lib/private/Notification/Notification.php +++ b/lib/private/Notification/Notification.php @@ -429,8 +429,7 @@ class Notification implements INotification { public function isValid(): bool { return $this->isValidCommon() - && - $this->getSubject() !== '' + && $this->getSubject() !== '' ; } @@ -456,8 +455,7 @@ class Notification implements INotification { return $this->isValidCommon() - && - $this->getParsedSubject() !== '' + && $this->getParsedSubject() !== '' ; } @@ -468,14 +466,10 @@ class Notification implements INotification { return $this->getApp() !== '' - && - $this->getUser() !== '' - && - $this->getDateTime()->getTimestamp() !== 0 - && - $this->getObjectType() !== '' - && - $this->getObjectId() !== '' + && $this->getUser() !== '' + && $this->getDateTime()->getTimestamp() !== 0 + && $this->getObjectType() !== '' + && $this->getObjectId() !== '' ; } } diff --git a/lib/private/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php index f4b0ac584de..be13d65a40f 100644 --- a/lib/private/OCM/Model/OCMProvider.php +++ b/lib/private/OCM/Model/OCMProvider.php @@ -11,18 +11,22 @@ namespace OC\OCM\Model; use NCU\Security\Signature\Model\Signatory; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; use OCP\OCM\Events\ResourceTypeRegisterEvent; use OCP\OCM\Exceptions\OCMArgumentException; use OCP\OCM\Exceptions\OCMProviderException; -use OCP\OCM\IOCMProvider; +use OCP\OCM\ICapabilityAwareOCMProvider; use OCP\OCM\IOCMResource; /** * @since 28.0.0 */ -class OCMProvider implements IOCMProvider { +class OCMProvider implements ICapabilityAwareOCMProvider { + private string $provider; private bool $enabled = false; private string $apiVersion = ''; + private string $inviteAcceptDialog = ''; + private array $capabilities = []; private string $endPoint = ''; /** @var IOCMResource[] */ private array $resourceTypes = []; @@ -31,7 +35,9 @@ class OCMProvider implements IOCMProvider { public function __construct( protected IEventDispatcher $dispatcher, + protected IConfig $config, ) { + $this->provider = 'Nextcloud ' . $config->getSystemValue('version'); } /** @@ -71,6 +77,30 @@ class OCMProvider implements IOCMProvider { } /** + * returns the invite accept dialog + * + * @return string + * @since 32.0.0 + */ + public function getInviteAcceptDialog(): string { + return $this->inviteAcceptDialog; + } + + /** + * set the invite accept dialog + * + * @param string $inviteAcceptDialog + * + * @return $this + * @since 32.0.0 + */ + public function setInviteAcceptDialog(string $inviteAcceptDialog): static { + $this->inviteAcceptDialog = $inviteAcceptDialog; + + return $this; + } + + /** * @param string $endPoint * * @return $this @@ -89,6 +119,34 @@ class OCMProvider implements IOCMProvider { } /** + * @return string + */ + public function getProvider(): string { + return $this->provider; + } + + /** + * @param array $capabilities + * + * @return $this + */ + public function setCapabilities(array $capabilities): static { + foreach ($capabilities as $value) { + if (!in_array($value, $this->capabilities)) { + array_push($this->capabilities, $value); + } + } + + return $this; + } + + /** + * @return array + */ + public function getCapabilities(): array { + return $this->capabilities; + } + /** * create a new resource to later add it with {@see IOCMProvider::addResourceType()} * @return IOCMResource */ @@ -166,9 +224,8 @@ class OCMProvider implements IOCMProvider { * * @param array $data * - * @return $this + * @return OCMProvider&static * @throws OCMProviderException in case a descent provider cannot be generated from data - * @see self::jsonSerialize() */ public function import(array $data): static { $this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false) @@ -209,21 +266,7 @@ class OCMProvider implements IOCMProvider { } /** - * @return array{ - * enabled: bool, - * apiVersion: '1.0-proposal1', - * endPoint: string, - * publicKey?: array{ - * keyId: string, - * publicKeyPem: string - * }, - * resourceTypes: list<array{ - * name: string, - * shareTypes: list<string>, - * protocols: array<string, string> - * }>, - * version: string - * } + * @since 28.0.0 */ public function jsonSerialize(): array { $resourceTypes = []; @@ -231,7 +274,7 @@ class OCMProvider implements IOCMProvider { $resourceTypes[] = $res->jsonSerialize(); } - return [ + $response = [ 'enabled' => $this->isEnabled(), 'apiVersion' => '1.0-proposal1', // deprecated, but keep it to stay compatible with old version 'version' => $this->getApiVersion(), // informative but real version @@ -239,5 +282,16 @@ class OCMProvider implements IOCMProvider { 'publicKey' => $this->getSignatory()?->jsonSerialize(), 'resourceTypes' => $resourceTypes ]; + + $capabilities = $this->getCapabilities(); + $inviteAcceptDialog = $this->getInviteAcceptDialog(); + if ($capabilities) { + $response['capabilities'] = $capabilities; + } + if ($inviteAcceptDialog) { + $response['inviteAcceptDialog'] = $inviteAcceptDialog; + } + return $response; + } } diff --git a/lib/private/OCM/OCMDiscoveryService.php b/lib/private/OCM/OCMDiscoveryService.php index af612416372..a151bbc753c 100644 --- a/lib/private/OCM/OCMDiscoveryService.php +++ b/lib/private/OCM/OCMDiscoveryService.php @@ -17,8 +17,8 @@ use OCP\ICache; use OCP\ICacheFactory; use OCP\IConfig; use OCP\OCM\Exceptions\OCMProviderException; +use OCP\OCM\ICapabilityAwareOCMProvider; use OCP\OCM\IOCMDiscoveryService; -use OCP\OCM\IOCMProvider; use Psr\Log\LoggerInterface; /** @@ -31,7 +31,7 @@ class OCMDiscoveryService implements IOCMDiscoveryService { ICacheFactory $cacheFactory, private IClientService $clientService, private IConfig $config, - private IOCMProvider $provider, + private ICapabilityAwareOCMProvider $provider, private LoggerInterface $logger, ) { $this->cache = $cacheFactory->createDistributed('ocm-discovery'); @@ -42,10 +42,10 @@ class OCMDiscoveryService implements IOCMDiscoveryService { * @param string $remote * @param bool $skipCache * - * @return IOCMProvider + * @return ICapabilityAwareOCMProvider * @throws OCMProviderException */ - public function discover(string $remote, bool $skipCache = false): IOCMProvider { + public function discover(string $remote, bool $skipCache = false): ICapabilityAwareOCMProvider { $remote = rtrim($remote, '/'); if (!str_starts_with($remote, 'http://') && !str_starts_with($remote, 'https://')) { // if scheme not specified, we test both; diff --git a/lib/private/Preview/Bundled.php b/lib/private/Preview/Bundled.php index 836dc4bd357..6100e8262a4 100644 --- a/lib/private/Preview/Bundled.php +++ b/lib/private/Preview/Bundled.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Preview/GeneratorHelper.php b/lib/private/Preview/GeneratorHelper.php index 5f43c94b624..e914dcc2002 100644 --- a/lib/private/Preview/GeneratorHelper.php +++ b/lib/private/Preview/GeneratorHelper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php index baa883f4bd9..d421da74ac8 100644 --- a/lib/private/Preview/Imaginary.php +++ b/lib/private/Preview/Imaginary.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Preview/Krita.php b/lib/private/Preview/Krita.php index 2e77c7befd2..e96fac993aa 100644 --- a/lib/private/Preview/Krita.php +++ b/lib/private/Preview/Krita.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Preview/MimeIconProvider.php b/lib/private/Preview/MimeIconProvider.php index 80545bd4063..d1963fe882b 100644 --- a/lib/private/Preview/MimeIconProvider.php +++ b/lib/private/Preview/MimeIconProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -54,7 +55,7 @@ class MimeIconProvider implements IMimeIconProvider { return null; } - + private function searchfileName(string $fileName): ?string { // If the file exists in the current enabled legacy // custom theme, let's return it @@ -65,7 +66,7 @@ class MimeIconProvider implements IMimeIconProvider { return $this->urlGenerator->getAbsoluteURL($path); } } - + // Previously, we used to pass this through Theming // But it was only used to colour icons containing // 0082c9. Since with vue we moved to inline svg icons, diff --git a/lib/private/Preview/Movie.php b/lib/private/Preview/Movie.php index 7de543198f4..47895f999d8 100644 --- a/lib/private/Preview/Movie.php +++ b/lib/private/Preview/Movie.php @@ -166,8 +166,8 @@ class Movie extends ProviderV2 { $returnCode = -1; $output = ''; if (is_resource($proc)) { - $stdout = trim(stream_get_contents($pipes[1])); $stderr = trim(stream_get_contents($pipes[2])); + $stdout = trim(stream_get_contents($pipes[1])); $returnCode = proc_close($proc); $output = $stdout . $stderr; } diff --git a/lib/private/Preview/SGI.php b/lib/private/Preview/SGI.php index 06ea9c0c69a..78b1ea5828a 100644 --- a/lib/private/Preview/SGI.php +++ b/lib/private/Preview/SGI.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Preview/TGA.php b/lib/private/Preview/TGA.php index 62e5aadc2af..675907b4e49 100644 --- a/lib/private/Preview/TGA.php +++ b/lib/private/Preview/TGA.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index 1f618dab22d..0bb0280406c 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -154,7 +154,7 @@ class PreviewManager implements IPreview { $mimeType = null, bool $cacheResult = true, ): ISimpleFile { - $this->throwIfPreviewsDisabled(); + $this->throwIfPreviewsDisabled($file, $mimeType); $previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all'); $sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency); try { @@ -178,7 +178,7 @@ class PreviewManager implements IPreview { * @since 19.0.0 */ public function generatePreviews(File $file, array $specifications, $mimeType = null) { - $this->throwIfPreviewsDisabled(); + $this->throwIfPreviewsDisabled($file, $mimeType); return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType); } @@ -213,13 +213,15 @@ class PreviewManager implements IPreview { /** * Check if a preview can be generated for a file */ - public function isAvailable(\OCP\Files\FileInfo $file): bool { + public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null): bool { if (!$this->enablePreviews) { return false; } + $fileMimeType = $mimeType ?? $file->getMimeType(); + $this->registerCoreProviders(); - if (!$this->isMimeSupported($file->getMimetype())) { + if (!$this->isMimeSupported($fileMimeType)) { return false; } @@ -229,7 +231,7 @@ class PreviewManager implements IPreview { } foreach ($this->providers as $supportedMimeType => $providers) { - if (preg_match($supportedMimeType, $file->getMimetype())) { + if (preg_match($supportedMimeType, $fileMimeType)) { foreach ($providers as $providerClosure) { $provider = $this->helper->getProvider($providerClosure); if (!($provider instanceof IProviderV2)) { @@ -455,8 +457,8 @@ class PreviewManager implements IPreview { /** * @throws NotFoundException if preview generation is disabled */ - private function throwIfPreviewsDisabled(): void { - if (!$this->enablePreviews) { + private function throwIfPreviewsDisabled(File $file, ?string $mimeType = null): void { + if (!$this->isAvailable($file, $mimeType)) { throw new NotFoundException('Previews disabled'); } } diff --git a/lib/private/PreviewNotAvailableException.php b/lib/private/PreviewNotAvailableException.php index b1fec912769..582f337acd1 100644 --- a/lib/private/PreviewNotAvailableException.php +++ b/lib/private/PreviewNotAvailableException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php index 13860286356..84c7ea48373 100644 --- a/lib/private/Profile/ProfileManager.php +++ b/lib/private/Profile/ProfileManager.php @@ -247,8 +247,8 @@ class ProfileManager implements IProfileManager { case IAccountManager::PROPERTY_ORGANISATION: case IAccountManager::PROPERTY_ROLE: case IAccountManager::PROPERTY_PRONOUNS: - $profileParameters[$property] = - $this->isProfileFieldVisible($property, $targetUser, $visitingUser) + $profileParameters[$property] + = $this->isProfileFieldVisible($property, $targetUser, $visitingUser) // Explicitly set to null when value is empty string ? ($account->getProperty($property)->getValue() ?: null) : null; diff --git a/lib/private/RedisFactory.php b/lib/private/RedisFactory.php index dcb56cee9ef..f13f9299c1c 100644 --- a/lib/private/RedisFactory.php +++ b/lib/private/RedisFactory.php @@ -152,8 +152,8 @@ class RedisFactory { } public function isAvailable(): bool { - return \extension_loaded('redis') && - \version_compare(\phpversion('redis'), self::REDIS_MINIMAL_VERSION, '>='); + return \extension_loaded('redis') + && \version_compare(\phpversion('redis'), self::REDIS_MINIMAL_VERSION, '>='); } /** @@ -163,7 +163,7 @@ class RedisFactory { * @return boolean */ private function isConnectionParametersSupported(): bool { - return \extension_loaded('redis') && - \version_compare(\phpversion('redis'), self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION, '>='); + return \extension_loaded('redis') + && \version_compare(\phpversion('redis'), self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION, '>='); } } diff --git a/lib/private/Remote/Api/ApiBase.php b/lib/private/Remote/Api/ApiBase.php index dff3edb51b9..b2f96fb3c24 100644 --- a/lib/private/Remote/Api/ApiBase.php +++ b/lib/private/Remote/Api/ApiBase.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Remote/Api/ApiCollection.php b/lib/private/Remote/Api/ApiCollection.php index 65039f4b5aa..f154cd21926 100644 --- a/lib/private/Remote/Api/ApiCollection.php +++ b/lib/private/Remote/Api/ApiCollection.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Remote/Api/ApiFactory.php b/lib/private/Remote/Api/ApiFactory.php index 7daddd16011..795584e566a 100644 --- a/lib/private/Remote/Api/ApiFactory.php +++ b/lib/private/Remote/Api/ApiFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Remote/Api/NotFoundException.php b/lib/private/Remote/Api/NotFoundException.php index 5251313f5f0..361d03d24ed 100644 --- a/lib/private/Remote/Api/NotFoundException.php +++ b/lib/private/Remote/Api/NotFoundException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Remote/Api/OCS.php b/lib/private/Remote/Api/OCS.php index de07eb8bc56..36bc22751a5 100644 --- a/lib/private/Remote/Api/OCS.php +++ b/lib/private/Remote/Api/OCS.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Remote/Credentials.php b/lib/private/Remote/Credentials.php index fb0f03ae148..7f1ffaa727c 100644 --- a/lib/private/Remote/Credentials.php +++ b/lib/private/Remote/Credentials.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Remote/Instance.php b/lib/private/Remote/Instance.php index ac3233b93c9..10403af4ec7 100644 --- a/lib/private/Remote/Instance.php +++ b/lib/private/Remote/Instance.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -123,7 +124,13 @@ class Instance implements IInstance { private function downloadStatus($url) { try { $request = $this->clientService->newClient()->get($url); - return $request->getBody(); + $content = $request->getBody(); + + // IResponse.getBody responds with null|resource if returning a stream response was requested. + // As that's not the case here, we can just ignore the psalm warning by adding an assertion. + assert(is_string($content)); + + return $content; } catch (\Exception $e) { return false; } diff --git a/lib/private/Remote/InstanceFactory.php b/lib/private/Remote/InstanceFactory.php index f3047b851b2..f1b7a1de4ba 100644 --- a/lib/private/Remote/InstanceFactory.php +++ b/lib/private/Remote/InstanceFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Remote/User.php b/lib/private/Remote/User.php index 5c8e9d3ca4e..ae1032cdc1c 100644 --- a/lib/private/Remote/User.php +++ b/lib/private/Remote/User.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/AddCleanupUpdaterBackupsJob.php b/lib/private/Repair/AddCleanupUpdaterBackupsJob.php index 8bd938b7e3a..e631a3303f1 100644 --- a/lib/private/Repair/AddCleanupUpdaterBackupsJob.php +++ b/lib/private/Repair/AddCleanupUpdaterBackupsJob.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/AddMetadataGenerationJob.php b/lib/private/Repair/AddMetadataGenerationJob.php index 4535fb0c9e0..76c60f303a7 100644 --- a/lib/private/Repair/AddMetadataGenerationJob.php +++ b/lib/private/Repair/AddMetadataGenerationJob.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/ClearFrontendCaches.php b/lib/private/Repair/ClearFrontendCaches.php index 77a3df5598a..5c57a63379d 100644 --- a/lib/private/Repair/ClearFrontendCaches.php +++ b/lib/private/Repair/ClearFrontendCaches.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/ClearGeneratedAvatarCache.php b/lib/private/Repair/ClearGeneratedAvatarCache.php index 2dea4bd2d61..0f743afbb4c 100644 --- a/lib/private/Repair/ClearGeneratedAvatarCache.php +++ b/lib/private/Repair/ClearGeneratedAvatarCache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php index 38cf03b731a..524a470e62a 100644 --- a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php +++ b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/Collation.php b/lib/private/Repair/Collation.php index 5a309892bf0..43229792217 100644 --- a/lib/private/Repair/Collation.php +++ b/lib/private/Repair/Collation.php @@ -97,11 +97,11 @@ class Collation implements IRepairStep { // fetch tables by columns $statement = $connection->executeQuery( - 'SELECT DISTINCT(TABLE_NAME) AS `table`' . - ' FROM INFORMATION_SCHEMA . COLUMNS' . - ' WHERE TABLE_SCHEMA = ?' . - " AND (COLLATION_NAME <> '" . $characterSet . "_bin' OR CHARACTER_SET_NAME <> '" . $characterSet . "')" . - " AND TABLE_NAME LIKE '*PREFIX*%'", + 'SELECT DISTINCT(TABLE_NAME) AS `table`' + . ' FROM INFORMATION_SCHEMA . COLUMNS' + . ' WHERE TABLE_SCHEMA = ?' + . " AND (COLLATION_NAME <> '" . $characterSet . "_bin' OR CHARACTER_SET_NAME <> '" . $characterSet . "')" + . " AND TABLE_NAME LIKE '*PREFIX*%'", [$dbName] ); $rows = $statement->fetchAll(); @@ -112,11 +112,11 @@ class Collation implements IRepairStep { // fetch tables by collation $statement = $connection->executeQuery( - 'SELECT DISTINCT(TABLE_NAME) AS `table`' . - ' FROM INFORMATION_SCHEMA . TABLES' . - ' WHERE TABLE_SCHEMA = ?' . - " AND TABLE_COLLATION <> '" . $characterSet . "_bin'" . - " AND TABLE_NAME LIKE '*PREFIX*%'", + 'SELECT DISTINCT(TABLE_NAME) AS `table`' + . ' FROM INFORMATION_SCHEMA . TABLES' + . ' WHERE TABLE_SCHEMA = ?' + . " AND TABLE_COLLATION <> '" . $characterSet . "_bin'" + . " AND TABLE_NAME LIKE '*PREFIX*%'", [$dbName] ); $rows = $statement->fetchAll(); diff --git a/lib/private/Repair/ConfigKeyMigration.php b/lib/private/Repair/ConfigKeyMigration.php new file mode 100644 index 00000000000..da4aa153dc5 --- /dev/null +++ b/lib/private/Repair/ConfigKeyMigration.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Repair; + +use OC\Config\ConfigManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class ConfigKeyMigration implements IRepairStep { + public function __construct( + private ConfigManager $configManager, + ) { + } + + public function getName(): string { + return 'Migrate config keys'; + } + + public function run(IOutput $output) { + $this->configManager->migrateConfigLexiconKeys(); + } +} diff --git a/lib/private/Repair/MoveUpdaterStepFile.php b/lib/private/Repair/MoveUpdaterStepFile.php index eb9f78b0a39..bb8f9d3acfc 100644 --- a/lib/private/Repair/MoveUpdaterStepFile.php +++ b/lib/private/Repair/MoveUpdaterStepFile.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/NC13/AddLogRotateJob.php b/lib/private/Repair/NC13/AddLogRotateJob.php index 8fe68a42819..bd6c510785f 100644 --- a/lib/private/Repair/NC13/AddLogRotateJob.php +++ b/lib/private/Repair/NC13/AddLogRotateJob.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php b/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php index 4f80b3809e8..5cee33b381c 100644 --- a/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php +++ b/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/Owncloud/CleanPreviews.php b/lib/private/Repair/Owncloud/CleanPreviews.php index 86e173cf402..50ee965e087 100644 --- a/lib/private/Repair/Owncloud/CleanPreviews.php +++ b/lib/private/Repair/Owncloud/CleanPreviews.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/Owncloud/DropAccountTermsTable.php b/lib/private/Repair/Owncloud/DropAccountTermsTable.php index 18f169c9b49..534825c146a 100644 --- a/lib/private/Repair/Owncloud/DropAccountTermsTable.php +++ b/lib/private/Repair/Owncloud/DropAccountTermsTable.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/Owncloud/MoveAvatars.php b/lib/private/Repair/Owncloud/MoveAvatars.php index 7fdabae7a66..9e3f4b89b13 100644 --- a/lib/private/Repair/Owncloud/MoveAvatars.php +++ b/lib/private/Repair/Owncloud/MoveAvatars.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/Owncloud/SaveAccountsTableData.php b/lib/private/Repair/Owncloud/SaveAccountsTableData.php index 08665687b29..ab1560ddb8d 100644 --- a/lib/private/Repair/Owncloud/SaveAccountsTableData.php +++ b/lib/private/Repair/Owncloud/SaveAccountsTableData.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php index e27ab06b2f3..8d9046ad49f 100644 --- a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php +++ b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Repair/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php index 28b22ec3f20..3c9720b9e91 100644 --- a/lib/private/Repair/RepairMimeTypes.php +++ b/lib/private/Repair/RepairMimeTypes.php @@ -350,7 +350,7 @@ class RepairMimeTypes implements IRepairStep { return $this->updateMimetypes($updatedMimetypes); } - + /** * Check if there are any migrations available diff --git a/lib/private/Route/CachingRouter.php b/lib/private/Route/CachingRouter.php index 7dd26827d3c..becdb807f73 100644 --- a/lib/private/Route/CachingRouter.php +++ b/lib/private/Route/CachingRouter.php @@ -15,10 +15,16 @@ use OCP\IConfig; use OCP\IRequest; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\CompiledUrlMatcher; +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; +use Symfony\Component\Routing\RouteCollection; class CachingRouter extends Router { protected ICache $cache; + protected array $legacyCreatedRoutes = []; + public function __construct( ICacheFactory $cacheFactory, LoggerInterface $logger, @@ -54,4 +60,98 @@ class CachingRouter extends Router { return $url; } } + + private function serializeRouteCollection(RouteCollection $collection): array { + $dumper = new CompiledUrlMatcherDumper($collection); + return $dumper->getCompiledRoutes(); + } + + /** + * Find the route matching $url + * + * @param string $url The url to find + * @throws \Exception + * @return array + */ + public function findMatchingRoute(string $url): array { + $this->eventLogger->start('cacheroute:match', 'Match route'); + $key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . '#rootCollection'; + $cachedRoutes = $this->cache->get($key); + if (!$cachedRoutes) { + parent::loadRoutes(); + $cachedRoutes = $this->serializeRouteCollection($this->root); + $this->cache->set($key, $cachedRoutes, ($this->config->getSystemValueBool('debug') ? 3 : 3600)); + } + $matcher = new CompiledUrlMatcher($cachedRoutes, $this->context); + $this->eventLogger->start('cacheroute:url:match', 'Symfony URL match call'); + try { + $parameters = $matcher->match($url); + } catch (ResourceNotFoundException $e) { + if (!str_ends_with($url, '/')) { + // We allow links to apps/files? for backwards compatibility reasons + // However, since Symfony does not allow empty route names, the route + // we need to match is '/', so we need to append the '/' here. + try { + $parameters = $matcher->match($url . '/'); + } catch (ResourceNotFoundException $newException) { + // If we still didn't match a route, we throw the original exception + throw $e; + } + } else { + throw $e; + } + } + $this->eventLogger->end('cacheroute:url:match'); + + $this->eventLogger->end('cacheroute:match'); + return $parameters; + } + + /** + * @param array{action:mixed, ...} $parameters + */ + protected function callLegacyActionRoute(array $parameters): void { + /* + * Closures cannot be serialized to cache, so for legacy routes calling an action we have to include the routes.php file again + */ + $app = $parameters['app']; + $this->useCollection($app); + parent::requireRouteFile($parameters['route-file'], $app); + $collection = $this->getCollection($app); + $parameters['action'] = $collection->get($parameters['_route'])?->getDefault('action'); + parent::callLegacyActionRoute($parameters); + } + + /** + * Create a \OC\Route\Route. + * Deprecated + * + * @param string $name Name of the route to create. + * @param string $pattern The pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + */ + public function create($name, $pattern, array $defaults = [], array $requirements = []): Route { + $this->legacyCreatedRoutes[] = $name; + return parent::create($name, $pattern, $defaults, $requirements); + } + + /** + * Require a routes.php file + */ + protected function requireRouteFile(string $file, string $appName): void { + $this->legacyCreatedRoutes = []; + parent::requireRouteFile($file, $appName); + foreach ($this->legacyCreatedRoutes as $routeName) { + $route = $this->collection?->get($routeName); + if ($route === null) { + /* Should never happen */ + throw new \Exception("Could not find route $routeName"); + } + if ($route->hasDefault('action')) { + $route->setDefault('route-file', $file); + $route->setDefault('app', $appName); + } + } + } } diff --git a/lib/private/Route/Route.php b/lib/private/Route/Route.php index ab5a1f6b59a..08231649e76 100644 --- a/lib/private/Route/Route.php +++ b/lib/private/Route/Route.php @@ -124,15 +124,9 @@ class Route extends SymfonyRoute implements IRoute { * The action to execute when this route matches, includes a file like * it is called directly * @param string $file - * @return void */ public function actionInclude($file) { - $function = function ($param) use ($file) { - unset($param['_route']); - $_GET = array_merge($_GET, $param); - unset($param); - require_once "$file"; - } ; - $this->action($function); + $this->setDefault('file', $file); + return $this; } } diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index 376852a1b6e..90225212e9a 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -53,7 +53,7 @@ class Router implements IRouter { public function __construct( protected LoggerInterface $logger, IRequest $request, - private IConfig $config, + protected IConfig $config, protected IEventLogger $eventLogger, private ContainerInterface $container, protected IAppManager $appManager, @@ -74,6 +74,14 @@ class Router implements IRouter { $this->root = $this->getCollection('root'); } + public function setContext(RequestContext $context): void { + $this->context = $context; + } + + public function getRouteCollection() { + return $this->root; + } + /** * Get the files to load the routes from * @@ -82,7 +90,7 @@ class Router implements IRouter { public function getRoutingFiles() { if ($this->routingFiles === null) { $this->routingFiles = []; - foreach (\OC_APP::getEnabledApps() as $app) { + foreach ($this->appManager->getEnabledApps() as $app) { try { $appPath = $this->appManager->getAppPath($app); $file = $appPath . '/appinfo/routes.php'; @@ -102,7 +110,7 @@ class Router implements IRouter { * * @param null|string $app */ - public function loadRoutes($app = null) { + public function loadRoutes(?string $app = null, bool $skipLoadingCore = false): void { if (is_string($app)) { $app = $this->appManager->cleanAppId($app); } @@ -117,7 +125,7 @@ class Router implements IRouter { $routingFiles = $this->getRoutingFiles(); $this->eventLogger->start('route:load:attributes', 'Loading Routes from attributes'); - foreach (\OC_App::getEnabledApps() as $enabledApp) { + foreach ($this->appManager->getEnabledApps() as $enabledApp) { $this->loadAttributeRoutes($enabledApp); } $this->eventLogger->end('route:load:attributes'); @@ -165,7 +173,7 @@ class Router implements IRouter { } $this->eventLogger->end('route:load:files'); - if (!isset($this->loadedApps['core'])) { + if (!$skipLoadingCore && !isset($this->loadedApps['core'])) { $this->loadedApps['core'] = true; $this->useCollection('root'); $this->setupRoutes($this->getAttributeRoutes('core'), 'core'); @@ -312,17 +320,11 @@ class Router implements IRouter { $application = $this->getApplicationClass($caller[0]); \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters); } elseif (isset($parameters['action'])) { - $action = $parameters['action']; - if (!is_callable($action)) { - throw new \Exception('not a callable action'); - } - unset($parameters['action']); - unset($parameters['caller']); - $this->eventLogger->start('route:run:call', 'Run callable route'); - call_user_func($action, $parameters); - $this->eventLogger->end('route:run:call'); + $this->logger->warning('Deprecated action route used', ['parameters' => $parameters]); + $this->callLegacyActionRoute($parameters); } elseif (isset($parameters['file'])) { - include $parameters['file']; + $this->logger->debug('Deprecated file route used', ['parameters' => $parameters]); + $this->includeLegacyFileRoute($parameters); } else { throw new \Exception('no action available'); } @@ -330,6 +332,32 @@ class Router implements IRouter { } /** + * @param array{file:mixed, ...} $parameters + */ + protected function includeLegacyFileRoute(array $parameters): void { + $param = $parameters; + unset($param['_route']); + $_GET = array_merge($_GET, $param); + unset($param); + require_once $parameters['file']; + } + + /** + * @param array{action:mixed, ...} $parameters + */ + protected function callLegacyActionRoute(array $parameters): void { + $action = $parameters['action']; + if (!is_callable($action)) { + throw new \Exception('not a callable action'); + } + unset($parameters['action']); + unset($parameters['caller']); + $this->eventLogger->start('route:run:call', 'Run callable route'); + call_user_func($action, $parameters); + $this->eventLogger->end('route:run:call'); + } + + /** * Get the url generator * * @return \Symfony\Component\Routing\Generator\UrlGenerator @@ -492,7 +520,7 @@ class Router implements IRouter { * @param string $file the route file location to include * @param string $appName */ - private function requireRouteFile($file, $appName) { + protected function requireRouteFile(string $file, string $appName): void { $this->setupRoutes(include $file, $appName); } diff --git a/lib/private/Security/Hasher.php b/lib/private/Security/Hasher.php index ba661f5a356..722fdab902f 100644 --- a/lib/private/Security/Hasher.php +++ b/lib/private/Security/Hasher.php @@ -106,8 +106,8 @@ class Hasher implements IHasher { // Verify whether it matches a legacy PHPass or SHA1 string $hashLength = \strlen($hash); - if (($hashLength === 60 && password_verify($message . $this->legacySalt, $hash)) || - ($hashLength === 40 && hash_equals($hash, sha1($message)))) { + if (($hashLength === 60 && password_verify($message . $this->legacySalt, $hash)) + || ($hashLength === 40 && hash_equals($hash, sha1($message)))) { $newHash = $this->hash($message); return true; } @@ -115,8 +115,8 @@ class Hasher implements IHasher { // Verify whether it matches a legacy PHPass or SHA1 string // Retry with empty passwordsalt for cases where it was not set $hashLength = \strlen($hash); - if (($hashLength === 60 && password_verify($message, $hash)) || - ($hashLength === 40 && hash_equals($hash, sha1($message)))) { + if (($hashLength === 60 && password_verify($message, $hash)) + || ($hashLength === 40 && hash_equals($hash, sha1($message)))) { $newHash = $this->hash($message); return true; } diff --git a/lib/private/Security/Signature/Model/SignedRequest.php b/lib/private/Security/Signature/Model/SignedRequest.php index f30935e83b1..12a43f32bcc 100644 --- a/lib/private/Security/Signature/Model/SignedRequest.php +++ b/lib/private/Security/Signature/Model/SignedRequest.php @@ -74,8 +74,8 @@ class SignedRequest implements ISignedRequest, JsonSerializable { */ public function getDigest(): string { if ($this->digest === '') { - $this->digest = $this->digestAlgorithm->value . '=' . - base64_encode(hash($this->digestAlgorithm->getHashingAlgorithm(), $this->body, true)); + $this->digest = $this->digestAlgorithm->value . '=' + . base64_encode(hash($this->digestAlgorithm->getHashingAlgorithm(), $this->body, true)); } return $this->digest; } diff --git a/lib/private/Security/VerificationToken/VerificationToken.php b/lib/private/Security/VerificationToken/VerificationToken.php index 1995b482597..89f45180359 100644 --- a/lib/private/Security/VerificationToken/VerificationToken.php +++ b/lib/private/Security/VerificationToken/VerificationToken.php @@ -85,9 +85,9 @@ class VerificationToken implements IVerificationToken { ): string { $token = $this->secureRandom->generate( 21, - ISecureRandom::CHAR_DIGITS . - ISecureRandom::CHAR_LOWER . - ISecureRandom::CHAR_UPPER + ISecureRandom::CHAR_DIGITS + . ISecureRandom::CHAR_LOWER + . ISecureRandom::CHAR_UPPER ); $tokenValue = $this->timeFactory->getTime() . ':' . $token; $encryptedValue = $this->crypto->encrypt($tokenValue, $passwordPrefix . $this->config->getSystemValueString('secret')); diff --git a/lib/private/Server.php b/lib/private/Server.php index ea8c1ce3797..c78decd90cb 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -55,6 +56,7 @@ use OC\Files\Mount\RootMountProvider; use OC\Files\Node\HookConnector; use OC\Files\Node\LazyRoot; use OC\Files\Node\Root; +use OC\Files\ObjectStore\PrimaryObjectStoreConfig; use OC\Files\SetupManager; use OC\Files\Storage\StorageFactory; use OC\Files\Template\TemplateManager; @@ -195,6 +197,7 @@ use OCP\Lock\ILockingProvider; use OCP\Lockdown\ILockdownManager; use OCP\Log\ILogFactory; use OCP\Mail\IMailer; +use OCP\OCM\ICapabilityAwareOCMProvider; use OCP\OCM\IOCMDiscoveryService; use OCP\OCM\IOCMProvider; use OCP\Preview\IMimeIconProvider; @@ -319,7 +322,7 @@ class Server extends ServerContainer implements IServerContainer { return new Profiler($c->get(SystemConfig::class)); }); - $this->registerService(\OCP\Encryption\IManager::class, function (Server $c): Encryption\Manager { + $this->registerService(Encryption\Manager::class, function (Server $c): Encryption\Manager { $view = new View(); $util = new Encryption\Util( $view, @@ -336,6 +339,7 @@ class Server extends ServerContainer implements IServerContainer { new ArrayCache() ); }); + $this->registerAlias(\OCP\Encryption\IManager::class, Encryption\Manager::class); $this->registerService(IFile::class, function (ContainerInterface $c) { $util = new Encryption\Util( @@ -568,6 +572,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(IAppConfig::class, \OC\AppConfig::class); $this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class); + $this->registerAlias(IAppManager::class, AppManager::class); $this->registerService(IFactory::class, function (Server $c) { return new \OC\L10N\Factory( @@ -604,7 +609,7 @@ class Server extends ServerContainer implements IServerContainer { $prefixClosure = function () use ($logQuery, $serverVersion): ?string { if (!$logQuery) { try { - $v = \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions(); + $v = \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions(true); } catch (\Doctrine\DBAL\Exception $e) { // Database service probably unavailable // Probably related to https://github.com/nextcloud/server/issues/37424 @@ -619,7 +624,7 @@ class Server extends ServerContainer implements IServerContainer { ]; } $v['core'] = implode(',', $serverVersion->getVersion()); - $version = implode(',', $v); + $version = implode(',', array_keys($v)) . implode(',', $v); $instanceId = \OC_Util::getInstanceId(); $path = \OC::$SERVERROOT; return md5($instanceId . '-' . $version . '-' . $path); @@ -776,21 +781,6 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias(ITempManager::class, TempManager::class); - - $this->registerService(AppManager::class, function (ContainerInterface $c) { - // TODO: use auto-wiring - return new \OC\App\AppManager( - $c->get(IUserSession::class), - $c->get(\OCP\IConfig::class), - $c->get(IGroupManager::class), - $c->get(ICacheFactory::class), - $c->get(IEventDispatcher::class), - $c->get(LoggerInterface::class), - $c->get(ServerVersion::class), - ); - }); - $this->registerAlias(IAppManager::class, AppManager::class); - $this->registerAlias(IDateTimeZone::class, DateTimeZone::class); $this->registerService(IDateTimeFormatter::class, function (Server $c) { @@ -819,10 +809,11 @@ class Server extends ServerContainer implements IServerContainer { $config = $c->get(\OCP\IConfig::class); $logger = $c->get(LoggerInterface::class); + $objectStoreConfig = $c->get(PrimaryObjectStoreConfig::class); $manager->registerProvider(new CacheMountProvider($config)); $manager->registerHomeProvider(new LocalHomeMountProvider()); - $manager->registerHomeProvider(new ObjectHomeMountProvider($config)); - $manager->registerRootProvider(new RootMountProvider($config, $c->get(LoggerInterface::class))); + $manager->registerHomeProvider(new ObjectHomeMountProvider($objectStoreConfig)); + $manager->registerRootProvider(new RootMountProvider($objectStoreConfig, $config)); $manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config)); return $manager; @@ -1269,7 +1260,8 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class); - $this->registerAlias(IOCMProvider::class, OCMProvider::class); + $this->registerAlias(ICapabilityAwareOCMProvider::class, OCMProvider::class); + $this->registerDeprecatedAlias(IOCMProvider::class, OCMProvider::class); $this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class); diff --git a/lib/private/Session/CryptoWrapper.php b/lib/private/Session/CryptoWrapper.php index 380c699d32d..40c2ba6adf3 100644 --- a/lib/private/Session/CryptoWrapper.php +++ b/lib/private/Session/CryptoWrapper.php @@ -59,7 +59,7 @@ class CryptoWrapper { [ 'expires' => 0, 'path' => $webRoot, - 'domain' => '', + 'domain' => \OCP\Server::get(\OCP\IConfig::class)->getSystemValueString('cookie_domain'), 'secure' => $secureCookie, 'httponly' => true, 'samesite' => 'Lax', diff --git a/lib/private/Settings/DeclarativeManager.php b/lib/private/Settings/DeclarativeManager.php index dea0c678f20..534b4b19984 100644 --- a/lib/private/Settings/DeclarativeManager.php +++ b/lib/private/Settings/DeclarativeManager.php @@ -15,6 +15,7 @@ use OCP\IAppConfig; use OCP\IConfig; use OCP\IGroupManager; use OCP\IUser; +use OCP\Security\ICrypto; use OCP\Server; use OCP\Settings\DeclarativeSettingsTypes; use OCP\Settings\Events\DeclarativeSettingsGetValueEvent; @@ -49,6 +50,7 @@ class DeclarativeManager implements IDeclarativeManager { private IConfig $config, private IAppConfig $appConfig, private LoggerInterface $logger, + private ICrypto $crypto, ) { } @@ -266,7 +268,7 @@ class DeclarativeManager implements IDeclarativeManager { $this->eventDispatcher->dispatchTyped(new DeclarativeSettingsSetValueEvent($user, $app, $formId, $fieldId, $value)); break; case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL: - $this->saveInternalValue($user, $app, $fieldId, $value); + $this->saveInternalValue($user, $app, $formId, $fieldId, $value); break; default: throw new Exception('Unknown storage type "' . $storageType . '"'); @@ -290,18 +292,52 @@ class DeclarativeManager implements IDeclarativeManager { private function getInternalValue(IUser $user, string $app, string $formId, string $fieldId): mixed { $sectionType = $this->getSectionType($app, $fieldId); $defaultValue = $this->getDefaultValue($app, $formId, $fieldId); + + $field = $this->getSchemaField($app, $formId, $fieldId); + $isSensitive = $field !== null && isset($field['sensitive']) && $field['sensitive'] === true; + switch ($sectionType) { case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN: - return $this->config->getAppValue($app, $fieldId, $defaultValue); + $value = $this->config->getAppValue($app, $fieldId, $defaultValue); + break; case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL: - return $this->config->getUserValue($user->getUID(), $app, $fieldId, $defaultValue); + $value = $this->config->getUserValue($user->getUID(), $app, $fieldId, $defaultValue); + break; default: throw new Exception('Unknown section type "' . $sectionType . '"'); } + if ($isSensitive && $value !== '') { + try { + $value = $this->crypto->decrypt($value); + } catch (Exception $e) { + $this->logger->warning('Failed to decrypt sensitive value for field {field} in app {app}: {message}', [ + 'field' => $fieldId, + 'app' => $app, + 'message' => $e->getMessage(), + ]); + $value = $defaultValue; + } + } + return $value; } - private function saveInternalValue(IUser $user, string $app, string $fieldId, mixed $value): void { + private function saveInternalValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void { $sectionType = $this->getSectionType($app, $fieldId); + + $field = $this->getSchemaField($app, $formId, $fieldId); + if ($field !== null && isset($field['sensitive']) && $field['sensitive'] === true && $value !== '' && $value !== 'dummySecret') { + try { + $value = $this->crypto->encrypt($value); + } catch (Exception $e) { + $this->logger->warning('Failed to decrypt sensitive value for field {field} in app {app}: {message}', [ + 'field' => $fieldId, + 'app' => $app, + 'message' => $e->getMessage()] + ); + throw new Exception('Failed to encrypt sensitive value'); + } + } + switch ($sectionType) { case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN: $this->appConfig->setValueString($app, $fieldId, $value); @@ -314,6 +350,27 @@ class DeclarativeManager implements IDeclarativeManager { } } + private function getSchemaField(string $app, string $formId, string $fieldId): ?array { + $form = $this->getForm($app, $formId); + if ($form !== null) { + foreach ($form->getSchema()['fields'] as $field) { + if ($field['id'] === $fieldId) { + return $field; + } + } + } + foreach ($this->appSchemas[$app] ?? [] as $schema) { + if ($schema['id'] === $formId) { + foreach ($schema['fields'] as $field) { + if ($field['id'] === $fieldId) { + return $field; + } + } + } + } + return null; + } + private function getDefaultValue(string $app, string $formId, string $fieldId): mixed { foreach ($this->appSchemas[$app] as $schema) { if ($schema['id'] === $formId) { @@ -391,6 +448,12 @@ class DeclarativeManager implements IDeclarativeManager { ]); return false; } + if (isset($field['sensitive']) && $field['sensitive'] === true && !in_array($field['type'], [DeclarativeSettingsTypes::TEXT, DeclarativeSettingsTypes::PASSWORD])) { + $this->logger->warning('Declarative settings: sensitive field type is supported only for TEXT and PASSWORD types ({app}, {form_id}, {field_id})', [ + 'app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId, + ]); + return false; + } if (!$this->validateField($appId, $formId, $field)) { return false; } diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index c96c04f34ff..78dc64c3c3f 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Settings/Section.php b/lib/private/Settings/Section.php index 9cc6523b9ae..6cd8885d2df 100644 --- a/lib/private/Settings/Section.php +++ b/lib/private/Settings/Section.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 959797fb962..4082cd5df50 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -187,8 +187,8 @@ class Setup { if (\OC_Util::runningOnMac()) { $errors[] = [ 'error' => $this->l10n->t( - 'Mac OS X is not supported and %s will not work properly on this platform. ' . - 'Use it at your own risk!', + 'Mac OS X is not supported and %s will not work properly on this platform. ' + . 'Use it at your own risk!', [$this->defaults->getProductName()] ), 'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.'), @@ -198,8 +198,8 @@ class Setup { if ($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) { $errors[] = [ 'error' => $this->l10n->t( - 'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' . - 'This will lead to problems with files over 4 GB and is highly discouraged.', + 'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' + . 'This will lead to problems with files over 4 GB and is highly discouraged.', [$this->defaults->getProductName()] ), 'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.'), @@ -304,11 +304,15 @@ class Setup { $error = []; $dbType = $options['dbtype']; - if (empty($options['adminlogin'])) { - $error[] = $l->t('Set an admin Login.'); - } - if (empty($options['adminpass'])) { - $error[] = $l->t('Set an admin password.'); + $disableAdminUser = (bool)($options['admindisable'] ?? false); + + if (!$disableAdminUser) { + if (empty($options['adminlogin'])) { + $error[] = $l->t('Set an admin Login.'); + } + if (empty($options['adminpass'])) { + $error[] = $l->t('Set an admin password.'); + } } if (empty($options['directory'])) { $options['directory'] = \OC::$SERVERROOT . '/data'; @@ -318,8 +322,6 @@ class Setup { $dbType = 'sqlite'; } - $username = htmlspecialchars_decode($options['adminlogin']); - $password = htmlspecialchars_decode($options['adminpass']); $dataDir = htmlspecialchars_decode($options['directory']); $class = self::$dbSetupClasses[$dbType]; @@ -375,7 +377,7 @@ class Setup { $this->outputDebug($output, 'Configuring database'); $dbSetup->initialize($options); try { - $dbSetup->setupDatabase($username); + $dbSetup->setupDatabase(); } catch (\OC\DatabaseSetupException $e) { $error[] = [ 'error' => $e->getMessage(), @@ -405,19 +407,22 @@ class Setup { return $error; } - $this->outputDebug($output, 'Create admin account'); - - // create the admin account and group $user = null; - try { - $user = Server::get(IUserManager::class)->createUser($username, $password); - if (!$user) { - $error[] = "Account <$username> could not be created."; + if (!$disableAdminUser) { + $username = htmlspecialchars_decode($options['adminlogin']); + $password = htmlspecialchars_decode($options['adminpass']); + $this->outputDebug($output, 'Create admin account'); + + try { + $user = Server::get(IUserManager::class)->createUser($username, $password); + if (!$user) { + $error[] = "Account <$username> could not be created."; + return $error; + } + } catch (Exception $exception) { + $error[] = $exception->getMessage(); return $error; } - } catch (Exception $exception) { - $error[] = $exception->getMessage(); - return $error; } $config = Server::get(IConfig::class); @@ -432,7 +437,7 @@ class Setup { } $group = Server::get(IGroupManager::class)->createGroup('admin'); - if ($group instanceof IGroup) { + if ($user !== null && $group instanceof IGroup) { $group->addUser($user); } @@ -464,26 +469,28 @@ class Setup { $bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class); $bootstrapCoordinator->runInitialRegistration(); - // Create a session token for the newly created user - // The token provider requires a working db, so it's not injected on setup - /** @var \OC\User\Session $userSession */ - $userSession = Server::get(IUserSession::class); - $provider = Server::get(PublicKeyTokenProvider::class); - $userSession->setTokenProvider($provider); - $userSession->login($username, $password); - $user = $userSession->getUser(); - if (!$user) { - $error[] = 'No account found in session.'; - return $error; - } - $userSession->createSessionToken($request, $user->getUID(), $username, $password); + if (!$disableAdminUser) { + // Create a session token for the newly created user + // The token provider requires a working db, so it's not injected on setup + /** @var \OC\User\Session $userSession */ + $userSession = Server::get(IUserSession::class); + $provider = Server::get(PublicKeyTokenProvider::class); + $userSession->setTokenProvider($provider); + $userSession->login($username, $password); + $user = $userSession->getUser(); + if (!$user) { + $error[] = 'No account found in session.'; + return $error; + } + $userSession->createSessionToken($request, $user->getUID(), $username, $password); - $session = $userSession->getSession(); - $session->set('last-password-confirm', Server::get(ITimeFactory::class)->getTime()); + $session = $userSession->getSession(); + $session->set('last-password-confirm', Server::get(ITimeFactory::class)->getTime()); - // Set email for admin - if (!empty($options['adminemail'])) { - $user->setSystemEMailAddress($options['adminemail']); + // Set email for admin + if (!empty($options['adminemail'])) { + $user->setSystemEMailAddress($options['adminemail']); + } } return $error; diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php index dbbb587206b..ec4ce040090 100644 --- a/lib/private/Setup/AbstractDatabase.php +++ b/lib/private/Setup/AbstractDatabase.php @@ -127,10 +127,7 @@ abstract class AbstractDatabase { return $connection; } - /** - * @param string $username - */ - abstract public function setupDatabase($username); + abstract public function setupDatabase(); public function runMigrations(?IOutput $output = null) { if (!is_dir(\OC::$SERVERROOT . '/core/Migrations')) { diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php index 6dd9855d851..1e2dda4c609 100644 --- a/lib/private/Setup/MySQL.php +++ b/lib/private/Setup/MySQL.php @@ -16,7 +16,7 @@ use OCP\Security\ISecureRandom; class MySQL extends AbstractDatabase { public $dbprettyname = 'MySQL/MariaDB'; - public function setupDatabase($username) { + public function setupDatabase() { //check if the database user has admin right $connection = $this->connect(['dbname' => null]); @@ -28,7 +28,7 @@ class MySQL extends AbstractDatabase { } if ($this->tryCreateDbUser) { - $this->createSpecificUser($username, new ConnectionAdapter($connection)); + $this->createSpecificUser('oc_admin', new ConnectionAdapter($connection)); } $this->config->setValues([ diff --git a/lib/private/Setup/OCI.php b/lib/private/Setup/OCI.php index 47e5e5436a5..61c7f968787 100644 --- a/lib/private/Setup/OCI.php +++ b/lib/private/Setup/OCI.php @@ -40,7 +40,7 @@ class OCI extends AbstractDatabase { return $errors; } - public function setupDatabase($username) { + public function setupDatabase() { try { $this->connect(); } catch (\Exception $e) { diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php index b1cf031e876..9a686db2e54 100644 --- a/lib/private/Setup/PostgreSQL.php +++ b/lib/private/Setup/PostgreSQL.php @@ -16,10 +16,9 @@ class PostgreSQL extends AbstractDatabase { public $dbprettyname = 'PostgreSQL'; /** - * @param string $username * @throws \OC\DatabaseSetupException */ - public function setupDatabase($username) { + public function setupDatabase() { try { $connection = $this->connect([ 'dbname' => 'postgres' @@ -46,7 +45,7 @@ class PostgreSQL extends AbstractDatabase { //use the admin login data for the new database user //add prefix to the postgresql user name to prevent collisions - $this->dbUser = 'oc_' . strtolower($username); + $this->dbUser = 'oc_admin'; //create a new password so we don't need to store the admin config in the config file $this->dbPassword = \OC::$server->get(ISecureRandom::class)->generate(30, ISecureRandom::CHAR_ALPHANUMERIC); diff --git a/lib/private/Setup/Sqlite.php b/lib/private/Setup/Sqlite.php index 1b90ebd5a5e..b34b1e32ede 100644 --- a/lib/private/Setup/Sqlite.php +++ b/lib/private/Setup/Sqlite.php @@ -45,7 +45,7 @@ class Sqlite extends AbstractDatabase { } } - public function setupDatabase($username) { + public function setupDatabase() { $datadir = $this->config->getValue( 'datadirectory', \OC::$SERVERROOT . '/data' diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php index 56a4c6410c5..1121d71e45f 100644 --- a/lib/private/Share/Share.php +++ b/lib/private/Share/Share.php @@ -133,8 +133,8 @@ class Share extends Constants { // for file/folder shares we need to compare file_source, otherwise we compare item_source // only group shares if they already point to the same target, otherwise the file where shared // before grouping of shares was added. In this case we don't group them to avoid confusions - if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) || - (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) { + if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) + || (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) { // add the first item to the list of grouped shares if (!isset($result[$key]['grouped'])) { $result[$key]['grouped'][] = $result[$key]; diff --git a/lib/private/Share20/Exception/BackendError.php b/lib/private/Share20/Exception/BackendError.php index 60f7dcc1a17..b2585367727 100644 --- a/lib/private/Share20/Exception/BackendError.php +++ b/lib/private/Share20/Exception/BackendError.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Share20/Exception/InvalidShare.php b/lib/private/Share20/Exception/InvalidShare.php index 755efdfd2cc..8756455f9d2 100644 --- a/lib/private/Share20/Exception/InvalidShare.php +++ b/lib/private/Share20/Exception/InvalidShare.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/Share20/LegacyHooks.php b/lib/private/Share20/LegacyHooks.php index 99c2b0a9a87..3bce0b9560a 100644 --- a/lib/private/Share20/LegacyHooks.php +++ b/lib/private/Share20/LegacyHooks.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -69,9 +70,9 @@ class LegacyHooks { // Prepare hook $shareType = $share->getShareType(); $sharedWith = ''; - if ($shareType === IShare::TYPE_USER || - $shareType === IShare::TYPE_GROUP || - $shareType === IShare::TYPE_REMOTE) { + if ($shareType === IShare::TYPE_USER + || $shareType === IShare::TYPE_GROUP + || $shareType === IShare::TYPE_REMOTE) { $sharedWith = $share->getSharedWith(); } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 2104c07593a..01664c6a0a3 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -182,8 +182,8 @@ class Manager implements IManager { } // Cannot share with yourself - if ($share->getShareType() === IShare::TYPE_USER && - $share->getSharedWith() === $share->getSharedBy()) { + if ($share->getShareType() === IShare::TYPE_USER + && $share->getSharedWith() === $share->getSharedBy()) { throw new \InvalidArgumentException($this->l->t('Cannot share with yourself')); } @@ -193,8 +193,8 @@ class Manager implements IManager { } // And it should be a file or a folder - if (!($share->getNode() instanceof \OCP\Files\File) && - !($share->getNode() instanceof \OCP\Files\Folder)) { + if (!($share->getNode() instanceof \OCP\Files\File) + && !($share->getNode() instanceof \OCP\Files\Folder)) { throw new \InvalidArgumentException($this->l->t('Shared path must be either a file or a folder')); } @@ -251,8 +251,8 @@ class Manager implements IManager { // Link shares are allowed to have no read permissions to allow upload to hidden folders $noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL; - if (!$noReadPermissionRequired && - ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) { + if (!$noReadPermissionRequired + && ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) { throw new \InvalidArgumentException($this->l->t('Shares need at least read permissions')); } @@ -561,8 +561,8 @@ class Manager implements IManager { } // Check if public upload is allowed - if ($share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload() && - ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) { + if ($share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload() + && ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) { throw new \InvalidArgumentException($this->l->t('Public upload is not allowed')); } } @@ -698,8 +698,8 @@ class Manager implements IManager { } // Cannot share with the owner - if ($share->getShareType() === IShare::TYPE_USER && - $share->getSharedWith() === $share->getShareOwner()) { + if ($share->getShareType() === IShare::TYPE_USER + && $share->getSharedWith() === $share->getShareOwner()) { throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner')); } @@ -791,14 +791,14 @@ class Manager implements IManager { } // We can only change the recipient on user shares - if ($share->getSharedWith() !== $originalShare->getSharedWith() && - $share->getShareType() !== IShare::TYPE_USER) { + if ($share->getSharedWith() !== $originalShare->getSharedWith() + && $share->getShareType() !== IShare::TYPE_USER) { throw new \InvalidArgumentException($this->l->t('Can only update recipient on user shares')); } // Cannot share with the owner - if ($share->getShareType() === IShare::TYPE_USER && - $share->getSharedWith() === $share->getShareOwner()) { + if ($share->getShareType() === IShare::TYPE_USER + && $share->getSharedWith() === $share->getShareOwner()) { throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner')); } @@ -949,11 +949,11 @@ class Manager implements IManager { * @return boolean whether the password was updated or not. */ private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) { - $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) && - (($share->getPassword() !== null && $originalShare->getPassword() === null) || - ($share->getPassword() === null && $originalShare->getPassword() !== null) || - ($share->getPassword() !== null && $originalShare->getPassword() !== null && - !$this->hasher->verify($share->getPassword(), $originalShare->getPassword()))); + $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) + && (($share->getPassword() !== null && $originalShare->getPassword() === null) + || ($share->getPassword() === null && $originalShare->getPassword() !== null) + || ($share->getPassword() !== null && $originalShare->getPassword() !== null + && !$this->hasher->verify($share->getPassword(), $originalShare->getPassword()))); // Password updated. if ($passwordsAreDifferent) { @@ -1237,9 +1237,9 @@ class Manager implements IManager { * @inheritdoc */ public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true) { - if ($path !== null && - !($path instanceof \OCP\Files\File) && - !($path instanceof \OCP\Files\Folder)) { + if ($path !== null + && !($path instanceof \OCP\Files\File) + && !($path instanceof \OCP\Files\Folder)) { throw new \InvalidArgumentException($this->l->t('Invalid path')); } @@ -1798,8 +1798,8 @@ class Manager implements IManager { * @return bool */ public function shareApiLinkDefaultExpireDateEnforced() { - return $this->shareApiLinkDefaultExpireDate() && - $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; + return $this->shareApiLinkDefaultExpireDate() + && $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; } @@ -1836,8 +1836,8 @@ class Manager implements IManager { * @return bool */ public function shareApiInternalDefaultExpireDateEnforced(): bool { - return $this->shareApiInternalDefaultExpireDate() && - $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes'; + return $this->shareApiInternalDefaultExpireDate() + && $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes'; } /** @@ -1846,8 +1846,8 @@ class Manager implements IManager { * @return bool */ public function shareApiRemoteDefaultExpireDateEnforced(): bool { - return $this->shareApiRemoteDefaultExpireDate() && - $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes'; + return $this->shareApiRemoteDefaultExpireDate() + && $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes'; } /** @@ -1915,13 +1915,13 @@ class Manager implements IManager { } public function limitEnumerationToGroups(): bool { - return $this->allowEnumeration() && - $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; + return $this->allowEnumeration() + && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; } public function limitEnumerationToPhone(): bool { - return $this->allowEnumeration() && - $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; + return $this->allowEnumeration() + && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; } public function allowEnumerationFullMatch(): bool { @@ -1940,6 +1940,10 @@ class Manager implements IManager { return $this->appConfig->getValueBool('core', 'shareapi_allow_custom_tokens', false); } + public function allowViewWithoutDownload(): bool { + return $this->appConfig->getValueBool('core', 'shareapi_allow_view_without_download', true); + } + public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool { if ($this->allowEnumerationFullMatch()) { return true; diff --git a/lib/private/Share20/ProviderFactory.php b/lib/private/Share20/ProviderFactory.php index eba3f4f26f1..d920edfd90e 100644 --- a/lib/private/Share20/ProviderFactory.php +++ b/lib/private/Share20/ProviderFactory.php @@ -174,9 +174,9 @@ class ProviderFactory implements IProviderFactory { public function getProviderForType($shareType) { $provider = null; - if ($shareType === IShare::TYPE_USER || - $shareType === IShare::TYPE_GROUP || - $shareType === IShare::TYPE_LINK + if ($shareType === IShare::TYPE_USER + || $shareType === IShare::TYPE_GROUP + || $shareType === IShare::TYPE_LINK ) { $provider = $this->defaultShareProvider(); } elseif ($shareType === IShare::TYPE_REMOTE || $shareType === IShare::TYPE_REMOTE_GROUP) { diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 466817efc9a..8caabb0898a 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -14,8 +14,10 @@ use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IUserManager; +use OCP\Server; use OCP\Share\Exceptions\IllegalIDChangeException; use OCP\Share\IAttributes; +use OCP\Share\IManager; use OCP\Share\IShare; class Share implements IShare { @@ -418,8 +420,8 @@ class Share implements IShare { * @inheritdoc */ public function isExpired() { - return $this->getExpirationDate() !== null && - $this->getExpirationDate() <= new \DateTime(); + return $this->getExpirationDate() !== null + && $this->getExpirationDate() <= new \DateTime(); } /** @@ -622,4 +624,23 @@ class Share implements IShare { public function getReminderSent(): bool { return $this->reminderSent; } + + public function canSeeContent(): bool { + $shareManager = Server::get(IManager::class); + + $allowViewWithoutDownload = $shareManager->allowViewWithoutDownload(); + // If the share manager allows viewing without download, we can always see the content. + if ($allowViewWithoutDownload) { + return true; + } + + // No "allow preview" header set, so we must check if + // the share has not explicitly disabled download permissions + $attributes = $this->getAttributes(); + if ($attributes?->getAttribute('permissions', 'download') === false) { + return false; + } + + return true; + } } diff --git a/lib/private/Share20/ShareAttributes.php b/lib/private/Share20/ShareAttributes.php index 96da1e336e3..f90fbd9c6cd 100644 --- a/lib/private/Share20/ShareAttributes.php +++ b/lib/private/Share20/ShareAttributes.php @@ -32,8 +32,8 @@ class ShareAttributes implements IAttributes { * @inheritdoc */ public function getAttribute(string $scope, string $key): mixed { - if (\array_key_exists($scope, $this->attributes) && - \array_key_exists($key, $this->attributes[$scope])) { + if (\array_key_exists($scope, $this->attributes) + && \array_key_exists($key, $this->attributes[$scope])) { return $this->attributes[$scope][$key]; } return null; diff --git a/lib/private/Share20/ShareHelper.php b/lib/private/Share20/ShareHelper.php index d4a54f1d687..3f6bab98a7f 100644 --- a/lib/private/Share20/ShareHelper.php +++ b/lib/private/Share20/ShareHelper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php index 5eed6885f78..34f24d1d026 100644 --- a/lib/private/Support/Subscription/Registry.php +++ b/lib/private/Support/Subscription/Registry.php @@ -138,8 +138,8 @@ class Registry implements IRegistry { */ public function delegateIsHardUserLimitReached(?IManager $notificationManager = null): bool { $subscription = $this->getSubscription(); - if ($subscription instanceof ISubscription && - $subscription->hasValidSubscription()) { + if ($subscription instanceof ISubscription + && $subscription->hasValidSubscription()) { $userLimitReached = $subscription->isHardUserLimitReached(); if ($userLimitReached && $notificationManager instanceof IManager) { $this->notifyAboutReachedUserLimit($notificationManager); diff --git a/lib/private/TaskProcessing/SynchronousBackgroundJob.php b/lib/private/TaskProcessing/SynchronousBackgroundJob.php index de3b424176c..19c53d59932 100644 --- a/lib/private/TaskProcessing/SynchronousBackgroundJob.php +++ b/lib/private/TaskProcessing/SynchronousBackgroundJob.php @@ -59,8 +59,8 @@ class SynchronousBackgroundJob extends QueuedJob { // check if this job needs to be scheduled again: // if there is at least one preferred synchronous provider that has a scheduled task - $synchronousProviders = array_filter($providers, fn ($provider) => - $provider instanceof ISynchronousProvider); + $synchronousProviders = array_filter($providers, fn ($provider) + => $provider instanceof ISynchronousProvider); $synchronousPreferredProviders = array_filter($synchronousProviders, function ($provider) { $taskTypeId = $provider->getTaskTypeId(); $preferredProvider = $this->taskProcessingManager->getPreferredProvider($taskTypeId); diff --git a/lib/private/Teams/TeamManager.php b/lib/private/Teams/TeamManager.php index d75b0209c71..13d6cc459a9 100644 --- a/lib/private/Teams/TeamManager.php +++ b/lib/private/Teams/TeamManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php index 5fce3effb3f..a94f822a448 100644 --- a/lib/private/Template/JSCombiner.php +++ b/lib/private/Template/JSCombiner.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index caffbfceefa..cfc387d2164 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -201,7 +201,7 @@ class TemplateLayout { if ($this->config->getSystemValueBool('installed', false)) { if (empty(self::$versionHash)) { - $v = $this->appManager->getAppInstalledVersions(); + $v = $this->appManager->getAppInstalledVersions(true); $v['core'] = implode('.', $this->serverVersion->getVersion()); self::$versionHash = substr(md5(implode(',', $v)), 0, 8); } diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index c78ecac0903..1a2978b84d7 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -189,14 +189,14 @@ class URLGenerator implements IURLGenerator { $basename = substr(basename($file), 0, -4); try { - $appPath = $this->getAppManager()->getAppPath($appName); - } catch (AppPathNotFoundException $e) { if ($appName === 'core' || $appName === '') { $appName = 'core'; $appPath = false; } else { - throw new RuntimeException('image not found: image: ' . $file . ' webroot: ' . \OC::$WEBROOT . ' serverroot: ' . \OC::$SERVERROOT); + $appPath = $this->getAppManager()->getAppPath($appName); } + } catch (AppPathNotFoundException $e) { + throw new RuntimeException('image not found: image: ' . $file . ' webroot: ' . \OC::$WEBROOT . ' serverroot: ' . \OC::$SERVERROOT); } // Check if the app is in the app folder diff --git a/lib/private/Updater.php b/lib/private/Updater.php index 7707a310d99..6495bad2da2 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -167,8 +167,8 @@ class Updater extends BasicEmitter { // Vendor was not set correctly on install, so we have to white-list known versions if ($currentVendor === '' && ( - isset($allowedPreviousVersions['owncloud'][$oldVersion]) || - isset($allowedPreviousVersions['owncloud'][$majorMinor]) + isset($allowedPreviousVersions['owncloud'][$oldVersion]) + || isset($allowedPreviousVersions['owncloud'][$majorMinor]) )) { $currentVendor = 'owncloud'; $this->config->setAppValue('core', 'vendor', $currentVendor); @@ -176,13 +176,13 @@ class Updater extends BasicEmitter { if ($currentVendor === 'nextcloud') { return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) - && (version_compare($oldVersion, $newVersion, '<=') || - $this->config->getSystemValueBool('debug', false)); + && (version_compare($oldVersion, $newVersion, '<=') + || $this->config->getSystemValueBool('debug', false)); } // Check if the instance can be migrated - return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) || - isset($allowedPreviousVersions[$currentVendor][$oldVersion]); + return isset($allowedPreviousVersions[$currentVendor][$majorMinor]) + || isset($allowedPreviousVersions[$currentVendor][$oldVersion]); } /** diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php index 53bfc0d5d5f..be410b06c3e 100644 --- a/lib/private/Updater/VersionCheck.php +++ b/lib/private/Updater/VersionCheck.php @@ -105,17 +105,20 @@ class VersionCheck { } /** - * @codeCoverageIgnore - * @param string $url - * @return resource|string * @throws \Exception */ - protected function getUrlContent($url) { - $client = $this->clientService->newClient(); - $response = $client->get($url, [ + protected function getUrlContent(string $url): string { + $response = $this->clientService->newClient()->get($url, [ 'timeout' => 5, ]); - return $response->getBody(); + + $content = $response->getBody(); + + // IResponse.getBody responds with null|resource if returning a stream response was requested. + // As that's not the case here, we can just ignore the psalm warning by adding an assertion. + assert(is_string($content)); + + return $content; } private function computeCategory(): int { diff --git a/lib/private/User/LazyUser.php b/lib/private/User/LazyUser.php index 715265f6a39..501169019d4 100644 --- a/lib/private/User/LazyUser.php +++ b/lib/private/User/LazyUser.php @@ -160,6 +160,10 @@ class LazyUser implements IUser { return $this->getUser()->getQuota(); } + public function getQuotaBytes(): int|float { + return $this->getUser()->getQuotaBytes(); + } + public function setQuota($quota) { $this->getUser()->setQuota($quota); } diff --git a/lib/private/User/Listeners/UserChangedListener.php b/lib/private/User/Listeners/UserChangedListener.php index 983a4e81233..8f618950255 100644 --- a/lib/private/User/Listeners/UserChangedListener.php +++ b/lib/private/User/Listeners/UserChangedListener.php @@ -28,7 +28,7 @@ class UserChangedListener implements IEventListener { if (!($event instanceof UserChangedEvent)) { return; } - + $user = $event->getUser(); $feature = $event->getFeature(); $oldValue = $event->getOldValue(); diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index ca5d90f8c00..097fd9a0dc8 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -320,9 +320,9 @@ class Manager extends PublicEmitter implements IUserManager { $users, function (IUser $user) use ($search): bool { try { - return mb_stripos($user->getUID(), $search) !== false || - mb_stripos($user->getDisplayName(), $search) !== false || - mb_stripos($user->getEMailAddress() ?? '', $search) !== false; + return mb_stripos($user->getUID(), $search) !== false + || mb_stripos($user->getDisplayName(), $search) !== false + || mb_stripos($user->getEMailAddress() ?? '', $search) !== false; } catch (NoUserException $ex) { $this->logger->error('Error while filtering disabled users', ['exception' => $ex, 'userUID' => $user->getUID()]); return false; @@ -724,7 +724,8 @@ class Manager extends PublicEmitter implements IUserManager { // User ID is too long if (strlen($uid) > IUser::MAX_USERID_LENGTH) { - throw new \InvalidArgumentException($l->t('Login is too long')); + // TRANSLATORS User ID is too long + throw new \InvalidArgumentException($l->t('Username is too long')); } if (!$this->verifyUid($uid, $checkDataDirectory)) { diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 27570822ef2..95e4b6e4a87 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -967,6 +968,7 @@ class Session implements IUserSession, Emitter { if ($webRoot === '') { $webRoot = '/'; } + $domain = $this->config->getSystemValueString('cookie_domain'); $maxAge = $this->config->getSystemValueInt('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); \OC\Http\CookieHelper::setCookie( @@ -974,7 +976,7 @@ class Session implements IUserSession, Emitter { $username, $maxAge, $webRoot, - '', + $domain, $secureCookie, true, \OC\Http\CookieHelper::SAMESITE_LAX @@ -984,7 +986,7 @@ class Session implements IUserSession, Emitter { $token, $maxAge, $webRoot, - '', + $domain, $secureCookie, true, \OC\Http\CookieHelper::SAMESITE_LAX @@ -995,7 +997,7 @@ class Session implements IUserSession, Emitter { $this->session->getId(), $maxAge, $webRoot, - '', + $domain, $secureCookie, true, \OC\Http\CookieHelper::SAMESITE_LAX @@ -1011,18 +1013,19 @@ class Session implements IUserSession, Emitter { public function unsetMagicInCookie() { //TODO: DI for cookies and IRequest $secureCookie = OC::$server->getRequest()->getServerProtocol() === 'https'; + $domain = $this->config->getSystemValueString('cookie_domain'); unset($_COOKIE['nc_username']); //TODO: DI unset($_COOKIE['nc_token']); unset($_COOKIE['nc_session_id']); - setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); - setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); - setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, '', $secureCookie, true); + setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, $domain, $secureCookie, true); + setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, $domain, $secureCookie, true); + setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT, $domain, $secureCookie, true); // old cookies might be stored under /webroot/ instead of /webroot // and Firefox doesn't like it! - setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); - setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); - setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', '', $secureCookie, true); + setcookie('nc_username', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', $domain, $secureCookie, true); + setcookie('nc_token', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', $domain, $secureCookie, true); + setcookie('nc_session_id', '', $this->timeFactory->getTime() - 3600, OC::$WEBROOT . '/', $domain, $secureCookie, true); } /** diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 8e01a15695c..88ed0d44387 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -558,6 +558,19 @@ class User implements IUser { return $quota; } + public function getQuotaBytes(): int|float { + $quota = $this->getQuota(); + if ($quota === 'none') { + return \OCP\Files\FileInfo::SPACE_UNLIMITED; + } + + $bytes = \OCP\Util::computerFileSize($quota); + if ($bytes === false) { + return \OCP\Files\FileInfo::SPACE_UNKNOWN; + } + return $bytes; + } + /** * set the users' quota * diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index abac0d2635e..24982ab9e80 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -9,6 +9,7 @@ declare(strict_types=1); use OC\App\DependencyAnalyzer; use OC\App\Platform; use OC\AppFramework\Bootstrap\Coordinator; +use OC\Config\ConfigManager; use OC\DB\MigrationService; use OC\Installer; use OC\Repair; @@ -211,7 +212,7 @@ class OC_App { array $groups = []) { // Check if app is already downloaded /** @var Installer $installer */ - $installer = \OCP\Server::get(Installer::class); + $installer = Server::get(Installer::class); $isDownloaded = $installer->isDownloaded($appId); if (!$isDownloaded) { @@ -246,7 +247,7 @@ class OC_App { } } - \OCP\Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']); + Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']); return null; } @@ -310,12 +311,14 @@ class OC_App { * @param string $appId * @param bool $refreshAppPath should be set to true only during install/upgrade * @return string|false - * @deprecated 11.0.0 use \OCP\Server::get(IAppManager)->getAppPath() + * @deprecated 11.0.0 use Server::get(IAppManager)->getAppPath() */ public static function getAppPath(string $appId, bool $refreshAppPath = false) { $appId = self::cleanAppId($appId); if ($appId === '') { return false; + } elseif ($appId === 'core') { + return __DIR__ . '/../../../core'; } if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) { @@ -347,7 +350,7 @@ class OC_App { */ public static function getAppVersionByPath(string $path): string { $infoFile = $path . '/appinfo/info.xml'; - $appData = \OCP\Server::get(IAppManager::class)->getAppInfoByPath($infoFile); + $appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile); return $appData['version'] ?? ''; } @@ -389,7 +392,7 @@ class OC_App { * @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface */ public static function registerLogIn(array $entry) { - \OCP\Server::get(LoggerInterface::class)->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface'); + Server::get(LoggerInterface::class)->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface'); self::$altLogin[] = $entry; } @@ -398,11 +401,11 @@ class OC_App { */ public static function getAlternativeLogIns(): array { /** @var Coordinator $bootstrapCoordinator */ - $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); + $bootstrapCoordinator = Server::get(Coordinator::class); foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) { if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) { - \OCP\Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [ + Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [ 'option' => $registration->getService(), 'interface' => IAlternativeLogin::class, 'app' => $registration->getAppId(), @@ -412,9 +415,9 @@ class OC_App { try { /** @var IAlternativeLogin $provider */ - $provider = \OCP\Server::get($registration->getService()); + $provider = Server::get($registration->getService()); } catch (ContainerExceptionInterface $e) { - \OCP\Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.', + Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.', [ 'exception' => $e, 'option' => $registration->getService(), @@ -431,7 +434,7 @@ class OC_App { 'class' => $provider->getClass(), ]; } catch (Throwable $e) { - \OCP\Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.', + Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.', [ 'exception' => $e, 'option' => $registration->getService(), @@ -450,7 +453,7 @@ class OC_App { * @deprecated 31.0.0 Use IAppManager::getAllAppsInAppsFolders instead */ public static function getAllApps(): array { - return \OCP\Server::get(IAppManager::class)->getAllAppsInAppsFolders(); + return Server::get(IAppManager::class)->getAllAppsInAppsFolders(); } /** @@ -459,7 +462,7 @@ class OC_App { * @deprecated 32.0.0 Use \OCP\Support\Subscription\IRegistry::delegateGetSupportedApps instead */ public function getSupportedApps(): array { - $subscriptionRegistry = \OCP\Server::get(\OCP\Support\Subscription\IRegistry::class); + $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class); $supportedApps = $subscriptionRegistry->delegateGetSupportedApps(); return $supportedApps; } @@ -484,12 +487,12 @@ class OC_App { if (!in_array($app, $blacklist)) { $info = $appManager->getAppInfo($app, false, $langCode); if (!is_array($info)) { - \OCP\Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']); + Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']); continue; } if (!isset($info['name'])) { - \OCP\Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']); + Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']); continue; } @@ -556,7 +559,7 @@ class OC_App { public static function shouldUpgrade(string $app): bool { $versions = self::getAppVersions(); - $currentVersion = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppVersion($app); + $currentVersion = Server::get(\OCP\App\IAppManager::class)->getAppVersion($app); if ($currentVersion && isset($versions[$app])) { $installedVersion = $versions[$app]; if (!version_compare($currentVersion, $installedVersion, '=')) { @@ -645,7 +648,7 @@ class OC_App { * @deprecated 32.0.0 Use IAppManager::getAppInstalledVersions or IAppConfig::getAppInstalledVersions instead */ public static function getAppVersions(): array { - return \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions(); + return Server::get(IAppConfig::class)->getAppInstalledVersions(); } /** @@ -663,13 +666,13 @@ class OC_App { } if (is_file($appPath . '/appinfo/database.xml')) { - \OCP\Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId); + Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId); return false; } \OC::$server->getAppManager()->clearAppsCache(); $l = \OC::$server->getL10N('core'); - $appData = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode()); + $appData = Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode()); $ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []); $ignoreMax = in_array($appId, $ignoreMaxApps, true); @@ -709,9 +712,13 @@ class OC_App { self::setAppTypes($appId); - $version = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId); + $version = Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId); \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version); + // migrate eventual new config keys in the process + /** @psalm-suppress InternalMethod */ + Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId); + \OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId)); \OC::$server->get(IEventDispatcher::class)->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent( ManagerEvent::EVENT_APP_UPDATE, $appId diff --git a/lib/private/legacy/OC_Defaults.php b/lib/private/legacy/OC_Defaults.php index f7015a1863a..0d460ff966d 100644 --- a/lib/private/legacy/OC_Defaults.php +++ b/lib/private/legacy/OC_Defaults.php @@ -228,9 +228,9 @@ class OC_Defaults { if ($this->themeExist('getShortFooter')) { $footer = $this->theme->getShortFooter(); } else { - $footer = '<a href="' . $this->getBaseUrl() . '" target="_blank"' . - ' rel="noreferrer noopener">' . $this->getEntity() . '</a>' . - ' – ' . $this->getSlogan(); + $footer = '<a href="' . $this->getBaseUrl() . '" target="_blank"' + . ' rel="noreferrer noopener">' . $this->getEntity() . '</a>' + . ' – ' . $this->getSlogan(); } return $footer; diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php index 87c820dcd53..4388f775623 100644 --- a/lib/private/legacy/OC_Helper.php +++ b/lib/private/legacy/OC_Helper.php @@ -272,7 +272,7 @@ class OC_Helper { } else { $user = \OC::$server->getUserSession()->getUser(); } - $quota = OC_Util::getUserQuota($user); + $quota = $user?->getQuotaBytes() ?? \OCP\Files\FileInfo::SPACE_UNKNOWN; if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) { // always get free space / total space from root + mount points return self::getGlobalStorageInfo($quota, $user, $mount); diff --git a/lib/private/legacy/OC_Response.php b/lib/private/legacy/OC_Response.php index 86274f5fcb7..c45852b4b1d 100644 --- a/lib/private/legacy/OC_Response.php +++ b/lib/private/legacy/OC_Response.php @@ -78,7 +78,6 @@ class OC_Response { header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html header('X-Robots-Tag: noindex, nofollow'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag - header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters } } } diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index 9444da4f36d..948dfcf7926 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -98,7 +98,7 @@ class OC_Util { * * @param IUser|null $user * @return int|\OCP\Files\FileInfo::SPACE_UNLIMITED|false|float Quota bytes - * @deprecated 9.0.0 - Use \OCP\IUser::getQuota + * @deprecated 9.0.0 - Use \OCP\IUser::getQuota or \OCP\IUser::getQuotaBytes */ public static function getUserQuota(?IUser $user) { if (is_null($user)) { @@ -187,14 +187,13 @@ class OC_Util { $child = $target->newFolder($file); self::copyr($source . '/' . $file, $child); } else { - $child = $target->newFile($file); $sourceStream = fopen($source . '/' . $file, 'r'); if ($sourceStream === false) { $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']); closedir($dir); return; } - $child->putContent($sourceStream); + $target->newFile($file, $sourceStream); } } } @@ -480,8 +479,8 @@ class OC_Util { * TODO: Should probably be implemented in the above generic dependency * check somehow in the long-term. */ - if ($iniWrapper->getBool('mbstring.func_overload') !== null && - $iniWrapper->getBool('mbstring.func_overload') === true) { + if ($iniWrapper->getBool('mbstring.func_overload') !== null + && $iniWrapper->getBool('mbstring.func_overload') === true) { $errors[] = [ 'error' => $l->t('<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>.', [$iniWrapper->getString('mbstring.func_overload')]), 'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.') |