diff options
Diffstat (limited to 'lib')
39 files changed, 657 insertions, 322 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 9243f0c6edb..60814aa4b9a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1027,6 +1027,7 @@ return array( 'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php', 'OC\\DB\\AdapterPgSql' => $baseDir . '/lib/private/DB/AdapterPgSql.php', 'OC\\DB\\AdapterSqlite' => $baseDir . '/lib/private/DB/AdapterSqlite.php', + 'OC\\DB\\BacktraceDebugStack' => $baseDir . '/lib/private/DB/BacktraceDebugStack.php', 'OC\\DB\\Connection' => $baseDir . '/lib/private/DB/Connection.php', 'OC\\DB\\ConnectionAdapter' => $baseDir . '/lib/private/DB/ConnectionAdapter.php', 'OC\\DB\\ConnectionFactory' => $baseDir . '/lib/private/DB/ConnectionFactory.php', @@ -1210,6 +1211,7 @@ return array( 'OC\\Files\\Type\\Detection' => $baseDir . '/lib/private/Files/Type/Detection.php', 'OC\\Files\\Type\\Loader' => $baseDir . '/lib/private/Files/Type/Loader.php', 'OC\\Files\\Type\\TemplateManager' => $baseDir . '/lib/private/Files/Type/TemplateManager.php', + 'OC\\Files\\Utils\\PathHelper' => $baseDir . '/lib/private/Files/Utils/PathHelper.php', 'OC\\Files\\Utils\\Scanner' => $baseDir . '/lib/private/Files/Utils/Scanner.php', 'OC\\Files\\View' => $baseDir . '/lib/private/Files/View.php', 'OC\\ForbiddenException' => $baseDir . '/lib/private/ForbiddenException.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 2c59850a4f0..47dbd7e9ef5 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1056,6 +1056,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php', 'OC\\DB\\AdapterPgSql' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterPgSql.php', 'OC\\DB\\AdapterSqlite' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterSqlite.php', + 'OC\\DB\\BacktraceDebugStack' => __DIR__ . '/../../..' . '/lib/private/DB/BacktraceDebugStack.php', 'OC\\DB\\Connection' => __DIR__ . '/../../..' . '/lib/private/DB/Connection.php', 'OC\\DB\\ConnectionAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionAdapter.php', 'OC\\DB\\ConnectionFactory' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionFactory.php', @@ -1239,6 +1240,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Files\\Type\\Detection' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Detection.php', 'OC\\Files\\Type\\Loader' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Loader.php', 'OC\\Files\\Type\\TemplateManager' => __DIR__ . '/../../..' . '/lib/private/Files/Type/TemplateManager.php', + 'OC\\Files\\Utils\\PathHelper' => __DIR__ . '/../../..' . '/lib/private/Files/Utils/PathHelper.php', 'OC\\Files\\Utils\\Scanner' => __DIR__ . '/../../..' . '/lib/private/Files/Utils/Scanner.php', 'OC\\Files\\View' => __DIR__ . '/../../..' . '/lib/private/Files/View.php', 'OC\\ForbiddenException' => __DIR__ . '/../../..' . '/lib/private/ForbiddenException.php', diff --git a/lib/l10n/es.js b/lib/l10n/es.js index 1bdb387aba3..be4367a2335 100644 --- a/lib/l10n/es.js +++ b/lib/l10n/es.js @@ -2,6 +2,7 @@ OC.L10N.register( "lib", { "Cannot write into \"config\" directory!" : "No se puede escribir en la carpeta \"config\"!", + "This can usually be fixed by giving the web server write access to the config directory." : "Normalmente esto se puede arreglar dando al servidor web acceso de escritura a la carpeta config.", "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Pero, si prefieres mantener el archivo config.php como solo de lectura, establece la opción \"config_is_read_only\" a true en él.", "See %s" : "Ver %s", "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la app %1$s no se han reemplazado correctamente. Asegúrate de que es una versión compatible con el servidor.", @@ -211,19 +212,36 @@ OC.L10N.register( "Authentication error" : "Error de autenticación", "Token expired. Please reload page." : "Token caducado. Por favor, recarge la página.", "No database drivers (sqlite, mysql, or postgresql) installed." : "No están instalados los drivers de BBDD (sqlite, mysql, o postgresql)", + "Cannot write into \"config\" directory." : "No se puede escribir en la carpeta «config».", + "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Normalmente esteo se puede arreglar dando al servidor web acceso de escritura a la carpeta config. Véase %s", "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, si prefieres mantener el archivo config.php como de solo lectura, marca la opción \"config_is_read_only\" a 'true' en él. Ver %s", + "Cannot write into \"apps\" directory." : "No se puede escribir en la carpeta «apps».", + "This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file." : "Normalmente esto se puede arreglar dando al servidor web acceso de escritura a la carpeta de apps o desactivando la App Store en el archivo de configuración.", "Cannot create \"data\" directory." : "No se puede crear la carpeta \"data\"", + "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Normalmente esto se puede arreglar dando al servidor web acceso de escritura a la carpeta raÃz. Véase %s", + "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Los permisos normalmente se pueden arreglar dando al servidor web acceso de escritura a la carpeta raÃz. Véase %s", + "Your data directory is not writable." : "No se puede escribir en tu carpeta de datos.", + "Setting locale to %s failed." : "Fallo al configurar el idioma a %s.", + "Please install one of these locales on your system and restart your web server." : "Por favor, instala uno de estos idiomas en tu sistema y reinicia tu servidor web.", "PHP module %s not installed." : "El módulo PHP %s no está instalado.", "Please ask your server administrator to install the module." : "Consulte al administrador de su servidor para instalar el módulo.", "PHP setting \"%s\" is not set to \"%s\"." : "La opción PHP \"%s\" no es \"%s\".", "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta configuración en php.ini hará que Nextcloud funcione de nuevo", + "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> está establecida como <code>%s</code> en lugar del valor esperado: <code>0</code>.", + "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Para arreglar este problema, establece <code>mbstring.func_overload</code> en<code>0</code> en tu php.ini.", "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 es requerido en esta o en versiones superiores. Ahora mismo tienes instalada %s.", "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este error, actualiza la versión de tu libxml2 y reinicia el servidor web.", "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado para eliminar bloques de documentos en lÃnea. Esto hará que varias aplicaciones principales estén inaccesibles.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Probablemente esto venga a causa de la caché o un acelerador, tales como Zend OPcache o eAccelerator.", "PHP modules have been installed, but they are still listed as missing?" : "Los módulos PHP se han instalado, pero aparecen listados como si faltaran", "Please ask your server administrator to restart the web server." : "Consulte al administrador de su servidor para reiniciar el servidor web.", + "PostgreSQL >= 9 required." : "PostgreSQL >= 9 requerido.", + "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos.", + "Your data directory is readable by other users." : "Tu carpeta de datos puede ser leÃdo por otros usuarios.", "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, cambia los permisos a 0770 para que el directorio no se pueda mostrar a otros usuarios.", + "Your data directory must be an absolute path." : "Tu carpeta de datos debe ser una ruta absoluta.", + "Check the value of \"datadirectory\" in your configuration." : "Comprueba el valor de «datadirectory» en tu configuración.", + "Your data directory is invalid." : "Tu carpeta de datos es inválida.", "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegúrate de que existe un archivo llamado \".ocdata\" en la raÃz del directorio de datos.", "Action \"%s\" not supported or implemented." : "La acción \"%s\" no está soportada o implementada.", "Authentication failed, wrong token or provider ID given" : "La autentificación ha fallado. Se ha dado un token o una ID de proveedor erróneos.", diff --git a/lib/l10n/es.json b/lib/l10n/es.json index 8f3e4a15f4f..81d1b451606 100644 --- a/lib/l10n/es.json +++ b/lib/l10n/es.json @@ -1,5 +1,6 @@ { "translations": { "Cannot write into \"config\" directory!" : "No se puede escribir en la carpeta \"config\"!", + "This can usually be fixed by giving the web server write access to the config directory." : "Normalmente esto se puede arreglar dando al servidor web acceso de escritura a la carpeta config.", "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Pero, si prefieres mantener el archivo config.php como solo de lectura, establece la opción \"config_is_read_only\" a true en él.", "See %s" : "Ver %s", "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la app %1$s no se han reemplazado correctamente. Asegúrate de que es una versión compatible con el servidor.", @@ -209,19 +210,36 @@ "Authentication error" : "Error de autenticación", "Token expired. Please reload page." : "Token caducado. Por favor, recarge la página.", "No database drivers (sqlite, mysql, or postgresql) installed." : "No están instalados los drivers de BBDD (sqlite, mysql, o postgresql)", + "Cannot write into \"config\" directory." : "No se puede escribir en la carpeta «config».", + "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Normalmente esteo se puede arreglar dando al servidor web acceso de escritura a la carpeta config. Véase %s", "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, si prefieres mantener el archivo config.php como de solo lectura, marca la opción \"config_is_read_only\" a 'true' en él. Ver %s", + "Cannot write into \"apps\" directory." : "No se puede escribir en la carpeta «apps».", + "This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file." : "Normalmente esto se puede arreglar dando al servidor web acceso de escritura a la carpeta de apps o desactivando la App Store en el archivo de configuración.", "Cannot create \"data\" directory." : "No se puede crear la carpeta \"data\"", + "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Normalmente esto se puede arreglar dando al servidor web acceso de escritura a la carpeta raÃz. Véase %s", + "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Los permisos normalmente se pueden arreglar dando al servidor web acceso de escritura a la carpeta raÃz. Véase %s", + "Your data directory is not writable." : "No se puede escribir en tu carpeta de datos.", + "Setting locale to %s failed." : "Fallo al configurar el idioma a %s.", + "Please install one of these locales on your system and restart your web server." : "Por favor, instala uno de estos idiomas en tu sistema y reinicia tu servidor web.", "PHP module %s not installed." : "El módulo PHP %s no está instalado.", "Please ask your server administrator to install the module." : "Consulte al administrador de su servidor para instalar el módulo.", "PHP setting \"%s\" is not set to \"%s\"." : "La opción PHP \"%s\" no es \"%s\".", "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta configuración en php.ini hará que Nextcloud funcione de nuevo", + "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> está establecida como <code>%s</code> en lugar del valor esperado: <code>0</code>.", + "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Para arreglar este problema, establece <code>mbstring.func_overload</code> en<code>0</code> en tu php.ini.", "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 es requerido en esta o en versiones superiores. Ahora mismo tienes instalada %s.", "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este error, actualiza la versión de tu libxml2 y reinicia el servidor web.", "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado para eliminar bloques de documentos en lÃnea. Esto hará que varias aplicaciones principales estén inaccesibles.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Probablemente esto venga a causa de la caché o un acelerador, tales como Zend OPcache o eAccelerator.", "PHP modules have been installed, but they are still listed as missing?" : "Los módulos PHP se han instalado, pero aparecen listados como si faltaran", "Please ask your server administrator to restart the web server." : "Consulte al administrador de su servidor para reiniciar el servidor web.", + "PostgreSQL >= 9 required." : "PostgreSQL >= 9 requerido.", + "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos.", + "Your data directory is readable by other users." : "Tu carpeta de datos puede ser leÃdo por otros usuarios.", "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Por favor, cambia los permisos a 0770 para que el directorio no se pueda mostrar a otros usuarios.", + "Your data directory must be an absolute path." : "Tu carpeta de datos debe ser una ruta absoluta.", + "Check the value of \"datadirectory\" in your configuration." : "Comprueba el valor de «datadirectory» en tu configuración.", + "Your data directory is invalid." : "Tu carpeta de datos es inválida.", "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asegúrate de que existe un archivo llamado \".ocdata\" en la raÃz del directorio de datos.", "Action \"%s\" not supported or implemented." : "La acción \"%s\" no está soportada o implementada.", "Authentication failed, wrong token or provider ID given" : "La autentificación ha fallado. Se ha dado un token o una ID de proveedor erróneos.", diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 127adc9ef38..5792ba1dc5d 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -41,6 +41,7 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; use OC\Profile\TProfileHelper; +use OC\Cache\CappedMemoryCache; use OCA\Settings\BackgroundJobs\VerifyUserData; use OCP\Accounts\IAccount; use OCP\Accounts\IAccountManager; @@ -116,6 +117,7 @@ class AccountManager implements IAccountManager { private $crypto; /** @var IFactory */ private $l10nfactory; + private CappedMemoryCache $internalCache; public function __construct( IDBConnection $connection, @@ -142,6 +144,7 @@ class AccountManager implements IAccountManager { $this->crypto = $crypto; // DIing IL10N results in a dependency loop $this->l10nfactory = $factory; + $this->internalCache = new CappedMemoryCache(); } /** @@ -763,7 +766,12 @@ class AccountManager implements IAccountManager { } public function getAccount(IUser $user): IAccount { - return $this->parseAccountData($user, $this->getUser($user)); + if ($this->internalCache->hasKey($user->getUID())) { + return $this->internalCache->get($user->getUID()); + } + $account = $this->parseAccountData($user, $this->getUser($user)); + $this->internalCache->set($user->getUID(), $account); + return $account; } public function updateAccount(IAccount $account): void { @@ -813,5 +821,6 @@ class AccountManager implements IAccountManager { } $this->updateUser($account->getUser(), $data, true); + $this->internalCache->set($account->getUser()->getUID(), $account); } } diff --git a/lib/private/Console/TimestampFormatter.php b/lib/private/Console/TimestampFormatter.php index 59e480b39e8..c25ed578805 100644 --- a/lib/private/Console/TimestampFormatter.php +++ b/lib/private/Console/TimestampFormatter.php @@ -99,6 +99,11 @@ class TimestampFormatter implements OutputFormatterInterface { * log timezone and dateformat, e.g. "2015-06-23T17:24:37+02:00" */ public function format($message) { + if (!$this->formatter->isDecorated()) { + // Don't add anything to the output when we shouldn't + return $this->formatter->format($message); + } + $timeZone = $this->config->getSystemValue('logtimezone', 'UTC'); $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null; diff --git a/lib/private/DB/BacktraceDebugStack.php b/lib/private/DB/BacktraceDebugStack.php new file mode 100644 index 00000000000..be37e5a35da --- /dev/null +++ b/lib/private/DB/BacktraceDebugStack.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Logging\DebugStack; + +class BacktraceDebugStack extends DebugStack { + public function startQuery($sql, ?array $params = null, ?array $types = null) { + parent::startQuery($sql, $params, $types); + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + $this->queries[$this->currentQuery]['backtrace'] = $backtrace; + } +} diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 2e38b1ddf5e..22c2bbbb793 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -42,7 +42,6 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\ConstraintViolationException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; -use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQL94Platform; @@ -113,7 +112,7 @@ class Connection extends \Doctrine\DBAL\Connection { if ($profiler->isEnabled()) { $this->dbDataCollector = new DbDataCollector($this); $profiler->add($this->dbDataCollector); - $debugStack = new DebugStack(); + $debugStack = new BacktraceDebugStack(); $this->dbDataCollector->setDebugStack($debugStack); $this->_config->setSQLLogger($debugStack); } diff --git a/lib/private/DB/DbDataCollector.php b/lib/private/DB/DbDataCollector.php index d708955b10e..60e3dbe797d 100644 --- a/lib/private/DB/DbDataCollector.php +++ b/lib/private/DB/DbDataCollector.php @@ -25,14 +25,13 @@ declare(strict_types = 1); namespace OC\DB; -use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use OC\AppFramework\Http\Request; use OCP\AppFramework\Http\Response; class DbDataCollector extends \OCP\DataCollector\AbstractDataCollector { - protected ?DebugStack $debugStack = null; + protected ?BacktraceDebugStack $debugStack = null; private Connection $connection; /** @@ -42,7 +41,7 @@ class DbDataCollector extends \OCP\DataCollector\AbstractDataCollector { $this->connection = $connection; } - public function setDebugStack(DebugStack $debugStack, $name = 'default'): void { + public function setDebugStack(BacktraceDebugStack $debugStack, $name = 'default'): void { $this->debugStack = $debugStack; } diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 046e3a4924b..92f45dfdbe8 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -27,7 +27,6 @@ */ namespace OC\DB; -use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Doctrine\DBAL\Schema\Index; @@ -424,10 +423,10 @@ class MigrationService { foreach ($toBeExecuted as $version) { try { $this->executeStep($version, $schemaOnly); - } catch (DriverException $e) { + } catch (\Exception $e) { // The exception itself does not contain the name of the migration, // so we wrap it here, to make debugging easier. - throw new \Exception('Database error when running migration ' . $to . ' for app ' . $this->getApp(), 0, $e); + throw new \Exception('Database error when running migration ' . $version . ' for app ' . $this->getApp() . PHP_EOL. $e->getMessage(), 0, $e); } } } @@ -560,9 +559,13 @@ class MigrationService { * - Primary key names must be set or the table name 23 chars or shorter * * Data constraints: + * - Tables need a primary key (Not specific to Oracle, but required for performant clustering support) * - Columns with "NotNull" can not have empty string as default value * - Columns with "NotNull" can not have number 0 as default value * - Columns with type "bool" (which is in fact integer of length 1) can not be "NotNull" as it can not store 0/false + * - Columns with type "string" can not be longer than 4.000 characters, use "text" instead + * + * @see https://github.com/nextcloud/documentation/blob/master/developer_manual/basics/storage/database.rst * * @param Schema $sourceSchema * @param Schema $targetSchema @@ -583,20 +586,30 @@ class MigrationService { } foreach ($table->getColumns() as $thing) { - if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && \strlen($thing->getName()) > 30) { - throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.'); - } + // If the table doesn't exist OR if the column doesn't exist in the table + if (!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) { + if (\strlen($thing->getName()) > 30) { + throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.'); + } - if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getDefault() === '' - && $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) { - throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.'); - } + if ($thing->getNotnull() && $thing->getDefault() === '' + && $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) { + throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.'); + } + + if ($thing->getNotnull() && $thing->getType()->getName() === Types::BOOLEAN) { + throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type Bool and also NotNull, so it can not store "false".'); + } - if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getType()->getName() === Types::BOOLEAN) { - throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type Bool and also NotNull, so it can not store "false".'); + $sourceColumn = null; + } else { + $sourceColumn = $sourceTable->getColumn($thing->getName()); } - if ($thing->getLength() > 4000 && $thing->getType()->getName() === Types::STRING) { + // If the column was just created OR the length changed OR the type changed + // we will NOT detect invalid length if the column is not modified + if (($sourceColumn === null || $sourceColumn->getLength() !== $thing->getLength() || $sourceColumn->getType()->getName() !== Types::STRING) + && $thing->getLength() > 4000 && $thing->getType()->getName() === Types::STRING) { throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type String, but exceeding the 4.000 length limit.'); } } diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 2de2c2f84d7..fb9e5500658 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -126,19 +126,9 @@ class Storage { * @param int $numericId * @return string|null either the storage id string or null if the numeric id is not known */ - public static function getStorageId($numericId) { - $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $query->select('id') - ->from('storages') - ->where($query->expr()->eq('numeric_id', $query->createNamedParameter($numericId))); - $result = $query->execute(); - $row = $result->fetch(); - $result->closeCursor(); - if ($row) { - return $row['id']; - } else { - return null; - } + public static function getStorageId(int $numericId): ?string { + $storage = self::getGlobalCache()->getStorageInfoByNumericId($numericId); + return $storage['id'] ?? null; } /** @@ -240,6 +230,7 @@ class Storage { ->from('mounts') ->where($query->expr()->eq('mount_id', $query->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); $storageIds = $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); + $storageIds = array_unique($storageIds); $query = $db->getQueryBuilder(); $query->delete('filecache') diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index 7162f8e4908..a898c435415 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -41,8 +41,10 @@ class StorageGlobal { /** @var IDBConnection */ private $connection; - /** @var array[] */ + /** @var array<string, array> */ private $cache = []; + /** @var array<int, array> */ + private $numericIdCache = []; public function __construct(IDBConnection $connection) { $this->connection = $connection; @@ -68,7 +70,7 @@ class StorageGlobal { * @param string $storageId * @return array|null */ - public function getStorageInfo($storageId) { + public function getStorageInfo(string $storageId): ?array { if (!isset($this->cache[$storageId])) { $builder = $this->connection->getQueryBuilder(); $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) @@ -81,9 +83,33 @@ class StorageGlobal { if ($row) { $this->cache[$storageId] = $row; + $this->numericIdCache[(int)$row['numeric_id']] = $row; } } - return isset($this->cache[$storageId]) ? $this->cache[$storageId] : null; + return $this->cache[$storageId] ?? null; + } + + /** + * @param int $numericId + * @return array|null + */ + public function getStorageInfoByNumericId(int $numericId): ?array { + if (!isset($this->numericIdCache[$numericId])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) + ->from('storages') + ->where($builder->expr()->eq('numeric_id', $builder->createNamedParameter($numericId))); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { + $this->numericIdCache[$numericId] = $row; + $this->cache[$row['id']] = $row; + } + } + return $this->numericIdCache[$numericId] ?? null; } public function clearCache() { diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index 2b0acf7d69d..334fce15d9e 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -97,10 +97,10 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { return $this->getUserMountsForProviders($user, $this->providers); } - public function getUserMountsForProviderClass(IUser $user, string $mountProviderClass): array { + public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array { $providers = array_filter( $this->providers, - fn (IMountProvider $mountProvider) => (get_class($mountProvider) === $mountProviderClass) + fn (IMountProvider $mountProvider) => (in_array(get_class($mountProvider), $mountProviderClasses)) ); return $this->getUserMountsForProviders($user, $providers); } diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 2f361fc051d..6389544184f 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -254,7 +254,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { */ public function getType() { if (!isset($this->data['type'])) { - $this->data['type'] = ($this->getMimetype() === 'httpd/unix-directory') ? self::TYPE_FOLDER : self::TYPE_FILE; + $this->data['type'] = ($this->getMimetype() === self::MIMETYPE_FOLDER) ? self::TYPE_FOLDER : self::TYPE_FILE; } return $this->data['type']; } diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index ecd97760f17..69285018d17 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -158,7 +158,6 @@ class Manager implements IMountManager { * @return IMountPoint[] */ public function findByStorageId(string $id): array { - \OC_Util::setupFS(); if (\strlen($id) > 64) { $id = md5($id); } @@ -204,4 +203,22 @@ class Manager implements IMountManager { public function getSetupManager(): SetupManager { return $this->setupManager; } + + /** + * Return all mounts in a path from a specific mount provider + * + * @param string $path + * @param string[] $mountProviders + * @return MountPoint[] + */ + public function getMountsByMountProvider(string $path, array $mountProviders) { + $this->getSetupManager()->setupForProvider($path, $mountProviders); + if (in_array('', $mountProviders)) { + return $this->mounts; + } else { + return array_filter($this->mounts, function ($mount) use ($mountProviders) { + return in_array($mount->getMountProvider(), $mountProviders); + }); + } + } } diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 400fd6bedcc..9c15f0edf41 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -36,6 +36,7 @@ use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; +use OC\Files\Utils\PathHelper; use OCP\Files\Cache\ICacheEntry; use OCP\Files\FileInfo; use OCP\Files\Mount\IMountPoint; @@ -76,17 +77,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @return string|null */ public function getRelativePath($path) { - if ($this->path === '' or $this->path === '/') { - return $this->normalizePath($path); - } - if ($path === $this->path) { - return '/'; - } elseif (strpos($path, $this->path . '/') !== 0) { - return null; - } else { - $path = substr($path, strlen($this->path)); - return $this->normalizePath($path); - } + return PathHelper::getRelativePath($this->getPath(), $path); } /** @@ -106,10 +97,10 @@ class Folder extends Node implements \OCP\Files\Folder { * @throws \OCP\Files\NotFoundException */ public function getDirectoryListing() { - $folderContent = $this->view->getDirectoryContent($this->path); + $folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo()); return array_map(function (FileInfo $info) { - if ($info->getMimetype() === 'httpd/unix-directory') { + if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) { return new Folder($this->root, $this->view, $info->getPath(), $info); } else { return new File($this->root, $this->view, $info->getPath(), $info); @@ -342,70 +333,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @return \OC\Files\Node\Node[] */ public function getById($id) { - $mountCache = $this->root->getUserMountCache(); - if (strpos($this->getPath(), '/', 1) > 0) { - [, $user] = explode('/', $this->getPath()); - } else { - $user = null; - } - $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user); - - // when a user has access trough the same storage trough multiple paths - // (such as an external storage that is both mounted for a user and shared to the user) - // the mount cache will only hold a single entry for the storage - // this can lead to issues as the different ways the user has access to a storage can have different permissions - // - // so instead of using the cached entries directly, we instead filter the current mounts by the rootid of the cache entry - - $mountRootIds = array_map(function ($mount) { - return $mount->getRootId(); - }, $mountsContainingFile); - $mountRootPaths = array_map(function ($mount) { - return $mount->getRootInternalPath(); - }, $mountsContainingFile); - $mountRoots = array_combine($mountRootIds, $mountRootPaths); - - $mounts = $this->root->getMountsIn($this->path); - $mounts[] = $this->root->getMount($this->path); - - $mountsContainingFile = array_filter($mounts, function ($mount) use ($mountRoots) { - return isset($mountRoots[$mount->getStorageRootId()]); - }); - - if (count($mountsContainingFile) === 0) { - if ($user === $this->getAppDataDirectoryName()) { - return $this->getByIdInRootMount((int)$id); - } - return []; - } - - $nodes = array_map(function (IMountPoint $mount) use ($id, $mountRoots) { - $rootInternalPath = $mountRoots[$mount->getStorageRootId()]; - $cacheEntry = $mount->getStorage()->getCache()->get((int)$id); - if (!$cacheEntry) { - return null; - } - - // cache jails will hide the "true" internal path - $internalPath = ltrim($rootInternalPath . '/' . $cacheEntry->getPath(), '/'); - $pathRelativeToMount = substr($internalPath, strlen($rootInternalPath)); - $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); - $absolutePath = rtrim($mount->getMountPoint() . $pathRelativeToMount, '/'); - return $this->root->createNode($absolutePath, new \OC\Files\FileInfo( - $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, - \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) - )); - }, $mountsContainingFile); - - $nodes = array_filter($nodes); - - $folders = array_filter($nodes, function (Node $node) { - return $this->getRelativePath($node->getPath()); - }); - usort($folders, function ($a, $b) { - return $b->getPath() <=> $a->getPath(); - }); - return $folders; + return $this->root->getByIdInPath((int)$id, $this->getPath()); } protected function getAppDataDirectoryName(): string { diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 45451e5c53c..7d5038e85a2 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -25,6 +25,7 @@ declare(strict_types=1); */ namespace OC\Files\Node; +use OC\Files\Utils\PathHelper; use OCP\Constants; /** @@ -382,13 +383,6 @@ class LazyFolder implements \OCP\Files\Folder { /** * @inheritDoc */ - public function getRelativePath($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ public function isSubNode($node) { return $this->__call(__FUNCTION__, func_get_args()); } @@ -518,4 +512,8 @@ class LazyFolder implements \OCP\Files\Folder { public function getUploadTime(): int { return $this->__call(__FUNCTION__, func_get_args()); } + + public function getRelativePath($path) { + return PathHelper::getRelativePath($this->getPath(), $path); + } } diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index c4d61f653e4..c01b9fdbb83 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -39,4 +39,8 @@ class LazyRoot extends LazyFolder implements IRootFolder { public function getUserFolder($userId) { return $this->__call(__FUNCTION__, func_get_args()); } + + public function getByIdInPath(int $id, string $path) { + return $this->__call(__FUNCTION__, func_get_args()); + } } diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php index 4c9e89ce233..d91759117c1 100644 --- a/lib/private/Files/Node/LazyUserFolder.php +++ b/lib/private/Files/Node/LazyUserFolder.php @@ -29,28 +29,38 @@ use OCP\Files\NotFoundException; use OCP\IUser; class LazyUserFolder extends LazyFolder { - private IRootFolder $rootFolder; + private IRootFolder $root; private IUser $user; + private string $path; public function __construct(IRootFolder $rootFolder, IUser $user) { - $this->rootFolder = $rootFolder; + $this->root = $rootFolder; $this->user = $user; + $this->path = '/' . $user->getUID() . '/files'; parent::__construct(function () use ($user) { try { - return $this->rootFolder->get('/' . $user->getUID() . '/files'); + return $this->root->get('/' . $user->getUID() . '/files'); } catch (NotFoundException $e) { - if (!$this->rootFolder->nodeExists('/' . $user->getUID())) { - $this->rootFolder->newFolder('/' . $user->getUID()); + if (!$this->root->nodeExists('/' . $user->getUID())) { + $this->root->newFolder('/' . $user->getUID()); } - return $this->rootFolder->newFolder('/' . $user->getUID() . '/files'); + return $this->root->newFolder('/' . $user->getUID() . '/files'); } }, [ - 'path' => '/' . $user->getUID() . '/files', + 'path' => $this->path, 'permissions' => Constants::PERMISSION_ALL, ]); } public function get($path) { - return $this->rootFolder->get('/' . $this->user->getUID() . '/files/' . rtrim($path, '/')); + return $this->root->get('/' . $this->user->getUID() . '/files/' . ltrim($path, '/')); + } + + /** + * @param int $id + * @return \OC\Files\Node\Node[] + */ + public function getById($id) { + return $this->root->getByIdInPath((int)$id, $this->getPath()); } } diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index b939bfce945..c8975154059 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -31,6 +31,7 @@ namespace OC\Files\Node; use OC\Files\Filesystem; use OC\Files\Mount\MoveableMount; +use OC\Files\Utils\PathHelper; use OCP\Files\FileInfo; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; @@ -153,12 +154,11 @@ class Node implements \OCP\Files\Node { } } - /** - * @return \OC\Files\Storage\Storage - * @throws \OCP\Files\NotFoundException - */ public function getStorage() { - [$storage,] = $this->view->resolvePath($this->path); + $storage = $this->getMountPoint()->getStorage(); + if (!$storage) { + throw new \Exception("No storage for node"); + } return $storage; } @@ -173,8 +173,7 @@ class Node implements \OCP\Files\Node { * @return string */ public function getInternalPath() { - [, $internalPath] = $this->view->resolvePath($this->path); - return $internalPath; + return $this->getFileInfo()->getInternalPath(); } /** @@ -298,23 +297,7 @@ class Node implements \OCP\Files\Node { * @return string */ protected function normalizePath($path) { - if ($path === '' or $path === '/') { - return '/'; - } - //no windows style slashes - $path = str_replace('\\', '/', $path); - //add leading slash - if ($path[0] !== '/') { - $path = '/' . $path; - } - //remove duplicate slashes - while (strpos($path, '//') !== false) { - $path = str_replace('//', '/', $path); - } - //remove trailing slash - $path = rtrim($path, '/'); - - return $path; + return PathHelper::normalizePath($path); } /** @@ -447,6 +430,15 @@ class Node implements \OCP\Files\Node { if (!$this->view->rename($this->path, $targetPath)) { throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); } + + $mountPoint = $this->getMountPoint(); + if ($mountPoint) { + // update the cached fileinfo with the new (internal) path + /** @var \OC\Files\FileInfo $oldFileInfo */ + $oldFileInfo = $this->getFileInfo(); + $this->fileInfo = new \OC\Files\FileInfo($targetPath, $oldFileInfo->getStorage(), $mountPoint->getInternalPath($targetPath), $oldFileInfo->getData(), $mountPoint, $oldFileInfo->getOwner()); + } + $targetNode = $this->root->get($targetPath); $this->sendHooks(['postRename'], [$this, $targetNode]); $this->sendHooks(['postWrite'], [$targetNode]); diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index a8b36ee7731..7592d4caf37 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -33,8 +33,10 @@ namespace OC\Files\Node; use OC\Cache\CappedMemoryCache; +use OC\Files\FileInfo; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; +use OC\Files\Utils\PathHelper; use OC\Files\View; use OC\Hooks\PublicEmitter; use OC\User\NoUserException; @@ -42,6 +44,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\IUser; @@ -214,7 +217,7 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @return \OC\Files\Node\Node + * @return Node * @throws \OCP\Files\NotPermittedException */ public function rename($targetPath) { @@ -227,7 +230,7 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @return \OC\Files\Node\Node + * @return Node * @throws \OCP\Files\NotPermittedException */ public function copy($targetPath) { @@ -402,4 +405,82 @@ class Root extends Folder implements IRootFolder { public function getUserMountCache() { return $this->userMountCache; } + + /** + * @param int $id + * @return Node[] + */ + public function getByIdInPath(int $id, string $path): array { + $mountCache = $this->getUserMountCache(); + if (strpos($path, '/', 1) > 0) { + [, $user] = explode('/', $path); + } else { + $user = null; + } + $mountsContainingFile = $mountCache->getMountsForFileId($id, $user); + + // when a user has access trough the same storage trough multiple paths + // (such as an external storage that is both mounted for a user and shared to the user) + // the mount cache will only hold a single entry for the storage + // this can lead to issues as the different ways the user has access to a storage can have different permissions + // + // so instead of using the cached entries directly, we instead filter the current mounts by the rootid of the cache entry + + $mountRootIds = array_map(function ($mount) { + return $mount->getRootId(); + }, $mountsContainingFile); + $mountRootPaths = array_map(function ($mount) { + return $mount->getRootInternalPath(); + }, $mountsContainingFile); + $mountProviders = array_unique(array_map(function ($mount) { + return $mount->getMountProvider(); + }, $mountsContainingFile)); + $mountRoots = array_combine($mountRootIds, $mountRootPaths); + + $mounts = $this->mountManager->getMountsByMountProvider($path, $mountProviders); + + $mountsContainingFile = array_filter($mounts, function ($mount) use ($mountRoots) { + return isset($mountRoots[$mount->getStorageRootId()]); + }); + + if (count($mountsContainingFile) === 0) { + if ($user === $this->getAppDataDirectoryName()) { + $folder = $this->get($path); + if ($folder instanceof Folder) { + return $folder->getByIdInRootMount($id); + } else { + throw new \Exception("getByIdInPath with non folder"); + } + } + return []; + } + + $nodes = array_map(function (IMountPoint $mount) use ($id, $mountRoots) { + $rootInternalPath = $mountRoots[$mount->getStorageRootId()]; + $cacheEntry = $mount->getStorage()->getCache()->get($id); + if (!$cacheEntry) { + return null; + } + + // cache jails will hide the "true" internal path + $internalPath = ltrim($rootInternalPath . '/' . $cacheEntry->getPath(), '/'); + $pathRelativeToMount = substr($internalPath, strlen($rootInternalPath)); + $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); + $absolutePath = rtrim($mount->getMountPoint() . $pathRelativeToMount, '/'); + return $this->createNode($absolutePath, new FileInfo( + $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, + \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) + )); + }, $mountsContainingFile); + + $nodes = array_filter($nodes); + + $folders = array_filter($nodes, function (Node $node) use ($path) { + return PathHelper::getRelativePath($path, $node->getPath()) !== null; + }); + usort($folders, function ($a, $b) { + return $b->getPath() <=> $a->getPath(); + }); + return $folders; + } } diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index da50983da32..d091b5c5e35 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -40,6 +40,7 @@ use OC_Util; use OCP\Constants; use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Config\IHomeMountProvider; use OCP\Files\Config\IMountProvider; use OCP\Files\Config\IUserMountCache; use OCP\Files\Events\InvalidateMountCacheEvent; @@ -263,6 +264,11 @@ class SetupManager { return strpos($mount->getMountPoint(), $userRoot) === 0; }); $this->userMountCache->registerMounts($user, $mounts); + + $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60); + if ($cacheDuration > 0) { + $this->cache->set($user->getUID(), true, $cacheDuration); + } } /** @@ -321,25 +327,32 @@ class SetupManager { } /** - * Set up the filesystem for the specified path + * Get the user to setup for a path or `null` if the root needs to be setup + * + * @param string $path + * @return IUser|null */ - public function setupForPath(string $path, bool $includeChildren = false): void { + private function getUserForPath(string $path) { if (substr_count($path, '/') < 2) { if ($user = $this->userSession->getUser()) { - $this->setupForUser($user); + return $user; } else { - $this->setupRoot(); + return null; } - return; } elseif (strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0 || strpos($path, '/files_external/') === 0) { - $this->setupRoot(); - return; + return null; } else { [, $userId] = explode('/', $path); } - $user = $this->userManager->get($userId); + return $this->userManager->get($userId); + } + /** + * Set up the filesystem for the specified path + */ + public function setupForPath(string $path, bool $includeChildren = false): void { + $user = $this->getUserForPath($path); if (!$user) { $this->setupRoot(); return; @@ -349,17 +362,14 @@ class SetupManager { return; } - // we perform a "cached" setup only after having done the full setup recently - // this is also used to trigger a full setup after handling events that are likely - // to change the available mounts - $cachedSetup = $this->cache->get($user->getUID()); - if (!$cachedSetup) { + if ($this->fullSetupRequired($user)) { $this->setupForUser($user); + return; + } - $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60); - if ($cacheDuration > 0) { - $this->cache->set($user->getUID(), true, $cacheDuration); - } + // for the user's home folder, it's always the home mount + if (rtrim($path) === "/" . $user->getUID() . "/files" && !$includeChildren) { + $this->oneTimeUserSetup($user); return; } @@ -381,7 +391,7 @@ class SetupManager { $setupProviders[] = $cachedMount->getMountProvider(); $currentProviders[] = $cachedMount->getMountProvider(); if ($cachedMount->getMountProvider()) { - $mounts = $this->mountProviderCollection->getUserMountsForProviderClass($user, $cachedMount->getMountProvider()); + $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]); } else { $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); $this->setupForUser($user); @@ -396,7 +406,7 @@ class SetupManager { $setupProviders[] = $cachedMount->getMountProvider(); $currentProviders[] = $cachedMount->getMountProvider(); if ($cachedMount->getMountProvider()) { - $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClass($user, $cachedMount->getMountProvider())); + $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()])); } else { $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); $this->setupForUser($user); @@ -416,6 +426,60 @@ class SetupManager { } } + private function fullSetupRequired(IUser $user): bool { + // we perform a "cached" setup only after having done the full setup recently + // this is also used to trigger a full setup after handling events that are likely + // to change the available mounts + return !$this->cache->get($user->getUID()); + } + + /** + * @param string $path + * @param string[] $providers + */ + public function setupForProvider(string $path, array $providers): void { + $user = $this->getUserForPath($path); + if (!$user) { + $this->setupRoot(); + return; + } + + if ($this->isSetupComplete($user)) { + return; + } + + if ($this->fullSetupRequired($user)) { + $this->setupForUser($user); + return; + } + + // home providers are always used + $providers = array_filter($providers, function (string $provider) { + return !is_subclass_of($provider, IHomeMountProvider::class); + }); + + if (in_array('', $providers)) { + $this->setupForUser($user); + } + $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? []; + + $providers = array_diff($providers, $setupProviders); + if (count($providers) === 0) { + if (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + return; + } else { + $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers); + $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers); + } + + $this->userMountCache->registerMounts($user, $mounts, $providers); + $this->setupForUserWith($user, function () use ($mounts) { + array_walk($mounts, [$this->mountManager, 'addMount']); + }); + } + public function tearDown() { $this->setupUsers = []; $this->setupUsersComplete = []; diff --git a/lib/private/Files/Utils/PathHelper.php b/lib/private/Files/Utils/PathHelper.php new file mode 100644 index 00000000000..07985e884ce --- /dev/null +++ b/lib/private/Files/Utils/PathHelper.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Utils; + +class PathHelper { + /** + * Make a path relative to a root path, or return null if the path is outside the root + * + * @param string $root + * @param string $path + * @return ?string + */ + public static function getRelativePath(string $root, string $path) { + if ($root === '' or $root === '/') { + return self::normalizePath($path); + } + if ($path === $root) { + return '/'; + } elseif (strpos($path, $root . '/') !== 0) { + return null; + } else { + $path = substr($path, strlen($root)); + return self::normalizePath($path); + } + } + + /** + * @param string $path + * @return string + */ + public static function normalizePath(string $path): string { + if ($path === '' or $path === '/') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + $path = rtrim($path, '/'); + + return $path; + } +} diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 779e0611591..30dc5518be8 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1099,6 +1099,7 @@ class View { [Filesystem::signal_param_path => $this->getHookPath($path)] ); } + /** @var Storage|null $storage */ [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix); if ($storage) { return $storage->hash($type, $internalPath, $raw); @@ -1179,7 +1180,7 @@ class View { throw $e; } - if ($result && in_array('delete', $hooks) and $result) { + if ($result && in_array('delete', $hooks)) { $this->removeUpdate($storage, $internalPath); } if ($result && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') { @@ -1430,129 +1431,134 @@ class View { * @param string $mimetype_filter limit returned content to this mimetype or mimepart * @return FileInfo[] */ - public function getDirectoryContent($directory, $mimetype_filter = '') { + public function getDirectoryContent($directory, $mimetype_filter = '', \OCP\Files\FileInfo $directoryInfo = null) { $this->assertPathLength($directory); if (!Filesystem::isValidPath($directory)) { return []; } + $path = $this->getAbsolutePath($directory); $path = Filesystem::normalizePath($path); $mount = $this->getMount($directory); - if (!$mount) { - return []; - } $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($path); - if ($storage) { - $cache = $storage->getCache($internalPath); - $user = \OC_User::getUser(); + if (!$storage) { + return []; + } - $data = $this->getCacheEntry($storage, $internalPath, $directory); + $cache = $storage->getCache($internalPath); + $user = \OC_User::getUser(); - if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) { + if (!$directoryInfo) { + $data = $this->getCacheEntry($storage, $internalPath, $directory); + if (!$data instanceof ICacheEntry || !isset($data['fileid'])) { return []; } + } else { + $data = $directoryInfo; + } - $folderId = $data['fileid']; - $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + if (!($data->getPermissions() & Constants::PERMISSION_READ)) { + return []; + } - $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); + $folderId = $data->getId(); + $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter - $fileNames = array_map(function (ICacheEntry $content) { - return $content->getName(); - }, $contents); - /** - * @var \OC\Files\FileInfo[] $fileInfos - */ - $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { - if ($sharingDisabled) { - $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } - $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); - return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); - }, $contents); - $files = array_combine($fileNames, $fileInfos); - - //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mounts = Filesystem::getMountManager()->findIn($path); - $dirLength = strlen($path); - foreach ($mounts as $mount) { - $mountPoint = $mount->getMountPoint(); - $subStorage = $mount->getStorage(); - if ($subStorage) { - $subCache = $subStorage->getCache(''); + $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); - $rootEntry = $subCache->get(''); - if (!$rootEntry) { - $subScanner = $subStorage->getScanner(); - try { - $subScanner->scanFile(''); - } catch (\OCP\Files\StorageNotAvailableException $e) { - continue; - } catch (\OCP\Files\StorageInvalidException $e) { - continue; - } catch (\Exception $e) { - // sometimes when the storage is not available it can be any exception - \OC::$server->getLogger()->logException($e, [ - 'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"', - 'level' => ILogger::ERROR, - 'app' => 'lib', - ]); - continue; - } - $rootEntry = $subCache->get(''); + $fileNames = array_map(function (ICacheEntry $content) { + return $content->getName(); + }, $contents); + /** + * @var \OC\Files\FileInfo[] $fileInfos + */ + $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { + if ($sharingDisabled) { + $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; + } + $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); + return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); + }, $contents); + $files = array_combine($fileNames, $fileInfos); + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mounts = Filesystem::getMountManager()->findIn($path); + $dirLength = strlen($path); + foreach ($mounts as $mount) { + $mountPoint = $mount->getMountPoint(); + $subStorage = $mount->getStorage(); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + $rootEntry = $subCache->get(''); + if (!$rootEntry) { + $subScanner = $subStorage->getScanner(); + try { + $subScanner->scanFile(''); + } catch (\OCP\Files\StorageNotAvailableException $e) { + continue; + } catch (\OCP\Files\StorageInvalidException $e) { + continue; + } catch (\Exception $e) { + // sometimes when the storage is not available it can be any exception + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"', + 'level' => ILogger::ERROR, + 'app' => 'lib', + ]); + continue; } + $rootEntry = $subCache->get(''); + } - if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) { - $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if ($pos = strpos($relativePath, '/')) { - //mountpoint inside subfolder add size to the correct folder - $entryName = substr($relativePath, 0, $pos); - foreach ($files as &$entry) { - if ($entry->getName() === $entryName) { - $entry->addSubEntry($rootEntry, $mountPoint); - } - } - } else { //mountpoint in this folder, add an entry for it - $rootEntry['name'] = $relativePath; - $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; - $permissions = $rootEntry['permissions']; - // do not allow renaming/deleting the mount point if they are not shared files/folders - // for shared files/folders we use the permissions given by the owner - if ($mount instanceof MoveableMount) { - $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; - } else { - $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); + if ($rootEntry && ($rootEntry->getPermissions() & Constants::PERMISSION_READ)) { + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { + //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry->getName() === $entryName) { + $entry->addSubEntry($rootEntry, $mountPoint); } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $permissions = $rootEntry['permissions']; + // do not allow renaming/deleting the mount point if they are not shared files/folders + // for shared files/folders we use the permissions given by the owner + if ($mount instanceof MoveableMount) { + $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; + } else { + $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); + } - $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ - - // if sharing was disabled for the user we remove the share permissions - if (\OCP\Util::isSharingDisabledForUser()) { - $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } + $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ - $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); - $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); + // if sharing was disabled for the user we remove the share permissions + if (\OCP\Util::isSharingDisabledForUser()) { + $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; } - } - } - } - if ($mimetype_filter) { - $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { - if (strpos($mimetype_filter, '/')) { - return $file->getMimetype() === $mimetype_filter; - } else { - return $file->getMimePart() === $mimetype_filter; + $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); + $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); } - }); + } } + } - return array_values($files); - } else { - return []; + if ($mimetype_filter) { + $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { + if (strpos($mimetype_filter, '/')) { + return $file->getMimetype() === $mimetype_filter; + } else { + return $file->getMimePart() === $mimetype_filter; + } + }); } + + return array_values($files); } /** diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php index 9a9996f7f60..2ef4d2ee23f 100644 --- a/lib/private/Group/Group.php +++ b/lib/private/Group/Group.php @@ -354,8 +354,7 @@ class Group implements IGroup { } foreach ($this->backends as $backend) { if ($backend->implementsActions(\OC\Group\Backend::DELETE_GROUP)) { - $result = true; - $backend->deleteGroup($this->gid); + $result = $result || $backend->deleteGroup($this->gid); } } if ($result) { diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php index a770fe3b459..cef3fa4039a 100644 --- a/lib/private/Preview/Generator.php +++ b/lib/private/Preview/Generator.php @@ -137,9 +137,10 @@ class Generator { $previewVersion = $file->getPreviewVersion() . '-'; } + // If imaginary is enabled, and we request a small thumbnail, + // let's not generate the max preview for performance reasons if (count($specifications) === 1 - && (($specifications[0]['width'] === 250 && $specifications[0]['height'] === 250) - || ($specifications[0]['width'] === 150 && $specifications[0]['height'] === 150)) + && ($specifications[0]['width'] <= 256 || $specifications[0]['height'] <= 256) && preg_match(Imaginary::supportedMimeTypes(), $mimeType) && $this->config->getSystemValueString('preview_imaginary_url', 'invalid') !== 'invalid') { $crop = $specifications[0]['crop'] ?? false; @@ -221,6 +222,10 @@ class Generator { return $preview; } + /** + * Generate a small image straight away without generating a max preview first + * Preview generated is 256x256 + */ private function getSmallImagePreview(ISimpleFolder $previewFolder, File $file, string $mimeType, string $prefix, bool $crop) { $nodes = $previewFolder->getDirectoryListing(); diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php index 7e6ce86d4eb..4da88f1ab26 100644 --- a/lib/private/Preview/Imaginary.php +++ b/lib/private/Preview/Imaginary.php @@ -89,18 +89,26 @@ class Imaginary extends ProviderV2 { $mimeType = 'jpeg'; } - $parameters = [ - 'width' => $maxX, - 'height' => $maxY, - 'stripmeta' => 'true', - 'type' => $mimeType, + $operations = [ + [ + 'operation' => 'autorotate', + ], + [ + 'operation' => ($crop ? 'smartcrop' : 'fit'), + 'params' => [ + 'width' => $maxX, + 'height' => $maxY, + 'stripmeta' => 'true', + 'type' => $mimeType, + 'norotation' => 'true', + ] + ] ]; - try { $response = $httpClient->post( - $imaginaryUrl . ($crop ? '/smartcrop' : '/fit'), [ - 'query' => $parameters, + $imaginaryUrl . '/pipeline', [ + 'query' => ['operations' => json_encode($operations)], 'stream' => true, 'content-type' => $file->getMimeType(), 'body' => $stream, diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 2a76ddafb25..3fca9e3fe14 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -1783,9 +1783,21 @@ class Manager implements IManager { /** * Is password on public link requires * + * @param bool Check group membership exclusion * @return bool */ - public function shareApiLinkEnforcePassword() { + public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) { + $excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', ''); + if ($excludedGroups !== '' && $checkGroupMembership) { + $excludedGroups = json_decode($excludedGroups); + $user = $this->userSession->getUser(); + if ($user) { + $userGroups = $this->groupManager->getUserGroupIds($user); + if ((bool)array_intersect($excludedGroups, $userGroups)) { + return false; + } + } + } return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes'; } diff --git a/lib/private/Streamer.php b/lib/private/Streamer.php index 176334b971d..80ab5a5524c 100644 --- a/lib/private/Streamer.php +++ b/lib/private/Streamer.php @@ -95,6 +95,7 @@ class Streamer { * @param string $name */ public function sendHeaders($name) { + header('X-Accel-Buffering: no'); $extension = $this->streamerInstance instanceof ZipStreamer ? '.zip' : '.tar'; $fullName = $name . $extension; $this->streamerInstance->sendHeaders($fullName); diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index cf06f021590..6d90d46ec67 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -47,7 +47,6 @@ use OC\Search\SearchQuery; use OC\Template\JSCombiner; use OC\Template\JSConfigHelper; use OC\Template\SCSSCacher; -use OCP\App\IAppManager; use OCP\AppFramework\Http\TemplateResponse; use OCP\Defaults; use OCP\IConfig; @@ -55,7 +54,6 @@ use OCP\IInitialStateService; use OCP\INavigationManager; use OCP\IUserSession; use OCP\Support\Subscription\IRegistry; -use OCP\UserStatus\IManager as IUserStatusManager; use OCP\Util; use Psr\Log\LoggerInterface; @@ -140,17 +138,6 @@ class TemplateLayout extends \OC_Template { } else { $this->assign('userAvatarSet', true); $this->assign('userAvatarVersion', $this->config->getUserValue(\OC_User::getUser(), 'avatar', 'version', 0)); - if (\OC::$server->get(IAppManager::class)->isEnabledForUser('user_status')) { - $userStatusManager = \OC::$server->get(IUserStatusManager::class); - $userStatuses = $userStatusManager->getUserStatuses([$user->getUID()]); - if (array_key_exists($user->getUID(), $userStatuses)) { - $this->assign('userStatus', $userStatuses[$user->getUID()]); - } else { - $this->assign('userStatus', false); - } - } else { - $this->assign('userStatus', false); - } } // check if app menu icons should be inverted diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index ca01e91216b..f290b7a610c 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -629,11 +629,21 @@ class OC_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)) { - $path_info = $request->getPathInfo(); + 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); } diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php index d1af5b24bdd..41ac20577b2 100644 --- a/lib/private/legacy/OC_Files.php +++ b/lib/private/legacy/OC_Files.php @@ -98,6 +98,7 @@ class OC_Files { } } header('Content-Type: '.$type, true); + header('X-Accel-Buffering: no'); } /** diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php index efb9252e346..547ffef8607 100644 --- a/lib/private/legacy/OC_Helper.php +++ b/lib/private/legacy/OC_Helper.php @@ -485,7 +485,7 @@ class OC_Helper { * @return array * @throws \OCP\Files\NotFoundException */ - public static function getStorageInfo($path, $rootInfo = null) { + public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true) { // return storage info without adding mount points $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); @@ -495,7 +495,7 @@ class OC_Helper { if (!$rootInfo instanceof \OCP\Files\FileInfo) { throw new \OCP\Files\NotFoundException(); } - $used = $rootInfo->getSize(); + $used = $rootInfo->getSize($includeMountPoints); if ($used < 0) { $used = 0; } diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index ceed79bc9d5..edee23995f1 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -116,15 +116,16 @@ class OC_Util { } /** - * check if a password is required for each public link + * Check if a password is required for each public link * + * @param bool $checkGroupMembership Check group membership exclusion * @return boolean * @suppress PhanDeprecatedFunction */ - public static function isPublicLinkPasswordRequired() { + public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) { /** @var IManager $shareManager */ $shareManager = \OC::$server->get(IManager::class); - return $shareManager->shareApiLinkEnforcePassword(); + return $shareManager->shareApiLinkEnforcePassword($checkGroupMembership); } /** diff --git a/lib/public/Files/Config/IMountProviderCollection.php b/lib/public/Files/Config/IMountProviderCollection.php index 5894d06a388..2d42246b863 100644 --- a/lib/public/Files/Config/IMountProviderCollection.php +++ b/lib/public/Files/Config/IMountProviderCollection.php @@ -42,11 +42,11 @@ interface IMountProviderCollection { * Get the configured mount points for the user from a specific mount provider * * @param \OCP\IUser $user - * @param class-string<IMountProvider> $mountProviderClass + * @param class-string<IMountProvider>[] $mountProviderClasses * @return \OCP\Files\Mount\IMountPoint[] * @since 24.0.0 */ - public function getUserMountsForProviderClass(IUser $user, string $mountProviderClass); + public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array; /** * Get the configured home mount for this user diff --git a/lib/public/Files/IRootFolder.php b/lib/public/Files/IRootFolder.php index f89a0041146..7d007cb690c 100644 --- a/lib/public/Files/IRootFolder.php +++ b/lib/public/Files/IRootFolder.php @@ -38,11 +38,22 @@ interface IRootFolder extends Folder, Emitter { * Returns a view to user's files folder * * @param string $userId user ID - * @return \OCP\Files\Folder + * @return Folder * @throws NoUserException * @throws NotPermittedException * * @since 8.2.0 */ public function getUserFolder($userId); + + /** + * Get a file or folder by fileid, inside a parent path + * + * @param int $id + * @param string $path + * @return Node[] + * + * @since 24.0.0 + */ + public function getByIdInPath(int $id, string $path); } diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index ff4b6af19e0..f6b74c4de4a 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -321,10 +321,12 @@ interface IManager { /** * Is password on public link requires * + * @param bool $checkGroupMembership Check group membership exclusion * @return bool * @since 9.0.0 + * @since 24.0.0 Added optional $checkGroupMembership parameter */ - public function shareApiLinkEnforcePassword(); + public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true); /** * Is default expire date enabled diff --git a/lib/public/User/Events/UserLiveStatusEvent.php b/lib/public/User/Events/UserLiveStatusEvent.php index dd90400cb3b..4bba71f95b9 100644 --- a/lib/public/User/Events/UserLiveStatusEvent.php +++ b/lib/public/User/Events/UserLiveStatusEvent.php @@ -27,6 +27,7 @@ namespace OCP\User\Events; use OCP\EventDispatcher\Event; use OCP\IUser; +use OCP\UserStatus\IUserStatus; /** * @since 20.0.0 @@ -51,19 +52,12 @@ class UserLiveStatusEvent extends Event { */ public const STATUS_OFFLINE = 'offline'; - /** @var IUser */ - private $user; - - /** @var string */ - private $status; - - /** @var int */ - private $timestamp; + private IUser $user; + private string $status; + private int $timestamp; + private ?IUserStatus $userStatus = null; /** - * @param IUser $user - * @param string $status - * @param int $timestamp * @since 20.0.0 */ public function __construct(IUser $user, @@ -98,4 +92,19 @@ class UserLiveStatusEvent extends Event { public function getTimestamp(): int { return $this->timestamp; } + + /** + * Get the user status that might be available after processing the event + * @since 24.0.0 + */ + public function getUserStatus(): ?IUserStatus { + return $this->userStatus; + } + + /** + * @since 24.0.0 + */ + public function setUserStatus(IUserStatus $userStatus) { + $this->userStatus = $userStatus; + } } diff --git a/lib/public/Util.php b/lib/public/Util.php index cd6f5f34a69..c8b55bb10e2 100644 --- a/lib/public/Util.php +++ b/lib/public/Util.php @@ -547,12 +547,14 @@ class Util { } /** - * check if a password is required for each public link + * Check if a password is required for each public link + * + * @param bool $checkGroupMembership Check group membership exclusion * @return boolean * @since 7.0.0 */ - public static function isPublicLinkPasswordRequired() { - return \OC_Util::isPublicLinkPasswordRequired(); + public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) { + return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership); } /** |