aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
authorMarcel Klehr <mklehr@gmx.net>2023-12-19 12:29:03 +0100
committerGitHub <noreply@github.com>2023-12-19 12:29:03 +0100
commit2e0141165aef63fa494ec8f7668ccdb379e8d55e (patch)
treee3b15303961a8e30e7bc939b6216aa8212af6f2c /lib/private
parentab736429ce1bf126bd8b1bef1db4cac9a31e139e (diff)
parentd8381acf861202bed821e92f3cf8647db87a0efe (diff)
downloadnextcloud-server-2e0141165aef63fa494ec8f7668ccdb379e8d55e.tar.gz
nextcloud-server-2e0141165aef63fa494ec8f7668ccdb379e8d55e.zip
Merge branch 'master' into enh/text-processing-provider-with-id
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/Accounts/AccountManager.php4
-rw-r--r--lib/private/Activity/Manager.php10
-rw-r--r--lib/private/AllConfig.php13
-rw-r--r--lib/private/App/AppManager.php23
-rw-r--r--lib/private/App/AppStore/Fetcher/AppFetcher.php12
-rw-r--r--lib/private/App/AppStore/Fetcher/CategoryFetcher.php10
-rw-r--r--lib/private/App/AppStore/Fetcher/Fetcher.php10
-rw-r--r--lib/private/App/Platform.php2
-rw-r--r--lib/private/AppFramework/App.php8
-rw-r--r--lib/private/AppFramework/Bootstrap/Coordinator.php10
-rw-r--r--lib/private/AppFramework/Bootstrap/EventListenerRegistration.php6
-rw-r--r--lib/private/AppFramework/Bootstrap/ParameterRegistration.php4
-rw-r--r--lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php4
-rw-r--r--lib/private/AppFramework/Bootstrap/RegistrationContext.php42
-rw-r--r--lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php4
-rw-r--r--lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php6
-rw-r--r--lib/private/AppFramework/Http/Dispatcher.php38
-rw-r--r--lib/private/AppFramework/Http/Request.php18
-rw-r--r--lib/private/AppFramework/Http/RequestId.php2
-rw-r--r--lib/private/AppFramework/Middleware/MiddlewareDispatcher.php12
-rw-r--r--lib/private/AppFramework/Middleware/Security/CORSMiddleware.php6
-rw-r--r--lib/private/AppFramework/Middleware/Security/CSPMiddleware.php4
-rw-r--r--lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php6
-rw-r--r--lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php2
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php24
-rw-r--r--lib/private/AppFramework/Middleware/SessionMiddleware.php2
-rw-r--r--lib/private/AppFramework/OCS/BaseResponse.php8
-rw-r--r--lib/private/AppFramework/ScopedPsrLogger.php2
-rw-r--r--lib/private/AppFramework/Utility/ControllerMethodReflector.php33
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php7
-rw-r--r--lib/private/AppScriptSort.php8
-rw-r--r--lib/private/Archive/TAR.php4
-rw-r--r--lib/private/Authentication/Listeners/RemoteWipeActivityListener.php2
-rw-r--r--lib/private/Authentication/Listeners/RemoteWipeEmailListener.php6
-rw-r--r--lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php2
-rw-r--r--lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php2
-rw-r--r--lib/private/Authentication/Login/Chain.php22
-rw-r--r--lib/private/Authentication/Login/CreateSessionTokenCommand.php2
-rw-r--r--lib/private/Authentication/Login/LoggedInCheckCommand.php2
-rw-r--r--lib/private/Authentication/Login/LoginData.php10
-rw-r--r--lib/private/Authentication/Login/LoginResult.php5
-rw-r--r--lib/private/Authentication/Login/SetUserTimezoneCommand.php2
-rw-r--r--lib/private/Authentication/Login/TwoFactorCommand.php8
-rw-r--r--lib/private/Authentication/Login/UserDisabledCheckCommand.php2
-rw-r--r--lib/private/Authentication/Login/WebAuthnChain.php18
-rw-r--r--lib/private/Authentication/LoginCredentials/Store.php4
-rw-r--r--lib/private/Authentication/Token/IProvider.php12
-rw-r--r--lib/private/Authentication/Token/Manager.php12
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php42
-rw-r--r--lib/private/Authentication/Token/RemoteWipe.php8
-rw-r--r--lib/private/Authentication/TwoFactorAuth/EnforcementState.php4
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Manager.php18
-rw-r--r--lib/private/Authentication/TwoFactorAuth/ProviderSet.php2
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Registry.php2
-rw-r--r--lib/private/Authentication/WebAuthn/Manager.php2
-rw-r--r--lib/private/Avatar/GuestAvatar.php2
-rw-r--r--lib/private/BackgroundJob/JobList.php3
-rw-r--r--lib/private/BinaryFinder.php2
-rw-r--r--lib/private/CapabilitiesManager.php2
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php2
-rw-r--r--lib/private/Command/AsyncBus.php2
-rw-r--r--lib/private/Command/ClosureJob.php2
-rw-r--r--lib/private/Command/CronBus.php2
-rw-r--r--lib/private/Comments/Comment.php29
-rw-r--r--lib/private/Comments/Manager.php64
-rw-r--r--lib/private/Console/Application.php8
-rw-r--r--lib/private/Console/TimestampFormatter.php19
-rw-r--r--lib/private/Contacts/ContactsMenu/ActionProviderStore.php13
-rw-r--r--lib/private/Contacts/ContactsMenu/ContactsStore.php87
-rw-r--r--lib/private/Contacts/ContactsMenu/Entry.php44
-rw-r--r--lib/private/Contacts/ContactsMenu/Manager.php28
-rw-r--r--lib/private/DB/Connection.php4
-rw-r--r--lib/private/DB/MigrationService.php6
-rw-r--r--lib/private/DB/Migrator.php6
-rw-r--r--lib/private/DB/MissingColumnInformation.php2
-rw-r--r--lib/private/DB/MissingIndexInformation.php10
-rw-r--r--lib/private/DB/MissingPrimaryKeyInformation.php4
-rw-r--r--lib/private/DB/QueryBuilder/QueryBuilder.php2
-rw-r--r--lib/private/Dashboard/Manager.php2
-rw-r--r--lib/private/DirectEditing/Manager.php4
-rw-r--r--lib/private/Encryption/EncryptionWrapper.php4
-rw-r--r--lib/private/Encryption/File.php6
-rw-r--r--lib/private/Encryption/HookManager.php2
-rw-r--r--lib/private/Encryption/Update.php16
-rw-r--r--lib/private/EventDispatcher/EventDispatcher.php22
-rw-r--r--lib/private/EventDispatcher/ServiceEventListener.php4
-rw-r--r--lib/private/Federation/CloudFederationShare.php20
-rw-r--r--lib/private/Files/AppData/AppData.php6
-rw-r--r--lib/private/Files/AppData/Factory.php2
-rw-r--r--lib/private/Files/Cache/Cache.php26
-rw-r--r--lib/private/Files/Cache/CacheQueryBuilder.php27
-rw-r--r--lib/private/Files/Cache/QuerySearchHelper.php79
-rw-r--r--lib/private/Files/Cache/Scanner.php4
-rw-r--r--lib/private/Files/Cache/SearchBuilder.php107
-rw-r--r--lib/private/Files/Cache/Watcher.php2
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheJail.php2
-rw-r--r--lib/private/Files/Config/CachedMountInfo.php6
-rw-r--r--lib/private/Files/Config/LazyStorageMountInfo.php8
-rw-r--r--lib/private/Files/Config/MountProviderCollection.php5
-rw-r--r--lib/private/Files/Config/UserMountCache.php104
-rw-r--r--lib/private/Files/FileInfo.php44
-rw-r--r--lib/private/Files/Filesystem.php2
-rw-r--r--lib/private/Files/Mount/HomeMountPoint.php49
-rw-r--r--lib/private/Files/Mount/LocalHomeMountProvider.php2
-rw-r--r--lib/private/Files/Mount/Manager.php13
-rw-r--r--lib/private/Files/Mount/ObjectHomeMountProvider.php2
-rw-r--r--lib/private/Files/Node/Folder.php2
-rw-r--r--lib/private/Files/Node/HookConnector.php4
-rw-r--r--lib/private/Files/Node/LazyFolder.php11
-rw-r--r--lib/private/Files/Node/LazyUserFolder.php6
-rw-r--r--lib/private/Files/Node/Node.php20
-rw-r--r--lib/private/Files/Node/Root.php2
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php7
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php8
-rw-r--r--lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php7
-rw-r--r--lib/private/Files/Search/SearchComparison.php44
-rw-r--r--lib/private/Files/Search/SearchOrder.php30
-rw-r--r--lib/private/Files/SetupManager.php41
-rw-r--r--lib/private/Files/SimpleFS/SimpleFolder.php2
-rw-r--r--lib/private/Files/Storage/DAV.php2
-rw-r--r--lib/private/Files/Storage/Local.php27
-rw-r--r--lib/private/Files/Storage/Wrapper/Encoding.php2
-rw-r--r--lib/private/Files/Storage/Wrapper/Jail.php5
-rw-r--r--lib/private/Files/Stream/Encryption.php24
-rw-r--r--lib/private/Files/Template/TemplateManager.php7
-rw-r--r--lib/private/Files/Type/Detection.php10
-rw-r--r--lib/private/Files/Utils/Scanner.php4
-rw-r--r--lib/private/Files/View.php6
-rw-r--r--lib/private/FilesMetadata/FilesMetadataManager.php347
-rw-r--r--lib/private/FilesMetadata/Job/UpdateSingleMetadata.php67
-rw-r--r--lib/private/FilesMetadata/Listener/MetadataDelete.php61
-rw-r--r--lib/private/FilesMetadata/Listener/MetadataUpdate.php64
-rw-r--r--lib/private/FilesMetadata/MetadataQuery.php167
-rw-r--r--lib/private/FilesMetadata/Model/FilesMetadata.php621
-rw-r--r--lib/private/FilesMetadata/Model/MetadataValueWrapper.php421
-rw-r--r--lib/private/FilesMetadata/Service/IndexRequestService.php195
-rw-r--r--lib/private/FilesMetadata/Service/MetadataRequestService.php194
-rw-r--r--lib/private/Group/Database.php4
-rw-r--r--lib/private/Group/Group.php44
-rw-r--r--lib/private/Group/Manager.php8
-rw-r--r--lib/private/Group/MetaData.php10
-rw-r--r--lib/private/Hooks/EmitterTrait.php2
-rw-r--r--lib/private/Http/CookieHelper.php14
-rw-r--r--lib/private/Http/WellKnown/RequestManager.php4
-rw-r--r--lib/private/Installer.php23
-rw-r--r--lib/private/IntegrityCheck/Checker.php26
-rw-r--r--lib/private/Log.php6
-rw-r--r--lib/private/Memcache/Factory.php2
-rw-r--r--lib/private/Metadata/Capabilities.php44
-rw-r--r--lib/private/Metadata/FileEventListener.php110
-rw-r--r--lib/private/Metadata/FileMetadata.php51
-rw-r--r--lib/private/Metadata/FileMetadataMapper.php177
-rw-r--r--lib/private/Metadata/IMetadataManager.php35
-rw-r--r--lib/private/Metadata/IMetadataProvider.php41
-rw-r--r--lib/private/Metadata/MetadataManager.php100
-rw-r--r--lib/private/Metadata/Provider/ExifProvider.php142
-rw-r--r--lib/private/Migration/BackgroundRepair.php24
-rw-r--r--lib/private/Migration/ConsoleOutput.php23
-rw-r--r--lib/private/Migration/SimpleOutput.php22
-rw-r--r--lib/private/NavigationManager.php68
-rw-r--r--lib/private/Net/HostnameClassifier.php4
-rw-r--r--lib/private/Net/IpAddressClassifier.php4
-rw-r--r--lib/private/Notification/Manager.php10
-rw-r--r--lib/private/OCS/DiscoveryService.php2
-rw-r--r--lib/private/OCS/Provider.php4
-rw-r--r--lib/private/Preview/BackgroundCleanupJob.php8
-rw-r--r--lib/private/Preview/EMF.php33
-rw-r--r--lib/private/Preview/Generator.php18
-rw-r--r--lib/private/Preview/Imaginary.php9
-rw-r--r--lib/private/Preview/Office.php60
-rw-r--r--lib/private/Preview/WatcherConnector.php2
-rw-r--r--lib/private/PreviewManager.php60
-rw-r--r--lib/private/Profile/Actions/FediverseAction.php2
-rw-r--r--lib/private/Profile/Actions/TwitterAction.php2
-rw-r--r--lib/private/Profile/ProfileManager.php10
-rw-r--r--lib/private/Profiler/Profiler.php6
-rw-r--r--lib/private/Repair.php21
-rw-r--r--lib/private/Repair/AddMetadataGenerationJob.php43
-rw-r--r--lib/private/Repair/AddRemoveOldTasksBackgroundJob.php8
-rw-r--r--lib/private/Repair/ClearFrontendCaches.php2
-rw-r--r--lib/private/Repair/ClearGeneratedAvatarCache.php2
-rw-r--r--lib/private/Repair/ClearGeneratedAvatarCacheJob.php4
-rw-r--r--lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php2
-rw-r--r--lib/private/Repair/NC20/EncryptionLegacyCipher.php2
-rw-r--r--lib/private/Repair/NC20/EncryptionMigration.php2
-rw-r--r--lib/private/Repair/NC21/ValidatePhoneNumber.php4
-rw-r--r--lib/private/Repair/Owncloud/CleanPreviews.php4
-rw-r--r--lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php8
-rw-r--r--lib/private/Repair/Owncloud/MigrateOauthTables.php4
-rw-r--r--lib/private/Repair/Owncloud/MoveAvatars.php2
-rw-r--r--lib/private/Repair/Owncloud/UpdateLanguageCodes.php2
-rw-r--r--lib/private/Repair/RemoveLinkShares.php8
-rw-r--r--lib/private/Repair/RepairMimeTypes.php26
-rw-r--r--lib/private/Route/Router.php10
-rw-r--r--lib/private/Search/Filter/BooleanFilter.php46
-rw-r--r--lib/private/Search/Filter/DateTimeFilter.php46
-rw-r--r--lib/private/Search/Filter/FloatFilter.php45
-rw-r--r--lib/private/Search/Filter/GroupFilter.php51
-rw-r--r--lib/private/Search/Filter/IntegerFilter.php45
-rw-r--r--lib/private/Search/Filter/StringFilter.php44
-rw-r--r--lib/private/Search/Filter/StringsFilter.php51
-rw-r--r--lib/private/Search/Filter/UserFilter.php51
-rw-r--r--lib/private/Search/FilterCollection.php60
-rw-r--r--lib/private/Search/FilterFactory.php60
-rw-r--r--lib/private/Search/SearchComposer.php252
-rw-r--r--lib/private/Search/SearchQuery.php80
-rw-r--r--lib/private/Search/UnsupportedFilter.php34
-rw-r--r--lib/private/Security/Bruteforce/Capabilities.php2
-rw-r--r--lib/private/Security/Bruteforce/Throttler.php4
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicy.php8
-rw-r--r--lib/private/Security/Normalizer/IpAddress.php69
-rw-r--r--lib/private/Security/RemoteHostValidator.php4
-rw-r--r--lib/private/Security/VerificationToken/CleanUpJob.php4
-rw-r--r--lib/private/Server.php37
-rw-r--r--lib/private/Session/CryptoSessionData.php34
-rw-r--r--lib/private/Session/CryptoWrapper.php6
-rw-r--r--lib/private/Settings/Manager.php39
-rw-r--r--lib/private/Setup.php32
-rw-r--r--lib/private/Setup/AbstractDatabase.php5
-rw-r--r--lib/private/Setup/MySQL.php2
-rw-r--r--lib/private/SetupCheck/SetupCheckManager.php3
-rw-r--r--lib/private/Share/Share.php6
-rw-r--r--lib/private/Share20/Manager.php20
-rw-r--r--lib/private/Share20/PublicShareTemplateFactory.php2
-rw-r--r--lib/private/Share20/Share.php2
-rw-r--r--lib/private/StreamImage.php2
-rw-r--r--lib/private/SubAdmin.php6
-rw-r--r--lib/private/Support/Subscription/Registry.php8
-rw-r--r--lib/private/SystemTag/ManagerFactory.php16
-rw-r--r--lib/private/SystemTag/SystemTag.php53
-rw-r--r--lib/private/SystemTag/SystemTagManager.php17
-rw-r--r--lib/private/SystemTag/SystemTagObjectMapper.php7
-rw-r--r--lib/private/SystemTag/SystemTagsInFilesDetector.php4
-rw-r--r--lib/private/TagManager.php2
-rw-r--r--lib/private/Tagging/TagMapper.php2
-rw-r--r--lib/private/Talk/Broker.php8
-rw-r--r--lib/private/Template/JSCombiner.php8
-rw-r--r--lib/private/Template/JSConfigHelper.php25
-rw-r--r--lib/private/Template/JSResourceLocator.php9
-rw-r--r--lib/private/TemplateLayout.php28
-rw-r--r--lib/private/TextProcessing/Db/Task.php10
-rw-r--r--lib/private/TextProcessing/Manager.php107
-rw-r--r--lib/private/TextToImage/Db/Task.php117
-rw-r--r--lib/private/TextToImage/Db/TaskMapper.php127
-rw-r--r--lib/private/TextToImage/Manager.php335
-rw-r--r--lib/private/TextToImage/RemoveOldTasksBackgroundJob.php78
-rw-r--r--lib/private/TextToImage/TaskBackgroundJob.php63
-rw-r--r--lib/private/URLGenerator.php23
-rw-r--r--lib/private/Updater.php20
-rw-r--r--lib/private/Updater/VersionCheck.php4
-rw-r--r--lib/private/User/AvailabilityCoordinator.php139
-rw-r--r--lib/private/User/Listeners/BeforeUserDeletedListener.php2
-rw-r--r--lib/private/User/Listeners/UserChangedListener.php2
-rw-r--r--lib/private/User/Manager.php8
-rw-r--r--lib/private/User/OutOfOfficeData.php74
-rw-r--r--lib/private/User/Session.php42
-rw-r--r--lib/private/User/User.php12
-rw-r--r--lib/private/UserStatus/ISettableProvider.php3
-rw-r--r--lib/private/UserStatus/Manager.php6
-rw-r--r--lib/private/legacy/OC_App.php16
-rw-r--r--lib/private/legacy/OC_Files.php6
-rw-r--r--lib/private/legacy/OC_Helper.php2
-rw-r--r--lib/private/legacy/OC_User.php2
263 files changed, 5765 insertions, 1994 deletions
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
index 3e33e783635..97156a027e6 100644
--- a/lib/private/Accounts/AccountManager.php
+++ b/lib/private/Accounts/AccountManager.php
@@ -38,15 +38,15 @@ namespace OC\Accounts;
use Exception;
use InvalidArgumentException;
use OC\Profile\TProfileHelper;
-use OCP\Accounts\UserUpdatedEvent;
-use OCP\Cache\CappedMemoryCache;
use OCA\Settings\BackgroundJobs\VerifyUserData;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\IAccountProperty;
use OCP\Accounts\IAccountPropertyCollection;
use OCP\Accounts\PropertyDoesNotExistException;
+use OCP\Accounts\UserUpdatedEvent;
use OCP\BackgroundJob\IJobList;
+use OCP\Cache\CappedMemoryCache;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php
index a7d24510d53..14069260c6c 100644
--- a/lib/private/Activity/Manager.php
+++ b/lib/private/Activity/Manager.php
@@ -70,11 +70,11 @@ class Manager implements IManager {
protected $l10n;
public function __construct(
- IRequest $request,
- IUserSession $session,
- IConfig $config,
- IValidator $validator,
- IL10N $l10n
+ IRequest $request,
+ IUserSession $session,
+ IConfig $config,
+ IValidator $validator,
+ IL10N $l10n
) {
$this->request = $request;
$this->session = $session;
diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php
index 2a0e8f53b14..92178d64635 100644
--- a/lib/private/AllConfig.php
+++ b/lib/private/AllConfig.php
@@ -32,6 +32,7 @@
*/
namespace OC;
+use Doctrine\DBAL\Platforms\OraclePlatform;
use OCP\Cache\CappedMemoryCache;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
@@ -490,12 +491,15 @@ class AllConfig implements IConfig {
$this->fixDIInit();
$qb = $this->connection->getQueryBuilder();
+ $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform)
+ ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)
+ : 'configvalue';
$result = $qb->select('userid')
->from('preferences')
->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq(
- $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR),
+ $configValueColumn,
$qb->createNamedParameter($value, IQueryBuilder::PARAM_STR))
)->orderBy('userid')
->executeQuery();
@@ -524,13 +528,18 @@ class AllConfig implements IConfig {
// Email address is always stored lowercase in the database
return $this->getUsersForUserValue($appName, $key, strtolower($value));
}
+
$qb = $this->connection->getQueryBuilder();
+ $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform)
+ ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)
+ : 'configvalue';
+
$result = $qb->select('userid')
->from('preferences')
->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq(
- $qb->func()->lower($qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)),
+ $qb->func()->lower($configValueColumn),
$qb->createNamedParameter(strtolower($value), IQueryBuilder::PARAM_STR))
)->orderBy('userid')
->executeQuery();
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index ab7b470bb8d..ad5fdc5afed 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -48,10 +48,10 @@ use OCP\App\Events\AppDisableEvent;
use OCP\App\Events\AppEnableEvent;
use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
-use OCP\EventDispatcher\IEventDispatcher;
use OCP\Collaboration\AutoComplete\IManager as IAutoCompleteManager;
use OCP\Collaboration\Collaborators\ISearch as ICollaboratorSearch;
use OCP\Diagnostics\IEventLogger;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IGroup;
@@ -105,12 +105,12 @@ class AppManager implements IAppManager {
private array $loadedApps = [];
public function __construct(IUserSession $userSession,
- IConfig $config,
- AppConfig $appConfig,
- IGroupManager $groupManager,
- ICacheFactory $memCacheFactory,
- IEventDispatcher $dispatcher,
- LoggerInterface $logger) {
+ IConfig $config,
+ AppConfig $appConfig,
+ IGroupManager $groupManager,
+ ICacheFactory $memCacheFactory,
+ IEventDispatcher $dispatcher,
+ LoggerInterface $logger) {
$this->userSession = $userSession;
$this->config = $config;
$this->appConfig = $appConfig;
@@ -838,9 +838,12 @@ class AppManager implements IAppManager {
/* Fallback on user defined apporder */
$customOrders = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
if (!empty($customOrders)) {
- $customOrders = array_map('min', $customOrders);
- asort($customOrders);
- $defaultApps = array_keys($customOrders);
+ // filter only entries with app key (when added using closures or NavigationManager::add the app is not guranteed to be set)
+ $customOrders = array_filter($customOrders, fn ($entry) => isset($entry['app']));
+ // sort apps by order
+ usort($customOrders, fn ($a, $b) => $a['order'] - $b['order']);
+ // set default apps to sorted apps
+ $defaultApps = array_map(fn ($entry) => $entry['app'], $customOrders);
}
}
}
diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php
index 47bdece372d..f9fbd05855b 100644
--- a/lib/private/App/AppStore/Fetcher/AppFetcher.php
+++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php
@@ -49,12 +49,12 @@ class AppFetcher extends Fetcher {
private $ignoreMaxVersion;
public function __construct(Factory $appDataFactory,
- IClientService $clientService,
- ITimeFactory $timeFactory,
- IConfig $config,
- CompareVersion $compareVersion,
- LoggerInterface $logger,
- IRegistry $registry) {
+ IClientService $clientService,
+ ITimeFactory $timeFactory,
+ IConfig $config,
+ CompareVersion $compareVersion,
+ LoggerInterface $logger,
+ IRegistry $registry) {
parent::__construct(
$appDataFactory,
$clientService,
diff --git a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php
index afe051e6281..d1bbe4f7b04 100644
--- a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php
+++ b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php
@@ -35,11 +35,11 @@ use Psr\Log\LoggerInterface;
class CategoryFetcher extends Fetcher {
public function __construct(Factory $appDataFactory,
- IClientService $clientService,
- ITimeFactory $timeFactory,
- IConfig $config,
- LoggerInterface $logger,
- IRegistry $registry) {
+ IClientService $clientService,
+ ITimeFactory $timeFactory,
+ IConfig $config,
+ LoggerInterface $logger,
+ IRegistry $registry) {
parent::__construct(
$appDataFactory,
$clientService,
diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php
index 095b026cb44..3e76ab2d5da 100644
--- a/lib/private/App/AppStore/Fetcher/Fetcher.php
+++ b/lib/private/App/AppStore/Fetcher/Fetcher.php
@@ -68,11 +68,11 @@ abstract class Fetcher {
protected $channel = null;
public function __construct(Factory $appDataFactory,
- IClientService $clientService,
- ITimeFactory $timeFactory,
- IConfig $config,
- LoggerInterface $logger,
- IRegistry $registry) {
+ IClientService $clientService,
+ ITimeFactory $timeFactory,
+ IConfig $config,
+ LoggerInterface $logger,
+ IRegistry $registry) {
$this->appData = $appDataFactory->get('appstore');
$this->clientService = $clientService;
$this->timeFactory = $timeFactory;
diff --git a/lib/private/App/Platform.php b/lib/private/App/Platform.php
index 1cab740bebb..daff247d1bd 100644
--- a/lib/private/App/Platform.php
+++ b/lib/private/App/Platform.php
@@ -25,8 +25,8 @@
*/
namespace OC\App;
-use OCP\IConfig;
use OCP\IBinaryFinder;
+use OCP\IConfig;
/**
* Class Platform
diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php
index ffd77da888e..b18c95a2f0d 100644
--- a/lib/private/AppFramework/App.php
+++ b/lib/private/AppFramework/App.php
@@ -34,16 +34,16 @@ namespace OC\AppFramework;
use OC\AppFramework\DependencyInjection\DIContainer;
use OC\AppFramework\Http\Dispatcher;
use OC\AppFramework\Http\Request;
-use OCP\App\IAppManager;
-use OCP\Profiler\IProfiler;
use OC\Profiler\RoutingDataCollector;
-use OCP\AppFramework\QueryException;
+use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\ICallbackResponse;
use OCP\AppFramework\Http\IOutput;
+use OCP\AppFramework\QueryException;
use OCP\Diagnostics\IEventLogger;
use OCP\HintException;
use OCP\IRequest;
+use OCP\Profiler\IProfiler;
/**
* Entry point for every request in your app. You can consider this as your
@@ -257,7 +257,7 @@ class App {
* @param DIContainer $container an instance of a pimple container.
*/
public static function part(string $controllerName, string $methodName, array $urlParams,
- DIContainer $container) {
+ DIContainer $container) {
$container['urlParams'] = $urlParams;
$controller = $container[$controllerName];
diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php
index f41b734a25b..8526a3dc1a1 100644
--- a/lib/private/AppFramework/Bootstrap/Coordinator.php
+++ b/lib/private/AppFramework/Bootstrap/Coordinator.php
@@ -30,20 +30,20 @@ declare(strict_types=1);
namespace OC\AppFramework\Bootstrap;
-use OCP\Diagnostics\IEventLogger;
-use function class_exists;
-use function class_implements;
-use function in_array;
-use OC_App;
use OC\Support\CrashReport\Registry;
+use OC_App;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\QueryException;
use OCP\Dashboard\IManager;
+use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IServerContainer;
use Psr\Log\LoggerInterface;
use Throwable;
+use function class_exists;
+use function class_implements;
+use function in_array;
class Coordinator {
/** @var IServerContainer */
diff --git a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
index 2ad410be26f..12801e62763 100644
--- a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
@@ -37,9 +37,9 @@ class EventListenerRegistration extends ServiceRegistration {
private $priority;
public function __construct(string $appId,
- string $event,
- string $service,
- int $priority) {
+ string $event,
+ string $service,
+ int $priority) {
parent::__construct($appId, $service);
$this->event = $event;
$this->priority = $priority;
diff --git a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
index b501a757abd..958f24cb600 100644
--- a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
@@ -36,8 +36,8 @@ final class ParameterRegistration extends ARegistration {
private $value;
public function __construct(string $appId,
- string $name,
- $value) {
+ string $name,
+ $value) {
parent::__construct($appId);
$this->name = $name;
$this->value = $value;
diff --git a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
index 36c5cae7db3..e4d75f75bc8 100644
--- a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
@@ -34,8 +34,8 @@ class PreviewProviderRegistration extends ServiceRegistration {
private $mimeTypeRegex;
public function __construct(string $appId,
- string $service,
- string $mimeTypeRegex) {
+ string $service,
+ string $mimeTypeRegex) {
parent::__construct($appId, $service);
$this->mimeTypeRegex = $mimeTypeRegex;
}
diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
index d4588527006..120ee7ea9fa 100644
--- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php
+++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
@@ -30,15 +30,6 @@ declare(strict_types=1);
namespace OC\AppFramework\Bootstrap;
use Closure;
-use OCP\Calendar\Resource\IBackend as IResourceBackend;
-use OCP\Calendar\Room\IBackend as IRoomBackend;
-use OCP\Collaboration\Reference\IReferenceProvider;
-use OCP\TextProcessing\IProvider as ITextProcessingProvider;
-use OCP\SpeechToText\ISpeechToTextProvider;
-use OCP\Talk\ITalkBackend;
-use OCP\Translation\ITranslationProvider;
-use RuntimeException;
-use function array_shift;
use OC\Support\CrashReport\Registry;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
@@ -46,7 +37,10 @@ use OCP\AppFramework\Middleware;
use OCP\AppFramework\Services\InitialStateProvider;
use OCP\Authentication\IAlternativeLogin;
use OCP\Calendar\ICalendarProvider;
+use OCP\Calendar\Resource\IBackend as IResourceBackend;
+use OCP\Calendar\Room\IBackend as IRoomBackend;
use OCP\Capabilities\ICapability;
+use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
use OCP\EventDispatcher\IEventDispatcher;
@@ -57,10 +51,16 @@ use OCP\Profile\ILinkAction;
use OCP\Search\IProvider;
use OCP\SetupCheck\ISetupCheck;
use OCP\Share\IPublicShareTemplateProvider;
+use OCP\SpeechToText\ISpeechToTextProvider;
use OCP\Support\CrashReport\IReporter;
+use OCP\Talk\ITalkBackend;
+use OCP\TextProcessing\IProvider as ITextProcessingProvider;
+use OCP\Translation\ITranslationProvider;
use OCP\UserMigration\IMigrator as IUserMigrator;
use Psr\Log\LoggerInterface;
+use RuntimeException;
use Throwable;
+use function array_shift;
class RegistrationContext {
/** @var ServiceRegistration<ICapability>[] */
@@ -138,6 +138,12 @@ class RegistrationContext {
/** @var ServiceRegistration<IReferenceProvider>[] */
private array $referenceProviders = [];
+ /** @var ServiceRegistration<\OCP\TextToImage\IProvider>[] */
+ private $textToImageProviders = [];
+
+
+
+
/** @var ParameterRegistration[] */
private $sensitiveMethods = [];
@@ -273,6 +279,13 @@ class RegistrationContext {
);
}
+ public function registerTextToImageProvider(string $providerClass): void {
+ $this->context->registerTextToImageProvider(
+ $this->appId,
+ $providerClass
+ );
+ }
+
public function registerTemplateProvider(string $providerClass): void {
$this->context->registerTemplateProvider(
$this->appId,
@@ -450,6 +463,10 @@ class RegistrationContext {
$this->textProcessingProviders[] = new ServiceRegistration($appId, $class);
}
+ public function registerTextToImageProvider(string $appId, string $class): void {
+ $this->textToImageProviders[] = new ServiceRegistration($appId, $class);
+ }
+
public function registerTemplateProvider(string $appId, string $class): void {
$this->templateProviders[] = new ServiceRegistration($appId, $class);
}
@@ -740,6 +757,13 @@ class RegistrationContext {
}
/**
+ * @return ServiceRegistration<\OCP\TextToImage\IProvider>[]
+ */
+ public function getTextToImageProviders(): array {
+ return $this->textToImageProviders;
+ }
+
+ /**
* @return ServiceRegistration<ICustomTemplateProvider>[]
*/
public function getTemplateProviders(): array {
diff --git a/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
index e2b115e0353..62c7169a7ee 100644
--- a/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
@@ -46,8 +46,8 @@ class ServiceAliasRegistration extends ARegistration {
* @paslm-param string|class-string $target
*/
public function __construct(string $appId,
- string $alias,
- string $target) {
+ string $alias,
+ string $target) {
parent::__construct($appId);
$this->alias = $alias;
$this->target = $target;
diff --git a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
index b6658e55239..9d166526d94 100644
--- a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
@@ -45,9 +45,9 @@ class ServiceFactoryRegistration extends ARegistration {
private $shared;
public function __construct(string $appId,
- string $alias,
- callable $target,
- bool $shared) {
+ string $alias,
+ callable $target,
+ bool $shared) {
parent::__construct($appId);
$this->name = $alias;
$this->factory = $target;
diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php
index 13b391eb287..6e946f2bfa3 100644
--- a/lib/private/AppFramework/Http/Dispatcher.php
+++ b/lib/private/AppFramework/Http/Dispatcher.php
@@ -38,6 +38,7 @@ use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\DB\ConnectionAdapter;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\ParameterOutOfRangeException;
use OCP\AppFramework\Http\Response;
use OCP\Diagnostics\IEventLogger;
use OCP\IConfig;
@@ -88,14 +89,14 @@ class Dispatcher {
* @param IEventLogger $eventLogger
*/
public function __construct(Http $protocol,
- MiddlewareDispatcher $middlewareDispatcher,
- ControllerMethodReflector $reflector,
- IRequest $request,
- IConfig $config,
- ConnectionAdapter $connection,
- LoggerInterface $logger,
- IEventLogger $eventLogger,
- ContainerInterface $appContainer) {
+ MiddlewareDispatcher $middlewareDispatcher,
+ ControllerMethodReflector $reflector,
+ IRequest $request,
+ IConfig $config,
+ ConnectionAdapter $connection,
+ LoggerInterface $logger,
+ IEventLogger $eventLogger,
+ ContainerInterface $appContainer) {
$this->protocol = $protocol;
$this->middlewareDispatcher = $middlewareDispatcher;
$this->reflector = $reflector;
@@ -197,7 +198,7 @@ class Dispatcher {
private function executeController(Controller $controller, string $methodName): Response {
$arguments = [];
- // valid types that will be casted
+ // valid types that will be cast
$types = ['int', 'integer', 'bool', 'boolean', 'float', 'double'];
foreach ($this->reflector->getParameters() as $param => $default) {
@@ -219,6 +220,7 @@ class Dispatcher {
$value = false;
} elseif ($value !== null && \in_array($type, $types, true)) {
settype($value, $type);
+ $this->ensureParameterValueSatisfiesRange($param, $value);
} elseif ($value === null && $type !== null && $this->appContainer->has($type)) {
$value = $this->appContainer->get($type);
}
@@ -250,4 +252,22 @@ class Dispatcher {
return $response;
}
+
+ /**
+ * @psalm-param mixed $value
+ * @throws ParameterOutOfRangeException
+ */
+ private function ensureParameterValueSatisfiesRange(string $param, $value): void {
+ $rangeInfo = $this->reflector->getRange($param);
+ if ($rangeInfo) {
+ if ($value < $rangeInfo['min'] || $value > $rangeInfo['max']) {
+ throw new ParameterOutOfRangeException(
+ $param,
+ $value,
+ $rangeInfo['min'],
+ $rangeInfo['max'],
+ );
+ }
+ }
+ }
}
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
index 26a76e0da27..b09737a6fc6 100644
--- a/lib/private/AppFramework/Http/Request.php
+++ b/lib/private/AppFramework/Http/Request.php
@@ -118,10 +118,10 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* @see https://www.php.net/manual/en/reserved.variables.php
*/
public function __construct(array $vars,
- IRequestId $requestId,
- IConfig $config,
- CsrfTokenManager $csrfTokenManager = null,
- string $stream = 'php://input') {
+ IRequestId $requestId,
+ IConfig $config,
+ CsrfTokenManager $csrfTokenManager = null,
+ string $stream = 'php://input') {
$this->inputStream = $stream;
$this->items['params'] = [];
$this->requestId = $requestId;
@@ -593,9 +593,11 @@ class Request implements \ArrayAccess, \Countable, IRequest {
// only have one default, so we cannot ship an insecure product out of the box
]);
- foreach ($forwardedForHeaders as $header) {
+ // Read the x-forwarded-for headers and values in reverse order as per
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address
+ foreach (array_reverse($forwardedForHeaders) as $header) {
if (isset($this->server[$header])) {
- foreach (explode(',', $this->server[$header]) as $IP) {
+ foreach (array_reverse(explode(',', $this->server[$header])) as $IP) {
$IP = trim($IP);
// remove brackets from IPv6 addresses
@@ -603,6 +605,10 @@ class Request implements \ArrayAccess, \Countable, IRequest {
$IP = substr($IP, 1, -1);
}
+ if ($this->isTrustedProxy($trustedProxies, $IP)) {
+ continue;
+ }
+
if (filter_var($IP, FILTER_VALIDATE_IP) !== false) {
return $IP;
}
diff --git a/lib/private/AppFramework/Http/RequestId.php b/lib/private/AppFramework/Http/RequestId.php
index 70032873a75..a6b24c0a2ff 100644
--- a/lib/private/AppFramework/Http/RequestId.php
+++ b/lib/private/AppFramework/Http/RequestId.php
@@ -31,7 +31,7 @@ class RequestId implements IRequestId {
protected string $requestId;
public function __construct(string $uniqueId,
- ISecureRandom $secureRandom) {
+ ISecureRandom $secureRandom) {
$this->requestId = $uniqueId;
$this->secureRandom = $secureRandom;
}
diff --git a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
index 35eb0098eed..e129f70aef6 100644
--- a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
+++ b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
@@ -40,15 +40,15 @@ use OCP\AppFramework\Middleware;
*/
class MiddlewareDispatcher {
/**
- * @var array array containing all the middlewares
+ * @var Middleware[] array containing all the middlewares
*/
- private $middlewares;
+ private array $middlewares;
/**
* @var int counter which tells us what middleware was executed once an
* exception occurs
*/
- private $middlewareCounter;
+ private int $middlewareCounter;
/**
@@ -64,14 +64,14 @@ class MiddlewareDispatcher {
* Adds a new middleware
* @param Middleware $middleWare the middleware which will be added
*/
- public function registerMiddleware(Middleware $middleWare) {
+ public function registerMiddleware(Middleware $middleWare): void {
$this->middlewares[] = $middleWare;
}
/**
* returns an array with all middleware elements
- * @return array the middlewares
+ * @return Middleware[] the middlewares
*/
public function getMiddlewares(): array {
return $this->middlewares;
@@ -86,7 +86,7 @@ class MiddlewareDispatcher {
* @param string $methodName the name of the method that will be called on
* the controller
*/
- public function beforeController(Controller $controller, string $methodName) {
+ public function beforeController(Controller $controller, string $methodName): void {
// we need to count so that we know which middlewares we have to ask in
// case there is an exception
$middlewareCount = \count($this->middlewares);
diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
index f0d6ece8a93..fef9632487e 100644
--- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
@@ -59,9 +59,9 @@ class CORSMiddleware extends Middleware {
private $throttler;
public function __construct(IRequest $request,
- ControllerMethodReflector $reflector,
- Session $session,
- IThrottler $throttler) {
+ ControllerMethodReflector $reflector,
+ Session $session,
+ IThrottler $throttler) {
$this->request = $request;
$this->reflector = $reflector;
$this->session = $session;
diff --git a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
index ae0dc1f134e..60a7cef8fa1 100644
--- a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
@@ -44,8 +44,8 @@ class CSPMiddleware extends Middleware {
private $csrfTokenManager;
public function __construct(ContentSecurityPolicyManager $policyManager,
- ContentSecurityPolicyNonceManager $cspNonceManager,
- CsrfTokenManager $csrfTokenManager) {
+ ContentSecurityPolicyNonceManager $cspNonceManager,
+ CsrfTokenManager $csrfTokenManager) {
$this->contentSecurityPolicyManager = $policyManager;
$this->cspNonceManager = $cspNonceManager;
$this->csrfTokenManager = $csrfTokenManager;
diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
index a72a7a40016..351f47ea924 100644
--- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
@@ -55,9 +55,9 @@ class PasswordConfirmationMiddleware extends Middleware {
* @param ITimeFactory $timeFactory
*/
public function __construct(ControllerMethodReflector $reflector,
- ISession $session,
- IUserSession $userSession,
- ITimeFactory $timeFactory) {
+ ISession $session,
+ IUserSession $userSession,
+ ITimeFactory $timeFactory) {
$this->reflector = $reflector;
$this->session = $session;
$this->userSession = $userSession;
diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
index e6d35dc66f2..870efdd44fa 100644
--- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
@@ -38,7 +38,7 @@ class SameSiteCookieMiddleware extends Middleware {
private $reflector;
public function __construct(Request $request,
- ControllerMethodReflector $reflector) {
+ ControllerMethodReflector $reflector) {
$this->request = $request;
$this->reflector = $reflector;
}
diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
index db6c7a02c77..a97876fd9ab 100644
--- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
@@ -104,18 +104,18 @@ class SecurityMiddleware extends Middleware {
private $userSession;
public function __construct(IRequest $request,
- ControllerMethodReflector $reflector,
- INavigationManager $navigationManager,
- IURLGenerator $urlGenerator,
- LoggerInterface $logger,
- string $appName,
- bool $isLoggedIn,
- bool $isAdminUser,
- bool $isSubAdmin,
- IAppManager $appManager,
- IL10N $l10n,
- AuthorizedGroupMapper $mapper,
- IUserSession $userSession
+ ControllerMethodReflector $reflector,
+ INavigationManager $navigationManager,
+ IURLGenerator $urlGenerator,
+ LoggerInterface $logger,
+ string $appName,
+ bool $isLoggedIn,
+ bool $isAdminUser,
+ bool $isSubAdmin,
+ IAppManager $appManager,
+ IL10N $l10n,
+ AuthorizedGroupMapper $mapper,
+ IUserSession $userSession
) {
$this->navigationManager = $navigationManager;
$this->request = $request;
diff --git a/lib/private/AppFramework/Middleware/SessionMiddleware.php b/lib/private/AppFramework/Middleware/SessionMiddleware.php
index 39f85915901..0acdcf8b7ef 100644
--- a/lib/private/AppFramework/Middleware/SessionMiddleware.php
+++ b/lib/private/AppFramework/Middleware/SessionMiddleware.php
@@ -44,7 +44,7 @@ class SessionMiddleware extends Middleware {
private $session;
public function __construct(ControllerMethodReflector $reflector,
- ISession $session) {
+ ISession $session) {
$this->reflector = $reflector;
$this->session = $session;
}
diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php
index 123b73d302c..3cfe8177ae7 100644
--- a/lib/private/AppFramework/OCS/BaseResponse.php
+++ b/lib/private/AppFramework/OCS/BaseResponse.php
@@ -64,10 +64,10 @@ abstract class BaseResponse extends Response {
* @param int|null $itemsPerPage
*/
public function __construct(DataResponse $dataResponse,
- $format = 'xml',
- $statusMessage = null,
- $itemsCount = null,
- $itemsPerPage = null) {
+ $format = 'xml',
+ $statusMessage = null,
+ $itemsCount = null,
+ $itemsPerPage = null) {
parent::__construct();
$this->format = $format;
diff --git a/lib/private/AppFramework/ScopedPsrLogger.php b/lib/private/AppFramework/ScopedPsrLogger.php
index 4ed91cdb6c0..1cb58da11ef 100644
--- a/lib/private/AppFramework/ScopedPsrLogger.php
+++ b/lib/private/AppFramework/ScopedPsrLogger.php
@@ -37,7 +37,7 @@ class ScopedPsrLogger implements LoggerInterface {
private $appId;
public function __construct(LoggerInterface $inner,
- string $appId) {
+ string $appId) {
$this->inner = $inner;
$this->appId = $appId;
}
diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
index b76b3c33c42..5a1ed0fd6ee 100644
--- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php
+++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
@@ -42,6 +42,7 @@ class ControllerMethodReflector implements IControllerMethodReflector {
public $annotations = [];
private $types = [];
private $parameters = [];
+ private array $ranges = [];
/**
* @param object $object an object or classname
@@ -54,26 +55,38 @@ class ControllerMethodReflector implements IControllerMethodReflector {
if ($docs !== false) {
// extract everything prefixed by @ and first letter uppercase
preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)((?P<parameter>.*))?$/m', $docs, $matches);
- foreach ($matches['annotation'] as $key => $annontation) {
- $annontation = strtolower($annontation);
+ foreach ($matches['annotation'] as $key => $annotation) {
+ $annotation = strtolower($annotation);
$annotationValue = $matches['parameter'][$key];
if (isset($annotationValue[0]) && $annotationValue[0] === '(' && $annotationValue[\strlen($annotationValue) - 1] === ')') {
$cutString = substr($annotationValue, 1, -1);
$cutString = str_replace(' ', '', $cutString);
- $splittedArray = explode(',', $cutString);
- foreach ($splittedArray as $annotationValues) {
+ $splitArray = explode(',', $cutString);
+ foreach ($splitArray as $annotationValues) {
[$key, $value] = explode('=', $annotationValues);
- $this->annotations[$annontation][$key] = $value;
+ $this->annotations[$annotation][$key] = $value;
}
continue;
}
- $this->annotations[$annontation] = [$annotationValue];
+ $this->annotations[$annotation] = [$annotationValue];
}
// extract type parameter information
preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches);
$this->types = array_combine($matches['var'], $matches['type']);
+ preg_match_all('/@psalm-param\h+(?P<type>\w+)<(?P<rangeMin>(-?\d+|min)),\h*(?P<rangeMax>(-?\d+|max))>\h+\$(?P<var>\w+)/', $docs, $matches);
+ foreach ($matches['var'] as $index => $varName) {
+ if ($matches['type'][$index] !== 'int') {
+ // only int ranges are possible at the moment
+ // @see https://psalm.dev/docs/annotating_code/type_syntax/scalar_types
+ continue;
+ }
+ $this->ranges[$varName] = [
+ 'min' => $matches['rangeMin'][$index] === 'min' ? PHP_INT_MIN : (int)$matches['rangeMin'][$index],
+ 'max' => $matches['rangeMax'][$index] === 'max' ? PHP_INT_MAX : (int)$matches['rangeMax'][$index],
+ ];
+ }
}
foreach ($reflection->getParameters() as $param) {
@@ -106,6 +119,14 @@ class ControllerMethodReflector implements IControllerMethodReflector {
return null;
}
+ public function getRange(string $parameter): ?array {
+ if (array_key_exists($parameter, $this->ranges)) {
+ return $this->ranges[$parameter];
+ }
+
+ return null;
+ }
+
/**
* @return array the arguments of the method with key => default value
*/
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index 7aa5cb83926..83aed4381b3 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -37,8 +37,8 @@ use Pimple\Container;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
-use ReflectionParameter;
use ReflectionNamedType;
+use ReflectionParameter;
use function class_exists;
/**
@@ -105,6 +105,11 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
try {
return $this->query($resolveName);
} catch (QueryException $e2) {
+ // Pass null if typed and nullable
+ if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) {
+ return null;
+ }
+
// don't lose the error we got while trying to query by type
throw new QueryException($e->getMessage(), (int) $e->getCode(), $e);
}
diff --git a/lib/private/AppScriptSort.php b/lib/private/AppScriptSort.php
index c42d02d485d..2e36034d04f 100644
--- a/lib/private/AppScriptSort.php
+++ b/lib/private/AppScriptSort.php
@@ -46,10 +46,10 @@ class AppScriptSort {
* @param array $sortedScriptDeps
*/
private function topSortVisit(
- AppScriptDependency $app,
- array &$parents,
- array &$scriptDeps,
- array &$sortedScriptDeps): void {
+ AppScriptDependency $app,
+ array &$parents,
+ array &$scriptDeps,
+ array &$sortedScriptDeps): void {
// Detect and log circular dependencies
if (isset($parents[$app->getId()])) {
$this->logger->error('Circular dependency in app scripts at app ' . $app->getId());
diff --git a/lib/private/Archive/TAR.php b/lib/private/Archive/TAR.php
index 9dc906384e0..a6140e44eb6 100644
--- a/lib/private/Archive/TAR.php
+++ b/lib/private/Archive/TAR.php
@@ -197,7 +197,7 @@ class TAR extends Archive {
if ($pos = strpos($result, '/')) {
$result = substr($result, 0, $pos + 1);
}
- if (array_search($result, $folderContent) === false) {
+ if (!in_array($result, $folderContent)) {
$folderContent[] = $result;
}
}
@@ -269,7 +269,7 @@ class TAR extends Archive {
*/
public function fileExists(string $path): bool {
$files = $this->getFiles();
- if ((array_search($path, $files) !== false) or (array_search($path . '/', $files) !== false)) {
+ if ((in_array($path, $files)) or (in_array($path . '/', $files))) {
return true;
} else {
$folderPath = rtrim($path, '/') . '/';
diff --git a/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php
index edebb2a2641..3e8348f075a 100644
--- a/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php
+++ b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php
@@ -46,7 +46,7 @@ class RemoteWipeActivityListener implements IEventListener {
private $logger;
public function __construct(IActvityManager $activityManager,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->activityManager = $activityManager;
$this->logger = $logger;
}
diff --git a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php
index cba2b183589..fb3f771d1e4 100644
--- a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php
+++ b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php
@@ -57,9 +57,9 @@ class RemoteWipeEmailListener implements IEventListener {
private $logger;
public function __construct(IMailer $mailer,
- IUserManager $userManager,
- IL10nFactory $l10nFactory,
- LoggerInterface $logger) {
+ IUserManager $userManager,
+ IL10nFactory $l10nFactory,
+ LoggerInterface $logger) {
$this->mailer = $mailer;
$this->userManager = $userManager;
$this->l10n = $l10nFactory->get('core');
diff --git a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
index 81feab32746..37732ecf5f2 100644
--- a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
+++ b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
@@ -45,7 +45,7 @@ class RemoteWipeNotificationsListener implements IEventListener {
private $timeFactory;
public function __construct(INotificationManager $notificationManager,
- ITimeFactory $timeFactory) {
+ ITimeFactory $timeFactory) {
$this->notificationManager = $notificationManager;
$this->timeFactory = $timeFactory;
}
diff --git a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php
index a09a08568d5..f4f08a50add 100644
--- a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php
+++ b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php
@@ -44,7 +44,7 @@ class UserDeletedTokenCleanupListener implements IEventListener {
private $logger;
public function __construct(Manager $manager,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->manager = $manager;
$this->logger = $logger;
}
diff --git a/lib/private/Authentication/Login/Chain.php b/lib/private/Authentication/Login/Chain.php
index 3c3179472c4..60ecd004388 100644
--- a/lib/private/Authentication/Login/Chain.php
+++ b/lib/private/Authentication/Login/Chain.php
@@ -63,17 +63,17 @@ class Chain {
private $finishRememberedLoginCommand;
public function __construct(PreLoginHookCommand $preLoginHookCommand,
- UserDisabledCheckCommand $userDisabledCheckCommand,
- UidLoginCommand $uidLoginCommand,
- EmailLoginCommand $emailLoginCommand,
- LoggedInCheckCommand $loggedInCheckCommand,
- CompleteLoginCommand $completeLoginCommand,
- CreateSessionTokenCommand $createSessionTokenCommand,
- ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
- UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
- SetUserTimezoneCommand $setUserTimezoneCommand,
- TwoFactorCommand $twoFactorCommand,
- FinishRememberedLoginCommand $finishRememberedLoginCommand
+ UserDisabledCheckCommand $userDisabledCheckCommand,
+ UidLoginCommand $uidLoginCommand,
+ EmailLoginCommand $emailLoginCommand,
+ LoggedInCheckCommand $loggedInCheckCommand,
+ CompleteLoginCommand $completeLoginCommand,
+ CreateSessionTokenCommand $createSessionTokenCommand,
+ ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
+ UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
+ SetUserTimezoneCommand $setUserTimezoneCommand,
+ TwoFactorCommand $twoFactorCommand,
+ FinishRememberedLoginCommand $finishRememberedLoginCommand
) {
$this->preLoginHookCommand = $preLoginHookCommand;
$this->userDisabledCheckCommand = $userDisabledCheckCommand;
diff --git a/lib/private/Authentication/Login/CreateSessionTokenCommand.php b/lib/private/Authentication/Login/CreateSessionTokenCommand.php
index ba237dfbf20..41616e6dad3 100644
--- a/lib/private/Authentication/Login/CreateSessionTokenCommand.php
+++ b/lib/private/Authentication/Login/CreateSessionTokenCommand.php
@@ -39,7 +39,7 @@ class CreateSessionTokenCommand extends ALoginCommand {
private $userSession;
public function __construct(IConfig $config,
- Session $userSession) {
+ Session $userSession) {
$this->config = $config;
$this->userSession = $userSession;
}
diff --git a/lib/private/Authentication/Login/LoggedInCheckCommand.php b/lib/private/Authentication/Login/LoggedInCheckCommand.php
index dc1a4d2d883..6b241d79746 100644
--- a/lib/private/Authentication/Login/LoggedInCheckCommand.php
+++ b/lib/private/Authentication/Login/LoggedInCheckCommand.php
@@ -39,7 +39,7 @@ class LoggedInCheckCommand extends ALoginCommand {
private $dispatcher;
public function __construct(LoggerInterface $logger,
- IEventDispatcher $dispatcher) {
+ IEventDispatcher $dispatcher) {
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
diff --git a/lib/private/Authentication/Login/LoginData.php b/lib/private/Authentication/Login/LoginData.php
index 240a1dc6476..0ce11cf70fc 100644
--- a/lib/private/Authentication/Login/LoginData.php
+++ b/lib/private/Authentication/Login/LoginData.php
@@ -55,11 +55,11 @@ class LoginData {
private $rememberLogin = true;
public function __construct(IRequest $request,
- string $username,
- ?string $password,
- string $redirectUrl = null,
- string $timeZone = '',
- string $timeZoneOffset = '') {
+ string $username,
+ ?string $password,
+ string $redirectUrl = null,
+ string $timeZone = '',
+ string $timeZoneOffset = '') {
$this->request = $request;
$this->username = $username;
$this->password = $password;
diff --git a/lib/private/Authentication/Login/LoginResult.php b/lib/private/Authentication/Login/LoginResult.php
index dec012c2fc9..18820d98a47 100644
--- a/lib/private/Authentication/Login/LoginResult.php
+++ b/lib/private/Authentication/Login/LoginResult.php
@@ -25,6 +25,8 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Login;
+use OC\Core\Controller\LoginController;
+
class LoginResult {
/** @var bool */
private $success;
@@ -59,6 +61,9 @@ class LoginResult {
return $result;
}
+ /**
+ * @param LoginController::LOGIN_MSG_*|null $msg
+ */
public static function failure(LoginData $data, string $msg = null): LoginResult {
$result = new static(false, $data);
if ($msg !== null) {
diff --git a/lib/private/Authentication/Login/SetUserTimezoneCommand.php b/lib/private/Authentication/Login/SetUserTimezoneCommand.php
index f68fce1771e..881e1c451a9 100644
--- a/lib/private/Authentication/Login/SetUserTimezoneCommand.php
+++ b/lib/private/Authentication/Login/SetUserTimezoneCommand.php
@@ -36,7 +36,7 @@ class SetUserTimezoneCommand extends ALoginCommand {
private $session;
public function __construct(IConfig $config,
- ISession $session) {
+ ISession $session) {
$this->config = $config;
$this->session = $session;
}
diff --git a/lib/private/Authentication/Login/TwoFactorCommand.php b/lib/private/Authentication/Login/TwoFactorCommand.php
index 256d88ffa81..aa5a2ff96f4 100644
--- a/lib/private/Authentication/Login/TwoFactorCommand.php
+++ b/lib/private/Authentication/Login/TwoFactorCommand.php
@@ -26,12 +26,12 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Login;
-use function array_pop;
-use function count;
use OC\Authentication\TwoFactorAuth\Manager;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\IURLGenerator;
+use function array_pop;
+use function count;
class TwoFactorCommand extends ALoginCommand {
/** @var Manager */
@@ -44,8 +44,8 @@ class TwoFactorCommand extends ALoginCommand {
private $urlGenerator;
public function __construct(Manager $twoFactorManager,
- MandatoryTwoFactor $mandatoryTwoFactor,
- IURLGenerator $urlGenerator) {
+ MandatoryTwoFactor $mandatoryTwoFactor,
+ IURLGenerator $urlGenerator) {
$this->twoFactorManager = $twoFactorManager;
$this->mandatoryTwoFactor = $mandatoryTwoFactor;
$this->urlGenerator = $urlGenerator;
diff --git a/lib/private/Authentication/Login/UserDisabledCheckCommand.php b/lib/private/Authentication/Login/UserDisabledCheckCommand.php
index 7cf4c7235ec..8354457b56a 100644
--- a/lib/private/Authentication/Login/UserDisabledCheckCommand.php
+++ b/lib/private/Authentication/Login/UserDisabledCheckCommand.php
@@ -38,7 +38,7 @@ class UserDisabledCheckCommand extends ALoginCommand {
private $logger;
public function __construct(IUserManager $userManager,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->userManager = $userManager;
$this->logger = $logger;
}
diff --git a/lib/private/Authentication/Login/WebAuthnChain.php b/lib/private/Authentication/Login/WebAuthnChain.php
index f3ebc313a44..d0fcf691d46 100644
--- a/lib/private/Authentication/Login/WebAuthnChain.php
+++ b/lib/private/Authentication/Login/WebAuthnChain.php
@@ -57,15 +57,15 @@ class WebAuthnChain {
private $webAuthnLoginCommand;
public function __construct(UserDisabledCheckCommand $userDisabledCheckCommand,
- WebAuthnLoginCommand $webAuthnLoginCommand,
- LoggedInCheckCommand $loggedInCheckCommand,
- CompleteLoginCommand $completeLoginCommand,
- CreateSessionTokenCommand $createSessionTokenCommand,
- ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
- UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
- SetUserTimezoneCommand $setUserTimezoneCommand,
- TwoFactorCommand $twoFactorCommand,
- FinishRememberedLoginCommand $finishRememberedLoginCommand
+ WebAuthnLoginCommand $webAuthnLoginCommand,
+ LoggedInCheckCommand $loggedInCheckCommand,
+ CompleteLoginCommand $completeLoginCommand,
+ CreateSessionTokenCommand $createSessionTokenCommand,
+ ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
+ UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
+ SetUserTimezoneCommand $setUserTimezoneCommand,
+ TwoFactorCommand $twoFactorCommand,
+ FinishRememberedLoginCommand $finishRememberedLoginCommand
) {
$this->userDisabledCheckCommand = $userDisabledCheckCommand;
$this->webAuthnLoginCommand = $webAuthnLoginCommand;
diff --git a/lib/private/Authentication/LoginCredentials/Store.php b/lib/private/Authentication/LoginCredentials/Store.php
index 3a09e983ee8..e1e29946446 100644
--- a/lib/private/Authentication/LoginCredentials/Store.php
+++ b/lib/private/Authentication/LoginCredentials/Store.php
@@ -48,8 +48,8 @@ class Store implements IStore {
private $tokenProvider;
public function __construct(ISession $session,
- LoggerInterface $logger,
- IProvider $tokenProvider = null) {
+ LoggerInterface $logger,
+ IProvider $tokenProvider = null) {
$this->session = $session;
$this->logger = $logger;
$this->tokenProvider = $tokenProvider;
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index a12d3ba34d9..4af5e2b25c3 100644
--- a/lib/private/Authentication/Token/IProvider.php
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -49,12 +49,12 @@ interface IProvider {
* @throws \RuntimeException when OpenSSL reports a problem
*/
public function generateToken(string $token,
- string $uid,
- string $loginName,
- ?string $password,
- string $name,
- int $type = IToken::TEMPORARY_TOKEN,
- int $remember = IToken::DO_NOT_REMEMBER): IToken;
+ string $uid,
+ string $loginName,
+ ?string $password,
+ string $name,
+ int $type = IToken::TEMPORARY_TOKEN,
+ int $remember = IToken::DO_NOT_REMEMBER): IToken;
/**
* Get a token by token id
diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php
index 6a1c7d4c1e7..18ec687cac2 100644
--- a/lib/private/Authentication/Token/Manager.php
+++ b/lib/private/Authentication/Token/Manager.php
@@ -55,12 +55,12 @@ class Manager implements IProvider, OCPIProvider {
* @return IToken
*/
public function generateToken(string $token,
- string $uid,
- string $loginName,
- $password,
- string $name,
- int $type = IToken::TEMPORARY_TOKEN,
- int $remember = IToken::DO_NOT_REMEMBER): IToken {
+ string $uid,
+ string $loginName,
+ $password,
+ string $name,
+ int $type = IToken::TEMPORARY_TOKEN,
+ int $remember = IToken::DO_NOT_REMEMBER): IToken {
if (mb_strlen($name) > 128) {
$name = mb_substr($name, 0, 120) . '…';
}
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 3fb11611076..a88194726ae 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -31,13 +31,13 @@ namespace OC\Authentication\Token;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
-use OC\Authentication\Exceptions\TokenPasswordExpiredException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
+use OC\Authentication\Exceptions\TokenPasswordExpiredException;
use OC\Authentication\Exceptions\WipeTokenException;
-use OCP\AppFramework\Db\TTransactional;
-use OCP\Cache\CappedMemoryCache;
use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\TTransactional;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Cache\CappedMemoryCache;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUserManager;
@@ -73,12 +73,12 @@ class PublicKeyTokenProvider implements IProvider {
private IHasher $hasher;
public function __construct(PublicKeyTokenMapper $mapper,
- ICrypto $crypto,
- IConfig $config,
- IDBConnection $db,
- LoggerInterface $logger,
- ITimeFactory $time,
- IHasher $hasher) {
+ ICrypto $crypto,
+ IConfig $config,
+ IDBConnection $db,
+ LoggerInterface $logger,
+ ITimeFactory $time,
+ IHasher $hasher) {
$this->mapper = $mapper;
$this->crypto = $crypto;
$this->config = $config;
@@ -94,12 +94,12 @@ class PublicKeyTokenProvider implements IProvider {
* {@inheritDoc}
*/
public function generateToken(string $token,
- string $uid,
- string $loginName,
- ?string $password,
- string $name,
- int $type = IToken::TEMPORARY_TOKEN,
- int $remember = IToken::DO_NOT_REMEMBER): IToken {
+ string $uid,
+ string $loginName,
+ ?string $password,
+ string $name,
+ int $type = IToken::TEMPORARY_TOKEN,
+ int $remember = IToken::DO_NOT_REMEMBER): IToken {
if (strlen($token) < self::TOKEN_MIN_LENGTH) {
$exception = new InvalidTokenException('Token is too short, minimum of ' . self::TOKEN_MIN_LENGTH . ' characters is required, ' . strlen($token) . ' characters given');
$this->logger->error('Invalid token provided when generating new token', ['exception' => $exception]);
@@ -425,12 +425,12 @@ class PublicKeyTokenProvider implements IProvider {
* @throws \RuntimeException when OpenSSL reports a problem
*/
private function newToken(string $token,
- string $uid,
- string $loginName,
- $password,
- string $name,
- int $type,
- int $remember): PublicKeyToken {
+ string $uid,
+ string $loginName,
+ $password,
+ string $name,
+ int $type,
+ int $remember): PublicKeyToken {
$dbToken = new PublicKeyToken();
$dbToken->setUid($uid);
$dbToken->setLoginName($loginName);
diff --git a/lib/private/Authentication/Token/RemoteWipe.php b/lib/private/Authentication/Token/RemoteWipe.php
index 5fd01cfbe87..e4882f678d9 100644
--- a/lib/private/Authentication/Token/RemoteWipe.php
+++ b/lib/private/Authentication/Token/RemoteWipe.php
@@ -27,14 +27,14 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Token;
-use Psr\Log\LoggerInterface;
-use function array_filter;
use OC\Authentication\Events\RemoteWipeFinished;
use OC\Authentication\Events\RemoteWipeStarted;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUser;
+use Psr\Log\LoggerInterface;
+use function array_filter;
class RemoteWipe {
/** @var IProvider */
@@ -47,8 +47,8 @@ class RemoteWipe {
private $logger;
public function __construct(IProvider $tokenProvider,
- IEventDispatcher $eventDispatcher,
- LoggerInterface $logger) {
+ IEventDispatcher $eventDispatcher,
+ LoggerInterface $logger) {
$this->tokenProvider = $tokenProvider;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
diff --git a/lib/private/Authentication/TwoFactorAuth/EnforcementState.php b/lib/private/Authentication/TwoFactorAuth/EnforcementState.php
index b95128c1e0f..91f133d6ad0 100644
--- a/lib/private/Authentication/TwoFactorAuth/EnforcementState.php
+++ b/lib/private/Authentication/TwoFactorAuth/EnforcementState.php
@@ -45,8 +45,8 @@ class EnforcementState implements JsonSerializable {
* @param string[] $excludedGroups
*/
public function __construct(bool $enforced,
- array $enforcedGroups = [],
- array $excludedGroups = []) {
+ array $enforcedGroups = [],
+ array $excludedGroups = []) {
$this->enforced = $enforced;
$this->enforcedGroups = $enforcedGroups;
$this->excludedGroups = $excludedGroups;
diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php
index ff0c33445a2..b0bb73c3115 100644
--- a/lib/private/Authentication/TwoFactorAuth/Manager.php
+++ b/lib/private/Authentication/TwoFactorAuth/Manager.php
@@ -89,15 +89,15 @@ class Manager {
private $userIsTwoFactorAuthenticated = [];
public function __construct(ProviderLoader $providerLoader,
- IRegistry $providerRegistry,
- MandatoryTwoFactor $mandatoryTwoFactor,
- ISession $session,
- IConfig $config,
- IManager $activityManager,
- LoggerInterface $logger,
- TokenProvider $tokenProvider,
- ITimeFactory $timeFactory,
- IEventDispatcher $eventDispatcher) {
+ IRegistry $providerRegistry,
+ MandatoryTwoFactor $mandatoryTwoFactor,
+ ISession $session,
+ IConfig $config,
+ IManager $activityManager,
+ LoggerInterface $logger,
+ TokenProvider $tokenProvider,
+ ITimeFactory $timeFactory,
+ IEventDispatcher $eventDispatcher) {
$this->providerLoader = $providerLoader;
$this->providerRegistry = $providerRegistry;
$this->mandatoryTwoFactor = $mandatoryTwoFactor;
diff --git a/lib/private/Authentication/TwoFactorAuth/ProviderSet.php b/lib/private/Authentication/TwoFactorAuth/ProviderSet.php
index af270fb83c8..4d39fd82bc6 100644
--- a/lib/private/Authentication/TwoFactorAuth/ProviderSet.php
+++ b/lib/private/Authentication/TwoFactorAuth/ProviderSet.php
@@ -25,9 +25,9 @@ declare(strict_types=1);
*/
namespace OC\Authentication\TwoFactorAuth;
-use function array_filter;
use OCA\TwoFactorBackupCodes\Provider\BackupCodesProvider;
use OCP\Authentication\TwoFactorAuth\IProvider;
+use function array_filter;
/**
* Contains all two-factor provider information for the two-factor login challenge
diff --git a/lib/private/Authentication/TwoFactorAuth/Registry.php b/lib/private/Authentication/TwoFactorAuth/Registry.php
index 482c025e144..db772265583 100644
--- a/lib/private/Authentication/TwoFactorAuth/Registry.php
+++ b/lib/private/Authentication/TwoFactorAuth/Registry.php
@@ -45,7 +45,7 @@ class Registry implements IRegistry {
private $dispatcher;
public function __construct(ProviderUserAssignmentDao $assignmentDao,
- IEventDispatcher $dispatcher) {
+ IEventDispatcher $dispatcher) {
$this->assignmentDao = $assignmentDao;
$this->dispatcher = $dispatcher;
}
diff --git a/lib/private/Authentication/WebAuthn/Manager.php b/lib/private/Authentication/WebAuthn/Manager.php
index 744a3fa354a..5a97a573b99 100644
--- a/lib/private/Authentication/WebAuthn/Manager.php
+++ b/lib/private/Authentication/WebAuthn/Manager.php
@@ -91,7 +91,7 @@ class Manager {
$user->getUID(), //Name
$user->getUID(), //ID
$user->getDisplayName() //Display name
-// 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon
+ // 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon
);
$challenge = random_bytes(32);
diff --git a/lib/private/Avatar/GuestAvatar.php b/lib/private/Avatar/GuestAvatar.php
index 26614cf6cfa..106e159d192 100644
--- a/lib/private/Avatar/GuestAvatar.php
+++ b/lib/private/Avatar/GuestAvatar.php
@@ -26,8 +26,8 @@ declare(strict_types=1);
*/
namespace OC\Avatar;
-use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\InMemoryFile;
+use OCP\Files\SimpleFS\ISimpleFile;
use Psr\Log\LoggerInterface;
/**
diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php
index 2b42e2ff1ee..3ec2e0af1b3 100644
--- a/lib/private/BackgroundJob/JobList.php
+++ b/lib/private/BackgroundJob/JobList.php
@@ -88,6 +88,7 @@ class JobList implements IJobList {
$query->update('jobs')
->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
->set('last_checked', $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT))
+ ->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('class', $query->createNamedParameter($class)))
->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson))));
}
@@ -413,7 +414,7 @@ class JobList implements IJobList {
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('jobs')
- ->where($query->expr()->neq('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
+ ->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 6 * 3600, IQueryBuilder::PARAM_INT)))
->setMaxResults(1);
if ($className !== null) {
diff --git a/lib/private/BinaryFinder.php b/lib/private/BinaryFinder.php
index a7ef55237db..17427e92619 100644
--- a/lib/private/BinaryFinder.php
+++ b/lib/private/BinaryFinder.php
@@ -22,9 +22,9 @@ declare(strict_types = 1);
namespace OC;
+use OCP\IBinaryFinder;
use OCP\ICache;
use OCP\ICacheFactory;
-use OCP\IBinaryFinder;
use Symfony\Component\Process\ExecutableFinder;
/**
diff --git a/lib/private/CapabilitiesManager.php b/lib/private/CapabilitiesManager.php
index 7885a98869d..6b34b50cb98 100644
--- a/lib/private/CapabilitiesManager.php
+++ b/lib/private/CapabilitiesManager.php
@@ -30,8 +30,8 @@ namespace OC;
use OCP\AppFramework\QueryException;
use OCP\Capabilities\ICapability;
-use OCP\Capabilities\IPublicCapability;
use OCP\Capabilities\IInitialStateExcludedCapability;
+use OCP\Capabilities\IPublicCapability;
use Psr\Log\LoggerInterface;
class CapabilitiesManager {
diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php
index cbdd84efbb3..37ebf2fb129 100644
--- a/lib/private/Collaboration/Collaborators/MailPlugin.php
+++ b/lib/private/Collaboration/Collaborators/MailPlugin.php
@@ -37,8 +37,8 @@ use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
-use OCP\Share\IShare;
use OCP\Mail\IMailer;
+use OCP\Share\IShare;
class MailPlugin implements ISearchPlugin {
protected bool $shareWithGroupOnly;
diff --git a/lib/private/Command/AsyncBus.php b/lib/private/Command/AsyncBus.php
index ec6fbc91f68..c65e6cad78e 100644
--- a/lib/private/Command/AsyncBus.php
+++ b/lib/private/Command/AsyncBus.php
@@ -82,7 +82,7 @@ abstract class AsyncBus implements IBus {
private function canRunAsync($command) {
$traits = $this->getTraits($command);
foreach ($traits as $trait) {
- if (array_search($trait, $this->syncTraits) !== false) {
+ if (in_array($trait, $this->syncTraits)) {
return false;
}
}
diff --git a/lib/private/Command/ClosureJob.php b/lib/private/Command/ClosureJob.php
index 7216bcc762a..f7b0ee1a3d3 100644
--- a/lib/private/Command/ClosureJob.php
+++ b/lib/private/Command/ClosureJob.php
@@ -22,8 +22,8 @@
*/
namespace OC\Command;
-use OC\BackgroundJob\QueuedJob;
use Laravel\SerializableClosure\SerializableClosure as LaravelClosure;
+use OC\BackgroundJob\QueuedJob;
class ClosureJob extends QueuedJob {
protected function run($argument) {
diff --git a/lib/private/Command/CronBus.php b/lib/private/Command/CronBus.php
index 8749ad0bff5..42ff458a95c 100644
--- a/lib/private/Command/CronBus.php
+++ b/lib/private/Command/CronBus.php
@@ -25,8 +25,8 @@
*/
namespace OC\Command;
-use OCP\Command\ICommand;
use Laravel\SerializableClosure\SerializableClosure;
+use OCP\Command\ICommand;
class CronBus extends AsyncBus {
/**
diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php
index 35e88c74438..183821e37b1 100644
--- a/lib/private/Comments/Comment.php
+++ b/lib/private/Comments/Comment.php
@@ -42,6 +42,7 @@ class Comment implements IComment {
'objectType' => '',
'objectId' => '',
'referenceId' => null,
+ 'metaData' => null,
'creationDT' => null,
'latestChildDT' => null,
'reactions' => null,
@@ -403,6 +404,34 @@ class Comment implements IComment {
/**
* @inheritDoc
*/
+ public function getMetaData(): ?array {
+ if ($this->data['metaData'] === null) {
+ return null;
+ }
+
+ try {
+ $metaData = json_decode($this->data['metaData'], true, flags: JSON_THROW_ON_ERROR);
+ } catch (\JsonException $e) {
+ return null;
+ }
+ return is_array($metaData) ? $metaData : null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMetaData(?array $metaData): IComment {
+ if ($metaData === null) {
+ $this->data['metaData'] = null;
+ } else {
+ $this->data['metaData'] = json_encode($metaData, JSON_THROW_ON_ERROR);
+ }
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
public function getReactions(): array {
return $this->data['reactions'] ?? [];
}
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php
index 725febef85d..85b56f9f25c 100644
--- a/lib/private/Comments/Manager.php
+++ b/lib/private/Comments/Manager.php
@@ -29,7 +29,6 @@
namespace OC\Comments;
use Doctrine\DBAL\Exception\DriverException;
-use Doctrine\DBAL\Exception\InvalidFieldNameException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\CommentsEvent;
use OCP\Comments\IComment;
@@ -40,8 +39,8 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IEmojiHelper;
-use OCP\IUser;
use OCP\IInitialStateService;
+use OCP\IUser;
use OCP\PreConditionNotMetException;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -66,11 +65,11 @@ class Manager implements ICommentsManager {
protected array $displayNameResolvers = [];
public function __construct(IDBConnection $dbConn,
- LoggerInterface $logger,
- IConfig $config,
- ITimeFactory $timeFactory,
- IEmojiHelper $emojiHelper,
- IInitialStateService $initialStateService) {
+ LoggerInterface $logger,
+ IConfig $config,
+ ITimeFactory $timeFactory,
+ IEmojiHelper $emojiHelper,
+ IInitialStateService $initialStateService) {
$this->dbConn = $dbConn;
$this->logger = $logger;
$this->config = $config;
@@ -97,7 +96,8 @@ class Manager implements ICommentsManager {
$data['expire_date'] = new \DateTime($data['expire_date']);
}
$data['children_count'] = (int)$data['children_count'];
- $data['reference_id'] = $data['reference_id'] ?? null;
+ $data['reference_id'] = $data['reference_id'];
+ $data['meta_data'] = json_decode($data['meta_data'], true);
if ($this->supportReactions()) {
if ($data['reactions'] !== null) {
$list = json_decode($data['reactions'], true);
@@ -536,8 +536,8 @@ class Manager implements ICommentsManager {
* @param int $id the comment to look for
*/
protected function getLastKnownComment(string $objectType,
- string $objectId,
- int $id): ?IComment {
+ string $objectId,
+ int $id): ?IComment {
$query = $this->dbConn->getQueryBuilder();
$query->select('*')
->from('comments')
@@ -1150,22 +1150,6 @@ class Manager implements ICommentsManager {
* @return bool
*/
protected function insert(IComment $comment): bool {
- try {
- $result = $this->insertQuery($comment, true);
- } catch (InvalidFieldNameException $e) {
- // The reference id field was only added in Nextcloud 19.
- // In order to not cause too long waiting times on the update,
- // it was decided to only add it lazy, as it is also not a critical
- // feature, but only helps to have a better experience while commenting.
- // So in case the reference_id field is missing,
- // we simply save the comment without that field.
- $result = $this->insertQuery($comment, false);
- }
-
- return $result;
- }
-
- protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool {
$qb = $this->dbConn->getQueryBuilder();
$values = [
@@ -1181,12 +1165,10 @@ class Manager implements ICommentsManager {
'object_type' => $qb->createNamedParameter($comment->getObjectType()),
'object_id' => $qb->createNamedParameter($comment->getObjectId()),
'expire_date' => $qb->createNamedParameter($comment->getExpireDate(), 'datetime'),
+ 'reference_id' => $qb->createNamedParameter($comment->getReferenceId()),
+ 'meta_data' => $qb->createNamedParameter(json_encode($comment->getMetaData())),
];
- if ($tryWritingReferenceId) {
- $values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId());
- }
-
$affectedRows = $qb->insert('comments')
->values($values)
->execute();
@@ -1289,12 +1271,7 @@ class Manager implements ICommentsManager {
$this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
$this->uncache($comment->getId());
- try {
- $result = $this->updateQuery($comment, true);
- } catch (InvalidFieldNameException $e) {
- // See function insert() for explanation
- $result = $this->updateQuery($comment, false);
- }
+ $result = $this->updateQuery($comment);
if ($comment->getVerb() === 'reaction_deleted') {
$this->deleteReaction($comment);
@@ -1305,7 +1282,7 @@ class Manager implements ICommentsManager {
return $result;
}
- protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool {
+ protected function updateQuery(IComment $comment): bool {
$qb = $this->dbConn->getQueryBuilder();
$qb
->update('comments')
@@ -1320,14 +1297,11 @@ class Manager implements ICommentsManager {
->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
->set('object_id', $qb->createNamedParameter($comment->getObjectId()))
- ->set('expire_date', $qb->createNamedParameter($comment->getExpireDate(), 'datetime'));
-
- if ($tryWritingReferenceId) {
- $qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()));
- }
-
- $affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())))
- ->execute();
+ ->set('expire_date', $qb->createNamedParameter($comment->getExpireDate(), 'datetime'))
+ ->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()))
+ ->set('meta_data', $qb->createNamedParameter(json_encode($comment->getMetaData())))
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())));
+ $affectedRows = $qb->executeStatement();
if ($affectedRows === 0) {
throw new NotFoundException('Comment to update does ceased to exist');
diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php
index a0306c9798c..900b2c57f41 100644
--- a/lib/private/Console/Application.php
+++ b/lib/private/Console/Application.php
@@ -55,10 +55,10 @@ class Application {
private MemoryInfo $memoryInfo;
public function __construct(IConfig $config,
- IEventDispatcher $dispatcher,
- IRequest $request,
- LoggerInterface $logger,
- MemoryInfo $memoryInfo) {
+ IEventDispatcher $dispatcher,
+ IRequest $request,
+ LoggerInterface $logger,
+ MemoryInfo $memoryInfo) {
$defaults = \OC::$server->getThemingDefaults();
$this->config = $config;
$this->application = new SymfonyApplication($defaults->getName(), \OC_Util::getVersionString());
diff --git a/lib/private/Console/TimestampFormatter.php b/lib/private/Console/TimestampFormatter.php
index 8d74c28e94f..afb1f67c37f 100644
--- a/lib/private/Console/TimestampFormatter.php
+++ b/lib/private/Console/TimestampFormatter.php
@@ -27,17 +27,17 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyleInterface;
class TimestampFormatter implements OutputFormatterInterface {
- /** @var IConfig */
+ /** @var ?IConfig */
protected $config;
/** @var OutputFormatterInterface */
protected $formatter;
/**
- * @param IConfig $config
+ * @param ?IConfig $config
* @param OutputFormatterInterface $formatter
*/
- public function __construct(IConfig $config, OutputFormatterInterface $formatter) {
+ public function __construct(?IConfig $config, OutputFormatterInterface $formatter) {
$this->config = $config;
$this->formatter = $formatter;
}
@@ -104,11 +104,16 @@ class TimestampFormatter implements OutputFormatterInterface {
return $this->formatter->format($message);
}
- $timeZone = $this->config->getSystemValue('logtimezone', 'UTC');
- $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null;
+ if ($this->config instanceof IConfig) {
+ $timeZone = $this->config->getSystemValue('logtimezone', 'UTC');
+ $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null;
- $time = new \DateTime('now', $timeZone);
- $timestampInfo = $time->format($this->config->getSystemValue('logdateformat', \DateTimeInterface::ATOM));
+ $time = new \DateTime('now', $timeZone);
+ $timestampInfo = $time->format($this->config->getSystemValue('logdateformat', \DateTimeInterface::ATOM));
+ } else {
+ $time = new \DateTime('now');
+ $timestampInfo = $time->format(\DateTimeInterface::ATOM);
+ }
return $timestampInfo . ' ' . $this->formatter->format($message);
}
diff --git a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php
index 7ba5db4bb33..67354a5fb2d 100644
--- a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php
+++ b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php
@@ -33,6 +33,7 @@ use OC\Contacts\ContactsMenu\Providers\EMailProvider;
use OC\Contacts\ContactsMenu\Providers\LocalTimeProvider;
use OC\Contacts\ContactsMenu\Providers\ProfileProvider;
use OCP\AppFramework\QueryException;
+use OCP\Contacts\ContactsMenu\IBulkProvider;
use OCP\Contacts\ContactsMenu\IProvider;
use OCP\IServerContainer;
use OCP\IUser;
@@ -47,18 +48,26 @@ class ActionProviderStore {
}
/**
- * @return IProvider[]
+ * @return list<IProvider|IBulkProvider>
* @throws Exception
*/
public function getProviders(IUser $user): array {
$appClasses = $this->getAppProviderClasses($user);
$providerClasses = $this->getServerProviderClasses();
$allClasses = array_merge($providerClasses, $appClasses);
+ /** @var list<IProvider|IBulkProvider> $providers */
$providers = [];
foreach ($allClasses as $class) {
try {
- $providers[] = $this->serverContainer->get($class);
+ $provider = $this->serverContainer->get($class);
+ if ($provider instanceof IProvider || $provider instanceof IBulkProvider) {
+ $providers[] = $provider;
+ } else {
+ $this->logger->warning('Ignoring invalid contacts menu provider', [
+ 'class' => $class,
+ ]);
+ }
} catch (QueryException $ex) {
$this->logger->error(
'Could not load contacts menu action provider ' . $class,
diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php
index c692b486ae4..eeb6ae56bc1 100644
--- a/lib/private/Contacts/ContactsMenu/ContactsStore.php
+++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php
@@ -33,6 +33,8 @@ namespace OC\Contacts\ContactsMenu;
use OC\KnownUser\KnownUserService;
use OC\Profile\ProfileManager;
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Service\StatusService;
use OCP\Contacts\ContactsMenu\IContactsStore;
use OCP\Contacts\ContactsMenu\IEntry;
use OCP\Contacts\IManager;
@@ -42,10 +44,17 @@ use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory as IL10NFactory;
+use function array_column;
+use function array_fill_keys;
+use function array_filter;
+use function array_key_exists;
+use function array_merge;
+use function count;
class ContactsStore implements IContactsStore {
public function __construct(
private IManager $contactsManager,
+ private ?StatusService $userStatusService,
private IConfig $config,
private ProfileManager $profileManager,
private IUserManager $userManager,
@@ -70,15 +79,75 @@ class ContactsStore implements IContactsStore {
if ($offset !== null) {
$options['offset'] = $offset;
}
+ // Status integration only works without pagination and filters
+ if ($offset === null && ($filter === null || $filter === '')) {
+ $recentStatuses = $this->userStatusService?->findAllRecentStatusChanges($limit, $offset) ?? [];
+ } else {
+ $recentStatuses = [];
+ }
- $allContacts = $this->contactsManager->search(
- $filter ?? '',
- [
- 'FN',
- 'EMAIL'
- ],
- $options
- );
+ // Search by status if there is no filter and statuses are available
+ if (!empty($recentStatuses)) {
+ $allContacts = array_filter(array_map(function (UserStatus $userStatus) use ($options) {
+ // UID is ambiguous with federation. We have to use the federated cloud ID to an exact match of
+ // A local user
+ $user = $this->userManager->get($userStatus->getUserId());
+ if ($user === null) {
+ return null;
+ }
+
+ $contact = $this->contactsManager->search(
+ $user->getCloudId(),
+ [
+ 'CLOUD',
+ ],
+ array_merge(
+ $options,
+ [
+ 'limit' => 1,
+ 'offset' => 0,
+ ],
+ ),
+ )[0] ?? null;
+ if ($contact !== null) {
+ $contact[Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP] = $userStatus->getStatusMessageTimestamp();
+ }
+ return $contact;
+ }, $recentStatuses));
+ if ($limit !== null && count($allContacts) < $limit) {
+ // More contacts were requested
+ $fromContacts = $this->contactsManager->search(
+ $filter ?? '',
+ [
+ 'FN',
+ 'EMAIL'
+ ],
+ array_merge(
+ $options,
+ [
+ 'limit' => $limit - count($allContacts),
+ ],
+ ),
+ );
+
+ // Create hash map of all status contacts
+ $existing = array_fill_keys(array_column($allContacts, 'URI'), null);
+ // Append the ones that are new
+ $allContacts = array_merge(
+ $allContacts,
+ array_filter($fromContacts, fn (array $contact): bool => !array_key_exists($contact['URI'], $existing))
+ );
+ }
+ } else {
+ $allContacts = $this->contactsManager->search(
+ $filter ?? '',
+ [
+ 'FN',
+ 'EMAIL'
+ ],
+ $options
+ );
+ }
$userId = $user->getUID();
$contacts = array_filter($allContacts, function ($contact) use ($userId) {
@@ -268,8 +337,10 @@ class ContactsStore implements IContactsStore {
if (isset($contact['UID'])) {
$uid = $contact['UID'];
$entry->setId($uid);
+ $entry->setProperty('isUser', false);
if (isset($contact['isLocalSystemBook'])) {
$avatar = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 64]);
+ $entry->setProperty('isUser', true);
} elseif (isset($contact['FN'])) {
$avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => $contact['FN'], 'size' => 64]);
} else {
diff --git a/lib/private/Contacts/ContactsMenu/Entry.php b/lib/private/Contacts/ContactsMenu/Entry.php
index f1cb4f9c52f..954f46e1296 100644
--- a/lib/private/Contacts/ContactsMenu/Entry.php
+++ b/lib/private/Contacts/ContactsMenu/Entry.php
@@ -29,8 +29,11 @@ namespace OC\Contacts\ContactsMenu;
use OCP\Contacts\ContactsMenu\IAction;
use OCP\Contacts\ContactsMenu\IEntry;
+use function array_merge;
class Entry implements IEntry {
+ public const PROPERTY_STATUS_MESSAGE_TIMESTAMP = 'statusMessageTimestamp';
+
/** @var string|int|null */
private $id = null;
@@ -50,6 +53,11 @@ class Entry implements IEntry {
private array $properties = [];
+ private ?string $status = null;
+ private ?string $statusMessage = null;
+ private ?int $statusMessageTimestamp = null;
+ private ?string $statusIcon = null;
+
public function setId(string $id): void {
$this->id = $id;
}
@@ -102,6 +110,16 @@ class Entry implements IEntry {
$this->sortActions();
}
+ public function setStatus(string $status,
+ string $statusMessage = null,
+ int $statusMessageTimestamp = null,
+ string $icon = null): void {
+ $this->status = $status;
+ $this->statusMessage = $statusMessage;
+ $this->statusMessageTimestamp = $statusMessageTimestamp;
+ $this->statusIcon = $icon;
+ }
+
/**
* @return IAction[]
*/
@@ -127,11 +145,15 @@ class Entry implements IEntry {
});
}
+ public function setProperty(string $propertyName, mixed $value) {
+ $this->properties[$propertyName] = $value;
+ }
+
/**
- * @param array $contact key-value array containing additional properties
+ * @param array $properties key-value array containing additional properties
*/
- public function setProperties(array $contact): void {
- $this->properties = $contact;
+ public function setProperties(array $properties): void {
+ $this->properties = array_merge($this->properties, $properties);
}
public function getProperty(string $key): mixed {
@@ -142,7 +164,7 @@ class Entry implements IEntry {
}
/**
- * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null}
+ * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null, status: string|null, statusMessage: null|string, statusMessageTimestamp: null|int, statusIcon: null|string, isUser: bool, uid: mixed}
*/
public function jsonSerialize(): array {
$topAction = !empty($this->actions) ? $this->actions[0]->jsonSerialize() : null;
@@ -160,6 +182,20 @@ class Entry implements IEntry {
'emailAddresses' => $this->getEMailAddresses(),
'profileTitle' => $this->profileTitle,
'profileUrl' => $this->profileUrl,
+ 'status' => $this->status,
+ 'statusMessage' => $this->statusMessage,
+ 'statusMessageTimestamp' => $this->statusMessageTimestamp,
+ 'statusIcon' => $this->statusIcon,
+ 'isUser' => $this->getProperty('isUser') === true,
+ 'uid' => $this->getProperty('UID'),
];
}
+
+ public function getStatusMessage(): ?string {
+ return $this->statusMessage;
+ }
+
+ public function getStatusMessageTimestamp(): ?int {
+ return $this->statusMessageTimestamp;
+ }
}
diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php
index 490cf602283..5cf9a07c8e3 100644
--- a/lib/private/Contacts/ContactsMenu/Manager.php
+++ b/lib/private/Contacts/ContactsMenu/Manager.php
@@ -28,7 +28,9 @@ namespace OC\Contacts\ContactsMenu;
use Exception;
use OCP\App\IAppManager;
use OCP\Constants;
+use OCP\Contacts\ContactsMenu\IBulkProvider;
use OCP\Contacts\ContactsMenu\IEntry;
+use OCP\Contacts\ContactsMenu\IProvider;
use OCP\IConfig;
use OCP\IUser;
@@ -80,8 +82,19 @@ class Manager {
* @return IEntry[]
*/
private function sortEntries(array $entries): array {
- usort($entries, function (IEntry $entryA, IEntry $entryB) {
- return strcasecmp($entryA->getFullName(), $entryB->getFullName());
+ usort($entries, function (Entry $entryA, Entry $entryB) {
+ $aStatusTimestamp = $entryA->getProperty(Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP);
+ $bStatusTimestamp = $entryB->getProperty(Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP);
+ if (!$aStatusTimestamp && !$bStatusTimestamp) {
+ return strcasecmp($entryA->getFullName(), $entryB->getFullName());
+ }
+ if ($aStatusTimestamp === null) {
+ return 1;
+ }
+ if ($bStatusTimestamp === null) {
+ return -1;
+ }
+ return $bStatusTimestamp - $aStatusTimestamp;
});
return $entries;
}
@@ -92,9 +105,14 @@ class Manager {
*/
private function processEntries(array $entries, IUser $user): void {
$providers = $this->actionProviderStore->getProviders($user);
- foreach ($entries as $entry) {
- foreach ($providers as $provider) {
- $provider->process($entry);
+
+ foreach ($providers as $provider) {
+ if ($provider instanceof IBulkProvider && !($provider instanceof IProvider)) {
+ $provider->process($entries);
+ } elseif ($provider instanceof IProvider && !($provider instanceof IBulkProvider)) {
+ foreach ($entries as $entry) {
+ $provider->process($entry);
+ }
}
}
}
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index df35e0b5e0d..6e2724ca5ab 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -46,13 +46,13 @@ use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Statement;
+use OC\DB\QueryBuilder\QueryBuilder;
+use OC\SystemConfig;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Diagnostics\IEventLogger;
use OCP\IRequestId;
use OCP\PreConditionNotMetException;
use OCP\Profiler\IProfiler;
-use OC\DB\QueryBuilder\QueryBuilder;
-use OC\SystemConfig;
use Psr\Log\LoggerInterface;
class Connection extends \Doctrine\DBAL\Connection {
diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php
index 29df1c1f78d..60f9b65cd5f 100644
--- a/lib/private/DB/MigrationService.php
+++ b/lib/private/DB/MigrationService.php
@@ -390,6 +390,7 @@ class MigrationService {
*/
public function migrate(string $to = 'latest', bool $schemaOnly = false): void {
if ($schemaOnly) {
+ $this->output->debug('Migrating schema only');
$this->migrateSchemaOnly($to);
return;
}
@@ -421,6 +422,7 @@ class MigrationService {
$toSchema = null;
foreach ($toBeExecuted as $version) {
+ $this->output->debug('- Reading ' . $version);
$instance = $this->createInstance($version);
$toSchema = $instance->changeSchema($this->output, function () use ($toSchema): ISchemaWrapper {
@@ -429,16 +431,20 @@ class MigrationService {
}
if ($toSchema instanceof SchemaWrapper) {
+ $this->output->debug('- Checking target database schema');
$targetSchema = $toSchema->getWrappedSchema();
$this->ensureUniqueNamesConstraints($targetSchema);
if ($this->checkOracle) {
$beforeSchema = $this->connection->createSchema();
$this->ensureOracleConstraints($beforeSchema, $targetSchema, strlen($this->connection->getPrefix()));
}
+
+ $this->output->debug('- Migrate database schema');
$this->connection->migrateToSchema($targetSchema);
$toSchema->performDropTableCalls();
}
+ $this->output->debug('- Mark migrations as executed');
foreach ($toBeExecuted as $version) {
$this->markAsExecuted($version);
}
diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php
index 1d960e72dc5..7cf95b04000 100644
--- a/lib/private/DB/Migrator.php
+++ b/lib/private/DB/Migrator.php
@@ -35,9 +35,9 @@ use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaDiff;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\Type;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use function preg_match;
-use OCP\EventDispatcher\IEventDispatcher;
class Migrator {
/** @var Connection */
@@ -52,8 +52,8 @@ class Migrator {
private $noEmit = false;
public function __construct(Connection $connection,
- IConfig $config,
- ?IEventDispatcher $dispatcher = null) {
+ IConfig $config,
+ ?IEventDispatcher $dispatcher = null) {
$this->connection = $connection;
$this->config = $config;
$this->dispatcher = $dispatcher;
diff --git a/lib/private/DB/MissingColumnInformation.php b/lib/private/DB/MissingColumnInformation.php
index f651546b4b3..919f8923a26 100644
--- a/lib/private/DB/MissingColumnInformation.php
+++ b/lib/private/DB/MissingColumnInformation.php
@@ -26,7 +26,7 @@ declare(strict_types=1);
namespace OC\DB;
class MissingColumnInformation {
- private $listOfMissingColumns = [];
+ private array $listOfMissingColumns = [];
public function addHintForMissingColumn(string $tableName, string $columnName): void {
$this->listOfMissingColumns[] = [
diff --git a/lib/private/DB/MissingIndexInformation.php b/lib/private/DB/MissingIndexInformation.php
index 74498668349..4fc3a52d3a4 100644
--- a/lib/private/DB/MissingIndexInformation.php
+++ b/lib/private/DB/MissingIndexInformation.php
@@ -27,16 +27,16 @@ declare(strict_types=1);
namespace OC\DB;
class MissingIndexInformation {
- private $listOfMissingIndexes = [];
+ private array $listOfMissingIndices = [];
- public function addHintForMissingSubject(string $tableName, string $indexName) {
- $this->listOfMissingIndexes[] = [
+ public function addHintForMissingIndex(string $tableName, string $indexName): void {
+ $this->listOfMissingIndices[] = [
'tableName' => $tableName,
'indexName' => $indexName
];
}
- public function getListOfMissingIndexes(): array {
- return $this->listOfMissingIndexes;
+ public function getListOfMissingIndices(): array {
+ return $this->listOfMissingIndices;
}
}
diff --git a/lib/private/DB/MissingPrimaryKeyInformation.php b/lib/private/DB/MissingPrimaryKeyInformation.php
index f28c8cfb352..42e5584291c 100644
--- a/lib/private/DB/MissingPrimaryKeyInformation.php
+++ b/lib/private/DB/MissingPrimaryKeyInformation.php
@@ -26,9 +26,9 @@ declare(strict_types=1);
namespace OC\DB;
class MissingPrimaryKeyInformation {
- private $listOfMissingPrimaryKeys = [];
+ private array $listOfMissingPrimaryKeys = [];
- public function addHintForMissingSubject(string $tableName) {
+ public function addHintForMissingPrimaryKey(string $tableName): void {
$this->listOfMissingPrimaryKeys[] = [
'tableName' => $tableName,
];
diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php
index 30dc02b0c16..c2818911ccf 100644
--- a/lib/private/DB/QueryBuilder/QueryBuilder.php
+++ b/lib/private/DB/QueryBuilder/QueryBuilder.php
@@ -866,7 +866,7 @@ class QueryBuilder implements IQueryBuilder {
public function where(...$predicates) {
if ($this->getQueryPart('where') !== null && $this->systemConfig->getValue('debug', false)) {
// Only logging a warning, not throwing for now.
- $e = new QueryException('Using where() on non-empty WHERE part, please verify it is intentional to not call whereAnd() or whereOr() instead. Otherwise consider creating a new query builder object or call resetQueryPart(\'where\') first.');
+ $e = new QueryException('Using where() on non-empty WHERE part, please verify it is intentional to not call andWhere() or orWhere() instead. Otherwise consider creating a new query builder object or call resetQueryPart(\'where\') first.');
$this->logger->warning($e->getMessage(), ['exception' => $e]);
}
diff --git a/lib/private/Dashboard/Manager.php b/lib/private/Dashboard/Manager.php
index afe28872e69..5a7e4f3c6dc 100644
--- a/lib/private/Dashboard/Manager.php
+++ b/lib/private/Dashboard/Manager.php
@@ -33,8 +33,8 @@ use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
-use Throwable;
use Psr\Log\LoggerInterface;
+use Throwable;
class Manager implements IManager {
/** @var array */
diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php
index 2dd2abe5408..d1be1f50330 100644
--- a/lib/private/DirectEditing/Manager.php
+++ b/lib/private/DirectEditing/Manager.php
@@ -25,8 +25,9 @@
*/
namespace OC\DirectEditing;
-use Doctrine\DBAL\FetchMode;
+use \OCP\DirectEditing\IManager;
use \OCP\Files\Folder;
+use Doctrine\DBAL\FetchMode;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
@@ -34,7 +35,6 @@ use OCP\Constants;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DirectEditing\ACreateFromTemplate;
use OCP\DirectEditing\IEditor;
-use \OCP\DirectEditing\IManager;
use OCP\DirectEditing\IToken;
use OCP\Encryption\IManager as EncryptionManager;
use OCP\Files\File;
diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php
index e58b3656593..a6bc72ef18f 100644
--- a/lib/private/Encryption/EncryptionWrapper.php
+++ b/lib/private/Encryption/EncryptionWrapper.php
@@ -53,8 +53,8 @@ class EncryptionWrapper {
* EncryptionWrapper constructor.
*/
public function __construct(ArrayCache $arrayCache,
- Manager $manager,
- LoggerInterface $logger
+ Manager $manager,
+ LoggerInterface $logger
) {
$this->arrayCache = $arrayCache;
$this->manager = $manager;
diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php
index daab097ce7c..f2b1de23234 100644
--- a/lib/private/Encryption/File.php
+++ b/lib/private/Encryption/File.php
@@ -27,9 +27,9 @@
*/
namespace OC\Encryption;
-use OCP\Cache\CappedMemoryCache;
use OCA\Files_External\Service\GlobalStoragesService;
use OCP\App\IAppManager;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Share\IManager;
@@ -47,8 +47,8 @@ class File implements \OCP\Encryption\IFile {
private ?IAppManager $appManager = null;
public function __construct(Util $util,
- IRootFolder $rootFolder,
- IManager $shareManager) {
+ IRootFolder $rootFolder,
+ IManager $shareManager) {
$this->util = $util;
$this->cache = new CappedMemoryCache();
$this->rootFolder = $rootFolder;
diff --git a/lib/private/Encryption/HookManager.php b/lib/private/Encryption/HookManager.php
index 5081bcccf94..afcb7ce3763 100644
--- a/lib/private/Encryption/HookManager.php
+++ b/lib/private/Encryption/HookManager.php
@@ -24,8 +24,8 @@
namespace OC\Encryption;
use OC\Files\Filesystem;
-use OC\Files\View;
use OC\Files\SetupManager;
+use OC\Files\View;
use Psr\Log\LoggerInterface;
class HookManager {
diff --git a/lib/private/Encryption/Update.php b/lib/private/Encryption/Update.php
index 2e390177baf..1d9ec8510d0 100644
--- a/lib/private/Encryption/Update.php
+++ b/lib/private/Encryption/Update.php
@@ -62,14 +62,14 @@ class Update {
* @param string $uid
*/
public function __construct(
- View $view,
- Util $util,
- Mount\Manager $mountManager,
- Manager $encryptionManager,
- File $file,
- LoggerInterface $logger,
- $uid
- ) {
+ View $view,
+ Util $util,
+ Mount\Manager $mountManager,
+ Manager $encryptionManager,
+ File $file,
+ LoggerInterface $logger,
+ $uid
+ ) {
$this->view = $view;
$this->util = $util;
$this->mountManager = $mountManager;
diff --git a/lib/private/EventDispatcher/EventDispatcher.php b/lib/private/EventDispatcher/EventDispatcher.php
index 88c6b2cf32c..14c13d516c0 100644
--- a/lib/private/EventDispatcher/EventDispatcher.php
+++ b/lib/private/EventDispatcher/EventDispatcher.php
@@ -27,17 +27,17 @@ declare(strict_types=1);
*/
namespace OC\EventDispatcher;
-use OC\Log;
-use Psr\Log\LoggerInterface;
-use function get_class;
use OC\Broadcast\Events\BroadcastEvent;
+use OC\Log;
use OCP\Broadcast\Events\IBroadcastEvent;
use OCP\EventDispatcher\ABroadcastedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IContainer;
use OCP\IServerContainer;
+use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyDispatcher;
+use function get_class;
class EventDispatcher implements IEventDispatcher {
/** @var SymfonyDispatcher */
@@ -50,8 +50,8 @@ class EventDispatcher implements IEventDispatcher {
private $logger;
public function __construct(SymfonyDispatcher $dispatcher,
- IServerContainer $container,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ LoggerInterface $logger) {
$this->dispatcher = $dispatcher;
$this->container = $container;
$this->logger = $logger;
@@ -64,19 +64,19 @@ class EventDispatcher implements IEventDispatcher {
}
public function addListener(string $eventName,
- callable $listener,
- int $priority = 0): void {
+ callable $listener,
+ int $priority = 0): void {
$this->dispatcher->addListener($eventName, $listener, $priority);
}
public function removeListener(string $eventName,
- callable $listener): void {
+ callable $listener): void {
$this->dispatcher->removeListener($eventName, $listener);
}
public function addServiceListener(string $eventName,
- string $className,
- int $priority = 0): void {
+ string $className,
+ int $priority = 0): void {
$listener = new ServiceEventListener(
$this->container,
$className,
@@ -90,7 +90,7 @@ class EventDispatcher implements IEventDispatcher {
* @deprecated
*/
public function dispatch(string $eventName,
- Event $event): void {
+ Event $event): void {
$this->dispatcher->dispatch($event, $eventName);
if ($event instanceof ABroadcastedEvent && !$event->isPropagationStopped()) {
diff --git a/lib/private/EventDispatcher/ServiceEventListener.php b/lib/private/EventDispatcher/ServiceEventListener.php
index 21cdf7f8cc2..a7bbbcd82aa 100644
--- a/lib/private/EventDispatcher/ServiceEventListener.php
+++ b/lib/private/EventDispatcher/ServiceEventListener.php
@@ -54,8 +54,8 @@ final class ServiceEventListener {
private $service;
public function __construct(IServerContainer $container,
- string $class,
- LoggerInterface $logger) {
+ string $class,
+ LoggerInterface $logger) {
$this->container = $container;
$this->class = $class;
$this->logger = $logger;
diff --git a/lib/private/Federation/CloudFederationShare.php b/lib/private/Federation/CloudFederationShare.php
index 0f79ba521ea..4b741b28bee 100644
--- a/lib/private/Federation/CloudFederationShare.php
+++ b/lib/private/Federation/CloudFederationShare.php
@@ -57,16 +57,16 @@ class CloudFederationShare implements ICloudFederationShare {
* @param string $sharedSecret
*/
public function __construct($shareWith = '',
- $name = '',
- $description = '',
- $providerId = '',
- $owner = '',
- $ownerDisplayName = '',
- $sharedBy = '',
- $sharedByDisplayName = '',
- $shareType = '',
- $resourceType = '',
- $sharedSecret = ''
+ $name = '',
+ $description = '',
+ $providerId = '',
+ $owner = '',
+ $ownerDisplayName = '',
+ $sharedBy = '',
+ $sharedByDisplayName = '',
+ $shareType = '',
+ $resourceType = '',
+ $sharedSecret = ''
) {
$this->setShareWith($shareWith);
$this->setResourceName($name);
diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php
index 237fcb42e03..1c632c3062f 100644
--- a/lib/private/Files/AppData/AppData.php
+++ b/lib/private/Files/AppData/AppData.php
@@ -26,9 +26,9 @@ declare(strict_types=1);
*/
namespace OC\Files\AppData;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\SimpleFS\SimpleFolder;
use OC\SystemConfig;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\Folder;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@@ -53,8 +53,8 @@ class AppData implements IAppData {
* @param string $appId
*/
public function __construct(IRootFolder $rootFolder,
- SystemConfig $systemConfig,
- string $appId) {
+ SystemConfig $systemConfig,
+ string $appId) {
$this->rootFolder = $rootFolder;
$this->config = $systemConfig;
$this->appId = $appId;
diff --git a/lib/private/Files/AppData/Factory.php b/lib/private/Files/AppData/Factory.php
index 03f8fdedcbd..a16c3df327d 100644
--- a/lib/private/Files/AppData/Factory.php
+++ b/lib/private/Files/AppData/Factory.php
@@ -39,7 +39,7 @@ class Factory implements IAppDataFactory {
private array $folders = [];
public function __construct(IRootFolder $rootFolder,
- SystemConfig $systemConfig) {
+ SystemConfig $systemConfig) {
$this->rootFolder = $rootFolder;
$this->config = $systemConfig;
}
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index 67d01bb6999..052b3c75ce8 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -47,9 +47,9 @@ use OC\Files\Storage\Wrapper\Encryption;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\CacheEntryInsertedEvent;
+use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Cache\CacheEntryUpdatedEvent;
use OCP\Files\Cache\CacheInsertEvent;
-use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Cache\CacheUpdateEvent;
use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
@@ -59,6 +59,7 @@ use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchQuery;
use OCP\Files\Storage\IStorage;
+use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\IDBConnection;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -132,7 +133,8 @@ class Cache implements ICache {
return new CacheQueryBuilder(
$this->connection,
\OC::$server->getSystemConfig(),
- \OC::$server->get(LoggerInterface::class)
+ \OC::$server->get(LoggerInterface::class),
+ \OC::$server->get(IFilesMetadataManager::class),
);
}
@@ -154,6 +156,7 @@ class Cache implements ICache {
public function get($file) {
$query = $this->getQueryBuilder();
$query->selectFileCache();
+ $metadataQuery = $query->selectMetadata();
if (is_string($file) || $file == '') {
// normalize file
@@ -175,6 +178,7 @@ class Cache implements ICache {
} elseif (!$data) {
return $data;
} else {
+ $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? [];
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}
}
@@ -239,11 +243,14 @@ class Cache implements ICache {
->whereParent($fileId)
->orderBy('name', 'ASC');
+ $metadataQuery = $query->selectMetadata();
+
$result = $query->execute();
$files = $result->fetchAll();
$result->closeCursor();
- return array_map(function (array $data) {
+ return array_map(function (array $data) use ($metadataQuery) {
+ $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? [];
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}, $files);
}
@@ -447,7 +454,7 @@ class Cache implements ICache {
$params = [];
$extensionParams = [];
foreach ($data as $name => $value) {
- if (array_search($name, $fields) !== false) {
+ if (in_array($name, $fields)) {
if ($name === 'path') {
$params['path_hash'] = md5($value);
} elseif ($name === 'mimetype') {
@@ -467,7 +474,7 @@ class Cache implements ICache {
}
$params[$name] = $value;
}
- if (array_search($name, $extensionFields) !== false) {
+ if (in_array($name, $extensionFields)) {
$extensionParams[$name] = $value;
}
}
@@ -599,9 +606,12 @@ class Cache implements ICache {
}
/** @var ICacheEntry[] $childFolders */
- $childFolders = array_filter($children, function ($child) {
- return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
- });
+ $childFolders = [];
+ foreach ($children as $child) {
+ if ($child->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
+ $childFolders[] = $child;
+ }
+ }
foreach ($childFolders as $folder) {
$parentIds[] = $folder->getId();
$queue[] = $folder->getId();
diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php
index 34d2177b84e..365d28fc8c5 100644
--- a/lib/private/Files/Cache/CacheQueryBuilder.php
+++ b/lib/private/Files/Cache/CacheQueryBuilder.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -28,6 +29,8 @@ namespace OC\Files\Cache;
use OC\DB\QueryBuilder\QueryBuilder;
use OC\SystemConfig;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;
@@ -35,9 +38,14 @@ use Psr\Log\LoggerInterface;
* Query builder with commonly used helpers for filecache queries
*/
class CacheQueryBuilder extends QueryBuilder {
- private $alias = null;
-
- public function __construct(IDBConnection $connection, SystemConfig $systemConfig, LoggerInterface $logger) {
+ private ?string $alias = null;
+
+ public function __construct(
+ IDBConnection $connection,
+ SystemConfig $systemConfig,
+ LoggerInterface $logger,
+ private IFilesMetadataManager $filesMetadataManager,
+ ) {
parent::__construct($connection, $systemConfig, $logger);
}
@@ -63,7 +71,7 @@ class CacheQueryBuilder extends QueryBuilder {
public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
$name = $alias ?: 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
- 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
+ 'storage_mtime', 'encrypted', 'etag', "$name.permissions", 'checksum', 'unencrypted_size')
->from('filecache', $name);
if ($joinExtendedCache) {
@@ -126,4 +134,15 @@ class CacheQueryBuilder extends QueryBuilder {
return $this;
}
+
+ /**
+ * join metadata to current query builder and returns an helper
+ *
+ * @return IMetadataQuery|null NULL if no metadata have never been generated
+ */
+ public function selectMetadata(): ?IMetadataQuery {
+ $metadataQuery = $this->filesMetadataManager->getMetadataQuery($this, $this->alias, 'fileid');
+ $metadataQuery?->retrieveMetadata();
+ return $metadataQuery;
+ }
}
diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php
index 15c089a0f11..d8c5e66e129 100644
--- a/lib/private/Files/Cache/QuerySearchHelper.php
+++ b/lib/private/Files/Cache/QuerySearchHelper.php
@@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tobias Kaminsky <tobias@kaminsky.me>
@@ -37,52 +38,47 @@ use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchQuery;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUser;
use Psr\Log\LoggerInterface;
class QuerySearchHelper {
- /** @var IMimeTypeLoader */
- private $mimetypeLoader;
- /** @var IDBConnection */
- private $connection;
- /** @var SystemConfig */
- private $systemConfig;
- private LoggerInterface $logger;
- /** @var SearchBuilder */
- private $searchBuilder;
- /** @var QueryOptimizer */
- private $queryOptimizer;
- private IGroupManager $groupManager;
-
public function __construct(
- IMimeTypeLoader $mimetypeLoader,
- IDBConnection $connection,
- SystemConfig $systemConfig,
- LoggerInterface $logger,
- SearchBuilder $searchBuilder,
- QueryOptimizer $queryOptimizer,
- IGroupManager $groupManager,
+ private IMimeTypeLoader $mimetypeLoader,
+ private IDBConnection $connection,
+ private SystemConfig $systemConfig,
+ private LoggerInterface $logger,
+ private SearchBuilder $searchBuilder,
+ private QueryOptimizer $queryOptimizer,
+ private IGroupManager $groupManager,
+ private IFilesMetadataManager $filesMetadataManager,
) {
- $this->mimetypeLoader = $mimetypeLoader;
- $this->connection = $connection;
- $this->systemConfig = $systemConfig;
- $this->logger = $logger;
- $this->searchBuilder = $searchBuilder;
- $this->queryOptimizer = $queryOptimizer;
- $this->groupManager = $groupManager;
}
protected function getQueryBuilder() {
return new CacheQueryBuilder(
$this->connection,
$this->systemConfig,
- $this->logger
+ $this->logger,
+ $this->filesMetadataManager,
);
}
- protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void {
+ /**
+ * @param CacheQueryBuilder $query
+ * @param ISearchQuery $searchQuery
+ * @param array $caches
+ * @param IMetadataQuery|null $metadataQuery
+ */
+ protected function applySearchConstraints(
+ CacheQueryBuilder $query,
+ ISearchQuery $searchQuery,
+ array $caches,
+ ?IMetadataQuery $metadataQuery = null
+ ): void {
$storageFilters = array_values(array_map(function (ICache $cache) {
return $cache->getQueryFilterForStorage();
}, $caches));
@@ -90,12 +86,12 @@ class QuerySearchHelper {
$filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]);
$this->queryOptimizer->processOperator($filter);
- $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter);
+ $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter, $metadataQuery);
if ($searchExpr) {
$query->andWhere($searchExpr);
}
- $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
+ $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder(), $metadataQuery);
if ($searchQuery->getLimit()) {
$query->setMaxResults($searchQuery->getLimit());
@@ -144,6 +140,11 @@ class QuerySearchHelper {
));
}
+
+ protected function equipQueryForShares(CacheQueryBuilder $query): void {
+ $query->join('file', 'share', 's', $query->expr()->eq('file.fileid', 's.file_source'));
+ }
+
/**
* Perform a file system search in multiple caches
*
@@ -175,19 +176,31 @@ class QuerySearchHelper {
$query = $builder->selectFileCache('file', false);
$requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());
+
if (in_array('systemtag', $requestedFields)) {
$this->equipQueryForSystemTags($query, $this->requireUser($searchQuery));
}
if (in_array('tagname', $requestedFields) || in_array('favorite', $requestedFields)) {
$this->equipQueryForDavTags($query, $this->requireUser($searchQuery));
}
+ if (in_array('owner', $requestedFields) || in_array('share_with', $requestedFields) || in_array('share_type', $requestedFields)) {
+ $this->equipQueryForShares($query);
+ }
- $this->applySearchConstraints($query, $searchQuery, $caches);
+ $metadataQuery = $query->selectMetadata();
+
+ $this->applySearchConstraints($query, $searchQuery, $caches, $metadataQuery);
$result = $query->execute();
$files = $result->fetchAll();
- $rawEntries = array_map(function (array $data) {
+ $rawEntries = array_map(function (array $data) use ($metadataQuery) {
+ // migrate to null safe ...
+ if ($metadataQuery === null) {
+ $data['metadata'] = [];
+ } else {
+ $data['metadata'] = $metadataQuery->extractMetadata($data)->asArray();
+ }
return Cache::cacheEntryFromData($data, $this->mimetypeLoader);
}, $files);
diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php
index 074e88e7639..2711fc8ad19 100644
--- a/lib/private/Files/Cache/Scanner.php
+++ b/lib/private/Files/Cache/Scanner.php
@@ -37,14 +37,14 @@ namespace OC\Files\Cache;
use Doctrine\DBAL\Exception;
use OC\Files\Storage\Wrapper\Encryption;
+use OC\Files\Storage\Wrapper\Jail;
+use OC\Hooks\BasicEmitter;
use OCP\Files\Cache\IScanner;
use OCP\Files\ForbiddenException;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IReliableEtagStorage;
use OCP\IDBConnection;
use OCP\Lock\ILockingProvider;
-use OC\Files\Storage\Wrapper\Jail;
-use OC\Hooks\BasicEmitter;
use Psr\Log\LoggerInterface;
/**
diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php
index b9a70bbd39b..38161ec9cc6 100644
--- a/lib/private/Files/Cache/SearchBuilder.php
+++ b/lib/private/Files/Cache/SearchBuilder.php
@@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tobias Kaminsky <tobias@kaminsky.me>
@@ -32,6 +33,7 @@ use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchOrder;
+use OCP\FilesMetadata\IMetadataQuery;
/**
* Tools for transforming search queries into database queries
@@ -45,6 +47,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
ISearchComparison::COMPARE_LESS_THAN => 'lt',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte',
+ ISearchComparison::COMPARE_DEFINED => 'isNotNull',
];
protected static $searchOperatorNegativeMap = [
@@ -55,6 +58,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
ISearchComparison::COMPARE_LESS_THAN => 'gte',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'gt',
+ ISearchComparison::COMPARE_DEFINED => 'isNull',
];
public const TAG_FAVORITE = '_$!<Favorite>!$_';
@@ -76,7 +80,7 @@ class SearchBuilder {
return array_reduce($operator->getArguments(), function (array $fields, ISearchOperator $operator) {
return array_unique(array_merge($fields, $this->extractRequestedFields($operator)));
}, []);
- } elseif ($operator instanceof ISearchComparison) {
+ } elseif ($operator instanceof ISearchComparison && !$operator->getExtra()) {
return [$operator->getField()];
}
return [];
@@ -86,13 +90,21 @@ class SearchBuilder {
* @param IQueryBuilder $builder
* @param ISearchOperator[] $operators
*/
- public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
- return array_filter(array_map(function ($operator) use ($builder) {
- return $this->searchOperatorToDBExpr($builder, $operator);
+ public function searchOperatorArrayToDBExprArray(
+ IQueryBuilder $builder,
+ array $operators,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
+ return array_filter(array_map(function ($operator) use ($builder, $metadataQuery) {
+ return $this->searchOperatorToDBExpr($builder, $operator, $metadataQuery);
}, $operators));
}
- public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
+ public function searchOperatorToDBExpr(
+ IQueryBuilder $builder,
+ ISearchOperator $operator,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
$expr = $builder->expr();
if ($operator instanceof ISearchBinaryOperator) {
@@ -104,29 +116,37 @@ class SearchBuilder {
case ISearchBinaryOperator::OPERATOR_NOT:
$negativeOperator = $operator->getArguments()[0];
if ($negativeOperator instanceof ISearchComparison) {
- return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
+ return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap, $metadataQuery);
} else {
throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
}
// no break
case ISearchBinaryOperator::OPERATOR_AND:
- return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
+ return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery));
case ISearchBinaryOperator::OPERATOR_OR:
- return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
+ return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery));
default:
throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
}
} elseif ($operator instanceof ISearchComparison) {
- return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
+ return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap, $metadataQuery);
} else {
throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
}
}
- private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) {
- $this->validateComparison($comparison);
+ private function searchComparisonToDBExpr(
+ IQueryBuilder $builder,
+ ISearchComparison $comparison,
+ array $operatorMap,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
+ if ($comparison->getExtra()) {
+ [$field, $value, $type] = $this->getExtraOperatorField($comparison, $metadataQuery);
+ } else {
+ [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison);
+ }
- [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison);
if (isset($operatorMap[$type])) {
$queryOperator = $operatorMap[$type];
return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
@@ -136,9 +156,12 @@ class SearchBuilder {
}
private function getOperatorFieldAndValue(ISearchComparison $operator) {
+ $this->validateComparison($operator);
+
$field = $operator->getField();
$value = $operator->getValue();
$type = $operator->getType();
+
if ($field === 'mimetype') {
$value = (string)$value;
if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
@@ -171,6 +194,8 @@ class SearchBuilder {
} elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL && $operator->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true)) {
$field = 'path_hash';
$value = md5((string)$value);
+ } elseif ($field === 'owner') {
+ $field = 'uid_owner';
}
return [$field, $value, $type];
}
@@ -187,6 +212,9 @@ class SearchBuilder {
'favorite' => 'boolean',
'fileid' => 'integer',
'storage' => 'integer',
+ 'share_with' => 'string',
+ 'share_type' => 'integer',
+ 'owner' => 'string',
];
$comparisons = [
'mimetype' => ['eq', 'like'],
@@ -199,6 +227,9 @@ class SearchBuilder {
'favorite' => ['eq'],
'fileid' => ['eq'],
'storage' => ['eq'],
+ 'share_with' => ['eq'],
+ 'share_type' => ['eq'],
+ 'owner' => ['eq'],
];
if (!isset($types[$operator->getField()])) {
@@ -213,6 +244,24 @@ class SearchBuilder {
}
}
+
+ private function getExtraOperatorField(ISearchComparison $operator, IMetadataQuery $metadataQuery): array {
+ $field = $operator->getField();
+ $value = $operator->getValue();
+ $type = $operator->getType();
+
+ switch($operator->getExtra()) {
+ case IMetadataQuery::EXTRA:
+ $metadataQuery->joinIndex($field); // join index table if not joined yet
+ $field = $metadataQuery->getMetadataValueField($field);
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid extra type: ' . $operator->getExtra());
+ }
+
+ return [$field, $value, $type];
+ }
+
private function getParameterForValue(IQueryBuilder $builder, $value) {
if ($value instanceof \DateTime) {
$value = $value->getTimestamp();
@@ -228,24 +277,32 @@ class SearchBuilder {
/**
* @param IQueryBuilder $query
* @param ISearchOrder[] $orders
+ * @param IMetadataQuery|null $metadataQuery
*/
- public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
+ public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders, ?IMetadataQuery $metadataQuery = null): void {
foreach ($orders as $order) {
$field = $order->getField();
- if ($field === 'fileid') {
- $field = 'file.fileid';
- }
+ switch ($order->getExtra()) {
+ case IMetadataQuery::EXTRA:
+ $metadataQuery->joinIndex($field); // join index table if not joined yet
+ $field = $metadataQuery->getMetadataValueField($order->getField());
+ break;
- // Mysql really likes to pick an index for sorting if it can't fully satisfy the where
- // filter with an index, since search queries pretty much never are fully filtered by index
- // mysql often picks an index for sorting instead of the much more useful index for filtering.
- //
- // By changing the order by to an expression, mysql isn't smart enough to see that it could still
- // use the index, so it instead picks an index for the filtering
- if ($field === 'mtime') {
- $field = $query->func()->add($field, $query->createNamedParameter(0));
- }
+ default:
+ if ($field === 'fileid') {
+ $field = 'file.fileid';
+ }
+ // Mysql really likes to pick an index for sorting if it can't fully satisfy the where
+ // filter with an index, since search queries pretty much never are fully filtered by index
+ // mysql often picks an index for sorting instead of the much more useful index for filtering.
+ //
+ // By changing the order by to an expression, mysql isn't smart enough to see that it could still
+ // use the index, so it instead picks an index for the filtering
+ if ($field === 'mtime') {
+ $field = $query->func()->add($field, $query->createNamedParameter(0));
+ }
+ }
$query->addOrderBy($field, $order->getDirection());
}
}
diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php
index acc76f263dc..61ea5b2f848 100644
--- a/lib/private/Files/Cache/Watcher.php
+++ b/lib/private/Files/Cache/Watcher.php
@@ -129,7 +129,7 @@ class Watcher implements IWatcher {
* @return bool
*/
public function needsUpdate($path, $cachedData) {
- if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and array_search($path, $this->checkedPaths) === false)) {
+ if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and !in_array($path, $this->checkedPaths))) {
$this->checkedPaths[] = $path;
return $this->storage->hasUpdated($path, $cachedData['storage_mtime']);
}
diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php
index d8cf3eb61d7..73c9a017019 100644
--- a/lib/private/Files/Cache/Wrapper/CacheJail.php
+++ b/lib/private/Files/Cache/Wrapper/CacheJail.php
@@ -52,8 +52,6 @@ class CacheJail extends CacheWrapper {
public function __construct($cache, $root) {
parent::__construct($cache);
$this->root = $root;
- $this->connection = \OC::$server->getDatabaseConnection();
- $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
if ($cache instanceof CacheJail) {
$this->unjailedRoot = $cache->getSourcePath($root);
diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php
index 43c9fae63ec..7c97135a565 100644
--- a/lib/private/Files/Config/CachedMountInfo.php
+++ b/lib/private/Files/Config/CachedMountInfo.php
@@ -35,6 +35,7 @@ class CachedMountInfo implements ICachedMountInfo {
protected ?int $mountId;
protected string $rootInternalPath;
protected string $mountProvider;
+ protected string $key;
/**
* CachedMountInfo constructor.
@@ -65,6 +66,7 @@ class CachedMountInfo implements ICachedMountInfo {
throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters");
}
$this->mountProvider = $mountProvider;
+ $this->key = $rootId . '::' . $mountPoint;
}
/**
@@ -132,4 +134,8 @@ class CachedMountInfo implements ICachedMountInfo {
public function getMountProvider(): string {
return $this->mountProvider;
}
+
+ public function getKey(): string {
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php
index 78055a2cdb8..7e4acb2e129 100644
--- a/lib/private/Files/Config/LazyStorageMountInfo.php
+++ b/lib/private/Files/Config/LazyStorageMountInfo.php
@@ -39,6 +39,7 @@ class LazyStorageMountInfo extends CachedMountInfo {
$this->rootId = 0;
$this->storageId = 0;
$this->mountPoint = '';
+ $this->key = '';
}
/**
@@ -87,4 +88,11 @@ class LazyStorageMountInfo extends CachedMountInfo {
public function getMountProvider(): string {
return $this->mount->getMountProvider();
}
+
+ public function getKey(): string {
+ if (!$this->key) {
+ $this->key = $this->getRootId() . '::' . $this->getMountPoint();
+ }
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php
index ae6481e45bb..d251199fd43 100644
--- a/lib/private/Files/Config/MountProviderCollection.php
+++ b/lib/private/Files/Config/MountProviderCollection.php
@@ -238,6 +238,11 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
$mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
return array_merge($mounts, $providerMounts);
}, []);
+
+ if (count($mounts) === 0) {
+ throw new \Exception("No root mounts provided by any provider");
+ }
+
return $mounts;
}
diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php
index 7502d65d044..2fb7a7d83f4 100644
--- a/lib/private/Files/Config/UserMountCache.php
+++ b/lib/private/Files/Config/UserMountCache.php
@@ -29,13 +29,11 @@
namespace OC\Files\Config;
use OCP\Cache\CappedMemoryCache;
-use OCA\Files_Sharing\SharedMount;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Diagnostics\IEventLogger;
use OCP\Files\Config\ICachedMountFileInfo;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
-use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IUser;
@@ -78,41 +76,27 @@ class UserMountCache implements IUserMountCache {
public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) {
$this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
- // filter out non-proper storages coming from unit tests
- $mounts = array_filter($mounts, function (IMountPoint $mount) {
- return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache());
- });
- /** @var ICachedMountInfo[] $newMounts */
- $newMounts = array_map(function (IMountPoint $mount) use ($user) {
+ /** @var array<string, ICachedMountInfo> $newMounts */
+ $newMounts = [];
+ foreach ($mounts as $mount) {
// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
- if ($mount->getStorageRootId() === -1) {
- return null;
- } else {
- return new LazyStorageMountInfo($user, $mount);
+ if ($mount->getStorageRootId() !== -1) {
+ $mountInfo = new LazyStorageMountInfo($user, $mount);
+ $newMounts[$mountInfo->getKey()] = $mountInfo;
}
- }, $mounts);
- $newMounts = array_values(array_filter($newMounts));
- $newMountKeys = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId() . '::' . $mount->getMountPoint();
- }, $newMounts);
- $newMounts = array_combine($newMountKeys, $newMounts);
+ }
$cachedMounts = $this->getMountsForUser($user);
if (is_array($mountProviderClasses)) {
$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
// for existing mounts that didn't have a mount provider set
// we still want the ones that map to new mounts
- $mountKey = $mountInfo->getRootId() . '::' . $mountInfo->getMountPoint();
- if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountKey])) {
+ if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
return true;
}
return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
});
}
- $cachedRootKeys = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId() . '::' . $mount->getMountPoint();
- }, $cachedMounts);
- $cachedMounts = array_combine($cachedRootKeys, $cachedMounts);
$addedMounts = [];
$removedMounts = [];
@@ -131,46 +115,44 @@ class UserMountCache implements IUserMountCache {
$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
- $this->connection->beginTransaction();
- try {
- foreach ($addedMounts as $mount) {
- $this->addToCache($mount);
- /** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$user->getUID()][] = $mount;
- }
- foreach ($removedMounts as $mount) {
- $this->removeFromCache($mount);
- $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
- unset($this->mountsForUsers[$user->getUID()][$index]);
- }
- foreach ($changedMounts as $mount) {
- $this->updateCachedMount($mount);
+ if ($addedMounts || $removedMounts || $changedMounts) {
+ $this->connection->beginTransaction();
+ $userUID = $user->getUID();
+ try {
+ foreach ($addedMounts as $mount) {
+ $this->addToCache($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ foreach ($removedMounts as $mount) {
+ $this->removeFromCache($mount);
+ unset($this->mountsForUsers[$userUID][$mount->getKey()]);
+ }
+ foreach ($changedMounts as $mount) {
+ $this->updateCachedMount($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ $this->connection->commit();
+ } catch (\Throwable $e) {
+ $this->connection->rollBack();
+ throw $e;
}
- $this->connection->commit();
- } catch (\Throwable $e) {
- $this->connection->rollBack();
- throw $e;
}
$this->eventLogger->end('fs:setup:user:register');
}
/**
- * @param ICachedMountInfo[] $newMounts
- * @param ICachedMountInfo[] $cachedMounts
+ * @param array<string, ICachedMountInfo> $newMounts
+ * @param array<string, ICachedMountInfo> $cachedMounts
* @return ICachedMountInfo[]
*/
private function findChangedMounts(array $newMounts, array $cachedMounts) {
- $new = [];
- foreach ($newMounts as $mount) {
- $new[$mount->getRootId() . '::' . $mount->getMountPoint()] = $mount;
- }
$changed = [];
- foreach ($cachedMounts as $cachedMount) {
- $key = $cachedMount->getRootId() . '::' . $cachedMount->getMountPoint();
- if (isset($new[$key])) {
- $newMount = $new[$key];
+ foreach ($cachedMounts as $key => $cachedMount) {
+ if (isset($newMounts[$key])) {
+ $newMount = $newMounts[$key];
if (
- $newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
$newMount->getStorageId() !== $cachedMount->getStorageId() ||
$newMount->getMountId() !== $cachedMount->getMountId() ||
$newMount->getMountProvider() !== $cachedMount->getMountProvider()
@@ -247,20 +229,28 @@ class UserMountCache implements IUserMountCache {
* @return ICachedMountInfo[]
*/
public function getMountsForUser(IUser $user) {
- if (!isset($this->mountsForUsers[$user->getUID()])) {
+ $userUID = $user->getUID();
+ if (!isset($this->mountsForUsers[$userUID])) {
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
+ ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($userUID)));
$result = $query->execute();
$rows = $result->fetchAll();
$result->closeCursor();
- $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
+ $this->mountsForUsers[$userUID] = [];
+ /** @var array<string, ICachedMountInfo> $mounts */
+ foreach ($rows as $row) {
+ $mount = $this->dbRowToMountInfo($row);
+ if ($mount !== null) {
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ }
}
- return $this->mountsForUsers[$user->getUID()];
+ return $this->mountsForUsers[$userUID];
}
/**
diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php
index 7800074460b..5ba2f27b78b 100644
--- a/lib/private/Files/FileInfo.php
+++ b/lib/private/Files/FileInfo.php
@@ -6,6 +6,7 @@
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Piotr M <mrow4a@yahoo.com>
* @author Robin Appelman <robin@icewind.nl>
@@ -32,9 +33,10 @@
*/
namespace OC\Files;
-use OCA\Files_Sharing\ISharedStorage;
+use OC\Files\Mount\HomeMountPoint;
+use OCA\Files_Sharing\External\Mount;
+use OCA\Files_Sharing\ISharedMountPoint;
use OCP\Files\Cache\ICacheEntry;
-use OCP\Files\IHomeStorage;
use OCP\Files\Mount\IMountPoint;
use OCP\IUser;
@@ -121,21 +123,14 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
- if ($offset === 'type') {
- return $this->getType();
- } elseif ($offset === 'etag') {
- return $this->getEtag();
- } elseif ($offset === 'size') {
- return $this->getSize();
- } elseif ($offset === 'mtime') {
- return $this->getMTime();
- } elseif ($offset === 'permissions') {
- return $this->getPermissions();
- } elseif (isset($this->data[$offset])) {
- return $this->data[$offset];
- } else {
- return null;
- }
+ return match ($offset) {
+ 'type' => $this->getType(),
+ 'etag' => $this->getEtag(),
+ 'size' => $this->getSize(),
+ 'mtime' => $this->getMTime(),
+ 'permissions' => $this->getPermissions(),
+ default => $this->data[$offset] ?? null,
+ };
}
/**
@@ -311,13 +306,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return bool
*/
public function isShared() {
- $storage = $this->getStorage();
- return $storage->instanceOfStorage(ISharedStorage::class);
+ return $this->mount instanceof ISharedMountPoint;
}
public function isMounted() {
- $storage = $this->getStorage();
- return !($storage->instanceOfStorage(IHomeStorage::class) || $storage->instanceOfStorage(ISharedStorage::class));
+ $isHome = $this->mount instanceof HomeMountPoint;
+ return !$isHome && !$this->isShared();
}
/**
@@ -416,4 +410,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
public function getParentId(): int {
return $this->data['parent'] ?? -1;
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->data['metadata'] ?? [];
+ }
}
diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php
index 5f7c0c403db..9f0d89052be 100644
--- a/lib/private/Files/Filesystem.php
+++ b/lib/private/Files/Filesystem.php
@@ -37,9 +37,9 @@
*/
namespace OC\Files;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Mount\MountPoint;
use OC\User\NoUserException;
+use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\Node\FilesystemTornDownEvent;
use OCP\Files\Mount\IMountManager;
diff --git a/lib/private/Files/Mount/HomeMountPoint.php b/lib/private/Files/Mount/HomeMountPoint.php
new file mode 100644
index 00000000000..0bec12af5c2
--- /dev/null
+++ b/lib/private/Files/Mount/HomeMountPoint.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023 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\Mount;
+
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IUser;
+
+class HomeMountPoint extends MountPoint {
+ private IUser $user;
+
+ public function __construct(
+ IUser $user,
+ $storage,
+ string $mountpoint,
+ array $arguments = null,
+ IStorageFactory $loader = null,
+ array $mountOptions = null,
+ int $mountId = null,
+ string $mountProvider = null
+ ) {
+ parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, $mountProvider);
+ $this->user = $user;
+ }
+
+ public function getUser(): IUser {
+ return $this->user;
+ }
+}
diff --git a/lib/private/Files/Mount/LocalHomeMountProvider.php b/lib/private/Files/Mount/LocalHomeMountProvider.php
index 25a67fc1574..964b607d152 100644
--- a/lib/private/Files/Mount/LocalHomeMountProvider.php
+++ b/lib/private/Files/Mount/LocalHomeMountProvider.php
@@ -38,6 +38,6 @@ class LocalHomeMountProvider implements IHomeMountProvider {
*/
public function getHomeMountForUser(IUser $user, IStorageFactory $loader) {
$arguments = ['user' => $user];
- return new MountPoint('\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
+ return new HomeMountPoint($user, '\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
}
}
diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php
index e623211cc7a..2b2de1fbff1 100644
--- a/lib/private/Files/Mount/Manager.php
+++ b/lib/private/Files/Mount/Manager.php
@@ -30,10 +30,10 @@ declare(strict_types=1);
namespace OC\Files\Mount;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Filesystem;
use OC\Files\SetupManager;
use OC\Files\SetupManagerFactory;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Mount\IMountPoint;
@@ -101,6 +101,15 @@ class Manager implements IMountManager {
return $this->pathCache[$path];
}
+
+
+ if (count($this->mounts) === 0) {
+ $this->setupManager->setupRoot();
+ if (count($this->mounts) === 0) {
+ throw new \Exception("No mounts even after explicitly setting up the root mounts");
+ }
+ }
+
$current = $path;
while (true) {
$mountPoint = $current . '/';
@@ -117,7 +126,7 @@ class Manager implements IMountManager {
}
}
- throw new NotFoundException("No mount for path " . $path . " existing mounts: " . implode(",", array_keys($this->mounts)));
+ throw new NotFoundException("No mount for path " . $path . " existing mounts (" . count($this->mounts) ."): " . implode(",", array_keys($this->mounts)));
}
/**
diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php
index 889a39fbd9e..3593a95c311 100644
--- a/lib/private/Files/Mount/ObjectHomeMountProvider.php
+++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php
@@ -65,7 +65,7 @@ class ObjectHomeMountProvider implements IHomeMountProvider {
return null;
}
- return new MountPoint('\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
+ return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
}
/**
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php
index ccd10da9d0c..c7462572fed 100644
--- a/lib/private/Files/Node/Folder.php
+++ b/lib/private/Files/Node/Folder.php
@@ -177,7 +177,7 @@ class Folder extends Node implements \OCP\Files\Folder {
* @throws \OCP\Files\NotPermittedException
*/
public function newFile($path, $content = null) {
- if (empty($path)) {
+ if ($path === '') {
throw new NotPermittedException('Could not create as provided path is empty');
}
if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php
index a8e76d95c22..f61eedee66e 100644
--- a/lib/private/Files/Node/HookConnector.php
+++ b/lib/private/Files/Node/HookConnector.php
@@ -133,7 +133,7 @@ class HookConnector {
$this->root->emit('\OC\Files', 'preDelete', [$node]);
$this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node));
- $event = new BeforeNodeDeletedEvent($node);
+ $event = new BeforeNodeDeletedEvent($node, $arguments['run']);
$this->dispatcher->dispatchTyped($event);
}
@@ -171,7 +171,7 @@ class HookConnector {
$this->root->emit('\OC\Files', 'preRename', [$source, $target]);
$this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target]));
- $event = new BeforeNodeRenamedEvent($source, $target);
+ $event = new BeforeNodeRenamedEvent($source, $target, $arguments['run']);
$this->dispatcher->dispatchTyped($event);
}
diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php
index f13cdc0c4f9..e30cfea693e 100644
--- a/lib/private/Files/Node/LazyFolder.php
+++ b/lib/private/Files/Node/LazyFolder.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -28,8 +29,8 @@ namespace OC\Files\Node;
use OC\Files\Filesystem;
use OC\Files\Utils\PathHelper;
-use OCP\Files\Folder;
use OCP\Constants;
+use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotPermittedException;
@@ -574,4 +575,12 @@ class LazyFolder implements Folder {
}
return $this->__call(__FUNCTION__, func_get_args());
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->data['metadata'] ?? $this->__call(__FUNCTION__, func_get_args());
+ }
}
diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php
index 503b0af8921..917ab80f366 100644
--- a/lib/private/Files/Node/LazyUserFolder.php
+++ b/lib/private/Files/Node/LazyUserFolder.php
@@ -23,13 +23,13 @@ declare(strict_types=1);
namespace OC\Files\Node;
-use OCP\Files\FileInfo;
use OCP\Constants;
+use OCP\Files\File;
+use OCP\Files\FileInfo;
+use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
use OCP\Files\NotFoundException;
-use OCP\Files\Folder;
-use OCP\Files\File;
use OCP\IUser;
use Psr\Log\LoggerInterface;
diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php
index 385d45f1e3e..acd91c56d3f 100644
--- a/lib/private/Files/Node/Node.php
+++ b/lib/private/Files/Node/Node.php
@@ -7,6 +7,7 @@
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
@@ -43,7 +44,7 @@ use OCP\Files\NotPermittedException;
use OCP\Lock\LockedException;
use OCP\PreConditionNotMetException;
-// FIXME: this class really should be abstract
+// FIXME: this class really should be abstract (+1)
class Node implements INode {
/**
* @var \OC\Files\View $view
@@ -131,7 +132,14 @@ class Node implements INode {
if (method_exists($this->root, 'emit')) {
$this->root->emit('\OC\Files', $hook, $args);
}
- $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args));
+
+ if (in_array($hook, ['preWrite', 'postWrite', 'preCreate', 'postCreate', 'preTouch', 'postTouch', 'preDelete', 'postDelete'], true)) {
+ $event = new GenericEvent($args[0]);
+ } else {
+ $event = new GenericEvent($args);
+ }
+
+ $dispatcher->dispatch('\OCP\Files::' . $hook, $event);
}
}
@@ -490,4 +498,12 @@ class Node implements INode {
public function getParentId(): int {
return $this->fileInfo->getParentId();
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->fileInfo->getMetadata();
+ }
}
diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php
index 1195b644083..ee344f9be8b 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -32,7 +32,6 @@
namespace OC\Files\Node;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\FileInfo;
use OC\Files\Mount\Manager;
use OC\Files\Mount\MountPoint;
@@ -40,6 +39,7 @@ use OC\Files\Utils\PathHelper;
use OC\Files\View;
use OC\Hooks\PublicEmitter;
use OC\User\NoUserException;
+use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Config\IUserMountCache;
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 4dceee9a58b..eb8aaffe1e0 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -68,6 +68,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
private $logger;
+ private bool $handleCopiesAsOwned;
+
/** @var bool */
protected $validateWrites = true;
@@ -88,6 +90,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
if (isset($params['validateWrites'])) {
$this->validateWrites = (bool)$params['validateWrites'];
}
+ $this->handleCopiesAsOwned = (bool)($params['handleCopiesAsOwned'] ?? false);
$this->logger = \OC::$server->getLogger();
}
@@ -651,6 +654,10 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
try {
$this->objectStore->copyObject($sourceUrn, $targetUrn);
+ if ($this->handleCopiesAsOwned) {
+ // Copied the file thus we gain all permissions as we are the owner now ! warning while this aligns with local storage it should not be used and instead fix local storage !
+ $cache->update($targetId, ['permissions' => \OCP\Constants::PERMISSION_ALL]);
+ }
} catch (\Exception $e) {
$cache->remove($to);
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index e9c52f11936..217e1a1a2ff 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -191,6 +191,11 @@ trait S3ObjectTrait {
}
public function copyObject($from, $to, array $options = []) {
+ $sourceMetadata = $this->getConnection()->headObject([
+ 'Bucket' => $this->getBucket(),
+ 'Key' => $from,
+ ] + $this->getSSECParameters());
+
$copy = new MultipartCopy($this->getConnection(), [
"source_bucket" => $this->getBucket(),
"source_key" => $from
@@ -198,7 +203,8 @@ trait S3ObjectTrait {
"bucket" => $this->getBucket(),
"key" => $to,
"acl" => "private",
- "params" => $this->getSSECParameters() + $this->getSSECParameters(true)
+ "params" => $this->getSSECParameters() + $this->getSSECParameters(true),
+ "source_metadata" => $sourceMetadata
], $options));
$copy->copy();
}
diff --git a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
index 0caa9b12a02..664402f1238 100644
--- a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
+++ b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ * @author 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
@@ -48,7 +51,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep {
}
public function processOperator(ISearchOperator &$operator) {
- if (!$this->useHashEq && $operator instanceof ISearchComparison && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) {
+ if (!$this->useHashEq && $operator instanceof ISearchComparison && !$operator->getExtra() && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) {
$operator->setQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, false);
}
@@ -69,7 +72,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep {
private function operatorPairIsPathPrefix(ISearchOperator $like, ISearchOperator $equal): bool {
return (
$like instanceof ISearchComparison && $equal instanceof ISearchComparison &&
- $like->getField() === 'path' && $equal->getField() === 'path' &&
+ !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path' &&
$like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL
&& $like->getValue() === SearchComparison::escapeLikeParameter($equal->getValue()) . '/%'
);
diff --git a/lib/private/Files/Search/SearchComparison.php b/lib/private/Files/Search/SearchComparison.php
index 122a1f730b4..d94b3e9dfab 100644
--- a/lib/private/Files/Search/SearchComparison.php
+++ b/lib/private/Files/Search/SearchComparison.php
@@ -1,7 +1,10 @@
<?php
+
+declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -25,48 +28,45 @@ namespace OC\Files\Search;
use OCP\Files\Search\ISearchComparison;
class SearchComparison implements ISearchComparison {
- /** @var string */
- private $type;
- /** @var string */
- private $field;
- /** @var string|integer|\DateTime */
- private $value;
- private $hints = [];
+ private array $hints = [];
- /**
- * SearchComparison constructor.
- *
- * @param string $type
- * @param string $field
- * @param \DateTime|int|string $value
- */
- public function __construct($type, $field, $value) {
- $this->type = $type;
- $this->field = $field;
- $this->value = $value;
+ public function __construct(
+ private string $type,
+ private string $field,
+ private \DateTime|int|string|bool $value,
+ private string $extra = ''
+ ) {
}
/**
* @return string
*/
- public function getType() {
+ public function getType(): string {
return $this->type;
}
/**
* @return string
*/
- public function getField() {
+ public function getField(): string {
return $this->field;
}
/**
- * @return \DateTime|int|string
+ * @return \DateTime|int|string|bool
*/
- public function getValue() {
+ public function getValue(): string|int|bool|\DateTime {
return $this->value;
}
+ /**
+ * @return string
+ * @since 28.0.0
+ */
+ public function getExtra(): string {
+ return $this->extra;
+ }
+
public function getQueryHint(string $name, $default) {
return $this->hints[$name] ?? $default;
}
diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php
index 1395a87ac72..de514262bf5 100644
--- a/lib/private/Files/Search/SearchOrder.php
+++ b/lib/private/Files/Search/SearchOrder.php
@@ -2,6 +2,7 @@
/**
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -26,34 +27,33 @@ use OCP\Files\FileInfo;
use OCP\Files\Search\ISearchOrder;
class SearchOrder implements ISearchOrder {
- /** @var string */
- private $direction;
- /** @var string */
- private $field;
+ public function __construct(
+ private string $direction,
+ private string $field,
+ private string $extra = ''
+ ) {
+ }
/**
- * SearchOrder constructor.
- *
- * @param string $direction
- * @param string $field
+ * @return string
*/
- public function __construct($direction, $field) {
- $this->direction = $direction;
- $this->field = $field;
+ public function getDirection(): string {
+ return $this->direction;
}
/**
* @return string
*/
- public function getDirection() {
- return $this->direction;
+ public function getField(): string {
+ return $this->field;
}
/**
* @return string
+ * @since 28.0.0
*/
- public function getField() {
- return $this->field;
+ public function getExtra(): string {
+ return $this->extra;
}
public function sortFileInfo(FileInfo $a, FileInfo $b): int {
diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php
index cae0fd2f232..511e80bd7d9 100644
--- a/lib/private/Files/SetupManager.php
+++ b/lib/private/Files/SetupManager.php
@@ -24,8 +24,8 @@ declare(strict_types=1);
namespace OC\Files;
use OC\Files\Config\MountProviderCollection;
+use OC\Files\Mount\HomeMountPoint;
use OC\Files\Mount\MountPoint;
-use OC\Files\ObjectStore\HomeObjectStoreStorage;
use OC\Files\Storage\Common;
use OC\Files\Storage\Home;
use OC\Files\Storage\Storage;
@@ -39,7 +39,10 @@ use OC\Share20\ShareDisableChecker;
use OC_App;
use OC_Hook;
use OC_Util;
-use OCA\Files_Sharing\ISharedStorage;
+use OCA\Files_External\Config\ConfigAdapter;
+use OCA\Files_Sharing\External\Mount;
+use OCA\Files_Sharing\ISharedMountPoint;
+use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
@@ -117,7 +120,7 @@ class SetupManager {
$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if ($storage->instanceOfStorage(Common::class)) {
+ if ($mount->getOptions() && $storage->instanceOfStorage(Common::class)) {
$storage->setMountOptions($mount->getOptions());
}
return $storage;
@@ -130,7 +133,7 @@ class SetupManager {
'sharing_mask',
function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
- $isShared = $storage->instanceOfStorage(ISharedStorage::class);
+ $isShared = $mount instanceof ISharedMountPoint;
if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
return new PermissionsMask([
'storage' => $storage,
@@ -142,35 +145,30 @@ class SetupManager {
);
// install storage availability wrapper, before most other wrappers
- Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) {
- if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
+ Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
+ $externalMount = $mount instanceof ConfigAdapter || $mount instanceof Mount;
+ if ($externalMount && !$storage->isLocal()) {
return new Availability(['storage' => $storage]);
}
return $storage;
});
Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
+ if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
return new Encoding(['storage' => $storage]);
}
return $storage;
});
$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
- Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) use ($quotaIncludeExternal) {
+ Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
// set up quota for home storages, even for other users
// which can happen when using sharing
-
- /**
- * @var Storage $storage
- */
- if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) {
- if (is_object($storage->getUser())) {
- $user = $storage->getUser();
- return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
- return OC_Util::getUserQuota($user);
- }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
- }
+ if ($mount instanceof HomeMountPoint) {
+ $user = $mount->getUser();
+ return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
+ return OC_Util::getUserQuota($user);
+ }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
}
return $storage;
@@ -337,12 +335,13 @@ class SetupManager {
if ($this->rootSetup) {
return;
}
+
+ $this->setupBuiltinWrappers();
+
$this->rootSetup = true;
$this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
- $this->setupBuiltinWrappers();
-
$rootMounts = $this->mountProviderCollection->getRootMounts();
foreach ($rootMounts as $rootMountProvider) {
$this->mountManager->addMount($rootMountProvider);
diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php
index 4d24aa138c1..2c1f23f8e44 100644
--- a/lib/private/Files/SimpleFS/SimpleFolder.php
+++ b/lib/private/Files/SimpleFS/SimpleFolder.php
@@ -28,8 +28,8 @@ use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
-use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
class SimpleFolder implements ISimpleFolder {
/** @var Folder */
diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php
index 2d2bb52635b..35add2c606b 100644
--- a/lib/private/Files/Storage/DAV.php
+++ b/lib/private/Files/Storage/DAV.php
@@ -55,11 +55,11 @@ use OCP\ICertificateManager;
use OCP\IConfig;
use OCP\Util;
use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LoggerInterface;
use Sabre\DAV\Client;
use Sabre\DAV\Xml\Property\ResourceType;
use Sabre\HTTP\ClientException;
use Sabre\HTTP\ClientHttpException;
-use Psr\Log\LoggerInterface;
use Sabre\HTTP\RequestInterface;
/**
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index eeb9e11b24e..0fca853da59 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -74,6 +74,8 @@ class Local extends \OC\Files\Storage\Common {
protected bool $unlinkOnTruncate;
+ protected bool $caseInsensitive = false;
+
public function __construct($arguments) {
if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
throw new \InvalidArgumentException('No data directory set for local storage');
@@ -93,6 +95,7 @@ class Local extends \OC\Files\Storage\Common {
$this->config = \OC::$server->get(IConfig::class);
$this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
$this->defUMask = $this->config->getSystemValue('localstorage.umask', 0022);
+ $this->caseInsensitive = $this->config->getSystemValueBool('localstorage.case_insensitive', false);
// support Write-Once-Read-Many file systems
$this->unlinkOnTruncate = $this->config->getSystemValueBool('localstorage.unlink_on_truncate', false);
@@ -162,6 +165,9 @@ class Local extends \OC\Files\Storage\Common {
}
public function is_dir($path) {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
if (str_ends_with($path, '/')) {
$path = substr($path, 0, -1);
}
@@ -169,6 +175,9 @@ class Local extends \OC\Files\Storage\Common {
}
public function is_file($path) {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
return is_file($this->getSourcePath($path));
}
@@ -271,7 +280,13 @@ class Local extends \OC\Files\Storage\Common {
}
public function file_exists($path) {
- return file_exists($this->getSourcePath($path));
+ if ($this->caseInsensitive) {
+ $fullPath = $this->getSourcePath($path);
+ $content = scandir(dirname($fullPath), SCANDIR_SORT_NONE);
+ return is_array($content) && array_search(basename($fullPath), $content) !== false;
+ } else {
+ return file_exists($this->getSourcePath($path));
+ }
}
public function filemtime($path) {
@@ -372,6 +387,11 @@ class Local extends \OC\Files\Storage\Common {
}
if (@rename($this->getSourcePath($source), $this->getSourcePath($target))) {
+ if ($this->caseInsensitive) {
+ if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) {
+ return false;
+ }
+ }
return true;
}
@@ -388,6 +408,11 @@ class Local extends \OC\Files\Storage\Common {
}
$result = copy($this->getSourcePath($source), $this->getSourcePath($target));
umask($oldMask);
+ if ($this->caseInsensitive) {
+ if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) {
+ return false;
+ }
+ }
return $result;
}
}
diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php
index 6633cbf41e3..1bdb0e39f14 100644
--- a/lib/private/Files/Storage/Wrapper/Encoding.php
+++ b/lib/private/Files/Storage/Wrapper/Encoding.php
@@ -28,8 +28,8 @@
*/
namespace OC\Files\Storage\Wrapper;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Filesystem;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\Storage\IStorage;
use OCP\ICache;
diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php
index 1921ac27848..592acd418ec 100644
--- a/lib/private/Files/Storage/Wrapper/Jail.php
+++ b/lib/private/Files/Storage/Wrapper/Jail.php
@@ -396,10 +396,7 @@ class Jail extends Wrapper {
* @return \OC\Files\Cache\Cache
*/
public function getCache($path = '', $storage = null) {
- if (!$storage) {
- $storage = $this->getWrapperStorage();
- }
- $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
+ $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path));
return new CacheJail($sourceCache, $this->rootPath);
}
diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php
index bcf0a10740b..c57991f35a9 100644
--- a/lib/private/Files/Stream/Encryption.php
+++ b/lib/private/Files/Stream/Encryption.php
@@ -153,18 +153,18 @@ class Encryption extends Wrapper {
* @throws \BadMethodCallException
*/
public static function wrap($source, $internalPath, $fullPath, array $header,
- $uid,
- \OCP\Encryption\IEncryptionModule $encryptionModule,
- \OC\Files\Storage\Storage $storage,
- \OC\Files\Storage\Wrapper\Encryption $encStorage,
- \OC\Encryption\Util $util,
- \OC\Encryption\File $file,
- $mode,
- $size,
- $unencryptedSize,
- $headerSize,
- $signed,
- $wrapper = Encryption::class) {
+ $uid,
+ \OCP\Encryption\IEncryptionModule $encryptionModule,
+ \OC\Files\Storage\Storage $storage,
+ \OC\Files\Storage\Wrapper\Encryption $encStorage,
+ \OC\Encryption\Util $util,
+ \OC\Encryption\File $file,
+ $mode,
+ $size,
+ $unencryptedSize,
+ $headerSize,
+ $signed,
+ $wrapper = Encryption::class) {
$context = stream_context_create([
'ocencryption' => [
'source' => $source,
diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php
index 878680caa4e..9d9f6416208 100644
--- a/lib/private/Files/Template/TemplateManager.php
+++ b/lib/private/Files/Template/TemplateManager.php
@@ -31,8 +31,8 @@ use OC\AppFramework\Bootstrap\Coordinator;
use OC\Files\Cache\Scanner;
use OC\Files\Filesystem;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Files\Folder;
use OCP\Files\File;
+use OCP\Files\Folder;
use OCP\Files\GenericFileException;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
@@ -275,6 +275,11 @@ class TemplateManager implements ITemplateManager {
$isDefaultTemplates = $skeletonTemplatePath === $defaultTemplateDirectory;
$userLang = $this->l10nFactory->getUserLanguage($this->userManager->get($this->userId));
+ if ($skeletonTemplatePath === '') {
+ $this->setTemplatePath('');
+ return '';
+ }
+
try {
$l10n = $this->l10nFactory->get('lib', $userLang);
$userFolder = $this->rootFolder->getUserFolder($this->userId);
diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php
index 9a61aa93b95..71b8cb986d7 100644
--- a/lib/private/Files/Type/Detection.php
+++ b/lib/private/Files/Type/Detection.php
@@ -75,9 +75,9 @@ class Detection implements IMimeTypeDetector {
private $defaultConfigDir;
public function __construct(IURLGenerator $urlGenerator,
- LoggerInterface $logger,
- string $customConfigDir,
- string $defaultConfigDir) {
+ LoggerInterface $logger,
+ string $customConfigDir,
+ string $defaultConfigDir) {
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->customConfigDir = $customConfigDir;
@@ -96,8 +96,8 @@ class Detection implements IMimeTypeDetector {
* @param string|null $secureMimeType
*/
public function registerType(string $extension,
- string $mimetype,
- ?string $secureMimeType = null): void {
+ string $mimetype,
+ ?string $secureMimeType = null): void {
$this->mimetypes[$extension] = [$mimetype, $secureMimeType];
$this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype;
}
diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php
index b7f6972ee10..226e5462b34 100644
--- a/lib/private/Files/Utils/Scanner.php
+++ b/lib/private/Files/Utils/Scanner.php
@@ -41,11 +41,11 @@ use OCA\Files_Sharing\SharedStorage;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\BeforeFileScannedEvent;
use OCP\Files\Events\BeforeFolderScannedEvent;
-use OCP\Files\Events\NodeAddedToCache;
use OCP\Files\Events\FileCacheUpdated;
-use OCP\Files\Events\NodeRemovedFromCache;
use OCP\Files\Events\FileScannedEvent;
use OCP\Files\Events\FolderScannedEvent;
+use OCP\Files\Events\NodeAddedToCache;
+use OCP\Files\Events\NodeRemovedFromCache;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\Files\StorageNotAvailableException;
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 86651ab3e1a..6eefb093795 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -49,10 +49,10 @@ namespace OC\Files;
use Icewind\Streams\CallbackWrapper;
use OC\Files\Mount\MoveableMount;
use OC\Files\Storage\Storage;
-use OC\User\LazyUser;
use OC\Share\Share;
-use OC\User\User;
+use OC\User\LazyUser;
use OC\User\Manager as UserManager;
+use OC\User\User;
use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
use OCP\Files\Cache\ICacheEntry;
@@ -1525,7 +1525,7 @@ class View {
$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()) {
+ if ($sharingDisabled) {
$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
}
diff --git a/lib/private/FilesMetadata/FilesMetadataManager.php b/lib/private/FilesMetadata/FilesMetadataManager.php
new file mode 100644
index 00000000000..013c85af604
--- /dev/null
+++ b/lib/private/FilesMetadata/FilesMetadataManager.php
@@ -0,0 +1,347 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata;
+
+use JsonException;
+use OC\FilesMetadata\Job\UpdateSingleMetadata;
+use OC\FilesMetadata\Listener\MetadataDelete;
+use OC\FilesMetadata\Listener\MetadataUpdate;
+use OC\FilesMetadata\Model\FilesMetadata;
+use OC\FilesMetadata\Service\IndexRequestService;
+use OC\FilesMetadata\Service\MetadataRequestService;
+use OCP\BackgroundJob\IJobList;
+use OCP\DB\Exception;
+use OCP\DB\Exception as DBException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Cache\CacheEntryRemovedEvent;
+use OCP\Files\Events\Node\NodeWrittenEvent;
+use OCP\Files\InvalidPathException;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\FilesMetadata\Event\MetadataBackgroundEvent;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use OCP\FilesMetadata\Event\MetadataNamedEvent;
+use OCP\FilesMetadata\Exceptions\FilesMetadataException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @inheritDoc
+ * @since 28.0.0
+ */
+class FilesMetadataManager implements IFilesMetadataManager {
+ public const CONFIG_KEY = 'files_metadata';
+ public const MIGRATION_DONE = 'files_metadata_installed';
+ private const JSON_MAXSIZE = 100000;
+
+ private ?IFilesMetadata $all = null;
+
+ public function __construct(
+ private IEventDispatcher $eventDispatcher,
+ private IJobList $jobList,
+ private IConfig $config,
+ private LoggerInterface $logger,
+ private MetadataRequestService $metadataRequestService,
+ private IndexRequestService $indexRequestService,
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param Node $node related node
+ * @param int $process type of process
+ *
+ * @return IFilesMetadata
+ * @throws FilesMetadataException if metadata are invalid
+ * @throws InvalidPathException if path to file is not valid
+ * @throws NotFoundException if file cannot be found
+ * @see self::PROCESS_BACKGROUND
+ * @see self::PROCESS_LIVE
+ * @since 28.0.0
+ */
+ public function refreshMetadata(
+ Node $node,
+ int $process = self::PROCESS_LIVE,
+ string $namedEvent = ''
+ ): IFilesMetadata {
+ try {
+ $metadata = $this->metadataRequestService->getMetadataFromFileId($node->getId());
+ } catch (FilesMetadataNotFoundException) {
+ $metadata = new FilesMetadata($node->getId());
+ }
+
+ // if $process is LIVE, we enforce LIVE
+ // if $process is NAMED, we go NAMED
+ // else BACKGROUND
+ if ((self::PROCESS_LIVE & $process) !== 0) {
+ $event = new MetadataLiveEvent($node, $metadata);
+ } elseif ((self::PROCESS_NAMED & $process) !== 0) {
+ $event = new MetadataNamedEvent($node, $metadata, $namedEvent);
+ } else {
+ $event = new MetadataBackgroundEvent($node, $metadata);
+ }
+
+ $this->eventDispatcher->dispatchTyped($event);
+ $this->saveMetadata($event->getMetadata());
+
+ // if requested, we add a new job for next cron to refresh metadata out of main thread
+ // if $process was set to LIVE+BACKGROUND, we run background process directly
+ if ($event instanceof MetadataLiveEvent && $event->isRunAsBackgroundJobRequested()) {
+ if ((self::PROCESS_BACKGROUND & $process) !== 0) {
+ return $this->refreshMetadata($node, self::PROCESS_BACKGROUND);
+ }
+
+ $this->jobList->add(UpdateSingleMetadata::class, [$node->getOwner()->getUID(), $node->getId()]);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @param int $fileId file id
+ * @param boolean $generate Generate if metadata does not exists
+ *
+ * @inheritDoc
+ * @return IFilesMetadata
+ * @throws FilesMetadataNotFoundException if not found
+ * @since 28.0.0
+ */
+ public function getMetadata(int $fileId, bool $generate = false): IFilesMetadata {
+ try {
+ return $this->metadataRequestService->getMetadataFromFileId($fileId);
+ } catch (FilesMetadataNotFoundException $ex) {
+ if ($generate) {
+ return new FilesMetadata($fileId);
+ }
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * returns metadata of multiple file ids
+ *
+ * @param int[] $fileIds file ids
+ *
+ * @return array File ID is the array key, files without metadata are not returned in the array
+ * @psalm-return array<int, IFilesMetadata>
+ * @since 28.0.0
+ */
+ public function getMetadataForFiles(array $fileIds): array {
+ return $this->metadataRequestService->getMetadataFromFileIds($fileIds);
+ }
+
+ /**
+ * @param IFilesMetadata $filesMetadata metadata
+ *
+ * @inheritDoc
+ * @throws FilesMetadataException if metadata seems malformed
+ * @since 28.0.0
+ */
+ public function saveMetadata(IFilesMetadata $filesMetadata): void {
+ if ($filesMetadata->getFileId() === 0 || !$filesMetadata->updated()) {
+ return;
+ }
+
+ $json = json_encode($filesMetadata->jsonSerialize());
+ if (strlen($json) > self::JSON_MAXSIZE) {
+ throw new FilesMetadataException('json cannot exceed ' . self::JSON_MAXSIZE . ' characters long');
+ }
+
+ try {
+ if ($filesMetadata->getSyncToken() === '') {
+ $this->metadataRequestService->store($filesMetadata);
+ } else {
+ $this->metadataRequestService->updateMetadata($filesMetadata);
+ }
+ } catch (DBException $e) {
+ // most of the logged exception are the result of race condition
+ // between 2 simultaneous process trying to create/update metadata
+ $this->logger->warning('issue while saveMetadata', ['exception' => $e, 'metadata' => $filesMetadata]);
+
+ return;
+ }
+
+ // update indexes
+ foreach ($filesMetadata->getIndexes() as $index) {
+ try {
+ $this->indexRequestService->updateIndex($filesMetadata, $index);
+ } catch (DBException $e) {
+ $this->logger->warning('issue while updateIndex', ['exception' => $e]);
+ }
+ }
+
+ // update metadata types list
+ $current = $this->getKnownMetadata();
+ $current->import($filesMetadata->jsonSerialize(true));
+ $this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current));
+ }
+
+ /**
+ * @param int $fileId file id
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ */
+ public function deleteMetadata(int $fileId): void {
+ try {
+ $this->metadataRequestService->dropMetadata($fileId);
+ } catch (Exception $e) {
+ $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]);
+ }
+
+ try {
+ $this->indexRequestService->dropIndex($fileId);
+ } catch (Exception $e) {
+ $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]);
+ }
+ }
+
+ /**
+ * @param IQueryBuilder $qb
+ * @param string $fileTableAlias alias of the table that contains data about files
+ * @param string $fileIdField alias of the field that contains file ids
+ *
+ * @inheritDoc
+ * @return IMetadataQuery|null
+ * @see IMetadataQuery
+ * @since 28.0.0
+ */
+ public function getMetadataQuery(
+ IQueryBuilder $qb,
+ string $fileTableAlias,
+ string $fileIdField
+ ): ?IMetadataQuery {
+ if (!$this->metadataInitiated()) {
+ return null;
+ }
+
+ return new MetadataQuery($qb, $this->getKnownMetadata(), $fileTableAlias, $fileIdField);
+ }
+
+ /**
+ * @inheritDoc
+ * @return IFilesMetadata
+ * @since 28.0.0
+ */
+ public function getKnownMetadata(): IFilesMetadata {
+ if (null !== $this->all) {
+ return $this->all;
+ }
+ $this->all = new FilesMetadata();
+
+ try {
+ $data = json_decode($this->config->getAppValue('core', self::CONFIG_KEY, '[]'), true, 127, JSON_THROW_ON_ERROR);
+ $this->all->import($data);
+ } catch (JsonException) {
+ $this->logger->warning('issue while reading stored list of metadata. Advised to run ./occ files:scan --all --generate-metadata');
+ }
+
+ return $this->all;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param string $type metadata type
+ * @param bool $indexed TRUE if metadata can be search
+ * @param int $editPermission remote edit permission via Webdav PROPPATCH
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ * @see IMetadataValueWrapper::TYPE_INT
+ * @see IMetadataValueWrapper::TYPE_FLOAT
+ * @see IMetadataValueWrapper::TYPE_BOOL
+ * @see IMetadataValueWrapper::TYPE_ARRAY
+ * @see IMetadataValueWrapper::TYPE_STRING_LIST
+ * @see IMetadataValueWrapper::TYPE_INT_LIST
+ * @see IMetadataValueWrapper::TYPE_STRING
+ * @see IMetadataValueWrapper::EDIT_FORBIDDEN
+ * @see IMetadataValueWrapper::EDIT_REQ_OWNERSHIP
+ * @see IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION
+ * @see IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION
+ */
+ public function initMetadata(
+ string $key,
+ string $type,
+ bool $indexed = false,
+ int $editPermission = IMetadataValueWrapper::EDIT_FORBIDDEN
+ ): void {
+ $current = $this->getKnownMetadata();
+ try {
+ if ($current->getType($key) === $type
+ && $indexed === $current->isIndex($key)
+ && $editPermission === $current->getEditPermission($key)) {
+ return; // if key exists, with same type and indexed, we do nothing.
+ }
+ } catch (FilesMetadataNotFoundException) {
+ // if value does not exist, we keep on the writing of course
+ }
+
+ $current->import([$key => ['type' => $type, 'indexed' => $indexed, 'editPermission' => $editPermission]]);
+ $this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current));
+ $this->all = $current;
+ }
+
+ /**
+ * load listeners
+ *
+ * @param IEventDispatcher $eventDispatcher
+ */
+ public static function loadListeners(IEventDispatcher $eventDispatcher): void {
+ $eventDispatcher->addServiceListener(NodeWrittenEvent::class, MetadataUpdate::class);
+ $eventDispatcher->addServiceListener(CacheEntryRemovedEvent::class, MetadataDelete::class);
+ }
+
+ /**
+ * Will confirm that tables were created and store an app value to cache the result.
+ * Can be removed in 29 as this is to avoid strange situation when Nextcloud files were
+ * replaced but the upgrade was not triggered yet.
+ *
+ * @return bool
+ */
+ private function metadataInitiated(): bool {
+ if ($this->config->getAppValue('core', self::MIGRATION_DONE, '0') === '1') {
+ return true;
+ }
+
+ $dbConnection = \OCP\Server::get(IDBConnection::class);
+ if ($dbConnection->tableExists(MetadataRequestService::TABLE_METADATA)) {
+ $this->config->setAppValue('core', self::MIGRATION_DONE, '1');
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php b/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php
new file mode 100644
index 00000000000..d18c8aa3680
--- /dev/null
+++ b/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata\Job;
+
+use OC\FilesMetadata\FilesMetadataManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\Files\IRootFolder;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Simple background job, created when requested by an app during the
+ * dispatch of MetadataLiveEvent.
+ * This background job will re-run the event to refresh metadata on a non-live thread.
+ *
+ * @see MetadataLiveEvent::requestBackgroundJob()
+ * @since 28.0.0
+ */
+class UpdateSingleMetadata extends QueuedJob {
+ public function __construct(
+ ITimeFactory $time,
+ private IRootFolder $rootFolder,
+ private FilesMetadataManager $filesMetadataManager,
+ private LoggerInterface $logger
+ ) {
+ parent::__construct($time);
+ }
+
+ protected function run($argument) {
+ [$userId, $fileId] = $argument;
+
+ try {
+ $node = $this->rootFolder->getUserFolder($userId)->getById($fileId);
+ if (count($node) > 0) {
+ $file = array_shift($node);
+ $this->filesMetadataManager->refreshMetadata($file, IFilesMetadataManager::PROCESS_BACKGROUND);
+ }
+ } catch (\Exception $e) {
+ $this->logger->warning('issue while running UpdateSingleMetadata', ['exception' => $e, 'userId' => $userId, 'fileId' => $fileId]);
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/Listener/MetadataDelete.php b/lib/private/FilesMetadata/Listener/MetadataDelete.php
new file mode 100644
index 00000000000..d950c2cea5f
--- /dev/null
+++ b/lib/private/FilesMetadata/Listener/MetadataDelete.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata\Listener;
+
+use Exception;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\Cache\CacheEntryRemovedEvent;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Handle file deletion event and remove stored metadata related to the deleted file
+ *
+ * @template-implements IEventListener<CacheEntryRemovedEvent>
+ */
+class MetadataDelete implements IEventListener {
+ public function __construct(
+ private IFilesMetadataManager $filesMetadataManager,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof CacheEntryRemovedEvent)) {
+ return;
+ }
+
+ try {
+ $nodeId = $event->getFileId();
+ if ($nodeId > 0) {
+ $this->filesMetadataManager->deleteMetadata($nodeId);
+ }
+ } catch (Exception $e) {
+ $this->logger->warning('issue while running MetadataDelete', ['exception' => $e]);
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/Listener/MetadataUpdate.php b/lib/private/FilesMetadata/Listener/MetadataUpdate.php
new file mode 100644
index 00000000000..9848f079882
--- /dev/null
+++ b/lib/private/FilesMetadata/Listener/MetadataUpdate.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata\Listener;
+
+use Exception;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\Events\Node\NodeCreatedEvent;
+use OCP\Files\Events\Node\NodeWrittenEvent;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Handle file creation/modification events and initiate a new event related to the created/edited file.
+ * The generated new event is broadcast in order to obtain file related metadata from other apps.
+ * metadata will be stored in database.
+ *
+ * @template-implements IEventListener<NodeCreatedEvent|NodeWrittenEvent>
+ */
+class MetadataUpdate implements IEventListener {
+ public function __construct(
+ private IFilesMetadataManager $filesMetadataManager,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ /**
+ * @param Event $event
+ */
+ public function handle(Event $event): void {
+ if (!($event instanceof NodeWrittenEvent)) {
+ return;
+ }
+
+ try {
+ $this->filesMetadataManager->refreshMetadata($event->getNode());
+ } catch (Exception $e) {
+ $this->logger->warning('issue while running MetadataUpdate', ['exception' => $e]);
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/MetadataQuery.php b/lib/private/FilesMetadata/MetadataQuery.php
new file mode 100644
index 00000000000..aa079c678d7
--- /dev/null
+++ b/lib/private/FilesMetadata/MetadataQuery.php
@@ -0,0 +1,167 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata;
+
+use OC\FilesMetadata\Model\FilesMetadata;
+use OC\FilesMetadata\Service\IndexRequestService;
+use OC\FilesMetadata\Service\MetadataRequestService;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\IMetadataQuery;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+
+/**
+ * @inheritDoc
+ * @since 28.0.0
+ */
+class MetadataQuery implements IMetadataQuery {
+ private array $knownJoinedIndex = [];
+ public function __construct(
+ private IQueryBuilder $queryBuilder,
+ private IFilesMetadata $knownMetadata,
+ private string $fileTableAlias = 'fc',
+ private string $fileIdField = 'fileid',
+ private string $alias = 'meta',
+ private string $aliasIndexPrefix = 'meta_index'
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ * @see self::extractMetadata()
+ * @since 28.0.0
+ */
+ public function retrieveMetadata(): void {
+ $this->queryBuilder->selectAlias($this->alias . '.json', 'meta_json');
+ $this->queryBuilder->selectAlias($this->alias . '.sync_token', 'meta_sync_token');
+ $this->queryBuilder->leftJoin(
+ $this->fileTableAlias, MetadataRequestService::TABLE_METADATA, $this->alias,
+ $this->queryBuilder->expr()->eq($this->fileTableAlias . '.' . $this->fileIdField, $this->alias . '.file_id')
+ );
+ }
+
+ /**
+ * @param array $row result row
+ *
+ * @inheritDoc
+ * @return IFilesMetadata metadata
+ * @see self::retrieveMetadata()
+ * @since 28.0.0
+ */
+ public function extractMetadata(array $row): IFilesMetadata {
+ $fileId = (array_key_exists($this->fileIdField, $row)) ? $row[$this->fileIdField] : 0;
+ $metadata = new FilesMetadata((int)$fileId);
+ try {
+ $metadata->importFromDatabase($row, $this->alias . '_');
+ } catch (FilesMetadataNotFoundException) {
+ // can be ignored as files' metadata are optional and might not exist in database
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @param string $metadataKey metadata key
+ * @param bool $enforce limit the request only to existing metadata
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ */
+ public function joinIndex(string $metadataKey, bool $enforce = false): string {
+ if (array_key_exists($metadataKey, $this->knownJoinedIndex)) {
+ return $this->knownJoinedIndex[$metadataKey];
+ }
+
+ $aliasIndex = $this->aliasIndexPrefix . '_' . count($this->knownJoinedIndex);
+ $this->knownJoinedIndex[$metadataKey] = $aliasIndex;
+
+ $expr = $this->queryBuilder->expr();
+ $andX = $expr->andX($expr->eq($aliasIndex . '.file_id', $this->fileTableAlias . '.' . $this->fileIdField));
+ $andX->add($expr->eq($this->getMetadataKeyField($metadataKey), $this->queryBuilder->createNamedParameter($metadataKey)));
+
+ if ($enforce) {
+ $this->queryBuilder->innerJoin(
+ $this->fileTableAlias,
+ IndexRequestService::TABLE_METADATA_INDEX,
+ $aliasIndex,
+ $andX
+ );
+ } else {
+ $this->queryBuilder->leftJoin(
+ $this->fileTableAlias,
+ IndexRequestService::TABLE_METADATA_INDEX,
+ $aliasIndex,
+ $andX
+ );
+ }
+
+ return $aliasIndex;
+ }
+
+ /**
+ * @throws FilesMetadataNotFoundException
+ */
+ private function joinedTableAlias(string $metadataKey): string {
+ if (!array_key_exists($metadataKey, $this->knownJoinedIndex)) {
+ throw new FilesMetadataNotFoundException('table related to ' . $metadataKey . ' not initiated, you need to use leftJoin() first.');
+ }
+
+ return $this->knownJoinedIndex[$metadataKey];
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $metadataKey metadata key
+ *
+ * @return string table field
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function getMetadataKeyField(string $metadataKey): string {
+ return $this->joinedTableAlias($metadataKey) . '.meta_key';
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $metadataKey metadata key
+ *
+ * @return string table field
+ * @throws FilesMetadataNotFoundException if metadataKey is not known
+ * @throws FilesMetadataTypeException is metadataKey is not set as indexed
+ * @since 28.0.0
+ */
+ public function getMetadataValueField(string $metadataKey): string {
+ return match ($this->knownMetadata->getType($metadataKey)) {
+ IMetadataValueWrapper::TYPE_STRING => $this->joinedTableAlias($metadataKey) . '.meta_value_string',
+ IMetadataValueWrapper::TYPE_INT, IMetadataValueWrapper::TYPE_BOOL => $this->joinedTableAlias($metadataKey) . '.meta_value_int',
+ default => throw new FilesMetadataTypeException('metadata is not set as indexed'),
+ };
+ }
+}
diff --git a/lib/private/FilesMetadata/Model/FilesMetadata.php b/lib/private/FilesMetadata/Model/FilesMetadata.php
new file mode 100644
index 00000000000..629b537dabe
--- /dev/null
+++ b/lib/private/FilesMetadata/Model/FilesMetadata.php
@@ -0,0 +1,621 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata\Model;
+
+use JsonException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataKeyFormatException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+
+/**
+ * Model that represent metadata linked to a specific file.
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ */
+class FilesMetadata implements IFilesMetadata {
+ /** @var array<string, MetadataValueWrapper> */
+ private array $metadata = [];
+ private bool $updated = false;
+ private int $lastUpdate = 0;
+ private string $syncToken = '';
+
+ public function __construct(
+ private int $fileId = 0
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ * @return int related file id
+ * @since 28.0.0
+ */
+ public function getFileId(): int {
+ return $this->fileId;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int timestamp
+ * @since 28.0.0
+ */
+ public function lastUpdateTimestamp(): int {
+ return $this->lastUpdate;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string token
+ * @since 28.0.0
+ */
+ public function getSyncToken(): string {
+ return $this->syncToken;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string[] list of keys
+ * @since 28.0.0
+ */
+ public function getKeys(): array {
+ return array_keys($this->metadata);
+ }
+
+ /**
+ * @param string $needle metadata key to search
+ *
+ * @inheritDoc
+ * @return bool TRUE if key exist
+ * @since 28.0.0
+ */
+ public function hasKey(string $needle): bool {
+ return (in_array($needle, $this->getKeys()));
+ }
+
+ /**
+ * @inheritDoc
+ * @return string[] list of indexes
+ * @since 28.0.0
+ */
+ public function getIndexes(): array {
+ $indexes = [];
+ foreach ($this->getKeys() as $key) {
+ if ($this->metadata[$key]->isIndexed()) {
+ $indexes[] = $key;
+ }
+ }
+
+ return $indexes;
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return bool TRUE if key exists and is set as indexed
+ * @since 28.0.0
+ */
+ public function isIndex(string $key): bool {
+ return $this->metadata[$key]?->isIndexed() ?? false;
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return int edit permission
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function getEditPermission(string $key): int {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getEditPermission();
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param int $permission edit permission
+ *
+ * @inheritDoc
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function setEditPermission(string $key, int $permission): void {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ $this->metadata[$key]->setEditPermission($permission);
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return string metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getString(string $key): string {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueString();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return int metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getInt(string $key): int {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueInt();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return float metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getFloat(string $key): float {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueFloat();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return bool metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getBool(string $key): bool {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueBool();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return array metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getArray(string $key): array {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueArray();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return string[] metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getStringList(string $key): array {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueStringList();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return int[] metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getIntList(string $key): array {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueIntList();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return string value type
+ * @throws FilesMetadataNotFoundException
+ * @see IMetadataValueWrapper::TYPE_STRING
+ * @see IMetadataValueWrapper::TYPE_INT
+ * @see IMetadataValueWrapper::TYPE_FLOAT
+ * @see IMetadataValueWrapper::TYPE_BOOL
+ * @see IMetadataValueWrapper::TYPE_ARRAY
+ * @see IMetadataValueWrapper::TYPE_STRING_LIST
+ * @see IMetadataValueWrapper::TYPE_INT_LIST
+ * @since 28.0.0
+ */
+ public function getType(string $key): string {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getType();
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param string $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setString(string $key, string $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getString($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value and index have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING);
+ $this->updated = true;
+ $this->metadata[$key] = $meta->setValueString($value)->setIndexed($index);
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param int $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setInt(string $key, int $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getInt($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_INT);
+ $this->metadata[$key] = $meta->setValueInt($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param float $value metadata value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setFloat(string $key, float $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getFloat($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_FLOAT);
+ $this->metadata[$key] = $meta->setValueFloat($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+
+ /**
+ * @param string $key metadata key
+ * @param bool $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setBool(string $key, bool $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getBool($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_BOOL);
+ $this->metadata[$key] = $meta->setValueBool($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+
+ /**
+ * @param string $key metadata key
+ * @param array $value metadata value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setArray(string $key, array $value): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getArray($key) === $value) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_ARRAY);
+ $this->metadata[$key] = $meta->setValueArray($value);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param string[] $value metadata value
+ * @param bool $index set TRUE if each values from the list must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setStringList(string $key, array $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getStringList($key) === $value) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST);
+ $this->metadata[$key] = $meta->setValueStringList($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param int[] $value metadata value
+ * @param bool $index set TRUE if each values from the list must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setIntList(string $key, array $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getIntList($key) === $value) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $valueWrapper = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST);
+ $this->metadata[$key] = $valueWrapper->setValueIntList($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function unset(string $key): IFilesMetadata {
+ if (!array_key_exists($key, $this->metadata)) {
+ return $this;
+ }
+
+ unset($this->metadata[$key]);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $keyPrefix metadata key prefix
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function removeStartsWith(string $keyPrefix): IFilesMetadata {
+ if ($keyPrefix === '') {
+ return $this;
+ }
+
+ foreach ($this->getKeys() as $key) {
+ if (str_starts_with($key, $keyPrefix)) {
+ $this->unset($key);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return void
+ * @throws FilesMetadataKeyFormatException
+ */
+ private function confirmKeyFormat(string $key): void {
+ $acceptedChars = ['-', '_'];
+ if (ctype_alnum(str_replace($acceptedChars, '', $key))) {
+ return;
+ }
+
+ throw new FilesMetadataKeyFormatException('key can only contains alphanumerical characters, and dash (-, _)');
+ }
+
+ /**
+ * @inheritDoc
+ * @return bool TRUE if metadata have been modified
+ * @since 28.0.0
+ */
+ public function updated(): bool {
+ return $this->updated;
+ }
+
+ public function jsonSerialize(bool $emptyValues = false): array {
+ $data = [];
+ foreach ($this->metadata as $metaKey => $metaValueWrapper) {
+ $data[$metaKey] = $metaValueWrapper->jsonSerialize($emptyValues);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return array<string, string|int|bool|float|string[]|int[]>
+ */
+ public function asArray(): array {
+ $data = [];
+ foreach ($this->metadata as $metaKey => $metaValueWrapper) {
+ try {
+ $data[$metaKey] = $metaValueWrapper->getValueAny();
+ } catch (FilesMetadataNotFoundException $e) {
+ // ignore exception
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @inheritDoc
+ * @return IFilesMetadata
+ * @since 28.0.0
+ */
+ public function import(array $data): IFilesMetadata {
+ foreach ($data as $k => $v) {
+ $valueWrapper = new MetadataValueWrapper();
+ $this->metadata[$k] = $valueWrapper->import($v);
+ }
+ $this->updated = false;
+
+ return $this;
+ }
+
+ /**
+ * import data from database to configure this model
+ *
+ * @param array $data
+ * @param string $prefix
+ *
+ * @return IFilesMetadata
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function importFromDatabase(array $data, string $prefix = ''): IFilesMetadata {
+ try {
+ $this->syncToken = $data[$prefix . 'sync_token'] ?? '';
+
+ return $this->import(
+ json_decode(
+ $data[$prefix . 'json'] ?? '[]',
+ true,
+ 512,
+ JSON_THROW_ON_ERROR
+ )
+ );
+ } catch (JsonException) {
+ throw new FilesMetadataNotFoundException();
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/Model/MetadataValueWrapper.php b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php
new file mode 100644
index 00000000000..90f1554180d
--- /dev/null
+++ b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php
@@ -0,0 +1,421 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata\Model;
+
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+
+/**
+ * @inheritDoc
+ * @see IFilesMetadata
+ * @since 28.0.0
+ */
+class MetadataValueWrapper implements IMetadataValueWrapper {
+ private string $type;
+ /** @var string|int|float|bool|array|string[]|int[] */
+ private mixed $value = null;
+ private bool $indexed = false;
+ private int $editPermission = self::EDIT_FORBIDDEN;
+
+ /**
+ * @param string $type value type
+ *
+ * @inheritDoc
+ * @see self::TYPE_INT
+ * @see self::TYPE_FLOAT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @since 28.0.0
+ */
+ public function __construct(string $type = '') {
+ $this->type = $type;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string value type
+ * @see self::TYPE_INT
+ * @see self::TYPE_FLOAT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @since 28.0.0
+ */
+ public function getType(): string {
+ return $this->type;
+ }
+
+ /**
+ * @param string $type value type
+ *
+ * @inheritDoc
+ * @return bool
+ * @see self::TYPE_INT
+ * @see self::TYPE_FLOAT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @since 28.0.0
+ */
+ public function isType(string $type): bool {
+ return (strtolower($type) === strtolower($this->type));
+ }
+
+ /**
+ * @param string $type value type
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if type cannot be confirmed
+ * @see self::TYPE_INT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @see self::TYPE_FLOAT
+ * @since 28.0.0
+ */
+ public function assertType(string $type): self {
+ if (!$this->isType($type)) {
+ throw new FilesMetadataTypeException('type is \'' . $this->getType() . '\', expecting \'' . $type . '\'');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $value string to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string
+ * @since 28.0.0
+ */
+ public function setValueString(string $value): self {
+ $this->assertType(self::TYPE_STRING);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param int $value int to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int
+ * @since 28.0.0
+ */
+ public function setValueInt(int $value): self {
+ $this->assertType(self::TYPE_INT);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param float $value float to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a float
+ * @since 28.0.0
+ */
+ public function setValueFloat(float $value): self {
+ $this->assertType(self::TYPE_FLOAT);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param bool $value bool to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a bool
+ * @since 28.0.0
+ */
+ public function setValueBool(bool $value): self {
+ $this->assertType(self::TYPE_BOOL);
+ $this->value = $value;
+
+
+ return $this;
+ }
+
+ /**
+ * @param array $value array to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an array
+ * @since 28.0.0
+ */
+ public function setValueArray(array $value): self {
+ $this->assertType(self::TYPE_ARRAY);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $value string list to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string list
+ * @since 28.0.0
+ */
+ public function setValueStringList(array $value): self {
+ $this->assertType(self::TYPE_STRING_LIST);
+ // TODO confirm value is an array or string ?
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param int[] $value int list to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int list
+ * @since 28.0.0
+ */
+ public function setValueIntList(array $value): self {
+ $this->assertType(self::TYPE_INT_LIST);
+ // TODO confirm value is an array of int ?
+ $this->value = $value;
+
+ return $this;
+ }
+
+
+ /**
+ * @inheritDoc
+ * @return string set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueString(): string {
+ $this->assertType(self::TYPE_STRING);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (string)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueInt(): int {
+ $this->assertType(self::TYPE_INT);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (int)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return float set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a float
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueFloat(): float {
+ $this->assertType(self::TYPE_FLOAT);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (float)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return bool set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a bool
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueBool(): bool {
+ $this->assertType(self::TYPE_BOOL);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (bool)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return array set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store an array
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueArray(): array {
+ $this->assertType(self::TYPE_ARRAY);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (array)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string[] set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string list
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueStringList(): array {
+ $this->assertType(self::TYPE_STRING_LIST);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (array)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int[] set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int list
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueIntList(): array {
+ $this->assertType(self::TYPE_INT_LIST);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (array)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string|int|float|bool|array|string[]|int[] set value
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueAny(): mixed {
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return $this->value;
+ }
+
+ /**
+ * @param bool $indexed TRUE to set the stored value as an indexed value
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function setIndexed(bool $indexed): self {
+ $this->indexed = $indexed;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ * @return bool TRUE if value is an indexed value
+ * @since 28.0.0
+ */
+ public function isIndexed(): bool {
+ return $this->indexed;
+ }
+
+ /**
+ * @param int $permission edit permission
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function setEditPermission(int $permission): self {
+ $this->editPermission = $permission;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int edit permission
+ * @since 28.0.0
+ */
+ public function getEditPermission(): int {
+ return $this->editPermission;
+ }
+
+ /**
+ * @param array $data serialized version of the object
+ *
+ * @inheritDoc
+ * @return self
+ * @see jsonSerialize
+ * @since 28.0.0
+ */
+ public function import(array $data): self {
+ $this->value = $data['value'] ?? null;
+ $this->type = $data['type'] ?? '';
+ $this->setIndexed($data['indexed'] ?? false);
+ $this->setEditPermission($data['editPermission'] ?? self::EDIT_FORBIDDEN);
+ return $this;
+ }
+
+ public function jsonSerialize(bool $emptyValues = false): array {
+ return [
+ 'value' => ($emptyValues) ? null : $this->value,
+ 'type' => $this->getType(),
+ 'indexed' => $this->isIndexed(),
+ 'editPermission' => $this->getEditPermission()
+ ];
+ }
+}
diff --git a/lib/private/FilesMetadata/Service/IndexRequestService.php b/lib/private/FilesMetadata/Service/IndexRequestService.php
new file mode 100644
index 00000000000..2a23e2c9a67
--- /dev/null
+++ b/lib/private/FilesMetadata/Service/IndexRequestService.php
@@ -0,0 +1,195 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata\Service;
+
+use OCP\DB\Exception as DbException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
+
+/**
+ * manage sql request to the metadata_index table
+ */
+class IndexRequestService {
+ public const TABLE_METADATA_INDEX = 'files_metadata_index';
+
+ public function __construct(
+ private IDBConnection $dbConnection,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ /**
+ * update the index for a specific metadata key
+ *
+ * @param IFilesMetadata $filesMetadata metadata
+ * @param string $key metadata key to update
+ *
+ * @throws DbException
+ */
+ public function updateIndex(IFilesMetadata $filesMetadata, string $key): void {
+ $fileId = $filesMetadata->getFileId();
+ try {
+ $metadataType = $filesMetadata->getType($key);
+ } catch (FilesMetadataNotFoundException $e) {
+ return;
+ }
+
+ /**
+ * might look harsh, but a lot simpler than comparing current indexed data, as we can expect
+ * conflict with a change of types.
+ * We assume that each time one random metadata were modified we can drop all index for this
+ * key and recreate them.
+ * To make it slightly cleaner, we'll use transaction
+ */
+ $this->dbConnection->beginTransaction();
+ try {
+ $this->dropIndex($fileId, $key);
+ match ($metadataType) {
+ IMetadataValueWrapper::TYPE_STRING => $this->insertIndexString($fileId, $key, $filesMetadata->getString($key)),
+ IMetadataValueWrapper::TYPE_INT => $this->insertIndexInt($fileId, $key, $filesMetadata->getInt($key)),
+ IMetadataValueWrapper::TYPE_BOOL => $this->insertIndexBool($fileId, $key, $filesMetadata->getBool($key)),
+ IMetadataValueWrapper::TYPE_STRING_LIST => $this->insertIndexStringList($fileId, $key, $filesMetadata->getStringList($key)),
+ IMetadataValueWrapper::TYPE_INT_LIST => $this->insertIndexIntList($fileId, $key, $filesMetadata->getIntList($key))
+ };
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException|DbException $e) {
+ $this->dbConnection->rollBack();
+ $this->logger->warning('issue while updateIndex', ['exception' => $e, 'fileId' => $fileId, 'key' => $key]);
+ }
+
+ $this->dbConnection->commit();
+ }
+
+ /**
+ * insert a new entry in the metadata_index table for a string value
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param string $value metadata value
+ *
+ * @throws DbException
+ */
+ private function insertIndexString(int $fileId, string $key, string $value): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA_INDEX)
+ ->setValue('meta_key', $qb->createNamedParameter($key))
+ ->setValue('meta_value_string', $qb->createNamedParameter($value))
+ ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
+ $qb->executeStatement();
+ }
+
+ /**
+ * insert a new entry in the metadata_index table for an int value
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param int $value metadata value
+ *
+ * @throws DbException
+ */
+ public function insertIndexInt(int $fileId, string $key, int $value): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA_INDEX)
+ ->setValue('meta_key', $qb->createNamedParameter($key))
+ ->setValue('meta_value_int', $qb->createNamedParameter($value, IQueryBuilder::PARAM_INT))
+ ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
+ $qb->executeStatement();
+ }
+
+ /**
+ * insert a new entry in the metadata_index table for a bool value
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param bool $value metadata value
+ *
+ * @throws DbException
+ */
+ public function insertIndexBool(int $fileId, string $key, bool $value): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA_INDEX)
+ ->setValue('meta_key', $qb->createNamedParameter($key))
+ ->setValue('meta_value_int', $qb->createNamedParameter(($value) ? '1' : '0', IQueryBuilder::PARAM_INT))
+ ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
+ $qb->executeStatement();
+ }
+
+ /**
+ * insert entries in the metadata_index table for list of string
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param string[] $values metadata values
+ *
+ * @throws DbException
+ */
+ public function insertIndexStringList(int $fileId, string $key, array $values): void {
+ foreach ($values as $value) {
+ $this->insertIndexString($fileId, $key, $value);
+ }
+ }
+
+ /**
+ * insert entries in the metadata_index table for list of int
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param int[] $values metadata values
+ *
+ * @throws DbException
+ */
+ public function insertIndexIntList(int $fileId, string $key, array $values): void {
+ foreach ($values as $value) {
+ $this->insertIndexInt($fileId, $key, $value);
+ }
+ }
+
+ /**
+ * drop indexes related to a file id
+ * if a key is specified, only drop entries related to it
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ *
+ * @throws DbException
+ */
+ public function dropIndex(int $fileId, string $key = ''): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $expr = $qb->expr();
+ $qb->delete(self::TABLE_METADATA_INDEX)
+ ->where($expr->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
+
+ if ($key !== '') {
+ $qb->andWhere($expr->eq('meta_key', $qb->createNamedParameter($key)));
+ }
+
+ $qb->executeStatement();
+ }
+}
diff --git a/lib/private/FilesMetadata/Service/MetadataRequestService.php b/lib/private/FilesMetadata/Service/MetadataRequestService.php
new file mode 100644
index 00000000000..cdce624d75c
--- /dev/null
+++ b/lib/private/FilesMetadata/Service/MetadataRequestService.php
@@ -0,0 +1,194 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @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\FilesMetadata\Service;
+
+use OC\FilesMetadata\Model\FilesMetadata;
+use OCP\DB\Exception;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
+
+/**
+ * manage sql request to the metadata table
+ */
+class MetadataRequestService {
+ public const TABLE_METADATA = 'files_metadata';
+
+ public function __construct(
+ private IDBConnection $dbConnection,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ /**
+ * store metadata into database
+ *
+ * @param IFilesMetadata $filesMetadata
+ *
+ * @throws Exception
+ */
+ public function store(IFilesMetadata $filesMetadata): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA)
+ ->setValue('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT))
+ ->setValue('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize())))
+ ->setValue('sync_token', $qb->createNamedParameter($this->generateSyncToken()))
+ ->setValue('last_update', (string) $qb->createFunction('NOW()'));
+ $qb->executeStatement();
+ }
+
+ /**
+ * returns metadata for a file id
+ *
+ * @param int $fileId file id
+ *
+ * @return IFilesMetadata
+ * @throws FilesMetadataNotFoundException if no metadata are found in database
+ */
+ public function getMetadataFromFileId(int $fileId): IFilesMetadata {
+ try {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('json', 'sync_token')->from(self::TABLE_METADATA);
+ $qb->where(
+ $qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))
+ );
+ $result = $qb->executeQuery();
+ $data = $result->fetch();
+ $result->closeCursor();
+ } catch (Exception $e) {
+ $this->logger->warning(
+ 'exception while getMetadataFromDatabase()', ['exception' => $e, 'fileId' => $fileId]
+ );
+ throw new FilesMetadataNotFoundException();
+ }
+
+ if ($data === false) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ $metadata = new FilesMetadata($fileId);
+ $metadata->importFromDatabase($data);
+
+ return $metadata;
+ }
+
+ /**
+ * returns metadata for multiple file ids
+ *
+ * If
+ *
+ * @param array $fileIds file ids
+ *
+ * @return array File ID is the array key, files without metadata are not returned in the array
+ * @psalm-return array<int, IFilesMetadata>
+ */
+ public function getMetadataFromFileIds(array $fileIds): array {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('file_id', 'json', 'sync_token')->from(self::TABLE_METADATA);
+ $qb->where(
+ $qb->expr()->in('file_id', $qb->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY))
+ );
+
+ $list = [];
+ $result = $qb->executeQuery();
+ while ($data = $result->fetch()) {
+ $fileId = (int) $data['file_id'];
+ $metadata = new FilesMetadata($fileId);
+ try {
+ $metadata->importFromDatabase($data);
+ } catch (FilesMetadataNotFoundException) {
+ continue;
+ }
+ $list[$fileId] = $metadata;
+ }
+ $result->closeCursor();
+
+ return $list;
+ }
+
+ /**
+ * drop metadata related to a file id
+ *
+ * @param int $fileId file id
+ *
+ * @return void
+ * @throws Exception
+ */
+ public function dropMetadata(int $fileId): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->delete(self::TABLE_METADATA)
+ ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
+ $qb->executeStatement();
+ }
+
+ /**
+ * update metadata in the database
+ *
+ * @param IFilesMetadata $filesMetadata metadata
+ *
+ * @return int number of affected rows
+ * @throws Exception
+ */
+ public function updateMetadata(IFilesMetadata $filesMetadata): int {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $expr = $qb->expr();
+
+ $qb->update(self::TABLE_METADATA)
+ ->set('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize())))
+ ->set('sync_token', $qb->createNamedParameter($this->generateSyncToken()))
+ ->set('last_update', $qb->createFunction('NOW()'))
+ ->where(
+ $expr->andX(
+ $expr->eq('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT)),
+ $expr->eq('sync_token', $qb->createNamedParameter($filesMetadata->getSyncToken()))
+ )
+ );
+
+ return $qb->executeStatement();
+ }
+
+ /**
+ * generate a random token
+ * @return string
+ */
+ private function generateSyncToken(): string {
+ $chars = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
+
+ $str = '';
+ $max = strlen($chars);
+ for ($i = 0; $i < 7; $i++) {
+ try {
+ $str .= $chars[random_int(0, $max - 2)];
+ } catch (\Exception $e) {
+ $this->logger->warning('exception during generateSyncToken', ['exception' => $e]);
+ }
+ }
+
+ return $str;
+ }
+}
diff --git a/lib/private/Group/Database.php b/lib/private/Group/Database.php
index 55792ce1dff..13837eef552 100644
--- a/lib/private/Group/Database.php
+++ b/lib/private/Group/Database.php
@@ -30,6 +30,7 @@
namespace OC\Group;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OC\User\LazyUser;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Group\Backend\ABackend;
use OCP\Group\Backend\IAddToGroupBackend;
@@ -40,13 +41,12 @@ use OCP\Group\Backend\ICreateGroupBackend;
use OCP\Group\Backend\IDeleteGroupBackend;
use OCP\Group\Backend\IGetDisplayNameBackend;
use OCP\Group\Backend\IGroupDetailsBackend;
+use OCP\Group\Backend\INamedBackend;
use OCP\Group\Backend\IRemoveFromGroupBackend;
use OCP\Group\Backend\ISearchableGroupBackend;
use OCP\Group\Backend\ISetDisplayNameBackend;
-use OCP\Group\Backend\INamedBackend;
use OCP\IDBConnection;
use OCP\IUserManager;
-use OC\User\LazyUser;
/**
* Class for group management in a SQL Database (e.g. MySQL, SQLite)
diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php
index 441ee64604d..d8d1a73762d 100644
--- a/lib/private/Group/Group.php
+++ b/lib/private/Group/Group.php
@@ -35,13 +35,6 @@ namespace OC\Group;
use OC\Hooks\PublicEmitter;
use OC\User\LazyUser;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Group\Events\BeforeGroupDeletedEvent;
-use OCP\Group\Events\BeforeUserAddedEvent;
-use OCP\Group\Events\BeforeUserRemovedEvent;
-use OCP\Group\Events\GroupDeletedEvent;
-use OCP\Group\Events\UserAddedEvent;
-use OCP\Group\Events\UserRemovedEvent;
-use OCP\GroupInterface;
use OCP\Group\Backend\ICountDisabledInGroup;
use OCP\Group\Backend\IGetDisplayNameBackend;
use OCP\Group\Backend\IHideFromCollaborationBackend;
@@ -49,7 +42,14 @@ use OCP\Group\Backend\INamedBackend;
use OCP\Group\Backend\ISearchableGroupBackend;
use OCP\Group\Backend\ISetDisplayNameBackend;
use OCP\Group\Events\BeforeGroupChangedEvent;
+use OCP\Group\Events\BeforeGroupDeletedEvent;
+use OCP\Group\Events\BeforeUserAddedEvent;
+use OCP\Group\Events\BeforeUserRemovedEvent;
use OCP\Group\Events\GroupChangedEvent;
+use OCP\Group\Events\GroupDeletedEvent;
+use OCP\Group\Events\UserAddedEvent;
+use OCP\Group\Events\UserRemovedEvent;
+use OCP\GroupInterface;
use OCP\IGroup;
use OCP\IUser;
use OCP\IUserManager;
@@ -85,11 +85,11 @@ class Group implements IGroup {
$this->displayName = $displayName;
}
- public function getGID() {
+ public function getGID(): string {
return $this->gid;
}
- public function getDisplayName() {
+ public function getDisplayName(): string {
if (is_null($this->displayName)) {
foreach ($this->backends as $backend) {
if ($backend instanceof IGetDisplayNameBackend) {
@@ -126,7 +126,7 @@ class Group implements IGroup {
*
* @return \OC\User\User[]
*/
- public function getUsers() {
+ public function getUsers(): array {
if ($this->usersLoaded) {
return $this->users;
}
@@ -153,7 +153,7 @@ class Group implements IGroup {
* @param IUser $user
* @return bool
*/
- public function inGroup(IUser $user) {
+ public function inGroup(IUser $user): bool {
if (isset($this->users[$user->getUID()])) {
return true;
}
@@ -171,7 +171,7 @@ class Group implements IGroup {
*
* @param IUser $user
*/
- public function addUser(IUser $user) {
+ public function addUser(IUser $user): void {
if ($this->inGroup($user)) {
return;
}
@@ -200,10 +200,8 @@ class Group implements IGroup {
/**
* remove a user from the group
- *
- * @param \OC\User\User $user
*/
- public function removeUser($user) {
+ public function removeUser(IUser $user): void {
$result = false;
$this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($this, $user));
if ($this->emitter) {
@@ -262,7 +260,7 @@ class Group implements IGroup {
* @param string $search
* @return int|bool
*/
- public function count($search = '') {
+ public function count($search = ''): int|bool {
$users = false;
foreach ($this->backends as $backend) {
if ($backend->implementsActions(\OC\Group\Backend::COUNT_USERS)) {
@@ -282,7 +280,7 @@ class Group implements IGroup {
*
* @return int|bool
*/
- public function countDisabled() {
+ public function countDisabled(): int|bool {
$users = false;
foreach ($this->backends as $backend) {
if ($backend instanceof ICountDisabledInGroup) {
@@ -306,7 +304,7 @@ class Group implements IGroup {
* @return IUser[]
* @deprecated 27.0.0 Use searchUsers instead (same implementation)
*/
- public function searchDisplayName($search, $limit = null, $offset = null) {
+ public function searchDisplayName(string $search, int $limit = null, int $offset = null): array {
return $this->searchUsers($search, $limit, $offset);
}
@@ -315,7 +313,7 @@ class Group implements IGroup {
*
* @return string[]
*/
- public function getBackendNames() {
+ public function getBackendNames(): array {
$backends = [];
foreach ($this->backends as $backend) {
if ($backend instanceof INamedBackend) {
@@ -329,11 +327,11 @@ class Group implements IGroup {
}
/**
- * delete the group
+ * Delete the group
*
* @return bool
*/
- public function delete() {
+ public function delete(): bool {
// Prevent users from deleting group admin
if ($this->getGID() === 'admin') {
return false;
@@ -378,7 +376,7 @@ class Group implements IGroup {
* @return bool
* @since 14.0.0
*/
- public function canRemoveUser() {
+ public function canRemoveUser(): bool {
foreach ($this->backends as $backend) {
if ($backend->implementsActions(GroupInterface::REMOVE_FROM_GOUP)) {
return true;
@@ -391,7 +389,7 @@ class Group implements IGroup {
* @return bool
* @since 14.0.0
*/
- public function canAddUser() {
+ public function canAddUser(): bool {
foreach ($this->backends as $backend) {
if ($backend->implementsActions(GroupInterface::ADD_TO_GROUP)) {
return true;
diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php
index 47475121ea0..eb9daebb222 100644
--- a/lib/private/Group/Manager.php
+++ b/lib/private/Group/Manager.php
@@ -89,9 +89,9 @@ class Manager extends PublicEmitter implements IGroupManager {
private DisplayNameCache $displayNameCache;
public function __construct(\OC\User\Manager $userManager,
- IEventDispatcher $dispatcher,
- LoggerInterface $logger,
- ICacheFactory $cacheFactory) {
+ IEventDispatcher $dispatcher,
+ LoggerInterface $logger,
+ ICacheFactory $cacheFactory) {
$this->userManager = $userManager;
$this->dispatcher = $dispatcher;
$this->logger = $logger;
@@ -371,7 +371,7 @@ class Manager extends PublicEmitter implements IGroupManager {
* @return bool if in group
*/
public function isInGroup($userId, $group) {
- return array_search($group, $this->getUserIdGroupIds($userId)) !== false;
+ return in_array($group, $this->getUserIdGroupIds($userId));
}
/**
diff --git a/lib/private/Group/MetaData.php b/lib/private/Group/MetaData.php
index a58d7e78bfc..973db134728 100644
--- a/lib/private/Group/MetaData.php
+++ b/lib/private/Group/MetaData.php
@@ -57,11 +57,11 @@ class MetaData {
* @param bool $isAdmin whether the current users is an admin
*/
public function __construct(
- string $user,
- bool $isAdmin,
- IGroupManager $groupManager,
- IUserSession $userSession
- ) {
+ string $user,
+ bool $isAdmin,
+ IGroupManager $groupManager,
+ IUserSession $userSession
+ ) {
$this->user = $user;
$this->isAdmin = $isAdmin;
$this->groupManager = $groupManager;
diff --git a/lib/private/Hooks/EmitterTrait.php b/lib/private/Hooks/EmitterTrait.php
index da4e3da2bd6..fe9cba893de 100644
--- a/lib/private/Hooks/EmitterTrait.php
+++ b/lib/private/Hooks/EmitterTrait.php
@@ -43,7 +43,7 @@ trait EmitterTrait {
if (!isset($this->listeners[$eventName])) {
$this->listeners[$eventName] = [];
}
- if (array_search($callback, $this->listeners[$eventName], true) === false) {
+ if (!in_array($callback, $this->listeners[$eventName], true)) {
$this->listeners[$eventName][] = $callback;
}
}
diff --git a/lib/private/Http/CookieHelper.php b/lib/private/Http/CookieHelper.php
index 720a1e9185d..eedb6e05c39 100644
--- a/lib/private/Http/CookieHelper.php
+++ b/lib/private/Http/CookieHelper.php
@@ -33,13 +33,13 @@ class CookieHelper {
public const SAMESITE_STRICT = 2;
public static function setCookie(string $name,
- string $value = '',
- int $maxAge = 0,
- string $path = '',
- string $domain = '',
- bool $secure = false,
- bool $httponly = false,
- int $samesite = self::SAMESITE_NONE) {
+ string $value = '',
+ int $maxAge = 0,
+ string $path = '',
+ string $domain = '',
+ bool $secure = false,
+ bool $httponly = false,
+ int $samesite = self::SAMESITE_NONE) {
$header = sprintf(
'Set-Cookie: %s=%s',
$name,
diff --git a/lib/private/Http/WellKnown/RequestManager.php b/lib/private/Http/WellKnown/RequestManager.php
index b83ff2ada50..783b04c0f5d 100644
--- a/lib/private/Http/WellKnown/RequestManager.php
+++ b/lib/private/Http/WellKnown/RequestManager.php
@@ -49,8 +49,8 @@ class RequestManager {
private $logger;
public function __construct(Coordinator $coordinator,
- IServerContainer $container,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ LoggerInterface $logger) {
$this->coordinator = $coordinator;
$this->container = $container;
$this->logger = $logger;
diff --git a/lib/private/Installer.php b/lib/private/Installer.php
index dc81135b644..dd4f1f790e3 100644
--- a/lib/private/Installer.php
+++ b/lib/private/Installer.php
@@ -53,6 +53,7 @@ use OCP\HintException;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\ITempManager;
+use OCP\Migration\IOutput;
use phpseclib\File\X509;
use Psr\Log\LoggerInterface;
@@ -536,7 +537,10 @@ class Installer {
* working ownCloud at the end instead of an aborted update.
* @return array Array of error messages (appid => Exception)
*/
- public static function installShippedApps($softErrors = false) {
+ public static function installShippedApps($softErrors = false, ?IOutput $output = null) {
+ if ($output instanceof IOutput) {
+ $output->debug('Installing shipped apps');
+ }
$appManager = \OC::$server->getAppManager();
$config = \OC::$server->getConfig();
$errors = [];
@@ -551,7 +555,7 @@ class Installer {
&& $config->getAppValue($filename, 'enabled') !== 'no') {
if ($softErrors) {
try {
- Installer::installShippedApp($filename);
+ Installer::installShippedApp($filename, $output);
} catch (HintException $e) {
if ($e->getPrevious() instanceof TableExistsException) {
$errors[$filename] = $e;
@@ -560,7 +564,7 @@ class Installer {
throw $e;
}
} else {
- Installer::installShippedApp($filename);
+ Installer::installShippedApp($filename, $output);
}
$config->setAppValue($filename, 'enabled', 'yes');
}
@@ -578,9 +582,12 @@ class Installer {
/**
* install an app already placed in the app folder
* @param string $app id of the app to install
- * @return integer
+ * @return string
*/
- public static function installShippedApp($app) {
+ public static function installShippedApp($app, ?IOutput $output = null) {
+ if ($output instanceof IOutput) {
+ $output->debug('Installing ' . $app);
+ }
//install the database
$appPath = OC_App::getAppPath($app);
\OC_App::registerAutoloading($app, $appPath);
@@ -588,6 +595,9 @@ class Installer {
$config = \OC::$server->getConfig();
$ms = new MigrationService($app, \OC::$server->get(Connection::class));
+ if ($output instanceof IOutput) {
+ $ms->setOutput($output);
+ }
$previousVersion = $config->getAppValue($app, 'installed_version', false);
$ms->migrate('latest', !$previousVersion);
@@ -598,6 +608,9 @@ class Installer {
if (is_null($info)) {
return false;
}
+ if ($output instanceof IOutput) {
+ $output->debug('Registering tasks of ' . $app);
+ }
\OC_App::setupBackgroundJobs($info['background-jobs']);
OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php
index a2ff62e4070..a5dec637bdb 100644
--- a/lib/private/IntegrityCheck/Checker.php
+++ b/lib/private/IntegrityCheck/Checker.php
@@ -83,12 +83,12 @@ class Checker {
* @param IMimeTypeDetector $mimeTypeDetector
*/
public function __construct(EnvironmentHelper $environmentHelper,
- FileAccessHelper $fileAccessHelper,
- AppLocator $appLocator,
- ?IConfig $config,
- ICacheFactory $cacheFactory,
- ?IAppManager $appManager,
- IMimeTypeDetector $mimeTypeDetector) {
+ FileAccessHelper $fileAccessHelper,
+ AppLocator $appLocator,
+ ?IConfig $config,
+ ICacheFactory $cacheFactory,
+ ?IAppManager $appManager,
+ IMimeTypeDetector $mimeTypeDetector) {
$this->environmentHelper = $environmentHelper;
$this->fileAccessHelper = $fileAccessHelper;
$this->appLocator = $appLocator;
@@ -161,7 +161,7 @@ class Checker {
* @return array Array of hashes.
*/
private function generateHashes(\RecursiveIteratorIterator $iterator,
- string $path): array {
+ string $path): array {
$hashes = [];
$baseDirectoryLength = \strlen($path);
@@ -223,8 +223,8 @@ class Checker {
* @return array
*/
private function createSignatureData(array $hashes,
- X509 $certificate,
- RSA $privateKey): array {
+ X509 $certificate,
+ RSA $privateKey): array {
ksort($hashes);
$privateKey->setSignatureMode(RSA::SIGNATURE_PSS);
@@ -249,8 +249,8 @@ class Checker {
* @throws \Exception
*/
public function writeAppSignature($path,
- X509 $certificate,
- RSA $privateKey) {
+ X509 $certificate,
+ RSA $privateKey) {
$appInfoDir = $path . '/appinfo';
try {
$this->fileAccessHelper->assertDirectoryExists($appInfoDir);
@@ -279,8 +279,8 @@ class Checker {
* @throws \Exception
*/
public function writeCoreSignature(X509 $certificate,
- RSA $rsa,
- $path) {
+ RSA $rsa,
+ $path) {
$coreDir = $path . '/core';
try {
$this->fileAccessHelper->assertDirectoryExists($coreDir);
diff --git a/lib/private/Log.php b/lib/private/Log.php
index d6750491d92..9975696ff06 100644
--- a/lib/private/Log.php
+++ b/lib/private/Log.php
@@ -38,6 +38,8 @@ namespace OC;
use Exception;
use Nextcloud\LogNormalizer\Normalizer;
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\Log\ExceptionSerializer;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger;
use OCP\IUserSession;
@@ -46,8 +48,6 @@ use OCP\Log\IDataLogger;
use OCP\Log\IFileBased;
use OCP\Log\IWriter;
use OCP\Support\CrashReport\IRegistry;
-use OC\AppFramework\Bootstrap\Coordinator;
-use OC\Log\ExceptionSerializer;
use Throwable;
use function array_merge;
use function strtr;
@@ -344,7 +344,7 @@ class Log implements ILogger, IDataLogger {
unset($data['app']);
unset($data['level']);
$data = array_merge($serializer->serializeException($exception), $data);
- $data = $this->interpolateMessage($data, $context['message'] ?? '--', 'CustomMessage');
+ $data = $this->interpolateMessage($data, isset($context['message']) && $context['message'] !== '' ? $context['message'] : ('Exception thrown: ' . get_class($exception)), 'CustomMessage');
array_walk($context, [$this->normalizer, 'format']);
diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php
index 16d6ae32f72..ab8fcea4e6a 100644
--- a/lib/private/Memcache/Factory.php
+++ b/lib/private/Memcache/Factory.php
@@ -32,10 +32,10 @@
namespace OC\Memcache;
use OCP\Cache\CappedMemoryCache;
-use OCP\Profiler\IProfiler;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IMemcache;
+use OCP\Profiler\IProfiler;
use Psr\Log\LoggerInterface;
class Factory implements ICacheFactory {
diff --git a/lib/private/Metadata/Capabilities.php b/lib/private/Metadata/Capabilities.php
deleted file mode 100644
index 2fa0006f581..00000000000
--- a/lib/private/Metadata/Capabilities.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @license AGPL-3.0-or-later
- *
- * 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/>
- *
- */
-
-namespace OC\Metadata;
-
-use OCP\Capabilities\IPublicCapability;
-use OCP\IConfig;
-
-class Capabilities implements IPublicCapability {
- private IMetadataManager $manager;
- private IConfig $config;
-
- public function __construct(IMetadataManager $manager, IConfig $config) {
- $this->manager = $manager;
- $this->config = $config;
- }
-
- public function getCapabilities() {
- if ($this->config->getSystemValueBool('enable_file_metadata', true)) {
- return ['metadataAvailable' => $this->manager->getCapabilities()];
- }
-
- return [];
- }
-}
diff --git a/lib/private/Metadata/FileEventListener.php b/lib/private/Metadata/FileEventListener.php
deleted file mode 100644
index 162e85ff3aa..00000000000
--- a/lib/private/Metadata/FileEventListener.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @license AGPL-3.0-or-later
- *
- * 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/>
- *
- */
-
-namespace OC\Metadata;
-
-use OC\Files\Filesystem;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventListener;
-use OCP\Files\Events\Node\NodeDeletedEvent;
-use OCP\Files\Events\Node\NodeWrittenEvent;
-use OCP\Files\Events\NodeRemovedFromCache;
-use OCP\Files\File;
-use OCP\Files\Node;
-use OCP\Files\NotFoundException;
-use OCP\Files\FileInfo;
-use Psr\Log\LoggerInterface;
-
-/**
- * @template-implements IEventListener<NodeRemovedFromCache>
- * @template-implements IEventListener<NodeDeletedEvent>
- * @template-implements IEventListener<NodeWrittenEvent>
- */
-class FileEventListener implements IEventListener {
- private IMetadataManager $manager;
- private LoggerInterface $logger;
-
- public function __construct(IMetadataManager $manager, LoggerInterface $logger) {
- $this->manager = $manager;
- $this->logger = $logger;
- }
-
- private function shouldExtractMetadata(Node $node): bool {
- try {
- if ($node->getMimetype() === 'httpd/unix-directory') {
- return false;
- }
- } catch (NotFoundException $e) {
- return false;
- }
- if ($node->getSize(false) <= 0) {
- return false;
- }
-
- $path = $node->getPath();
- return $this->isCorrectPath($path);
- }
-
- private function isCorrectPath(string $path): bool {
- // TODO make this more dynamic, we have the same issue in other places
- return !str_starts_with($path, 'appdata_') && !str_starts_with($path, 'files_versions/') && !str_starts_with($path, 'files_trashbin/');
- }
-
- public function handle(Event $event): void {
- if ($event instanceof NodeRemovedFromCache) {
- if (!$this->isCorrectPath($event->getPath())) {
- // Don't listen to paths for which we don't extract metadata
- return;
- }
- $view = Filesystem::getView();
- if (!$view) {
- // Should not happen since a scan in the user folder should setup
- // the file system.
- $e = new \Exception(); // don't trigger, just get backtrace
- $this->logger->error('Detecting deletion of a file with possible metadata but file system setup is not setup', [
- 'exception' => $e,
- 'app' => 'metadata'
- ]);
- return;
- }
- $info = $view->getFileInfo($event->getPath());
- if ($info && $info->getType() === FileInfo::TYPE_FILE) {
- $this->manager->clearMetadata($info->getId());
- }
- }
-
- if ($event instanceof NodeDeletedEvent) {
- $node = $event->getNode();
- if ($this->shouldExtractMetadata($node)) {
- /** @var File $node */
- $this->manager->clearMetadata($event->getNode()->getId());
- }
- }
-
- if ($event instanceof NodeWrittenEvent) {
- $node = $event->getNode();
- if ($this->shouldExtractMetadata($node)) {
- /** @var File $node */
- $this->manager->generateMetadata($event->getNode(), false);
- }
- }
- }
-}
diff --git a/lib/private/Metadata/FileMetadata.php b/lib/private/Metadata/FileMetadata.php
deleted file mode 100644
index a9808a86998..00000000000
--- a/lib/private/Metadata/FileMetadata.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- *
- * @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/>
- *
- */
-
-namespace OC\Metadata;
-
-use OCP\AppFramework\Db\Entity;
-use OCP\DB\Types;
-
-/**
- * @method string getGroupName()
- * @method void setGroupName(string $groupName)
- * @method string getValue()
- * @method void setValue(string $value)
- * @see \OC\Core\Migrations\Version240000Date20220404230027
- */
-class FileMetadata extends Entity {
- protected ?string $groupName = null;
- protected ?string $value = null;
-
- public function __construct() {
- $this->addType('groupName', 'string');
- $this->addType('value', Types::STRING);
- }
-
- public function getDecodedValue(): array {
- return json_decode($this->getValue(), true) ?? [];
- }
-
- public function setArrayAsValue(array $value): void {
- $this->setValue(json_encode($value, JSON_THROW_ON_ERROR));
- }
-}
diff --git a/lib/private/Metadata/FileMetadataMapper.php b/lib/private/Metadata/FileMetadataMapper.php
deleted file mode 100644
index 003ab13126e..00000000000
--- a/lib/private/Metadata/FileMetadataMapper.php
+++ /dev/null
@@ -1,177 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @copyright Copyright 2022 Louis Chmn <louis@chmn.me>
- * @license AGPL-3.0-or-later
- *
- * 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/>
- *
- */
-
-namespace OC\Metadata;
-
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\AppFramework\Db\MultipleObjectsReturnedException;
-use OCP\AppFramework\Db\QBMapper;
-use OCP\AppFramework\Db\Entity;
-use OCP\DB\Exception;
-use OCP\DB\QueryBuilder\IQueryBuilder;
-use OCP\IDBConnection;
-
-/**
- * @template-extends QBMapper<FileMetadata>
- */
-class FileMetadataMapper extends QBMapper {
- public function __construct(IDBConnection $db) {
- parent::__construct($db, 'file_metadata', FileMetadata::class);
- }
-
- /**
- * @return FileMetadata[]
- * @throws Exception
- */
- public function findForFile(int $fileId): array {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
-
- return $this->findEntities($qb);
- }
-
- /**
- * @throws DoesNotExistException
- * @throws MultipleObjectsReturnedException
- * @throws Exception
- */
- public function findForGroupForFile(int $fileId, string $groupName): FileMetadata {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, IQueryBuilder::PARAM_STR)));
-
- return $this->findEntity($qb);
- }
-
- /**
- * @return array<int, FileMetadata>
- * @throws Exception
- */
- public function findForGroupForFiles(array $fileIds, string $groupName): array {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->in('id', $qb->createParameter('fileIds')))
- ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, IQueryBuilder::PARAM_STR)));
-
- $metadata = [];
- foreach (array_chunk($fileIds, 1000) as $fileIdsChunk) {
- $qb->setParameter('fileIds', $fileIdsChunk, IQueryBuilder::PARAM_INT_ARRAY);
- /** @var FileMetadata[] $rawEntities */
- $rawEntities = $this->findEntities($qb);
- foreach ($rawEntities as $entity) {
- $metadata[$entity->getId()] = $entity;
- }
- }
-
- foreach ($fileIds as $id) {
- if (isset($metadata[$id])) {
- continue;
- }
- $empty = new FileMetadata();
- $empty->setValue('');
- $empty->setGroupName($groupName);
- $empty->setId($id);
- $metadata[$id] = $empty;
- }
- return $metadata;
- }
-
- public function clear(int $fileId): void {
- $qb = $this->db->getQueryBuilder();
- $qb->delete($this->getTableName())
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
-
- $qb->executeStatement();
- }
-
- /**
- * Updates an entry in the db from an entity
- *
- * @param FileMetadata $entity the entity that should be created
- * @return FileMetadata the saved entity with the set id
- * @throws Exception
- * @throws \InvalidArgumentException if entity has no id
- */
- public function update(Entity $entity): FileMetadata {
- if (!($entity instanceof FileMetadata)) {
- throw new \Exception("Entity should be a FileMetadata entity");
- }
-
- // entity needs an id
- $id = $entity->getId();
- if ($id === null) {
- throw new \InvalidArgumentException('Entity which should be updated has no id');
- }
-
- // entity needs an group_name
- $groupName = $entity->getGroupName();
- if ($groupName === null) {
- throw new \InvalidArgumentException('Entity which should be updated has no group_name');
- }
-
- $idType = $this->getParameterTypeForProperty($entity, 'id');
- $groupNameType = $this->getParameterTypeForProperty($entity, 'groupName');
- $value = $entity->getValue();
- $valueType = $this->getParameterTypeForProperty($entity, 'value');
-
- $qb = $this->db->getQueryBuilder();
-
- $qb->update($this->tableName)
- ->set('value', $qb->createNamedParameter($value, $valueType))
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, $idType)))
- ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, $groupNameType)))
- ->executeStatement();
-
- return $entity;
- }
-
- /**
- * Override the insertOrUpdate as we could be in a transaction in which case we can not afford on error.
- *
- * @param FileMetadata $entity the entity that should be created/updated
- * @return FileMetadata the saved entity with the (new) id
- * @throws Exception
- * @throws \InvalidArgumentException if entity has no id
- */
- public function insertOrUpdate(Entity $entity): FileMetadata {
- try {
- $existingEntity = $this->findForGroupForFile($entity->getId(), $entity->getGroupName());
- } catch (\Throwable) {
- $existingEntity = null;
- }
-
- if ($existingEntity !== null) {
- if ($entity->getValue() !== $existingEntity->getValue()) {
- return $this->update($entity);
- } else {
- return $existingEntity;
- }
- } else {
- return parent::insertOrUpdate($entity);
- }
- }
-}
diff --git a/lib/private/Metadata/IMetadataManager.php b/lib/private/Metadata/IMetadataManager.php
deleted file mode 100644
index fa0bcc22801..00000000000
--- a/lib/private/Metadata/IMetadataManager.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace OC\Metadata;
-
-use OCP\Files\File;
-
-/**
- * Interface to manage additional metadata for files
- */
-interface IMetadataManager {
- /**
- * @param class-string<IMetadataProvider> $className
- */
- public function registerProvider(string $className): void;
-
- /**
- * Generate the metadata for one file
- */
- public function generateMetadata(File $file, bool $checkExisting = false): void;
-
- /**
- * Clear the metadata for one file
- */
- public function clearMetadata(int $fileId): void;
-
- /** @return array<int, FileMetadata> */
- public function fetchMetadataFor(string $group, array $fileIds): array;
-
- /**
- * Get the capabilities as an array of mimetype regex to the type provided
- */
- public function getCapabilities(): array;
-}
diff --git a/lib/private/Metadata/IMetadataProvider.php b/lib/private/Metadata/IMetadataProvider.php
deleted file mode 100644
index 7cbe102a538..00000000000
--- a/lib/private/Metadata/IMetadataProvider.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-namespace OC\Metadata;
-
-use OCP\Files\File;
-
-/**
- * Interface for the metadata providers. If you want an application to provide
- * some metadata, you can use this to store them.
- */
-interface IMetadataProvider {
- /**
- * The list of groups that this metadata provider is able to provide.
- *
- * @return string[]
- */
- public static function groupsProvided(): array;
-
- /**
- * Check if the metadata provider is available. A metadata provider might be
- * unavailable due to a php extension not being installed.
- */
- public static function isAvailable(): bool;
-
- /**
- * Get the mimetypes supported as a regex.
- */
- public static function getMimetypesSupported(): string;
-
- /**
- * Execute the extraction on the specified file. The metadata should be
- * grouped by metadata
- *
- * Each group should be json serializable and the string representation
- * shouldn't be longer than 4000 characters.
- *
- * @param File $file The file to extract the metadata from
- * @param array<string, FileMetadata> An array containing all the metadata fetched.
- */
- public function execute(File $file): array;
-}
diff --git a/lib/private/Metadata/MetadataManager.php b/lib/private/Metadata/MetadataManager.php
deleted file mode 100644
index 6d96ff1ab68..00000000000
--- a/lib/private/Metadata/MetadataManager.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @license AGPL-3.0-or-later
- *
- * 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/>
- *
- */
-
-namespace OC\Metadata;
-
-use OC\Metadata\Provider\ExifProvider;
-use OCP\Files\File;
-
-class MetadataManager implements IMetadataManager {
- /** @var array<string, IMetadataProvider> */
- private array $providers;
- private array $providerClasses;
- private FileMetadataMapper $fileMetadataMapper;
-
- public function __construct(
- FileMetadataMapper $fileMetadataMapper
- ) {
- $this->providers = [];
- $this->providerClasses = [];
- $this->fileMetadataMapper = $fileMetadataMapper;
-
- // TODO move to another place, where?
- $this->registerProvider(ExifProvider::class);
- }
-
- /**
- * @param class-string<IMetadataProvider> $className
- */
- public function registerProvider(string $className):void {
- if (in_array($className, $this->providerClasses)) {
- return;
- }
-
- if (call_user_func([$className, 'isAvailable'])) {
- $this->providers[call_user_func([$className, 'getMimetypesSupported'])] = \OC::$server->get($className);
- }
- }
-
- public function generateMetadata(File $file, bool $checkExisting = false): void {
- $existingMetadataGroups = [];
-
- if ($checkExisting) {
- $existingMetadata = $this->fileMetadataMapper->findForFile($file->getId());
- foreach ($existingMetadata as $metadata) {
- $existingMetadataGroups[] = $metadata->getGroupName();
- }
- }
-
- foreach ($this->providers as $supportedMimetype => $provider) {
- if (preg_match($supportedMimetype, $file->getMimeType())) {
- if (count(array_diff($provider::groupsProvided(), $existingMetadataGroups)) > 0) {
- $metaDataGroup = $provider->execute($file);
- foreach ($metaDataGroup as $group => $metadata) {
- $this->fileMetadataMapper->insertOrUpdate($metadata);
- }
- }
- }
- }
- }
-
- public function clearMetadata(int $fileId): void {
- $this->fileMetadataMapper->clear($fileId);
- }
-
- /**
- * @return array<int, FileMetadata>
- */
- public function fetchMetadataFor(string $group, array $fileIds): array {
- return $this->fileMetadataMapper->findForGroupForFiles($fileIds, $group);
- }
-
- public function getCapabilities(): array {
- $capabilities = [];
- foreach ($this->providers as $supportedMimetype => $provider) {
- foreach ($provider::groupsProvided() as $group) {
- if (isset($capabilities[$group])) {
- $capabilities[$group][] = $supportedMimetype;
- }
- $capabilities[$group] = [$supportedMimetype];
- }
- }
- return $capabilities;
- }
-}
diff --git a/lib/private/Metadata/Provider/ExifProvider.php b/lib/private/Metadata/Provider/ExifProvider.php
deleted file mode 100644
index b1598abbbc8..00000000000
--- a/lib/private/Metadata/Provider/ExifProvider.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @copyright Copyright 2022 Louis Chmn <louis@chmn.me>
- * @license AGPL-3.0-or-later
- *
- * 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/>
- *
- */
-
-namespace OC\Metadata\Provider;
-
-use OC\Metadata\FileMetadata;
-use OC\Metadata\IMetadataProvider;
-use OCP\Files\File;
-use Psr\Log\LoggerInterface;
-
-class ExifProvider implements IMetadataProvider {
- private LoggerInterface $logger;
-
- public function __construct(
- LoggerInterface $logger
- ) {
- $this->logger = $logger;
- }
-
- public static function groupsProvided(): array {
- return ['size', 'gps'];
- }
-
- public static function isAvailable(): bool {
- return extension_loaded('exif');
- }
-
- /** @return array{'gps'?: FileMetadata, 'size'?: FileMetadata} */
- public function execute(File $file): array {
- $exifData = [];
- $fileDescriptor = $file->fopen('rb');
-
- if ($fileDescriptor === false) {
- return [];
- }
-
- $data = null;
- try {
- // Needed to make reading exif data reliable.
- // This is to trigger this condition: https://github.com/php/php-src/blob/d64aa6f646a7b5e58359dc79479860164239580a/main/streams/streams.c#L710
- // But I don't understand why 1 as a special meaning.
- // Revert right after reading the exif data.
- $oldBufferSize = stream_set_chunk_size($fileDescriptor, 1);
- $data = @exif_read_data($fileDescriptor, 'ANY_TAG', true);
- stream_set_chunk_size($fileDescriptor, $oldBufferSize);
- } catch (\Exception $ex) {
- $this->logger->info("Couldn't extract metadata for ".$file->getId(), ['exception' => $ex]);
- }
-
- $size = new FileMetadata();
- $size->setGroupName('size');
- $size->setId($file->getId());
- $size->setArrayAsValue([]);
-
- if (!$data) {
- $sizeResult = getimagesizefromstring($file->getContent());
- if ($sizeResult !== false) {
- $size->setArrayAsValue([
- 'width' => $sizeResult[0],
- 'height' => $sizeResult[1],
- ]);
-
- $exifData['size'] = $size;
- }
- } elseif (array_key_exists('COMPUTED', $data)) {
- if (array_key_exists('Width', $data['COMPUTED']) && array_key_exists('Height', $data['COMPUTED'])) {
- $size->setArrayAsValue([
- 'width' => $data['COMPUTED']['Width'],
- 'height' => $data['COMPUTED']['Height'],
- ]);
-
- $exifData['size'] = $size;
- }
- }
-
- if ($data && array_key_exists('GPS', $data)
- && array_key_exists('GPSLatitude', $data['GPS']) && array_key_exists('GPSLatitudeRef', $data['GPS'])
- && array_key_exists('GPSLongitude', $data['GPS']) && array_key_exists('GPSLongitudeRef', $data['GPS'])
- ) {
- $gps = new FileMetadata();
- $gps->setGroupName('gps');
- $gps->setId($file->getId());
- $gps->setArrayAsValue([
- 'latitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLatitude'], $data['GPS']['GPSLatitudeRef']),
- 'longitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLongitude'], $data['GPS']['GPSLongitudeRef']),
- ]);
-
- $exifData['gps'] = $gps;
- }
-
- return $exifData;
- }
-
- public static function getMimetypesSupported(): string {
- return '/image\/(png|jpeg|heif|webp|tiff)/';
- }
-
- /**
- * @param array|string $coordinates
- */
- private static function gpsDegreesToDecimal($coordinates, ?string $hemisphere): float {
- if (is_string($coordinates)) {
- $coordinates = array_map("trim", explode(",", $coordinates));
- }
-
- if (count($coordinates) !== 3) {
- throw new \Exception('Invalid coordinate format: ' . json_encode($coordinates));
- }
-
- [$degrees, $minutes, $seconds] = array_map(function (string $rawDegree) {
- $parts = explode('/', $rawDegree);
-
- if ($parts[1] === '0') {
- return 0;
- }
-
- return floatval($parts[0]) / floatval($parts[1] ?? 1);
- }, $coordinates);
-
- $sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1;
- return $sign * ($degrees + $minutes / 60 + $seconds / 3600);
- }
-}
diff --git a/lib/private/Migration/BackgroundRepair.php b/lib/private/Migration/BackgroundRepair.php
index 579ba494e58..dd1b15c7492 100644
--- a/lib/private/Migration/BackgroundRepair.php
+++ b/lib/private/Migration/BackgroundRepair.php
@@ -26,13 +26,13 @@
*/
namespace OC\Migration;
+use OC\NeedsUpdateException;
+use OC\Repair;
+use OC_App;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\TimedJob;
use OCP\EventDispatcher\IEventDispatcher;
-use OC\NeedsUpdateException;
-use OC\Repair;
-use OC_App;
use Psr\Log\LoggerInterface;
/**
@@ -41,15 +41,13 @@ use Psr\Log\LoggerInterface;
* @package OC\Migration
*/
class BackgroundRepair extends TimedJob {
- private IJobList $jobList;
- private LoggerInterface $logger;
- private IEventDispatcher $dispatcher;
-
- public function __construct(IEventDispatcher $dispatcher, ITimeFactory $time, LoggerInterface $logger, IJobList $jobList) {
+ public function __construct(
+ private IEventDispatcher $dispatcher,
+ ITimeFactory $time,
+ private LoggerInterface $logger,
+ private IJobList $jobList,
+ ) {
parent::__construct($time);
- $this->dispatcher = $dispatcher;
- $this->logger = $logger;
- $this->jobList = $jobList;
$this->setInterval(15 * 60);
}
@@ -58,7 +56,7 @@ class BackgroundRepair extends TimedJob {
* @throws \Exception
* @throws \OC\NeedsUpdateException
*/
- protected function run($argument) {
+ protected function run($argument): void {
if (!isset($argument['app']) || !isset($argument['step'])) {
// remove the job - we can never execute it
$this->jobList->remove($this, $this->argument);
@@ -101,7 +99,7 @@ class BackgroundRepair extends TimedJob {
* @param $app
* @throws NeedsUpdateException
*/
- protected function loadApp($app) {
+ protected function loadApp($app): void {
OC_App::loadApp($app);
}
}
diff --git a/lib/private/Migration/ConsoleOutput.php b/lib/private/Migration/ConsoleOutput.php
index 9e3396f2a75..841e3d302fc 100644
--- a/lib/private/Migration/ConsoleOutput.php
+++ b/lib/private/Migration/ConsoleOutput.php
@@ -34,34 +34,35 @@ use Symfony\Component\Console\Output\OutputInterface;
* @package OC\Migration
*/
class ConsoleOutput implements IOutput {
- /** @var OutputInterface */
- private $output;
+ private ?ProgressBar $progressBar = null;
- /** @var ProgressBar */
- private $progressBar;
+ public function __construct(
+ private OutputInterface $output,
+ ) {
+ }
- public function __construct(OutputInterface $output) {
- $this->output = $output;
+ public function debug(string $message): void {
+ $this->output->writeln($message, OutputInterface::VERBOSITY_VERBOSE);
}
/**
* @param string $message
*/
- public function info($message) {
+ public function info($message): void {
$this->output->writeln("<info>$message</info>");
}
/**
* @param string $message
*/
- public function warning($message) {
+ public function warning($message): void {
$this->output->writeln("<comment>$message</comment>");
}
/**
* @param int $max
*/
- public function startProgress($max = 0) {
+ public function startProgress($max = 0): void {
if (!is_null($this->progressBar)) {
$this->progressBar->finish();
}
@@ -73,7 +74,7 @@ class ConsoleOutput implements IOutput {
* @param int $step
* @param string $description
*/
- public function advance($step = 1, $description = '') {
+ public function advance($step = 1, $description = ''): void {
if (is_null($this->progressBar)) {
$this->progressBar = new ProgressBar($this->output);
$this->progressBar->start();
@@ -84,7 +85,7 @@ class ConsoleOutput implements IOutput {
}
}
- public function finishProgress() {
+ public function finishProgress(): void {
if (is_null($this->progressBar)) {
return;
}
diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php
index f97bcb767f8..f1b06d008bb 100644
--- a/lib/private/Migration/SimpleOutput.php
+++ b/lib/private/Migration/SimpleOutput.php
@@ -33,19 +33,21 @@ use Psr\Log\LoggerInterface;
* @package OC\Migration
*/
class SimpleOutput implements IOutput {
- private LoggerInterface $logger;
- private $appName;
+ public function __construct(
+ private LoggerInterface $logger,
+ private $appName,
+ ) {
+ }
- public function __construct(LoggerInterface $logger, $appName) {
- $this->logger = $logger;
- $this->appName = $appName;
+ public function debug(string $message): void {
+ $this->logger->debug($message, ['app' => $this->appName]);
}
/**
* @param string $message
* @since 9.1.0
*/
- public function info($message) {
+ public function info($message): void {
$this->logger->info($message, ['app' => $this->appName]);
}
@@ -53,7 +55,7 @@ class SimpleOutput implements IOutput {
* @param string $message
* @since 9.1.0
*/
- public function warning($message) {
+ public function warning($message): void {
$this->logger->warning($message, ['app' => $this->appName]);
}
@@ -61,7 +63,7 @@ class SimpleOutput implements IOutput {
* @param int $max
* @since 9.1.0
*/
- public function startProgress($max = 0) {
+ public function startProgress($max = 0): void {
}
/**
@@ -69,12 +71,12 @@ class SimpleOutput implements IOutput {
* @param string $description
* @since 9.1.0
*/
- public function advance($step = 1, $description = '') {
+ public function advance($step = 1, $description = ''): void {
}
/**
* @since 9.1.0
*/
- public function finishProgress() {
+ public function finishProgress(): void {
}
}
diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php
index d34ba5fed98..17573d9db86 100644
--- a/lib/private/NavigationManager.php
+++ b/lib/private/NavigationManager.php
@@ -65,19 +65,25 @@ class NavigationManager implements INavigationManager {
private $groupManager;
/** @var IConfig */
private $config;
+ /** The default app for the current user (cached for the `add` function) */
+ private ?string $defaultApp;
+ /** User defined app order (cached for the `add` function) */
+ private array $customAppOrder;
public function __construct(IAppManager $appManager,
- IURLGenerator $urlGenerator,
- IFactory $l10nFac,
- IUserSession $userSession,
- IGroupManager $groupManager,
- IConfig $config) {
+ IURLGenerator $urlGenerator,
+ IFactory $l10nFac,
+ IUserSession $userSession,
+ IGroupManager $groupManager,
+ IConfig $config) {
$this->appManager = $appManager;
$this->urlGenerator = $urlGenerator;
$this->l10nFac = $l10nFac;
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->config = $config;
+
+ $this->defaultApp = null;
}
/**
@@ -89,7 +95,10 @@ class NavigationManager implements INavigationManager {
return;
}
+ $id = $entry['id'];
+
$entry['active'] = false;
+ $entry['unread'] = $this->unreadCounters[$id] ?? 0;
if (!isset($entry['icon'])) {
$entry['icon'] = '';
}
@@ -100,8 +109,17 @@ class NavigationManager implements INavigationManager {
$entry['type'] = 'link';
}
- $id = $entry['id'];
- $entry['unread'] = $this->unreadCounters[$id] ?? 0;
+ if ($entry['type'] === 'link') {
+ // app might not be set when using closures, in this case try to fallback to ID
+ if (!isset($entry['app']) && $this->appManager->isEnabledForUser($id)) {
+ $entry['app'] = $id;
+ }
+
+ // This is the default app that will always be shown first
+ $entry['default'] = ($entry['app'] ?? false) === $this->defaultApp;
+ // Set order from user defined app order
+ $entry['order'] = $this->customAppOrder[$id]['order'] ?? $entry['order'] ?? 100;
+ }
$this->entries[$id] = $entry;
}
@@ -218,7 +236,25 @@ class NavigationManager implements INavigationManager {
]);
}
+ if ($this->appManager === 'null') {
+ return;
+ }
+
+ $this->defaultApp = $this->appManager->getDefaultAppForUser($this->userSession->getUser(), false);
+
if ($this->userSession->isLoggedIn()) {
+ // Profile
+ $this->add([
+ 'type' => 'settings',
+ 'id' => 'profile',
+ 'order' => 1,
+ 'href' => $this->urlGenerator->linkToRoute(
+ 'core.ProfilePage.index',
+ ['targetUserId' => $this->userSession->getUser()->getUID()],
+ ),
+ 'name' => $l->t('View profile'),
+ ]);
+
// Accessibility settings
if ($this->appManager->isEnabledForUser('theming', $this->userSession->getUser())) {
$this->add([
@@ -230,6 +266,7 @@ class NavigationManager implements INavigationManager {
'icon' => $this->urlGenerator->imagePath('theming', 'accessibility-dark.svg'),
]);
}
+
if ($this->isAdmin()) {
// App management
$this->add([
@@ -298,22 +335,15 @@ class NavigationManager implements INavigationManager {
}
}
- if ($this->appManager === 'null') {
- return;
- }
-
if ($this->userSession->isLoggedIn()) {
$user = $this->userSession->getUser();
$apps = $this->appManager->getEnabledAppsForUser($user);
- $customOrders = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
+ $this->customAppOrder = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
} else {
$apps = $this->appManager->getInstalledApps();
- $customOrders = [];
+ $this->customAppOrder = [];
}
- // The default app of the current user without fallbacks
- $defaultApp = $this->appManager->getDefaultAppForUser($this->userSession->getUser(), false);
-
foreach ($apps as $app) {
if (!$this->userSession->isLoggedIn() && !$this->appManager->isEnabledForUser($app, $this->userSession->getUser())) {
continue;
@@ -339,7 +369,7 @@ class NavigationManager implements INavigationManager {
}
$l = $this->l10nFac->get($app);
$id = $nav['id'] ?? $app . ($key === 0 ? '' : $key);
- $order = $customOrders[$app][$key] ?? $nav['order'] ?? 100;
+ $order = $nav['order'] ?? 100;
$type = $nav['type'];
$route = !empty($nav['route']) ? $this->urlGenerator->linkToRoute($nav['route']) : '';
$icon = $nav['icon'] ?? 'app.svg';
@@ -369,12 +399,8 @@ class NavigationManager implements INavigationManager {
// Localized name of the navigation entry
'name' => $l->t($nav['name']),
], $type === 'link' ? [
- // This is the default app that will always be shown first
- 'default' => $defaultApp === $id,
// App that registered this navigation entry (not necessarly the same as the id)
'app' => $app,
- // The key used to identify this entry in the navigations entries
- 'key' => $key,
] : []
));
}
diff --git a/lib/private/Net/HostnameClassifier.php b/lib/private/Net/HostnameClassifier.php
index 626aa47083e..42dae790152 100644
--- a/lib/private/Net/HostnameClassifier.php
+++ b/lib/private/Net/HostnameClassifier.php
@@ -52,10 +52,6 @@ class HostnameClassifier {
* Check host identifier for local hostname
*
* IP addresses are not considered local. Use the IpAddressClassifier for those.
- *
- * @param string $hostname
- *
- * @return bool
*/
public function isLocalHostname(string $hostname): bool {
// Disallow local network top-level domains from RFC 6762
diff --git a/lib/private/Net/IpAddressClassifier.php b/lib/private/Net/IpAddressClassifier.php
index d4698864ec8..b012ca8e956 100644
--- a/lib/private/Net/IpAddressClassifier.php
+++ b/lib/private/Net/IpAddressClassifier.php
@@ -46,10 +46,6 @@ class IpAddressClassifier {
* Check host identifier for local IPv4 and IPv6 address ranges
*
* Hostnames are not considered local. Use the HostnameClassifier for those.
- *
- * @param string $ip
- *
- * @return bool
*/
public function isLocalAddress(string $ip): bool {
$parsedIp = Factory::parseAddressString(
diff --git a/lib/private/Notification/Manager.php b/lib/private/Notification/Manager.php
index 3d77f643d93..e81b6c4fa35 100644
--- a/lib/private/Notification/Manager.php
+++ b/lib/private/Notification/Manager.php
@@ -74,11 +74,11 @@ class Manager implements IManager {
private $parsedRegistrationContext;
public function __construct(IValidator $validator,
- IUserManager $userManager,
- ICacheFactory $cacheFactory,
- IRegistry $subscription,
- LoggerInterface $logger,
- Coordinator $coordinator) {
+ IUserManager $userManager,
+ ICacheFactory $cacheFactory,
+ IRegistry $subscription,
+ LoggerInterface $logger,
+ Coordinator $coordinator) {
$this->validator = $validator;
$this->userManager = $userManager;
$this->cache = $cacheFactory->createDistributed('notifications');
diff --git a/lib/private/OCS/DiscoveryService.php b/lib/private/OCS/DiscoveryService.php
index 8f98ff7d5ae..f53d39465e8 100644
--- a/lib/private/OCS/DiscoveryService.php
+++ b/lib/private/OCS/DiscoveryService.php
@@ -47,7 +47,7 @@ class DiscoveryService implements IDiscoveryService {
* @param IClientService $clientService
*/
public function __construct(ICacheFactory $cacheFactory,
- IClientService $clientService
+ IClientService $clientService
) {
$this->cache = $cacheFactory->createDistributed('ocs-discovery');
$this->client = $clientService->newClient();
diff --git a/lib/private/OCS/Provider.php b/lib/private/OCS/Provider.php
index 5e7a86a1341..83219c018f8 100644
--- a/lib/private/OCS/Provider.php
+++ b/lib/private/OCS/Provider.php
@@ -34,8 +34,8 @@ class Provider extends \OCP\AppFramework\Controller {
* @param \OCP\App\IAppManager $appManager
*/
public function __construct($appName,
- \OCP\IRequest $request,
- \OCP\App\IAppManager $appManager) {
+ \OCP\IRequest $request,
+ \OCP\App\IAppManager $appManager) {
parent::__construct($appName, $request);
$this->appManager = $appManager;
}
diff --git a/lib/private/Preview/BackgroundCleanupJob.php b/lib/private/Preview/BackgroundCleanupJob.php
index 4eba96d1a82..4376cc06eca 100644
--- a/lib/private/Preview/BackgroundCleanupJob.php
+++ b/lib/private/Preview/BackgroundCleanupJob.php
@@ -48,10 +48,10 @@ class BackgroundCleanupJob extends TimedJob {
private $mimeTypeLoader;
public function __construct(ITimeFactory $timeFactory,
- IDBConnection $connection,
- Root $previewFolder,
- IMimeTypeLoader $mimeTypeLoader,
- bool $isCLI) {
+ IDBConnection $connection,
+ Root $previewFolder,
+ IMimeTypeLoader $mimeTypeLoader,
+ bool $isCLI) {
parent::__construct($timeFactory);
// Run at most once an hour
$this->setInterval(3600);
diff --git a/lib/private/Preview/EMF.php b/lib/private/Preview/EMF.php
new file mode 100644
index 00000000000..2b5f40e66af
--- /dev/null
+++ b/lib/private/Preview/EMF.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @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\Preview;
+
+class EMF extends Office {
+ public function getMimeType(): string {
+ return '/image\/emf/';
+ }
+}
diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php
index 4a1270fa4a6..695d4a3357f 100644
--- a/lib/private/Preview/Generator.php
+++ b/lib/private/Preview/Generator.php
@@ -221,7 +221,7 @@ class Generator {
*
* @param int $semId
* @param int $concurrency
- * @return false|resource the semaphore on success or false on failure
+ * @return false|\SysvSemaphore the semaphore on success or false on failure
*/
public static function guardWithSemaphore(int $semId, int $concurrency) {
if (!extension_loaded('sysvsem')) {
@@ -240,11 +240,11 @@ class Generator {
/**
* Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
*
- * @param resource|bool $semId the semaphore identifier returned by guardWithSemaphore
+ * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
* @return bool
*/
- public static function unguardWithSemaphore($semId): bool {
- if (!is_resource($semId) || !extension_loaded('sysvsem')) {
+ public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
+ if ($semId === false || !($semId instanceof \SysvSemaphore)) {
return false;
}
return sem_release($semId);
@@ -257,9 +257,15 @@ class Generator {
*/
public static function getHardwareConcurrency(): int {
static $width;
+
if (!isset($width)) {
- if (is_file("/proc/cpuinfo")) {
- $width = substr_count(file_get_contents("/proc/cpuinfo"), "processor");
+ if (function_exists('ini_get')) {
+ $openBasedir = ini_get('open_basedir');
+ if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
+ $width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
+ } else {
+ $width = 0;
+ }
} else {
$width = 0;
}
diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php
index 7184f7e9e76..faf84696e17 100644
--- a/lib/private/Preview/Imaginary.php
+++ b/lib/private/Preview/Imaginary.php
@@ -23,13 +23,13 @@
namespace OC\Preview;
+use OC\StreamImage;
use OCP\Files\File;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IImage;
-use OCP\Image;
-use OC\StreamImage;
+use OCP\Image;
use Psr\Log\LoggerInterface;
class Imaginary extends ProviderV2 {
@@ -78,6 +78,9 @@ class Imaginary extends ProviderV2 {
// Object store
$stream = $file->fopen('r');
+ if (!$stream || !is_resource($stream) || feof($stream)) {
+ return null;
+ }
$httpClient = $this->service->newClient();
@@ -165,7 +168,7 @@ class Imaginary extends ProviderV2 {
'timeout' => 120,
'connect_timeout' => 3,
]);
- } catch (\Exception $e) {
+ } catch (\Throwable $e) {
$this->logger->info('Imaginary preview generation failed: ' . $e->getMessage(), [
'exception' => $e,
]);
diff --git a/lib/private/Preview/Office.php b/lib/private/Preview/Office.php
index 3ba7c5a21a0..68499a6fea6 100644
--- a/lib/private/Preview/Office.php
+++ b/lib/private/Preview/Office.php
@@ -31,7 +31,8 @@ namespace OC\Preview;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\IImage;
-use Psr\Log\LoggerInterface;
+use OCP\ITempManager;
+use OCP\Server;
abstract class Office extends ProviderV2 {
/**
@@ -49,51 +50,60 @@ abstract class Office extends ProviderV2 {
return null;
}
- $absPath = $this->getLocalFile($file);
-
- $tmpDir = \OC::$server->getTempManager()->getTempBaseDir();
+ $tempManager = Server::get(ITempManager::class);
- $defaultParameters = ' -env:UserInstallation=file://' . escapeshellarg($tmpDir . '/owncloud-' . \OC_Util::getInstanceId() . '/') . ' --headless --nologo --nofirststartwizard --invisible --norestore --convert-to png --outdir ';
- $clParameters = \OC::$server->getConfig()->getSystemValue('preview_office_cl_parameters', $defaultParameters);
+ // The file to generate the preview for.
+ $absPath = $this->getLocalFile($file);
- $cmd = $this->options['officeBinary'] . $clParameters . escapeshellarg($tmpDir) . ' ' . escapeshellarg($absPath);
+ // The destination for the LibreOffice user profile.
+ // LibreOffice can rune once per user profile and therefore instance id and file id are included.
+ $profile = $tempManager->getTemporaryFolder(
+ 'nextcloud-office-profile-' . \OC_Util::getInstanceId() . '-' . $file->getId()
+ );
- exec($cmd, $output, $returnCode);
+ // The destination for the LibreOffice convert result.
+ $outdir = $tempManager->getTemporaryFolder(
+ 'nextcloud-office-preview-' . \OC_Util::getInstanceId() . '-' . $file->getId()
+ );
- if ($returnCode !== 0) {
+ if ($profile === false || $outdir === false) {
$this->cleanTmpFiles();
return null;
}
- //create imagick object from png
- $pngPreview = null;
- try {
- [$dirname, , , $filename] = array_values(pathinfo($absPath));
- $pngPreview = $tmpDir . '/' . $filename . '.png';
+ $parameters = [
+ $this->options['officeBinary'],
+ '-env:UserInstallation=file://' . escapeshellarg($profile),
+ '--headless',
+ '--nologo',
+ '--nofirststartwizard',
+ '--invisible',
+ '--norestore',
+ '--convert-to png',
+ '--outdir ' . escapeshellarg($outdir),
+ escapeshellarg($absPath),
+ ];
- $png = new \Imagick($pngPreview . '[0]');
- $png->setImageFormat('jpg');
- } catch (\Exception $e) {
+ $cmd = implode(' ', $parameters);
+ exec($cmd, $output, $returnCode);
+
+ if ($returnCode !== 0) {
$this->cleanTmpFiles();
- unlink($pngPreview);
- \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [
- 'exception' => $e,
- 'app' => 'core',
- ]);
return null;
}
+ $preview = $outdir . pathinfo($absPath, PATHINFO_FILENAME) . '.png';
+
$image = new \OCP\Image();
- $image->loadFromData((string) $png);
+ $image->loadFromFile($preview);
$this->cleanTmpFiles();
- unlink($pngPreview);
if ($image->valid()) {
$image->scaleDownToFit($maxX, $maxY);
-
return $image;
}
+
return null;
}
}
diff --git a/lib/private/Preview/WatcherConnector.php b/lib/private/Preview/WatcherConnector.php
index ffbdf825211..b11a6ab86da 100644
--- a/lib/private/Preview/WatcherConnector.php
+++ b/lib/private/Preview/WatcherConnector.php
@@ -43,7 +43,7 @@ class WatcherConnector {
* @param SystemConfig $config
*/
public function __construct(IRootFolder $root,
- SystemConfig $config) {
+ SystemConfig $config) {
$this->root = $root;
$this->config = $config;
}
diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php
index 3af6848686e..aedcbbce335 100644
--- a/lib/private/PreviewManager.php
+++ b/lib/private/PreviewManager.php
@@ -366,7 +366,7 @@ class PreviewManager implements IPreview {
$this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
$this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes());
- // SVG, Office and Bitmap require imagick
+ // SVG and Bitmap require imagick
if ($this->imagickSupport->hasExtension()) {
$imagickProviders = [
'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class],
@@ -391,27 +391,10 @@ class PreviewManager implements IPreview {
$this->registerCoreProvider($class, $provider['mimetype']);
}
}
-
- if ($this->imagickSupport->supportsFormat('PDF')) {
- // Office requires openoffice or libreoffice
- $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', null);
- if (!is_string($officeBinary)) {
- $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
- }
- if (!is_string($officeBinary)) {
- $officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
- }
-
- if (is_string($officeBinary)) {
- $this->registerCoreProvider(Preview\MSOfficeDoc::class, '/application\/msword/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\MSOffice2003::class, '/application\/vnd.ms-.*/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\MSOffice2007::class, '/application\/vnd.openxmlformats-officedocument.*/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\StarOffice::class, '/application\/vnd.sun.xml.*/', ["officeBinary" => $officeBinary]);
- }
- }
}
+ $this->registerCoreProvidersOffice();
+
// Video requires avconv or ffmpeg
if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) {
$movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null);
@@ -429,6 +412,43 @@ class PreviewManager implements IPreview {
}
}
+ private function registerCoreProvidersOffice(): void {
+ $officeProviders = [
+ ['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class],
+ ['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class],
+ ['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class],
+ ['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class],
+ ['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class],
+ ['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class],
+ ];
+
+ $findBinary = true;
+ $officeBinary = false;
+
+ foreach ($officeProviders as $provider) {
+ $class = $provider['class'];
+ if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
+ continue;
+ }
+
+ if ($findBinary) {
+ // Office requires openoffice or libreoffice
+ $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false);
+ if ($officeBinary === false) {
+ $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
+ }
+ if ($officeBinary === false) {
+ $officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
+ }
+ $findBinary = false;
+ }
+
+ if ($officeBinary) {
+ $this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]);
+ }
+ }
+ }
+
private function registerBootstrapProviders(): void {
$context = $this->bootstrapCoordinator->getRegistrationContext();
diff --git a/lib/private/Profile/Actions/FediverseAction.php b/lib/private/Profile/Actions/FediverseAction.php
index f96d2c07de4..4c73f785dd0 100644
--- a/lib/private/Profile/Actions/FediverseAction.php
+++ b/lib/private/Profile/Actions/FediverseAction.php
@@ -26,12 +26,12 @@ declare(strict_types=1);
namespace OC\Profile\Actions;
-use function substr;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
+use function substr;
class FediverseAction implements ILinkAction {
private string $value = '';
diff --git a/lib/private/Profile/Actions/TwitterAction.php b/lib/private/Profile/Actions/TwitterAction.php
index d63c2d3ee08..f7f57d4c6d1 100644
--- a/lib/private/Profile/Actions/TwitterAction.php
+++ b/lib/private/Profile/Actions/TwitterAction.php
@@ -26,12 +26,12 @@ declare(strict_types=1);
namespace OC\Profile\Actions;
-use function substr;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
+use function substr;
class TwitterAction implements ILinkAction {
private string $value = '';
diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php
index 39c51ea0e77..c8fb780bbe8 100644
--- a/lib/private/Profile/ProfileManager.php
+++ b/lib/private/Profile/ProfileManager.php
@@ -26,29 +26,29 @@ declare(strict_types=1);
namespace OC\Profile;
-use OCP\Profile\IProfileManager;
-use function array_flip;
-use function usort;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Core\Db\ProfileConfig;
use OC\Core\Db\ProfileConfigMapper;
use OC\KnownUser\KnownUserService;
use OC\Profile\Actions\EmailAction;
+use OC\Profile\Actions\FediverseAction;
use OC\Profile\Actions\PhoneAction;
use OC\Profile\Actions\TwitterAction;
-use OC\Profile\Actions\FediverseAction;
use OC\Profile\Actions\WebsiteAction;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\PropertyDoesNotExistException;
use OCP\App\IAppManager;
use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\Cache\CappedMemoryCache;
use OCP\IConfig;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
-use OCP\Cache\CappedMemoryCache;
+use OCP\Profile\IProfileManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use function array_flip;
+use function usort;
class ProfileManager implements IProfileManager {
/** @var ILinkAction[] */
diff --git a/lib/private/Profiler/Profiler.php b/lib/private/Profiler/Profiler.php
index 40050b7bf43..d6749c55e3c 100644
--- a/lib/private/Profiler/Profiler.php
+++ b/lib/private/Profiler/Profiler.php
@@ -27,11 +27,11 @@ declare(strict_types = 1);
namespace OC\Profiler;
use OC\AppFramework\Http\Request;
+use OC\SystemConfig;
use OCP\AppFramework\Http\Response;
use OCP\DataCollector\IDataCollector;
-use OCP\Profiler\IProfiler;
use OCP\Profiler\IProfile;
-use OC\SystemConfig;
+use OCP\Profiler\IProfiler;
class Profiler implements IProfiler {
/** @var array<string, IDataCollector> */
@@ -95,7 +95,7 @@ class Profiler implements IProfiler {
* @return array[]
*/
public function find(?string $url, ?int $limit, ?string $method, ?int $start, ?int $end,
- string $statusCode = null): array {
+ string $statusCode = null): array {
if ($this->storage) {
return $this->storage->find($url, $limit, $method, $start, $end, $statusCode);
} else {
diff --git a/lib/private/Repair.php b/lib/private/Repair.php
index 5c68c106993..21caed3e39f 100644
--- a/lib/private/Repair.php
+++ b/lib/private/Repair.php
@@ -34,19 +34,14 @@
*/
namespace OC;
-use OC\Repair\AddRemoveOldTasksBackgroundJob;
-use OC\Repair\CleanUpAbandonedApps;
-use OCP\AppFramework\QueryException;
-use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\Collaboration\Resources\IManager;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Migration\IOutput;
-use OCP\Migration\IRepairStep;
use OC\DB\Connection;
use OC\DB\ConnectionAdapter;
use OC\Repair\AddBruteForceCleanupJob;
use OC\Repair\AddCleanupUpdaterBackupsJob;
+use OC\Repair\AddMetadataGenerationJob;
+use OC\Repair\AddRemoveOldTasksBackgroundJob;
use OC\Repair\CleanTags;
+use OC\Repair\CleanUpAbandonedApps;
use OC\Repair\ClearFrontendCaches;
use OC\Repair\ClearGeneratedAvatarCache;
use OC\Repair\Collation;
@@ -85,6 +80,12 @@ use OC\Repair\RepairDavShares;
use OC\Repair\RepairInvalidShares;
use OC\Repair\RepairMimeTypes;
use OC\Template\JSCombiner;
+use OCP\AppFramework\QueryException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Collaboration\Resources\IManager;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
use Psr\Log\LoggerInterface;
use Throwable;
@@ -211,6 +212,7 @@ class Repair implements IOutput {
\OCP\Server::get(CleanUpAbandonedApps::class),
\OCP\Server::get(AddMissingSecretJob::class),
\OCP\Server::get(AddRemoveOldTasksBackgroundJob::class),
+ \OCP\Server::get(AddMetadataGenerationJob::class),
];
}
@@ -246,6 +248,9 @@ class Repair implements IOutput {
return $steps;
}
+ public function debug(string $message): void {
+ }
+
/**
* @param string $message
*/
diff --git a/lib/private/Repair/AddMetadataGenerationJob.php b/lib/private/Repair/AddMetadataGenerationJob.php
new file mode 100644
index 00000000000..72e5df03bbd
--- /dev/null
+++ b/lib/private/Repair/AddMetadataGenerationJob.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (c) 2023 Louis Chmn <louis@chmn.me>
+ *
+ * @author Louis Chmn <louis@chmn.me>
+ *
+ * @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\Repair;
+
+use OC\Core\BackgroundJobs\GenerateMetadataJob;
+use OCP\BackgroundJob\IJobList;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class AddMetadataGenerationJob implements IRepairStep {
+ public function __construct(
+ private IJobList $jobList,
+ ) {
+ }
+
+ public function getName() {
+ return 'Queue a job to generate metadata';
+ }
+
+ public function run(IOutput $output) {
+ $this->jobList->add(GenerateMetadataJob::class);
+ }
+}
diff --git a/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php b/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php
index 94ae39f2183..00badbb726d 100644
--- a/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php
+++ b/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php
@@ -25,7 +25,8 @@ declare(strict_types=1);
*/
namespace OC\Repair;
-use OC\TextProcessing\RemoveOldTasksBackgroundJob;
+use OC\TextProcessing\RemoveOldTasksBackgroundJob as RemoveOldTextProcessingTasksBackgroundJob;
+use OC\TextToImage\RemoveOldTasksBackgroundJob as RemoveOldTextToImageTasksBackgroundJob;
use OCP\BackgroundJob\IJobList;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
@@ -38,10 +39,11 @@ class AddRemoveOldTasksBackgroundJob implements IRepairStep {
}
public function getName(): string {
- return 'Add language model tasks cleanup job';
+ return 'Add AI tasks cleanup job';
}
public function run(IOutput $output) {
- $this->jobList->add(RemoveOldTasksBackgroundJob::class);
+ $this->jobList->add(RemoveOldTextProcessingTasksBackgroundJob::class);
+ $this->jobList->add(RemoveOldTextToImageTasksBackgroundJob::class);
}
}
diff --git a/lib/private/Repair/ClearFrontendCaches.php b/lib/private/Repair/ClearFrontendCaches.php
index bf94e5bfbff..3661560c5f6 100644
--- a/lib/private/Repair/ClearFrontendCaches.php
+++ b/lib/private/Repair/ClearFrontendCaches.php
@@ -37,7 +37,7 @@ class ClearFrontendCaches implements IRepairStep {
protected $jsCombiner;
public function __construct(ICacheFactory $cacheFactory,
- JSCombiner $JSCombiner) {
+ JSCombiner $JSCombiner) {
$this->cacheFactory = $cacheFactory;
$this->jsCombiner = $JSCombiner;
}
diff --git a/lib/private/Repair/ClearGeneratedAvatarCache.php b/lib/private/Repair/ClearGeneratedAvatarCache.php
index fb3b42847dc..88b2b07ead5 100644
--- a/lib/private/Repair/ClearGeneratedAvatarCache.php
+++ b/lib/private/Repair/ClearGeneratedAvatarCache.php
@@ -25,8 +25,8 @@
namespace OC\Repair;
use OC\Avatar\AvatarManager;
-use OCP\IConfig;
use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
diff --git a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php
index e8513e7a933..5caa74638e5 100644
--- a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php
+++ b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php
@@ -20,9 +20,9 @@
*/
namespace OC\Repair;
-use OCP\BackgroundJob\QueuedJob;
-use OCP\AppFramework\Utility\ITimeFactory;
use OC\Avatar\AvatarManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
class ClearGeneratedAvatarCacheJob extends QueuedJob {
protected AvatarManager $avatarManager;
diff --git a/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php b/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php
index d5ae1d7ab63..185ff3be1be 100644
--- a/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php
+++ b/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php
@@ -37,7 +37,7 @@ class ResetGeneratedAvatarFlag implements IRepairStep {
private $connection;
public function __construct(IConfig $config,
- IDBConnection $connection) {
+ IDBConnection $connection) {
$this->config = $config;
$this->connection = $connection;
}
diff --git a/lib/private/Repair/NC20/EncryptionLegacyCipher.php b/lib/private/Repair/NC20/EncryptionLegacyCipher.php
index a7d008e87be..42a8778662b 100644
--- a/lib/private/Repair/NC20/EncryptionLegacyCipher.php
+++ b/lib/private/Repair/NC20/EncryptionLegacyCipher.php
@@ -38,7 +38,7 @@ class EncryptionLegacyCipher implements IRepairStep {
private $manager;
public function __construct(IConfig $config,
- IManager $manager) {
+ IManager $manager) {
$this->config = $config;
$this->manager = $manager;
}
diff --git a/lib/private/Repair/NC20/EncryptionMigration.php b/lib/private/Repair/NC20/EncryptionMigration.php
index 239a62c2718..dea51b1b57e 100644
--- a/lib/private/Repair/NC20/EncryptionMigration.php
+++ b/lib/private/Repair/NC20/EncryptionMigration.php
@@ -38,7 +38,7 @@ class EncryptionMigration implements IRepairStep {
private $manager;
public function __construct(IConfig $config,
- IManager $manager) {
+ IManager $manager) {
$this->config = $config;
$this->manager = $manager;
}
diff --git a/lib/private/Repair/NC21/ValidatePhoneNumber.php b/lib/private/Repair/NC21/ValidatePhoneNumber.php
index b3534dbeae8..51120c9d139 100644
--- a/lib/private/Repair/NC21/ValidatePhoneNumber.php
+++ b/lib/private/Repair/NC21/ValidatePhoneNumber.php
@@ -42,8 +42,8 @@ class ValidatePhoneNumber implements IRepairStep {
private $accountManager;
public function __construct(IUserManager $userManager,
- IAccountManager $accountManager,
- IConfig $config) {
+ IAccountManager $accountManager,
+ IConfig $config) {
$this->config = $config;
$this->userManager = $userManager;
$this->accountManager = $accountManager;
diff --git a/lib/private/Repair/Owncloud/CleanPreviews.php b/lib/private/Repair/Owncloud/CleanPreviews.php
index 853a94c8adc..2020ae8bfc1 100644
--- a/lib/private/Repair/Owncloud/CleanPreviews.php
+++ b/lib/private/Repair/Owncloud/CleanPreviews.php
@@ -47,8 +47,8 @@ class CleanPreviews implements IRepairStep {
* @param IConfig $config
*/
public function __construct(IJobList $jobList,
- IUserManager $userManager,
- IConfig $config) {
+ IUserManager $userManager,
+ IConfig $config) {
$this->jobList = $jobList;
$this->userManager = $userManager;
$this->config = $config;
diff --git a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php
index 7f4bbc35c17..4ba9ad083e3 100644
--- a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php
+++ b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php
@@ -51,10 +51,10 @@ class CleanPreviewsBackgroundJob extends QueuedJob {
* CleanPreviewsBackgroundJob constructor.
*/
public function __construct(IRootFolder $rootFolder,
- LoggerInterface $logger,
- IJobList $jobList,
- ITimeFactory $timeFactory,
- IUserManager $userManager) {
+ LoggerInterface $logger,
+ IJobList $jobList,
+ ITimeFactory $timeFactory,
+ IUserManager $userManager) {
$this->rootFolder = $rootFolder;
$this->logger = $logger;
$this->jobList = $jobList;
diff --git a/lib/private/Repair/Owncloud/MigrateOauthTables.php b/lib/private/Repair/Owncloud/MigrateOauthTables.php
index 5bf0816d8de..ae2b46e1949 100644
--- a/lib/private/Repair/Owncloud/MigrateOauthTables.php
+++ b/lib/private/Repair/Owncloud/MigrateOauthTables.php
@@ -20,11 +20,11 @@
*/
namespace OC\Repair\Owncloud;
-use OCP\Migration\IOutput;
-use OCP\Migration\IRepairStep;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
class MigrateOauthTables implements IRepairStep {
/** @var Connection */
diff --git a/lib/private/Repair/Owncloud/MoveAvatars.php b/lib/private/Repair/Owncloud/MoveAvatars.php
index 44ba9b7643b..1ec08710b3a 100644
--- a/lib/private/Repair/Owncloud/MoveAvatars.php
+++ b/lib/private/Repair/Owncloud/MoveAvatars.php
@@ -41,7 +41,7 @@ class MoveAvatars implements IRepairStep {
* @param IConfig $config
*/
public function __construct(IJobList $jobList,
- IConfig $config) {
+ IConfig $config) {
$this->jobList = $jobList;
$this->config = $config;
}
diff --git a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php
index e08a0b55a9a..ae8e8bb0743 100644
--- a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php
+++ b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php
@@ -40,7 +40,7 @@ class UpdateLanguageCodes implements IRepairStep {
* @param IConfig $config
*/
public function __construct(IDBConnection $connection,
- IConfig $config) {
+ IConfig $config) {
$this->connection = $connection;
$this->config = $config;
}
diff --git a/lib/private/Repair/RemoveLinkShares.php b/lib/private/Repair/RemoveLinkShares.php
index b45a1d83a56..3e47e3233a2 100644
--- a/lib/private/Repair/RemoveLinkShares.php
+++ b/lib/private/Repair/RemoveLinkShares.php
@@ -54,10 +54,10 @@ class RemoveLinkShares implements IRepairStep {
private $timeFactory;
public function __construct(IDBConnection $connection,
- IConfig $config,
- IGroupManager $groupManager,
- IManager $notificationManager,
- ITimeFactory $timeFactory) {
+ IConfig $config,
+ IGroupManager $groupManager,
+ IManager $notificationManager,
+ ITimeFactory $timeFactory) {
$this->connection = $connection;
$this->config = $config;
$this->groupManager = $groupManager;
diff --git a/lib/private/Repair/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php
index ee5a84ad65c..4d15dda45dd 100644
--- a/lib/private/Repair/RepairMimeTypes.php
+++ b/lib/private/Repair/RepairMimeTypes.php
@@ -49,7 +49,7 @@ class RepairMimeTypes implements IRepairStep {
protected $folderMimeTypeId;
public function __construct(IConfig $config,
- IDBConnection $connection) {
+ IDBConnection $connection) {
$this->config = $config;
$this->connection = $connection;
}
@@ -229,6 +229,22 @@ class RepairMimeTypes implements IRepairStep {
return $this->updateMimetypes($updatedMimetypes);
}
+ private function introduceEnhancedMetafileFormatType() {
+ $updatedMimetypes = [
+ 'emf' => 'image/emf',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceEmlAndMsgFormatType() {
+ $updatedMimetypes = [
+ 'eml' => 'message/rfc822',
+ 'msg' => 'application/vnd.ms-outlook',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
/**
* Fix mime types
@@ -286,5 +302,13 @@ class RepairMimeTypes implements IRepairStep {
if (version_compare($ocVersionFromBeforeUpdate, '26.0.0.1', '<') && $this->introduceAsciidocType()) {
$out->info('Fixed AsciiDoc mime types');
}
+
+ if (version_compare($ocVersionFromBeforeUpdate, '28.0.0.5', '<') && $this->introduceEnhancedMetafileFormatType()) {
+ $out->info('Fixed Enhanced Metafile Format mime types');
+ }
+
+ if (version_compare($ocVersionFromBeforeUpdate, '29.0.0.2', '<') && $this->introduceEmlAndMsgFormatType()) {
+ $out->info('Fixed eml and msg mime type');
+ }
}
}
diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php
index 5ce6c7c5c8f..65bbf602be0 100644
--- a/lib/private/Route/Router.php
+++ b/lib/private/Route/Router.php
@@ -230,9 +230,9 @@ class Router implements IRouter {
* @return \OC\Route\Route
*/
public function create($name,
- $pattern,
- array $defaults = [],
- array $requirements = []) {
+ $pattern,
+ array $defaults = [],
+ array $requirements = []) {
$route = new Route($pattern, $defaults, $requirements);
$this->collection->add($name, $route);
return $route;
@@ -354,8 +354,8 @@ class Router implements IRouter {
* @return string
*/
public function generate($name,
- $parameters = [],
- $absolute = false) {
+ $parameters = [],
+ $absolute = false) {
$referenceType = UrlGenerator::ABSOLUTE_URL;
if ($absolute === false) {
$referenceType = UrlGenerator::ABSOLUTE_PATH;
diff --git a/lib/private/Search/Filter/BooleanFilter.php b/lib/private/Search/Filter/BooleanFilter.php
new file mode 100644
index 00000000000..a64bf17f31c
--- /dev/null
+++ b/lib/private/Search/Filter/BooleanFilter.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class BooleanFilter implements IFilter {
+ private bool $value;
+
+ public function __construct(string $value) {
+ $this->value = match ($value) {
+ 'true', 'yes', 'y', '1' => true,
+ 'false', 'no', 'n', '0', '' => false,
+ default => throw new InvalidArgumentException('Invalid boolean value '. $value),
+ };
+ }
+
+ public function get(): bool {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/DateTimeFilter.php b/lib/private/Search/Filter/DateTimeFilter.php
new file mode 100644
index 00000000000..79abf9ad542
--- /dev/null
+++ b/lib/private/Search/Filter/DateTimeFilter.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use DateTimeImmutable;
+use OCP\Search\IFilter;
+
+class DateTimeFilter implements IFilter {
+ private DateTimeImmutable $value;
+
+ public function __construct(string $value) {
+ if (filter_var($value, FILTER_VALIDATE_INT)) {
+ $value = '@'.$value;
+ }
+
+ $this->value = new DateTimeImmutable($value);
+ }
+
+ public function get(): DateTimeImmutable {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/FloatFilter.php b/lib/private/Search/Filter/FloatFilter.php
new file mode 100644
index 00000000000..3db19ded59b
--- /dev/null
+++ b/lib/private/Search/Filter/FloatFilter.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class FloatFilter implements IFilter {
+ private float $value;
+
+ public function __construct(string $value) {
+ $this->value = filter_var($value, FILTER_VALIDATE_FLOAT);
+ if ($this->value === false) {
+ throw new InvalidArgumentException('Invalid float value '. $value);
+ }
+ }
+
+ public function get(): float {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/GroupFilter.php b/lib/private/Search/Filter/GroupFilter.php
new file mode 100644
index 00000000000..f0b34a360ca
--- /dev/null
+++ b/lib/private/Search/Filter/GroupFilter.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\IGroup;
+use OCP\IGroupManager;
+use OCP\Search\IFilter;
+
+class GroupFilter implements IFilter {
+ private IGroup $group;
+
+ public function __construct(
+ string $value,
+ IGroupManager $groupManager,
+ ) {
+ $group = $groupManager->get($value);
+ if ($group === null) {
+ throw new InvalidArgumentException('Group '.$value.' not found');
+ }
+ $this->group = $group;
+ }
+
+ public function get(): IGroup {
+ return $this->group;
+ }
+}
diff --git a/lib/private/Search/Filter/IntegerFilter.php b/lib/private/Search/Filter/IntegerFilter.php
new file mode 100644
index 00000000000..b5b907b220e
--- /dev/null
+++ b/lib/private/Search/Filter/IntegerFilter.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class IntegerFilter implements IFilter {
+ private int $value;
+
+ public function __construct(string $value) {
+ $this->value = filter_var($value, FILTER_VALIDATE_INT);
+ if ($this->value === false) {
+ throw new InvalidArgumentException('Invalid integer value '. $value);
+ }
+ }
+
+ public function get(): int {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/StringFilter.php b/lib/private/Search/Filter/StringFilter.php
new file mode 100644
index 00000000000..8f754d12051
--- /dev/null
+++ b/lib/private/Search/Filter/StringFilter.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class StringFilter implements IFilter {
+ public function __construct(
+ private string $value,
+ ) {
+ if ($value === '') {
+ throw new InvalidArgumentException('String filter can’t be empty');
+ }
+ }
+
+ public function get(): string {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/StringsFilter.php b/lib/private/Search/Filter/StringsFilter.php
new file mode 100644
index 00000000000..7a8d88768e8
--- /dev/null
+++ b/lib/private/Search/Filter/StringsFilter.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class StringsFilter implements IFilter {
+ /**
+ * @var string[]
+ */
+ private array $values;
+
+ public function __construct(string ...$values) {
+ $this->values = array_unique(array_filter($values));
+ if (empty($this->values)) {
+ throw new InvalidArgumentException('Strings filter can’t be empty');
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ public function get(): array {
+ return $this->values;
+ }
+}
diff --git a/lib/private/Search/Filter/UserFilter.php b/lib/private/Search/Filter/UserFilter.php
new file mode 100644
index 00000000000..963d5e123ac
--- /dev/null
+++ b/lib/private/Search/Filter/UserFilter.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Search\IFilter;
+
+class UserFilter implements IFilter {
+ private IUser $user;
+
+ public function __construct(
+ string $value,
+ IUserManager $userManager,
+ ) {
+ $user = $userManager->get($value);
+ if ($user === null) {
+ throw new InvalidArgumentException('User '.$value.' not found');
+ }
+ $this->user = $user;
+ }
+
+ public function get(): IUser {
+ return $this->user;
+ }
+}
diff --git a/lib/private/Search/FilterCollection.php b/lib/private/Search/FilterCollection.php
new file mode 100644
index 00000000000..15d6695dcac
--- /dev/null
+++ b/lib/private/Search/FilterCollection.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search;
+
+use Generator;
+use OCP\Search\IFilter;
+use OCP\Search\IFilterCollection;
+
+/**
+ * Interface for search filters
+ *
+ * @since 28.0.0
+ */
+class FilterCollection implements IFilterCollection {
+ /**
+ * @var IFilter[]
+ */
+ private array $filters;
+
+ public function __construct(IFilter ...$filters) {
+ $this->filters = $filters;
+ }
+
+ public function has(string $name): bool {
+ return isset($this->filters[$name]);
+ }
+
+ public function get(string $name): ?IFilter {
+ return $this->filters[$name] ?? null;
+ }
+
+ public function getIterator(): Generator {
+ foreach ($this->filters as $k => $v) {
+ yield $k => $v;
+ }
+ }
+}
diff --git a/lib/private/Search/FilterFactory.php b/lib/private/Search/FilterFactory.php
new file mode 100644
index 00000000000..3f4388f405c
--- /dev/null
+++ b/lib/private/Search/FilterFactory.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search;
+
+use OCP\IGroupManager;
+use OCP\IUserManager;
+use OCP\Search\FilterDefinition;
+use OCP\Search\IFilter;
+use RuntimeException;
+
+final class FilterFactory {
+ private const PERSON_TYPE_SEPARATOR = '/';
+
+ public static function get(string $type, string|array $filter): IFilter {
+ return match ($type) {
+ FilterDefinition::TYPE_BOOL => new Filter\BooleanFilter($filter),
+ FilterDefinition::TYPE_DATETIME => new Filter\DateTimeFilter($filter),
+ FilterDefinition::TYPE_FLOAT => new Filter\FloatFilter($filter),
+ FilterDefinition::TYPE_INT => new Filter\IntegerFilter($filter),
+ FilterDefinition::TYPE_NC_GROUP => new Filter\GroupFilter($filter, \OC::$server->get(IGroupManager::class)),
+ FilterDefinition::TYPE_NC_USER => new Filter\UserFilter($filter, \OC::$server->get(IUserManager::class)),
+ FilterDefinition::TYPE_PERSON => self::getPerson($filter),
+ FilterDefinition::TYPE_STRING => new Filter\StringFilter($filter),
+ FilterDefinition::TYPE_STRINGS => new Filter\StringsFilter(... (array) $filter),
+ default => throw new RuntimeException('Invalid filter type '. $type),
+ };
+ }
+
+ private static function getPerson(string $person): IFilter {
+ $parts = explode(self::PERSON_TYPE_SEPARATOR, $person, 2);
+
+ return match (count($parts)) {
+ 1 => self::get(FilterDefinition::TYPE_NC_USER, $person),
+ 2 => self::get(... $parts),
+ };
+ }
+}
diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php
index 4ec73ec54e9..03e84a079fe 100644
--- a/lib/private/Search/SearchComposer.php
+++ b/lib/private/Search/SearchComposer.php
@@ -28,14 +28,20 @@ declare(strict_types=1);
namespace OC\Search;
use InvalidArgumentException;
-use OCP\AppFramework\QueryException;
-use OCP\IServerContainer;
+use OC\AppFramework\Bootstrap\Coordinator;
+use OCP\IURLGenerator;
use OCP\IUser;
+use OCP\Search\FilterDefinition;
+use OCP\Search\IFilter;
+use OCP\Search\IFilteringProvider;
+use OCP\Search\IInAppSearch;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
-use OC\AppFramework\Bootstrap\Coordinator;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use RuntimeException;
use function array_map;
/**
@@ -58,31 +64,40 @@ use function array_map;
* @see IProvider::search() for the arguments of the individual search requests
*/
class SearchComposer {
- /** @var IProvider[] */
- private $providers = [];
-
- /** @var Coordinator */
- private $bootstrapCoordinator;
+ /**
+ * @var array<string, array{appId: string, provider: IProvider}>
+ */
+ private array $providers = [];
- /** @var IServerContainer */
- private $container;
+ private array $commonFilters;
+ private array $customFilters = [];
- private LoggerInterface $logger;
+ private array $handlers = [];
- public function __construct(Coordinator $bootstrapCoordinator,
- IServerContainer $container,
- LoggerInterface $logger) {
- $this->container = $container;
- $this->logger = $logger;
- $this->bootstrapCoordinator = $bootstrapCoordinator;
+ public function __construct(
+ private Coordinator $bootstrapCoordinator,
+ private ContainerInterface $container,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger
+ ) {
+ $this->commonFilters = [
+ IFilter::BUILTIN_TERM => new FilterDefinition(IFilter::BUILTIN_TERM, FilterDefinition::TYPE_STRING),
+ IFilter::BUILTIN_SINCE => new FilterDefinition(IFilter::BUILTIN_SINCE, FilterDefinition::TYPE_DATETIME),
+ IFilter::BUILTIN_UNTIL => new FilterDefinition(IFilter::BUILTIN_UNTIL, FilterDefinition::TYPE_DATETIME),
+ IFilter::BUILTIN_TITLE_ONLY => new FilterDefinition(IFilter::BUILTIN_TITLE_ONLY, FilterDefinition::TYPE_BOOL, false),
+ IFilter::BUILTIN_PERSON => new FilterDefinition(IFilter::BUILTIN_PERSON, FilterDefinition::TYPE_PERSON),
+ IFilter::BUILTIN_PLACES => new FilterDefinition(IFilter::BUILTIN_PLACES, FilterDefinition::TYPE_STRINGS, false),
+ IFilter::BUILTIN_PROVIDER => new FilterDefinition(IFilter::BUILTIN_PROVIDER, FilterDefinition::TYPE_STRING, false),
+ ];
}
/**
* Load all providers dynamically that were registered through `registerProvider`
*
+ * If $targetProviderId is provided, only this provider is loaded
* If a provider can't be loaded we log it but the operation continues nevertheless
*/
- private function loadLazyProviders(): void {
+ private function loadLazyProviders(?string $targetProviderId = null): void {
$context = $this->bootstrapCoordinator->getRegistrationContext();
if ($context === null) {
// Too early, nothing registered yet
@@ -93,9 +108,20 @@ class SearchComposer {
foreach ($registrations as $registration) {
try {
/** @var IProvider $provider */
- $provider = $this->container->query($registration->getService());
- $this->providers[$provider->getId()] = $provider;
- } catch (QueryException $e) {
+ $provider = $this->container->get($registration->getService());
+ $providerId = $provider->getId();
+ if ($targetProviderId !== null && $targetProviderId !== $providerId) {
+ continue;
+ }
+ $this->providers[$providerId] = [
+ 'appId' => $registration->getAppId(),
+ 'provider' => $provider,
+ ];
+ $this->handlers[$providerId] = [$providerId];
+ if ($targetProviderId !== null) {
+ break;
+ }
+ } catch (ContainerExceptionInterface $e) {
// Log an continue. We can be fault tolerant here.
$this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [
'exception' => $e,
@@ -103,6 +129,43 @@ class SearchComposer {
]);
}
}
+
+ $this->loadFilters();
+ }
+
+ private function loadFilters(): void {
+ foreach ($this->providers as $providerId => $providerData) {
+ $appId = $providerData['appId'];
+ $provider = $providerData['provider'];
+ if (!$provider instanceof IFilteringProvider) {
+ continue;
+ }
+
+ foreach ($provider->getCustomFilters() as $filter) {
+ $this->registerCustomFilter($filter, $providerId);
+ }
+ foreach ($provider->getAlternateIds() as $alternateId) {
+ $this->handlers[$alternateId][] = $providerId;
+ }
+ foreach ($provider->getSupportedFilters() as $filterName) {
+ if ($this->getFilterDefinition($filterName, $providerId) === null) {
+ throw new InvalidArgumentException('Invalid filter '. $filterName);
+ }
+ }
+ }
+ }
+
+ private function registerCustomFilter(FilterDefinition $filter, string $providerId): void {
+ $name = $filter->name();
+ if (isset($this->commonFilters[$name])) {
+ throw new InvalidArgumentException('Filter name is already used');
+ }
+
+ if (isset($this->customFilters[$providerId])) {
+ $this->customFilters[$providerId][$name] = $filter;
+ } else {
+ $this->customFilters[$providerId] = [$name => $filter];
+ }
}
/**
@@ -117,26 +180,146 @@ class SearchComposer {
public function getProviders(string $route, array $routeParameters): array {
$this->loadLazyProviders();
- $providers = array_values(
- array_map(function (IProvider $provider) use ($route, $routeParameters) {
+ $providers = array_map(
+ function (array $providerData) use ($route, $routeParameters) {
+ $appId = $providerData['appId'];
+ $provider = $providerData['provider'];
+ $order = $provider->getOrder($route, $routeParameters);
+ if ($order === null) {
+ return;
+ }
+ $triggers = [$provider->getId()];
+ if ($provider instanceof IFilteringProvider) {
+ $triggers += $provider->getAlternateIds();
+ $filters = $provider->getSupportedFilters();
+ } else {
+ $filters = [IFilter::BUILTIN_TERM];
+ }
+
return [
'id' => $provider->getId(),
+ 'appId' => $appId,
'name' => $provider->getName(),
- 'order' => $provider->getOrder($route, $routeParameters),
+ 'icon' => $this->fetchIcon($appId, $provider->getId()),
+ 'order' => $order,
+ 'triggers' => $triggers,
+ 'filters' => $this->getFiltersType($filters, $provider->getId()),
+ 'inAppSearch' => $provider instanceof IInAppSearch,
];
- }, $this->providers)
+ },
+ $this->providers,
);
+ $providers = array_filter($providers);
+ // Sort providers by order and strip associative keys
usort($providers, function ($provider1, $provider2) {
return $provider1['order'] <=> $provider2['order'];
});
- /**
- * Return an array with the IDs, but strip the associative keys
- */
return $providers;
}
+ private function fetchIcon(string $appId, string $providerId): string {
+ $icons = [
+ [$providerId, $providerId.'.svg'],
+ [$providerId, 'app.svg'],
+ [$appId, $providerId.'.svg'],
+ [$appId, $appId.'.svg'],
+ [$appId, 'app.svg'],
+ ['core', 'places/default-app-icon.svg'],
+ ];
+ if ($appId === 'settings' && $providerId === 'users') {
+ // Conflict:
+ // the file /apps/settings/users.svg is already used in black version by top right user menu
+ // Override icon name here
+ $icons = [['settings', 'users-white.svg']];
+ }
+ foreach ($icons as $i => $icon) {
+ try {
+ return $this->urlGenerator->imagePath(... $icon);
+ } catch (RuntimeException $e) {
+ // Ignore error
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * @param $filters string[]
+ * @return array<string, string>
+ */
+ private function getFiltersType(array $filters, string $providerId): array {
+ $filterList = [];
+ foreach ($filters as $filter) {
+ $filterList[$filter] = $this->getFilterDefinition($filter, $providerId)->type();
+ }
+
+ return $filterList;
+ }
+
+ private function getFilterDefinition(string $name, string $providerId): ?FilterDefinition {
+ if (isset($this->commonFilters[$name])) {
+ return $this->commonFilters[$name];
+ }
+ if (isset($this->customFilters[$providerId][$name])) {
+ return $this->customFilters[$providerId][$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param array<string, string> $parameters
+ */
+ public function buildFilterList(string $providerId, array $parameters): FilterCollection {
+ $this->loadLazyProviders($providerId);
+
+ $list = [];
+ foreach ($parameters as $name => $value) {
+ $filter = $this->buildFilter($name, $value, $providerId);
+ if ($filter === null) {
+ continue;
+ }
+ $list[$name] = $filter;
+ }
+
+ return new FilterCollection(... $list);
+ }
+
+ private function buildFilter(string $name, string $value, string $providerId): ?IFilter {
+ $filterDefinition = $this->getFilterDefinition($name, $providerId);
+ if ($filterDefinition === null) {
+ $this->logger->debug('Unable to find {name} definition', [
+ 'name' => $name,
+ 'value' => $value,
+ ]);
+
+ return null;
+ }
+
+ if (!$this->filterSupportedByProvider($filterDefinition, $providerId)) {
+ // FIXME Use dedicated exception and handle it
+ throw new UnsupportedFilter($name, $providerId);
+ }
+
+ return FilterFactory::get($filterDefinition->type(), $value);
+ }
+
+ private function filterSupportedByProvider(FilterDefinition $filterDefinition, string $providerId): bool {
+ // Non exclusive filters can be ommited by apps
+ if (!$filterDefinition->exclusive()) {
+ return true;
+ }
+
+ $provider = $this->providers[$providerId]['provider'];
+ $supportedFilters = $provider instanceof IFilteringProvider
+ ? $provider->getSupportedFilters()
+ : [IFilter::BUILTIN_TERM];
+
+ return in_array($filterDefinition->name(), $supportedFilters, true);
+ }
+
/**
* Query an individual search provider for results
*
@@ -147,15 +330,18 @@ class SearchComposer {
* @return SearchResult
* @throws InvalidArgumentException when the $providerId does not correspond to a registered provider
*/
- public function search(IUser $user,
- string $providerId,
- ISearchQuery $query): SearchResult {
- $this->loadLazyProviders();
+ public function search(
+ IUser $user,
+ string $providerId,
+ ISearchQuery $query,
+ ): SearchResult {
+ $this->loadLazyProviders($providerId);
- $provider = $this->providers[$providerId] ?? null;
+ $provider = $this->providers[$providerId]['provider'] ?? null;
if ($provider === null) {
throw new InvalidArgumentException("Provider $providerId is unknown");
}
+
return $provider->search($user, $query);
}
}
diff --git a/lib/private/Search/SearchQuery.php b/lib/private/Search/SearchQuery.php
index c89446d5970..e4295c4ab76 100644
--- a/lib/private/Search/SearchQuery.php
+++ b/lib/private/Search/SearchQuery.php
@@ -27,89 +27,57 @@ declare(strict_types=1);
*/
namespace OC\Search;
+use OCP\Search\IFilter;
+use OCP\Search\IFilterCollection;
use OCP\Search\ISearchQuery;
class SearchQuery implements ISearchQuery {
public const LIMIT_DEFAULT = 5;
- /** @var string */
- private $term;
-
- /** @var int */
- private $sortOrder;
-
- /** @var int */
- private $limit;
-
- /** @var int|string|null */
- private $cursor;
-
- /** @var string */
- private $route;
-
- /** @var array */
- private $routeParameters;
-
/**
- * @param string $term
- * @param int $sortOrder
- * @param int $limit
- * @param int|string|null $cursor
- * @param string $route
- * @param array $routeParameters
+ * @param string[] $params Request query
+ * @param string[] $routeParameters
*/
- public function __construct(string $term,
- int $sortOrder = ISearchQuery::SORT_DATE_DESC,
- int $limit = self::LIMIT_DEFAULT,
- $cursor = null,
- string $route = '',
- array $routeParameters = []) {
- $this->term = $term;
- $this->sortOrder = $sortOrder;
- $this->limit = $limit;
- $this->cursor = $cursor;
- $this->route = $route;
- $this->routeParameters = $routeParameters;
+ public function __construct(
+ private IFilterCollection $filters,
+ private int $sortOrder = ISearchQuery::SORT_DATE_DESC,
+ private int $limit = self::LIMIT_DEFAULT,
+ private int|string|null $cursor = null,
+ private string $route = '',
+ private array $routeParameters = [],
+ ) {
}
- /**
- * @inheritDoc
- */
public function getTerm(): string {
- return $this->term;
+ return $this->getFilter('term')?->get() ?? '';
+ }
+
+ public function getFilter(string $name): ?IFilter {
+ return $this->filters->has($name)
+ ? $this->filters->get($name)
+ : null;
+ }
+
+ public function getFilters(): IFilterCollection {
+ return $this->filters;
}
- /**
- * @inheritDoc
- */
public function getSortOrder(): int {
return $this->sortOrder;
}
- /**
- * @inheritDoc
- */
public function getLimit(): int {
return $this->limit;
}
- /**
- * @inheritDoc
- */
- public function getCursor() {
+ public function getCursor(): int|string|null {
return $this->cursor;
}
- /**
- * @inheritDoc
- */
public function getRoute(): string {
return $this->route;
}
- /**
- * @inheritDoc
- */
public function getRouteParameters(): array {
return $this->routeParameters;
}
diff --git a/lib/private/Search/UnsupportedFilter.php b/lib/private/Search/UnsupportedFilter.php
new file mode 100644
index 00000000000..84b6163d2fa
--- /dev/null
+++ b/lib/private/Search/UnsupportedFilter.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @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\Search;
+
+use Exception;
+
+final class UnsupportedFilter extends Exception {
+ public function __construct(string $filerName, $providerId) {
+ parent::__construct('Provider '.$providerId.' doesn’t support filter '.$filerName.'.');
+ }
+}
diff --git a/lib/private/Security/Bruteforce/Capabilities.php b/lib/private/Security/Bruteforce/Capabilities.php
index b50eea0b7af..add2bb8d8b5 100644
--- a/lib/private/Security/Bruteforce/Capabilities.php
+++ b/lib/private/Security/Bruteforce/Capabilities.php
@@ -29,8 +29,8 @@ declare(strict_types=1);
*/
namespace OC\Security\Bruteforce;
-use OCP\Capabilities\IPublicCapability;
use OCP\Capabilities\IInitialStateExcludedCapability;
+use OCP\Capabilities\IPublicCapability;
use OCP\IRequest;
use OCP\Security\Bruteforce\IThrottler;
diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php
index 5316071f25c..7e5f1daa28c 100644
--- a/lib/private/Security/Bruteforce/Throttler.php
+++ b/lib/private/Security/Bruteforce/Throttler.php
@@ -72,8 +72,8 @@ class Throttler implements IThrottler {
* {@inheritDoc}
*/
public function registerAttempt(string $action,
- string $ip,
- array $metadata = []): void {
+ string $ip,
+ array $metadata = []): void {
// No need to log if the bruteforce protection is disabled
if (!$this->config->getSystemValueBool('auth.bruteforce.protection.enabled', true)) {
return;
diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php
index eca3e2b6b29..ee525af4c2a 100644
--- a/lib/private/Security/CSP/ContentSecurityPolicy.php
+++ b/lib/private/Security/CSP/ContentSecurityPolicy.php
@@ -191,4 +191,12 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
public function setStrictDynamicAllowed(bool $strictDynamicAllowed): void {
$this->strictDynamicAllowed = $strictDynamicAllowed;
}
+
+ public function isStrictDynamicAllowedOnScripts(): bool {
+ return $this->strictDynamicAllowedOnScripts;
+ }
+
+ public function setStrictDynamicAllowedOnScripts(bool $strictDynamicAllowedOnScripts): void {
+ $this->strictDynamicAllowedOnScripts = $strictDynamicAllowedOnScripts;
+ }
}
diff --git a/lib/private/Security/Normalizer/IpAddress.php b/lib/private/Security/Normalizer/IpAddress.php
index 9aade6c3591..f8e55370da7 100644
--- a/lib/private/Security/Normalizer/IpAddress.php
+++ b/lib/private/Security/Normalizer/IpAddress.php
@@ -38,7 +38,7 @@ namespace OC\Security\Normalizer;
*/
class IpAddress {
/**
- * @param string $ip IP to normalized
+ * @param string $ip IP to normalize
*/
public function __construct(
private string $ip,
@@ -46,24 +46,9 @@ class IpAddress {
}
/**
- * Return the given subnet for an IPv4 address and mask bits
+ * Return the given subnet for an IPv6 address (64 first bits)
*/
- private function getIPv4Subnet(string $ip, int $maskBits = 32): string {
- $binary = \inet_pton($ip);
- for ($i = 32; $i > $maskBits; $i -= 8) {
- $j = \intdiv($i, 8) - 1;
- $k = \min(8, $i - $maskBits);
- $mask = (0xff - ((2 ** $k) - 1));
- $int = \unpack('C', $binary[$j]);
- $binary[$j] = \pack('C', $int[1] & $mask);
- }
- return \inet_ntop($binary).'/'.$maskBits;
- }
-
- /**
- * Return the given subnet for an IPv6 address and mask bits
- */
- private function getIPv6Subnet(string $ip, int $maskBits = 48): string {
+ private function getIPv6Subnet(string $ip): string {
if ($ip[0] === '[' && $ip[-1] === ']') { // If IP is with brackets, for example [::1]
$ip = substr($ip, 1, strlen($ip) - 2);
}
@@ -71,15 +56,11 @@ class IpAddress {
if ($pos !== false) {
$ip = substr($ip, 0, $pos - 1);
}
+
$binary = \inet_pton($ip);
- for ($i = 128; $i > $maskBits; $i -= 8) {
- $j = \intdiv($i, 8) - 1;
- $k = \min(8, $i - $maskBits);
- $mask = (0xff - ((2 ** $k) - 1));
- $int = \unpack('C', $binary[$j]);
- $binary[$j] = \pack('C', $int[1] & $mask);
- }
- return \inet_ntop($binary).'/'.$maskBits;
+ $mask = inet_pton('FFFF:FFFF:FFFF:FFFF::');
+
+ return inet_ntop($binary & $mask).'/64';
}
/**
@@ -93,24 +74,13 @@ class IpAddress {
if (!$binary) {
return null;
}
- for ($i = 0; $i <= 9; $i++) {
- if (unpack('C', $binary[$i])[1] !== 0) {
- return null;
- }
- }
- for ($i = 10; $i <= 11; $i++) {
- if (unpack('C', $binary[$i])[1] !== 255) {
- return null;
- }
- }
-
- $binary4 = '';
- for ($i = 12; $i < 16; $i++) {
- $binary4 .= $binary[$i];
+ $mask = inet_pton('::FFFF:FFFF');
+ if (($binary & ~$mask) !== inet_pton('::FFFF:0.0.0.0')) {
+ return null;
}
- return inet_ntop($binary4);
+ return inet_ntop(substr($binary, -4));
}
@@ -118,25 +88,16 @@ class IpAddress {
* Gets either the /32 (IPv4) or the /64 (IPv6) subnet of an IP address
*/
public function getSubnet(): string {
- if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->ip)) {
- return $this->getIPv4Subnet(
- $this->ip,
- 32
- );
+ if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return $this->ip.'/32';
}
$ipv4 = $this->getEmbeddedIpv4($this->ip);
if ($ipv4 !== null) {
- return $this->getIPv4Subnet(
- $ipv4,
- 32
- );
+ return $ipv4.'/32';
}
- return $this->getIPv6Subnet(
- $this->ip,
- 64
- );
+ return $this->getIPv6Subnet($this->ip);
}
/**
diff --git a/lib/private/Security/RemoteHostValidator.php b/lib/private/Security/RemoteHostValidator.php
index 385b38cff98..9cc69594c32 100644
--- a/lib/private/Security/RemoteHostValidator.php
+++ b/lib/private/Security/RemoteHostValidator.php
@@ -52,6 +52,10 @@ final class RemoteHostValidator implements IRemoteHostValidator {
}
$host = idn_to_utf8(strtolower(urldecode($host)));
+ if ($host === false) {
+ return false;
+ }
+
// Remove brackets from IPv6 addresses
if (str_starts_with($host, '[') && str_ends_with($host, ']')) {
$host = substr($host, 1, -1);
diff --git a/lib/private/Security/VerificationToken/CleanUpJob.php b/lib/private/Security/VerificationToken/CleanUpJob.php
index 1f4af046451..9c1b27d344d 100644
--- a/lib/private/Security/VerificationToken/CleanUpJob.php
+++ b/lib/private/Security/VerificationToken/CleanUpJob.php
@@ -27,10 +27,10 @@ declare(strict_types=1);
namespace OC\Security\VerificationToken;
use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\IConfig;
-use OCP\IUserManager;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\Job;
+use OCP\IConfig;
+use OCP\IUserManager;
use OCP\Security\VerificationToken\InvalidTokenException;
use OCP\Security\VerificationToken\IVerificationToken;
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 37b7669f624..cf4262e2d50 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -102,6 +102,7 @@ use OC\Files\Storage\StorageFactory;
use OC\Files\Template\TemplateManager;
use OC\Files\Type\Loader;
use OC\Files\View;
+use OC\FilesMetadata\FilesMetadataManager;
use OC\FullTextSearch\FullTextSearchManager;
use OC\Http\Client\ClientService;
use OC\Http\Client\NegativeDnsCache;
@@ -109,8 +110,8 @@ use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\AppLocator;
use OC\IntegrityCheck\Helpers\EnvironmentHelper;
use OC\IntegrityCheck\Helpers\FileAccessHelper;
-use OC\LDAP\NullLDAPProviderFactory;
use OC\KnownUser\KnownUserService;
+use OC\LDAP\NullLDAPProviderFactory;
use OC\Lock\DBLockingProvider;
use OC\Lock\MemcacheLockingProvider;
use OC\Lock\NoopLockingProvider;
@@ -120,9 +121,6 @@ use OC\Log\PsrLoggerAdapter;
use OC\Mail\Mailer;
use OC\Memcache\ArrayCache;
use OC\Memcache\Factory;
-use OC\Metadata\Capabilities as MetadataCapabilities;
-use OC\Metadata\IMetadataManager;
-use OC\Metadata\MetadataManager;
use OC\Notification\Manager;
use OC\OCM\Model\OCMProvider;
use OC\OCM\OCMDiscoveryService;
@@ -131,6 +129,7 @@ use OC\Preview\GeneratorHelper;
use OC\Preview\IMagickSupport;
use OC\Preview\MimeIconProvider;
use OC\Profile\ProfileManager;
+use OC\Profiler\Profiler;
use OC\Remote\Api\ApiFactory;
use OC\Remote\InstanceFactory;
use OC\RichObjectStrings\Validator;
@@ -160,10 +159,15 @@ use OC\Tagging\TagMapper;
use OC\Talk\Broker;
use OC\Template\JSCombiner;
use OC\Translation\TranslationManager;
+use OC\User\AvailabilityCoordinator;
use OC\User\DisplayNameCache;
use OC\User\Listeners\BeforeUserDeletedListener;
use OC\User\Listeners\UserChangedListener;
use OC\User\Session;
+use OCA\Files_External\Service\BackendService;
+use OCA\Files_External\Service\GlobalStoragesService;
+use OCA\Files_External\Service\UserGlobalStoragesService;
+use OCA\Files_External\Service\UserStoragesService;
use OCA\Theming\ImageManager;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
@@ -196,16 +200,17 @@ use OCP\Files\Lock\ILockManager;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Storage\IStorageFactory;
use OCP\Files\Template\ITemplateManager;
+use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\FullTextSearch\IFullTextSearchManager;
use OCP\GlobalScale\IConfig;
use OCP\Group\ISubAdmin;
use OCP\Http\Client\IClientService;
use OCP\IAppConfig;
use OCP\IAvatarManager;
+use OCP\IBinaryFinder;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\ICertificateManager;
-use OCP\IBinaryFinder;
use OCP\IDateTimeFormatter;
use OCP\IDateTimeZone;
use OCP\IDBConnection;
@@ -236,7 +241,9 @@ use OCP\Log\ILogFactory;
use OCP\Mail\IMailer;
use OCP\OCM\IOCMDiscoveryService;
use OCP\OCM\IOCMProvider;
+use OCP\Preview\IMimeIconProvider;
use OCP\Profile\IProfileManager;
+use OCP\Profiler\IProfiler;
use OCP\Remote\Api\IApiFactory;
use OCP\Remote\IInstanceFactory;
use OCP\RichObjectStrings\IValidator;
@@ -266,16 +273,10 @@ use OCP\User\Events\UserChangedEvent;
use OCP\User\Events\UserLoggedInEvent;
use OCP\User\Events\UserLoggedInWithCookieEvent;
use OCP\User\Events\UserLoggedOutEvent;
+use OCP\User\IAvailabilityCoordinator;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
-use OCA\Files_External\Service\UserStoragesService;
-use OCA\Files_External\Service\UserGlobalStoragesService;
-use OCA\Files_External\Service\GlobalStoragesService;
-use OCA\Files_External\Service\BackendService;
-use OCP\Profiler\IProfiler;
-use OC\Profiler\Profiler;
-use OCP\Preview\IMimeIconProvider;
/**
* Class Server
@@ -1134,9 +1135,6 @@ class Server extends ServerContainer implements IServerContainer {
$manager->registerCapability(function () use ($c) {
return $c->get(\OC\Security\Bruteforce\Capabilities::class);
});
- $manager->registerCapability(function () use ($c) {
- return $c->get(MetadataCapabilities::class);
- });
return $manager;
});
/** @deprecated 19.0.0 */
@@ -1401,6 +1399,7 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class);
$this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class);
+ $this->registerAlias(IFilesMetadataManager::class, FilesMetadataManager::class);
$this->registerAlias(ISubAdmin::class, SubAdmin::class);
@@ -1412,8 +1411,6 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(IBroker::class, Broker::class);
- $this->registerAlias(IMetadataManager::class, MetadataManager::class);
-
$this->registerAlias(\OCP\Files\AppData\IAppDataFactory::class, \OC\Files\AppData\Factory::class);
$this->registerAlias(IBinaryFinder::class, BinaryFinder::class);
@@ -1428,6 +1425,8 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(\OCP\TextProcessing\IManager::class, \OC\TextProcessing\Manager::class);
+ $this->registerAlias(\OCP\TextToImage\IManager::class, \OC\TextToImage\Manager::class);
+
$this->registerAlias(ILimiter::class, Limiter::class);
$this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class);
@@ -1438,6 +1437,8 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(IProfileManager::class, ProfileManager::class);
+ $this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class);
+
$this->connectDispatcher();
}
@@ -1478,6 +1479,8 @@ class Server extends ServerContainer implements IServerContainer {
$eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class);
$eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class);
$eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class);
+
+ FilesMetadataManager::loadListeners($eventDispatcher);
}
/**
diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php
index 76a214584a6..22d2aba0405 100644
--- a/lib/private/Session/CryptoSessionData.php
+++ b/lib/private/Session/CryptoSessionData.php
@@ -32,6 +32,7 @@ namespace OC\Session;
use OCP\ISession;
use OCP\Security\ICrypto;
use OCP\Session\Exceptions\SessionNotAvailableException;
+use function json_decode;
use function OCP\Log\logger;
/**
@@ -58,8 +59,8 @@ class CryptoSessionData implements \ArrayAccess, ISession {
* @param string $passphrase
*/
public function __construct(ISession $session,
- ICrypto $crypto,
- string $passphrase) {
+ ICrypto $crypto,
+ string $passphrase) {
$this->crypto = $crypto;
$this->session = $session;
$this->passphrase = $passphrase;
@@ -80,19 +81,24 @@ class CryptoSessionData implements \ArrayAccess, ISession {
protected function initializeSession() {
$encryptedSessionData = $this->session->get(self::encryptedSessionName) ?: '';
- try {
- $this->sessionValues = json_decode(
- $this->crypto->decrypt($encryptedSessionData, $this->passphrase),
- true,
- 512,
- JSON_THROW_ON_ERROR,
- );
- } catch (\Exception $e) {
- logger('core')->critical('Could not decrypt or decode encrypted session data', [
- 'exception' => $e,
- ]);
+ if ($encryptedSessionData === '') {
+ // Nothing to decrypt
$this->sessionValues = [];
- $this->regenerateId(true, false);
+ } else {
+ try {
+ $this->sessionValues = json_decode(
+ $this->crypto->decrypt($encryptedSessionData, $this->passphrase),
+ true,
+ 512,
+ JSON_THROW_ON_ERROR,
+ );
+ } catch (\Exception $e) {
+ logger('core')->critical('Could not decrypt or decode encrypted session data', [
+ 'exception' => $e,
+ ]);
+ $this->sessionValues = [];
+ $this->regenerateId(true, false);
+ }
}
}
diff --git a/lib/private/Session/CryptoWrapper.php b/lib/private/Session/CryptoWrapper.php
index e98aac3b8bf..5004ebf82cf 100644
--- a/lib/private/Session/CryptoWrapper.php
+++ b/lib/private/Session/CryptoWrapper.php
@@ -68,9 +68,9 @@ class CryptoWrapper {
* @param IRequest $request
*/
public function __construct(IConfig $config,
- ICrypto $crypto,
- ISecureRandom $random,
- IRequest $request) {
+ ICrypto $crypto,
+ ISecureRandom $random,
+ IRequest $request) {
$this->crypto = $crypto;
$this->config = $config;
$this->random = $random;
diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php
index 2d44ac7d3df..839d3e5ce38 100644
--- a/lib/private/Settings/Manager.php
+++ b/lib/private/Settings/Manager.php
@@ -12,6 +12,7 @@
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author sualko <klaus@jsxc.org>
* @author Carl Schwan <carl@carlschwan.eu>
+ * @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
@@ -90,17 +91,14 @@ class Manager implements IManager {
$this->subAdmin = $subAdmin;
}
- /** @var array */
+ /** @var array<self::SETTINGS_*, list<class-string<IIconSection>>> */
protected $sectionClasses = [];
- /** @var array */
+ /** @var array<self::SETTINGS_*, array<string, IIconSection>> */
protected $sections = [];
/**
- * @param string $type 'admin' or 'personal'
- * @param string $section Class must implement OCP\Settings\IIconSection
- *
- * @return void
+ * @inheritdoc
*/
public function registerSection(string $type, string $section) {
if (!isset($this->sectionClasses[$type])) {
@@ -111,7 +109,7 @@ class Manager implements IManager {
}
/**
- * @param string $type 'admin' or 'personal'
+ * @psalm-param self::SETTINGS_* $type
*
* @return IIconSection[]
*/
@@ -149,6 +147,9 @@ class Manager implements IManager {
return $this->sections[$type];
}
+ /**
+ * @inheritdoc
+ */
public function getSection(string $type, string $sectionId): ?IIconSection {
if (isset($this->sections[$type]) && isset($this->sections[$type][$sectionId])) {
return $this->sections[$type][$sectionId];
@@ -163,27 +164,23 @@ class Manager implements IManager {
], true);
}
- /** @var array */
+ /** @var array<class-string<ISettings>, self::SETTINGS_*> */
protected $settingClasses = [];
- /** @var array */
+ /** @var array<self::SETTINGS_*, array<string, list<ISettings>>> */
protected $settings = [];
/**
- * @psam-param 'admin'|'personal' $type The type of the setting.
- * @param string $setting Class must implement OCP\Settings\ISettings
- * @param bool $allowedDelegation
- *
- * @return void
+ * @inheritdoc
*/
public function registerSetting(string $type, string $setting) {
$this->settingClasses[$setting] = $type;
}
/**
- * @param string $type 'admin' or 'personal'
+ * @psalm-param self::SETTINGS_* $type The type of the setting.
* @param string $section
- * @param Closure $filter optional filter to apply on all loaded ISettings
+ * @param ?Closure $filter optional filter to apply on all loaded ISettings
*
* @return ISettings[]
*/
@@ -258,7 +255,7 @@ class Manager implements IManager {
/**
* @inheritdoc
*/
- public function getAdminSettings($section, bool $subAdminOnly = false): array {
+ public function getAdminSettings(string $section, bool $subAdminOnly = false): array {
if ($subAdminOnly) {
$subAdminSettingsFilter = function (ISettings $settings) {
return $settings instanceof ISubAdminSettings;
@@ -329,7 +326,7 @@ class Manager implements IManager {
/**
* @inheritdoc
*/
- public function getPersonalSettings($section): array {
+ public function getPersonalSettings(string $section): array {
$settings = [];
$appSettings = $this->getSettings('personal', $section);
@@ -344,6 +341,9 @@ class Manager implements IManager {
return $settings;
}
+ /**
+ * @inheritdoc
+ */
public function getAllowedAdminSettings(string $section, IUser $user): array {
$isAdmin = $this->groupManager->isAdmin($user->getUID());
if ($isAdmin) {
@@ -375,6 +375,9 @@ class Manager implements IManager {
return $settings;
}
+ /**
+ * @inheritdoc
+ */
public function getAllAllowedAdminSettings(IUser $user): array {
$this->getSettings('admin', ''); // Make sure all the settings are loaded
$settings = [];
diff --git a/lib/private/Setup.php b/lib/private/Setup.php
index f167d19adeb..ec86a844334 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -53,13 +53,14 @@ use Exception;
use InvalidArgumentException;
use OC\Authentication\Token\PublicKeyTokenProvider;
use OC\Authentication\Token\TokenCleanupJob;
-use OC\TextProcessing\RemoveOldTasksBackgroundJob;
use OC\Log\Rotate;
use OC\Preview\BackgroundCleanupJob;
+use OC\TextProcessing\RemoveOldTasksBackgroundJob;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Defaults;
use OCP\IGroup;
use OCP\IL10N;
+use OCP\Migration\IOutput;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
@@ -275,7 +276,7 @@ class Setup {
* @param $options
* @return array
*/
- public function install($options) {
+ public function install($options, ?IOutput $output = null) {
$l = $this->l10n;
$error = [];
@@ -349,6 +350,7 @@ class Setup {
$this->config->setValues($newConfigValues);
+ $this->outputDebug($output, 'Configuring database');
$dbSetup->initialize($options);
try {
$dbSetup->setupDatabase($username);
@@ -367,9 +369,11 @@ class Setup {
];
return $error;
}
+
+ $this->outputDebug($output, 'Run server migrations');
try {
// apply necessary migrations
- $dbSetup->runMigrations();
+ $dbSetup->runMigrations($output);
} catch (Exception $e) {
$error[] = [
'error' => 'Error while trying to initialise the database: ' . $e->getMessage(),
@@ -379,6 +383,7 @@ class Setup {
return $error;
}
+ $this->outputDebug($output, 'Create admin user');
//create the user and group
$user = null;
try {
@@ -407,16 +412,19 @@ class Setup {
}
// Install shipped apps and specified app bundles
- Installer::installShippedApps();
+ $this->outputDebug($output, 'Install default apps');
+ Installer::installShippedApps(false, $output);
// create empty file in data dir, so we can later find
// out that this is indeed an ownCloud data directory
+ $this->outputDebug($output, 'Setup data directory');
file_put_contents($config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
// Update .htaccess files
self::updateHtaccess();
self::protectDataDirectory();
+ $this->outputDebug($output, 'Install background jobs');
self::installBackgroundJobs();
//and we are done
@@ -531,7 +539,7 @@ class Setup {
$content .= "\n Options -MultiViews";
$content .= "\n RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]";
$content .= "\n RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]";
- $content .= "\n RewriteCond %{REQUEST_FILENAME} !\\.(css|js|mjs|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav|wasm|tflite)$";
+ $content .= "\n RewriteCond %{REQUEST_FILENAME} !\\.(css|js|mjs|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav|flac|wasm|tflite)$";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/core/ajax/update\\.php";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/core/img/(favicon\\.ico|manifest\\.json)$";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/(cron|public|remote|status)\\.php";
@@ -552,6 +560,14 @@ class Setup {
}
if ($content !== '') {
+ // Never write file back if disk space should be too low
+ if (function_exists('disk_free_space')) {
+ $df = disk_free_space(\OC::$SERVERROOT);
+ $size = strlen($content) + 10240;
+ if ($df !== false && $df < (float)$size) {
+ throw new \Exception(\OC::$SERVERROOT . " does not have enough space for writing the htaccess file! Not writing it back!");
+ }
+ }
//suppress errors in case we don't have permissions for it
return (bool)@file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent . $content . "\n");
}
@@ -616,4 +632,10 @@ class Setup {
public function canInstallFileExists() {
return is_file(\OC::$configDir.'/CAN_INSTALL');
}
+
+ protected function outputDebug(?IOutput $output, string $message): void {
+ if ($output instanceof IOutput) {
+ $output->debug($message);
+ }
+ }
}
diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php
index 79f23de8ef8..6bef40338c9 100644
--- a/lib/private/Setup/AbstractDatabase.php
+++ b/lib/private/Setup/AbstractDatabase.php
@@ -33,6 +33,7 @@ use OC\DB\ConnectionFactory;
use OC\DB\MigrationService;
use OC\SystemConfig;
use OCP\IL10N;
+use OCP\Migration\IOutput;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
@@ -150,11 +151,11 @@ abstract class AbstractDatabase {
*/
abstract public function setupDatabase($username);
- public function runMigrations() {
+ public function runMigrations(?IOutput $output = null) {
if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) {
return;
}
- $ms = new MigrationService('core', \OC::$server->get(Connection::class));
+ $ms = new MigrationService('core', \OC::$server->get(Connection::class), $output);
$ms->migrate('latest', true);
}
}
diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php
index 50f566728a9..0d672f324eb 100644
--- a/lib/private/Setup/MySQL.php
+++ b/lib/private/Setup/MySQL.php
@@ -29,10 +29,10 @@
*/
namespace OC\Setup;
+use Doctrine\DBAL\Platforms\MySQL80Platform;
use OC\DB\ConnectionAdapter;
use OC\DB\MySqlTools;
use OCP\IDBConnection;
-use Doctrine\DBAL\Platforms\MySQL80Platform;
use OCP\Security\ISecureRandom;
class MySQL extends AbstractDatabase {
diff --git a/lib/private/SetupCheck/SetupCheckManager.php b/lib/private/SetupCheck/SetupCheckManager.php
index f9e67772019..b8b6cfa11e7 100644
--- a/lib/private/SetupCheck/SetupCheckManager.php
+++ b/lib/private/SetupCheck/SetupCheckManager.php
@@ -47,9 +47,10 @@ class SetupCheckManager implements ISetupCheckManager {
$setupCheckObject = Server::get($setupCheck->getService());
$this->logger->debug('Running check '.get_class($setupCheckObject));
$setupResult = $setupCheckObject->run();
+ $setupResult->setName($setupCheckObject->getName());
$category = $setupCheckObject->getCategory();
$results[$category] ??= [];
- $results[$category][$setupCheckObject->getName()] = $setupResult;
+ $results[$category][$setupCheckObject::class] = $setupResult;
}
return $results;
}
diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php
index 8d14f293e5a..3aa01b3cf29 100644
--- a/lib/private/Share/Share.php
+++ b/lib/private/Share/Share.php
@@ -243,7 +243,7 @@ class Share extends Constants {
* * defacto $parameters and $format is always the default and therefore is removed in the subsequent call
*/
public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
- $parameters = null, $includeCollections = false) {
+ $parameters = null, $includeCollections = false) {
return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE,
null, -1, $includeCollections);
}
@@ -349,8 +349,8 @@ class Share extends Constants {
* * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call
*/
public static function getItems($itemType, ?string $item = null, ?int $shareType = null, $shareWith = null,
- $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
- $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
+ $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
+ $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
return [];
}
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 4606101b7e6..31f3924f053 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -880,12 +880,12 @@ class Manager implements IManager {
* @param \DateTime|null $expiration
*/
protected function sendMailNotification(IL10N $l,
- $filename,
- $link,
- $initiator,
- $shareWith,
- \DateTime $expiration = null,
- $note = '') {
+ $filename,
+ $link,
+ $initiator,
+ $shareWith,
+ \DateTime $expiration = null,
+ $note = '') {
$initiatorUser = $this->userManager->get($initiator);
$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
@@ -1573,14 +1573,8 @@ class Manager implements IManager {
* @return bool
*/
public function checkPassword(IShare $share, $password) {
- $passwordProtected = $share->getShareType() !== IShare::TYPE_LINK
- || $share->getShareType() !== IShare::TYPE_EMAIL
- || $share->getShareType() !== IShare::TYPE_CIRCLE;
- if (!$passwordProtected) {
- //TODO maybe exception?
- return false;
- }
+ // if there is no password on the share object / passsword is null, there is nothing to check
if ($password === null || $share->getPassword() === null) {
return false;
}
diff --git a/lib/private/Share20/PublicShareTemplateFactory.php b/lib/private/Share20/PublicShareTemplateFactory.php
index 222f327496a..0e9642c306e 100644
--- a/lib/private/Share20/PublicShareTemplateFactory.php
+++ b/lib/private/Share20/PublicShareTemplateFactory.php
@@ -27,9 +27,9 @@ use Exception;
use OC\AppFramework\Bootstrap\Coordinator;
use OCA\Files_Sharing\DefaultPublicShareTemplateProvider;
use OCP\Server;
-use OCP\Share\IShare;
use OCP\Share\IPublicShareTemplateFactory;
use OCP\Share\IPublicShareTemplateProvider;
+use OCP\Share\IShare;
class PublicShareTemplateFactory implements IPublicShareTemplateFactory {
public function __construct(
diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php
index 0a50fa0ccfb..c80d332e9db 100644
--- a/lib/private/Share20/Share.php
+++ b/lib/private/Share20/Share.php
@@ -29,8 +29,8 @@
*/
namespace OC\Share20;
-use OCP\Files\File;
use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
diff --git a/lib/private/StreamImage.php b/lib/private/StreamImage.php
index 33078310d27..e2f854bc233 100644
--- a/lib/private/StreamImage.php
+++ b/lib/private/StreamImage.php
@@ -23,8 +23,8 @@
namespace OC;
-use OCP\IStreamImage;
use OCP\IImage;
+use OCP\IStreamImage;
/**
* Only useful when dealing with transferring streamed previews from an external
diff --git a/lib/private/SubAdmin.php b/lib/private/SubAdmin.php
index 54f14b8ab88..9f079d30e04 100644
--- a/lib/private/SubAdmin.php
+++ b/lib/private/SubAdmin.php
@@ -60,9 +60,9 @@ class SubAdmin extends PublicEmitter implements ISubAdmin {
* @param IDBConnection $dbConn
*/
public function __construct(IUserManager $userManager,
- IGroupManager $groupManager,
- IDBConnection $dbConn,
- IEventDispatcher $eventDispatcher) {
+ IGroupManager $groupManager,
+ IDBConnection $dbConn,
+ IEventDispatcher $eventDispatcher) {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->dbConn = $dbConn;
diff --git a/lib/private/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php
index eba76ca103e..008cded5dd3 100644
--- a/lib/private/Support/Subscription/Registry.php
+++ b/lib/private/Support/Subscription/Registry.php
@@ -62,10 +62,10 @@ class Registry implements IRegistry {
private $logger;
public function __construct(IConfig $config,
- IServerContainer $container,
- IUserManager $userManager,
- IGroupManager $groupManager,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ IUserManager $userManager,
+ IGroupManager $groupManager,
+ LoggerInterface $logger) {
$this->config = $config;
$this->container = $container;
$this->userManager = $userManager;
diff --git a/lib/private/SystemTag/ManagerFactory.php b/lib/private/SystemTag/ManagerFactory.php
index 6670922407e..d8f7d4d772b 100644
--- a/lib/private/SystemTag/ManagerFactory.php
+++ b/lib/private/SystemTag/ManagerFactory.php
@@ -40,25 +40,16 @@ use OCP\SystemTag\ISystemTagObjectMapper;
*/
class ManagerFactory implements ISystemTagManagerFactory {
/**
- * Server container
- *
- * @var IServerContainer
- */
- private $serverContainer;
-
- /**
* Constructor for the system tag manager factory
- *
- * @param IServerContainer $serverContainer server container
*/
- public function __construct(IServerContainer $serverContainer) {
- $this->serverContainer = $serverContainer;
+ public function __construct(
+ private IServerContainer $serverContainer,
+ ) {
}
/**
* Creates and returns an instance of the system tag manager
*
- * @return ISystemTagManager
* @since 9.0.0
*/
public function getManager(): ISystemTagManager {
@@ -73,7 +64,6 @@ class ManagerFactory implements ISystemTagManagerFactory {
* Creates and returns an instance of the system tag object
* mapper
*
- * @return ISystemTagObjectMapper
* @since 9.0.0
*/
public function getObjectMapper(): ISystemTagObjectMapper {
diff --git a/lib/private/SystemTag/SystemTag.php b/lib/private/SystemTag/SystemTag.php
index da6d4bd4b11..cd8010201d3 100644
--- a/lib/private/SystemTag/SystemTag.php
+++ b/lib/private/SystemTag/SystemTag.php
@@ -30,39 +30,12 @@ namespace OC\SystemTag;
use OCP\SystemTag\ISystemTag;
class SystemTag implements ISystemTag {
- /**
- * @var string
- */
- private $id;
-
- /**
- * @var string
- */
- private $name;
-
- /**
- * @var bool
- */
- private $userVisible;
-
- /**
- * @var bool
- */
- private $userAssignable;
-
- /**
- * Constructor.
- *
- * @param string $id tag id
- * @param string $name tag name
- * @param bool $userVisible whether the tag is user visible
- * @param bool $userAssignable whether the tag is user assignable
- */
- public function __construct(string $id, string $name, bool $userVisible, bool $userAssignable) {
- $this->id = $id;
- $this->name = $name;
- $this->userVisible = $userVisible;
- $this->userAssignable = $userAssignable;
+ public function __construct(
+ private string $id,
+ private string $name,
+ private bool $userVisible,
+ private bool $userAssignable,
+ ) {
}
/**
@@ -97,14 +70,14 @@ class SystemTag implements ISystemTag {
* {@inheritdoc}
*/
public function getAccessLevel(): int {
- if ($this->userVisible) {
- if ($this->userAssignable) {
- return self::ACCESS_LEVEL_PUBLIC;
- } else {
- return self::ACCESS_LEVEL_RESTRICTED;
- }
- } else {
+ if (!$this->userVisible) {
return self::ACCESS_LEVEL_INVISIBLE;
}
+
+ if (!$this->userAssignable) {
+ return self::ACCESS_LEVEL_RESTRICTED;
+ }
+
+ return self::ACCESS_LEVEL_PUBLIC;
}
}
diff --git a/lib/private/SystemTag/SystemTagManager.php b/lib/private/SystemTag/SystemTagManager.php
index c52c350b6f8..67e1a7d921f 100644
--- a/lib/private/SystemTag/SystemTagManager.php
+++ b/lib/private/SystemTag/SystemTagManager.php
@@ -50,10 +50,8 @@ class SystemTagManager implements ISystemTagManager {
/**
* Prepared query for selecting tags directly
- *
- * @var \OCP\DB\QueryBuilder\IQueryBuilder
*/
- private $selectTagQuery;
+ private IQueryBuilder $selectTagQuery;
public function __construct(
protected IDBConnection $connection,
@@ -219,7 +217,12 @@ class SystemTagManager implements ISystemTagManager {
/**
* {@inheritdoc}
*/
- public function updateTag(string $tagId, string $newName, bool $userVisible, bool $userAssignable) {
+ public function updateTag(
+ string $tagId,
+ string $newName,
+ bool $userVisible,
+ bool $userAssignable,
+ ): void {
try {
$tags = $this->getTagsByIds($tagId);
} catch (TagNotFoundException $e) {
@@ -271,7 +274,7 @@ class SystemTagManager implements ISystemTagManager {
/**
* {@inheritdoc}
*/
- public function deleteTags($tagIds) {
+ public function deleteTags($tagIds): void {
if (!\is_array($tagIds)) {
$tagIds = [$tagIds];
}
@@ -363,14 +366,14 @@ class SystemTagManager implements ISystemTagManager {
return false;
}
- private function createSystemTagFromRow($row) {
+ private function createSystemTagFromRow($row): SystemTag {
return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable']);
}
/**
* {@inheritdoc}
*/
- public function setTagGroups(ISystemTag $tag, array $groupIds) {
+ public function setTagGroups(ISystemTag $tag, array $groupIds): void {
// delete relationships first
$this->connection->beginTransaction();
try {
diff --git a/lib/private/SystemTag/SystemTagObjectMapper.php b/lib/private/SystemTag/SystemTagObjectMapper.php
index 66a21e58609..614d0274add 100644
--- a/lib/private/SystemTag/SystemTagObjectMapper.php
+++ b/lib/private/SystemTag/SystemTagObjectMapper.php
@@ -81,7 +81,6 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
$result->closeCursor();
}
-
return $mapping;
}
@@ -128,7 +127,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
/**
* {@inheritdoc}
*/
- public function assignTags(string $objId, string $objectType, $tagIds) {
+ public function assignTags(string $objId, string $objectType, $tagIds): void {
if (!\is_array($tagIds)) {
$tagIds = [$tagIds];
}
@@ -169,7 +168,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
/**
* {@inheritdoc}
*/
- public function unassignTags(string $objId, string $objectType, $tagIds) {
+ public function unassignTags(string $objId, string $objectType, $tagIds): void {
if (!\is_array($tagIds)) {
$tagIds = [$tagIds];
}
@@ -241,7 +240,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper {
*
* @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist
*/
- private function assertTagsExist($tagIds) {
+ private function assertTagsExist(array $tagIds): void {
$tags = $this->tagManager->getTagsByIds($tagIds);
if (\count($tags) !== \count($tagIds)) {
// at least one tag missing, bail out
diff --git a/lib/private/SystemTag/SystemTagsInFilesDetector.php b/lib/private/SystemTag/SystemTagsInFilesDetector.php
index c9f26c58c02..044322733ea 100644
--- a/lib/private/SystemTag/SystemTagsInFilesDetector.php
+++ b/lib/private/SystemTag/SystemTagsInFilesDetector.php
@@ -36,7 +36,9 @@ use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
class SystemTagsInFilesDetector {
- public function __construct(protected QuerySearchHelper $searchHelper) {
+ public function __construct(
+ protected QuerySearchHelper $searchHelper,
+ ) {
}
public function detectAssignedSystemTagsIn(
diff --git a/lib/private/TagManager.php b/lib/private/TagManager.php
index 82c4dd2188d..552113f89dc 100644
--- a/lib/private/TagManager.php
+++ b/lib/private/TagManager.php
@@ -27,6 +27,7 @@
namespace OC;
use OC\Tagging\TagMapper;
+use OCP\Db\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
@@ -35,7 +36,6 @@ use OCP\ITagManager;
use OCP\ITags;
use OCP\IUserSession;
use OCP\User\Events\UserDeletedEvent;
-use OCP\Db\Exception as DBException;
use Psr\Log\LoggerInterface;
/**
diff --git a/lib/private/Tagging/TagMapper.php b/lib/private/Tagging/TagMapper.php
index 1ee9c33acf7..ab227de5f7f 100644
--- a/lib/private/Tagging/TagMapper.php
+++ b/lib/private/Tagging/TagMapper.php
@@ -27,8 +27,8 @@ namespace OC\Tagging;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
-use OCP\IDBConnection;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
/**
* Mapper for Tag entity
diff --git a/lib/private/Talk/Broker.php b/lib/private/Talk/Broker.php
index 12e6c5fb34b..451e7822790 100644
--- a/lib/private/Talk/Broker.php
+++ b/lib/private/Talk/Broker.php
@@ -48,8 +48,8 @@ class Broker implements IBroker {
private ?ITalkBackend $backend = null;
public function __construct(Coordinator $coordinator,
- IServerContainer $container,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ LoggerInterface $logger) {
$this->coordinator = $coordinator;
$this->container = $container;
$this->logger = $logger;
@@ -94,8 +94,8 @@ class Broker implements IBroker {
}
public function createConversation(string $name,
- array $moderators,
- IConversationOptions $options = null): IConversation {
+ array $moderators,
+ IConversationOptions $options = null): IConversation {
if (!$this->hasBackend()) {
throw new NoBackendException("The Talk broker has no registered backend");
}
diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php
index b87829360d5..fad69a76ae0 100644
--- a/lib/private/Template/JSCombiner.php
+++ b/lib/private/Template/JSCombiner.php
@@ -55,10 +55,10 @@ class JSCombiner {
private $cacheFactory;
public function __construct(IAppData $appData,
- IURLGenerator $urlGenerator,
- ICacheFactory $cacheFactory,
- SystemConfig $config,
- LoggerInterface $logger) {
+ IURLGenerator $urlGenerator,
+ ICacheFactory $cacheFactory,
+ SystemConfig $config,
+ LoggerInterface $logger) {
$this->appData = $appData;
$this->urlGenerator = $urlGenerator;
$this->cacheFactory = $cacheFactory;
diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php
index 7b6d0a6a346..8cba93f1f4e 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -45,9 +45,9 @@ use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IInitialStateService;
use OCP\IL10N;
+use OCP\ILogger;
use OCP\ISession;
use OCP\IURLGenerator;
-use OCP\ILogger;
use OCP\IUser;
use OCP\User\Backend\IPasswordConfirmationBackend;
use OCP\Util;
@@ -69,16 +69,16 @@ class JSConfigHelper {
private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true];
public function __construct(IL10N $l,
- Defaults $defaults,
- IAppManager $appManager,
- ISession $session,
- ?IUser $currentUser,
- IConfig $config,
- IGroupManager $groupManager,
- IniGetWrapper $iniWrapper,
- IURLGenerator $urlGenerator,
- CapabilitiesManager $capabilitiesManager,
- IInitialStateService $initialStateService) {
+ Defaults $defaults,
+ IAppManager $appManager,
+ ISession $session,
+ ?IUser $currentUser,
+ IConfig $config,
+ IGroupManager $groupManager,
+ IniGetWrapper $iniWrapper,
+ IURLGenerator $urlGenerator,
+ CapabilitiesManager $capabilitiesManager,
+ IInitialStateService $initialStateService) {
$this->l = $l;
$this->defaults = $defaults;
$this->appManager = $appManager;
@@ -179,7 +179,8 @@ class JSConfigHelper {
'sharing.maxAutocompleteResults' => max(0, $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT)),
'sharing.minSearchStringLength' => $this->config->getSystemValueInt('sharing.minSearchStringLength', 0),
'version' => implode('.', Util::getVersion()),
- 'versionstring' => \OC_Util::getVersionString()
+ 'versionstring' => \OC_Util::getVersionString(),
+ 'enable_non-accessible_features' => $this->config->getSystemValueBool('enable_non-accessible_features', true),
];
$array = [
diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php
index b283f0b610f..68a83fa4b73 100644
--- a/lib/private/Template/JSResourceLocator.php
+++ b/lib/private/Template/JSResourceLocator.php
@@ -60,8 +60,13 @@ class JSResourceLocator extends ResourceLocator {
$found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'core/'.$script);
$found += $this->appendScriptIfExist($this->serverroot, $script);
$found += $this->appendScriptIfExist($this->serverroot, $theme_dir.$script);
- $found += $this->appendScriptIfExist($this->serverroot, 'apps/'.$script);
- $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'apps/'.$script);
+
+ foreach (\OC::$APPSROOTS as $appRoot) {
+ $dirName = basename($appRoot['path']);
+ $rootPath = dirname($appRoot['path']);
+ $found += $this->appendScriptIfExist($rootPath, $dirName.'/'.$script);
+ $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.$dirName.'/'.$script);
+ }
if ($found) {
return;
diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php
index e2504363257..96d0ae3e517 100644
--- a/lib/private/TemplateLayout.php
+++ b/lib/private/TemplateLayout.php
@@ -47,11 +47,13 @@ use OC\Search\SearchQuery;
use OC\Template\CSSResourceLocator;
use OC\Template\JSConfigHelper;
use OC\Template\JSResourceLocator;
+use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\INavigationManager;
+use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Support\Subscription\IRegistry;
use OCP\Util;
@@ -106,11 +108,15 @@ class TemplateLayout extends \OC_Template {
$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
$this->initialState->provideInitialState('core', 'apps', $this->navigationManager->getAll());
- $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
- $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
- $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
- Util::addScript('core', 'unified-search', 'core');
+ if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
+ $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
+ $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
+ $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
+ Util::addScript('core', 'legacy-unified-search', 'core');
+ } else {
+ Util::addScript('core', 'unified-search', 'core');
+ }
// Set body data-theme
$this->assign('enabledThemes', []);
if (\OC::$server->getAppManager()->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
@@ -199,7 +205,21 @@ class TemplateLayout extends \OC_Template {
if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) {
$showSimpleSignup = false;
}
+
+ $defaultSignUpLink = 'https://nextcloud.com/signup/';
+ $signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink);
+ if ($signUpLink !== $defaultSignUpLink) {
+ $showSimpleSignup = true;
+ }
+
+ $appManager = \OCP\Server::get(IAppManager::class);
+ if ($appManager->isEnabledForUser('registration')) {
+ $urlGenerator = \OCP\Server::get(IURLGenerator::class);
+ $signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/');
+ }
+
$this->assign('showSimpleSignUpLink', $showSimpleSignup);
+ $this->assign('signUpLink', $signUpLink);
} else {
parent::__construct('core', 'layout.base');
}
diff --git a/lib/private/TextProcessing/Db/Task.php b/lib/private/TextProcessing/Db/Task.php
index 9c6f16d11ae..5f362d429f3 100644
--- a/lib/private/TextProcessing/Db/Task.php
+++ b/lib/private/TextProcessing/Db/Task.php
@@ -45,6 +45,8 @@ use OCP\TextProcessing\Task as OCPTask;
* @method string getAppId()
* @method setIdentifier(string $identifier)
* @method string getIdentifier()
+ * @method setCompletionExpectedAt(null|\DateTime $completionExpectedAt)
+ * @method null|\DateTime getCompletionExpectedAt()
*/
class Task extends Entity {
protected $lastUpdated;
@@ -55,16 +57,17 @@ class Task extends Entity {
protected $userId;
protected $appId;
protected $identifier;
+ protected $completionExpectedAt;
/**
* @var string[]
*/
- public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier'];
+ public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier', 'completion_expected_at'];
/**
* @var string[]
*/
- public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier'];
+ public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier', 'completionExpectedAt'];
public function __construct() {
@@ -78,6 +81,7 @@ class Task extends Entity {
$this->addType('userId', 'string');
$this->addType('appId', 'string');
$this->addType('identifier', 'string');
+ $this->addType('completionExpectedAt', 'datetime');
}
public function toRow(): array {
@@ -98,6 +102,7 @@ class Task extends Entity {
'userId' => $task->getUserId(),
'appId' => $task->getAppId(),
'identifier' => $task->getIdentifier(),
+ 'completionExpectedAt' => $task->getCompletionExpectedAt(),
]);
return $task;
}
@@ -107,6 +112,7 @@ class Task extends Entity {
$task->setId($this->getId());
$task->setStatus($this->getStatus());
$task->setOutput($this->getOutput());
+ $task->setCompletionExpectedAt($this->getCompletionExpectedAt());
return $task;
}
}
diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php
index 47af57bf3ec..70f5f322aa5 100644
--- a/lib/private/TextProcessing/Manager.php
+++ b/lib/private/TextProcessing/Manager.php
@@ -27,20 +27,22 @@ namespace OC\TextProcessing;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\TextProcessing\Db\Task as DbTask;
-use OCP\IConfig;
-use OCP\TextProcessing\IProviderWithId;
-use OCP\TextProcessing\Task;
-use OCP\TextProcessing\Task as OCPTask;
use OC\TextProcessing\Db\TaskMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\BackgroundJob\IJobList;
use OCP\Common\Exception\NotFoundException;
use OCP\DB\Exception;
+use OCP\IConfig;
use OCP\IServerContainer;
+use OCP\PreConditionNotMetException;
+use OCP\TextProcessing\Exception\TaskFailureException;
use OCP\TextProcessing\IManager;
use OCP\TextProcessing\IProvider;
-use OCP\PreConditionNotMetException;
+use OCP\TextProcessing\IProviderWithExpectedRuntime;
+use OCP\TextProcessing\IProviderWithId;
+use OCP\TextProcessing\Task;
+use OCP\TextProcessing\Task as OCPTask;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Throwable;
@@ -115,31 +117,16 @@ class Manager implements IManager {
if (!$this->canHandleTask($task)) {
throw new PreConditionNotMetException('No text processing provider is installed that can handle this task');
}
- $providers = $this->getProviders();
- $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', '');
- if ($json !== '') {
- $preferences = json_decode($json, true);
- if (isset($preferences[$task->getType()])) {
- // If a preference for this task type is set, move the preferred provider to the start
- $provider = current(array_filter($providers, function ($provider) use ($preferences, $task) {
- if ($provider instanceof IProviderWithId) {
- return $provider->getId() === $preferences[$task->getType()];
- }
- return $provider::class === $preferences[$task->getType()];
- }));
- if ($provider !== false) {
- $providers = array_filter($providers, fn ($p) => $p !== $provider);
- array_unshift($providers, $provider);
- }
- }
- }
+ $providers = $this->getPreferredProviders($task);
foreach ($providers as $provider) {
- if (!$task->canUseProvider($provider)) {
- continue;
- }
try {
$task->setStatus(OCPTask::STATUS_RUNNING);
+ if ($provider instanceof IProviderWithExpectedRuntime) {
+ $completionExpectedAt = new \DateTime('now');
+ $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S'));
+ $task->setCompletionExpectedAt($completionExpectedAt);
+ }
if ($task->getId() === null) {
$taskEntity = $this->taskMapper->insert(DbTask::fromPublicTask($task));
$task->setId($taskEntity->getId());
@@ -151,31 +138,37 @@ class Manager implements IManager {
$task->setStatus(OCPTask::STATUS_SUCCESSFUL);
$this->taskMapper->update(DbTask::fromPublicTask($task));
return $output;
- } catch (\RuntimeException $e) {
- $this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]);
- $task->setStatus(OCPTask::STATUS_FAILED);
- $this->taskMapper->update(DbTask::fromPublicTask($task));
- throw $e;
} catch (\Throwable $e) {
$this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]);
$task->setStatus(OCPTask::STATUS_FAILED);
$this->taskMapper->update(DbTask::fromPublicTask($task));
- throw new RuntimeException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e);
+ throw new TaskFailureException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e);
}
}
- throw new RuntimeException('Could not run task');
+ $task->setStatus(OCPTask::STATUS_FAILED);
+ $this->taskMapper->update(DbTask::fromPublicTask($task));
+ throw new TaskFailureException('Could not run task');
}
/**
* @inheritDoc
- * @throws Exception
*/
public function scheduleTask(OCPTask $task): void {
if (!$this->canHandleTask($task)) {
throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task');
}
$task->setStatus(OCPTask::STATUS_SCHEDULED);
+ $providers = $this->getPreferredProviders($task);
+ if (count($providers) === 0) {
+ throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task');
+ }
+ [$provider,] = $providers;
+ if ($provider instanceof IProviderWithExpectedRuntime) {
+ $completionExpectedAt = new \DateTime('now');
+ $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S'));
+ $task->setCompletionExpectedAt($completionExpectedAt);
+ }
$taskEntity = DbTask::fromPublicTask($task);
$this->taskMapper->insert($taskEntity);
$task->setId($taskEntity->getId());
@@ -187,6 +180,25 @@ class Manager implements IManager {
/**
* @inheritDoc
*/
+ public function runOrScheduleTask(OCPTask $task): bool {
+ if (!$this->canHandleTask($task)) {
+ throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task');
+ }
+ [$provider,] = $this->getPreferredProviders($task);
+ $maxExecutionTime = (int) ini_get('max_execution_time');
+ // Offload the task to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time
+ // or if the provider doesn't provide a getExpectedRuntime() method
+ if (!$provider instanceof IProviderWithExpectedRuntime || $provider->getExpectedRuntime() > $maxExecutionTime * 0.8) {
+ $this->scheduleTask($task);
+ return false;
+ }
+ $this->runTask($task);
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ */
public function deleteTask(Task $task): void {
$taskEntity = DbTask::fromPublicTask($task);
$this->taskMapper->delete($taskEntity);
@@ -259,4 +271,31 @@ class Manager implements IManager {
throw new RuntimeException('Failure while trying to find tasks by appId and identifier: ' . $e->getMessage(), 0, $e);
}
}
+
+ /**
+ * @param OCPTask $task
+ * @return IProvider[]
+ */
+ public function getPreferredProviders(OCPTask $task): array {
+ $providers = $this->getProviders();
+ $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', '');
+ if ($json !== '') {
+ $preferences = json_decode($json, true);
+ if (isset($preferences[$task->getType()])) {
+ // If a preference for this task type is set, move the preferred provider to the start
+ $provider = current(array_values(array_filter($providers, function ($provider) use ($preferences, $task) {
+ if ($provider instanceof IProviderWithId) {
+ return $provider->getId() === $preferences[$task->getType()];
+ }
+ $provider::class === $preferences[$task->getType()];
+ })));
+ if ($provider !== false) {
+ $providers = array_filter($providers, fn ($p) => $p !== $provider);
+ array_unshift($providers, $provider);
+ }
+ }
+ }
+ $providers = array_values(array_filter($providers, fn (IProvider $provider) => $task->canUseProvider($provider)));
+ return $providers;
+ }
}
diff --git a/lib/private/TextToImage/Db/Task.php b/lib/private/TextToImage/Db/Task.php
new file mode 100644
index 00000000000..96dd6e4e165
--- /dev/null
+++ b/lib/private/TextToImage/Db/Task.php
@@ -0,0 +1,117 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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\TextToImage\Db;
+
+use DateTime;
+use OCP\AppFramework\Db\Entity;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\TextToImage\Task as OCPTask;
+
+/**
+ * @method setLastUpdated(DateTime $lastUpdated)
+ * @method DateTime getLastUpdated()
+ * @method setInput(string $type)
+ * @method string getInput()
+ * @method setResultPath(string $resultPath)
+ * @method string getResultPath()
+ * @method setStatus(int $type)
+ * @method int getStatus()
+ * @method setUserId(?string $userId)
+ * @method string|null getUserId()
+ * @method setAppId(string $type)
+ * @method string getAppId()
+ * @method setIdentifier(string $identifier)
+ * @method string|null getIdentifier()
+ * @method setNumberOfImages(int $numberOfImages)
+ * @method int getNumberOfImages()
+ * @method setCompletionExpectedAt(DateTime $at)
+ * @method DateTime getCompletionExpectedAt()
+ */
+class Task extends Entity {
+ protected $lastUpdated;
+ protected $type;
+ protected $input;
+ protected $status;
+ protected $userId;
+ protected $appId;
+ protected $identifier;
+ protected $numberOfImages;
+ protected $completionExpectedAt;
+
+ /**
+ * @var string[]
+ */
+ public static array $columns = ['id', 'last_updated', 'input', 'status', 'user_id', 'app_id', 'identifier', 'number_of_images', 'completion_expected_at'];
+
+ /**
+ * @var string[]
+ */
+ public static array $fields = ['id', 'lastUpdated', 'input', 'status', 'userId', 'appId', 'identifier', 'numberOfImages', 'completionExpectedAt'];
+
+
+ public function __construct() {
+ // add types in constructor
+ $this->addType('id', 'integer');
+ $this->addType('lastUpdated', 'datetime');
+ $this->addType('input', 'string');
+ $this->addType('status', 'integer');
+ $this->addType('userId', 'string');
+ $this->addType('appId', 'string');
+ $this->addType('identifier', 'string');
+ $this->addType('numberOfImages', 'integer');
+ $this->addType('completionExpectedAt', 'datetime');
+ }
+
+ public function toRow(): array {
+ return array_combine(self::$columns, array_map(function ($field) {
+ return $this->{'get'.ucfirst($field)}();
+ }, self::$fields));
+ }
+
+ public static function fromPublicTask(OCPTask $task): Task {
+ /** @var Task $dbTask */
+ $dbTask = Task::fromParams([
+ 'id' => $task->getId(),
+ 'lastUpdated' => \OCP\Server::get(ITimeFactory::class)->getDateTime(),
+ 'status' => $task->getStatus(),
+ 'numberOfImages' => $task->getNumberOfImages(),
+ 'input' => $task->getInput(),
+ 'userId' => $task->getUserId(),
+ 'appId' => $task->getAppId(),
+ 'identifier' => $task->getIdentifier(),
+ 'completionExpectedAt' => $task->getCompletionExpectedAt(),
+ ]);
+ return $dbTask;
+ }
+
+ public function toPublicTask(): OCPTask {
+ $task = new OCPTask($this->getInput(), $this->getAppId(), $this->getNumberOfImages(), $this->getuserId(), $this->getIdentifier());
+ $task->setId($this->getId());
+ $task->setStatus($this->getStatus());
+ $task->setCompletionExpectedAt($this->getCompletionExpectedAt());
+ return $task;
+ }
+}
diff --git a/lib/private/TextToImage/Db/TaskMapper.php b/lib/private/TextToImage/Db/TaskMapper.php
new file mode 100644
index 00000000000..68fdd8f40de
--- /dev/null
+++ b/lib/private/TextToImage/Db/TaskMapper.php
@@ -0,0 +1,127 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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\TextToImage\Db;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\Entity;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\DB\Exception;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * @extends QBMapper<Task>
+ */
+class TaskMapper extends QBMapper {
+ public function __construct(
+ IDBConnection $db,
+ private ITimeFactory $timeFactory,
+ ) {
+ parent::__construct($db, 'text2image_tasks', Task::class);
+ }
+
+ /**
+ * @param int $id
+ * @return Task
+ * @throws Exception
+ * @throws DoesNotExistException
+ * @throws MultipleObjectsReturnedException
+ */
+ public function find(int $id): Task {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @param int $id
+ * @param string|null $userId
+ * @return Task
+ * @throws DoesNotExistException
+ * @throws Exception
+ * @throws MultipleObjectsReturnedException
+ */
+ public function findByIdAndUser(int $id, ?string $userId): Task {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
+ if ($userId === null) {
+ $qb->andWhere($qb->expr()->isNull('user_id'));
+ } else {
+ $qb->andWhere($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
+ }
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @param string $userId
+ * @param string $appId
+ * @param string|null $identifier
+ * @return array
+ * @throws Exception
+ */
+ public function findUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
+ ->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId)));
+ if ($identifier !== null) {
+ $qb->andWhere($qb->expr()->eq('identifier', $qb->createPositionalParameter($identifier)));
+ }
+ return $this->findEntities($qb);
+ }
+
+ /**
+ * @param int $timeout
+ * @return Task[] the deleted tasks
+ * @throws Exception
+ */
+ public function deleteOlderThan(int $timeout): array {
+ $datetime = $this->timeFactory->getDateTime();
+ $datetime->sub(new \DateInterval('PT'.$timeout.'S'));
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from($this->tableName)
+ ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE)));
+ $deletedTasks = $this->findEntities($qb);
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete($this->tableName)
+ ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE)));
+ $qb->executeStatement();
+ return $deletedTasks;
+ }
+
+ public function update(Entity $entity): Entity {
+ $entity->setLastUpdated($this->timeFactory->getDateTime());
+ return parent::update($entity);
+ }
+}
diff --git a/lib/private/TextToImage/Manager.php b/lib/private/TextToImage/Manager.php
new file mode 100644
index 00000000000..86212709c42
--- /dev/null
+++ b/lib/private/TextToImage/Manager.php
@@ -0,0 +1,335 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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\TextToImage;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\TextToImage\Db\Task as DbTask;
+use OC\TextToImage\Db\TaskMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\BackgroundJob\IJobList;
+use OCP\DB\Exception;
+use OCP\Files\AppData\IAppDataFactory;
+use OCP\Files\IAppData;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IConfig;
+use OCP\IServerContainer;
+use OCP\PreConditionNotMetException;
+use OCP\TextToImage\Exception\TaskFailureException;
+use OCP\TextToImage\Exception\TaskNotFoundException;
+use OCP\TextToImage\IManager;
+use OCP\TextToImage\IProvider;
+use OCP\TextToImage\Task;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use Throwable;
+
+class Manager implements IManager {
+ /** @var ?IProvider[] */
+ private ?array $providers = null;
+ private IAppData $appData;
+
+ public function __construct(
+ private IServerContainer $serverContainer,
+ private Coordinator $coordinator,
+ private LoggerInterface $logger,
+ private IJobList $jobList,
+ private TaskMapper $taskMapper,
+ private IConfig $config,
+ IAppDataFactory $appDataFactory,
+ ) {
+ $this->appData = $appDataFactory->get('core');
+ }
+
+ /**
+ * @inerhitDocs
+ */
+ public function getProviders(): array {
+ $context = $this->coordinator->getRegistrationContext();
+ if ($context === null) {
+ return [];
+ }
+
+ if ($this->providers !== null) {
+ return $this->providers;
+ }
+
+ $this->providers = [];
+
+ foreach ($context->getTextToImageProviders() as $providerServiceRegistration) {
+ $class = $providerServiceRegistration->getService();
+ try {
+ $this->providers[$class] = $this->serverContainer->get($class);
+ } catch (Throwable $e) {
+ $this->logger->error('Failed to load Text to image provider ' . $class, [
+ 'exception' => $e,
+ ]);
+ }
+ }
+
+ return $this->providers;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function hasProviders(): bool {
+ $context = $this->coordinator->getRegistrationContext();
+ if ($context === null) {
+ return false;
+ }
+ return count($context->getTextToImageProviders()) > 0;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function runTask(Task $task): void {
+ $this->logger->debug('Running TextToImage Task');
+ if (!$this->hasProviders()) {
+ throw new PreConditionNotMetException('No text to image provider is installed that can handle this task');
+ }
+ $providers = $this->getPreferredProviders();
+
+ foreach ($providers as $provider) {
+ $this->logger->debug('Trying to run Text2Image provider '.$provider::class);
+ try {
+ $task->setStatus(Task::STATUS_RUNNING);
+ $completionExpectedAt = new \DateTime('now');
+ $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S'));
+ $task->setCompletionExpectedAt($completionExpectedAt);
+ if ($task->getId() === null) {
+ $this->logger->debug('Inserting Text2Image task into DB');
+ $taskEntity = $this->taskMapper->insert(DbTask::fromPublicTask($task));
+ $task->setId($taskEntity->getId());
+ } else {
+ $this->logger->debug('Updating Text2Image task in DB');
+ $this->taskMapper->update(DbTask::fromPublicTask($task));
+ }
+ try {
+ $folder = $this->appData->getFolder('text2image');
+ } catch(NotFoundException) {
+ $this->logger->debug('Creating folder in appdata for Text2Image results');
+ $folder = $this->appData->newFolder('text2image');
+ }
+ try {
+ $folder = $folder->getFolder((string) $task->getId());
+ } catch(NotFoundException) {
+ $this->logger->debug('Creating new folder in appdata Text2Image results folder');
+ $folder = $folder->newFolder((string) $task->getId());
+ }
+ $this->logger->debug('Creating result files for Text2Image task');
+ $resources = [];
+ $files = [];
+ for ($i = 0; $i < $task->getNumberOfImages(); $i++) {
+ $file = $folder->newFile((string) $i);
+ $files[] = $file;
+ $resource = $file->write();
+ if ($resource !== false && $resource !== true && is_resource($resource)) {
+ $resources[] = $resource;
+ } else {
+ throw new RuntimeException('Text2Image generation using provider "' . $provider->getName() . '" failed: Couldn\'t open file to write.');
+ }
+ }
+ $this->logger->debug('Calling Text2Image provider\'s generate method');
+ $provider->generate($task->getInput(), $resources);
+ for ($i = 0; $i < $task->getNumberOfImages(); $i++) {
+ if (is_resource($resources[$i])) {
+ // If $resource hasn't been closed yet, we'll do that here
+ fclose($resources[$i]);
+ }
+ }
+ $task->setStatus(Task::STATUS_SUCCESSFUL);
+ $this->logger->debug('Updating Text2Image task in DB');
+ $this->taskMapper->update(DbTask::fromPublicTask($task));
+ return;
+ } catch (\RuntimeException|\Throwable $e) {
+ for ($i = 0; $i < $task->getNumberOfImages(); $i++) {
+ if (isset($files, $files[$i])) {
+ try {
+ $files[$i]->delete();
+ } catch(NotPermittedException $e) {
+ $this->logger->warning('Failed to clean up Text2Image result file after error', ['exception' => $e]);
+ }
+ }
+ }
+
+ $this->logger->info('Text2Image generation using provider "' . $provider->getName() . '" failed', ['exception' => $e]);
+ $task->setStatus(Task::STATUS_FAILED);
+ try {
+ $this->taskMapper->update(DbTask::fromPublicTask($task));
+ } catch (Exception $e) {
+ $this->logger->warning('Failed to update database after Text2Image error', ['exception' => $e]);
+ }
+ throw new TaskFailureException('Text2Image generation using provider "' . $provider->getName() . '" failed: ' . $e->getMessage(), 0, $e);
+ }
+ }
+
+ $task->setStatus(Task::STATUS_FAILED);
+ try {
+ $this->taskMapper->update(DbTask::fromPublicTask($task));
+ } catch (Exception $e) {
+ $this->logger->warning('Failed to update database after Text2Image error', ['exception' => $e]);
+ }
+ throw new TaskFailureException('Could not run task');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function scheduleTask(Task $task): void {
+ if (!$this->hasProviders()) {
+ throw new PreConditionNotMetException('No text to image provider is installed that can handle this task');
+ }
+ $this->logger->debug('Scheduling Text2Image Task');
+ $task->setStatus(Task::STATUS_SCHEDULED);
+ $completionExpectedAt = new \DateTime('now');
+ $completionExpectedAt->add(new \DateInterval('PT'.$this->getPreferredProviders()[0]->getExpectedRuntime().'S'));
+ $task->setCompletionExpectedAt($completionExpectedAt);
+ $taskEntity = DbTask::fromPublicTask($task);
+ $this->taskMapper->insert($taskEntity);
+ $task->setId($taskEntity->getId());
+ $this->jobList->add(TaskBackgroundJob::class, [
+ 'taskId' => $task->getId()
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function runOrScheduleTask(Task $task) : void {
+ if (!$this->hasProviders()) {
+ throw new PreConditionNotMetException('No text to image provider is installed that can handle this task');
+ }
+ $providers = $this->getPreferredProviders();
+ $maxExecutionTime = (int) ini_get('max_execution_time');
+ // Offload the task to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time
+ if ($providers[0]->getExpectedRuntime() > $maxExecutionTime * 0.8) {
+ $this->scheduleTask($task);
+ return;
+ }
+ $this->runTask($task);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function deleteTask(Task $task): void {
+ $taskEntity = DbTask::fromPublicTask($task);
+ $this->taskMapper->delete($taskEntity);
+ $this->jobList->remove(TaskBackgroundJob::class, [
+ 'taskId' => $task->getId()
+ ]);
+ }
+
+ /**
+ * Get a task from its id
+ *
+ * @param int $id The id of the task
+ * @return Task
+ * @throws RuntimeException If the query failed
+ * @throws TaskNotFoundException If the task could not be found
+ */
+ public function getTask(int $id): Task {
+ try {
+ $taskEntity = $this->taskMapper->find($id);
+ return $taskEntity->toPublicTask();
+ } catch (DoesNotExistException $e) {
+ throw new TaskNotFoundException('Could not find task with the provided id');
+ } catch (MultipleObjectsReturnedException $e) {
+ throw new RuntimeException('Could not uniquely identify task with given id', 0, $e);
+ } catch (Exception $e) {
+ throw new RuntimeException('Failure while trying to find task by id: ' . $e->getMessage(), 0, $e);
+ }
+ }
+
+ /**
+ * Get a task from its user id and task id
+ * If userId is null, this can only get a task that was scheduled anonymously
+ *
+ * @param int $id The id of the task
+ * @param string|null $userId The user id that scheduled the task
+ * @return Task
+ * @throws RuntimeException If the query failed
+ * @throws TaskNotFoundException If the task could not be found
+ */
+ public function getUserTask(int $id, ?string $userId): Task {
+ try {
+ $taskEntity = $this->taskMapper->findByIdAndUser($id, $userId);
+ return $taskEntity->toPublicTask();
+ } catch (DoesNotExistException $e) {
+ throw new TaskNotFoundException('Could not find task with the provided id and user id');
+ } catch (MultipleObjectsReturnedException $e) {
+ throw new RuntimeException('Could not uniquely identify task with given id and user id', 0, $e);
+ } catch (Exception $e) {
+ throw new RuntimeException('Failure while trying to find task by id and user id: ' . $e->getMessage(), 0, $e);
+ }
+ }
+
+ /**
+ * Get a list of tasks scheduled by a specific user for a specific app
+ * and optionally with a specific identifier.
+ * This cannot be used to get anonymously scheduled tasks
+ *
+ * @param string $userId
+ * @param string $appId
+ * @param string|null $identifier
+ * @return Task[]
+ * @throws RuntimeException
+ */
+ public function getUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array {
+ try {
+ $taskEntities = $this->taskMapper->findUserTasksByApp($userId, $appId, $identifier);
+ return array_map(static function (DbTask $taskEntity) {
+ return $taskEntity->toPublicTask();
+ }, $taskEntities);
+ } catch (Exception $e) {
+ throw new RuntimeException('Failure while trying to find tasks by appId and identifier: ' . $e->getMessage(), 0, $e);
+ }
+ }
+
+ /**
+ * @return IProvider[]
+ */
+ private function getPreferredProviders() {
+ $providers = $this->getProviders();
+ $json = $this->config->getAppValue('core', 'ai.text2image_provider', '');
+ if ($json !== '') {
+ try {
+ $id = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
+ $provider = current(array_filter($providers, fn ($provider) => $provider->getId() === $id));
+ if ($provider !== false && $provider !== null) {
+ $providers = [$provider];
+ }
+ } catch (\JsonException $e) {
+ $this->logger->warning('Failed to decode Text2Image setting `ai.text2image_provider`', ['exception' => $e]);
+ }
+ }
+
+ return $providers;
+ }
+}
diff --git a/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php b/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php
new file mode 100644
index 00000000000..2ecebc241bf
--- /dev/null
+++ b/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php
@@ -0,0 +1,78 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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\TextToImage;
+
+use OC\TextToImage\Db\TaskMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+use OCP\DB\Exception;
+use OCP\Files\AppData\IAppDataFactory;
+use OCP\Files\IAppData;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use Psr\Log\LoggerInterface;
+
+class RemoveOldTasksBackgroundJob extends TimedJob {
+ public const MAX_TASK_AGE_SECONDS = 60 * 50 * 24 * 7; // 1 week
+
+ private IAppData $appData;
+
+ public function __construct(
+ ITimeFactory $timeFactory,
+ private TaskMapper $taskMapper,
+ private LoggerInterface $logger,
+ IAppDataFactory $appDataFactory,
+ ) {
+ parent::__construct($timeFactory);
+ $this->appData = $appDataFactory->get('core');
+ $this->setInterval(60 * 60 * 24);
+ }
+
+ /**
+ * @param mixed $argument
+ * @inheritDoc
+ */
+ protected function run($argument) {
+ try {
+ $deletedTasks = $this->taskMapper->deleteOlderThan(self::MAX_TASK_AGE_SECONDS);
+ $folder = $this->appData->getFolder('text2image');
+ foreach ($deletedTasks as $deletedTask) {
+ try {
+ $folder->getFolder((string)$deletedTask->getId())->delete();
+ } catch (NotFoundException) {
+ // noop
+ } catch (NotPermittedException $e) {
+ $this->logger->warning('Failed to delete stale text to image task files', ['exception' => $e]);
+ }
+ }
+ } catch (Exception $e) {
+ $this->logger->warning('Failed to delete stale text to image tasks', ['exception' => $e]);
+ } catch(NotFoundException) {
+ // noop
+ }
+ }
+}
diff --git a/lib/private/TextToImage/TaskBackgroundJob.php b/lib/private/TextToImage/TaskBackgroundJob.php
new file mode 100644
index 00000000000..ac5cd6b59b5
--- /dev/null
+++ b/lib/private/TextToImage/TaskBackgroundJob.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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\TextToImage;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\TextToImage\Events\TaskFailedEvent;
+use OCP\TextToImage\Events\TaskSuccessfulEvent;
+use OCP\TextToImage\IManager;
+
+class TaskBackgroundJob extends QueuedJob {
+ public function __construct(
+ ITimeFactory $timeFactory,
+ private IManager $text2imageManager,
+ private IEventDispatcher $eventDispatcher,
+ ) {
+ parent::__construct($timeFactory);
+ // We want to avoid overloading the machine with these jobs
+ // so we only allow running one job at a time
+ $this->setAllowParallelRuns(false);
+ }
+
+ /**
+ * @param array{taskId: int} $argument
+ * @inheritDoc
+ */
+ protected function run($argument) {
+ $taskId = $argument['taskId'];
+ $task = $this->text2imageManager->getTask($taskId);
+ try {
+ $this->text2imageManager->runTask($task);
+ $event = new TaskSuccessfulEvent($task);
+ } catch (\Throwable $e) {
+ $event = new TaskFailedEvent($task, $e->getMessage());
+ }
+ $this->eventDispatcher->dispatchTyped($event);
+ }
+}
diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php
index 57bafc3e18d..3d384de5842 100644
--- a/lib/private/URLGenerator.php
+++ b/lib/private/URLGenerator.php
@@ -70,10 +70,10 @@ class URLGenerator implements IURLGenerator {
private ?IAppManager $appManager = null;
public function __construct(IConfig $config,
- IUserSession $userSession,
- ICacheFactory $cacheFactory,
- IRequest $request,
- Router $router
+ IUserSession $userSession,
+ ICacheFactory $cacheFactory,
+ IRequest $request,
+ Router $router
) {
$this->config = $config;
$this->userSession = $userSession;
@@ -116,16 +116,25 @@ class URLGenerator implements IURLGenerator {
}
public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string {
+ // Returns `/subfolder/index.php/ocsapp/…` with `'htaccess.IgnoreFrontController' => false` in config.php
+ // And `/subfolder/ocsapp/…` with `'htaccess.IgnoreFrontController' => true` in config.php
$route = $this->router->generate('ocs.'.$routeName, $arguments, false);
- $indexPhpPos = strpos($route, '/index.php/');
- if ($indexPhpPos !== false) {
- $route = substr($route, $indexPhpPos + 10);
+ // Cut off `/subfolder`
+ if (\OC::$WEBROOT !== '' && str_starts_with($route, \OC::$WEBROOT)) {
+ $route = substr($route, \strlen(\OC::$WEBROOT));
}
+ if (str_starts_with($route, '/index.php/')) {
+ $route = substr($route, 10);
+ }
+
+ // Remove `ocsapp/` bit
$route = substr($route, 7);
+ // Prefix with ocs/v2.php endpoint
$route = '/ocs/v2.php' . $route;
+ // Turn into an absolute URL
return $this->getAbsoluteURL($route);
}
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 5a14bb17507..018e4797232 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -40,13 +40,6 @@ declare(strict_types=1);
*/
namespace OC;
-use OCP\App\IAppManager;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\HintException;
-use OCP\IConfig;
-use OCP\ILogger;
-use OCP\Util;
use OC\App\AppManager;
use OC\DB\Connection;
use OC\DB\MigrationService;
@@ -61,6 +54,13 @@ use OC\Repair\Events\RepairStartEvent;
use OC\Repair\Events\RepairStepEvent;
use OC\Repair\Events\RepairWarningEvent;
use OC_App;
+use OCP\App\IAppManager;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\HintException;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\Util;
use Psr\Log\LoggerInterface;
/**
@@ -94,9 +94,9 @@ class Updater extends BasicEmitter {
];
public function __construct(IConfig $config,
- Checker $checker,
- ?LoggerInterface $log,
- Installer $installer) {
+ Checker $checker,
+ ?LoggerInterface $log,
+ Installer $installer) {
$this->log = $log;
$this->config = $config;
$this->checker = $checker;
diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php
index 97f770b6998..e37024ec2c2 100644
--- a/lib/private/Updater/VersionCheck.php
+++ b/lib/private/Updater/VersionCheck.php
@@ -127,7 +127,9 @@ class VersionCheck {
*/
protected function getUrlContent($url) {
$client = $this->clientService->newClient();
- $response = $client->get($url);
+ $response = $client->get($url, [
+ 'timeout' => 5,
+ ]);
return $response->getBody();
}
diff --git a/lib/private/User/AvailabilityCoordinator.php b/lib/private/User/AvailabilityCoordinator.php
new file mode 100644
index 00000000000..c32c3005c32
--- /dev/null
+++ b/lib/private/User/AvailabilityCoordinator.php
@@ -0,0 +1,139 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Richard Steinmetz <richard@steinmetz.cloud>
+ *
+ * @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\User;
+
+use JsonException;
+use OCA\DAV\AppInfo\Application;
+use OCA\DAV\CalDAV\TimezoneService;
+use OCA\DAV\Service\AbsenceService;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\User\IAvailabilityCoordinator;
+use OCP\User\IOutOfOfficeData;
+use Psr\Log\LoggerInterface;
+
+class AvailabilityCoordinator implements IAvailabilityCoordinator {
+ private ICache $cache;
+
+ public function __construct(
+ ICacheFactory $cacheFactory,
+ private IConfig $config,
+ private AbsenceService $absenceService,
+ private LoggerInterface $logger,
+ private TimezoneService $timezoneService,
+ ) {
+ $this->cache = $cacheFactory->createLocal('OutOfOfficeData');
+ }
+
+ public function isEnabled(): bool {
+ return $this->config->getAppValue(Application::APP_ID, 'hide_absence_settings', 'no') === 'no';
+ }
+
+ private function getCachedOutOfOfficeData(IUser $user): ?OutOfOfficeData {
+ $cachedString = $this->cache->get($user->getUID());
+ if ($cachedString === null) {
+ return null;
+ }
+
+ try {
+ $cachedData = json_decode($cachedString, true, 10, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ $this->logger->error('Failed to deserialize cached out-of-office data: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'json' => $cachedString,
+ ]);
+ return null;
+ }
+
+ return new OutOfOfficeData(
+ $cachedData['id'],
+ $user,
+ $cachedData['startDate'],
+ $cachedData['endDate'],
+ $cachedData['shortMessage'],
+ $cachedData['message'],
+ );
+ }
+
+ private function setCachedOutOfOfficeData(IOutOfOfficeData $data): void {
+ try {
+ $cachedString = json_encode([
+ 'id' => $data->getId(),
+ 'startDate' => $data->getStartDate(),
+ 'endDate' => $data->getEndDate(),
+ 'shortMessage' => $data->getShortMessage(),
+ 'message' => $data->getMessage(),
+ ], JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ $this->logger->error('Failed to serialize out-of-office data: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ return;
+ }
+
+ $this->cache->set($data->getUser()->getUID(), $cachedString, 300);
+ }
+
+ public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData {
+ $timezone = $this->getCachedTimezone($user->getUID());
+ if ($timezone === null) {
+ $timezone = $this->timezoneService->getUserTimezone($user->getUID()) ?? $this->timezoneService->getDefaultTimezone();
+ $this->setCachedTimezone($user->getUID(), $timezone);
+ }
+
+ $data = $this->getCachedOutOfOfficeData($user);
+ if ($data === null) {
+ $absenceData = $this->absenceService->getAbsence($user->getUID());
+ if ($absenceData === null) {
+ return null;
+ }
+ $data = $absenceData->toOutOufOfficeData($user, $timezone);
+ }
+
+ $this->setCachedOutOfOfficeData($data);
+ return $data;
+ }
+
+ private function getCachedTimezone(string $userId): ?string {
+ return $this->cache->get($userId . '_timezone') ?? null;
+ }
+
+ private function setCachedTimezone(string $userId, string $timezone): void {
+ $this->cache->set($userId . '_timezone', $timezone, 3600);
+ }
+
+ public function clearCache(string $userId): void {
+ $this->cache->set($userId, null, 300);
+ $this->cache->set($userId . '_timezone', null, 3600);
+ }
+
+ public function isInEffect(IOutOfOfficeData $data): bool {
+ return $this->absenceService->isInEffect($data);
+ }
+}
diff --git a/lib/private/User/Listeners/BeforeUserDeletedListener.php b/lib/private/User/Listeners/BeforeUserDeletedListener.php
index ec1f80c5413..8978c341a13 100644
--- a/lib/private/User/Listeners/BeforeUserDeletedListener.php
+++ b/lib/private/User/Listeners/BeforeUserDeletedListener.php
@@ -25,9 +25,9 @@ namespace OC\User\Listeners;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
-use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\Files\NotFoundException;
use OCP\IAvatarManager;
+use OCP\User\Events\BeforeUserDeletedEvent;
use Psr\Log\LoggerInterface;
/**
diff --git a/lib/private/User/Listeners/UserChangedListener.php b/lib/private/User/Listeners/UserChangedListener.php
index a561db2423d..0fa5ceeb0ed 100644
--- a/lib/private/User/Listeners/UserChangedListener.php
+++ b/lib/private/User/Listeners/UserChangedListener.php
@@ -25,9 +25,9 @@ namespace OC\User\Listeners;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
-use OCP\User\Events\UserChangedEvent;
use OCP\Files\NotFoundException;
use OCP\IAvatarManager;
+use OCP\User\Events\UserChangedEvent;
/**
* @template-implements IEventListener<UserChangedEvent>
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php
index 8ec8ef0c4be..5013c9bed7a 100644
--- a/lib/private/User/Manager.php
+++ b/lib/private/User/Manager.php
@@ -48,11 +48,11 @@ use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Server;
use OCP\Support\Subscription\IAssertion;
-use OCP\User\Backend\IGetRealUIDBackend;
-use OCP\User\Backend\ISearchKnownUsersBackend;
use OCP\User\Backend\ICheckPasswordBackend;
use OCP\User\Backend\ICountUsersBackend;
+use OCP\User\Backend\IGetRealUIDBackend;
use OCP\User\Backend\IProvideEnabledStateBackend;
+use OCP\User\Backend\ISearchKnownUsersBackend;
use OCP\User\Events\BeforeUserCreatedEvent;
use OCP\User\Events\UserCreatedEvent;
use OCP\UserInterface;
@@ -97,8 +97,8 @@ class Manager extends PublicEmitter implements IUserManager {
private DisplayNameCache $displayNameCache;
public function __construct(IConfig $config,
- ICacheFactory $cacheFactory,
- IEventDispatcher $eventDispatcher) {
+ ICacheFactory $cacheFactory,
+ IEventDispatcher $eventDispatcher) {
$this->config = $config;
$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
$cachedUsers = &$this->cachedUsers;
diff --git a/lib/private/User/OutOfOfficeData.php b/lib/private/User/OutOfOfficeData.php
new file mode 100644
index 00000000000..72e42afab6a
--- /dev/null
+++ b/lib/private/User/OutOfOfficeData.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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\User;
+
+use OCP\IUser;
+use OCP\User\IOutOfOfficeData;
+
+class OutOfOfficeData implements IOutOfOfficeData {
+ public function __construct(private string $id,
+ private IUser $user,
+ private int $startDate,
+ private int $endDate,
+ private string $shortMessage,
+ private string $message) {
+ }
+
+ public function getId(): string {
+ return $this->id;
+ }
+
+ public function getUser(): IUser {
+ return $this->user;
+ }
+
+ public function getStartDate(): int {
+ return $this->startDate;
+ }
+
+ public function getEndDate(): int {
+ return $this->endDate;
+ }
+
+ public function getShortMessage(): string {
+ return $this->shortMessage;
+ }
+
+ public function getMessage(): string {
+ return $this->message;
+ }
+
+ public function jsonSerialize(): array {
+ return [
+ 'id' => $this->getId(),
+ 'userId' => $this->getUser()->getUID(),
+ 'startDate' => $this->getStartDate(),
+ 'endDate' => $this->getEndDate(),
+ 'shortMessage' => $this->getShortMessage(),
+ 'message' => $this->getMessage(),
+ ];
+ }
+}
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index d6971d1486b..5689de3995f 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -120,14 +120,14 @@ class Session implements IUserSession, Emitter {
private $dispatcher;
public function __construct(Manager $manager,
- ISession $session,
- ITimeFactory $timeFactory,
- ?IProvider $tokenProvider,
- IConfig $config,
- ISecureRandom $random,
- ILockdownManager $lockdownManager,
- LoggerInterface $logger,
- IEventDispatcher $dispatcher
+ ISession $session,
+ ITimeFactory $timeFactory,
+ ?IProvider $tokenProvider,
+ IConfig $config,
+ ISecureRandom $random,
+ ILockdownManager $lockdownManager,
+ LoggerInterface $logger,
+ IEventDispatcher $dispatcher
) {
$this->manager = $manager;
$this->session = $session;
@@ -425,9 +425,9 @@ class Session implements IUserSession, Emitter {
* @return boolean
*/
public function logClientIn($user,
- $password,
- IRequest $request,
- IThrottler $throttler) {
+ $password,
+ IRequest $request,
+ IThrottler $throttler) {
$remoteAddress = $request->getRemoteAddress();
$currentDelay = $throttler->sleepDelayOrThrowOnMax($remoteAddress, 'login');
@@ -456,8 +456,17 @@ class Session implements IUserSession, Emitter {
$this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password);
return false;
}
- $users = $this->manager->getByEmail($user);
- if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
+
+ if ($isTokenPassword) {
+ $dbToken = $this->tokenProvider->getToken($password);
+ $userFromToken = $this->manager->get($dbToken->getUID());
+ $isValidEmailLogin = $userFromToken->getEMailAddress() === $user;
+ } else {
+ $users = $this->manager->getByEmail($user);
+ $isValidEmailLogin = (\count($users) === 1 && $this->login($users[0]->getUID(), $password));
+ }
+
+ if (!$isValidEmailLogin) {
$this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password);
return false;
}
@@ -576,7 +585,7 @@ class Session implements IUserSession, Emitter {
* @return boolean if the login was successful
*/
public function tryBasicAuthLogin(IRequest $request,
- IThrottler $throttler) {
+ IThrottler $throttler) {
if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
try {
if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
@@ -783,7 +792,7 @@ class Session implements IUserSession, Emitter {
try {
$dbToken = $this->tokenProvider->getToken($token);
} catch (InvalidTokenException $ex) {
- $this->logger->warning('Session token is invalid because it does not exist', [
+ $this->logger->debug('Session token is invalid because it does not exist', [
'app' => 'core',
'user' => $user,
'exception' => $ex,
@@ -916,9 +925,10 @@ class Session implements IUserSession, Emitter {
]);
return false;
} catch (InvalidTokenException $ex) {
- $this->logger->error('Renewing session token failed', [
+ $this->logger->error('Renewing session token failed: ' . $ex->getMessage(), [
'app' => 'core',
'user' => $uid,
+ 'exception' => $ex,
]);
return false;
}
diff --git a/lib/private/User/User.php b/lib/private/User/User.php
index 69ef82f3e85..580c590e6eb 100644
--- a/lib/private/User/User.php
+++ b/lib/private/User/User.php
@@ -49,17 +49,17 @@ use OCP\IImage;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserBackend;
+use OCP\User\Backend\IGetHomeBackend;
+use OCP\User\Backend\IProvideAvatarBackend;
+use OCP\User\Backend\IProvideEnabledStateBackend;
+use OCP\User\Backend\ISetDisplayNameBackend;
+use OCP\User\Backend\ISetPasswordBackend;
use OCP\User\Events\BeforePasswordUpdatedEvent;
use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\User\Events\PasswordUpdatedEvent;
use OCP\User\Events\UserChangedEvent;
use OCP\User\Events\UserDeletedEvent;
use OCP\User\GetQuotaEvent;
-use OCP\User\Backend\ISetDisplayNameBackend;
-use OCP\User\Backend\ISetPasswordBackend;
-use OCP\User\Backend\IProvideAvatarBackend;
-use OCP\User\Backend\IProvideEnabledStateBackend;
-use OCP\User\Backend\IGetHomeBackend;
use OCP\UserInterface;
use function json_decode;
use function json_encode;
@@ -576,7 +576,7 @@ class User implements IUser {
public function getAvatarImage($size) {
// delay the initialization
if (is_null($this->avatarManager)) {
- $this->avatarManager = \OC::$server->getAvatarManager();
+ $this->avatarManager = \OC::$server->get(IAvatarManager::class);
}
$avatar = $this->avatarManager->getAvatar($this->uid);
diff --git a/lib/private/UserStatus/ISettableProvider.php b/lib/private/UserStatus/ISettableProvider.php
index 88a107d1f86..957d3274f1d 100644
--- a/lib/private/UserStatus/ISettableProvider.php
+++ b/lib/private/UserStatus/ISettableProvider.php
@@ -39,8 +39,9 @@ interface ISettableProvider extends IProvider {
* @param string $messageId The new message id.
* @param string $status The new status.
* @param bool $createBackup If true, this will store the old status so that it is possible to revert it later (e.g. after a call).
+ * @param string|null $customMessage
*/
- public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup): void;
+ public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup, ?string $customMessage = null): void;
/**
* Revert an automatically set user status. For example after leaving a call,
diff --git a/lib/private/UserStatus/Manager.php b/lib/private/UserStatus/Manager.php
index 89a1bb455c7..a5594158c1e 100644
--- a/lib/private/UserStatus/Manager.php
+++ b/lib/private/UserStatus/Manager.php
@@ -51,7 +51,7 @@ class Manager implements IManager {
* @param LoggerInterface $logger
*/
public function __construct(IServerContainer $container,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->container = $container;
$this->logger = $logger;
}
@@ -104,13 +104,13 @@ class Manager implements IManager {
$this->provider = $provider;
}
- public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false): void {
+ public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false, ?string $customMessage = null): void {
$this->setupProvider();
if (!$this->provider || !($this->provider instanceof ISettableProvider)) {
return;
}
- $this->provider->setUserStatus($userId, $messageId, $status, $createBackup);
+ $this->provider->setUserStatus($userId, $messageId, $status, $createBackup, $customMessage);
}
public function revertUserStatus(string $userId, string $messageId, string $status): void {
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index 23e0b099e91..395c1f44c03 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -51,18 +51,18 @@ declare(strict_types=1);
*
*/
-use OCP\App\Events\AppUpdateEvent;
-use OCP\App\IAppManager;
-use OCP\App\ManagerEvent;
-use OCP\Authentication\IAlternativeLogin;
-use OCP\EventDispatcher\IEventDispatcher;
-use OC\AppFramework\Bootstrap\Coordinator;
use OC\App\DependencyAnalyzer;
use OC\App\Platform;
+use OC\AppFramework\Bootstrap\Coordinator;
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 Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface;
@@ -251,7 +251,7 @@ class OC_App {
* This function set an app as enabled in appconfig.
*/
public function enable(string $appId,
- array $groups = []) {
+ array $groups = []) {
// Check if app is already downloaded
/** @var Installer $installer */
$installer = \OCP\Server::get(Installer::class);
@@ -564,7 +564,7 @@ class OC_App {
$supportedApps = $this->getSupportedApps();
foreach ($installedApps as $app) {
- if (array_search($app, $blacklist) === false) {
+ if (!in_array($app, $blacklist)) {
$info = $appManager->getAppInfo($app, false, $langCode);
if (!is_array($info)) {
\OCP\Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php
index ac0a2bbd0e9..a2f47639e65 100644
--- a/lib/private/legacy/OC_Files.php
+++ b/lib/private/legacy/OC_Files.php
@@ -43,10 +43,10 @@
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Files\View;
use OC\Streamer;
-use OCP\Lock\ILockingProvider;
-use OCP\Files\Events\BeforeZipCreatedEvent;
-use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Events\BeforeDirectFileDownloadEvent;
+use OCP\Files\Events\BeforeZipCreatedEvent;
+use OCP\Lock\ILockingProvider;
/**
* Class for file server access
diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php
index cf39d045ae9..37fbf7f5f8f 100644
--- a/lib/private/legacy/OC_Helper.php
+++ b/lib/private/legacy/OC_Helper.php
@@ -46,8 +46,8 @@
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Files\Filesystem;
use OCP\Files\Mount\IMountPoint;
-use OCP\ICacheFactory;
use OCP\IBinaryFinder;
+use OCP\ICacheFactory;
use OCP\IUser;
use OCP\Util;
use Psr\Log\LoggerInterface;
diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php
index 5751b813f2c..d2dc2a2389f 100644
--- a/lib/private/legacy/OC_User.php
+++ b/lib/private/legacy/OC_User.php
@@ -138,7 +138,7 @@ class OC_User {
$class = $config['class'];
$arguments = $config['arguments'];
if (class_exists($class)) {
- if (array_search($i, self::$_setupedBackends) === false) {
+ if (!in_array($i, self::$_setupedBackends)) {
// make a reflection object
$reflectionObj = new ReflectionClass($class);