aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/legacy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/legacy')
-rw-r--r--lib/private/legacy/OC_App.php781
-rw-r--r--lib/private/legacy/OC_Defaults.php335
-rw-r--r--lib/private/legacy/OC_Helper.php423
-rw-r--r--lib/private/legacy/OC_Hook.php125
-rw-r--r--lib/private/legacy/OC_JSON.php105
-rw-r--r--lib/private/legacy/OC_Response.php83
-rw-r--r--lib/private/legacy/OC_Template.php51
-rw-r--r--lib/private/legacy/OC_User.php405
-rw-r--r--lib/private/legacy/OC_Util.php847
-rw-r--r--lib/private/legacy/l10n.php342
10 files changed, 3155 insertions, 342 deletions
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
new file mode 100644
index 00000000000..10b78e2a7ef
--- /dev/null
+++ b/lib/private/legacy/OC_App.php
@@ -0,0 +1,781 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+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;
+use OC\Repair\Events\RepairErrorEvent;
+use OCP\App\Events\AppUpdateEvent;
+use OCP\App\IAppManager;
+use OCP\App\ManagerEvent;
+use OCP\Authentication\IAlternativeLogin;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IAppConfig;
+use OCP\Server;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Log\LoggerInterface;
+use function OCP\Log\logger;
+
+/**
+ * This class manages the apps. It allows them to register and integrate in the
+ * Nextcloud ecosystem. Furthermore, this class is responsible for installing,
+ * upgrading and removing apps.
+ */
+class OC_App {
+ private static $altLogin = [];
+ private static $alreadyRegistered = [];
+ public const supportedApp = 300;
+ public const officialApp = 200;
+
+ /**
+ * clean the appId
+ *
+ * @psalm-taint-escape file
+ * @psalm-taint-escape include
+ * @psalm-taint-escape html
+ * @psalm-taint-escape has_quotes
+ *
+ * @deprecated 31.0.0 use IAppManager::cleanAppId
+ */
+ public static function cleanAppId(string $app): string {
+ return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
+ }
+
+ /**
+ * Check if an app is loaded
+ *
+ * @param string $app
+ * @return bool
+ * @deprecated 27.0.0 use IAppManager::isAppLoaded
+ */
+ public static function isAppLoaded(string $app): bool {
+ return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
+ }
+
+ /**
+ * loads all apps
+ *
+ * @param string[] $types
+ * @return bool
+ *
+ * This function walks through the Nextcloud directory and loads all apps
+ * it can find. A directory contains an app if the file /appinfo/info.xml
+ * exists.
+ *
+ * if $types is set to non-empty array, only apps of those types will be loaded
+ *
+ * @deprecated 29.0.0 use IAppManager::loadApps instead
+ */
+ public static function loadApps(array $types = []): bool {
+ if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
+ // This should be done before calling this method so that appmanager can be used
+ return false;
+ }
+ return \OC::$server->get(IAppManager::class)->loadApps($types);
+ }
+
+ /**
+ * load a single app
+ *
+ * @param string $app
+ * @throws Exception
+ * @deprecated 27.0.0 use IAppManager::loadApp
+ */
+ public static function loadApp(string $app): void {
+ \OC::$server->get(IAppManager::class)->loadApp($app);
+ }
+
+ /**
+ * @internal
+ * @param string $app
+ * @param string $path
+ * @param bool $force
+ */
+ public static function registerAutoloading(string $app, string $path, bool $force = false) {
+ $key = $app . '-' . $path;
+ if (!$force && isset(self::$alreadyRegistered[$key])) {
+ return;
+ }
+
+ self::$alreadyRegistered[$key] = true;
+
+ // Register on PSR-4 composer autoloader
+ $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
+ \OC::$server->registerNamespace($app, $appNamespace);
+
+ if (file_exists($path . '/composer/autoload.php')) {
+ require_once $path . '/composer/autoload.php';
+ } else {
+ \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
+ }
+
+ // Register Test namespace only when testing
+ if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
+ \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
+ }
+ }
+
+ /**
+ * check if an app is of a specific type
+ *
+ * @param string $app
+ * @param array $types
+ * @return bool
+ * @deprecated 27.0.0 use IAppManager::isType
+ */
+ public static function isType(string $app, array $types): bool {
+ return \OC::$server->get(IAppManager::class)->isType($app, $types);
+ }
+
+ /**
+ * read app types from info.xml and cache them in the database
+ */
+ public static function setAppTypes(string $app) {
+ $appManager = \OC::$server->getAppManager();
+ $appData = $appManager->getAppInfo($app);
+ if (!is_array($appData)) {
+ return;
+ }
+
+ if (isset($appData['types'])) {
+ $appTypes = implode(',', $appData['types']);
+ } else {
+ $appTypes = '';
+ $appData['types'] = [];
+ }
+
+ $config = \OC::$server->getConfig();
+ $config->setAppValue($app, 'types', $appTypes);
+
+ if ($appManager->hasProtectedAppType($appData['types'])) {
+ $enabled = $config->getAppValue($app, 'enabled', 'yes');
+ if ($enabled !== 'yes' && $enabled !== 'no') {
+ $config->setAppValue($app, 'enabled', 'yes');
+ }
+ }
+ }
+
+ /**
+ * Returns apps enabled for the current user.
+ *
+ * @param bool $forceRefresh whether to refresh the cache
+ * @param bool $all whether to return apps for all users, not only the
+ * currently logged in one
+ * @return list<string>
+ */
+ public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
+ if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
+ return [];
+ }
+ // in incognito mode or when logged out, $user will be false,
+ // which is also the case during an upgrade
+ $appManager = \OC::$server->getAppManager();
+ if ($all) {
+ $user = null;
+ } else {
+ $user = \OC::$server->getUserSession()->getUser();
+ }
+
+ if (is_null($user)) {
+ $apps = $appManager->getEnabledApps();
+ } else {
+ $apps = $appManager->getEnabledAppsForUser($user);
+ }
+ $apps = array_filter($apps, function ($app) {
+ return $app !== 'files';//we add this manually
+ });
+ sort($apps);
+ array_unshift($apps, 'files');
+ return $apps;
+ }
+
+ /**
+ * enables an app
+ *
+ * @param string $appId
+ * @param array $groups (optional) when set, only these groups will have access to the app
+ * @throws \Exception
+ * @return void
+ *
+ * This function set an app as enabled in appconfig.
+ */
+ public function enable(string $appId,
+ array $groups = []) {
+ // Check if app is already downloaded
+ /** @var Installer $installer */
+ $installer = Server::get(Installer::class);
+ $isDownloaded = $installer->isDownloaded($appId);
+
+ if (!$isDownloaded) {
+ $installer->downloadApp($appId);
+ }
+
+ $installer->installApp($appId);
+
+ $appManager = \OC::$server->getAppManager();
+ if ($groups !== []) {
+ $groupManager = \OC::$server->getGroupManager();
+ $groupsList = [];
+ foreach ($groups as $group) {
+ $groupItem = $groupManager->get($group);
+ if ($groupItem instanceof \OCP\IGroup) {
+ $groupsList[] = $groupManager->get($group);
+ }
+ }
+ $appManager->enableAppForGroups($appId, $groupsList);
+ } else {
+ $appManager->enableApp($appId);
+ }
+ }
+
+ /**
+ * Find the apps root for an app id.
+ *
+ * If multiple copies are found, the apps root the latest version is returned.
+ *
+ * @param string $appId
+ * @param bool $ignoreCache ignore cache and rebuild it
+ * @return false|array{path: string, url: string} the apps root shape
+ */
+ public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
+ $sanitizedAppId = self::cleanAppId($appId);
+ if ($sanitizedAppId !== $appId) {
+ return false;
+ }
+ static $app_dir = [];
+
+ if (isset($app_dir[$appId]) && !$ignoreCache) {
+ return $app_dir[$appId];
+ }
+
+ $possibleApps = [];
+ foreach (OC::$APPSROOTS as $dir) {
+ if (file_exists($dir['path'] . '/' . $appId)) {
+ $possibleApps[] = $dir;
+ }
+ }
+
+ if (empty($possibleApps)) {
+ return false;
+ } elseif (count($possibleApps) === 1) {
+ $dir = array_shift($possibleApps);
+ $app_dir[$appId] = $dir;
+ return $dir;
+ } else {
+ $versionToLoad = [];
+ foreach ($possibleApps as $possibleApp) {
+ $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
+ if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
+ $versionToLoad = [
+ 'dir' => $possibleApp,
+ 'version' => $version,
+ ];
+ }
+ }
+ $app_dir[$appId] = $versionToLoad['dir'];
+ return $versionToLoad['dir'];
+ //TODO - write test
+ }
+ }
+
+ /**
+ * Get the directory for the given app.
+ * If the app is defined in multiple directories, the first one is taken. (false if not found)
+ *
+ * @psalm-taint-specialize
+ *
+ * @param string $appId
+ * @param bool $refreshAppPath should be set to true only during install/upgrade
+ * @return string|false
+ * @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) {
+ return $dir['path'] . '/' . $appId;
+ }
+ return false;
+ }
+
+ /**
+ * Get the path for the given app on the access
+ * If the app is defined in multiple directories, the first one is taken. (false if not found)
+ *
+ * @param string $appId
+ * @return string|false
+ * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
+ */
+ public static function getAppWebPath(string $appId) {
+ if (($dir = self::findAppInDirectories($appId)) != false) {
+ return OC::$WEBROOT . $dir['url'] . '/' . $appId;
+ }
+ return false;
+ }
+
+ /**
+ * get app's version based on it's path
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function getAppVersionByPath(string $path): string {
+ $infoFile = $path . '/appinfo/info.xml';
+ $appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
+ return $appData['version'] ?? '';
+ }
+
+ /**
+ * get the id of loaded app
+ *
+ * @return string
+ */
+ public static function getCurrentApp(): string {
+ if (\OC::$CLI) {
+ return '';
+ }
+
+ $request = \OC::$server->getRequest();
+ $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
+ $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
+ if (empty($topFolder)) {
+ try {
+ $path_info = $request->getPathInfo();
+ } catch (Exception $e) {
+ // Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
+ \OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
+ return '';
+ }
+ if ($path_info) {
+ $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
+ }
+ }
+ if ($topFolder == 'apps') {
+ $length = strlen($topFolder);
+ return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
+ } else {
+ return $topFolder;
+ }
+ }
+
+ /**
+ * @param array $entry
+ * @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) {
+ 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;
+ }
+
+ /**
+ * @return array
+ */
+ public static function getAlternativeLogIns(): array {
+ /** @var Coordinator $bootstrapCoordinator */
+ $bootstrapCoordinator = Server::get(Coordinator::class);
+
+ foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
+ if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
+ 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(),
+ ]);
+ continue;
+ }
+
+ try {
+ /** @var IAlternativeLogin $provider */
+ $provider = Server::get($registration->getService());
+ } catch (ContainerExceptionInterface $e) {
+ Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
+ [
+ 'exception' => $e,
+ 'option' => $registration->getService(),
+ 'app' => $registration->getAppId(),
+ ]);
+ }
+
+ try {
+ $provider->load();
+
+ self::$altLogin[] = [
+ 'name' => $provider->getLabel(),
+ 'href' => $provider->getLink(),
+ 'class' => $provider->getClass(),
+ ];
+ } catch (Throwable $e) {
+ Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
+ [
+ 'exception' => $e,
+ 'option' => $registration->getService(),
+ 'app' => $registration->getAppId(),
+ ]);
+ }
+ }
+
+ return self::$altLogin;
+ }
+
+ /**
+ * get a list of all apps in the apps folder
+ *
+ * @return string[] an array of app names (string IDs)
+ * @deprecated 31.0.0 Use IAppManager::getAllAppsInAppsFolders instead
+ */
+ public static function getAllApps(): array {
+ return Server::get(IAppManager::class)->getAllAppsInAppsFolders();
+ }
+
+ /**
+ * List all supported apps
+ *
+ * @deprecated 32.0.0 Use \OCP\Support\Subscription\IRegistry::delegateGetSupportedApps instead
+ */
+ public function getSupportedApps(): array {
+ $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
+ $supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
+ return $supportedApps;
+ }
+
+ /**
+ * List all apps, this is used in apps.php
+ *
+ * @return array
+ */
+ public function listAllApps(): array {
+ $appManager = \OC::$server->getAppManager();
+
+ $installedApps = $appManager->getAllAppsInAppsFolders();
+ //we don't want to show configuration for these
+ $blacklist = $appManager->getAlwaysEnabledApps();
+ $appList = [];
+ $langCode = \OC::$server->getL10N('core')->getLanguageCode();
+ $urlGenerator = \OC::$server->getURLGenerator();
+ $supportedApps = $this->getSupportedApps();
+
+ foreach ($installedApps as $app) {
+ if (!in_array($app, $blacklist)) {
+ $info = $appManager->getAppInfo($app, false, $langCode);
+ if (!is_array($info)) {
+ Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
+ continue;
+ }
+
+ if (!isset($info['name'])) {
+ Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
+ continue;
+ }
+
+ $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
+ $info['groups'] = null;
+ if ($enabled === 'yes') {
+ $active = true;
+ } elseif ($enabled === 'no') {
+ $active = false;
+ } else {
+ $active = true;
+ $info['groups'] = $enabled;
+ }
+
+ $info['active'] = $active;
+
+ if ($appManager->isShipped($app)) {
+ $info['internal'] = true;
+ $info['level'] = self::officialApp;
+ $info['removable'] = false;
+ } else {
+ $info['internal'] = false;
+ $info['removable'] = true;
+ }
+
+ if (in_array($app, $supportedApps)) {
+ $info['level'] = self::supportedApp;
+ }
+
+ $appPath = self::getAppPath($app);
+ if ($appPath !== false) {
+ $appIcon = $appPath . '/img/' . $app . '.svg';
+ if (file_exists($appIcon)) {
+ $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
+ $info['previewAsIcon'] = true;
+ } else {
+ $appIcon = $appPath . '/img/app.svg';
+ if (file_exists($appIcon)) {
+ $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
+ $info['previewAsIcon'] = true;
+ }
+ }
+ }
+ // fix documentation
+ if (isset($info['documentation']) && is_array($info['documentation'])) {
+ foreach ($info['documentation'] as $key => $url) {
+ // If it is not an absolute URL we assume it is a key
+ // i.e. admin-ldap will get converted to go.php?to=admin-ldap
+ if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
+ $url = $urlGenerator->linkToDocs($url);
+ }
+
+ $info['documentation'][$key] = $url;
+ }
+ }
+
+ $info['version'] = $appManager->getAppVersion($app);
+ $appList[] = $info;
+ }
+ }
+
+ return $appList;
+ }
+
+ public static function shouldUpgrade(string $app): bool {
+ $versions = self::getAppVersions();
+ $currentVersion = Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
+ if ($currentVersion && isset($versions[$app])) {
+ $installedVersion = $versions[$app];
+ if (!version_compare($currentVersion, $installedVersion, '=')) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adjust the number of version parts of $version1 to match
+ * the number of version parts of $version2.
+ *
+ * @param string $version1 version to adjust
+ * @param string $version2 version to take the number of parts from
+ * @return string shortened $version1
+ */
+ private static function adjustVersionParts(string $version1, string $version2): string {
+ $version1 = explode('.', $version1);
+ $version2 = explode('.', $version2);
+ // reduce $version1 to match the number of parts in $version2
+ while (count($version1) > count($version2)) {
+ array_pop($version1);
+ }
+ // if $version1 does not have enough parts, add some
+ while (count($version1) < count($version2)) {
+ $version1[] = '0';
+ }
+ return implode('.', $version1);
+ }
+
+ /**
+ * Check whether the current Nextcloud version matches the given
+ * application's version requirements.
+ *
+ * The comparison is made based on the number of parts that the
+ * app info version has. For example for ownCloud 6.0.3 if the
+ * app info version is expecting version 6.0, the comparison is
+ * made on the first two parts of the ownCloud version.
+ * This means that it's possible to specify "requiremin" => 6
+ * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
+ *
+ * @param string $ocVersion Nextcloud version to check against
+ * @param array $appInfo app info (from xml)
+ *
+ * @return boolean true if compatible, otherwise false
+ */
+ public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
+ $requireMin = '';
+ $requireMax = '';
+ if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
+ $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
+ } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
+ $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
+ } elseif (isset($appInfo['requiremin'])) {
+ $requireMin = $appInfo['requiremin'];
+ } elseif (isset($appInfo['require'])) {
+ $requireMin = $appInfo['require'];
+ }
+
+ if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
+ $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
+ } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
+ $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
+ } elseif (isset($appInfo['requiremax'])) {
+ $requireMax = $appInfo['requiremax'];
+ }
+
+ if (!empty($requireMin)
+ && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
+ ) {
+ return false;
+ }
+
+ if (!$ignoreMax && !empty($requireMax)
+ && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * get the installed version of all apps
+ * @deprecated 32.0.0 Use IAppManager::getAppInstalledVersions or IAppConfig::getAppInstalledVersions instead
+ */
+ public static function getAppVersions(): array {
+ return Server::get(IAppConfig::class)->getAppInstalledVersions();
+ }
+
+ /**
+ * update the database for the app and call the update script
+ *
+ * @param string $appId
+ * @return bool
+ */
+ public static function updateApp(string $appId): bool {
+ // for apps distributed with core, we refresh app path in case the downloaded version
+ // have been installed in custom apps and not in the default path
+ $appPath = self::getAppPath($appId, true);
+ if ($appPath === false) {
+ return false;
+ }
+
+ if (is_file($appPath . '/appinfo/database.xml')) {
+ 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 = 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);
+ \OC_App::checkAppDependencies(
+ \OC::$server->getConfig(),
+ $l,
+ $appData,
+ $ignoreMax
+ );
+
+ self::registerAutoloading($appId, $appPath, true);
+ self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
+
+ $ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
+ $ms->migrate();
+
+ self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
+ self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
+ // update appversion in app manager
+ \OC::$server->getAppManager()->clearAppsCache();
+ \OC::$server->getAppManager()->getAppVersion($appId, false);
+
+ self::setupBackgroundJobs($appData['background-jobs']);
+
+ //set remote/public handlers
+ if (array_key_exists('ocsid', $appData)) {
+ \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
+ } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
+ \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
+ }
+ foreach ($appData['remote'] as $name => $path) {
+ \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
+ }
+ foreach ($appData['public'] as $name => $path) {
+ \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
+ }
+
+ self::setAppTypes($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
+ ));
+
+ return true;
+ }
+
+ /**
+ * @param string $appId
+ * @param string[] $steps
+ * @throws \OC\NeedsUpdateException
+ */
+ public static function executeRepairSteps(string $appId, array $steps) {
+ if (empty($steps)) {
+ return;
+ }
+ // load the app
+ self::loadApp($appId);
+
+ $dispatcher = Server::get(IEventDispatcher::class);
+
+ // load the steps
+ $r = Server::get(Repair::class);
+ foreach ($steps as $step) {
+ try {
+ $r->addStep($step);
+ } catch (Exception $ex) {
+ $dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
+ logger('core')->error('Failed to add app migration step ' . $step, ['exception' => $ex]);
+ }
+ }
+ // run the steps
+ $r->run();
+ }
+
+ public static function setupBackgroundJobs(array $jobs) {
+ $queue = \OC::$server->getJobList();
+ foreach ($jobs as $job) {
+ $queue->add($job);
+ }
+ }
+
+ /**
+ * @param string $appId
+ * @param string[] $steps
+ */
+ private static function setupLiveMigrations(string $appId, array $steps) {
+ $queue = \OC::$server->getJobList();
+ foreach ($steps as $step) {
+ $queue->add('OC\Migration\BackgroundRepair', [
+ 'app' => $appId,
+ 'step' => $step]);
+ }
+ }
+
+ /**
+ * @param \OCP\IConfig $config
+ * @param \OCP\IL10N $l
+ * @param array $info
+ * @throws \Exception
+ */
+ public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
+ $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
+ $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
+ if (!empty($missing)) {
+ $missingMsg = implode(PHP_EOL, $missing);
+ throw new \Exception(
+ $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
+ [$info['name'], $missingMsg]
+ )
+ );
+ }
+ }
+}
diff --git a/lib/private/legacy/OC_Defaults.php b/lib/private/legacy/OC_Defaults.php
new file mode 100644
index 00000000000..0d460ff966d
--- /dev/null
+++ b/lib/private/legacy/OC_Defaults.php
@@ -0,0 +1,335 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+use OCP\IConfig;
+use OCP\Server;
+use OCP\ServerVersion;
+
+class OC_Defaults {
+ private $theme;
+
+ private $defaultEntity;
+ private $defaultName;
+ private $defaultTitle;
+ private $defaultBaseUrl;
+ private $defaultSyncClientUrl;
+ private $defaultiOSClientUrl;
+ private $defaultiTunesAppId;
+ private $defaultAndroidClientUrl;
+ private $defaultFDroidClientUrl;
+ private $defaultDocBaseUrl;
+ private $defaultDocVersion;
+ private $defaultSlogan;
+ private $defaultColorBackground;
+ private $defaultColorPrimary;
+ private $defaultTextColorPrimary;
+ private $defaultProductName;
+
+ public function __construct() {
+ $config = Server::get(IConfig::class);
+ $serverVersion = Server::get(ServerVersion::class);
+
+ $this->defaultEntity = 'Nextcloud'; /* e.g. company name, used for footers and copyright notices */
+ $this->defaultName = 'Nextcloud'; /* short name, used when referring to the software */
+ $this->defaultTitle = 'Nextcloud'; /* can be a longer name, for titles */
+ $this->defaultBaseUrl = 'https://nextcloud.com';
+ $this->defaultSyncClientUrl = $config->getSystemValue('customclient_desktop', 'https://nextcloud.com/install/#install-clients');
+ $this->defaultiOSClientUrl = $config->getSystemValue('customclient_ios', 'https://geo.itunes.apple.com/us/app/nextcloud/id1125420102?mt=8');
+ $this->defaultiTunesAppId = $config->getSystemValue('customclient_ios_appid', '1125420102');
+ $this->defaultAndroidClientUrl = $config->getSystemValue('customclient_android', 'https://play.google.com/store/apps/details?id=com.nextcloud.client');
+ $this->defaultFDroidClientUrl = $config->getSystemValue('customclient_fdroid', 'https://f-droid.org/packages/com.nextcloud.client/');
+ $this->defaultDocBaseUrl = 'https://docs.nextcloud.com';
+ $this->defaultDocVersion = $serverVersion->getMajorVersion(); // used to generate doc links
+ $this->defaultColorBackground = '#00679e';
+ $this->defaultColorPrimary = '#00679e';
+ $this->defaultTextColorPrimary = '#ffffff';
+ $this->defaultProductName = 'Nextcloud';
+
+ $themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php';
+ if (file_exists($themePath)) {
+ // prevent defaults.php from printing output
+ ob_start();
+ require_once $themePath;
+ ob_end_clean();
+ if (class_exists('OC_Theme')) {
+ $this->theme = new OC_Theme();
+ }
+ }
+ }
+
+ /**
+ * @param string $method
+ */
+ private function themeExist($method) {
+ if (isset($this->theme) && method_exists($this->theme, $method)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the base URL
+ * @return string URL
+ */
+ public function getBaseUrl() {
+ if ($this->themeExist('getBaseUrl')) {
+ return $this->theme->getBaseUrl();
+ } else {
+ return $this->defaultBaseUrl;
+ }
+ }
+
+ /**
+ * Returns the URL where the sync clients are listed
+ * @return string URL
+ */
+ public function getSyncClientUrl() {
+ if ($this->themeExist('getSyncClientUrl')) {
+ return $this->theme->getSyncClientUrl();
+ } else {
+ return $this->defaultSyncClientUrl;
+ }
+ }
+
+ /**
+ * Returns the URL to the App Store for the iOS Client
+ * @return string URL
+ */
+ public function getiOSClientUrl() {
+ if ($this->themeExist('getiOSClientUrl')) {
+ return $this->theme->getiOSClientUrl();
+ } else {
+ return $this->defaultiOSClientUrl;
+ }
+ }
+
+ /**
+ * Returns the AppId for the App Store for the iOS Client
+ * @return string AppId
+ */
+ public function getiTunesAppId() {
+ if ($this->themeExist('getiTunesAppId')) {
+ return $this->theme->getiTunesAppId();
+ } else {
+ return $this->defaultiTunesAppId;
+ }
+ }
+
+ /**
+ * Returns the URL to Google Play for the Android Client
+ * @return string URL
+ */
+ public function getAndroidClientUrl() {
+ if ($this->themeExist('getAndroidClientUrl')) {
+ return $this->theme->getAndroidClientUrl();
+ } else {
+ return $this->defaultAndroidClientUrl;
+ }
+ }
+
+ /**
+ * Returns the URL to Google Play for the Android Client
+ * @return string URL
+ */
+ public function getFDroidClientUrl() {
+ if ($this->themeExist('getFDroidClientUrl')) {
+ return $this->theme->getFDroidClientUrl();
+ } else {
+ return $this->defaultFDroidClientUrl;
+ }
+ }
+
+ /**
+ * Returns the documentation URL
+ * @return string URL
+ */
+ public function getDocBaseUrl() {
+ if ($this->themeExist('getDocBaseUrl')) {
+ return $this->theme->getDocBaseUrl();
+ } else {
+ return $this->defaultDocBaseUrl;
+ }
+ }
+
+ /**
+ * Returns the title
+ * @return string title
+ */
+ public function getTitle() {
+ if ($this->themeExist('getTitle')) {
+ return $this->theme->getTitle();
+ } else {
+ return $this->defaultTitle;
+ }
+ }
+
+ /**
+ * Returns the short name of the software
+ * @return string title
+ */
+ public function getName() {
+ if ($this->themeExist('getName')) {
+ return $this->theme->getName();
+ } else {
+ return $this->defaultName;
+ }
+ }
+
+ /**
+ * Returns the short name of the software containing HTML strings
+ * @return string title
+ */
+ public function getHTMLName() {
+ if ($this->themeExist('getHTMLName')) {
+ return $this->theme->getHTMLName();
+ } else {
+ return $this->defaultName;
+ }
+ }
+
+ /**
+ * Returns entity (e.g. company name) - used for footer, copyright
+ * @return string entity name
+ */
+ public function getEntity() {
+ if ($this->themeExist('getEntity')) {
+ return $this->theme->getEntity();
+ } else {
+ return $this->defaultEntity;
+ }
+ }
+
+ /**
+ * Returns slogan
+ * @return string slogan
+ */
+ public function getSlogan(?string $lang = null) {
+ if ($this->themeExist('getSlogan')) {
+ return $this->theme->getSlogan($lang);
+ } else {
+ if ($this->defaultSlogan === null) {
+ $l10n = \OC::$server->getL10N('lib', $lang);
+ $this->defaultSlogan = $l10n->t('a safe home for all your data');
+ }
+ return $this->defaultSlogan;
+ }
+ }
+
+ /**
+ * Returns short version of the footer
+ * @return string short footer
+ */
+ public function getShortFooter() {
+ if ($this->themeExist('getShortFooter')) {
+ $footer = $this->theme->getShortFooter();
+ } else {
+ $footer = '<a href="' . $this->getBaseUrl() . '" target="_blank"'
+ . ' rel="noreferrer noopener">' . $this->getEntity() . '</a>'
+ . ' – ' . $this->getSlogan();
+ }
+
+ return $footer;
+ }
+
+ /**
+ * Returns long version of the footer
+ * @return string long footer
+ */
+ public function getLongFooter() {
+ if ($this->themeExist('getLongFooter')) {
+ $footer = $this->theme->getLongFooter();
+ } else {
+ $footer = $this->getShortFooter();
+ }
+
+ return $footer;
+ }
+
+ /**
+ * @param string $key
+ * @return string URL to doc with key
+ */
+ public function buildDocLinkToKey($key) {
+ if ($this->themeExist('buildDocLinkToKey')) {
+ return $this->theme->buildDocLinkToKey($key);
+ }
+ return $this->getDocBaseUrl() . '/server/' . $this->defaultDocVersion . '/go.php?to=' . $key;
+ }
+
+ /**
+ * Returns primary color
+ * @return string
+ */
+ public function getColorPrimary() {
+ if ($this->themeExist('getColorPrimary')) {
+ return $this->theme->getColorPrimary();
+ }
+ if ($this->themeExist('getMailHeaderColor')) {
+ return $this->theme->getMailHeaderColor();
+ }
+ return $this->defaultColorPrimary;
+ }
+
+ /**
+ * Returns primary color
+ * @return string
+ */
+ public function getColorBackground() {
+ if ($this->themeExist('getColorBackground')) {
+ return $this->theme->getColorBackground();
+ }
+ return $this->defaultColorBackground;
+ }
+
+ /**
+ * @return array scss variables to overwrite
+ */
+ public function getScssVariables() {
+ if ($this->themeExist('getScssVariables')) {
+ return $this->theme->getScssVariables();
+ }
+ return [];
+ }
+
+ public function shouldReplaceIcons() {
+ return false;
+ }
+
+ /**
+ * Themed logo url
+ *
+ * @param bool $useSvg Whether to point to the SVG image or a fallback
+ * @return string
+ */
+ public function getLogo($useSvg = true) {
+ if ($this->themeExist('getLogo')) {
+ return $this->theme->getLogo($useSvg);
+ }
+
+ if ($useSvg) {
+ $logo = \OC::$server->getURLGenerator()->imagePath('core', 'logo/logo.svg');
+ } else {
+ $logo = \OC::$server->getURLGenerator()->imagePath('core', 'logo/logo.png');
+ }
+ return $logo . '?v=' . hash('sha1', implode('.', \OCP\Util::getVersion()));
+ }
+
+ public function getTextColorPrimary() {
+ if ($this->themeExist('getTextColorPrimary')) {
+ return $this->theme->getTextColorPrimary();
+ }
+ return $this->defaultTextColorPrimary;
+ }
+
+ public function getProductName() {
+ if ($this->themeExist('getProductName')) {
+ return $this->theme->getProductName();
+ }
+ return $this->defaultProductName;
+ }
+}
diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php
new file mode 100644
index 00000000000..4388f775623
--- /dev/null
+++ b/lib/private/legacy/OC_Helper.php
@@ -0,0 +1,423 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+use bantu\IniGetWrapper\IniGetWrapper;
+use OC\Files\FilenameValidator;
+use OC\Files\Filesystem;
+use OCP\Files\Mount\IMountPoint;
+use OCP\IBinaryFinder;
+use OCP\ICacheFactory;
+use OCP\IUser;
+use OCP\Server;
+use OCP\Util;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Collection of useful functions
+ *
+ * @psalm-type StorageInfo = array{
+ * free: float|int,
+ * mountPoint: string,
+ * mountType: string,
+ * owner: string,
+ * ownerDisplayName: string,
+ * quota: float|int,
+ * relative: float|int,
+ * total: float|int,
+ * used: float|int,
+ * }
+ */
+class OC_Helper {
+ private static $templateManager;
+ private static ?ICacheFactory $cacheFactory = null;
+ private static ?bool $quotaIncludeExternalStorage = null;
+
+ /**
+ * Recursive copying of folders
+ * @param string $src source folder
+ * @param string $dest target folder
+ * @return void
+ * @deprecated 32.0.0 - use \OCP\Files\Folder::copy
+ */
+ public static function copyr($src, $dest) {
+ if (!file_exists($src)) {
+ return;
+ }
+
+ if (is_dir($src)) {
+ if (!is_dir($dest)) {
+ mkdir($dest);
+ }
+ $files = scandir($src);
+ foreach ($files as $file) {
+ if ($file != '.' && $file != '..') {
+ self::copyr("$src/$file", "$dest/$file");
+ }
+ }
+ } else {
+ $validator = \OCP\Server::get(FilenameValidator::class);
+ if (!$validator->isForbidden($src)) {
+ copy($src, $dest);
+ }
+ }
+ }
+
+ /**
+ * @deprecated 18.0.0
+ * @return \OC\Files\Type\TemplateManager
+ */
+ public static function getFileTemplateManager() {
+ if (!self::$templateManager) {
+ self::$templateManager = new \OC\Files\Type\TemplateManager();
+ }
+ return self::$templateManager;
+ }
+
+ /**
+ * detect if a given program is found in the search PATH
+ *
+ * @param string $name
+ * @param bool $path
+ * @internal param string $program name
+ * @internal param string $optional search path, defaults to $PATH
+ * @return bool true if executable program found in path
+ * @deprecated 32.0.0 use the \OCP\IBinaryFinder
+ */
+ public static function canExecute($name, $path = false) {
+ // path defaults to PATH from environment if not set
+ if ($path === false) {
+ $path = getenv('PATH');
+ }
+ // we look for an executable file of that name
+ $exts = [''];
+ $check_fn = 'is_executable';
+ // Default check will be done with $path directories :
+ $dirs = explode(PATH_SEPARATOR, (string)$path);
+ // WARNING : We have to check if open_basedir is enabled :
+ $obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
+ if ($obd != 'none') {
+ $obd_values = explode(PATH_SEPARATOR, $obd);
+ if (count($obd_values) > 0 and $obd_values[0]) {
+ // open_basedir is in effect !
+ // We need to check if the program is in one of these dirs :
+ $dirs = $obd_values;
+ }
+ }
+ foreach ($dirs as $dir) {
+ foreach ($exts as $ext) {
+ if ($check_fn("$dir/$name" . $ext)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * copy the contents of one stream to another
+ *
+ * @param resource $source
+ * @param resource $target
+ * @return array the number of bytes copied and result
+ * @deprecated 5.0.0 - Use \OCP\Files::streamCopy
+ */
+ public static function streamCopy($source, $target) {
+ return \OCP\Files::streamCopy($source, $target, true);
+ }
+
+ /**
+ * Adds a suffix to the name in case the file exists
+ *
+ * @param string $path
+ * @param string $filename
+ * @return string
+ */
+ public static function buildNotExistingFileName($path, $filename) {
+ $view = \OC\Files\Filesystem::getView();
+ return self::buildNotExistingFileNameForView($path, $filename, $view);
+ }
+
+ /**
+ * Adds a suffix to the name in case the file exists
+ *
+ * @param string $path
+ * @param string $filename
+ * @return string
+ */
+ public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
+ if ($path === '/') {
+ $path = '';
+ }
+ if ($pos = strrpos($filename, '.')) {
+ $name = substr($filename, 0, $pos);
+ $ext = substr($filename, $pos);
+ } else {
+ $name = $filename;
+ $ext = '';
+ }
+
+ $newpath = $path . '/' . $filename;
+ if ($view->file_exists($newpath)) {
+ if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
+ //Replace the last "(number)" with "(number+1)"
+ $last_match = count($matches[0]) - 1;
+ $counter = $matches[1][$last_match][0] + 1;
+ $offset = $matches[0][$last_match][1];
+ $match_length = strlen($matches[0][$last_match][0]);
+ } else {
+ $counter = 2;
+ $match_length = 0;
+ $offset = false;
+ }
+ do {
+ if ($offset) {
+ //Replace the last "(number)" with "(number+1)"
+ $newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
+ } else {
+ $newname = $name . ' (' . $counter . ')';
+ }
+ $newpath = $path . '/' . $newname . $ext;
+ $counter++;
+ } while ($view->file_exists($newpath));
+ }
+
+ return $newpath;
+ }
+
+ /**
+ * Checks if a function is available
+ *
+ * @deprecated 25.0.0 use \OCP\Util::isFunctionEnabled instead
+ */
+ public static function is_function_enabled(string $function_name): bool {
+ return Util::isFunctionEnabled($function_name);
+ }
+
+ /**
+ * Try to find a program
+ * @deprecated 25.0.0 Use \OCP\IBinaryFinder directly
+ */
+ public static function findBinaryPath(string $program): ?string {
+ $result = Server::get(IBinaryFinder::class)->findBinaryPath($program);
+ return $result !== false ? $result : null;
+ }
+
+ /**
+ * Calculate the disc space for the given path
+ *
+ * BEWARE: this requires that Util::setupFS() was called
+ * already !
+ *
+ * @param string $path
+ * @param \OCP\Files\FileInfo $rootInfo (optional)
+ * @param bool $includeMountPoints whether to include mount points in the size calculation
+ * @param bool $useCache whether to use the cached quota values
+ * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
+ * @return StorageInfo
+ * @throws \OCP\Files\NotFoundException
+ */
+ public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) {
+ if (!self::$cacheFactory) {
+ self::$cacheFactory = Server::get(ICacheFactory::class);
+ }
+ $memcache = self::$cacheFactory->createLocal('storage_info');
+
+ // return storage info without adding mount points
+ if (self::$quotaIncludeExternalStorage === null) {
+ self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
+ }
+
+ $view = Filesystem::getView();
+ if (!$view) {
+ throw new \OCP\Files\NotFoundException();
+ }
+ $fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
+
+ $cacheKey = $fullPath . '::' . ($includeMountPoints ? 'include' : 'exclude');
+ if ($useCache) {
+ $cached = $memcache->get($cacheKey);
+ if ($cached) {
+ return $cached;
+ }
+ }
+
+ if (!$rootInfo) {
+ $rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false);
+ }
+ if (!$rootInfo instanceof \OCP\Files\FileInfo) {
+ throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing');
+ }
+ $used = $rootInfo->getSize($includeMountPoints);
+ if ($used < 0) {
+ $used = 0.0;
+ }
+ /** @var int|float $quota */
+ $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
+ $mount = $rootInfo->getMountPoint();
+ $storage = $mount->getStorage();
+ $sourceStorage = $storage;
+ if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
+ self::$quotaIncludeExternalStorage = false;
+ }
+ if (self::$quotaIncludeExternalStorage) {
+ if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
+ || $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
+ ) {
+ /** @var \OC\Files\Storage\Home $storage */
+ $user = $storage->getUser();
+ } else {
+ $user = \OC::$server->getUserSession()->getUser();
+ }
+ $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);
+ }
+ }
+
+ // TODO: need a better way to get total space from storage
+ if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
+ /** @var \OC\Files\Storage\Wrapper\Quota $storage */
+ $quota = $sourceStorage->getQuota();
+ }
+ try {
+ $free = $sourceStorage->free_space($rootInfo->getInternalPath());
+ if (is_bool($free)) {
+ $free = 0.0;
+ }
+ } catch (\Exception $e) {
+ if ($path === '') {
+ throw $e;
+ }
+ /** @var LoggerInterface $logger */
+ $logger = \OC::$server->get(LoggerInterface::class);
+ $logger->warning('Error while getting quota info, using root quota', ['exception' => $e]);
+ $rootInfo = self::getStorageInfo('');
+ $memcache->set($cacheKey, $rootInfo, 5 * 60);
+ return $rootInfo;
+ }
+ if ($free >= 0) {
+ $total = $free + $used;
+ } else {
+ $total = $free; //either unknown or unlimited
+ }
+ if ($total > 0) {
+ if ($quota > 0 && $total > $quota) {
+ $total = $quota;
+ }
+ // prevent division by zero or error codes (negative values)
+ $relative = round(($used / $total) * 10000) / 100;
+ } else {
+ $relative = 0;
+ }
+
+ /*
+ * \OCA\Files_Sharing\External\Storage returns the cloud ID as the owner for the storage.
+ * It is unnecessary to query the user manager for the display name, as it won't have this information.
+ */
+ $isRemoteShare = $storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class);
+
+ $ownerId = $storage->getOwner($path);
+ $ownerDisplayName = '';
+
+ if ($isRemoteShare === false && $ownerId !== false) {
+ $ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? '';
+ }
+
+ if (substr_count($mount->getMountPoint(), '/') < 3) {
+ $mountPoint = '';
+ } else {
+ [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
+ }
+
+ $info = [
+ 'free' => $free,
+ 'used' => $used,
+ 'quota' => $quota,
+ 'total' => $total,
+ 'relative' => $relative,
+ 'owner' => $ownerId,
+ 'ownerDisplayName' => $ownerDisplayName,
+ 'mountType' => $mount->getMountType(),
+ 'mountPoint' => trim($mountPoint, '/'),
+ ];
+
+ if ($isRemoteShare === false && $ownerId !== false && $path === '/') {
+ // If path is root, store this as last known quota usage for this user
+ \OCP\Server::get(\OCP\IConfig::class)->setUserValue($ownerId, 'files', 'lastSeenQuotaUsage', (string)$relative);
+ }
+
+ $memcache->set($cacheKey, $info, 5 * 60);
+
+ return $info;
+ }
+
+ /**
+ * Get storage info including all mount points and quota
+ *
+ * @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
+ * @return StorageInfo
+ */
+ private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array {
+ $rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
+ /** @var int|float $used */
+ $used = $rootInfo['size'];
+ if ($used < 0) {
+ $used = 0.0;
+ }
+
+ $total = $quota;
+ /** @var int|float $free */
+ $free = $quota - $used;
+
+ if ($total > 0) {
+ if ($quota > 0 && $total > $quota) {
+ $total = $quota;
+ }
+ // prevent division by zero or error codes (negative values)
+ $relative = round(($used / $total) * 10000) / 100;
+ } else {
+ $relative = 0.0;
+ }
+
+ if (substr_count($mount->getMountPoint(), '/') < 3) {
+ $mountPoint = '';
+ } else {
+ [,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
+ }
+
+ return [
+ 'free' => $free,
+ 'used' => $used,
+ 'total' => $total,
+ 'relative' => $relative,
+ 'quota' => $quota,
+ 'owner' => $user->getUID(),
+ 'ownerDisplayName' => $user->getDisplayName(),
+ 'mountType' => $mount->getMountType(),
+ 'mountPoint' => trim($mountPoint, '/'),
+ ];
+ }
+
+ public static function clearStorageInfo(string $absolutePath): void {
+ /** @var ICacheFactory $cacheFactory */
+ $cacheFactory = \OC::$server->get(ICacheFactory::class);
+ $memcache = $cacheFactory->createLocal('storage_info');
+ $cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
+ $memcache->remove($cacheKeyPrefix . 'include');
+ $memcache->remove($cacheKeyPrefix . 'exclude');
+ }
+
+ /**
+ * Returns whether the config file is set manually to read-only
+ * @return bool
+ * @deprecated 32.0.0 use the `config_is_read_only` system config directly
+ */
+ public static function isReadOnlyConfigEnabled() {
+ return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false);
+ }
+}
diff --git a/lib/private/legacy/OC_Hook.php b/lib/private/legacy/OC_Hook.php
new file mode 100644
index 00000000000..e472b105498
--- /dev/null
+++ b/lib/private/legacy/OC_Hook.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+use Psr\Log\LoggerInterface;
+
+class OC_Hook {
+ public static $thrownExceptions = [];
+
+ private static $registered = [];
+
+ /**
+ * connects a function to a hook
+ *
+ * @param string $signalClass class name of emitter
+ * @param string $signalName name of signal
+ * @param string|object $slotClass class name of slot
+ * @param string $slotName name of slot
+ * @return bool
+ *
+ * This function makes it very easy to connect to use hooks.
+ *
+ * TODO: write example
+ */
+ public static function connect($signalClass, $signalName, $slotClass, $slotName) {
+ // If we're trying to connect to an emitting class that isn't
+ // yet registered, register it
+ if (!array_key_exists($signalClass, self::$registered)) {
+ self::$registered[$signalClass] = [];
+ }
+ // If we're trying to connect to an emitting method that isn't
+ // yet registered, register it with the emitting class
+ if (!array_key_exists($signalName, self::$registered[$signalClass])) {
+ self::$registered[$signalClass][$signalName] = [];
+ }
+
+ // don't connect hooks twice
+ foreach (self::$registered[$signalClass][$signalName] as $hook) {
+ if ($hook['class'] === $slotClass and $hook['name'] === $slotName) {
+ return false;
+ }
+ }
+ // Connect the hook handler to the requested emitter
+ self::$registered[$signalClass][$signalName][] = [
+ 'class' => $slotClass,
+ 'name' => $slotName
+ ];
+
+ // No chance for failure ;-)
+ return true;
+ }
+
+ /**
+ * emits a signal
+ *
+ * @param string $signalClass class name of emitter
+ * @param string $signalName name of signal
+ * @param mixed $params default: array() array with additional data
+ * @return bool true if slots exists or false if not
+ * @throws \OCP\HintException
+ * @throws \OC\ServerNotAvailableException Emits a signal. To get data from the slot use references!
+ *
+ * TODO: write example
+ */
+ public static function emit($signalClass, $signalName, $params = []) {
+ // Return false if no hook handlers are listening to this
+ // emitting class
+ if (!array_key_exists($signalClass, self::$registered)) {
+ return false;
+ }
+
+ // Return false if no hook handlers are listening to this
+ // emitting method
+ if (!array_key_exists($signalName, self::$registered[$signalClass])) {
+ return false;
+ }
+
+ // Call all slots
+ foreach (self::$registered[$signalClass][$signalName] as $i) {
+ try {
+ call_user_func([ $i['class'], $i['name'] ], $params);
+ } catch (Exception $e) {
+ self::$thrownExceptions[] = $e;
+ \OCP\Server::get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
+ if ($e instanceof \OCP\HintException) {
+ throw $e;
+ }
+ if ($e instanceof \OC\ServerNotAvailableException) {
+ throw $e;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * clear hooks
+ * @param string $signalClass
+ * @param string $signalName
+ */
+ public static function clear($signalClass = '', $signalName = '') {
+ if ($signalClass) {
+ if ($signalName) {
+ self::$registered[$signalClass][$signalName] = [];
+ } else {
+ self::$registered[$signalClass] = [];
+ }
+ } else {
+ self::$registered = [];
+ }
+ }
+
+ /**
+ * DO NOT USE!
+ * For unit tests ONLY!
+ */
+ public static function getHooks() {
+ return self::$registered;
+ }
+}
diff --git a/lib/private/legacy/OC_JSON.php b/lib/private/legacy/OC_JSON.php
new file mode 100644
index 00000000000..6daef18dd61
--- /dev/null
+++ b/lib/private/legacy/OC_JSON.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+use OC\Authentication\TwoFactorAuth\Manager as TwoFactorAuthManager;
+
+class OC_JSON {
+ /**
+ * Check if the app is enabled, send json error msg if not
+ * @param string $app
+ * @deprecated 12.0.0 Use the AppFramework instead. It will automatically check if the app is enabled.
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function checkAppEnabled($app) {
+ if (!\OC::$server->getAppManager()->isEnabledForUser($app)) {
+ $l = \OC::$server->getL10N('lib');
+ self::error([ 'data' => [ 'message' => $l->t('Application is not enabled'), 'error' => 'application_not_enabled' ]]);
+ exit();
+ }
+ }
+
+ /**
+ * Check if the user is logged in, send json error msg if not
+ * @deprecated 12.0.0 Use annotation based ACLs from the AppFramework instead
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function checkLoggedIn() {
+ $twoFactorAuthManger = \OC::$server->get(TwoFactorAuthManager::class);
+ if (!\OC::$server->getUserSession()->isLoggedIn()
+ || $twoFactorAuthManger->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
+ $l = \OC::$server->getL10N('lib');
+ http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED);
+ self::error([ 'data' => [ 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ]]);
+ exit();
+ }
+ }
+
+ /**
+ * Check an ajax get/post call if the request token is valid, send json error msg if not.
+ * @deprecated 12.0.0 Use annotation based CSRF checks from the AppFramework instead
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function callCheck() {
+ if (!\OC::$server->getRequest()->passesStrictCookieCheck()) {
+ header('Location: ' . \OC::$WEBROOT);
+ exit();
+ }
+
+ if (!\OC::$server->getRequest()->passesCSRFCheck()) {
+ $l = \OC::$server->getL10N('lib');
+ self::error([ 'data' => [ 'message' => $l->t('Token expired. Please reload page.'), 'error' => 'token_expired' ]]);
+ exit();
+ }
+ }
+
+ /**
+ * Check if the user is a admin, send json error msg if not.
+ * @deprecated 12.0.0 Use annotation based ACLs from the AppFramework instead
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function checkAdminUser() {
+ if (!OC_User::isAdminUser(OC_User::getUser())) {
+ $l = \OC::$server->getL10N('lib');
+ self::error([ 'data' => [ 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ]]);
+ exit();
+ }
+ }
+
+ /**
+ * Send json error msg
+ * @deprecated 12.0.0 Use a AppFramework JSONResponse instead
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function error($data = []) {
+ $data['status'] = 'error';
+ header('Content-Type: application/json; charset=utf-8');
+ echo self::encode($data);
+ }
+
+ /**
+ * Send json success msg
+ * @deprecated 12.0.0 Use a AppFramework JSONResponse instead
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function success($data = []) {
+ $data['status'] = 'success';
+ header('Content-Type: application/json; charset=utf-8');
+ echo self::encode($data);
+ }
+
+ /**
+ * Encode JSON
+ * @deprecated 12.0.0 Use a AppFramework JSONResponse instead
+ *
+ * @psalm-taint-escape has_quotes
+ * @psalm-taint-escape html
+ */
+ private static function encode($data) {
+ return json_encode($data, JSON_HEX_TAG);
+ }
+}
diff --git a/lib/private/legacy/OC_Response.php b/lib/private/legacy/OC_Response.php
new file mode 100644
index 00000000000..c45852b4b1d
--- /dev/null
+++ b/lib/private/legacy/OC_Response.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+class OC_Response {
+ /**
+ * Sets the content disposition header (with possible workarounds)
+ * @param string $filename file name
+ * @param string $type disposition type, either 'attachment' or 'inline'
+ */
+ public static function setContentDispositionHeader($filename, $type = 'attachment') {
+ if (\OC::$server->getRequest()->isUserAgent(
+ [
+ \OC\AppFramework\Http\Request::USER_AGENT_IE,
+ \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME,
+ \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX,
+ ])) {
+ header('Content-Disposition: ' . rawurlencode($type) . '; filename="' . rawurlencode($filename) . '"');
+ } else {
+ header('Content-Disposition: ' . rawurlencode($type) . '; filename*=UTF-8\'\'' . rawurlencode($filename)
+ . '; filename="' . rawurlencode($filename) . '"');
+ }
+ }
+
+ /**
+ * Sets the content length header (with possible workarounds)
+ * @param string|int|float $length Length to be sent
+ */
+ public static function setContentLengthHeader($length) {
+ if (PHP_INT_SIZE === 4) {
+ if ($length > PHP_INT_MAX && stripos(PHP_SAPI, 'apache') === 0) {
+ // Apache PHP SAPI casts Content-Length headers to PHP integers.
+ // This enforces a limit of PHP_INT_MAX (2147483647 on 32-bit
+ // platforms). So, if the length is greater than PHP_INT_MAX,
+ // we just do not send a Content-Length header to prevent
+ // bodies from being received incompletely.
+ return;
+ }
+ // Convert signed integer or float to unsigned base-10 string.
+ $lfh = new \OC\LargeFileHelper;
+ $length = $lfh->formatUnsignedInteger($length);
+ }
+ header('Content-Length: ' . $length);
+ }
+
+ /**
+ * This function adds some security related headers to all requests served via base.php
+ * The implementation of this function has to happen here to ensure that all third-party
+ * components (e.g. SabreDAV) also benefit from this headers.
+ */
+ public static function addSecurityHeaders() {
+ /**
+ * FIXME: Content Security Policy for legacy ownCloud components. This
+ * can be removed once \OCP\AppFramework\Http\Response from the AppFramework
+ * is used everywhere.
+ * @see \OCP\AppFramework\Http\Response::getHeaders
+ */
+ $policy = 'default-src \'self\'; '
+ . 'script-src \'self\' \'nonce-' . \OC::$server->getContentSecurityPolicyNonceManager()->getNonce() . '\'; '
+ . 'style-src \'self\' \'unsafe-inline\'; '
+ . 'frame-src *; '
+ . 'img-src * data: blob:; '
+ . 'font-src \'self\' data:; '
+ . 'media-src *; '
+ . 'connect-src *; '
+ . 'object-src \'none\'; '
+ . 'base-uri \'self\'; ';
+ header('Content-Security-Policy:' . $policy);
+
+ // Send fallback headers for installations that don't have the possibility to send
+ // custom headers on the webserver side
+ if (getenv('modHeadersAvailable') !== 'true') {
+ header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/
+ header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE
+ 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
+ }
+ }
+}
diff --git a/lib/private/legacy/OC_Template.php b/lib/private/legacy/OC_Template.php
new file mode 100644
index 00000000000..bccf99af65e
--- /dev/null
+++ b/lib/private/legacy/OC_Template.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+use OCP\Server;
+use OCP\Template\ITemplateManager;
+
+/**
+ * This class provides the templates for ownCloud.
+ * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead
+ */
+class OC_Template extends \OC\Template\Template {
+ /**
+ * Shortcut to print a simple page for guests
+ * @param string $application The application we render the template for
+ * @param string $name Name of the template
+ * @param array $parameters Parameters for the template
+ * @return bool
+ * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead
+ */
+ public static function printGuestPage($application, $name, $parameters = []) {
+ Server::get(ITemplateManager::class)->printGuestPage($application, $name, $parameters);
+ return true;
+ }
+
+ /**
+ * Print a fatal error page and terminates the script
+ * @param string $error_msg The error message to show
+ * @param string $hint An optional hint message - needs to be properly escape
+ * @param int $statusCode
+ * @return never
+ * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead
+ */
+ public static function printErrorPage($error_msg, $hint = '', $statusCode = 500) {
+ Server::get(ITemplateManager::class)->printErrorPage($error_msg, $hint, $statusCode);
+ }
+
+ /**
+ * print error page using Exception details
+ * @param Exception|Throwable $exception
+ * @param int $statusCode
+ * @return never
+ * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead
+ */
+ public static function printExceptionErrorPage($exception, $statusCode = 503) {
+ Server::get(ITemplateManager::class)->printExceptionErrorPage($exception, $statusCode);
+ }
+}
diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php
new file mode 100644
index 00000000000..e5343864c45
--- /dev/null
+++ b/lib/private/legacy/OC_User.php
@@ -0,0 +1,405 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+use OC\Authentication\Token\IProvider;
+use OC\User\DisabledUserException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\Authentication\Exceptions\WipeTokenException;
+use OCP\Authentication\Token\IToken;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IGroupManager;
+use OCP\ISession;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Server;
+use OCP\Session\Exceptions\SessionNotAvailableException;
+use OCP\User\Events\BeforeUserLoggedInEvent;
+use OCP\User\Events\UserLoggedInEvent;
+use Psr\Log\LoggerInterface;
+
+/**
+ * This class provides wrapper methods for user management. Multiple backends are
+ * supported. User management operations are delegated to the configured backend for
+ * execution.
+ *
+ * Note that &run is deprecated and won't work anymore.
+ *
+ * Hooks provided:
+ * pre_createUser(&run, uid, password)
+ * post_createUser(uid, password)
+ * pre_deleteUser(&run, uid)
+ * post_deleteUser(uid)
+ * pre_setPassword(&run, uid, password, recoveryPassword)
+ * post_setPassword(uid, password, recoveryPassword)
+ * pre_login(&run, uid, password)
+ * post_login(uid)
+ * logout()
+ */
+class OC_User {
+ private static $_setupedBackends = [];
+
+ // bool, stores if a user want to access a resource anonymously, e.g if they open a public link
+ private static $incognitoMode = false;
+
+ /**
+ * Adds the backend to the list of used backends
+ *
+ * @param string|\OCP\UserInterface $backend default: database The backend to use for user management
+ * @return bool
+ * @deprecated 32.0.0 Use IUserManager::registerBackend instead
+ *
+ * Set the User Authentication Module
+ */
+ public static function useBackend($backend = 'database') {
+ if ($backend instanceof \OCP\UserInterface) {
+ Server::get(IUserManager::class)->registerBackend($backend);
+ } else {
+ // You'll never know what happens
+ if ($backend === null or !is_string($backend)) {
+ $backend = 'database';
+ }
+
+ // Load backend
+ switch ($backend) {
+ case 'database':
+ case 'mysql':
+ case 'sqlite':
+ Server::get(LoggerInterface::class)->debug('Adding user backend ' . $backend . '.', ['app' => 'core']);
+ Server::get(IUserManager::class)->registerBackend(new \OC\User\Database());
+ break;
+ case 'dummy':
+ Server::get(IUserManager::class)->registerBackend(new \Test\Util\User\Dummy());
+ break;
+ default:
+ Server::get(LoggerInterface::class)->debug('Adding default user backend ' . $backend . '.', ['app' => 'core']);
+ $className = 'OC_USER_' . strtoupper($backend);
+ Server::get(IUserManager::class)->registerBackend(new $className());
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * remove all used backends
+ * @deprecated 32.0.0 Use IUserManager::clearBackends instead
+ */
+ public static function clearBackends() {
+ Server::get(IUserManager::class)->clearBackends();
+ }
+
+ /**
+ * setup the configured backends in config.php
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function setupBackends() {
+ OC_App::loadApps(['prelogin']);
+ $backends = \OC::$server->getSystemConfig()->getValue('user_backends', []);
+ if (isset($backends['default']) && !$backends['default']) {
+ // clear default backends
+ self::clearBackends();
+ }
+ foreach ($backends as $i => $config) {
+ if (!is_array($config)) {
+ continue;
+ }
+ $class = $config['class'];
+ $arguments = $config['arguments'];
+ if (class_exists($class)) {
+ if (!in_array($i, self::$_setupedBackends)) {
+ // make a reflection object
+ $reflectionObj = new ReflectionClass($class);
+
+ // use Reflection to create a new instance, using the $args
+ $backend = $reflectionObj->newInstanceArgs($arguments);
+ self::useBackend($backend);
+ self::$_setupedBackends[] = $i;
+ } else {
+ Server::get(LoggerInterface::class)->debug('User backend ' . $class . ' already initialized.', ['app' => 'core']);
+ }
+ } else {
+ Server::get(LoggerInterface::class)->error('User backend ' . $class . ' not found.', ['app' => 'core']);
+ }
+ }
+ }
+
+ /**
+ * Try to login a user, assuming authentication
+ * has already happened (e.g. via Single Sign On).
+ *
+ * Log in a user and regenerate a new session.
+ *
+ * @param \OCP\Authentication\IApacheBackend $backend
+ * @return bool
+ */
+ public static function loginWithApache(\OCP\Authentication\IApacheBackend $backend) {
+ $uid = $backend->getCurrentUserId();
+ $run = true;
+ OC_Hook::emit('OC_User', 'pre_login', ['run' => &$run, 'uid' => $uid, 'backend' => $backend]);
+
+ if ($uid) {
+ if (self::getUser() !== $uid) {
+ self::setUserId($uid);
+ $userSession = \OC::$server->getUserSession();
+
+ /** @var IEventDispatcher $dispatcher */
+ $dispatcher = \OC::$server->get(IEventDispatcher::class);
+
+ if ($userSession->getUser() && !$userSession->getUser()->isEnabled()) {
+ $message = \OC::$server->getL10N('lib')->t('Account disabled');
+ throw new DisabledUserException($message);
+ }
+ $userSession->setLoginName($uid);
+ $request = OC::$server->getRequest();
+ $password = null;
+ if ($backend instanceof \OCP\Authentication\IProvideUserSecretBackend) {
+ $password = $backend->getCurrentUserSecret();
+ }
+
+ /** @var IEventDispatcher $dispatcher */
+ $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password, $backend));
+
+ $userSession->createSessionToken($request, $uid, $uid, $password);
+ $userSession->createRememberMeToken($userSession->getUser());
+
+ if (empty($password)) {
+ $tokenProvider = \OC::$server->get(IProvider::class);
+ try {
+ $token = $tokenProvider->getToken($userSession->getSession()->getId());
+ $token->setScope([
+ IToken::SCOPE_SKIP_PASSWORD_VALIDATION => true,
+ IToken::SCOPE_FILESYSTEM => true,
+ ]);
+ $tokenProvider->updateToken($token);
+ } catch (InvalidTokenException|WipeTokenException|SessionNotAvailableException) {
+ // swallow the exceptions as we do not deal with them here
+ // simply skip updating the token when is it missing
+ }
+ }
+
+ // setup the filesystem
+ OC_Util::setupFS($uid);
+ // first call the post_login hooks, the login-process needs to be
+ // completed before we can safely create the users folder.
+ // For example encryption needs to initialize the users keys first
+ // before we can create the user folder with the skeleton files
+ OC_Hook::emit(
+ 'OC_User',
+ 'post_login',
+ [
+ 'uid' => $uid,
+ 'password' => $password,
+ 'isTokenLogin' => false,
+ ]
+ );
+ $dispatcher->dispatchTyped(new UserLoggedInEvent(
+ \OC::$server->get(IUserManager::class)->get($uid),
+ $uid,
+ null,
+ false)
+ );
+
+ //trigger creation of user home and /files folder
+ \OC::$server->getUserFolder($uid);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Verify with Apache whether user is authenticated.
+ *
+ * @return boolean|null
+ * true: authenticated
+ * false: not authenticated
+ * null: not handled / no backend available
+ */
+ public static function handleApacheAuth() {
+ $backend = self::findFirstActiveUsedBackend();
+ if ($backend) {
+ OC_App::loadApps();
+
+ //setup extra user backends
+ self::setupBackends();
+ \OC::$server->getUserSession()->unsetMagicInCookie();
+
+ return self::loginWithApache($backend);
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Sets user id for session and triggers emit
+ *
+ * @param string $uid
+ */
+ public static function setUserId($uid) {
+ $userSession = \OC::$server->getUserSession();
+ $userManager = Server::get(IUserManager::class);
+ if ($user = $userManager->get($uid)) {
+ $userSession->setUser($user);
+ } else {
+ \OC::$server->getSession()->set('user_id', $uid);
+ }
+ }
+
+ /**
+ * Check if the user is logged in, considers also the HTTP basic credentials
+ *
+ * @deprecated 12.0.0 use \OC::$server->getUserSession()->isLoggedIn()
+ * @return bool
+ */
+ public static function isLoggedIn() {
+ return \OC::$server->getUserSession()->isLoggedIn();
+ }
+
+ /**
+ * set incognito mode, e.g. if a user wants to open a public link
+ *
+ * @param bool $status
+ */
+ public static function setIncognitoMode($status) {
+ self::$incognitoMode = $status;
+ }
+
+ /**
+ * get incognito mode status
+ *
+ * @return bool
+ */
+ public static function isIncognitoMode() {
+ return self::$incognitoMode;
+ }
+
+ /**
+ * Returns the current logout URL valid for the currently logged-in user
+ *
+ * @param \OCP\IURLGenerator $urlGenerator
+ * @return string
+ */
+ public static function getLogoutUrl(\OCP\IURLGenerator $urlGenerator) {
+ $backend = self::findFirstActiveUsedBackend();
+ if ($backend) {
+ return $backend->getLogoutUrl();
+ }
+
+ $user = \OC::$server->getUserSession()->getUser();
+ if ($user instanceof IUser) {
+ $backend = $user->getBackend();
+ if ($backend instanceof \OCP\User\Backend\ICustomLogout) {
+ return $backend->getLogoutUrl();
+ }
+ }
+
+ $logoutUrl = $urlGenerator->linkToRoute('core.login.logout');
+ $logoutUrl .= '?requesttoken=' . urlencode(\OCP\Util::callRegister());
+
+ return $logoutUrl;
+ }
+
+ /**
+ * Check if the user is an admin user
+ *
+ * @param string $uid uid of the admin
+ * @return bool
+ */
+ public static function isAdminUser($uid) {
+ $user = Server::get(IUserManager::class)->get($uid);
+ $isAdmin = $user && Server::get(IGroupManager::class)->isAdmin($user->getUID());
+ return $isAdmin && self::$incognitoMode === false;
+ }
+
+
+ /**
+ * get the user id of the user currently logged in.
+ *
+ * @return string|false uid or false
+ */
+ public static function getUser() {
+ $uid = Server::get(ISession::class)?->get('user_id');
+ if (!is_null($uid) && self::$incognitoMode === false) {
+ return $uid;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set password
+ *
+ * @param string $uid The username
+ * @param string $password The new password
+ * @param string $recoveryPassword for the encryption app to reset encryption keys
+ * @return bool
+ *
+ * Change the password of a user
+ */
+ public static function setPassword($uid, $password, $recoveryPassword = null) {
+ $user = Server::get(IUserManager::class)->get($uid);
+ if ($user) {
+ return $user->setPassword($password, $recoveryPassword);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param string $uid The username
+ * @return string
+ *
+ * returns the path to the users home directory
+ * @deprecated 12.0.0 Use \OC::$server->getUserManager->getHome()
+ */
+ public static function getHome($uid) {
+ $user = Server::get(IUserManager::class)->get($uid);
+ if ($user) {
+ return $user->getHome();
+ } else {
+ return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid;
+ }
+ }
+
+ /**
+ * Get a list of all users display name
+ *
+ * @param string $search
+ * @param int $limit
+ * @param int $offset
+ * @return array associative array with all display names (value) and corresponding uids (key)
+ *
+ * Get a list of all display names and user ids.
+ * @deprecated 12.0.0 Use \OC::$server->getUserManager->searchDisplayName($search, $limit, $offset) instead.
+ */
+ public static function getDisplayNames($search = '', $limit = null, $offset = null) {
+ $displayNames = [];
+ $users = Server::get(IUserManager::class)->searchDisplayName($search, $limit, $offset);
+ foreach ($users as $user) {
+ $displayNames[$user->getUID()] = $user->getDisplayName();
+ }
+ return $displayNames;
+ }
+
+ /**
+ * Returns the first active backend from self::$_usedBackends.
+ *
+ * @return OCP\Authentication\IApacheBackend|null if no backend active, otherwise OCP\Authentication\IApacheBackend
+ */
+ private static function findFirstActiveUsedBackend() {
+ foreach (Server::get(IUserManager::class)->getBackends() as $backend) {
+ if ($backend instanceof OCP\Authentication\IApacheBackend) {
+ if ($backend->isSessionActive()) {
+ return $backend;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php
new file mode 100644
index 00000000000..ebca1105838
--- /dev/null
+++ b/lib/private/legacy/OC_Util.php
@@ -0,0 +1,847 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+use bantu\IniGetWrapper\IniGetWrapper;
+use OC\Authentication\TwoFactorAuth\Manager as TwoFactorAuthManager;
+use OC\Files\SetupManager;
+use OCP\Files\Template\ITemplateManager;
+use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\L10N\IFactory;
+use OCP\Security\ISecureRandom;
+use OCP\Share\IManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @deprecated 32.0.0 Use \OCP\Util or any appropriate official API instead
+ */
+class OC_Util {
+ public static $styles = [];
+ public static $headers = [];
+
+ /**
+ * Setup the file system
+ *
+ * @param string|null $user
+ * @return boolean
+ * @description configure the initial filesystem based on the configuration
+ * @suppress PhanDeprecatedFunction
+ * @suppress PhanAccessMethodInternal
+ */
+ public static function setupFS(?string $user = '') {
+ // If we are not forced to load a specific user we load the one that is logged in
+ if ($user === '') {
+ $userObject = \OC::$server->get(\OCP\IUserSession::class)->getUser();
+ } else {
+ $userObject = \OC::$server->get(\OCP\IUserManager::class)->get($user);
+ }
+
+ /** @var SetupManager $setupManager */
+ $setupManager = \OC::$server->get(SetupManager::class);
+
+ if ($userObject) {
+ $setupManager->setupForUser($userObject);
+ } else {
+ $setupManager->setupRoot();
+ }
+ return true;
+ }
+
+ /**
+ * Check if a password is required for each public link
+ *
+ * @param bool $checkGroupMembership Check group membership exclusion
+ * @return bool
+ * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkEnforcePassword directly
+ */
+ public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
+ /** @var IManager $shareManager */
+ $shareManager = \OC::$server->get(IManager::class);
+ return $shareManager->shareApiLinkEnforcePassword($checkGroupMembership);
+ }
+
+ /**
+ * check if sharing is disabled for the current user
+ * @param IConfig $config
+ * @param IGroupManager $groupManager
+ * @param IUser|null $user
+ * @return bool
+ * @deprecated 32.0.0 use OCP\Share\IManager's sharingDisabledForUser directly
+ */
+ public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
+ /** @var IManager $shareManager */
+ $shareManager = \OC::$server->get(IManager::class);
+ $userId = $user ? $user->getUID() : null;
+ return $shareManager->sharingDisabledForUser($userId);
+ }
+
+ /**
+ * check if share API enforces a default expire date
+ *
+ * @return bool
+ * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkDefaultExpireDateEnforced directly
+ */
+ public static function isDefaultExpireDateEnforced() {
+ /** @var IManager $shareManager */
+ $shareManager = \OC::$server->get(IManager::class);
+ return $shareManager->shareApiLinkDefaultExpireDateEnforced();
+ }
+
+ /**
+ * Get the quota of a user
+ *
+ * @param IUser|null $user
+ * @return int|\OCP\Files\FileInfo::SPACE_UNLIMITED|false|float Quota bytes
+ * @deprecated 9.0.0 - Use \OCP\IUser::getQuota or \OCP\IUser::getQuotaBytes
+ */
+ public static function getUserQuota(?IUser $user) {
+ if (is_null($user)) {
+ return \OCP\Files\FileInfo::SPACE_UNLIMITED;
+ }
+ $userQuota = $user->getQuota();
+ if ($userQuota === 'none') {
+ return \OCP\Files\FileInfo::SPACE_UNLIMITED;
+ }
+ return \OCP\Util::computerFileSize($userQuota);
+ }
+
+ /**
+ * copies the skeleton to the users /files
+ *
+ * @param string $userId
+ * @param \OCP\Files\Folder $userDirectory
+ * @throws \OCP\Files\NotFoundException
+ * @throws \OCP\Files\NotPermittedException
+ * @suppress PhanDeprecatedFunction
+ */
+ public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
+ /** @var LoggerInterface $logger */
+ $logger = \OC::$server->get(LoggerInterface::class);
+
+ $plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
+ $userLang = \OC::$server->get(IFactory::class)->findLanguage();
+ $skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
+
+ if (!file_exists($skeletonDirectory)) {
+ $dialectStart = strpos($userLang, '_');
+ if ($dialectStart !== false) {
+ $skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
+ }
+ if ($dialectStart === false || !file_exists($skeletonDirectory)) {
+ $skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
+ }
+ if (!file_exists($skeletonDirectory)) {
+ $skeletonDirectory = '';
+ }
+ }
+
+ $instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
+
+ if ($instanceId === null) {
+ throw new \RuntimeException('no instance id!');
+ }
+ $appdata = 'appdata_' . $instanceId;
+ if ($userId === $appdata) {
+ throw new \RuntimeException('username is reserved name: ' . $appdata);
+ }
+
+ if (!empty($skeletonDirectory)) {
+ $logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
+ self::copyr($skeletonDirectory, $userDirectory);
+ // update the file cache
+ $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
+
+ /** @var ITemplateManager $templateManager */
+ $templateManager = \OC::$server->get(ITemplateManager::class);
+ $templateManager->initializeTemplateDirectory(null, $userId);
+ }
+ }
+
+ /**
+ * copies a directory recursively by using streams
+ *
+ * @param string $source
+ * @param \OCP\Files\Folder $target
+ * @return void
+ */
+ public static function copyr($source, \OCP\Files\Folder $target) {
+ $logger = \OCP\Server::get(LoggerInterface::class);
+
+ // Verify if folder exists
+ $dir = opendir($source);
+ if ($dir === false) {
+ $logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
+ return;
+ }
+
+ // Copy the files
+ while (false !== ($file = readdir($dir))) {
+ if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
+ if (is_dir($source . '/' . $file)) {
+ $child = $target->newFolder($file);
+ self::copyr($source . '/' . $file, $child);
+ } else {
+ $sourceStream = fopen($source . '/' . $file, 'r');
+ if ($sourceStream === false) {
+ $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
+ closedir($dir);
+ return;
+ }
+ $target->newFile($file, $sourceStream);
+ }
+ }
+ }
+ closedir($dir);
+ }
+
+ /**
+ * @deprecated 32.0.0 Call tearDown directly on SetupManager
+ */
+ public static function tearDownFS(): void {
+ $setupManager = \OCP\Server::get(SetupManager::class);
+ $setupManager->tearDown();
+ }
+
+ /**
+ * generates a path for JS/CSS files. If no application is provided it will create the path for core.
+ *
+ * @param string $application application to get the files from
+ * @param string $directory directory within this application (css, js, vendor, etc)
+ * @param ?string $file the file inside of the above folder
+ */
+ private static function generatePath($application, $directory, $file): string {
+ if (is_null($file)) {
+ $file = $application;
+ $application = '';
+ }
+ if (!empty($application)) {
+ return "$application/$directory/$file";
+ } else {
+ return "$directory/$file";
+ }
+ }
+
+ /**
+ * add a css file
+ *
+ * @param string $application application id
+ * @param string|null $file filename
+ * @param bool $prepend prepend the Style to the beginning of the list
+ * @deprecated 32.0.0 Use \OCP\Util::addStyle
+ */
+ public static function addStyle($application, $file = null, $prepend = false): void {
+ $path = OC_Util::generatePath($application, 'css', $file);
+ self::addExternalResource($application, $prepend, $path, 'style');
+ }
+
+ /**
+ * add a css file from the vendor sub folder
+ *
+ * @param string $application application id
+ * @param string|null $file filename
+ * @param bool $prepend prepend the Style to the beginning of the list
+ * @deprecated 32.0.0
+ */
+ public static function addVendorStyle($application, $file = null, $prepend = false): void {
+ $path = OC_Util::generatePath($application, 'vendor', $file);
+ self::addExternalResource($application, $prepend, $path, 'style');
+ }
+
+ /**
+ * add an external resource css/js file
+ *
+ * @param string $application application id
+ * @param bool $prepend prepend the file to the beginning of the list
+ * @param string $path
+ * @param string $type (script or style)
+ */
+ private static function addExternalResource($application, $prepend, $path, $type = 'script'): void {
+ if ($type === 'style') {
+ if (!in_array($path, self::$styles)) {
+ if ($prepend === true) {
+ array_unshift(self::$styles, $path);
+ } else {
+ self::$styles[] = $path;
+ }
+ }
+ }
+ }
+
+ /**
+ * Add a custom element to the header
+ * If $text is null then the element will be written as empty element.
+ * So use "" to get a closing tag.
+ * @param string $tag tag name of the element
+ * @param array $attributes array of attributes for the element
+ * @param string $text the text content for the element
+ * @param bool $prepend prepend the header to the beginning of the list
+ * @deprecated 32.0.0 Use \OCP\Util::addHeader instead
+ */
+ public static function addHeader($tag, $attributes, $text = null, $prepend = false): void {
+ $header = [
+ 'tag' => $tag,
+ 'attributes' => $attributes,
+ 'text' => $text
+ ];
+ if ($prepend === true) {
+ array_unshift(self::$headers, $header);
+ } else {
+ self::$headers[] = $header;
+ }
+ }
+
+ /**
+ * check if the current server configuration is suitable for ownCloud
+ *
+ * @return array arrays with error messages and hints
+ */
+ public static function checkServer(\OC\SystemConfig $config) {
+ $l = \OC::$server->getL10N('lib');
+ $errors = [];
+ $CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
+
+ if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
+ // this check needs to be done every time
+ $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
+ }
+
+ // Assume that if checkServer() succeeded before in this session, then all is fine.
+ if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
+ return $errors;
+ }
+
+ $webServerRestart = false;
+ $setup = \OCP\Server::get(\OC\Setup::class);
+
+ $urlGenerator = \OC::$server->getURLGenerator();
+
+ $availableDatabases = $setup->getSupportedDatabases();
+ if (empty($availableDatabases)) {
+ $errors[] = [
+ 'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
+ 'hint' => '' //TODO: sane hint
+ ];
+ $webServerRestart = true;
+ }
+
+ // Check if config folder is writable.
+ if (!(bool)$config->getValue('config_is_read_only', false)) {
+ if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
+ $errors[] = [
+ 'error' => $l->t('Cannot write into "config" directory.'),
+ 'hint' => $l->t('This can usually be fixed by giving the web server write access to the config directory. See %s',
+ [ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
+ . $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
+ [ $urlGenerator->linkToDocs('admin-config') ])
+ ];
+ }
+ }
+
+ // Create root dir.
+ if ($config->getValue('installed', false)) {
+ if (!is_dir($CONFIG_DATADIRECTORY)) {
+ $success = @mkdir($CONFIG_DATADIRECTORY);
+ if ($success) {
+ $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
+ } else {
+ $errors[] = [
+ 'error' => $l->t('Cannot create "data" directory.'),
+ 'hint' => $l->t('This can usually be fixed by giving the web server write access to the root directory. See %s',
+ [$urlGenerator->linkToDocs('admin-dir_permissions')])
+ ];
+ }
+ } elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
+ // is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
+ $testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
+ $handle = fopen($testFile, 'w');
+ if (!$handle || fwrite($handle, 'Test write operation') === false) {
+ $permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.',
+ [$urlGenerator->linkToDocs('admin-dir_permissions')]);
+ $errors[] = [
+ 'error' => $l->t('Your data directory is not writable.'),
+ 'hint' => $permissionsHint
+ ];
+ } else {
+ fclose($handle);
+ unlink($testFile);
+ }
+ } else {
+ $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
+ }
+ }
+
+ if (!OC_Util::isSetLocaleWorking()) {
+ $errors[] = [
+ 'error' => $l->t('Setting locale to %s failed.',
+ ['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
+ . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
+ 'hint' => $l->t('Please install one of these locales on your system and restart your web server.')
+ ];
+ }
+
+ // Contains the dependencies that should be checked against
+ // classes = class_exists
+ // functions = function_exists
+ // defined = defined
+ // ini = ini_get
+ // If the dependency is not found the missing module name is shown to the EndUser
+ // When adding new checks always verify that they pass on CI as well
+ $dependencies = [
+ 'classes' => [
+ 'ZipArchive' => 'zip',
+ 'DOMDocument' => 'dom',
+ 'XMLWriter' => 'XMLWriter',
+ 'XMLReader' => 'XMLReader',
+ ],
+ 'functions' => [
+ 'xml_parser_create' => 'libxml',
+ 'mb_strcut' => 'mbstring',
+ 'ctype_digit' => 'ctype',
+ 'json_encode' => 'JSON',
+ 'gd_info' => 'GD',
+ 'gzencode' => 'zlib',
+ 'simplexml_load_string' => 'SimpleXML',
+ 'hash' => 'HASH Message Digest Framework',
+ 'curl_init' => 'cURL',
+ 'openssl_verify' => 'OpenSSL',
+ ],
+ 'defined' => [
+ 'PDO::ATTR_DRIVER_NAME' => 'PDO'
+ ],
+ 'ini' => [
+ 'default_charset' => 'UTF-8',
+ ],
+ ];
+ $missingDependencies = [];
+ $invalidIniSettings = [];
+
+ $iniWrapper = \OC::$server->get(IniGetWrapper::class);
+ foreach ($dependencies['classes'] as $class => $module) {
+ if (!class_exists($class)) {
+ $missingDependencies[] = $module;
+ }
+ }
+ foreach ($dependencies['functions'] as $function => $module) {
+ if (!function_exists($function)) {
+ $missingDependencies[] = $module;
+ }
+ }
+ foreach ($dependencies['defined'] as $defined => $module) {
+ if (!defined($defined)) {
+ $missingDependencies[] = $module;
+ }
+ }
+ foreach ($dependencies['ini'] as $setting => $expected) {
+ if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
+ $invalidIniSettings[] = [$setting, $expected];
+ }
+ }
+
+ foreach ($missingDependencies as $missingDependency) {
+ $errors[] = [
+ 'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
+ 'hint' => $l->t('Please ask your server administrator to install the module.'),
+ ];
+ $webServerRestart = true;
+ }
+ foreach ($invalidIniSettings as $setting) {
+ $errors[] = [
+ 'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
+ 'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
+ ];
+ $webServerRestart = true;
+ }
+
+ /**
+ * The mbstring.func_overload check can only be performed if the mbstring
+ * module is installed as it will return null if the checking setting is
+ * not available and thus a check on the boolean value fails.
+ *
+ * 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) {
+ $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.')
+ ];
+ }
+
+ if (!self::isAnnotationsWorking()) {
+ $errors[] = [
+ 'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
+ 'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
+ ];
+ }
+
+ if (!\OC::$CLI && $webServerRestart) {
+ $errors[] = [
+ 'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
+ 'hint' => $l->t('Please ask your server administrator to restart the web server.')
+ ];
+ }
+
+ foreach (['secret', 'instanceid', 'passwordsalt'] as $requiredConfig) {
+ if ($config->getValue($requiredConfig, '') === '' && !\OC::$CLI && $config->getValue('installed', false)) {
+ $errors[] = [
+ 'error' => $l->t('The required %s config variable is not configured in the config.php file.', [$requiredConfig]),
+ 'hint' => $l->t('Please ask your server administrator to check the Nextcloud configuration.')
+ ];
+ }
+ }
+
+ // Cache the result of this function
+ \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
+
+ return $errors;
+ }
+
+ /**
+ * Check for correct file permissions of data directory
+ *
+ * @param string $dataDirectory
+ * @return array arrays with error messages and hints
+ * @internal
+ */
+ public static function checkDataDirectoryPermissions($dataDirectory) {
+ if (!\OC::$server->getConfig()->getSystemValueBool('check_data_directory_permissions', true)) {
+ return [];
+ }
+
+ $perms = substr(decoct(@fileperms($dataDirectory)), -3);
+ if (substr($perms, -1) !== '0') {
+ chmod($dataDirectory, 0770);
+ clearstatcache();
+ $perms = substr(decoct(@fileperms($dataDirectory)), -3);
+ if ($perms[2] !== '0') {
+ $l = \OC::$server->getL10N('lib');
+ return [[
+ 'error' => $l->t('Your data directory is readable by other people.'),
+ 'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other people.'),
+ ]];
+ }
+ }
+ return [];
+ }
+
+ /**
+ * Check that the data directory exists and is valid by
+ * checking the existence of the ".ncdata" file.
+ *
+ * @param string $dataDirectory data directory path
+ * @return array errors found
+ * @internal
+ */
+ public static function checkDataDirectoryValidity($dataDirectory) {
+ $l = \OC::$server->getL10N('lib');
+ $errors = [];
+ if ($dataDirectory[0] !== '/') {
+ $errors[] = [
+ 'error' => $l->t('Your data directory must be an absolute path.'),
+ 'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
+ ];
+ }
+
+ if (!file_exists($dataDirectory . '/.ncdata')) {
+ $errors[] = [
+ 'error' => $l->t('Your data directory is invalid.'),
+ 'hint' => $l->t('Ensure there is a file called "%1$s" in the root of the data directory. It should have the content: "%2$s"', ['.ncdata', '# Nextcloud data directory']),
+ ];
+ }
+ return $errors;
+ }
+
+ /**
+ * Check if the user is logged in, redirects to home if not. With
+ * redirect URL parameter to the request URI.
+ *
+ * @deprecated 32.0.0
+ */
+ public static function checkLoggedIn(): void {
+ // Check if we are a user
+ if (!\OC::$server->getUserSession()->isLoggedIn()) {
+ header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
+ 'core.login.showLoginForm',
+ [
+ 'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
+ ]
+ )
+ );
+ exit();
+ }
+ // Redirect to 2FA challenge selection if 2FA challenge was not solved yet
+ if (\OC::$server->get(TwoFactorAuthManager::class)->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
+ header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
+ exit();
+ }
+ }
+
+ /**
+ * Check if the user is a admin, redirects to home if not
+ *
+ * @deprecated 32.0.0
+ */
+ public static function checkAdminUser(): void {
+ self::checkLoggedIn();
+ if (!OC_User::isAdminUser(OC_User::getUser())) {
+ header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
+ exit();
+ }
+ }
+
+ /**
+ * Returns the URL of the default page
+ * based on the system configuration and
+ * the apps visible for the current user
+ *
+ * @return string URL
+ * @deprecated 32.0.0 use IURLGenerator's linkToDefaultPageUrl directly
+ */
+ public static function getDefaultPageUrl() {
+ /** @var IURLGenerator $urlGenerator */
+ $urlGenerator = \OC::$server->get(IURLGenerator::class);
+ return $urlGenerator->linkToDefaultPageUrl();
+ }
+
+ /**
+ * Redirect to the user default page
+ *
+ * @deprecated 32.0.0
+ */
+ public static function redirectToDefaultPage(): void {
+ $location = self::getDefaultPageUrl();
+ header('Location: ' . $location);
+ exit();
+ }
+
+ /**
+ * get an id unique for this instance
+ *
+ * @return string
+ */
+ public static function getInstanceId(): string {
+ $id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
+ if (is_null($id)) {
+ // We need to guarantee at least one letter in instanceid so it can be used as the session_name
+ $id = 'oc' . \OC::$server->get(ISecureRandom::class)->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS);
+ \OC::$server->getSystemConfig()->setValue('instanceid', $id);
+ }
+ return $id;
+ }
+
+ /**
+ * Public function to sanitize HTML
+ *
+ * This function is used to sanitize HTML and should be applied on any
+ * string or array of strings before displaying it on a web page.
+ *
+ * @param string|string[] $value
+ * @return ($value is array ? string[] : string)
+ * @deprecated 32.0.0 use \OCP\Util::sanitizeHTML instead
+ */
+ public static function sanitizeHTML($value) {
+ if (is_array($value)) {
+ $value = array_map(function ($value) {
+ return self::sanitizeHTML($value);
+ }, $value);
+ } else {
+ // Specify encoding for PHP<5.4
+ $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
+ }
+ return $value;
+ }
+
+ /**
+ * Public function to encode url parameters
+ *
+ * This function is used to encode path to file before output.
+ * Encoding is done according to RFC 3986 with one exception:
+ * Character '/' is preserved as is.
+ *
+ * @param string $component part of URI to encode
+ * @return string
+ * @deprecated 32.0.0 use \OCP\Util::encodePath instead
+ */
+ public static function encodePath($component) {
+ $encoded = rawurlencode($component);
+ $encoded = str_replace('%2F', '/', $encoded);
+ return $encoded;
+ }
+
+ /**
+ * Check if current locale is non-UTF8
+ *
+ * @return bool
+ */
+ private static function isNonUTF8Locale() {
+ if (function_exists('escapeshellcmd')) {
+ return escapeshellcmd('§') === '';
+ } elseif (function_exists('escapeshellarg')) {
+ return escapeshellarg('§') === '\'\'';
+ } else {
+ return preg_match('/utf-?8/i', setlocale(LC_CTYPE, 0)) === 0;
+ }
+ }
+
+ /**
+ * Check if the setlocale call does not work. This can happen if the right
+ * local packages are not available on the server.
+ *
+ * @internal
+ */
+ public static function isSetLocaleWorking(): bool {
+ if (self::isNonUTF8Locale()) {
+ // Borrowed from \Patchwork\Utf8\Bootup::initLocale
+ setlocale(LC_ALL, 'C.UTF-8', 'C');
+ setlocale(LC_CTYPE, 'en_US.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8', 'de_DE.UTF-8', 'ru_RU.UTF-8', 'pt_BR.UTF-8', 'it_IT.UTF-8', 'ja_JP.UTF-8', 'zh_CN.UTF-8', '0');
+
+ // Check again
+ if (self::isNonUTF8Locale()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if it's possible to get the inline annotations
+ *
+ * @internal
+ */
+ public static function isAnnotationsWorking(): bool {
+ if (PHP_VERSION_ID >= 80300) {
+ /** @psalm-suppress UndefinedMethod */
+ $reflection = \ReflectionMethod::createFromMethodName(__METHOD__);
+ } else {
+ $reflection = new \ReflectionMethod(__METHOD__);
+ }
+ $docs = $reflection->getDocComment();
+
+ return (is_string($docs) && strlen($docs) > 50);
+ }
+
+ /**
+ * Check if the PHP module fileinfo is loaded.
+ *
+ * @internal
+ */
+ public static function fileInfoLoaded(): bool {
+ return function_exists('finfo_open');
+ }
+
+ /**
+ * clear all levels of output buffering
+ *
+ * @return void
+ */
+ public static function obEnd() {
+ while (ob_get_level()) {
+ ob_end_clean();
+ }
+ }
+
+ /**
+ * Checks whether the server is running on Mac OS X
+ *
+ * @return bool true if running on Mac OS X, false otherwise
+ */
+ public static function runningOnMac() {
+ return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
+ }
+
+ /**
+ * Handles the case that there may not be a theme, then check if a "default"
+ * theme exists and take that one
+ *
+ * @return string the theme
+ */
+ public static function getTheme() {
+ $theme = \OC::$server->getSystemConfig()->getValue('theme', '');
+
+ if ($theme === '') {
+ if (is_dir(OC::$SERVERROOT . '/themes/default')) {
+ $theme = 'default';
+ }
+ }
+
+ return $theme;
+ }
+
+ /**
+ * Normalize a unicode string
+ *
+ * @param string $value a not normalized string
+ * @return string The normalized string or the input if the normalization failed
+ */
+ public static function normalizeUnicode(string $value): string {
+ if (Normalizer::isNormalized($value)) {
+ return $value;
+ }
+
+ $normalizedValue = Normalizer::normalize($value);
+ if ($normalizedValue === false) {
+ \OCP\Server::get(LoggerInterface::class)->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
+ return $value;
+ }
+
+ return $normalizedValue;
+ }
+
+ /**
+ * Check whether the instance needs to perform an upgrade,
+ * either when the core version is higher or any app requires
+ * an upgrade.
+ *
+ * @param \OC\SystemConfig $config
+ * @return bool whether the core or any app needs an upgrade
+ * @throws \OCP\HintException When the upgrade from the given version is not allowed
+ * @deprecated 32.0.0 Use \OCP\Util::needUpgrade instead
+ */
+ public static function needUpgrade(\OC\SystemConfig $config) {
+ if ($config->getValue('installed', false)) {
+ $installedVersion = $config->getValue('version', '0.0.0');
+ $currentVersion = implode('.', \OCP\Util::getVersion());
+ $versionDiff = version_compare($currentVersion, $installedVersion);
+ if ($versionDiff > 0) {
+ return true;
+ } elseif ($config->getValue('debug', false) && $versionDiff < 0) {
+ // downgrade with debug
+ $installedMajor = explode('.', $installedVersion);
+ $installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
+ $currentMajor = explode('.', $currentVersion);
+ $currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
+ if ($installedMajor === $currentMajor) {
+ // Same major, allow downgrade for developers
+ return true;
+ } else {
+ // downgrade attempt, throw exception
+ throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
+ }
+ } elseif ($versionDiff < 0) {
+ // downgrade attempt, throw exception
+ throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
+ }
+
+ // also check for upgrades for apps (independently from the user)
+ $apps = \OC_App::getEnabledApps(false, true);
+ $shouldUpgrade = false;
+ foreach ($apps as $app) {
+ if (\OC_App::shouldUpgrade($app)) {
+ $shouldUpgrade = true;
+ break;
+ }
+ }
+ return $shouldUpgrade;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/lib/private/legacy/l10n.php b/lib/private/legacy/l10n.php
deleted file mode 100644
index 3530bb4aad1..00000000000
--- a/lib/private/legacy/l10n.php
+++ /dev/null
@@ -1,342 +0,0 @@
-<?php
-/**
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Jakob Sack <mail@jakobsack.de>
- * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
- * @author Joas Schilling <nickvergessen@owncloud.com>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Thomas Tanghus <thomas@tanghus.net>
- *
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-/**
- * This class is for i18n and l10n
- * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->get() instead
- */
-class OC_L10N implements \OCP\IL10N {
- /**
- * cache
- */
- protected static $cache = array();
- protected static $availableLanguages = array();
-
- /**
- * The best language
- */
- protected static $language = '';
-
- /**
- * App of this object
- */
- protected $app;
-
- /**
- * Language of this object
- */
- protected $lang;
-
- /**
- * Translations
- */
- private $translations = array();
-
- /**
- * Plural forms (string)
- */
- private $pluralFormString = 'nplurals=2; plural=(n != 1);';
-
- /**
- * Plural forms (function)
- */
- private $pluralFormFunction = null;
-
- /**
- * The constructor
- * @param string $app app requesting l10n
- * @param string $lang default: null Language
- *
- * If language is not set, the constructor tries to find the right
- * language.
- * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->get() instead
- */
- public function __construct($app, $lang = null) {
- $app = \OC_App::cleanAppId($app);
- $this->app = $app;
-
- if ($lang !== null) {
- $lang = str_replace(array('\0', '/', '\\', '..'), '', $lang);
- }
-
- // Find the right language
- if ($app !== 'test' && !\OC::$server->getL10NFactory()->languageExists($app, $lang)) {
- $lang = \OC::$server->getL10NFactory()->findLanguage($app);
- }
-
- $this->lang = $lang;
- }
-
- /**
- * @param $transFile
- * @return bool
- */
- public function load($transFile) {
- $this->app = true;
-
- $json = json_decode(file_get_contents($transFile), true);
- if (!is_array($json)) {
- $jsonError = json_last_error();
- \OC::$server->getLogger()->warning("Failed to load $transFile - json error code: $jsonError", ['app' => 'l10n']);
- return false;
- }
-
- $this->pluralFormString = $json['pluralForm'];
- $translations = $json['translations'];
-
- $this->translations = array_merge($this->translations, $translations);
-
- return true;
- }
-
- protected function init() {
- if ($this->app === true) {
- return;
- }
- $app = $this->app;
- $lang = $this->lang;
- $this->app = true;
-
- /** @var \OC\L10N\Factory $factory */
- $factory = \OC::$server->getL10NFactory();
- $languageFiles = $factory->getL10nFilesForApp($app, $lang);
-
- $this->translations = [];
- foreach ($languageFiles as $languageFile) {
- $this->load($languageFile);
- }
- }
-
- /**
- * Translating
- * @param string $text The text we need a translation for
- * @param array $parameters default:array() Parameters for sprintf
- * @return \OC_L10N_String Translation or the same text
- *
- * Returns the translation. If no translation is found, $text will be
- * returned.
- */
- public function t($text, $parameters = array()) {
- return new OC_L10N_String($this, $text, $parameters);
- }
-
- /**
- * Translating
- * @param string $text_singular the string to translate for exactly one object
- * @param string $text_plural the string to translate for n objects
- * @param integer $count Number of objects
- * @param array $parameters default:array() Parameters for sprintf
- * @return \OC_L10N_String Translation or the same text
- *
- * Returns the translation. If no translation is found, $text will be
- * returned. %n will be replaced with the number of objects.
- *
- * The correct plural is determined by the plural_forms-function
- * provided by the po file.
- *
- */
- public function n($text_singular, $text_plural, $count, $parameters = array()) {
- $this->init();
- $identifier = "_${text_singular}_::_${text_plural}_";
- if( array_key_exists($identifier, $this->translations)) {
- return new OC_L10N_String( $this, $identifier, $parameters, $count );
- }else{
- if($count === 1) {
- return new OC_L10N_String($this, $text_singular, $parameters, $count);
- }else{
- return new OC_L10N_String($this, $text_plural, $parameters, $count);
- }
- }
- }
-
- /**
- * getTranslations
- * @return array Fetch all translations
- *
- * Returns an associative array with all translations
- */
- public function getTranslations() {
- $this->init();
- return $this->translations;
- }
-
- /**
- * getPluralFormFunction
- * @return string the plural form function
- *
- * returned function accepts the argument $n
- */
- public function getPluralFormFunction() {
- $this->init();
- if (is_null($this->pluralFormFunction)) {
- $this->pluralFormFunction = \OC::$server->getL10NFactory()->createPluralFunction($this->pluralFormString);
- }
- return $this->pluralFormFunction;
- }
-
- /**
- * Localization
- * @param string $type Type of localization
- * @param array|int|string $data parameters for this localization
- * @param array $options
- * @return string|false
- *
- * Returns the localized data.
- *
- * Implemented types:
- * - date
- * - Creates a date
- * - params: timestamp (int/string)
- * - datetime
- * - Creates date and time
- * - params: timestamp (int/string)
- * - time
- * - Creates a time
- * - params: timestamp (int/string)
- * - firstday: Returns the first day of the week (0 sunday - 6 saturday)
- * - jsdate: Returns the short JS date format
- */
- public function l($type, $data, $options = array()) {
- if ($type === 'firstday') {
- return $this->getFirstWeekDay();
- }
- if ($type === 'jsdate') {
- return $this->getDateFormat();
- }
-
- $this->init();
- $value = new DateTime();
- if($data instanceof DateTime) {
- $value = $data;
- } elseif(is_string($data) && !is_numeric($data)) {
- $data = strtotime($data);
- $value->setTimestamp($data);
- } else {
- $value->setTimestamp($data);
- }
-
- // Use the language of the instance
- $locale = $this->transformToCLDRLocale($this->getLanguageCode());
-
- $options = array_merge(array('width' => 'long'), $options);
- $width = $options['width'];
- switch($type) {
- case 'date':
- return Punic\Calendar::formatDate($value, $width, $locale);
- case 'datetime':
- return Punic\Calendar::formatDatetime($value, $width, $locale);
- case 'time':
- return Punic\Calendar::formatTime($value, $width, $locale);
- default:
- return false;
- }
- }
-
- /**
- * The code (en, de, ...) of the language that is used for this OC_L10N object
- *
- * @return string language
- */
- public function getLanguageCode() {
- return $this->lang;
- }
-
- /**
- * @return string
- * @throws \Punic\Exception\ValueNotInList
- * @deprecated 9.0.0 Use $this->l('jsdate', null) instead
- */
- public function getDateFormat() {
- $locale = $this->transformToCLDRLocale($this->getLanguageCode());
- return Punic\Calendar::getDateFormat('short', $locale);
- }
-
- /**
- * @return int
- * @deprecated 9.0.0 Use $this->l('firstday', null) instead
- */
- public function getFirstWeekDay() {
- $locale = $this->transformToCLDRLocale($this->getLanguageCode());
- return Punic\Calendar::getFirstWeekday($locale);
- }
-
- /**
- * @param string $locale
- * @return string
- */
- private function transformToCLDRLocale($locale) {
- if ($locale === 'sr@latin') {
- return 'sr_latn';
- }
-
- return $locale;
- }
-
- /**
- * find the best language
- * @param string $app
- * @return string language
- *
- * If nothing works it returns 'en'
- * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->findLanguage() instead
- */
- public static function findLanguage($app = null) {
- return \OC::$server->getL10NFactory()->findLanguage($app);
- }
-
- /**
- * @return string
- * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->setLanguageFromRequest() instead
- */
- public static function setLanguageFromRequest() {
- return \OC::$server->getL10NFactory()->setLanguageFromRequest();
- }
-
- /**
- * find all available languages for an app
- * @param string $app App that needs to be translated
- * @return array an array of available languages
- * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->findAvailableLanguages() instead
- */
- public static function findAvailableLanguages($app=null) {
- return \OC::$server->getL10NFactory()->findAvailableLanguages($app);
- }
-
- /**
- * @param string $app
- * @param string $lang
- * @return bool
- * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->languageExists() instead
- */
- public static function languageExists($app, $lang) {
- return \OC::$server->getL10NFactory()->languageExists($app, $lang);
- }
-}