aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/autoloader.php4
-rw-r--r--lib/base.php29
-rw-r--r--lib/composer/composer/InstalledVersions.php17
-rw-r--r--lib/composer/composer/autoload_classmap.php112
-rw-r--r--lib/composer/composer/autoload_static.php112
-rw-r--r--lib/composer/composer/installed.php14
-rw-r--r--lib/l10n/ar.js4
-rw-r--r--lib/l10n/ar.json4
-rw-r--r--lib/l10n/ast.js4
-rw-r--r--lib/l10n/ast.json4
-rw-r--r--lib/l10n/bg.js4
-rw-r--r--lib/l10n/bg.json4
-rw-r--r--lib/l10n/ca.js4
-rw-r--r--lib/l10n/ca.json4
-rw-r--r--lib/l10n/cs.js9
-rw-r--r--lib/l10n/cs.json9
-rw-r--r--lib/l10n/cy_GB.js5
-rw-r--r--lib/l10n/cy_GB.json5
-rw-r--r--lib/l10n/da.js6
-rw-r--r--lib/l10n/da.json6
-rw-r--r--lib/l10n/de.js8
-rw-r--r--lib/l10n/de.json8
-rw-r--r--lib/l10n/de_DE.js4
-rw-r--r--lib/l10n/de_DE.json4
-rw-r--r--lib/l10n/en_GB.js4
-rw-r--r--lib/l10n/en_GB.json4
-rw-r--r--lib/l10n/eo.js1
-rw-r--r--lib/l10n/eo.json1
-rw-r--r--lib/l10n/es.js4
-rw-r--r--lib/l10n/es.json4
-rw-r--r--lib/l10n/es_EC.js4
-rw-r--r--lib/l10n/es_EC.json4
-rw-r--r--lib/l10n/eu.js4
-rw-r--r--lib/l10n/eu.json4
-rw-r--r--lib/l10n/fa.js4
-rw-r--r--lib/l10n/fa.json4
-rw-r--r--lib/l10n/fi.js4
-rw-r--r--lib/l10n/fi.json4
-rw-r--r--lib/l10n/fr.js53
-rw-r--r--lib/l10n/fr.json53
-rw-r--r--lib/l10n/gl.js26
-rw-r--r--lib/l10n/gl.json26
-rw-r--r--lib/l10n/hu.js13
-rw-r--r--lib/l10n/hu.json13
-rw-r--r--lib/l10n/it.js4
-rw-r--r--lib/l10n/it.json4
-rw-r--r--lib/l10n/ja.js5
-rw-r--r--lib/l10n/ja.json5
-rw-r--r--lib/l10n/lt_LT.js1
-rw-r--r--lib/l10n/lt_LT.json1
-rw-r--r--lib/l10n/lv.js4
-rw-r--r--lib/l10n/lv.json4
-rw-r--r--lib/l10n/mk.js5
-rw-r--r--lib/l10n/mk.json5
-rw-r--r--lib/l10n/nb.js4
-rw-r--r--lib/l10n/nb.json4
-rw-r--r--lib/l10n/nl.js1
-rw-r--r--lib/l10n/nl.json1
-rw-r--r--lib/l10n/pl.js15
-rw-r--r--lib/l10n/pl.json15
-rw-r--r--lib/l10n/pt_BR.js4
-rw-r--r--lib/l10n/pt_BR.json4
-rw-r--r--lib/l10n/ro.js2
-rw-r--r--lib/l10n/ro.json2
-rw-r--r--lib/l10n/ru.js13
-rw-r--r--lib/l10n/ru.json13
-rw-r--r--lib/l10n/sk.js8
-rw-r--r--lib/l10n/sk.json8
-rw-r--r--lib/l10n/sl.js4
-rw-r--r--lib/l10n/sl.json4
-rw-r--r--lib/l10n/sr.js4
-rw-r--r--lib/l10n/sr.json4
-rw-r--r--lib/l10n/sv.js5
-rw-r--r--lib/l10n/sv.json5
-rw-r--r--lib/l10n/th.js18
-rw-r--r--lib/l10n/th.json18
-rw-r--r--lib/l10n/tr.js12
-rw-r--r--lib/l10n/tr.json12
-rw-r--r--lib/l10n/uk.js5
-rw-r--r--lib/l10n/uk.json5
-rw-r--r--lib/l10n/vi.js5
-rw-r--r--lib/l10n/vi.json5
-rw-r--r--lib/l10n/zh_CN.js4
-rw-r--r--lib/l10n/zh_CN.json4
-rw-r--r--lib/l10n/zh_HK.js4
-rw-r--r--lib/l10n/zh_HK.json4
-rw-r--r--lib/l10n/zh_TW.js4
-rw-r--r--lib/l10n/zh_TW.json4
-rw-r--r--lib/private/Accounts/AccountManager.php15
-rw-r--r--lib/private/App/AppManager.php39
-rw-r--r--lib/private/App/AppStore/Version/VersionParser.php4
-rw-r--r--lib/private/AppConfig.php2
-rw-r--r--lib/private/AppFramework/Bootstrap/RegistrationContext.php58
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php27
-rw-r--r--lib/private/AppFramework/Http/Request.php18
-rw-r--r--lib/private/AppFramework/Middleware/Security/CORSMiddleware.php5
-rw-r--r--lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php3
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php5
-rw-r--r--lib/private/AppFramework/Utility/TimeFactory.php2
-rw-r--r--lib/private/Authentication/Exceptions/ExpiredTokenException.php20
-rw-r--r--lib/private/Authentication/Exceptions/InvalidTokenException.php7
-rw-r--r--lib/private/Authentication/Exceptions/WipeTokenException.php20
-rw-r--r--lib/private/Authentication/Login/LoginResult.php5
-rw-r--r--lib/private/Authentication/Token/IToken.php109
-rw-r--r--lib/private/Authentication/Token/PublicKeyToken.php16
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php37
-rw-r--r--lib/private/Avatar/AvatarManager.php59
-rw-r--r--lib/private/Avatar/GuestAvatar.php12
-rw-r--r--lib/private/Avatar/PlaceholderAvatar.php27
-rw-r--r--lib/private/Avatar/UserAvatar.php31
-rw-r--r--lib/private/BackgroundJob/JobList.php23
-rw-r--r--lib/private/Collaboration/AutoComplete/Manager.php25
-rw-r--r--lib/private/Collaboration/Collaborators/GroupPlugin.php34
-rw-r--r--lib/private/Collaboration/Collaborators/LookupPlugin.php30
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php65
-rw-r--r--lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php17
-rw-r--r--lib/private/Collaboration/Collaborators/RemotePlugin.php34
-rw-r--r--lib/private/Collaboration/Collaborators/Search.php22
-rw-r--r--lib/private/Collaboration/Collaborators/SearchResult.php16
-rw-r--r--lib/private/Collaboration/Collaborators/UserPlugin.php66
-rw-r--r--lib/private/Collaboration/Reference/File/FileReferenceEventListener.php7
-rw-r--r--lib/private/Collaboration/Reference/File/FileReferenceProvider.php20
-rw-r--r--lib/private/Collaboration/Reference/LinkReferenceProvider.php30
-rw-r--r--lib/private/Collaboration/Reference/ReferenceManager.php49
-rw-r--r--lib/private/Collaboration/Reference/RenderReferenceEventListener.php10
-rw-r--r--lib/private/Collaboration/Resources/Collection.php47
-rw-r--r--lib/private/Collaboration/Resources/Manager.php51
-rw-r--r--lib/private/Collaboration/Resources/ProviderManager.php17
-rw-r--r--lib/private/Collaboration/Resources/Resource.php44
-rw-r--r--lib/private/Command/ClosureJob.php5
-rw-r--r--lib/private/Command/CommandJob.php4
-rw-r--r--lib/private/Comments/Manager.php16
-rw-r--r--lib/private/Console/Application.php17
-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/ContactsManager.php25
-rw-r--r--lib/private/DB/Adapter.php10
-rw-r--r--lib/private/DB/Connection.php21
-rw-r--r--lib/private/DB/ConnectionAdapter.php21
-rw-r--r--lib/private/DB/ConnectionFactory.php2
-rw-r--r--lib/private/DB/MigrationService.php64
-rw-r--r--lib/private/DB/Migrator.php24
-rw-r--r--lib/private/DB/MySQLMigrator.php50
-rw-r--r--lib/private/DB/OracleConnection.php2
-rw-r--r--lib/private/DB/OracleMigrator.php241
-rw-r--r--lib/private/DB/PostgreSqlMigrator.php55
-rw-r--r--lib/private/DB/QueryBuilder/QueryBuilder.php6
-rw-r--r--lib/private/DB/SQLiteMigrator.php11
-rw-r--r--lib/private/Dashboard/Manager.php7
-rw-r--r--lib/private/DateTimeFormatter.php4
-rw-r--r--lib/private/Federation/CloudFederationProviderManager.php100
-rw-r--r--lib/private/Files/Cache/Cache.php11
-rw-r--r--lib/private/Files/Cache/CacheEntry.php2
-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.php13
-rw-r--r--lib/private/Files/Cache/SearchBuilder.php105
-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.php108
-rw-r--r--lib/private/Files/FileInfo.php60
-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.php30
-rw-r--r--lib/private/Files/Mount/MountPoint.php2
-rw-r--r--lib/private/Files/Mount/ObjectHomeMountProvider.php4
-rw-r--r--lib/private/Files/Node/Folder.php2
-rw-r--r--lib/private/Files/Node/LazyFolder.php78
-rw-r--r--lib/private/Files/Node/LazyRoot.php22
-rw-r--r--lib/private/Files/Node/LazyUserFolder.php19
-rw-r--r--lib/private/Files/Node/Node.php59
-rw-r--r--lib/private/Files/Node/Root.php26
-rw-r--r--lib/private/Files/ObjectStore/HomeObjectStoreStorage.php3
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreScanner.php2
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php2
-rw-r--r--lib/private/Files/ObjectStore/S3ConnectionTrait.php2
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php22
-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.php105
-rw-r--r--lib/private/Files/SetupManagerFactory.php45
-rw-r--r--lib/private/Files/Storage/Common.php2
-rw-r--r--lib/private/Files/Storage/DAV.php4
-rw-r--r--lib/private/Files/Storage/Home.php3
-rw-r--r--lib/private/Files/Storage/Local.php38
-rw-r--r--lib/private/Files/Storage/Wrapper/Encryption.php2
-rw-r--r--lib/private/Files/Storage/Wrapper/Jail.php5
-rw-r--r--lib/private/Files/Storage/Wrapper/KnownMtime.php142
-rw-r--r--lib/private/Files/Storage/Wrapper/PermissionsMask.php4
-rw-r--r--lib/private/Files/Template/TemplateManager.php3
-rw-r--r--lib/private/Files/Type/Loader.php36
-rw-r--r--lib/private/Files/View.php16
-rw-r--r--lib/private/FilesMetadata/FilesMetadataManager.php310
-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.php166
-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.php160
-rw-r--r--lib/private/Group/Database.php88
-rw-r--r--lib/private/Group/Group.php30
-rw-r--r--lib/private/Group/Manager.php77
-rw-r--r--lib/private/Http/Client/DnsPinMiddleware.php15
-rw-r--r--lib/private/Installer.php23
-rw-r--r--lib/private/L10N/Factory.php8
-rw-r--r--lib/private/Lock/AbstractLockingProvider.php12
-rw-r--r--lib/private/Lock/DBLockingProvider.php14
-rw-r--r--lib/private/Lock/MemcacheLockingProvider.php10
-rw-r--r--lib/private/Log.php2
-rw-r--r--lib/private/Log/ErrorHandler.php28
-rw-r--r--lib/private/Log/Errorlog.php13
-rw-r--r--lib/private/Log/ExceptionSerializer.php12
-rw-r--r--lib/private/Log/File.php28
-rw-r--r--lib/private/Log/LogDetails.php8
-rw-r--r--lib/private/Log/LogFactory.php58
-rw-r--r--lib/private/Log/PsrLoggerAdapter.php53
-rw-r--r--lib/private/Log/Rotate.php2
-rw-r--r--lib/private/Log/Syslog.php11
-rw-r--r--lib/private/Log/Systemdlog.php13
-rw-r--r--lib/private/Memcache/Factory.php10
-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.php18
-rw-r--r--lib/private/Migration/ConsoleOutput.php23
-rw-r--r--lib/private/Migration/SimpleOutput.php22
-rw-r--r--lib/private/NavigationManager.php92
-rw-r--r--lib/private/Net/HostnameClassifier.php4
-rw-r--r--lib/private/Net/IpAddressClassifier.php4
-rw-r--r--lib/private/OCM/Model/OCMProvider.php234
-rw-r--r--lib/private/OCM/Model/OCMResource.php123
-rw-r--r--lib/private/OCM/OCMDiscoveryService.php137
-rw-r--r--lib/private/PhoneNumberUtil.php61
-rw-r--r--lib/private/Preview/EMF.php33
-rw-r--r--lib/private/Preview/Generator.php56
-rw-r--r--lib/private/Preview/Imaginary.php25
-rw-r--r--lib/private/Preview/Office.php60
-rw-r--r--lib/private/PreviewManager.php60
-rw-r--r--lib/private/Profile/Actions/EmailAction.php21
-rw-r--r--lib/private/Profile/Actions/FediverseAction.php16
-rw-r--r--lib/private/Profile/Actions/PhoneAction.php21
-rw-r--r--lib/private/Profile/Actions/TwitterAction.php23
-rw-r--r--lib/private/Profile/Actions/WebsiteAction.php21
-rw-r--r--lib/private/Profile/ProfileManager.php146
-rw-r--r--lib/private/Remote/User.php2
-rw-r--r--lib/private/Repair.php9
-rw-r--r--lib/private/Repair/AddMetadataGenerationJob.php43
-rw-r--r--lib/private/Repair/AddRemoveOldTasksBackgroundJob.php8
-rw-r--r--lib/private/Repair/RepairMimeTypes.php11
-rw-r--r--lib/private/Repair/SqliteAutoincrement.php100
-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/CleanupJob.php11
-rw-r--r--lib/private/Security/Bruteforce/Throttler.php3
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicy.php115
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicyManager.php24
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php25
-rw-r--r--lib/private/Security/CSRF/CsrfToken.php14
-rw-r--r--lib/private/Security/CSRF/CsrfTokenGenerator.php12
-rw-r--r--lib/private/Security/CSRF/CsrfTokenManager.php28
-rw-r--r--lib/private/Security/CSRF/TokenStorage/SessionStorage.php26
-rw-r--r--lib/private/Security/Certificate.php48
-rw-r--r--lib/private/Security/CertificateManager.php29
-rw-r--r--lib/private/Security/CredentialsManager.php24
-rw-r--r--lib/private/Security/Crypto.php21
-rw-r--r--lib/private/Security/FeaturePolicy/FeaturePolicyManager.php16
-rw-r--r--lib/private/Security/Hasher.php25
-rw-r--r--lib/private/Security/Normalizer/IpAddress.php89
-rw-r--r--lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php3
-rw-r--r--lib/private/Security/RateLimiting/Limiter.php3
-rw-r--r--lib/private/Security/RemoteHostValidator.php19
-rw-r--r--lib/private/Security/SecureRandom.php7
-rw-r--r--lib/private/Security/TrustedDomainHelper.php12
-rw-r--r--lib/private/Server.php53
-rw-r--r--lib/private/Session/CryptoSessionData.php26
-rw-r--r--lib/private/Setup.php32
-rw-r--r--lib/private/Setup/AbstractDatabase.php7
-rw-r--r--lib/private/SetupCheck/SetupCheckManager.php57
-rw-r--r--lib/private/Share20/DefaultShareProvider.php60
-rw-r--r--lib/private/Share20/Manager.php41
-rw-r--r--lib/private/Share20/ProviderFactory.php3
-rw-r--r--lib/private/Share20/ShareDisableChecker.php65
-rw-r--r--lib/private/SystemConfig.php3
-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/Tags.php10
-rw-r--r--lib/private/Template/JSResourceLocator.php9
-rw-r--r--lib/private/TemplateLayout.php39
-rw-r--r--lib/private/TextProcessing/Db/Task.php10
-rw-r--r--lib/private/TextProcessing/Manager.php87
-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.php17
-rw-r--r--lib/private/Updater/VersionCheck.php4
-rw-r--r--lib/private/User/AvailabilityCoordinator.php122
-rw-r--r--lib/private/User/Manager.php30
-rw-r--r--lib/private/User/OutOfOfficeData.php63
-rw-r--r--lib/private/User/Session.php41
-rw-r--r--lib/private/User/User.php2
-rw-r--r--lib/private/legacy/OC_API.php2
-rw-r--r--lib/private/legacy/OC_App.php15
-rw-r--r--lib/private/legacy/OC_Files.php5
-rw-r--r--lib/private/legacy/OC_User.php10
-rw-r--r--lib/private/legacy/OC_Util.php3
-rw-r--r--lib/public/App/IAppManager.php25
-rw-r--r--lib/public/AppFramework/Bootstrap/IRegistrationContext.php22
-rw-r--r--lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php1
-rw-r--r--lib/public/AppFramework/Http/Attribute/OpenAPI.php99
-rw-r--r--lib/public/AppFramework/Http/ContentSecurityPolicy.php2
-rw-r--r--lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php42
-rw-r--r--lib/public/AppFramework/Http/Response.php7
-rw-r--r--lib/public/AppFramework/Utility/ITimeFactory.php2
-rw-r--r--lib/public/Authentication/Exceptions/ExpiredTokenException.php49
-rw-r--r--lib/public/Authentication/Exceptions/InvalidTokenException.php33
-rw-r--r--lib/public/Authentication/Exceptions/WipeTokenException.php49
-rw-r--r--lib/public/Authentication/Token/IProvider.php15
-rw-r--r--lib/public/Authentication/Token/IToken.php139
-rw-r--r--lib/public/BackgroundJob/IJobList.php16
-rw-r--r--lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php1
-rw-r--r--lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php107
-rw-r--r--lib/public/Collaboration/Reference/IReference.php7
-rw-r--r--lib/public/Collaboration/Reference/Reference.php2
-rw-r--r--lib/public/Contacts/ContactsMenu/IBulkProvider.php40
-rw-r--r--lib/public/Contacts/ContactsMenu/IEntry.php14
-rw-r--r--lib/public/Contacts/ContactsMenu/IProvider.php7
-rw-r--r--lib/public/Contacts/IManager.php17
-rw-r--r--lib/public/DB/QueryBuilder/IQueryBuilder.php9
-rw-r--r--lib/public/Dashboard/IManager.php2
-rw-r--r--lib/public/Federation/ICloudFederationNotification.php4
-rw-r--r--lib/public/Files/Config/ICachedMountInfo.php8
-rw-r--r--lib/public/Files/ConnectionLostException.php33
-rw-r--r--lib/public/Files/FileInfo.php18
-rw-r--r--lib/public/Files/IHomeStorage.php8
-rw-r--r--lib/public/Files/IRootFolder.php12
-rw-r--r--lib/public/Files/Mount/IMountManager.php12
-rw-r--r--lib/public/Files/Search/ISearchComparison.php17
-rw-r--r--lib/public/Files/Search/ISearchOrder.php13
-rw-r--r--lib/public/Files/Template/TemplateFileCreator.php1
-rw-r--r--lib/public/FilesMetadata/AMetadataEvent.php68
-rw-r--r--lib/public/FilesMetadata/Event/MetadataBackgroundEvent.php40
-rw-r--r--lib/public/FilesMetadata/Event/MetadataLiveEvent.php67
-rw-r--r--lib/public/FilesMetadata/Event/MetadataNamedEvent.php74
-rw-r--r--lib/public/FilesMetadata/Exceptions/FilesMetadataException.php34
-rw-r--r--lib/public/FilesMetadata/Exceptions/FilesMetadataKeyFormatException.php32
-rw-r--r--lib/public/FilesMetadata/Exceptions/FilesMetadataNotFoundException.php32
-rw-r--r--lib/public/FilesMetadata/Exceptions/FilesMetadataTypeException.php32
-rw-r--r--lib/public/FilesMetadata/IFilesMetadataManager.php158
-rw-r--r--lib/public/FilesMetadata/IMetadataQuery.php92
-rw-r--r--lib/public/FilesMetadata/Model/IFilesMetadata.php367
-rw-r--r--lib/public/FilesMetadata/Model/IMetadataValueWrapper.php334
-rw-r--r--lib/public/Group/Backend/ABackend.php28
-rw-r--r--lib/public/Group/Backend/IBatchMethodsBackend.php61
-rw-r--r--lib/public/Group/Backend/IGroupDetailsBackend.php8
-rw-r--r--lib/public/GroupInterface.php6
-rw-r--r--lib/public/ICacheFactory.php16
-rw-r--r--lib/public/IDBConnection.php20
-rw-r--r--lib/public/IGroup.php60
-rw-r--r--lib/public/INavigationManager.php8
-rw-r--r--lib/public/IPhoneNumberUtil.php57
-rw-r--r--lib/public/IUserManager.php6
-rw-r--r--lib/public/Migration/IOutput.php7
-rw-r--r--lib/public/OCM/Events/ResourceTypeRegisterEvent.php62
-rw-r--r--lib/public/OCM/Exceptions/OCMArgumentException.php34
-rw-r--r--lib/public/OCM/Exceptions/OCMProviderException.php34
-rw-r--r--lib/public/OCM/IOCMDiscoveryService.php48
-rw-r--r--lib/public/OCM/IOCMProvider.php165
-rw-r--r--lib/public/OCM/IOCMResource.php111
-rw-r--r--lib/public/Profile/IProfileManager.php106
-rw-r--r--lib/public/Search/FilterDefinition.php101
-rw-r--r--lib/public/Search/IFilter.php55
-rw-r--r--lib/public/Search/IFilterCollection.php57
-rw-r--r--lib/public/Search/IFilteringProvider.php72
-rw-r--r--lib/public/Search/IInAppSearch.php34
-rw-r--r--lib/public/Search/IProvider.php6
-rw-r--r--lib/public/Search/ISearchQuery.php14
-rw-r--r--lib/public/Security/RateLimiting/ILimiter.php72
-rw-r--r--lib/public/Security/RateLimiting/IRateLimitExceededException.php36
-rw-r--r--lib/public/SetupCheck/ISetupCheck.php53
-rw-r--r--lib/public/SetupCheck/ISetupCheckManager.php (renamed from lib/private/Metadata/Capabilities.php)37
-rw-r--r--lib/public/SetupCheck/SetupResult.php156
-rw-r--r--lib/public/Share/IShare.php2
-rw-r--r--lib/public/TextProcessing/Exception/TaskFailureException.php10
-rw-r--r--lib/public/TextProcessing/IManager.php19
-rw-r--r--lib/public/TextProcessing/IProviderWithExpectedRuntime.php41
-rw-r--r--lib/public/TextProcessing/IProviderWithUserId.php41
-rw-r--r--lib/public/TextProcessing/Task.php24
-rw-r--r--lib/public/TextToImage/Events/AbstractTextToImageEvent.php52
-rw-r--r--lib/public/TextToImage/Events/TaskFailedEvent.php54
-rw-r--r--lib/public/TextToImage/Events/TaskSuccessfulEvent.php33
-rw-r--r--lib/public/TextToImage/Exception/TaskFailureException.php31
-rw-r--r--lib/public/TextToImage/Exception/TaskNotFoundException.php31
-rw-r--r--lib/public/TextToImage/Exception/TextToImageException.php31
-rw-r--r--lib/public/TextToImage/IManager.php116
-rw-r--r--lib/public/TextToImage/IProvider.php64
-rw-r--r--lib/public/TextToImage/Task.php212
-rw-r--r--lib/public/User/Events/OutOfOfficeChangedEvent.php50
-rw-r--r--lib/public/User/Events/OutOfOfficeClearedEvent.php50
-rw-r--r--lib/public/User/Events/OutOfOfficeScheduledEvent.php50
-rw-r--r--lib/public/User/IAvailabilityCoordinator.php51
-rw-r--r--lib/public/User/IOutOfOfficeData.php77
-rw-r--r--lib/public/UserStatus/IUserStatus.php18
-rw-r--r--lib/public/Util.php50
-rw-r--r--lib/versioncheck.php6
433 files changed, 12583 insertions, 4014 deletions
diff --git a/lib/autoloader.php b/lib/autoloader.php
index a29b9aece79..26540b754a5 100644
--- a/lib/autoloader.php
+++ b/lib/autoloader.php
@@ -37,8 +37,8 @@ declare(strict_types=1);
namespace OC;
use \OCP\AutoloadNotAllowedException;
-use OCP\ILogger;
use OCP\ICache;
+use Psr\Log\LoggerInterface;
class Autoloader {
/** @var bool */
@@ -105,7 +105,7 @@ class Autoloader {
* Remove "apps/" from inclusion path for smooth migration to multi app dir
*/
if (strpos(\OC::$CLASSPATH[$class], 'apps/') === 0) {
- \OCP\Util::writeLog('core', 'include path for class "' . $class . '" starts with "apps/"', ILogger::DEBUG);
+ \OCP\Server::get(LoggerInterface::class)->debug('include path for class "' . $class . '" starts with "apps/"', ['app' => 'core']);
$paths[] = str_replace('apps/', '', \OC::$CLASSPATH[$class]);
}
} elseif (strpos($class, 'OC_') === 0) {
diff --git a/lib/base.php b/lib/base.php
index 7153e481eda..d0a2072cc66 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -114,8 +114,6 @@ class OC {
public static string $configDir;
- public static int $VERSION_MTIME = 0;
-
/**
* requested app
*/
@@ -610,10 +608,9 @@ class OC {
self::$CLI = (php_sapi_name() == 'cli');
- // Add default composer PSR-4 autoloader
+ // Add default composer PSR-4 autoloader, ensure apcu to be disabled
self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
- OC::$VERSION_MTIME = filemtime(OC::$SERVERROOT . '/version.php');
- self::$composerAutoloader->setApcuPrefix('composer_autoload_' . md5(OC::$SERVERROOT . '_' . OC::$VERSION_MTIME));
+ self::$composerAutoloader->setApcuPrefix(null);
try {
self::initPaths();
@@ -991,16 +988,17 @@ class OC {
// Check if Nextcloud is installed or in maintenance (update) mode
if (!$systemConfig->getValue('installed', false)) {
\OC::$server->getSession()->clear();
+ $logger = Server::get(\Psr\Log\LoggerInterface::class);
$setupHelper = new OC\Setup(
$systemConfig,
Server::get(\bantu\IniGetWrapper\IniGetWrapper::class),
Server::get(\OCP\L10N\IFactory::class)->get('lib'),
Server::get(\OCP\Defaults::class),
- Server::get(\Psr\Log\LoggerInterface::class),
+ $logger,
Server::get(\OCP\Security\ISecureRandom::class),
Server::get(\OC\Installer::class)
);
- $controller = new OC\Core\Controller\SetupController($setupHelper);
+ $controller = new OC\Core\Controller\SetupController($setupHelper, $logger);
$controller->run($_POST);
exit();
}
@@ -1123,7 +1121,7 @@ class OC {
}
$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
OC_Template::printErrorPage(
- $l->t('404'),
+ '404',
$l->t('The page could not be found on the server.'),
404
);
@@ -1134,11 +1132,14 @@ class OC {
* Check login: apache auth, auth token, basic auth
*/
public static function handleLogin(OCP\IRequest $request): bool {
+ if ($request->getHeader('X-Nextcloud-Federation')) {
+ return false;
+ }
$userSession = Server::get(\OC\User\Session::class);
if (OC_User::handleApacheAuth()) {
return true;
}
- if (self::tryAppEcosystemV2Login($request)) {
+ if (self::tryAppAPILogin($request)) {
return true;
}
if ($userSession->tryTokenLogin($request)) {
@@ -1179,17 +1180,17 @@ class OC {
}
}
- protected static function tryAppEcosystemV2Login(OCP\IRequest $request): bool {
+ protected static function tryAppAPILogin(OCP\IRequest $request): bool {
$appManager = Server::get(OCP\App\IAppManager::class);
- if (!$request->getHeader('AE-SIGNATURE')) {
+ if (!$request->getHeader('AUTHORIZATION-APP-API')) {
return false;
}
- if (!$appManager->isInstalled('app_ecosystem_v2')) {
+ if (!$appManager->isInstalled('app_api')) {
return false;
}
try {
- $appEcosystemV2Service = Server::get(OCA\AppEcosystemV2\Service\AppEcosystemV2Service::class);
- return $appEcosystemV2Service->validateExAppRequestToNC($request);
+ $appAPIService = Server::get(OCA\AppAPI\Service\AppAPIService::class);
+ return $appAPIService->validateExAppRequestToNC($request);
} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
return false;
}
diff --git a/lib/composer/composer/InstalledVersions.php b/lib/composer/composer/InstalledVersions.php
index c6b54af7ba2..51e734a774b 100644
--- a/lib/composer/composer/InstalledVersions.php
+++ b/lib/composer/composer/InstalledVersions.php
@@ -98,7 +98,7 @@ class InstalledVersions
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
- return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
@@ -119,7 +119,7 @@ class InstalledVersions
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
- $constraint = $parser->parseConstraints($constraint);
+ $constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
@@ -328,7 +328,9 @@ class InstalledVersions
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
- $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ $installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
@@ -340,12 +342,17 @@ class InstalledVersions
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
- self::$installed = require __DIR__ . '/installed.php';
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
} else {
self::$installed = array();
}
}
- $installed[] = self::$installed;
+
+ if (self::$installed !== array()) {
+ $installed[] = self::$installed;
+ }
return $installed;
}
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 704d59d000b..b90e2866bc6 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -44,6 +44,7 @@ return array(
'OCP\\AppFramework\\Http\\Attribute\\IgnoreOpenAPI' => $baseDir . '/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\NoAdminRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\NoCSRFRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php',
+ 'OCP\\AppFramework\\Http\\Attribute\\OpenAPI' => $baseDir . '/lib/public/AppFramework/Http/Attribute/OpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\PasswordConfirmationRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\PublicPage' => $baseDir . '/lib/public/AppFramework/Http/Attribute/PublicPage.php',
'OCP\\AppFramework\\Http\\Attribute\\StrictCookiesRequired' => $baseDir . '/lib/public/AppFramework/Http/Attribute/StrictCookiesRequired.php',
@@ -106,13 +107,17 @@ return array(
'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php',
'OCP\\Authentication\\Events\\LoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/LoginFailedEvent.php',
'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php',
+ 'OCP\\Authentication\\Exceptions\\ExpiredTokenException' => $baseDir . '/lib/public/Authentication/Exceptions/ExpiredTokenException.php',
+ 'OCP\\Authentication\\Exceptions\\InvalidTokenException' => $baseDir . '/lib/public/Authentication/Exceptions/InvalidTokenException.php',
'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
+ 'OCP\\Authentication\\Exceptions\\WipeTokenException' => $baseDir . '/lib/public/Authentication/Exceptions/WipeTokenException.php',
'OCP\\Authentication\\IAlternativeLogin' => $baseDir . '/lib/public/Authentication/IAlternativeLogin.php',
'OCP\\Authentication\\IApacheBackend' => $baseDir . '/lib/public/Authentication/IApacheBackend.php',
'OCP\\Authentication\\IProvideUserSecretBackend' => $baseDir . '/lib/public/Authentication/IProvideUserSecretBackend.php',
'OCP\\Authentication\\LoginCredentials\\ICredentials' => $baseDir . '/lib/public/Authentication/LoginCredentials/ICredentials.php',
'OCP\\Authentication\\LoginCredentials\\IStore' => $baseDir . '/lib/public/Authentication/LoginCredentials/IStore.php',
'OCP\\Authentication\\Token\\IProvider' => $baseDir . '/lib/public/Authentication/Token/IProvider.php',
+ 'OCP\\Authentication\\Token\\IToken' => $baseDir . '/lib/public/Authentication/Token/IToken.php',
'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php',
'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php',
'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php',
@@ -165,6 +170,7 @@ return array(
'OCP\\Capabilities\\IInitialStateExcludedCapability' => $baseDir . '/lib/public/Capabilities/IInitialStateExcludedCapability.php',
'OCP\\Capabilities\\IPublicCapability' => $baseDir . '/lib/public/Capabilities/IPublicCapability.php',
'OCP\\Collaboration\\AutoComplete\\AutoCompleteEvent' => $baseDir . '/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php',
+ 'OCP\\Collaboration\\AutoComplete\\AutoCompleteFilterEvent' => $baseDir . '/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php',
'OCP\\Collaboration\\AutoComplete\\IManager' => $baseDir . '/lib/public/Collaboration/AutoComplete/IManager.php',
'OCP\\Collaboration\\AutoComplete\\ISorter' => $baseDir . '/lib/public/Collaboration/AutoComplete/ISorter.php',
'OCP\\Collaboration\\Collaborators\\ISearch' => $baseDir . '/lib/public/Collaboration/Collaborators/ISearch.php',
@@ -206,6 +212,7 @@ return array(
'OCP\\Constants' => $baseDir . '/lib/public/Constants.php',
'OCP\\Contacts\\ContactsMenu\\IAction' => $baseDir . '/lib/public/Contacts/ContactsMenu/IAction.php',
'OCP\\Contacts\\ContactsMenu\\IActionFactory' => $baseDir . '/lib/public/Contacts/ContactsMenu/IActionFactory.php',
+ 'OCP\\Contacts\\ContactsMenu\\IBulkProvider' => $baseDir . '/lib/public/Contacts/ContactsMenu/IBulkProvider.php',
'OCP\\Contacts\\ContactsMenu\\IContactsStore' => $baseDir . '/lib/public/Contacts/ContactsMenu/IContactsStore.php',
'OCP\\Contacts\\ContactsMenu\\IEntry' => $baseDir . '/lib/public/Contacts/ContactsMenu/IEntry.php',
'OCP\\Contacts\\ContactsMenu\\ILinkAction' => $baseDir . '/lib/public/Contacts/ContactsMenu/ILinkAction.php',
@@ -280,6 +287,18 @@ return array(
'OCP\\Federation\\ICloudId' => $baseDir . '/lib/public/Federation/ICloudId.php',
'OCP\\Federation\\ICloudIdManager' => $baseDir . '/lib/public/Federation/ICloudIdManager.php',
'OCP\\Files' => $baseDir . '/lib/public/Files.php',
+ 'OCP\\FilesMetadata\\AMetadataEvent' => $baseDir . '/lib/public/FilesMetadata/AMetadataEvent.php',
+ 'OCP\\FilesMetadata\\Event\\MetadataBackgroundEvent' => $baseDir . '/lib/public/FilesMetadata/Event/MetadataBackgroundEvent.php',
+ 'OCP\\FilesMetadata\\Event\\MetadataLiveEvent' => $baseDir . '/lib/public/FilesMetadata/Event/MetadataLiveEvent.php',
+ 'OCP\\FilesMetadata\\Event\\MetadataNamedEvent' => $baseDir . '/lib/public/FilesMetadata/Event/MetadataNamedEvent.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataException' => $baseDir . '/lib/public/FilesMetadata/Exceptions/FilesMetadataException.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataKeyFormatException' => $baseDir . '/lib/public/FilesMetadata/Exceptions/FilesMetadataKeyFormatException.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataNotFoundException' => $baseDir . '/lib/public/FilesMetadata/Exceptions/FilesMetadataNotFoundException.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataTypeException' => $baseDir . '/lib/public/FilesMetadata/Exceptions/FilesMetadataTypeException.php',
+ 'OCP\\FilesMetadata\\IFilesMetadataManager' => $baseDir . '/lib/public/FilesMetadata/IFilesMetadataManager.php',
+ 'OCP\\FilesMetadata\\IMetadataQuery' => $baseDir . '/lib/public/FilesMetadata/IMetadataQuery.php',
+ 'OCP\\FilesMetadata\\Model\\IFilesMetadata' => $baseDir . '/lib/public/FilesMetadata/Model/IFilesMetadata.php',
+ 'OCP\\FilesMetadata\\Model\\IMetadataValueWrapper' => $baseDir . '/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php',
'OCP\\Files\\AlreadyExistsException' => $baseDir . '/lib/public/Files/AlreadyExistsException.php',
'OCP\\Files\\AppData\\IAppDataFactory' => $baseDir . '/lib/public/Files/AppData/IAppDataFactory.php',
'OCP\\Files\\Cache\\AbstractCacheEvent' => $baseDir . '/lib/public/Files/Cache/AbstractCacheEvent.php',
@@ -302,6 +321,7 @@ return array(
'OCP\\Files\\Config\\IMountProviderCollection' => $baseDir . '/lib/public/Files/Config/IMountProviderCollection.php',
'OCP\\Files\\Config\\IRootMountProvider' => $baseDir . '/lib/public/Files/Config/IRootMountProvider.php',
'OCP\\Files\\Config\\IUserMountCache' => $baseDir . '/lib/public/Files/Config/IUserMountCache.php',
+ 'OCP\\Files\\ConnectionLostException' => $baseDir . '/lib/public/Files/ConnectionLostException.php',
'OCP\\Files\\DavUtil' => $baseDir . '/lib/public/Files/DavUtil.php',
'OCP\\Files\\EmptyFileNameException' => $baseDir . '/lib/public/Files/EmptyFileNameException.php',
'OCP\\Files\\EntityTooLargeException' => $baseDir . '/lib/public/Files/EntityTooLargeException.php',
@@ -421,6 +441,7 @@ return array(
'OCP\\GroupInterface' => $baseDir . '/lib/public/GroupInterface.php',
'OCP\\Group\\Backend\\ABackend' => $baseDir . '/lib/public/Group/Backend/ABackend.php',
'OCP\\Group\\Backend\\IAddToGroupBackend' => $baseDir . '/lib/public/Group/Backend/IAddToGroupBackend.php',
+ 'OCP\\Group\\Backend\\IBatchMethodsBackend' => $baseDir . '/lib/public/Group/Backend/IBatchMethodsBackend.php',
'OCP\\Group\\Backend\\ICountDisabledInGroup' => $baseDir . '/lib/public/Group/Backend/ICountDisabledInGroup.php',
'OCP\\Group\\Backend\\ICountUsersBackend' => $baseDir . '/lib/public/Group/Backend/ICountUsersBackend.php',
'OCP\\Group\\Backend\\ICreateGroupBackend' => $baseDir . '/lib/public/Group/Backend/ICreateGroupBackend.php',
@@ -483,6 +504,7 @@ return array(
'OCP\\IMemcache' => $baseDir . '/lib/public/IMemcache.php',
'OCP\\IMemcacheTTL' => $baseDir . '/lib/public/IMemcacheTTL.php',
'OCP\\INavigationManager' => $baseDir . '/lib/public/INavigationManager.php',
+ 'OCP\\IPhoneNumberUtil' => $baseDir . '/lib/public/IPhoneNumberUtil.php',
'OCP\\IPreview' => $baseDir . '/lib/public/IPreview.php',
'OCP\\IRequest' => $baseDir . '/lib/public/IRequest.php',
'OCP\\IRequestId' => $baseDir . '/lib/public/IRequestId.php',
@@ -534,6 +556,12 @@ return array(
'OCP\\Notification\\IManager' => $baseDir . '/lib/public/Notification/IManager.php',
'OCP\\Notification\\INotification' => $baseDir . '/lib/public/Notification/INotification.php',
'OCP\\Notification\\INotifier' => $baseDir . '/lib/public/Notification/INotifier.php',
+ 'OCP\\OCM\\Events\\ResourceTypeRegisterEvent' => $baseDir . '/lib/public/OCM/Events/ResourceTypeRegisterEvent.php',
+ 'OCP\\OCM\\Exceptions\\OCMArgumentException' => $baseDir . '/lib/public/OCM/Exceptions/OCMArgumentException.php',
+ 'OCP\\OCM\\Exceptions\\OCMProviderException' => $baseDir . '/lib/public/OCM/Exceptions/OCMProviderException.php',
+ 'OCP\\OCM\\IOCMDiscoveryService' => $baseDir . '/lib/public/OCM/IOCMDiscoveryService.php',
+ 'OCP\\OCM\\IOCMProvider' => $baseDir . '/lib/public/OCM/IOCMProvider.php',
+ 'OCP\\OCM\\IOCMResource' => $baseDir . '/lib/public/OCM/IOCMResource.php',
'OCP\\OCS\\IDiscoveryService' => $baseDir . '/lib/public/OCS/IDiscoveryService.php',
'OCP\\PreConditionNotMetException' => $baseDir . '/lib/public/PreConditionNotMetException.php',
'OCP\\Preview\\BeforePreviewFetchedEvent' => $baseDir . '/lib/public/Preview/BeforePreviewFetchedEvent.php',
@@ -543,6 +571,7 @@ return array(
'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php',
'OCP\\Profile\\BeforeTemplateRenderedEvent' => $baseDir . '/lib/public/Profile/BeforeTemplateRenderedEvent.php',
'OCP\\Profile\\ILinkAction' => $baseDir . '/lib/public/Profile/ILinkAction.php',
+ 'OCP\\Profile\\IProfileManager' => $baseDir . '/lib/public/Profile/IProfileManager.php',
'OCP\\Profile\\ParameterDoesNotExistException' => $baseDir . '/lib/public/Profile/ParameterDoesNotExistException.php',
'OCP\\Profiler\\IProfile' => $baseDir . '/lib/public/Profiler/IProfile.php',
'OCP\\Profiler\\IProfiler' => $baseDir . '/lib/public/Profiler/IProfiler.php',
@@ -561,6 +590,11 @@ return array(
'OCP\\Route\\IRouter' => $baseDir . '/lib/public/Route/IRouter.php',
'OCP\\SabrePluginEvent' => $baseDir . '/lib/public/SabrePluginEvent.php',
'OCP\\SabrePluginException' => $baseDir . '/lib/public/SabrePluginException.php',
+ 'OCP\\Search\\FilterDefinition' => $baseDir . '/lib/public/Search/FilterDefinition.php',
+ 'OCP\\Search\\IFilter' => $baseDir . '/lib/public/Search/IFilter.php',
+ 'OCP\\Search\\IFilterCollection' => $baseDir . '/lib/public/Search/IFilterCollection.php',
+ 'OCP\\Search\\IFilteringProvider' => $baseDir . '/lib/public/Search/IFilteringProvider.php',
+ 'OCP\\Search\\IInAppSearch' => $baseDir . '/lib/public/Search/IInAppSearch.php',
'OCP\\Search\\IProvider' => $baseDir . '/lib/public/Search/IProvider.php',
'OCP\\Search\\ISearchQuery' => $baseDir . '/lib/public/Search/ISearchQuery.php',
'OCP\\Search\\PagedProvider' => $baseDir . '/lib/public/Search/PagedProvider.php',
@@ -581,6 +615,8 @@ return array(
'OCP\\Security\\IRemoteHostValidator' => $baseDir . '/lib/public/Security/IRemoteHostValidator.php',
'OCP\\Security\\ISecureRandom' => $baseDir . '/lib/public/Security/ISecureRandom.php',
'OCP\\Security\\ITrustedDomainHelper' => $baseDir . '/lib/public/Security/ITrustedDomainHelper.php',
+ 'OCP\\Security\\RateLimiting\\ILimiter' => $baseDir . '/lib/public/Security/RateLimiting/ILimiter.php',
+ 'OCP\\Security\\RateLimiting\\IRateLimitExceededException' => $baseDir . '/lib/public/Security/RateLimiting/IRateLimitExceededException.php',
'OCP\\Security\\VerificationToken\\IVerificationToken' => $baseDir . '/lib/public/Security/VerificationToken/IVerificationToken.php',
'OCP\\Security\\VerificationToken\\InvalidTokenException' => $baseDir . '/lib/public/Security/VerificationToken/InvalidTokenException.php',
'OCP\\Server' => $baseDir . '/lib/public/Server.php',
@@ -590,6 +626,9 @@ return array(
'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php',
'OCP\\Settings\\ISettings' => $baseDir . '/lib/public/Settings/ISettings.php',
'OCP\\Settings\\ISubAdminSettings' => $baseDir . '/lib/public/Settings/ISubAdminSettings.php',
+ 'OCP\\SetupCheck\\ISetupCheck' => $baseDir . '/lib/public/SetupCheck/ISetupCheck.php',
+ 'OCP\\SetupCheck\\ISetupCheckManager' => $baseDir . '/lib/public/SetupCheck/ISetupCheckManager.php',
+ 'OCP\\SetupCheck\\SetupResult' => $baseDir . '/lib/public/SetupCheck/SetupResult.php',
'OCP\\Share' => $baseDir . '/lib/public/Share.php',
'OCP\\Share\\Events\\BeforeShareCreatedEvent' => $baseDir . '/lib/public/Share/Events/BeforeShareCreatedEvent.php',
'OCP\\Share\\Events\\BeforeShareDeletedEvent' => $baseDir . '/lib/public/Share/Events/BeforeShareDeletedEvent.php',
@@ -645,14 +684,26 @@ return array(
'OCP\\TextProcessing\\Events\\AbstractTextProcessingEvent' => $baseDir . '/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php',
'OCP\\TextProcessing\\Events\\TaskFailedEvent' => $baseDir . '/lib/public/TextProcessing/Events/TaskFailedEvent.php',
'OCP\\TextProcessing\\Events\\TaskSuccessfulEvent' => $baseDir . '/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php',
+ 'OCP\\TextProcessing\\Exception\\TaskFailureException' => $baseDir . '/lib/public/TextProcessing/Exception/TaskFailureException.php',
'OCP\\TextProcessing\\FreePromptTaskType' => $baseDir . '/lib/public/TextProcessing/FreePromptTaskType.php',
'OCP\\TextProcessing\\HeadlineTaskType' => $baseDir . '/lib/public/TextProcessing/HeadlineTaskType.php',
'OCP\\TextProcessing\\IManager' => $baseDir . '/lib/public/TextProcessing/IManager.php',
'OCP\\TextProcessing\\IProvider' => $baseDir . '/lib/public/TextProcessing/IProvider.php',
+ 'OCP\\TextProcessing\\IProviderWithExpectedRuntime' => $baseDir . '/lib/public/TextProcessing/IProviderWithExpectedRuntime.php',
+ 'OCP\\TextProcessing\\IProviderWithUserId' => $baseDir . '/lib/public/TextProcessing/IProviderWithUserId.php',
'OCP\\TextProcessing\\ITaskType' => $baseDir . '/lib/public/TextProcessing/ITaskType.php',
'OCP\\TextProcessing\\SummaryTaskType' => $baseDir . '/lib/public/TextProcessing/SummaryTaskType.php',
'OCP\\TextProcessing\\Task' => $baseDir . '/lib/public/TextProcessing/Task.php',
'OCP\\TextProcessing\\TopicsTaskType' => $baseDir . '/lib/public/TextProcessing/TopicsTaskType.php',
+ 'OCP\\TextToImage\\Events\\AbstractTextToImageEvent' => $baseDir . '/lib/public/TextToImage/Events/AbstractTextToImageEvent.php',
+ 'OCP\\TextToImage\\Events\\TaskFailedEvent' => $baseDir . '/lib/public/TextToImage/Events/TaskFailedEvent.php',
+ 'OCP\\TextToImage\\Events\\TaskSuccessfulEvent' => $baseDir . '/lib/public/TextToImage/Events/TaskSuccessfulEvent.php',
+ 'OCP\\TextToImage\\Exception\\TaskFailureException' => $baseDir . '/lib/public/TextToImage/Exception/TaskFailureException.php',
+ 'OCP\\TextToImage\\Exception\\TaskNotFoundException' => $baseDir . '/lib/public/TextToImage/Exception/TaskNotFoundException.php',
+ 'OCP\\TextToImage\\Exception\\TextToImageException' => $baseDir . '/lib/public/TextToImage/Exception/TextToImageException.php',
+ 'OCP\\TextToImage\\IManager' => $baseDir . '/lib/public/TextToImage/IManager.php',
+ 'OCP\\TextToImage\\IProvider' => $baseDir . '/lib/public/TextToImage/IProvider.php',
+ 'OCP\\TextToImage\\Task' => $baseDir . '/lib/public/TextToImage/Task.php',
'OCP\\Translation\\CouldNotTranslateException' => $baseDir . '/lib/public/Translation/CouldNotTranslateException.php',
'OCP\\Translation\\IDetectLanguageProvider' => $baseDir . '/lib/public/Translation/IDetectLanguageProvider.php',
'OCP\\Translation\\ITranslationManager' => $baseDir . '/lib/public/Translation/ITranslationManager.php',
@@ -689,6 +740,9 @@ return array(
'OCP\\User\\Events\\BeforeUserLoggedInEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeChangedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeChangedEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeClearedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeClearedEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
'OCP\\User\\Events\\PasswordUpdatedEvent' => $baseDir . '/lib/public/User/Events/PasswordUpdatedEvent.php',
'OCP\\User\\Events\\PostLoginEvent' => $baseDir . '/lib/public/User/Events/PostLoginEvent.php',
'OCP\\User\\Events\\UserChangedEvent' => $baseDir . '/lib/public/User/Events/UserChangedEvent.php',
@@ -700,6 +754,8 @@ return array(
'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\UserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/UserLoggedOutEvent.php',
'OCP\\User\\GetQuotaEvent' => $baseDir . '/lib/public/User/GetQuotaEvent.php',
+ 'OCP\\User\\IAvailabilityCoordinator' => $baseDir . '/lib/public/User/IAvailabilityCoordinator.php',
+ 'OCP\\User\\IOutOfOfficeData' => $baseDir . '/lib/public/User/IOutOfOfficeData.php',
'OCP\\Util' => $baseDir . '/lib/public/Util.php',
'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php',
@@ -947,6 +1003,7 @@ return array(
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => $baseDir . '/core/BackgroundJobs/CheckForUserCertificates.php',
'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => $baseDir . '/core/BackgroundJobs/CleanupLoginFlowV2.php',
+ 'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => $baseDir . '/core/BackgroundJobs/GenerateMetadataJob.php',
'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => $baseDir . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php',
'OC\\Core\\Command\\App\\Disable' => $baseDir . '/core/Command/App/Disable.php',
'OC\\Core\\Command\\App\\Enable' => $baseDir . '/core/Command/App/Enable.php',
@@ -994,6 +1051,7 @@ return array(
'OC\\Core\\Command\\Encryption\\SetDefaultModule' => $baseDir . '/core/Command/Encryption/SetDefaultModule.php',
'OC\\Core\\Command\\Encryption\\ShowKeyStorageRoot' => $baseDir . '/core/Command/Encryption/ShowKeyStorageRoot.php',
'OC\\Core\\Command\\Encryption\\Status' => $baseDir . '/core/Command/Encryption/Status.php',
+ 'OC\\Core\\Command\\FilesMetadata\\Get' => $baseDir . '/core/Command/FilesMetadata/Get.php',
'OC\\Core\\Command\\Group\\Add' => $baseDir . '/core/Command/Group/Add.php',
'OC\\Core\\Command\\Group\\AddUser' => $baseDir . '/core/Command/Group/AddUser.php',
'OC\\Core\\Command\\Group\\Delete' => $baseDir . '/core/Command/Group/Delete.php',
@@ -1029,6 +1087,7 @@ return array(
'OC\\Core\\Command\\Security\\ImportCertificate' => $baseDir . '/core/Command/Security/ImportCertificate.php',
'OC\\Core\\Command\\Security\\ListCertificates' => $baseDir . '/core/Command/Security/ListCertificates.php',
'OC\\Core\\Command\\Security\\RemoveCertificate' => $baseDir . '/core/Command/Security/RemoveCertificate.php',
+ 'OC\\Core\\Command\\SetupChecks' => $baseDir . '/core/Command/SetupChecks.php',
'OC\\Core\\Command\\Status' => $baseDir . '/core/Command/Status.php',
'OC\\Core\\Command\\SystemTag\\Add' => $baseDir . '/core/Command/SystemTag/Add.php',
'OC\\Core\\Command\\SystemTag\\Delete' => $baseDir . '/core/Command/SystemTag/Delete.php',
@@ -1072,6 +1131,7 @@ return array(
'OC\\Core\\Controller\\LostController' => $baseDir . '/core/Controller/LostController.php',
'OC\\Core\\Controller\\NavigationController' => $baseDir . '/core/Controller/NavigationController.php',
'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php',
+ 'OC\\Core\\Controller\\OCMController' => $baseDir . '/core/Controller/OCMController.php',
'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php',
'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php',
'OC\\Core\\Controller\\ProfileApiController' => $baseDir . '/core/Controller/ProfileApiController.php',
@@ -1082,6 +1142,7 @@ return array(
'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php',
'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php',
'OC\\Core\\Controller\\TextProcessingApiController' => $baseDir . '/core/Controller/TextProcessingApiController.php',
+ 'OC\\Core\\Controller\\TextToImageApiController' => $baseDir . '/core/Controller/TextToImageApiController.php',
'OC\\Core\\Controller\\TranslationApiController' => $baseDir . '/core/Controller/TranslationApiController.php',
'OC\\Core\\Controller\\TwoFactorChallengeController' => $baseDir . '/core/Controller/TwoFactorChallengeController.php',
'OC\\Core\\Controller\\UnifiedSearchController' => $baseDir . '/core/Controller/UnifiedSearchController.php',
@@ -1163,6 +1224,9 @@ return array(
'OC\\Core\\Migrations\\Version28000Date20230616104802' => $baseDir . '/core/Migrations/Version28000Date20230616104802.php',
'OC\\Core\\Migrations\\Version28000Date20230728104802' => $baseDir . '/core/Migrations/Version28000Date20230728104802.php',
'OC\\Core\\Migrations\\Version28000Date20230803221055' => $baseDir . '/core/Migrations/Version28000Date20230803221055.php',
+ 'OC\\Core\\Migrations\\Version28000Date20230906104802' => $baseDir . '/core/Migrations/Version28000Date20230906104802.php',
+ 'OC\\Core\\Migrations\\Version28000Date20231004103301' => $baseDir . '/core/Migrations/Version28000Date20231004103301.php',
+ 'OC\\Core\\Migrations\\Version28000Date20231103104802' => $baseDir . '/core/Migrations/Version28000Date20231103104802.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
@@ -1183,14 +1247,12 @@ return array(
'OC\\DB\\MissingColumnInformation' => $baseDir . '/lib/private/DB/MissingColumnInformation.php',
'OC\\DB\\MissingIndexInformation' => $baseDir . '/lib/private/DB/MissingIndexInformation.php',
'OC\\DB\\MissingPrimaryKeyInformation' => $baseDir . '/lib/private/DB/MissingPrimaryKeyInformation.php',
- 'OC\\DB\\MySQLMigrator' => $baseDir . '/lib/private/DB/MySQLMigrator.php',
'OC\\DB\\MySqlTools' => $baseDir . '/lib/private/DB/MySqlTools.php',
'OC\\DB\\OCSqlitePlatform' => $baseDir . '/lib/private/DB/OCSqlitePlatform.php',
'OC\\DB\\ObjectParameter' => $baseDir . '/lib/private/DB/ObjectParameter.php',
'OC\\DB\\OracleConnection' => $baseDir . '/lib/private/DB/OracleConnection.php',
'OC\\DB\\OracleMigrator' => $baseDir . '/lib/private/DB/OracleMigrator.php',
'OC\\DB\\PgSqlTools' => $baseDir . '/lib/private/DB/PgSqlTools.php',
- 'OC\\DB\\PostgreSqlMigrator' => $baseDir . '/lib/private/DB/PostgreSqlMigrator.php',
'OC\\DB\\PreparedStatement' => $baseDir . '/lib/private/DB/PreparedStatement.php',
'OC\\DB\\QueryBuilder\\CompositeExpression' => $baseDir . '/lib/private/DB/QueryBuilder/CompositeExpression.php',
'OC\\DB\\QueryBuilder\\ExpressionBuilder\\ExpressionBuilder' => $baseDir . '/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php',
@@ -1249,6 +1311,15 @@ return array(
'OC\\Federation\\CloudFederationShare' => $baseDir . '/lib/private/Federation/CloudFederationShare.php',
'OC\\Federation\\CloudId' => $baseDir . '/lib/private/Federation/CloudId.php',
'OC\\Federation\\CloudIdManager' => $baseDir . '/lib/private/Federation/CloudIdManager.php',
+ 'OC\\FilesMetadata\\FilesMetadataManager' => $baseDir . '/lib/private/FilesMetadata/FilesMetadataManager.php',
+ 'OC\\FilesMetadata\\Job\\UpdateSingleMetadata' => $baseDir . '/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php',
+ 'OC\\FilesMetadata\\Listener\\MetadataDelete' => $baseDir . '/lib/private/FilesMetadata/Listener/MetadataDelete.php',
+ 'OC\\FilesMetadata\\Listener\\MetadataUpdate' => $baseDir . '/lib/private/FilesMetadata/Listener/MetadataUpdate.php',
+ 'OC\\FilesMetadata\\MetadataQuery' => $baseDir . '/lib/private/FilesMetadata/MetadataQuery.php',
+ 'OC\\FilesMetadata\\Model\\FilesMetadata' => $baseDir . '/lib/private/FilesMetadata/Model/FilesMetadata.php',
+ 'OC\\FilesMetadata\\Model\\MetadataValueWrapper' => $baseDir . '/lib/private/FilesMetadata/Model/MetadataValueWrapper.php',
+ 'OC\\FilesMetadata\\Service\\IndexRequestService' => $baseDir . '/lib/private/FilesMetadata/Service/IndexRequestService.php',
+ 'OC\\FilesMetadata\\Service\\MetadataRequestService' => $baseDir . '/lib/private/FilesMetadata/Service/MetadataRequestService.php',
'OC\\Files\\AppData\\AppData' => $baseDir . '/lib/private/Files/AppData/AppData.php',
'OC\\Files\\AppData\\Factory' => $baseDir . '/lib/private/Files/AppData/Factory.php',
'OC\\Files\\Cache\\Cache' => $baseDir . '/lib/private/Files/Cache/Cache.php',
@@ -1282,6 +1353,7 @@ return array(
'OC\\Files\\Filesystem' => $baseDir . '/lib/private/Files/Filesystem.php',
'OC\\Files\\Lock\\LockManager' => $baseDir . '/lib/private/Files/Lock/LockManager.php',
'OC\\Files\\Mount\\CacheMountProvider' => $baseDir . '/lib/private/Files/Mount/CacheMountProvider.php',
+ 'OC\\Files\\Mount\\HomeMountPoint' => $baseDir . '/lib/private/Files/Mount/HomeMountPoint.php',
'OC\\Files\\Mount\\LocalHomeMountProvider' => $baseDir . '/lib/private/Files/Mount/LocalHomeMountProvider.php',
'OC\\Files\\Mount\\Manager' => $baseDir . '/lib/private/Files/Mount/Manager.php',
'OC\\Files\\Mount\\MountPoint' => $baseDir . '/lib/private/Files/Mount/MountPoint.php',
@@ -1344,6 +1416,7 @@ return array(
'OC\\Files\\Storage\\Wrapper\\EncodingDirectoryWrapper' => $baseDir . '/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php',
'OC\\Files\\Storage\\Wrapper\\Encryption' => $baseDir . '/lib/private/Files/Storage/Wrapper/Encryption.php',
'OC\\Files\\Storage\\Wrapper\\Jail' => $baseDir . '/lib/private/Files/Storage/Wrapper/Jail.php',
+ 'OC\\Files\\Storage\\Wrapper\\KnownMtime' => $baseDir . '/lib/private/Files/Storage/Wrapper/KnownMtime.php',
'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => $baseDir . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php',
'OC\\Files\\Storage\\Wrapper\\Quota' => $baseDir . '/lib/private/Files/Storage/Wrapper/Quota.php',
'OC\\Files\\Storage\\Wrapper\\Wrapper' => $baseDir . '/lib/private/Files/Storage/Wrapper/Wrapper.php',
@@ -1440,14 +1513,6 @@ return array(
'OC\\Memcache\\Redis' => $baseDir . '/lib/private/Memcache/Redis.php',
'OC\\Memcache\\WithLocalCache' => $baseDir . '/lib/private/Memcache/WithLocalCache.php',
'OC\\MemoryInfo' => $baseDir . '/lib/private/MemoryInfo.php',
- 'OC\\Metadata\\Capabilities' => $baseDir . '/lib/private/Metadata/Capabilities.php',
- 'OC\\Metadata\\FileEventListener' => $baseDir . '/lib/private/Metadata/FileEventListener.php',
- 'OC\\Metadata\\FileMetadata' => $baseDir . '/lib/private/Metadata/FileMetadata.php',
- 'OC\\Metadata\\FileMetadataMapper' => $baseDir . '/lib/private/Metadata/FileMetadataMapper.php',
- 'OC\\Metadata\\IMetadataManager' => $baseDir . '/lib/private/Metadata/IMetadataManager.php',
- 'OC\\Metadata\\IMetadataProvider' => $baseDir . '/lib/private/Metadata/IMetadataProvider.php',
- 'OC\\Metadata\\MetadataManager' => $baseDir . '/lib/private/Metadata/MetadataManager.php',
- 'OC\\Metadata\\Provider\\ExifProvider' => $baseDir . '/lib/private/Metadata/Provider/ExifProvider.php',
'OC\\Migration\\BackgroundRepair' => $baseDir . '/lib/private/Migration/BackgroundRepair.php',
'OC\\Migration\\ConsoleOutput' => $baseDir . '/lib/private/Migration/ConsoleOutput.php',
'OC\\Migration\\SimpleOutput' => $baseDir . '/lib/private/Migration/SimpleOutput.php',
@@ -1461,17 +1526,22 @@ return array(
'OC\\Notification\\Action' => $baseDir . '/lib/private/Notification/Action.php',
'OC\\Notification\\Manager' => $baseDir . '/lib/private/Notification/Manager.php',
'OC\\Notification\\Notification' => $baseDir . '/lib/private/Notification/Notification.php',
+ 'OC\\OCM\\Model\\OCMProvider' => $baseDir . '/lib/private/OCM/Model/OCMProvider.php',
+ 'OC\\OCM\\Model\\OCMResource' => $baseDir . '/lib/private/OCM/Model/OCMResource.php',
+ 'OC\\OCM\\OCMDiscoveryService' => $baseDir . '/lib/private/OCM/OCMDiscoveryService.php',
'OC\\OCS\\CoreCapabilities' => $baseDir . '/lib/private/OCS/CoreCapabilities.php',
'OC\\OCS\\DiscoveryService' => $baseDir . '/lib/private/OCS/DiscoveryService.php',
'OC\\OCS\\Exception' => $baseDir . '/lib/private/OCS/Exception.php',
'OC\\OCS\\Provider' => $baseDir . '/lib/private/OCS/Provider.php',
'OC\\OCS\\Result' => $baseDir . '/lib/private/OCS/Result.php',
+ 'OC\\PhoneNumberUtil' => $baseDir . '/lib/private/PhoneNumberUtil.php',
'OC\\PreviewManager' => $baseDir . '/lib/private/PreviewManager.php',
'OC\\PreviewNotAvailableException' => $baseDir . '/lib/private/PreviewNotAvailableException.php',
'OC\\Preview\\BMP' => $baseDir . '/lib/private/Preview/BMP.php',
'OC\\Preview\\BackgroundCleanupJob' => $baseDir . '/lib/private/Preview/BackgroundCleanupJob.php',
'OC\\Preview\\Bitmap' => $baseDir . '/lib/private/Preview/Bitmap.php',
'OC\\Preview\\Bundled' => $baseDir . '/lib/private/Preview/Bundled.php',
+ 'OC\\Preview\\EMF' => $baseDir . '/lib/private/Preview/EMF.php',
'OC\\Preview\\Font' => $baseDir . '/lib/private/Preview/Font.php',
'OC\\Preview\\GIF' => $baseDir . '/lib/private/Preview/GIF.php',
'OC\\Preview\\Generator' => $baseDir . '/lib/private/Preview/Generator.php',
@@ -1535,6 +1605,7 @@ return array(
'OC\\RepairException' => $baseDir . '/lib/private/RepairException.php',
'OC\\Repair\\AddBruteForceCleanupJob' => $baseDir . '/lib/private/Repair/AddBruteForceCleanupJob.php',
'OC\\Repair\\AddCleanupUpdaterBackupsJob' => $baseDir . '/lib/private/Repair/AddCleanupUpdaterBackupsJob.php',
+ 'OC\\Repair\\AddMetadataGenerationJob' => $baseDir . '/lib/private/Repair/AddMetadataGenerationJob.php',
'OC\\Repair\\AddRemoveOldTasksBackgroundJob' => $baseDir . '/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php',
'OC\\Repair\\CleanTags' => $baseDir . '/lib/private/Repair/CleanTags.php',
'OC\\Repair\\CleanUpAbandonedApps' => $baseDir . '/lib/private/Repair/CleanUpAbandonedApps.php',
@@ -1578,12 +1649,21 @@ return array(
'OC\\Repair\\RepairDavShares' => $baseDir . '/lib/private/Repair/RepairDavShares.php',
'OC\\Repair\\RepairInvalidShares' => $baseDir . '/lib/private/Repair/RepairInvalidShares.php',
'OC\\Repair\\RepairMimeTypes' => $baseDir . '/lib/private/Repair/RepairMimeTypes.php',
- 'OC\\Repair\\SqliteAutoincrement' => $baseDir . '/lib/private/Repair/SqliteAutoincrement.php',
'OC\\RichObjectStrings\\Validator' => $baseDir . '/lib/private/RichObjectStrings/Validator.php',
'OC\\Route\\CachingRouter' => $baseDir . '/lib/private/Route/CachingRouter.php',
'OC\\Route\\Route' => $baseDir . '/lib/private/Route/Route.php',
'OC\\Route\\Router' => $baseDir . '/lib/private/Route/Router.php',
'OC\\Search' => $baseDir . '/lib/private/Search.php',
+ 'OC\\Search\\FilterCollection' => $baseDir . '/lib/private/Search/FilterCollection.php',
+ 'OC\\Search\\FilterFactory' => $baseDir . '/lib/private/Search/FilterFactory.php',
+ 'OC\\Search\\Filter\\BooleanFilter' => $baseDir . '/lib/private/Search/Filter/BooleanFilter.php',
+ 'OC\\Search\\Filter\\DateTimeFilter' => $baseDir . '/lib/private/Search/Filter/DateTimeFilter.php',
+ 'OC\\Search\\Filter\\FloatFilter' => $baseDir . '/lib/private/Search/Filter/FloatFilter.php',
+ 'OC\\Search\\Filter\\GroupFilter' => $baseDir . '/lib/private/Search/Filter/GroupFilter.php',
+ 'OC\\Search\\Filter\\IntegerFilter' => $baseDir . '/lib/private/Search/Filter/IntegerFilter.php',
+ 'OC\\Search\\Filter\\StringFilter' => $baseDir . '/lib/private/Search/Filter/StringFilter.php',
+ 'OC\\Search\\Filter\\StringsFilter' => $baseDir . '/lib/private/Search/Filter/StringsFilter.php',
+ 'OC\\Search\\Filter\\UserFilter' => $baseDir . '/lib/private/Search/Filter/UserFilter.php',
'OC\\Search\\Provider\\File' => $baseDir . '/lib/private/Search/Provider/File.php',
'OC\\Search\\Result\\Audio' => $baseDir . '/lib/private/Search/Result/Audio.php',
'OC\\Search\\Result\\File' => $baseDir . '/lib/private/Search/Result/File.php',
@@ -1591,6 +1671,7 @@ return array(
'OC\\Search\\Result\\Image' => $baseDir . '/lib/private/Search/Result/Image.php',
'OC\\Search\\SearchComposer' => $baseDir . '/lib/private/Search/SearchComposer.php',
'OC\\Search\\SearchQuery' => $baseDir . '/lib/private/Search/SearchQuery.php',
+ 'OC\\Search\\UnsupportedFilter' => $baseDir . '/lib/private/Search/UnsupportedFilter.php',
'OC\\Security\\Bruteforce\\Backend\\DatabaseBackend' => $baseDir . '/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php',
'OC\\Security\\Bruteforce\\Backend\\IBackend' => $baseDir . '/lib/private/Security/Bruteforce/Backend/IBackend.php',
'OC\\Security\\Bruteforce\\Backend\\MemoryCacheBackend' => $baseDir . '/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php',
@@ -1639,6 +1720,7 @@ return array(
'OC\\Settings\\Manager' => $baseDir . '/lib/private/Settings/Manager.php',
'OC\\Settings\\Section' => $baseDir . '/lib/private/Settings/Section.php',
'OC\\Setup' => $baseDir . '/lib/private/Setup.php',
+ 'OC\\SetupCheck\\SetupCheckManager' => $baseDir . '/lib/private/SetupCheck/SetupCheckManager.php',
'OC\\Setup\\AbstractDatabase' => $baseDir . '/lib/private/Setup/AbstractDatabase.php',
'OC\\Setup\\MySQL' => $baseDir . '/lib/private/Setup/MySQL.php',
'OC\\Setup\\OCI' => $baseDir . '/lib/private/Setup/OCI.php',
@@ -1655,6 +1737,7 @@ return array(
'OC\\Share20\\PublicShareTemplateFactory' => $baseDir . '/lib/private/Share20/PublicShareTemplateFactory.php',
'OC\\Share20\\Share' => $baseDir . '/lib/private/Share20/Share.php',
'OC\\Share20\\ShareAttributes' => $baseDir . '/lib/private/Share20/ShareAttributes.php',
+ 'OC\\Share20\\ShareDisableChecker' => $baseDir . '/lib/private/Share20/ShareDisableChecker.php',
'OC\\Share20\\ShareHelper' => $baseDir . '/lib/private/Share20/ShareHelper.php',
'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php',
'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php',
@@ -1695,6 +1778,11 @@ return array(
'OC\\TextProcessing\\Manager' => $baseDir . '/lib/private/TextProcessing/Manager.php',
'OC\\TextProcessing\\RemoveOldTasksBackgroundJob' => $baseDir . '/lib/private/TextProcessing/RemoveOldTasksBackgroundJob.php',
'OC\\TextProcessing\\TaskBackgroundJob' => $baseDir . '/lib/private/TextProcessing/TaskBackgroundJob.php',
+ 'OC\\TextToImage\\Db\\Task' => $baseDir . '/lib/private/TextToImage/Db/Task.php',
+ 'OC\\TextToImage\\Db\\TaskMapper' => $baseDir . '/lib/private/TextToImage/Db/TaskMapper.php',
+ 'OC\\TextToImage\\Manager' => $baseDir . '/lib/private/TextToImage/Manager.php',
+ 'OC\\TextToImage\\RemoveOldTasksBackgroundJob' => $baseDir . '/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php',
+ 'OC\\TextToImage\\TaskBackgroundJob' => $baseDir . '/lib/private/TextToImage/TaskBackgroundJob.php',
'OC\\Translation\\TranslationManager' => $baseDir . '/lib/private/Translation/TranslationManager.php',
'OC\\URLGenerator' => $baseDir . '/lib/private/URLGenerator.php',
'OC\\Updater' => $baseDir . '/lib/private/Updater.php',
@@ -1704,6 +1792,7 @@ return array(
'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php',
'OC\\UserStatus\\ISettableProvider' => $baseDir . '/lib/private/UserStatus/ISettableProvider.php',
'OC\\UserStatus\\Manager' => $baseDir . '/lib/private/UserStatus/Manager.php',
+ 'OC\\User\\AvailabilityCoordinator' => $baseDir . '/lib/private/User/AvailabilityCoordinator.php',
'OC\\User\\Backend' => $baseDir . '/lib/private/User/Backend.php',
'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php',
'OC\\User\\DisplayNameCache' => $baseDir . '/lib/private/User/DisplayNameCache.php',
@@ -1713,6 +1802,7 @@ return array(
'OC\\User\\LoginException' => $baseDir . '/lib/private/User/LoginException.php',
'OC\\User\\Manager' => $baseDir . '/lib/private/User/Manager.php',
'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php',
+ 'OC\\User\\OutOfOfficeData' => $baseDir . '/lib/private/User/OutOfOfficeData.php',
'OC\\User\\Session' => $baseDir . '/lib/private/User/Session.php',
'OC\\User\\User' => $baseDir . '/lib/private/User/User.php',
'OC_API' => $baseDir . '/lib/private/legacy/OC_API.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index fb3ef9ad3d0..c1c3bc25869 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -77,6 +77,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\AppFramework\\Http\\Attribute\\IgnoreOpenAPI' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\NoAdminRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\NoCSRFRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php',
+ 'OCP\\AppFramework\\Http\\Attribute\\OpenAPI' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/OpenAPI.php',
'OCP\\AppFramework\\Http\\Attribute\\PasswordConfirmationRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php',
'OCP\\AppFramework\\Http\\Attribute\\PublicPage' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/PublicPage.php',
'OCP\\AppFramework\\Http\\Attribute\\StrictCookiesRequired' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/StrictCookiesRequired.php',
@@ -139,13 +140,17 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php',
'OCP\\Authentication\\Events\\LoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/LoginFailedEvent.php',
'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php',
+ 'OCP\\Authentication\\Exceptions\\ExpiredTokenException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/ExpiredTokenException.php',
+ 'OCP\\Authentication\\Exceptions\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/InvalidTokenException.php',
'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
+ 'OCP\\Authentication\\Exceptions\\WipeTokenException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/WipeTokenException.php',
'OCP\\Authentication\\IAlternativeLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/IAlternativeLogin.php',
'OCP\\Authentication\\IApacheBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IApacheBackend.php',
'OCP\\Authentication\\IProvideUserSecretBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IProvideUserSecretBackend.php',
'OCP\\Authentication\\LoginCredentials\\ICredentials' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/ICredentials.php',
'OCP\\Authentication\\LoginCredentials\\IStore' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/IStore.php',
'OCP\\Authentication\\Token\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/Token/IProvider.php',
+ 'OCP\\Authentication\\Token\\IToken' => __DIR__ . '/../../..' . '/lib/public/Authentication/Token/IToken.php',
'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php',
'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php',
'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php',
@@ -198,6 +203,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Capabilities\\IInitialStateExcludedCapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/IInitialStateExcludedCapability.php',
'OCP\\Capabilities\\IPublicCapability' => __DIR__ . '/../../..' . '/lib/public/Capabilities/IPublicCapability.php',
'OCP\\Collaboration\\AutoComplete\\AutoCompleteEvent' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php',
+ 'OCP\\Collaboration\\AutoComplete\\AutoCompleteFilterEvent' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php',
'OCP\\Collaboration\\AutoComplete\\IManager' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/IManager.php',
'OCP\\Collaboration\\AutoComplete\\ISorter' => __DIR__ . '/../../..' . '/lib/public/Collaboration/AutoComplete/ISorter.php',
'OCP\\Collaboration\\Collaborators\\ISearch' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Collaborators/ISearch.php',
@@ -239,6 +245,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Constants' => __DIR__ . '/../../..' . '/lib/public/Constants.php',
'OCP\\Contacts\\ContactsMenu\\IAction' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IAction.php',
'OCP\\Contacts\\ContactsMenu\\IActionFactory' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IActionFactory.php',
+ 'OCP\\Contacts\\ContactsMenu\\IBulkProvider' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IBulkProvider.php',
'OCP\\Contacts\\ContactsMenu\\IContactsStore' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IContactsStore.php',
'OCP\\Contacts\\ContactsMenu\\IEntry' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IEntry.php',
'OCP\\Contacts\\ContactsMenu\\ILinkAction' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/ILinkAction.php',
@@ -313,6 +320,18 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Federation\\ICloudId' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudId.php',
'OCP\\Federation\\ICloudIdManager' => __DIR__ . '/../../..' . '/lib/public/Federation/ICloudIdManager.php',
'OCP\\Files' => __DIR__ . '/../../..' . '/lib/public/Files.php',
+ 'OCP\\FilesMetadata\\AMetadataEvent' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/AMetadataEvent.php',
+ 'OCP\\FilesMetadata\\Event\\MetadataBackgroundEvent' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Event/MetadataBackgroundEvent.php',
+ 'OCP\\FilesMetadata\\Event\\MetadataLiveEvent' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Event/MetadataLiveEvent.php',
+ 'OCP\\FilesMetadata\\Event\\MetadataNamedEvent' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Event/MetadataNamedEvent.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataException' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Exceptions/FilesMetadataException.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataKeyFormatException' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Exceptions/FilesMetadataKeyFormatException.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataNotFoundException' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Exceptions/FilesMetadataNotFoundException.php',
+ 'OCP\\FilesMetadata\\Exceptions\\FilesMetadataTypeException' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Exceptions/FilesMetadataTypeException.php',
+ 'OCP\\FilesMetadata\\IFilesMetadataManager' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/IFilesMetadataManager.php',
+ 'OCP\\FilesMetadata\\IMetadataQuery' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/IMetadataQuery.php',
+ 'OCP\\FilesMetadata\\Model\\IFilesMetadata' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Model/IFilesMetadata.php',
+ 'OCP\\FilesMetadata\\Model\\IMetadataValueWrapper' => __DIR__ . '/../../..' . '/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php',
'OCP\\Files\\AlreadyExistsException' => __DIR__ . '/../../..' . '/lib/public/Files/AlreadyExistsException.php',
'OCP\\Files\\AppData\\IAppDataFactory' => __DIR__ . '/../../..' . '/lib/public/Files/AppData/IAppDataFactory.php',
'OCP\\Files\\Cache\\AbstractCacheEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Cache/AbstractCacheEvent.php',
@@ -335,6 +354,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Files\\Config\\IMountProviderCollection' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IMountProviderCollection.php',
'OCP\\Files\\Config\\IRootMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IRootMountProvider.php',
'OCP\\Files\\Config\\IUserMountCache' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IUserMountCache.php',
+ 'OCP\\Files\\ConnectionLostException' => __DIR__ . '/../../..' . '/lib/public/Files/ConnectionLostException.php',
'OCP\\Files\\DavUtil' => __DIR__ . '/../../..' . '/lib/public/Files/DavUtil.php',
'OCP\\Files\\EmptyFileNameException' => __DIR__ . '/../../..' . '/lib/public/Files/EmptyFileNameException.php',
'OCP\\Files\\EntityTooLargeException' => __DIR__ . '/../../..' . '/lib/public/Files/EntityTooLargeException.php',
@@ -454,6 +474,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\GroupInterface' => __DIR__ . '/../../..' . '/lib/public/GroupInterface.php',
'OCP\\Group\\Backend\\ABackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ABackend.php',
'OCP\\Group\\Backend\\IAddToGroupBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IAddToGroupBackend.php',
+ 'OCP\\Group\\Backend\\IBatchMethodsBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/IBatchMethodsBackend.php',
'OCP\\Group\\Backend\\ICountDisabledInGroup' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ICountDisabledInGroup.php',
'OCP\\Group\\Backend\\ICountUsersBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ICountUsersBackend.php',
'OCP\\Group\\Backend\\ICreateGroupBackend' => __DIR__ . '/../../..' . '/lib/public/Group/Backend/ICreateGroupBackend.php',
@@ -516,6 +537,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\IMemcache' => __DIR__ . '/../../..' . '/lib/public/IMemcache.php',
'OCP\\IMemcacheTTL' => __DIR__ . '/../../..' . '/lib/public/IMemcacheTTL.php',
'OCP\\INavigationManager' => __DIR__ . '/../../..' . '/lib/public/INavigationManager.php',
+ 'OCP\\IPhoneNumberUtil' => __DIR__ . '/../../..' . '/lib/public/IPhoneNumberUtil.php',
'OCP\\IPreview' => __DIR__ . '/../../..' . '/lib/public/IPreview.php',
'OCP\\IRequest' => __DIR__ . '/../../..' . '/lib/public/IRequest.php',
'OCP\\IRequestId' => __DIR__ . '/../../..' . '/lib/public/IRequestId.php',
@@ -567,6 +589,12 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Notification\\IManager' => __DIR__ . '/../../..' . '/lib/public/Notification/IManager.php',
'OCP\\Notification\\INotification' => __DIR__ . '/../../..' . '/lib/public/Notification/INotification.php',
'OCP\\Notification\\INotifier' => __DIR__ . '/../../..' . '/lib/public/Notification/INotifier.php',
+ 'OCP\\OCM\\Events\\ResourceTypeRegisterEvent' => __DIR__ . '/../../..' . '/lib/public/OCM/Events/ResourceTypeRegisterEvent.php',
+ 'OCP\\OCM\\Exceptions\\OCMArgumentException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMArgumentException.php',
+ 'OCP\\OCM\\Exceptions\\OCMProviderException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMProviderException.php',
+ 'OCP\\OCM\\IOCMDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMDiscoveryService.php',
+ 'OCP\\OCM\\IOCMProvider' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMProvider.php',
+ 'OCP\\OCM\\IOCMResource' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMResource.php',
'OCP\\OCS\\IDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCS/IDiscoveryService.php',
'OCP\\PreConditionNotMetException' => __DIR__ . '/../../..' . '/lib/public/PreConditionNotMetException.php',
'OCP\\Preview\\BeforePreviewFetchedEvent' => __DIR__ . '/../../..' . '/lib/public/Preview/BeforePreviewFetchedEvent.php',
@@ -576,6 +604,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php',
'OCP\\Profile\\BeforeTemplateRenderedEvent' => __DIR__ . '/../../..' . '/lib/public/Profile/BeforeTemplateRenderedEvent.php',
'OCP\\Profile\\ILinkAction' => __DIR__ . '/../../..' . '/lib/public/Profile/ILinkAction.php',
+ 'OCP\\Profile\\IProfileManager' => __DIR__ . '/../../..' . '/lib/public/Profile/IProfileManager.php',
'OCP\\Profile\\ParameterDoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/Profile/ParameterDoesNotExistException.php',
'OCP\\Profiler\\IProfile' => __DIR__ . '/../../..' . '/lib/public/Profiler/IProfile.php',
'OCP\\Profiler\\IProfiler' => __DIR__ . '/../../..' . '/lib/public/Profiler/IProfiler.php',
@@ -594,6 +623,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Route\\IRouter' => __DIR__ . '/../../..' . '/lib/public/Route/IRouter.php',
'OCP\\SabrePluginEvent' => __DIR__ . '/../../..' . '/lib/public/SabrePluginEvent.php',
'OCP\\SabrePluginException' => __DIR__ . '/../../..' . '/lib/public/SabrePluginException.php',
+ 'OCP\\Search\\FilterDefinition' => __DIR__ . '/../../..' . '/lib/public/Search/FilterDefinition.php',
+ 'OCP\\Search\\IFilter' => __DIR__ . '/../../..' . '/lib/public/Search/IFilter.php',
+ 'OCP\\Search\\IFilterCollection' => __DIR__ . '/../../..' . '/lib/public/Search/IFilterCollection.php',
+ 'OCP\\Search\\IFilteringProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IFilteringProvider.php',
+ 'OCP\\Search\\IInAppSearch' => __DIR__ . '/../../..' . '/lib/public/Search/IInAppSearch.php',
'OCP\\Search\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IProvider.php',
'OCP\\Search\\ISearchQuery' => __DIR__ . '/../../..' . '/lib/public/Search/ISearchQuery.php',
'OCP\\Search\\PagedProvider' => __DIR__ . '/../../..' . '/lib/public/Search/PagedProvider.php',
@@ -614,6 +648,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Security\\IRemoteHostValidator' => __DIR__ . '/../../..' . '/lib/public/Security/IRemoteHostValidator.php',
'OCP\\Security\\ISecureRandom' => __DIR__ . '/../../..' . '/lib/public/Security/ISecureRandom.php',
'OCP\\Security\\ITrustedDomainHelper' => __DIR__ . '/../../..' . '/lib/public/Security/ITrustedDomainHelper.php',
+ 'OCP\\Security\\RateLimiting\\ILimiter' => __DIR__ . '/../../..' . '/lib/public/Security/RateLimiting/ILimiter.php',
+ 'OCP\\Security\\RateLimiting\\IRateLimitExceededException' => __DIR__ . '/../../..' . '/lib/public/Security/RateLimiting/IRateLimitExceededException.php',
'OCP\\Security\\VerificationToken\\IVerificationToken' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/IVerificationToken.php',
'OCP\\Security\\VerificationToken\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/InvalidTokenException.php',
'OCP\\Server' => __DIR__ . '/../../..' . '/lib/public/Server.php',
@@ -623,6 +659,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php',
'OCP\\Settings\\ISettings' => __DIR__ . '/../../..' . '/lib/public/Settings/ISettings.php',
'OCP\\Settings\\ISubAdminSettings' => __DIR__ . '/../../..' . '/lib/public/Settings/ISubAdminSettings.php',
+ 'OCP\\SetupCheck\\ISetupCheck' => __DIR__ . '/../../..' . '/lib/public/SetupCheck/ISetupCheck.php',
+ 'OCP\\SetupCheck\\ISetupCheckManager' => __DIR__ . '/../../..' . '/lib/public/SetupCheck/ISetupCheckManager.php',
+ 'OCP\\SetupCheck\\SetupResult' => __DIR__ . '/../../..' . '/lib/public/SetupCheck/SetupResult.php',
'OCP\\Share' => __DIR__ . '/../../..' . '/lib/public/Share.php',
'OCP\\Share\\Events\\BeforeShareCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/BeforeShareCreatedEvent.php',
'OCP\\Share\\Events\\BeforeShareDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/BeforeShareDeletedEvent.php',
@@ -678,14 +717,26 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\TextProcessing\\Events\\AbstractTextProcessingEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php',
'OCP\\TextProcessing\\Events\\TaskFailedEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/TaskFailedEvent.php',
'OCP\\TextProcessing\\Events\\TaskSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php',
+ 'OCP\\TextProcessing\\Exception\\TaskFailureException' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Exception/TaskFailureException.php',
'OCP\\TextProcessing\\FreePromptTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/FreePromptTaskType.php',
'OCP\\TextProcessing\\HeadlineTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/HeadlineTaskType.php',
'OCP\\TextProcessing\\IManager' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IManager.php',
'OCP\\TextProcessing\\IProvider' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProvider.php',
+ 'OCP\\TextProcessing\\IProviderWithExpectedRuntime' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithExpectedRuntime.php',
+ 'OCP\\TextProcessing\\IProviderWithUserId' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithUserId.php',
'OCP\\TextProcessing\\ITaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/ITaskType.php',
'OCP\\TextProcessing\\SummaryTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/SummaryTaskType.php',
'OCP\\TextProcessing\\Task' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Task.php',
'OCP\\TextProcessing\\TopicsTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/TopicsTaskType.php',
+ 'OCP\\TextToImage\\Events\\AbstractTextToImageEvent' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Events/AbstractTextToImageEvent.php',
+ 'OCP\\TextToImage\\Events\\TaskFailedEvent' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Events/TaskFailedEvent.php',
+ 'OCP\\TextToImage\\Events\\TaskSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Events/TaskSuccessfulEvent.php',
+ 'OCP\\TextToImage\\Exception\\TaskFailureException' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Exception/TaskFailureException.php',
+ 'OCP\\TextToImage\\Exception\\TaskNotFoundException' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Exception/TaskNotFoundException.php',
+ 'OCP\\TextToImage\\Exception\\TextToImageException' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Exception/TextToImageException.php',
+ 'OCP\\TextToImage\\IManager' => __DIR__ . '/../../..' . '/lib/public/TextToImage/IManager.php',
+ 'OCP\\TextToImage\\IProvider' => __DIR__ . '/../../..' . '/lib/public/TextToImage/IProvider.php',
+ 'OCP\\TextToImage\\Task' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Task.php',
'OCP\\Translation\\CouldNotTranslateException' => __DIR__ . '/../../..' . '/lib/public/Translation/CouldNotTranslateException.php',
'OCP\\Translation\\IDetectLanguageProvider' => __DIR__ . '/../../..' . '/lib/public/Translation/IDetectLanguageProvider.php',
'OCP\\Translation\\ITranslationManager' => __DIR__ . '/../../..' . '/lib/public/Translation/ITranslationManager.php',
@@ -722,6 +773,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\User\\Events\\BeforeUserLoggedInEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeChangedEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeClearedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeClearedEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
'OCP\\User\\Events\\PasswordUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PasswordUpdatedEvent.php',
'OCP\\User\\Events\\PostLoginEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PostLoginEvent.php',
'OCP\\User\\Events\\UserChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserChangedEvent.php',
@@ -733,6 +787,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\UserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedOutEvent.php',
'OCP\\User\\GetQuotaEvent' => __DIR__ . '/../../..' . '/lib/public/User/GetQuotaEvent.php',
+ 'OCP\\User\\IAvailabilityCoordinator' => __DIR__ . '/../../..' . '/lib/public/User/IAvailabilityCoordinator.php',
+ 'OCP\\User\\IOutOfOfficeData' => __DIR__ . '/../../..' . '/lib/public/User/IOutOfOfficeData.php',
'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php',
'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php',
@@ -980,6 +1036,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CheckForUserCertificates.php',
'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CleanupLoginFlowV2.php',
+ 'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/GenerateMetadataJob.php',
'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php',
'OC\\Core\\Command\\App\\Disable' => __DIR__ . '/../../..' . '/core/Command/App/Disable.php',
'OC\\Core\\Command\\App\\Enable' => __DIR__ . '/../../..' . '/core/Command/App/Enable.php',
@@ -1027,6 +1084,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Command\\Encryption\\SetDefaultModule' => __DIR__ . '/../../..' . '/core/Command/Encryption/SetDefaultModule.php',
'OC\\Core\\Command\\Encryption\\ShowKeyStorageRoot' => __DIR__ . '/../../..' . '/core/Command/Encryption/ShowKeyStorageRoot.php',
'OC\\Core\\Command\\Encryption\\Status' => __DIR__ . '/../../..' . '/core/Command/Encryption/Status.php',
+ 'OC\\Core\\Command\\FilesMetadata\\Get' => __DIR__ . '/../../..' . '/core/Command/FilesMetadata/Get.php',
'OC\\Core\\Command\\Group\\Add' => __DIR__ . '/../../..' . '/core/Command/Group/Add.php',
'OC\\Core\\Command\\Group\\AddUser' => __DIR__ . '/../../..' . '/core/Command/Group/AddUser.php',
'OC\\Core\\Command\\Group\\Delete' => __DIR__ . '/../../..' . '/core/Command/Group/Delete.php',
@@ -1062,6 +1120,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Command\\Security\\ImportCertificate' => __DIR__ . '/../../..' . '/core/Command/Security/ImportCertificate.php',
'OC\\Core\\Command\\Security\\ListCertificates' => __DIR__ . '/../../..' . '/core/Command/Security/ListCertificates.php',
'OC\\Core\\Command\\Security\\RemoveCertificate' => __DIR__ . '/../../..' . '/core/Command/Security/RemoveCertificate.php',
+ 'OC\\Core\\Command\\SetupChecks' => __DIR__ . '/../../..' . '/core/Command/SetupChecks.php',
'OC\\Core\\Command\\Status' => __DIR__ . '/../../..' . '/core/Command/Status.php',
'OC\\Core\\Command\\SystemTag\\Add' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Add.php',
'OC\\Core\\Command\\SystemTag\\Delete' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Delete.php',
@@ -1105,6 +1164,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Controller\\LostController' => __DIR__ . '/../../..' . '/core/Controller/LostController.php',
'OC\\Core\\Controller\\NavigationController' => __DIR__ . '/../../..' . '/core/Controller/NavigationController.php',
'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php',
+ 'OC\\Core\\Controller\\OCMController' => __DIR__ . '/../../..' . '/core/Controller/OCMController.php',
'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php',
'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php',
'OC\\Core\\Controller\\ProfileApiController' => __DIR__ . '/../../..' . '/core/Controller/ProfileApiController.php',
@@ -1115,6 +1175,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php',
'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php',
'OC\\Core\\Controller\\TextProcessingApiController' => __DIR__ . '/../../..' . '/core/Controller/TextProcessingApiController.php',
+ 'OC\\Core\\Controller\\TextToImageApiController' => __DIR__ . '/../../..' . '/core/Controller/TextToImageApiController.php',
'OC\\Core\\Controller\\TranslationApiController' => __DIR__ . '/../../..' . '/core/Controller/TranslationApiController.php',
'OC\\Core\\Controller\\TwoFactorChallengeController' => __DIR__ . '/../../..' . '/core/Controller/TwoFactorChallengeController.php',
'OC\\Core\\Controller\\UnifiedSearchController' => __DIR__ . '/../../..' . '/core/Controller/UnifiedSearchController.php',
@@ -1196,6 +1257,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Migrations\\Version28000Date20230616104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230616104802.php',
'OC\\Core\\Migrations\\Version28000Date20230728104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230728104802.php',
'OC\\Core\\Migrations\\Version28000Date20230803221055' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230803221055.php',
+ 'OC\\Core\\Migrations\\Version28000Date20230906104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230906104802.php',
+ 'OC\\Core\\Migrations\\Version28000Date20231004103301' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20231004103301.php',
+ 'OC\\Core\\Migrations\\Version28000Date20231103104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20231103104802.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
@@ -1216,14 +1280,12 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\DB\\MissingColumnInformation' => __DIR__ . '/../../..' . '/lib/private/DB/MissingColumnInformation.php',
'OC\\DB\\MissingIndexInformation' => __DIR__ . '/../../..' . '/lib/private/DB/MissingIndexInformation.php',
'OC\\DB\\MissingPrimaryKeyInformation' => __DIR__ . '/../../..' . '/lib/private/DB/MissingPrimaryKeyInformation.php',
- 'OC\\DB\\MySQLMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/MySQLMigrator.php',
'OC\\DB\\MySqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/MySqlTools.php',
'OC\\DB\\OCSqlitePlatform' => __DIR__ . '/../../..' . '/lib/private/DB/OCSqlitePlatform.php',
'OC\\DB\\ObjectParameter' => __DIR__ . '/../../..' . '/lib/private/DB/ObjectParameter.php',
'OC\\DB\\OracleConnection' => __DIR__ . '/../../..' . '/lib/private/DB/OracleConnection.php',
'OC\\DB\\OracleMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/OracleMigrator.php',
'OC\\DB\\PgSqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/PgSqlTools.php',
- 'OC\\DB\\PostgreSqlMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/PostgreSqlMigrator.php',
'OC\\DB\\PreparedStatement' => __DIR__ . '/../../..' . '/lib/private/DB/PreparedStatement.php',
'OC\\DB\\QueryBuilder\\CompositeExpression' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/CompositeExpression.php',
'OC\\DB\\QueryBuilder\\ExpressionBuilder\\ExpressionBuilder' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php',
@@ -1282,6 +1344,15 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Federation\\CloudFederationShare' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudFederationShare.php',
'OC\\Federation\\CloudId' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudId.php',
'OC\\Federation\\CloudIdManager' => __DIR__ . '/../../..' . '/lib/private/Federation/CloudIdManager.php',
+ 'OC\\FilesMetadata\\FilesMetadataManager' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/FilesMetadataManager.php',
+ 'OC\\FilesMetadata\\Job\\UpdateSingleMetadata' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php',
+ 'OC\\FilesMetadata\\Listener\\MetadataDelete' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/Listener/MetadataDelete.php',
+ 'OC\\FilesMetadata\\Listener\\MetadataUpdate' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/Listener/MetadataUpdate.php',
+ 'OC\\FilesMetadata\\MetadataQuery' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/MetadataQuery.php',
+ 'OC\\FilesMetadata\\Model\\FilesMetadata' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/Model/FilesMetadata.php',
+ 'OC\\FilesMetadata\\Model\\MetadataValueWrapper' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/Model/MetadataValueWrapper.php',
+ 'OC\\FilesMetadata\\Service\\IndexRequestService' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/Service/IndexRequestService.php',
+ 'OC\\FilesMetadata\\Service\\MetadataRequestService' => __DIR__ . '/../../..' . '/lib/private/FilesMetadata/Service/MetadataRequestService.php',
'OC\\Files\\AppData\\AppData' => __DIR__ . '/../../..' . '/lib/private/Files/AppData/AppData.php',
'OC\\Files\\AppData\\Factory' => __DIR__ . '/../../..' . '/lib/private/Files/AppData/Factory.php',
'OC\\Files\\Cache\\Cache' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Cache.php',
@@ -1315,6 +1386,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Files\\Filesystem' => __DIR__ . '/../../..' . '/lib/private/Files/Filesystem.php',
'OC\\Files\\Lock\\LockManager' => __DIR__ . '/../../..' . '/lib/private/Files/Lock/LockManager.php',
'OC\\Files\\Mount\\CacheMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/CacheMountProvider.php',
+ 'OC\\Files\\Mount\\HomeMountPoint' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/HomeMountPoint.php',
'OC\\Files\\Mount\\LocalHomeMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/LocalHomeMountProvider.php',
'OC\\Files\\Mount\\Manager' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/Manager.php',
'OC\\Files\\Mount\\MountPoint' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/MountPoint.php',
@@ -1377,6 +1449,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Files\\Storage\\Wrapper\\EncodingDirectoryWrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php',
'OC\\Files\\Storage\\Wrapper\\Encryption' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Encryption.php',
'OC\\Files\\Storage\\Wrapper\\Jail' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Jail.php',
+ 'OC\\Files\\Storage\\Wrapper\\KnownMtime' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/KnownMtime.php',
'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php',
'OC\\Files\\Storage\\Wrapper\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Quota.php',
'OC\\Files\\Storage\\Wrapper\\Wrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Wrapper.php',
@@ -1473,14 +1546,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Memcache\\Redis' => __DIR__ . '/../../..' . '/lib/private/Memcache/Redis.php',
'OC\\Memcache\\WithLocalCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/WithLocalCache.php',
'OC\\MemoryInfo' => __DIR__ . '/../../..' . '/lib/private/MemoryInfo.php',
- 'OC\\Metadata\\Capabilities' => __DIR__ . '/../../..' . '/lib/private/Metadata/Capabilities.php',
- 'OC\\Metadata\\FileEventListener' => __DIR__ . '/../../..' . '/lib/private/Metadata/FileEventListener.php',
- 'OC\\Metadata\\FileMetadata' => __DIR__ . '/../../..' . '/lib/private/Metadata/FileMetadata.php',
- 'OC\\Metadata\\FileMetadataMapper' => __DIR__ . '/../../..' . '/lib/private/Metadata/FileMetadataMapper.php',
- 'OC\\Metadata\\IMetadataManager' => __DIR__ . '/../../..' . '/lib/private/Metadata/IMetadataManager.php',
- 'OC\\Metadata\\IMetadataProvider' => __DIR__ . '/../../..' . '/lib/private/Metadata/IMetadataProvider.php',
- 'OC\\Metadata\\MetadataManager' => __DIR__ . '/../../..' . '/lib/private/Metadata/MetadataManager.php',
- 'OC\\Metadata\\Provider\\ExifProvider' => __DIR__ . '/../../..' . '/lib/private/Metadata/Provider/ExifProvider.php',
'OC\\Migration\\BackgroundRepair' => __DIR__ . '/../../..' . '/lib/private/Migration/BackgroundRepair.php',
'OC\\Migration\\ConsoleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/ConsoleOutput.php',
'OC\\Migration\\SimpleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/SimpleOutput.php',
@@ -1494,17 +1559,22 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Notification\\Action' => __DIR__ . '/../../..' . '/lib/private/Notification/Action.php',
'OC\\Notification\\Manager' => __DIR__ . '/../../..' . '/lib/private/Notification/Manager.php',
'OC\\Notification\\Notification' => __DIR__ . '/../../..' . '/lib/private/Notification/Notification.php',
+ 'OC\\OCM\\Model\\OCMProvider' => __DIR__ . '/../../..' . '/lib/private/OCM/Model/OCMProvider.php',
+ 'OC\\OCM\\Model\\OCMResource' => __DIR__ . '/../../..' . '/lib/private/OCM/Model/OCMResource.php',
+ 'OC\\OCM\\OCMDiscoveryService' => __DIR__ . '/../../..' . '/lib/private/OCM/OCMDiscoveryService.php',
'OC\\OCS\\CoreCapabilities' => __DIR__ . '/../../..' . '/lib/private/OCS/CoreCapabilities.php',
'OC\\OCS\\DiscoveryService' => __DIR__ . '/../../..' . '/lib/private/OCS/DiscoveryService.php',
'OC\\OCS\\Exception' => __DIR__ . '/../../..' . '/lib/private/OCS/Exception.php',
'OC\\OCS\\Provider' => __DIR__ . '/../../..' . '/lib/private/OCS/Provider.php',
'OC\\OCS\\Result' => __DIR__ . '/../../..' . '/lib/private/OCS/Result.php',
+ 'OC\\PhoneNumberUtil' => __DIR__ . '/../../..' . '/lib/private/PhoneNumberUtil.php',
'OC\\PreviewManager' => __DIR__ . '/../../..' . '/lib/private/PreviewManager.php',
'OC\\PreviewNotAvailableException' => __DIR__ . '/../../..' . '/lib/private/PreviewNotAvailableException.php',
'OC\\Preview\\BMP' => __DIR__ . '/../../..' . '/lib/private/Preview/BMP.php',
'OC\\Preview\\BackgroundCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Preview/BackgroundCleanupJob.php',
'OC\\Preview\\Bitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/Bitmap.php',
'OC\\Preview\\Bundled' => __DIR__ . '/../../..' . '/lib/private/Preview/Bundled.php',
+ 'OC\\Preview\\EMF' => __DIR__ . '/../../..' . '/lib/private/Preview/EMF.php',
'OC\\Preview\\Font' => __DIR__ . '/../../..' . '/lib/private/Preview/Font.php',
'OC\\Preview\\GIF' => __DIR__ . '/../../..' . '/lib/private/Preview/GIF.php',
'OC\\Preview\\Generator' => __DIR__ . '/../../..' . '/lib/private/Preview/Generator.php',
@@ -1568,6 +1638,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\RepairException' => __DIR__ . '/../../..' . '/lib/private/RepairException.php',
'OC\\Repair\\AddBruteForceCleanupJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddBruteForceCleanupJob.php',
'OC\\Repair\\AddCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddCleanupUpdaterBackupsJob.php',
+ 'OC\\Repair\\AddMetadataGenerationJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddMetadataGenerationJob.php',
'OC\\Repair\\AddRemoveOldTasksBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php',
'OC\\Repair\\CleanTags' => __DIR__ . '/../../..' . '/lib/private/Repair/CleanTags.php',
'OC\\Repair\\CleanUpAbandonedApps' => __DIR__ . '/../../..' . '/lib/private/Repair/CleanUpAbandonedApps.php',
@@ -1611,12 +1682,21 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Repair\\RepairDavShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairDavShares.php',
'OC\\Repair\\RepairInvalidShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairInvalidShares.php',
'OC\\Repair\\RepairMimeTypes' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairMimeTypes.php',
- 'OC\\Repair\\SqliteAutoincrement' => __DIR__ . '/../../..' . '/lib/private/Repair/SqliteAutoincrement.php',
'OC\\RichObjectStrings\\Validator' => __DIR__ . '/../../..' . '/lib/private/RichObjectStrings/Validator.php',
'OC\\Route\\CachingRouter' => __DIR__ . '/../../..' . '/lib/private/Route/CachingRouter.php',
'OC\\Route\\Route' => __DIR__ . '/../../..' . '/lib/private/Route/Route.php',
'OC\\Route\\Router' => __DIR__ . '/../../..' . '/lib/private/Route/Router.php',
'OC\\Search' => __DIR__ . '/../../..' . '/lib/private/Search.php',
+ 'OC\\Search\\FilterCollection' => __DIR__ . '/../../..' . '/lib/private/Search/FilterCollection.php',
+ 'OC\\Search\\FilterFactory' => __DIR__ . '/../../..' . '/lib/private/Search/FilterFactory.php',
+ 'OC\\Search\\Filter\\BooleanFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/BooleanFilter.php',
+ 'OC\\Search\\Filter\\DateTimeFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/DateTimeFilter.php',
+ 'OC\\Search\\Filter\\FloatFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/FloatFilter.php',
+ 'OC\\Search\\Filter\\GroupFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/GroupFilter.php',
+ 'OC\\Search\\Filter\\IntegerFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/IntegerFilter.php',
+ 'OC\\Search\\Filter\\StringFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/StringFilter.php',
+ 'OC\\Search\\Filter\\StringsFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/StringsFilter.php',
+ 'OC\\Search\\Filter\\UserFilter' => __DIR__ . '/../../..' . '/lib/private/Search/Filter/UserFilter.php',
'OC\\Search\\Provider\\File' => __DIR__ . '/../../..' . '/lib/private/Search/Provider/File.php',
'OC\\Search\\Result\\Audio' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Audio.php',
'OC\\Search\\Result\\File' => __DIR__ . '/../../..' . '/lib/private/Search/Result/File.php',
@@ -1624,6 +1704,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Search\\Result\\Image' => __DIR__ . '/../../..' . '/lib/private/Search/Result/Image.php',
'OC\\Search\\SearchComposer' => __DIR__ . '/../../..' . '/lib/private/Search/SearchComposer.php',
'OC\\Search\\SearchQuery' => __DIR__ . '/../../..' . '/lib/private/Search/SearchQuery.php',
+ 'OC\\Search\\UnsupportedFilter' => __DIR__ . '/../../..' . '/lib/private/Search/UnsupportedFilter.php',
'OC\\Security\\Bruteforce\\Backend\\DatabaseBackend' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Backend/DatabaseBackend.php',
'OC\\Security\\Bruteforce\\Backend\\IBackend' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Backend/IBackend.php',
'OC\\Security\\Bruteforce\\Backend\\MemoryCacheBackend' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Backend/MemoryCacheBackend.php',
@@ -1672,6 +1753,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Settings\\Manager' => __DIR__ . '/../../..' . '/lib/private/Settings/Manager.php',
'OC\\Settings\\Section' => __DIR__ . '/../../..' . '/lib/private/Settings/Section.php',
'OC\\Setup' => __DIR__ . '/../../..' . '/lib/private/Setup.php',
+ 'OC\\SetupCheck\\SetupCheckManager' => __DIR__ . '/../../..' . '/lib/private/SetupCheck/SetupCheckManager.php',
'OC\\Setup\\AbstractDatabase' => __DIR__ . '/../../..' . '/lib/private/Setup/AbstractDatabase.php',
'OC\\Setup\\MySQL' => __DIR__ . '/../../..' . '/lib/private/Setup/MySQL.php',
'OC\\Setup\\OCI' => __DIR__ . '/../../..' . '/lib/private/Setup/OCI.php',
@@ -1688,6 +1770,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Share20\\PublicShareTemplateFactory' => __DIR__ . '/../../..' . '/lib/private/Share20/PublicShareTemplateFactory.php',
'OC\\Share20\\Share' => __DIR__ . '/../../..' . '/lib/private/Share20/Share.php',
'OC\\Share20\\ShareAttributes' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareAttributes.php',
+ 'OC\\Share20\\ShareDisableChecker' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareDisableChecker.php',
'OC\\Share20\\ShareHelper' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareHelper.php',
'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php',
'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php',
@@ -1728,6 +1811,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\TextProcessing\\Manager' => __DIR__ . '/../../..' . '/lib/private/TextProcessing/Manager.php',
'OC\\TextProcessing\\RemoveOldTasksBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/TextProcessing/RemoveOldTasksBackgroundJob.php',
'OC\\TextProcessing\\TaskBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/TextProcessing/TaskBackgroundJob.php',
+ 'OC\\TextToImage\\Db\\Task' => __DIR__ . '/../../..' . '/lib/private/TextToImage/Db/Task.php',
+ 'OC\\TextToImage\\Db\\TaskMapper' => __DIR__ . '/../../..' . '/lib/private/TextToImage/Db/TaskMapper.php',
+ 'OC\\TextToImage\\Manager' => __DIR__ . '/../../..' . '/lib/private/TextToImage/Manager.php',
+ 'OC\\TextToImage\\RemoveOldTasksBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php',
+ 'OC\\TextToImage\\TaskBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/TextToImage/TaskBackgroundJob.php',
'OC\\Translation\\TranslationManager' => __DIR__ . '/../../..' . '/lib/private/Translation/TranslationManager.php',
'OC\\URLGenerator' => __DIR__ . '/../../..' . '/lib/private/URLGenerator.php',
'OC\\Updater' => __DIR__ . '/../../..' . '/lib/private/Updater.php',
@@ -1737,6 +1825,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php',
'OC\\UserStatus\\ISettableProvider' => __DIR__ . '/../../..' . '/lib/private/UserStatus/ISettableProvider.php',
'OC\\UserStatus\\Manager' => __DIR__ . '/../../..' . '/lib/private/UserStatus/Manager.php',
+ 'OC\\User\\AvailabilityCoordinator' => __DIR__ . '/../../..' . '/lib/private/User/AvailabilityCoordinator.php',
'OC\\User\\Backend' => __DIR__ . '/../../..' . '/lib/private/User/Backend.php',
'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php',
'OC\\User\\DisplayNameCache' => __DIR__ . '/../../..' . '/lib/private/User/DisplayNameCache.php',
@@ -1746,6 +1835,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\User\\LoginException' => __DIR__ . '/../../..' . '/lib/private/User/LoginException.php',
'OC\\User\\Manager' => __DIR__ . '/../../..' . '/lib/private/User/Manager.php',
'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php',
+ 'OC\\User\\OutOfOfficeData' => __DIR__ . '/../../..' . '/lib/private/User/OutOfOfficeData.php',
'OC\\User\\Session' => __DIR__ . '/../../..' . '/lib/private/User/Session.php',
'OC\\User\\User' => __DIR__ . '/../../..' . '/lib/private/User/User.php',
'OC_API' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_API.php',
diff --git a/lib/composer/composer/installed.php b/lib/composer/composer/installed.php
index 1f382499aeb..7115927cba0 100644
--- a/lib/composer/composer/installed.php
+++ b/lib/composer/composer/installed.php
@@ -1,22 +1,22 @@
<?php return array(
'root' => array(
- 'pretty_version' => '1.0.0+no-version-set',
- 'version' => '1.0.0.0',
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '41d274cd58f168047eb6a7673a7e43fff69ac07f',
'type' => 'library',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
- 'reference' => NULL,
- 'name' => '__root__',
'dev' => false,
),
'versions' => array(
'__root__' => array(
- 'pretty_version' => '1.0.0+no-version-set',
- 'version' => '1.0.0.0',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '41d274cd58f168047eb6a7673a7e43fff69ac07f',
'type' => 'library',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
- 'reference' => NULL,
'dev_requirement' => false,
),
),
diff --git a/lib/l10n/ar.js b/lib/l10n/ar.js
index bc043479d2f..8e7dfd09696 100644
--- a/lib/l10n/ar.js
+++ b/lib/l10n/ar.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "التطبيق %1$s غير موجود أو ليس له إصدار متطابق مع هذا الخادوم. رجاءً، راجع دليل التطبيقات apps directory.",
"Sample configuration detected" : "تمّ العثور على عيّنة إعدادات sample configuration.",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "تمّ اكتشاف أن عيّنة الإعدادات قد تمّ نسخها. يُمكن لهذا أن يُعطّل عملية التنصيب و هو أمر غير مدعوم. نرجو الاطلاع على التعليمات الواردة في وثائق النظام قبل إحداث أي تعديلات على ملف config.php",
- "404" : "404",
"The page could not be found on the server." : "تعذّر العثور على الصفحة في الخادوم",
"%s email verification" : "%s التحقّق من الإيميل",
"Email verification" : "التحقّق من الإيميل",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "نحتاج النسخة 2.7.0 من libxml2 على الأقل. النسخة المتوافرة حالياً هي %s",
"To fix this issue update your libxml2 version and restart your web server." : "لإصلاح هذه المشكلة، قم بتحديث إصدار libxml2 الخاص بك وأعد تشغيل خادم الويب.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 مطلوبة.",
- "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك."
+ "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك.",
+ "404" : "404"
},
"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;");
diff --git a/lib/l10n/ar.json b/lib/l10n/ar.json
index 5ac53142dee..abffc79003c 100644
--- a/lib/l10n/ar.json
+++ b/lib/l10n/ar.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "التطبيق %1$s غير موجود أو ليس له إصدار متطابق مع هذا الخادوم. رجاءً، راجع دليل التطبيقات apps directory.",
"Sample configuration detected" : "تمّ العثور على عيّنة إعدادات sample configuration.",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "تمّ اكتشاف أن عيّنة الإعدادات قد تمّ نسخها. يُمكن لهذا أن يُعطّل عملية التنصيب و هو أمر غير مدعوم. نرجو الاطلاع على التعليمات الواردة في وثائق النظام قبل إحداث أي تعديلات على ملف config.php",
- "404" : "404",
"The page could not be found on the server." : "تعذّر العثور على الصفحة في الخادوم",
"%s email verification" : "%s التحقّق من الإيميل",
"Email verification" : "التحقّق من الإيميل",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "نحتاج النسخة 2.7.0 من libxml2 على الأقل. النسخة المتوافرة حالياً هي %s",
"To fix this issue update your libxml2 version and restart your web server." : "لإصلاح هذه المشكلة، قم بتحديث إصدار libxml2 الخاص بك وأعد تشغيل خادم الويب.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 مطلوبة.",
- "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك."
+ "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك.",
+ "404" : "404"
},"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"
} \ No newline at end of file
diff --git a/lib/l10n/ast.js b/lib/l10n/ast.js
index 330901f03c3..7635cc90e60 100644
--- a/lib/l10n/ast.js
+++ b/lib/l10n/ast.js
@@ -69,6 +69,8 @@ OC.L10N.register(
"a safe home for all your data" : "un llugar seguru pa los datos personales",
"Application is not enabled" : "L'aplicación nun ta activada",
"Please ask your server administrator to install the module." : "Pidi a l'alministración del sirvidor qu'instale'l módulu.",
- "Full name" : "Nome completu"
+ "Full name" : "Nome completu",
+ "To fix this issue update your libxml2 version and restart your web server." : "Pa iguar esti problema, anueva la versión de libxml2 y reanicia'l sirvidor web.",
+ "Please upgrade your database version." : "Anueva la versión de la base de datos."
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/ast.json b/lib/l10n/ast.json
index 5780dd94141..af224611393 100644
--- a/lib/l10n/ast.json
+++ b/lib/l10n/ast.json
@@ -67,6 +67,8 @@
"a safe home for all your data" : "un llugar seguru pa los datos personales",
"Application is not enabled" : "L'aplicación nun ta activada",
"Please ask your server administrator to install the module." : "Pidi a l'alministración del sirvidor qu'instale'l módulu.",
- "Full name" : "Nome completu"
+ "Full name" : "Nome completu",
+ "To fix this issue update your libxml2 version and restart your web server." : "Pa iguar esti problema, anueva la versión de libxml2 y reanicia'l sirvidor web.",
+ "Please upgrade your database version." : "Anueva la versión de la base de datos."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/bg.js b/lib/l10n/bg.js
index a6532e5be26..9603dd7adc0 100644
--- a/lib/l10n/bg.js
+++ b/lib/l10n/bg.js
@@ -7,7 +7,6 @@ OC.L10N.register(
"See %s" : "Вижте %s",
"Sample configuration detected" : "Открита е примерна конфигурация",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Усетено беше че примерната конфигурация е копирана. Това може да развли инсталацията ти и не се поддържа. Моля, прочети документацията преди да правиш промени на config.php",
- "404" : "404",
"The page could not be found on the server." : "Страницата не е намерена на сървъра.",
"%s email verification" : "%s имейл потвърждение",
"Email verification" : "Имейл потвърждение",
@@ -267,6 +266,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Нужно е поне libxml2 2.7.0. В момента са инсталирани %s.",
"To fix this issue update your libxml2 version and restart your web server." : "За да отстраните този проблем, актуализирайте версията на libxml2 и рестартирайте вашия уеб сървър.",
"PostgreSQL >= 9 required." : "Нужно е PostgreSQL >= 9",
- "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни."
+ "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/bg.json b/lib/l10n/bg.json
index 6bc6ae317c6..ef36ae9d24c 100644
--- a/lib/l10n/bg.json
+++ b/lib/l10n/bg.json
@@ -5,7 +5,6 @@
"See %s" : "Вижте %s",
"Sample configuration detected" : "Открита е примерна конфигурация",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Усетено беше че примерната конфигурация е копирана. Това може да развли инсталацията ти и не се поддържа. Моля, прочети документацията преди да правиш промени на config.php",
- "404" : "404",
"The page could not be found on the server." : "Страницата не е намерена на сървъра.",
"%s email verification" : "%s имейл потвърждение",
"Email verification" : "Имейл потвърждение",
@@ -265,6 +264,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Нужно е поне libxml2 2.7.0. В момента са инсталирани %s.",
"To fix this issue update your libxml2 version and restart your web server." : "За да отстраните този проблем, актуализирайте версията на libxml2 и рестартирайте вашия уеб сървър.",
"PostgreSQL >= 9 required." : "Нужно е PostgreSQL >= 9",
- "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни."
+ "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/ca.js b/lib/l10n/ca.js
index 421f7e69652..6b2fc3a193e 100644
--- a/lib/l10n/ca.js
+++ b/lib/l10n/ca.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Falta l'aplicació %1$s o té una versió no compatible amb aquest servidor. Comproveu la carpeta d'aplicacions.",
"Sample configuration detected" : "S'ha detectat una configuració d'exemple",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S'ha detectat que s'ha copiat la configuració d'exemple. Això no s'admet i pot malmetre la instal·lació. Llegiu la documentació abans d'aplicar cap canvi al fitxer config.php",
- "404" : "404",
"The page could not be found on the server." : "No s'ha pogut trobar la pàgina en el servidor.",
"%s email verification" : "Verificació de l'adreça electrònica del %s",
"Email verification" : "Verificació de l'adreça electrònica",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Cal almenys libxml2 2.7.0. Actualment s'ha instal·lat %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Per a resoldre aquest problema, actualitzeu la versió de libxml2 i reinicieu el servidor web.",
"PostgreSQL >= 9 required." : "Cal el PostgreSQL >= 9.",
- "Please upgrade your database version." : "Actualitzeu la versió de la base de dades."
+ "Please upgrade your database version." : "Actualitzeu la versió de la base de dades.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/ca.json b/lib/l10n/ca.json
index e18f5be8c9d..54c1f2bac8b 100644
--- a/lib/l10n/ca.json
+++ b/lib/l10n/ca.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Falta l'aplicació %1$s o té una versió no compatible amb aquest servidor. Comproveu la carpeta d'aplicacions.",
"Sample configuration detected" : "S'ha detectat una configuració d'exemple",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S'ha detectat que s'ha copiat la configuració d'exemple. Això no s'admet i pot malmetre la instal·lació. Llegiu la documentació abans d'aplicar cap canvi al fitxer config.php",
- "404" : "404",
"The page could not be found on the server." : "No s'ha pogut trobar la pàgina en el servidor.",
"%s email verification" : "Verificació de l'adreça electrònica del %s",
"Email verification" : "Verificació de l'adreça electrònica",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Cal almenys libxml2 2.7.0. Actualment s'ha instal·lat %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Per a resoldre aquest problema, actualitzeu la versió de libxml2 i reinicieu el servidor web.",
"PostgreSQL >= 9 required." : "Cal el PostgreSQL >= 9.",
- "Please upgrade your database version." : "Actualitzeu la versió de la base de dades."
+ "Please upgrade your database version." : "Actualitzeu la versió de la base de dades.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/cs.js b/lib/l10n/cs.js
index 422c9e0cee5..8351155d3b6 100644
--- a/lib/l10n/cs.js
+++ b/lib/l10n/cs.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplikace %1$s není přítomná nebo její verze není kompatibilní s tímto serverem. Zkontrolujte složku s aplikacemi. ",
"Sample configuration detected" : "Bylo zjištěno setrvání u předváděcího nastavení",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pravděpodobně byla zkopírována nastavení ze vzorových souborů. Toto není podporováno a může poškodit vaši instalaci. Před prováděním změn v souboru config.php si přečtěte dokumentaci",
- "404" : "404",
"The page could not be found on the server." : "Stránka nebyla na serveru nalezena.",
"%s email verification" : "%s ověřování e-mailem",
"Email verification" : "Ověřování e-mailem",
@@ -137,7 +136,7 @@ OC.L10N.register(
"Set an admin password." : "Nastavte heslo pro účet správce.",
"Cannot create or write into the data directory %s" : "Nedaří se vytvořit nebo zapisovat do datového adresáře %s",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "Je třeba, aby podpůrná vrstva pro sdílení %s implementovala rozhraní OCP\\Share_Backend",
- "Sharing backend %s not found" : "Úložiště sdílení %s nenalezeno",
+ "Sharing backend %s not found" : "Podpůrná vrstva pro sdílení %s nenalezena",
"Sharing backend for %s not found" : "Úložiště sdílení pro %s nenalezeno",
"%1$s shared »%2$s« with you and wants to add:" : "%1$s sdílí „%2$s“ a dodává:",
"%1$s shared »%2$s« with you and wants to add" : "%1$s sdílí „%2$s“ a dodává",
@@ -156,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$s vám nasdílel(a) „%2$s“.",
"Click the button below to open it." : "Pro otevření klikněte na tlačítko níže.",
"The requested share does not exist anymore" : "Požadované sdílení už neexistuje",
+ "The requested share comes from a disabled user" : "Požadované sdílení pochází od vypnutého uživatelského účtu",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Uživatel nebyl vytvořen protože bylo dosaženo limitu počtu uživatelů. Více se dozvíte v upozorněních.",
"Could not find category \"%s\"" : "Nedaří se nalézt kategorii „%s“",
"Sunday" : "neděle",
@@ -240,7 +240,7 @@ OC.L10N.register(
"To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pro nápravu nastavte v souboru php.ini parametr <code>mbstring.func_overload</code> na <code>0</code>.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je patrně nastaveno tak, aby odstraňovalo bloky komentářů. Toto bude mít za následek znepřístupnění mnoha důležitých aplikací.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Toto je pravděpodobně způsobeno aplikacemi pro urychlení načítání jako jsou Zend OPcache nebo eAccelerator.",
- "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, ale stále se tváří jako chybějící?",
+ "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, přesto jsou uváděny jako chybějící?",
"Please ask your server administrator to restart the web server." : "Požádejte správce serveru, který využíváte o restart webového serveru.",
"The required %s config variable is not configured in the config.php file." : "Požadovaná proměnná nastavení %s není v souboru s nastaveními config.php nastavena.",
"Please ask your server administrator to check the Nextcloud configuration." : "Požádejte správce serveru, který využíváte, aby zkontroloval nastavení serveru.",
@@ -276,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Je zapotřebí verze softwarové knihovny libxml2 přinejmenším 2.7.0. Nyní je nainstalována verze %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Tento problém opravíte instalací novější verze knihovny libxml2 a restartem webového serveru.",
"PostgreSQL >= 9 required." : "Je vyžadováno PostgreSQL verze 9 a novější.",
- "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze."
+ "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze.",
+ "404" : "404"
},
"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;");
diff --git a/lib/l10n/cs.json b/lib/l10n/cs.json
index 164c56f7e47..f0381ab1a60 100644
--- a/lib/l10n/cs.json
+++ b/lib/l10n/cs.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplikace %1$s není přítomná nebo její verze není kompatibilní s tímto serverem. Zkontrolujte složku s aplikacemi. ",
"Sample configuration detected" : "Bylo zjištěno setrvání u předváděcího nastavení",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Pravděpodobně byla zkopírována nastavení ze vzorových souborů. Toto není podporováno a může poškodit vaši instalaci. Před prováděním změn v souboru config.php si přečtěte dokumentaci",
- "404" : "404",
"The page could not be found on the server." : "Stránka nebyla na serveru nalezena.",
"%s email verification" : "%s ověřování e-mailem",
"Email verification" : "Ověřování e-mailem",
@@ -135,7 +134,7 @@
"Set an admin password." : "Nastavte heslo pro účet správce.",
"Cannot create or write into the data directory %s" : "Nedaří se vytvořit nebo zapisovat do datového adresáře %s",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "Je třeba, aby podpůrná vrstva pro sdílení %s implementovala rozhraní OCP\\Share_Backend",
- "Sharing backend %s not found" : "Úložiště sdílení %s nenalezeno",
+ "Sharing backend %s not found" : "Podpůrná vrstva pro sdílení %s nenalezena",
"Sharing backend for %s not found" : "Úložiště sdílení pro %s nenalezeno",
"%1$s shared »%2$s« with you and wants to add:" : "%1$s sdílí „%2$s“ a dodává:",
"%1$s shared »%2$s« with you and wants to add" : "%1$s sdílí „%2$s“ a dodává",
@@ -154,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$s vám nasdílel(a) „%2$s“.",
"Click the button below to open it." : "Pro otevření klikněte na tlačítko níže.",
"The requested share does not exist anymore" : "Požadované sdílení už neexistuje",
+ "The requested share comes from a disabled user" : "Požadované sdílení pochází od vypnutého uživatelského účtu",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Uživatel nebyl vytvořen protože bylo dosaženo limitu počtu uživatelů. Více se dozvíte v upozorněních.",
"Could not find category \"%s\"" : "Nedaří se nalézt kategorii „%s“",
"Sunday" : "neděle",
@@ -238,7 +238,7 @@
"To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pro nápravu nastavte v souboru php.ini parametr <code>mbstring.func_overload</code> na <code>0</code>.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je patrně nastaveno tak, aby odstraňovalo bloky komentářů. Toto bude mít za následek znepřístupnění mnoha důležitých aplikací.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Toto je pravděpodobně způsobeno aplikacemi pro urychlení načítání jako jsou Zend OPcache nebo eAccelerator.",
- "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, ale stále se tváří jako chybějící?",
+ "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, přesto jsou uváděny jako chybějící?",
"Please ask your server administrator to restart the web server." : "Požádejte správce serveru, který využíváte o restart webového serveru.",
"The required %s config variable is not configured in the config.php file." : "Požadovaná proměnná nastavení %s není v souboru s nastaveními config.php nastavena.",
"Please ask your server administrator to check the Nextcloud configuration." : "Požádejte správce serveru, který využíváte, aby zkontroloval nastavení serveru.",
@@ -274,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Je zapotřebí verze softwarové knihovny libxml2 přinejmenším 2.7.0. Nyní je nainstalována verze %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Tento problém opravíte instalací novější verze knihovny libxml2 a restartem webového serveru.",
"PostgreSQL >= 9 required." : "Je vyžadováno PostgreSQL verze 9 a novější.",
- "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze."
+ "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze.",
+ "404" : "404"
},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;"
} \ No newline at end of file
diff --git a/lib/l10n/cy_GB.js b/lib/l10n/cy_GB.js
index 43d76a38ee2..f8c226fff7d 100644
--- a/lib/l10n/cy_GB.js
+++ b/lib/l10n/cy_GB.js
@@ -22,6 +22,8 @@ OC.L10N.register(
"Set an admin username." : "Creu enw defnyddiwr i'r gweinyddwr.",
"Set an admin password." : "Gosod cyfrinair y gweinyddwr.",
"Open »%s«" : "Agor »%s«",
+ "%1$s via %2$s" : "%1$s trwy %2$s",
+ "Click the button below to open it." : "Cliciwch ar y botwm isod i'w agor.",
"Could not find category \"%s\"" : "Methu canfod categori \"%s\"",
"Sunday" : "Sul",
"Monday" : "Llun",
@@ -63,6 +65,7 @@ OC.L10N.register(
"Dec." : "Rhag.",
"Application is not enabled" : "Nid yw'r pecyn wedi'i alluogi",
"Authentication error" : "Gwall dilysu",
- "Token expired. Please reload page." : "Tocyn wedi dod i ben. Ail-lwythwch y dudalen."
+ "Token expired. Please reload page." : "Tocyn wedi dod i ben. Ail-lwythwch y dudalen.",
+ "Full name" : "Enw llawn"
},
"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;");
diff --git a/lib/l10n/cy_GB.json b/lib/l10n/cy_GB.json
index e2a8fb651ad..4c34ea03f7e 100644
--- a/lib/l10n/cy_GB.json
+++ b/lib/l10n/cy_GB.json
@@ -20,6 +20,8 @@
"Set an admin username." : "Creu enw defnyddiwr i'r gweinyddwr.",
"Set an admin password." : "Gosod cyfrinair y gweinyddwr.",
"Open »%s«" : "Agor »%s«",
+ "%1$s via %2$s" : "%1$s trwy %2$s",
+ "Click the button below to open it." : "Cliciwch ar y botwm isod i'w agor.",
"Could not find category \"%s\"" : "Methu canfod categori \"%s\"",
"Sunday" : "Sul",
"Monday" : "Llun",
@@ -61,6 +63,7 @@
"Dec." : "Rhag.",
"Application is not enabled" : "Nid yw'r pecyn wedi'i alluogi",
"Authentication error" : "Gwall dilysu",
- "Token expired. Please reload page." : "Tocyn wedi dod i ben. Ail-lwythwch y dudalen."
+ "Token expired. Please reload page." : "Tocyn wedi dod i ben. Ail-lwythwch y dudalen.",
+ "Full name" : "Enw llawn"
},"pluralForm" :"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;"
} \ No newline at end of file
diff --git a/lib/l10n/da.js b/lib/l10n/da.js
index c549f68b3f3..057ba4d14b6 100644
--- a/lib/l10n/da.js
+++ b/lib/l10n/da.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Applikationen %1$s er ikke til stede eller har en ikke-kompatibel version med denne server. Tjek venligst apps mappen.",
"Sample configuration detected" : "Eksempel for konfiguration registreret",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Der er registreret at konfigurations eksemplet er blevet kopieret direkte. Dette kan ødelægge din installation og understøttes ikke. Læs venligst dokumentationen før der foretages ændringer i config.php",
- "404" : "404",
"The page could not be found on the server." : "Siden kunne ikke findes på serveren.",
"%s email verification" : "%s email verifikation",
"Email verification" : "Email verifikation",
@@ -77,7 +76,7 @@ OC.L10N.register(
"_in %n minute_::_in %n minutes_" : ["om %n minut","om %n minutter"],
"_%n minute ago_::_%n minutes ago_" : ["%n minut siden","%n minutter siden"],
"in a few seconds" : "om få sekunder",
- "seconds ago" : "sekunder siden",
+ "seconds ago" : "få sekunder siden",
"Empty file" : "Tom fil",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulet med ID: %s eksisterer ikke. Aktiver det venligst i dine indstillinger eller kontakt din administrator.",
"File already exists" : "Filen findes allerede",
@@ -276,6 +275,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 skal mindst være version 2.7.0. Du har version %s installeret.",
"To fix this issue update your libxml2 version and restart your web server." : "Opdater din libxml2 version og genstart webserveren for at løse problemet.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 kræves.",
- "Please upgrade your database version." : "Opgradér venligst din databaseversion."
+ "Please upgrade your database version." : "Opgradér venligst din databaseversion.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/da.json b/lib/l10n/da.json
index 24b42a7ac5e..5494cbecd00 100644
--- a/lib/l10n/da.json
+++ b/lib/l10n/da.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Applikationen %1$s er ikke til stede eller har en ikke-kompatibel version med denne server. Tjek venligst apps mappen.",
"Sample configuration detected" : "Eksempel for konfiguration registreret",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Der er registreret at konfigurations eksemplet er blevet kopieret direkte. Dette kan ødelægge din installation og understøttes ikke. Læs venligst dokumentationen før der foretages ændringer i config.php",
- "404" : "404",
"The page could not be found on the server." : "Siden kunne ikke findes på serveren.",
"%s email verification" : "%s email verifikation",
"Email verification" : "Email verifikation",
@@ -75,7 +74,7 @@
"_in %n minute_::_in %n minutes_" : ["om %n minut","om %n minutter"],
"_%n minute ago_::_%n minutes ago_" : ["%n minut siden","%n minutter siden"],
"in a few seconds" : "om få sekunder",
- "seconds ago" : "sekunder siden",
+ "seconds ago" : "få sekunder siden",
"Empty file" : "Tom fil",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulet med ID: %s eksisterer ikke. Aktiver det venligst i dine indstillinger eller kontakt din administrator.",
"File already exists" : "Filen findes allerede",
@@ -274,6 +273,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 skal mindst være version 2.7.0. Du har version %s installeret.",
"To fix this issue update your libxml2 version and restart your web server." : "Opdater din libxml2 version og genstart webserveren for at løse problemet.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 kræves.",
- "Please upgrade your database version." : "Opgradér venligst din databaseversion."
+ "Please upgrade your database version." : "Opgradér venligst din databaseversion.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/de.js b/lib/l10n/de.js
index 4adbe25257c..097f2a76f86 100644
--- a/lib/l10n/de.js
+++ b/lib/l10n/de.js
@@ -7,7 +7,6 @@ OC.L10N.register(
"See %s" : "Siehe %s",
"Sample configuration detected" : "Beispielkonfiguration gefunden",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann deine Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.",
- "404" : "404",
"The page could not be found on the server." : "Die Seite konnte auf dem Server nicht gefunden werden.",
"%s email verification" : "%s E-Mail-Überprüfung",
"Email verification" : "E-Mail-Überprüfung",
@@ -106,8 +105,8 @@ OC.L10N.register(
"View %s on the fediverse" : "Zeige %s auf dem Fediverse",
"Phone" : "Telefon",
"Call %s" : "%s anrufen",
- "Twitter" : "Twitter",
- "View %s on Twitter" : "%s auf Twitter anzeigen",
+ "Twitter" : "X",
+ "View %s on Twitter" : "%s auf X anzeigen",
"Website" : "Webseite",
"Visit %s" : "%s besuchen",
"Address" : "Adresse",
@@ -267,6 +266,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.",
"To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, musst du die libxml2 Version aktualisieren und den Webserver neustarten.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 benötigt",
- "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion"
+ "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/de.json b/lib/l10n/de.json
index 1356295ad32..74b60fb7e48 100644
--- a/lib/l10n/de.json
+++ b/lib/l10n/de.json
@@ -5,7 +5,6 @@
"See %s" : "Siehe %s",
"Sample configuration detected" : "Beispielkonfiguration gefunden",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann deine Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.",
- "404" : "404",
"The page could not be found on the server." : "Die Seite konnte auf dem Server nicht gefunden werden.",
"%s email verification" : "%s E-Mail-Überprüfung",
"Email verification" : "E-Mail-Überprüfung",
@@ -104,8 +103,8 @@
"View %s on the fediverse" : "Zeige %s auf dem Fediverse",
"Phone" : "Telefon",
"Call %s" : "%s anrufen",
- "Twitter" : "Twitter",
- "View %s on Twitter" : "%s auf Twitter anzeigen",
+ "Twitter" : "X",
+ "View %s on Twitter" : "%s auf X anzeigen",
"Website" : "Webseite",
"Visit %s" : "%s besuchen",
"Address" : "Adresse",
@@ -265,6 +264,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.",
"To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, musst du die libxml2 Version aktualisieren und den Webserver neustarten.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 benötigt",
- "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion"
+ "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/de_DE.js b/lib/l10n/de_DE.js
index 4b1a4c4c274..fdc840d0b19 100644
--- a/lib/l10n/de_DE.js
+++ b/lib/l10n/de_DE.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Die Anwendung %1$s ist nicht vorhanden oder hat eine mit diesem Server nicht kompatible Version. Bitte überprüfen Sie das Apps-Verzeichnis.",
"Sample configuration detected" : "Beispielkonfiguration gefunden",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Ihre Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.",
- "404" : "404",
"The page could not be found on the server." : "Die Seite konnte auf dem Server nicht gefunden werden.",
"%s email verification" : "%s E-Mail-Überprüfung",
"Email verification" : "E-Mail-Überprüfung",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.",
"To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, müssen Sie die libxml2 Version aktualisieren und den Webserver neustarten.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 benötigt.",
- "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion."
+ "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/de_DE.json b/lib/l10n/de_DE.json
index 98681f76384..1887e243dc3 100644
--- a/lib/l10n/de_DE.json
+++ b/lib/l10n/de_DE.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Die Anwendung %1$s ist nicht vorhanden oder hat eine mit diesem Server nicht kompatible Version. Bitte überprüfen Sie das Apps-Verzeichnis.",
"Sample configuration detected" : "Beispielkonfiguration gefunden",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Es wurde festgestellt, dass die Beispielkonfiguration kopiert wurde. Dies kann Ihre Installation zerstören und wird nicht unterstützt. Bitte die Dokumentation lesen, bevor Änderungen an der config.php vorgenommen werden.",
- "404" : "404",
"The page could not be found on the server." : "Die Seite konnte auf dem Server nicht gefunden werden.",
"%s email verification" : "%s E-Mail-Überprüfung",
"Email verification" : "E-Mail-Überprüfung",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 ist mindestestens erforderlich. Im Moment ist %s installiert.",
"To fix this issue update your libxml2 version and restart your web server." : "Um den Fehler zu beheben, müssen Sie die libxml2 Version aktualisieren und den Webserver neustarten.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 benötigt.",
- "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion."
+ "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/en_GB.js b/lib/l10n/en_GB.js
index ed51e1fd5fc..c2fb2209747 100644
--- a/lib/l10n/en_GB.js
+++ b/lib/l10n/en_GB.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.",
"Sample configuration detected" : "Sample configuration detected",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php",
- "404" : "404",
"The page could not be found on the server." : "The page could not be found on the server.",
"%s email verification" : "%s email verification",
"Email verification" : "Email verification",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 is at least required. Currently %s is installed.",
"To fix this issue update your libxml2 version and restart your web server." : "To fix this issue update your libxml2 version and restart your web server.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 required.",
- "Please upgrade your database version." : "Please upgrade your database version."
+ "Please upgrade your database version." : "Please upgrade your database version.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/en_GB.json b/lib/l10n/en_GB.json
index 40563e1980e..a52ee86c2d8 100644
--- a/lib/l10n/en_GB.json
+++ b/lib/l10n/en_GB.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.",
"Sample configuration detected" : "Sample configuration detected",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php",
- "404" : "404",
"The page could not be found on the server." : "The page could not be found on the server.",
"%s email verification" : "%s email verification",
"Email verification" : "Email verification",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 is at least required. Currently %s is installed.",
"To fix this issue update your libxml2 version and restart your web server." : "To fix this issue update your libxml2 version and restart your web server.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 required.",
- "Please upgrade your database version." : "Please upgrade your database version."
+ "Please upgrade your database version." : "Please upgrade your database version.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/eo.js b/lib/l10n/eo.js
index 69e5688c25c..1c901f1febd 100644
--- a/lib/l10n/eo.js
+++ b/lib/l10n/eo.js
@@ -46,6 +46,7 @@ OC.L10N.register(
"Invalid image" : "Nevalida bildo",
"Avatar image is not square" : "Avatarbildo ne estas kvadrata",
"Files" : "Dosieroj",
+ "View profile" : "Vidi profilon",
"today" : "hodiaŭ",
"tomorrow" : "morgaŭ",
"yesterday" : "hieraŭ",
diff --git a/lib/l10n/eo.json b/lib/l10n/eo.json
index 6c06f5cae1a..c4bd6cdee15 100644
--- a/lib/l10n/eo.json
+++ b/lib/l10n/eo.json
@@ -44,6 +44,7 @@
"Invalid image" : "Nevalida bildo",
"Avatar image is not square" : "Avatarbildo ne estas kvadrata",
"Files" : "Dosieroj",
+ "View profile" : "Vidi profilon",
"today" : "hodiaŭ",
"tomorrow" : "morgaŭ",
"yesterday" : "hieraŭ",
diff --git a/lib/l10n/es.js b/lib/l10n/es.js
index b80753e6e10..bac38858d13 100644
--- a/lib/l10n/es.js
+++ b/lib/l10n/es.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "La aplicación %1$s no está presente o tiene una versión que no es compatible con este servidor. Por favor, chequee la carpeta de apps.",
"Sample configuration detected" : "Configuración de ejemplo detectada",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que el ejemplo de configuración ha sido copiado. Esto podría afectar a su instalación, por lo que no tiene soporte. Lea la documentación antes de hacer cambios en config.php",
- "404" : "404",
"The page could not be found on the server." : "La página no se ha encontrado en el servidor.",
"%s email verification" : "%s verificación del correo electrónico",
"Email verification" : "Verificación del correo electrónico",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 es requerido en esta o en versiones superiores. Ahora mismo tienes instalada %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Para corregir este error, actualiza la versión de tu libxml2 y reinicia el servidor web.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 requerido.",
- "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos."
+ "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos.",
+ "404" : "404"
},
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
diff --git a/lib/l10n/es.json b/lib/l10n/es.json
index a4870ed898d..1aaf483f009 100644
--- a/lib/l10n/es.json
+++ b/lib/l10n/es.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "La aplicación %1$s no está presente o tiene una versión que no es compatible con este servidor. Por favor, chequee la carpeta de apps.",
"Sample configuration detected" : "Configuración de ejemplo detectada",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que el ejemplo de configuración ha sido copiado. Esto podría afectar a su instalación, por lo que no tiene soporte. Lea la documentación antes de hacer cambios en config.php",
- "404" : "404",
"The page could not be found on the server." : "La página no se ha encontrado en el servidor.",
"%s email verification" : "%s verificación del correo electrónico",
"Email verification" : "Verificación del correo electrónico",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 es requerido en esta o en versiones superiores. Ahora mismo tienes instalada %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Para corregir este error, actualiza la versión de tu libxml2 y reinicia el servidor web.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 requerido.",
- "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos."
+ "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos.",
+ "404" : "404"
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
} \ No newline at end of file
diff --git a/lib/l10n/es_EC.js b/lib/l10n/es_EC.js
index dd170b3d912..7696a74a271 100644
--- a/lib/l10n/es_EC.js
+++ b/lib/l10n/es_EC.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "La aplicación %1$s no está presente o tiene una versión no compatible con este servidor. Por favor, revisa el directorio de aplicaciones.",
"Sample configuration detected" : "Se ha detectado la configuración de muestra",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php",
- "404" : "404",
"The page could not be found on the server." : "No se pudo encontrar la página en el servidor.",
"%s email verification" : "%s verificación de correo electrónico",
"Email verification" : "Verificación de correo electrónico",
@@ -268,6 +267,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ",
"To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ",
"PostgreSQL >= 9 required." : "Se requiere PostgreSQL >= 9.",
- "Please upgrade your database version." : "Actualiza la versión de tu base de datos."
+ "Please upgrade your database version." : "Actualiza la versión de tu base de datos.",
+ "404" : "404"
},
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
diff --git a/lib/l10n/es_EC.json b/lib/l10n/es_EC.json
index 55b7dcd4d84..04c1a0ba25a 100644
--- a/lib/l10n/es_EC.json
+++ b/lib/l10n/es_EC.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "La aplicación %1$s no está presente o tiene una versión no compatible con este servidor. Por favor, revisa el directorio de aplicaciones.",
"Sample configuration detected" : "Se ha detectado la configuración de muestra",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Se ha detectado que la configuración de muestra ha sido copiada. Esto puede arruiniar tu instalacón y no está soportado. Por favor lee la documentación antes de hacer cambios en el archivo config.php",
- "404" : "404",
"The page could not be found on the server." : "No se pudo encontrar la página en el servidor.",
"%s email verification" : "%s verificación de correo electrónico",
"Email verification" : "Verificación de correo electrónico",
@@ -266,6 +265,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s está instalado. ",
"To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, por favor actualiza la versión de su libxml2 y reinicia tu servidor web. ",
"PostgreSQL >= 9 required." : "Se requiere PostgreSQL >= 9.",
- "Please upgrade your database version." : "Actualiza la versión de tu base de datos."
+ "Please upgrade your database version." : "Actualiza la versión de tu base de datos.",
+ "404" : "404"
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
} \ No newline at end of file
diff --git a/lib/l10n/eu.js b/lib/l10n/eu.js
index 5b80a7c1a09..c9f9aceb660 100644
--- a/lib/l10n/eu.js
+++ b/lib/l10n/eu.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "%1$s aplikazioa ez dago edo zerbitzari honekiko bertsio bateraezina du. Mesedez egiaztatu aplikazioen karpeta.",
"Sample configuration detected" : "Adibide-ezarpena detektatua",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Adibide-ezarpena kopiatu dela detektatu da. Honek zure instalazioa apur dezake eta ez da onartzen. Irakurri dokumentazioa config.php fitxategia aldatu aurretik.",
- "404" : "404",
"The page could not be found on the server." : "Orria ez da zerbitzarian aurkitu.",
"%s email verification" : "%sposta elektronikoaren egiaztapena",
"Email verification" : "Posta elektronikoaren egiaztapena",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 bertsioa edo berriagoa behar da. Orain %s dago instalatuta.",
"To fix this issue update your libxml2 version and restart your web server." : "Arazo hori konpontzeko, eguneratu zure libxml2 bertsioa eta berrabiarazi web zerbitzaria.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 behar da",
- "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa."
+ "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/eu.json b/lib/l10n/eu.json
index 1f63ae8b053..d07e4622dee 100644
--- a/lib/l10n/eu.json
+++ b/lib/l10n/eu.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "%1$s aplikazioa ez dago edo zerbitzari honekiko bertsio bateraezina du. Mesedez egiaztatu aplikazioen karpeta.",
"Sample configuration detected" : "Adibide-ezarpena detektatua",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Adibide-ezarpena kopiatu dela detektatu da. Honek zure instalazioa apur dezake eta ez da onartzen. Irakurri dokumentazioa config.php fitxategia aldatu aurretik.",
- "404" : "404",
"The page could not be found on the server." : "Orria ez da zerbitzarian aurkitu.",
"%s email verification" : "%sposta elektronikoaren egiaztapena",
"Email verification" : "Posta elektronikoaren egiaztapena",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 bertsioa edo berriagoa behar da. Orain %s dago instalatuta.",
"To fix this issue update your libxml2 version and restart your web server." : "Arazo hori konpontzeko, eguneratu zure libxml2 bertsioa eta berrabiarazi web zerbitzaria.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 behar da",
- "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa."
+ "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/fa.js b/lib/l10n/fa.js
index 6c69b85d691..83132586486 100644
--- a/lib/l10n/fa.js
+++ b/lib/l10n/fa.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.",
"Sample configuration detected" : "فایل پیکربندی نمونه پیدا شد",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "تشخیص داده شده است که پیکربندی نمونه کپی شده است. این می تواند نصب شما را خراب کند و پشتیبانی نمی شود. لطفاً قبل از انجام تغییرات در config.php ، اسناد را بخوانید",
- "404" : "۴۰۴",
"The page could not be found on the server." : "The page could not be found on the server.",
"%s email verification" : "%s email verification",
"Email verification" : "Email verification",
@@ -276,6 +275,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 حداقل مورد نیاز است. در حال حاضر %sنصب شده است",
"To fix this issue update your libxml2 version and restart your web server." : "برای رفع این مشکل نسخه libxml2 خود را به روز کنید و سرور وب خود را مجدداً راه اندازی کنید.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 required.",
- "Please upgrade your database version." : "Please upgrade your database version."
+ "Please upgrade your database version." : "Please upgrade your database version.",
+ "404" : "۴۰۴"
},
"nplurals=2; plural=(n > 1);");
diff --git a/lib/l10n/fa.json b/lib/l10n/fa.json
index a5024351f34..e5bae83fdcf 100644
--- a/lib/l10n/fa.json
+++ b/lib/l10n/fa.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.",
"Sample configuration detected" : "فایل پیکربندی نمونه پیدا شد",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "تشخیص داده شده است که پیکربندی نمونه کپی شده است. این می تواند نصب شما را خراب کند و پشتیبانی نمی شود. لطفاً قبل از انجام تغییرات در config.php ، اسناد را بخوانید",
- "404" : "۴۰۴",
"The page could not be found on the server." : "The page could not be found on the server.",
"%s email verification" : "%s email verification",
"Email verification" : "Email verification",
@@ -274,6 +273,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 حداقل مورد نیاز است. در حال حاضر %sنصب شده است",
"To fix this issue update your libxml2 version and restart your web server." : "برای رفع این مشکل نسخه libxml2 خود را به روز کنید و سرور وب خود را مجدداً راه اندازی کنید.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 required.",
- "Please upgrade your database version." : "Please upgrade your database version."
+ "Please upgrade your database version." : "Please upgrade your database version.",
+ "404" : "۴۰۴"
},"pluralForm" :"nplurals=2; plural=(n > 1);"
} \ No newline at end of file
diff --git a/lib/l10n/fi.js b/lib/l10n/fi.js
index 7eb441edd14..3325fbc9321 100644
--- a/lib/l10n/fi.js
+++ b/lib/l10n/fi.js
@@ -6,7 +6,6 @@ OC.L10N.register(
"See %s" : "Katso %s",
"Sample configuration detected" : "Esimerkkimääritykset havaittu",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "On havaittu, että esimerkkimäärityksen on kopioitu. Se voi rikkoa asennuksesi, eikä sitä tueta. Lue ohjeet ennen kuin muutat config.php tiedostoa.",
- "404" : "404",
"The page could not be found on the server." : "Sivua ei löytynyt palvelimelta.",
"Email verification" : "Sähköpostin vahvistus",
"Click the following button to confirm your email." : "Napsauta seuraavaa painiketta vahvistaaksesi sähköpostiosoitteesi.",
@@ -232,6 +231,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Vähintään libxml2 2.7.0 vaaditaan. %s on asennettu.",
"To fix this issue update your libxml2 version and restart your web server." : "Päivitä libxml2:n versio ja käynnistä http-palvelin uudelleen.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 vaaditaan.",
- "Please upgrade your database version." : "Päivitä tietokannan versio."
+ "Please upgrade your database version." : "Päivitä tietokannan versio.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/fi.json b/lib/l10n/fi.json
index 3ec59a0eff9..13b52751e44 100644
--- a/lib/l10n/fi.json
+++ b/lib/l10n/fi.json
@@ -4,7 +4,6 @@
"See %s" : "Katso %s",
"Sample configuration detected" : "Esimerkkimääritykset havaittu",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "On havaittu, että esimerkkimäärityksen on kopioitu. Se voi rikkoa asennuksesi, eikä sitä tueta. Lue ohjeet ennen kuin muutat config.php tiedostoa.",
- "404" : "404",
"The page could not be found on the server." : "Sivua ei löytynyt palvelimelta.",
"Email verification" : "Sähköpostin vahvistus",
"Click the following button to confirm your email." : "Napsauta seuraavaa painiketta vahvistaaksesi sähköpostiosoitteesi.",
@@ -230,6 +229,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Vähintään libxml2 2.7.0 vaaditaan. %s on asennettu.",
"To fix this issue update your libxml2 version and restart your web server." : "Päivitä libxml2:n versio ja käynnistä http-palvelin uudelleen.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 vaaditaan.",
- "Please upgrade your database version." : "Päivitä tietokannan versio."
+ "Please upgrade your database version." : "Päivitä tietokannan versio.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/fr.js b/lib/l10n/fr.js
index 412c4c86c53..9977ca38926 100644
--- a/lib/l10n/fr.js
+++ b/lib/l10n/fr.js
@@ -3,12 +3,11 @@ OC.L10N.register(
{
"Cannot write into \"config\" directory!" : "Impossible d’écrire dans le répertoire « config » !",
"This can usually be fixed by giving the web server write access to the config directory." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire de configuration.",
- "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option « config_is_read_only » sur true.",
"See %s" : "Voir %s",
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "L'application %1$s n'est pas présente ou n'est pas compatible avec cette version du serveur. Veuillez vérifier le répertoire des applications.",
"Sample configuration detected" : "Configuration d'exemple détectée",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Il a été détecté que la configuration donnée à titre d'exemple a été copiée. Cela peut rendre votre installation inopérante et n'est pas pris en charge. Veuillez lire la documentation avant d'effectuer des modifications dans config.php",
- "404" : "404",
"The page could not be found on the server." : "La page n'a pas pu être trouvée sur le serveur.",
"%s email verification" : "Vérification de l'e-mail %s",
"Email verification" : "Vérification de l'e-mail",
@@ -22,7 +21,7 @@ OC.L10N.register(
"%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s et %5$s",
"Education Edition" : "Édition pour l'éducation ",
"Enterprise bundle" : "Pack pour entreprise",
- "Groupware bundle" : "Pack pour travail collaboratif",
+ "Groupware bundle" : "Pack Groupware",
"Hub bundle" : "Pack Nextcloud Hub",
"Social sharing bundle" : "Pack pour partage social",
"PHP %s or higher is required." : "PHP %s ou supérieur est requis.",
@@ -54,8 +53,8 @@ OC.L10N.register(
"The remote wipe on %s has finished" : "Le nettoyage à distance de %s est terminé",
"Authentication" : "Authentification",
"Unknown filetype" : "Type de fichier inconnu",
- "Invalid image" : "Image non valable",
- "Avatar image is not square" : "L'image d'avatar n'est pas carré",
+ "Invalid image" : "Image invalide",
+ "Avatar image is not square" : "L'image d'avatar n'est pas carrée",
"Files" : "Fichiers",
"View profile" : "Voir le profil",
"Local time: %s" : "Heure locale : %s",
@@ -85,12 +84,12 @@ OC.L10N.register(
"Failed to create file from template" : "Impossible de créer le fichier à partir du modèle",
"Templates" : "Modèles",
"File name is a reserved word" : "Ce nom de fichier est un mot réservé",
- "File name contains at least one invalid character" : "Le nom de fichier contient un (des) caractère(s) non valide(s)",
+ "File name contains at least one invalid character" : "Le nom de fichier contient au moins un caractère invalide",
"File name is too long" : "Nom de fichier trop long",
"Dot files are not allowed" : "Le nom de fichier ne peut pas commencer par un point",
- "Empty filename is not allowed" : "Le nom de fichier ne peut pas être vide",
- "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'application \"%s\" ne peut pas être installée car le fichier appinfo ne peut pas être lu.",
- "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'application \"%s\" ne peut être installée car elle n'est pas compatible avec cette version du serveur",
+ "Empty filename is not allowed" : "Le nom de fichier n'est pas autorisé",
+ "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'application « %s » ne peut pas être installée car le fichier appinfo ne peut pas être lu.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'application « %s » ne peut être installée car elle n'est pas compatible avec cette version du serveur.",
"__language_name__" : "Français",
"This is an automatically sent email, please do not reply." : "Ceci est un e-mail envoyé automatiquement, veuillez ne pas y répondre.",
"Help" : "Aide",
@@ -131,8 +130,8 @@ OC.L10N.register(
"PostgreSQL username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base PostgreSQL non valide(s)",
"Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X n'est pas pris en charge et %s ne fonctionnera pas correctement sur cette plate-forme. Son utilisation est à vos risques et périls !",
"For the best results, please consider using a GNU/Linux server instead." : "Pour obtenir les meilleurs résultats, vous devriez utiliser un serveur GNU/Linux.",
- "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Il semble que cette instance %s fonctionne sur un environnement PHP 32-bit et open_basedir a été configuré dans php.ini. Cela engendre des problèmes avec les fichiers de taille supérieure à 4 Go et est donc fortement déconseillé.",
- "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Veuillez supprimer la configuration open_basedir de votre php.ini ou utiliser une version PHP 64-bit.",
+ "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Il semble que cette instance %s fonctionne sur un environnement PHP 32 bits et open_basedir a été configuré dans php.ini. Cela engendre des problèmes avec les fichiers de taille supérieure à 4 Go et est donc fortement déconseillé.",
+ "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Veuillez retirer la configuration open_basedir de votre php.ini ou utiliser une version PHP 64-bit.",
"Set an admin username." : "Spécifiez un nom d'utilisateur pour l'administrateur.",
"Set an admin password." : "Spécifiez un mot de passe pour l'administrateur.",
"Cannot create or write into the data directory %s" : "Impossible de créer ou d'écrire dans le répertoire des données %s",
@@ -149,15 +148,16 @@ OC.L10N.register(
"Files cannot be shared with delete permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de suppression",
"Files cannot be shared with create permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de création",
"Expiration date is in the past" : "La date d'expiration est dans le passé",
- "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Impossible de définir la date d'expiration à dans plus de %s jour","Impossible de définir la date d'expiration à dans plus de %s jours","Impossible de définir la date d'expiration à dans plus de %s jours"],
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Impossible de définir la date d'expiration à dans plus de %n jour","Impossible de définir la date d'expiration à dans plus de %n jours","Impossible de définir la date d'expiration à dans plus de %n jours"],
"Sharing is only allowed with group members" : "Le partage n'est que possible qu'avec les membres du groupe",
"Sharing %s failed, because this item is already shared with user %s" : "Impossible de partager %s car il est déjà partagé avec l'utilisateur %s",
"%1$s shared »%2$s« with you" : "%1$s a partagé « %2$s » avec vous",
"%1$s shared »%2$s« with you." : "%1$s a partagé « %2$s » avec vous.",
"Click the button below to open it." : "Cliquez sur le bouton ci-dessous pour l'ouvrir",
"The requested share does not exist anymore" : "Le partage demandé n'existe plus",
+ "The requested share comes from a disabled user" : "Le partage demandé provient d'un utilisateur désactivé",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "L'utilisateur n'a pas été créé car la limite du nombre d'utilisateurs a été atteinte. Consultez vos notifications pour en savoir plus.",
- "Could not find category \"%s\"" : "Impossible de trouver la catégorie \"%s\"",
+ "Could not find category \"%s\"" : "Impossible de trouver la catégorie « %s »",
"Sunday" : "Dimanche",
"Monday" : "Lundi",
"Tuesday" : "Mardi",
@@ -212,8 +212,8 @@ OC.L10N.register(
"Username must not consist of dots only" : "Le nom d'utilisateur ne doit pas être composé uniquement de points",
"Username is invalid because files already exist for this user" : "Ce nom d'utilisateur n'est pas valide car des fichiers existent déjà pour cet utilisateur",
"User disabled" : "Utilisateur désactivé",
- "Login canceled by app" : "L'authentification a été annulé par l'application",
- "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application \"%1$s\" ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s",
+ "Login canceled by app" : "L'authentification a été annulée par l'application",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application « %1$s » ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s",
"a safe home for all your data" : "un lieu sûr pour toutes vos données",
"File is currently busy, please try again later" : "Le fichier est actuellement utilisé, veuillez réessayer plus tard",
"Cannot download file" : "Impossible de télécharger le fichier",
@@ -221,10 +221,10 @@ OC.L10N.register(
"Authentication error" : "Erreur d'authentification",
"Token expired. Please reload page." : "La session a expiré. Veuillez recharger la page.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Aucun pilote de base de données n’est installé (sqlite, mysql ou postgresql).",
- "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire \"config\".",
- "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\". Voir %s",
- "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true. Voir %s",
- "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire \"apps\".",
+ "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire « config ».",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire « config ». Voir %s",
+ "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option « config_is_read_only » sur true. Voir %s",
+ "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire « apps ».",
"This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire des applications ou en désactivant le magasin d'applications dans le fichier de configuration.",
"Cannot create \"data\" directory." : "Impossible de créer le dossier \"data\".",
"This can usually be fixed by giving the web server write access to the root directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s",
@@ -234,7 +234,7 @@ OC.L10N.register(
"Please install one of these locales on your system and restart your web server." : "Veuillez installer l'un de ces paramètres régionaux sur votre système et redémarrer votre serveur web.",
"PHP module %s not installed." : "Le module PHP %s n’est pas installé.",
"Please ask your server administrator to install the module." : "Veuillez demander à votre administrateur d’installer le module.",
- "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP \"%s\" n'est pas \"%s\".",
+ "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP « %s » n'est pas « %s ».",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Ajuster ce paramètre dans php.ini fera fonctionner Nextcould à nouveau",
"<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> est défini à <code>%s</code> alors que la valeur <code>0</code> est attendue.",
"To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pour corriger ce problème définissez <code>mbstring.func_overload</code> à <code>0</code> dans votre php.ini.",
@@ -255,12 +255,20 @@ OC.L10N.register(
"Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Paramètres manquants pour compléter la requête. Paramètres manquants : \"%s\"",
"ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "L'identifiant \"%1$s\" est déjà utilisé par l'instance de Cloud Fédéré \"%2$s\"",
"Cloud Federation Provider with ID: \"%s\" does not exist." : "L'instance de Cloud Fédéré dont l'identifiant est \"%s\" n'existe pas.",
- "Could not obtain lock type %d on \"%s\"." : "Impossible d'obtenir le verrouillage de type %d sur \"%s\".",
+ "Could not obtain lock type %d on \"%s\"." : "Impossible d'obtenir le verrouillage de type %d sur « %s ».",
"Storage unauthorized. %s" : "Espace de stockage non autorisé. %s",
"Storage incomplete configuration. %s" : "Configuration de l'espace de stockage incomplète. %s",
"Storage connection error. %s" : "Erreur de connexion à l'espace stockage. %s",
"Storage is temporarily not available" : "Le support de stockage est temporairement indisponible",
"Storage connection timeout. %s" : "Le délai d'attente pour la connexion à l'espace de stockage a été dépassé. %s",
+ "Free prompt" : "Prompt",
+ "Runs an arbitrary prompt through the language model." : "Exécute une commande arbitraire via le modèle de langage.",
+ "Generate headline" : "Générer un titre",
+ "Generates a possible headline for a text." : "Génère un titre possible pour un texte.",
+ "Summarize" : "Résumer",
+ "Summarizes text by reducing its length without losing key information." : "Résume un texte en réduisant sa longueur sans perdre d'informations essentielles.",
+ "Extract topics" : "Extraire des thèmes",
+ "Extracts topics from a text and outputs them separated by commas." : "Extrait les thèmes d'un texte et les restitue séparés par des virgules.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Les fichiers de l'application %1$s n'ont pas été remplacés correctement. Veuillez vérifier que c'est une version compatible avec le serveur.",
"Full name" : "Nom complet",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "La limite d'utilisateurs à été atteinte et cet utilisateur n'a pas été créé. Consultez vos notifications pour en savoir plus.",
@@ -268,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 au moins est requis. Actuellement %s est installé.",
"To fix this issue update your libxml2 version and restart your web server." : "Pour régler ce problème, mettez à jour votre version de libxml2 et redémarrez votre serveur web.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 requis.",
- "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données."
+ "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données.",
+ "404" : "404"
},
"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
diff --git a/lib/l10n/fr.json b/lib/l10n/fr.json
index 6d75989d48e..008a2b4f558 100644
--- a/lib/l10n/fr.json
+++ b/lib/l10n/fr.json
@@ -1,12 +1,11 @@
{ "translations": {
"Cannot write into \"config\" directory!" : "Impossible d’écrire dans le répertoire « config » !",
"This can usually be fixed by giving the web server write access to the config directory." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire de configuration.",
- "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option « config_is_read_only » sur true.",
"See %s" : "Voir %s",
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "L'application %1$s n'est pas présente ou n'est pas compatible avec cette version du serveur. Veuillez vérifier le répertoire des applications.",
"Sample configuration detected" : "Configuration d'exemple détectée",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Il a été détecté que la configuration donnée à titre d'exemple a été copiée. Cela peut rendre votre installation inopérante et n'est pas pris en charge. Veuillez lire la documentation avant d'effectuer des modifications dans config.php",
- "404" : "404",
"The page could not be found on the server." : "La page n'a pas pu être trouvée sur le serveur.",
"%s email verification" : "Vérification de l'e-mail %s",
"Email verification" : "Vérification de l'e-mail",
@@ -20,7 +19,7 @@
"%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s et %5$s",
"Education Edition" : "Édition pour l'éducation ",
"Enterprise bundle" : "Pack pour entreprise",
- "Groupware bundle" : "Pack pour travail collaboratif",
+ "Groupware bundle" : "Pack Groupware",
"Hub bundle" : "Pack Nextcloud Hub",
"Social sharing bundle" : "Pack pour partage social",
"PHP %s or higher is required." : "PHP %s ou supérieur est requis.",
@@ -52,8 +51,8 @@
"The remote wipe on %s has finished" : "Le nettoyage à distance de %s est terminé",
"Authentication" : "Authentification",
"Unknown filetype" : "Type de fichier inconnu",
- "Invalid image" : "Image non valable",
- "Avatar image is not square" : "L'image d'avatar n'est pas carré",
+ "Invalid image" : "Image invalide",
+ "Avatar image is not square" : "L'image d'avatar n'est pas carrée",
"Files" : "Fichiers",
"View profile" : "Voir le profil",
"Local time: %s" : "Heure locale : %s",
@@ -83,12 +82,12 @@
"Failed to create file from template" : "Impossible de créer le fichier à partir du modèle",
"Templates" : "Modèles",
"File name is a reserved word" : "Ce nom de fichier est un mot réservé",
- "File name contains at least one invalid character" : "Le nom de fichier contient un (des) caractère(s) non valide(s)",
+ "File name contains at least one invalid character" : "Le nom de fichier contient au moins un caractère invalide",
"File name is too long" : "Nom de fichier trop long",
"Dot files are not allowed" : "Le nom de fichier ne peut pas commencer par un point",
- "Empty filename is not allowed" : "Le nom de fichier ne peut pas être vide",
- "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'application \"%s\" ne peut pas être installée car le fichier appinfo ne peut pas être lu.",
- "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'application \"%s\" ne peut être installée car elle n'est pas compatible avec cette version du serveur",
+ "Empty filename is not allowed" : "Le nom de fichier n'est pas autorisé",
+ "App \"%s\" cannot be installed because appinfo file cannot be read." : "L'application « %s » ne peut pas être installée car le fichier appinfo ne peut pas être lu.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "L'application « %s » ne peut être installée car elle n'est pas compatible avec cette version du serveur.",
"__language_name__" : "Français",
"This is an automatically sent email, please do not reply." : "Ceci est un e-mail envoyé automatiquement, veuillez ne pas y répondre.",
"Help" : "Aide",
@@ -129,8 +128,8 @@
"PostgreSQL username and/or password not valid" : "Nom d'utilisateur et/ou mot de passe de la base PostgreSQL non valide(s)",
"Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X n'est pas pris en charge et %s ne fonctionnera pas correctement sur cette plate-forme. Son utilisation est à vos risques et périls !",
"For the best results, please consider using a GNU/Linux server instead." : "Pour obtenir les meilleurs résultats, vous devriez utiliser un serveur GNU/Linux.",
- "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Il semble que cette instance %s fonctionne sur un environnement PHP 32-bit et open_basedir a été configuré dans php.ini. Cela engendre des problèmes avec les fichiers de taille supérieure à 4 Go et est donc fortement déconseillé.",
- "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Veuillez supprimer la configuration open_basedir de votre php.ini ou utiliser une version PHP 64-bit.",
+ "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Il semble que cette instance %s fonctionne sur un environnement PHP 32 bits et open_basedir a été configuré dans php.ini. Cela engendre des problèmes avec les fichiers de taille supérieure à 4 Go et est donc fortement déconseillé.",
+ "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Veuillez retirer la configuration open_basedir de votre php.ini ou utiliser une version PHP 64-bit.",
"Set an admin username." : "Spécifiez un nom d'utilisateur pour l'administrateur.",
"Set an admin password." : "Spécifiez un mot de passe pour l'administrateur.",
"Cannot create or write into the data directory %s" : "Impossible de créer ou d'écrire dans le répertoire des données %s",
@@ -147,15 +146,16 @@
"Files cannot be shared with delete permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de suppression",
"Files cannot be shared with create permissions" : "Les fichiers ne peuvent pas être partagés avec les autorisations de création",
"Expiration date is in the past" : "La date d'expiration est dans le passé",
- "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Impossible de définir la date d'expiration à dans plus de %s jour","Impossible de définir la date d'expiration à dans plus de %s jours","Impossible de définir la date d'expiration à dans plus de %s jours"],
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Impossible de définir la date d'expiration à dans plus de %n jour","Impossible de définir la date d'expiration à dans plus de %n jours","Impossible de définir la date d'expiration à dans plus de %n jours"],
"Sharing is only allowed with group members" : "Le partage n'est que possible qu'avec les membres du groupe",
"Sharing %s failed, because this item is already shared with user %s" : "Impossible de partager %s car il est déjà partagé avec l'utilisateur %s",
"%1$s shared »%2$s« with you" : "%1$s a partagé « %2$s » avec vous",
"%1$s shared »%2$s« with you." : "%1$s a partagé « %2$s » avec vous.",
"Click the button below to open it." : "Cliquez sur le bouton ci-dessous pour l'ouvrir",
"The requested share does not exist anymore" : "Le partage demandé n'existe plus",
+ "The requested share comes from a disabled user" : "Le partage demandé provient d'un utilisateur désactivé",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "L'utilisateur n'a pas été créé car la limite du nombre d'utilisateurs a été atteinte. Consultez vos notifications pour en savoir plus.",
- "Could not find category \"%s\"" : "Impossible de trouver la catégorie \"%s\"",
+ "Could not find category \"%s\"" : "Impossible de trouver la catégorie « %s »",
"Sunday" : "Dimanche",
"Monday" : "Lundi",
"Tuesday" : "Mardi",
@@ -210,8 +210,8 @@
"Username must not consist of dots only" : "Le nom d'utilisateur ne doit pas être composé uniquement de points",
"Username is invalid because files already exist for this user" : "Ce nom d'utilisateur n'est pas valide car des fichiers existent déjà pour cet utilisateur",
"User disabled" : "Utilisateur désactivé",
- "Login canceled by app" : "L'authentification a été annulé par l'application",
- "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application \"%1$s\" ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s",
+ "Login canceled by app" : "L'authentification a été annulée par l'application",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application « %1$s » ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s",
"a safe home for all your data" : "un lieu sûr pour toutes vos données",
"File is currently busy, please try again later" : "Le fichier est actuellement utilisé, veuillez réessayer plus tard",
"Cannot download file" : "Impossible de télécharger le fichier",
@@ -219,10 +219,10 @@
"Authentication error" : "Erreur d'authentification",
"Token expired. Please reload page." : "La session a expiré. Veuillez recharger la page.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Aucun pilote de base de données n’est installé (sqlite, mysql ou postgresql).",
- "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire \"config\".",
- "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\". Voir %s",
- "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true. Voir %s",
- "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire \"apps\".",
+ "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire « config ».",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire « config ». Voir %s",
+ "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option « config_is_read_only » sur true. Voir %s",
+ "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire « apps ».",
"This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire des applications ou en désactivant le magasin d'applications dans le fichier de configuration.",
"Cannot create \"data\" directory." : "Impossible de créer le dossier \"data\".",
"This can usually be fixed by giving the web server write access to the root directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s",
@@ -232,7 +232,7 @@
"Please install one of these locales on your system and restart your web server." : "Veuillez installer l'un de ces paramètres régionaux sur votre système et redémarrer votre serveur web.",
"PHP module %s not installed." : "Le module PHP %s n’est pas installé.",
"Please ask your server administrator to install the module." : "Veuillez demander à votre administrateur d’installer le module.",
- "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP \"%s\" n'est pas \"%s\".",
+ "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP « %s » n'est pas « %s ».",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Ajuster ce paramètre dans php.ini fera fonctionner Nextcould à nouveau",
"<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> est défini à <code>%s</code> alors que la valeur <code>0</code> est attendue.",
"To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pour corriger ce problème définissez <code>mbstring.func_overload</code> à <code>0</code> dans votre php.ini.",
@@ -253,12 +253,20 @@
"Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Paramètres manquants pour compléter la requête. Paramètres manquants : \"%s\"",
"ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "L'identifiant \"%1$s\" est déjà utilisé par l'instance de Cloud Fédéré \"%2$s\"",
"Cloud Federation Provider with ID: \"%s\" does not exist." : "L'instance de Cloud Fédéré dont l'identifiant est \"%s\" n'existe pas.",
- "Could not obtain lock type %d on \"%s\"." : "Impossible d'obtenir le verrouillage de type %d sur \"%s\".",
+ "Could not obtain lock type %d on \"%s\"." : "Impossible d'obtenir le verrouillage de type %d sur « %s ».",
"Storage unauthorized. %s" : "Espace de stockage non autorisé. %s",
"Storage incomplete configuration. %s" : "Configuration de l'espace de stockage incomplète. %s",
"Storage connection error. %s" : "Erreur de connexion à l'espace stockage. %s",
"Storage is temporarily not available" : "Le support de stockage est temporairement indisponible",
"Storage connection timeout. %s" : "Le délai d'attente pour la connexion à l'espace de stockage a été dépassé. %s",
+ "Free prompt" : "Prompt",
+ "Runs an arbitrary prompt through the language model." : "Exécute une commande arbitraire via le modèle de langage.",
+ "Generate headline" : "Générer un titre",
+ "Generates a possible headline for a text." : "Génère un titre possible pour un texte.",
+ "Summarize" : "Résumer",
+ "Summarizes text by reducing its length without losing key information." : "Résume un texte en réduisant sa longueur sans perdre d'informations essentielles.",
+ "Extract topics" : "Extraire des thèmes",
+ "Extracts topics from a text and outputs them separated by commas." : "Extrait les thèmes d'un texte et les restitue séparés par des virgules.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Les fichiers de l'application %1$s n'ont pas été remplacés correctement. Veuillez vérifier que c'est une version compatible avec le serveur.",
"Full name" : "Nom complet",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "La limite d'utilisateurs à été atteinte et cet utilisateur n'a pas été créé. Consultez vos notifications pour en savoir plus.",
@@ -266,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 au moins est requis. Actuellement %s est installé.",
"To fix this issue update your libxml2 version and restart your web server." : "Pour régler ce problème, mettez à jour votre version de libxml2 et redémarrez votre serveur web.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 requis.",
- "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données."
+ "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données.",
+ "404" : "404"
},"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
} \ No newline at end of file
diff --git a/lib/l10n/gl.js b/lib/l10n/gl.js
index 46440e27bcc..c325c87649b 100644
--- a/lib/l10n/gl.js
+++ b/lib/l10n/gl.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "A aplicación %1$s non está presente ou ten unha versión non compatíbel con este servidor. Comprobe o directorio de aplicacións.",
"Sample configuration detected" : "Detectouse a configuración de exemplo",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectouse que foi copiada a configuración de exemplo. Isto pode rachar a súa instalación e non é compatíbel. Lea a documentación antes de facer cambios en config.php",
- "404" : "404",
"The page could not be found on the server." : "Non foi posíbel atopar a páxina no servidor.",
"%s email verification" : "Verificación do correo-e %s",
"Email verification" : "Verificación do correo-e",
@@ -25,18 +24,18 @@ OC.L10N.register(
"Groupware bundle" : "Paquete de software colaborativo",
"Hub bundle" : "Paquete de concentradores",
"Social sharing bundle" : "Paquete para compartir en redes sociais",
- "PHP %s or higher is required." : "Precisase PHP %s ou superior.",
- "PHP with a version lower than %s is required." : "Precisase PHP cunha versión inferior a %s.",
- "%sbit or higher PHP required." : "Precisase PHP para %sbits ou superior.",
+ "PHP %s or higher is required." : "Precísase de PHP %s ou superior.",
+ "PHP with a version lower than %s is required." : "Precísase de PHP cunha versión inferior a %s.",
+ "%sbit or higher PHP required." : "Precísase de PHP para %s bits ou superior.",
"The following architectures are supported: %s" : "Admítense as seguintes arquitecturas: %s",
"The following databases are supported: %s" : "Admítense as seguintes bases de datos: %s",
"The command line tool %s could not be found" : "Non foi posíbel atopar a ferramenta de liña de ordes %s",
"The library %s is not available." : "Non está dispoñíbel a biblioteca %s.",
- "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Precisase a biblioteca %1$s cunha versión superior a %2$s - dispoñíbel a versión %3$s.",
- "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Precisase a biblioteca %1$s cunha versión inferior a %2$s - dispoñíbel a versión %3$s.",
+ "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Precísase da biblioteca %1$s cunha versión superior a %2$s - dispoñíbel a versión %3$s.",
+ "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Precísase da biblioteca %1$s cunha versión inferior a %2$s - dispoñíbel a versión %3$s.",
"The following platforms are supported: %s" : "Admítense as seguintes plataformas: %s",
- "Server version %s or higher is required." : "Precisase a versión %s ou superior do servidor.",
- "Server version %s or lower is required." : "Precisase a versión %s ou inferior do servidor.",
+ "Server version %s or higher is required." : "Precísase da versión %s ou superior do servidor.",
+ "Server version %s or lower is required." : "Precísase da versión %s ou inferior do servidor.",
"Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "O usuario que accede debe ser un administrador, un subadministrador ou ter un dereito especial para acceder a esta configuración",
"Logged in user must be an admin or sub admin" : "O usuario autenticado debe ser un administrador ou subadministrador",
"Logged in user must be an admin" : "O usuario conectado debe ser un administrador",
@@ -215,7 +214,7 @@ OC.L10N.register(
"User disabled" : "Usuario desactivado",
"Login canceled by app" : "Acceso cancelado pola aplicación",
"App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Non é posíbel instalar a aplicación «%1$s» por mor de non cumprirse as dependencias: %2$s",
- "a safe home for all your data" : "un lugar seguro para todos os seus datos",
+ "a safe home for all your data" : "un acubillo seguro para todos os seus datos",
"File is currently busy, please try again later" : "O ficheiro está ocupado neste momento, ténteo máis adiante.",
"Cannot download file" : "Non é posíbel descargar o ficheiro",
"Application is not enabled" : "A aplicación non está activada",
@@ -243,7 +242,7 @@ OC.L10N.register(
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isto probabelmente se debe unha caché/acelerador como Zend OPcache ou eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Instaláronse os módulos de PHP, mais aínda aparecen listados como perdidos?",
"Please ask your server administrator to restart the web server." : "Pídalle á administración do seu servidor que reinicie o servidor web.",
- "The required %s config variable is not configured in the config.php file." : "Precísase a variábel de configuración %s e non está configurada no ficheiro config.php.",
+ "The required %s config variable is not configured in the config.php file." : "Precísase da variábel de configuración %s e non está configurada no ficheiro config.php.",
"Please ask your server administrator to check the Nextcloud configuration." : "Pídalle á administración do seu servidor que verifique a configuración de Nextcloud.",
"Your data directory is readable by other users." : "Outros usuarios poden leer o seu directorio de datos.",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Cambie os permisos a 0770 para que o directorio non poida ser listado por outros usuarios.",
@@ -274,9 +273,10 @@ OC.L10N.register(
"Full name" : "Nome completo",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Acadouse o límite de usuarios e non se creou o usuario. Consulte as súas notificacións para obter máis información.",
"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Só os seguintes caracteres están permitidos nos nomes de usuario: «a-z», «A-Z», «0-9» e «_.@-'»",
- "libxml2 2.7.0 is at least required. Currently %s is installed." : "Precisase, cando menos, libxml2 2.7.0. Actualmente esta instalado %s.",
+ "libxml2 2.7.0 is at least required. Currently %s is installed." : "Precísase, cando menos, de libxml2 2.7.0. Actualmente esta instalado %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Para arranxar esta incidencia, actualice a versión de libxml2 e reinicie o servidor web. ",
- "PostgreSQL >= 9 required." : "Precisase PostgreSQL >= 9.",
- "Please upgrade your database version." : "Anove a versión da súa base de datos"
+ "PostgreSQL >= 9 required." : "Precísase de PostgreSQL >= 9.",
+ "Please upgrade your database version." : "Anove a versión da súa base de datos",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/gl.json b/lib/l10n/gl.json
index 982199d4f7c..aa8ec0bc4ee 100644
--- a/lib/l10n/gl.json
+++ b/lib/l10n/gl.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "A aplicación %1$s non está presente ou ten unha versión non compatíbel con este servidor. Comprobe o directorio de aplicacións.",
"Sample configuration detected" : "Detectouse a configuración de exemplo",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Detectouse que foi copiada a configuración de exemplo. Isto pode rachar a súa instalación e non é compatíbel. Lea a documentación antes de facer cambios en config.php",
- "404" : "404",
"The page could not be found on the server." : "Non foi posíbel atopar a páxina no servidor.",
"%s email verification" : "Verificación do correo-e %s",
"Email verification" : "Verificación do correo-e",
@@ -23,18 +22,18 @@
"Groupware bundle" : "Paquete de software colaborativo",
"Hub bundle" : "Paquete de concentradores",
"Social sharing bundle" : "Paquete para compartir en redes sociais",
- "PHP %s or higher is required." : "Precisase PHP %s ou superior.",
- "PHP with a version lower than %s is required." : "Precisase PHP cunha versión inferior a %s.",
- "%sbit or higher PHP required." : "Precisase PHP para %sbits ou superior.",
+ "PHP %s or higher is required." : "Precísase de PHP %s ou superior.",
+ "PHP with a version lower than %s is required." : "Precísase de PHP cunha versión inferior a %s.",
+ "%sbit or higher PHP required." : "Precísase de PHP para %s bits ou superior.",
"The following architectures are supported: %s" : "Admítense as seguintes arquitecturas: %s",
"The following databases are supported: %s" : "Admítense as seguintes bases de datos: %s",
"The command line tool %s could not be found" : "Non foi posíbel atopar a ferramenta de liña de ordes %s",
"The library %s is not available." : "Non está dispoñíbel a biblioteca %s.",
- "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Precisase a biblioteca %1$s cunha versión superior a %2$s - dispoñíbel a versión %3$s.",
- "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Precisase a biblioteca %1$s cunha versión inferior a %2$s - dispoñíbel a versión %3$s.",
+ "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Precísase da biblioteca %1$s cunha versión superior a %2$s - dispoñíbel a versión %3$s.",
+ "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Precísase da biblioteca %1$s cunha versión inferior a %2$s - dispoñíbel a versión %3$s.",
"The following platforms are supported: %s" : "Admítense as seguintes plataformas: %s",
- "Server version %s or higher is required." : "Precisase a versión %s ou superior do servidor.",
- "Server version %s or lower is required." : "Precisase a versión %s ou inferior do servidor.",
+ "Server version %s or higher is required." : "Precísase da versión %s ou superior do servidor.",
+ "Server version %s or lower is required." : "Precísase da versión %s ou inferior do servidor.",
"Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "O usuario que accede debe ser un administrador, un subadministrador ou ter un dereito especial para acceder a esta configuración",
"Logged in user must be an admin or sub admin" : "O usuario autenticado debe ser un administrador ou subadministrador",
"Logged in user must be an admin" : "O usuario conectado debe ser un administrador",
@@ -213,7 +212,7 @@
"User disabled" : "Usuario desactivado",
"Login canceled by app" : "Acceso cancelado pola aplicación",
"App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Non é posíbel instalar a aplicación «%1$s» por mor de non cumprirse as dependencias: %2$s",
- "a safe home for all your data" : "un lugar seguro para todos os seus datos",
+ "a safe home for all your data" : "un acubillo seguro para todos os seus datos",
"File is currently busy, please try again later" : "O ficheiro está ocupado neste momento, ténteo máis adiante.",
"Cannot download file" : "Non é posíbel descargar o ficheiro",
"Application is not enabled" : "A aplicación non está activada",
@@ -241,7 +240,7 @@
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Isto probabelmente se debe unha caché/acelerador como Zend OPcache ou eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Instaláronse os módulos de PHP, mais aínda aparecen listados como perdidos?",
"Please ask your server administrator to restart the web server." : "Pídalle á administración do seu servidor que reinicie o servidor web.",
- "The required %s config variable is not configured in the config.php file." : "Precísase a variábel de configuración %s e non está configurada no ficheiro config.php.",
+ "The required %s config variable is not configured in the config.php file." : "Precísase da variábel de configuración %s e non está configurada no ficheiro config.php.",
"Please ask your server administrator to check the Nextcloud configuration." : "Pídalle á administración do seu servidor que verifique a configuración de Nextcloud.",
"Your data directory is readable by other users." : "Outros usuarios poden leer o seu directorio de datos.",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Cambie os permisos a 0770 para que o directorio non poida ser listado por outros usuarios.",
@@ -272,9 +271,10 @@
"Full name" : "Nome completo",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Acadouse o límite de usuarios e non se creou o usuario. Consulte as súas notificacións para obter máis información.",
"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Só os seguintes caracteres están permitidos nos nomes de usuario: «a-z», «A-Z», «0-9» e «_.@-'»",
- "libxml2 2.7.0 is at least required. Currently %s is installed." : "Precisase, cando menos, libxml2 2.7.0. Actualmente esta instalado %s.",
+ "libxml2 2.7.0 is at least required. Currently %s is installed." : "Precísase, cando menos, de libxml2 2.7.0. Actualmente esta instalado %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Para arranxar esta incidencia, actualice a versión de libxml2 e reinicie o servidor web. ",
- "PostgreSQL >= 9 required." : "Precisase PostgreSQL >= 9.",
- "Please upgrade your database version." : "Anove a versión da súa base de datos"
+ "PostgreSQL >= 9 required." : "Precísase de PostgreSQL >= 9.",
+ "Please upgrade your database version." : "Anove a versión da súa base de datos",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/hu.js b/lib/l10n/hu.js
index 54aa8d7f36a..81908533395 100644
--- a/lib/l10n/hu.js
+++ b/lib/l10n/hu.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "A(z) %1$s alkalmazás nincs jelen, vagy a verziója nem kompatibilis ezzel a kiszolgálóval. Ellenőrizze az alkalmazástárat.",
"Sample configuration detected" : "Példabeállítások észlelve",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Úgy tűnik, hogy a példakonfigurációt másolta le. Ez működésképtelenné teheti a telepítést, és nem támogatott. Olvassa el a dokumentációt, mielőtt módosításokat véget a config.php fájlban.",
- "404" : "404",
"The page could not be found on the server." : "Az oldal nem található a kiszolgálón.",
"%s email verification" : "%s e-mail ellenőrzés",
"Email verification" : "E-mail ellenőrzés",
@@ -156,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$s megosztotta Önnel: „%2$s”.",
"Click the button below to open it." : "Kattintson a lenti gombra a megnyitáshoz.",
"The requested share does not exist anymore" : "A kért megosztás már nem létezik",
+ "The requested share comes from a disabled user" : "A kért megosztás letiltott felhasználótól származik",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "A felhasználó nem jött létre, mert elérte a felhasználókorlátot. Nézze meg az értesítéseit, hogy többet tudjon meg.",
"Could not find category \"%s\"" : "Ez a kategória nem található: \"%s\"",
"Sunday" : "Vasárnap",
@@ -261,6 +261,14 @@ OC.L10N.register(
"Storage connection error. %s" : "Tároló kapcsolódási hiba. %s",
"Storage is temporarily not available" : "A tároló átmenetileg nem érhető el",
"Storage connection timeout. %s" : "Időtúllépés a tárolókapcsolatban. %s",
+ "Free prompt" : "Szabad prompt",
+ "Runs an arbitrary prompt through the language model." : "Tetszőleges promptot futtat a nyelvi modellen.",
+ "Generate headline" : "Címsor generálás",
+ "Generates a possible headline for a text." : "Lehetséges címsort generál a szövegnek.",
+ "Summarize" : "Összesítés",
+ "Summarizes text by reducing its length without losing key information." : "Összesíti a szöveget a hosszúság csökkentésével anélkül, hogy a kulcs információk elvesznének.",
+ "Extract topics" : "Témák kibontása",
+ "Extracts topics from a text and outputs them separated by commas." : "Kibontja a témákat a szövegből és vesszővel elválasztva megjeleníti",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "A(z) %1$s alkalmazás fájljait helytelenül cserélték le. Ellenőrizze, hogy a verzió kompatibilis-e a kiszolgálóval.",
"Full name" : "Teljes név",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Elérte a felhasználókorlátot, és a felhasználó nem jött létre. Nézze meg az értesítéseit, hogy többet tudjon meg.",
@@ -268,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Legalább libxml2 2.7.0 szükséges. Jelenleg telepített: %s.",
"To fix this issue update your libxml2 version and restart your web server." : "A probléma javításához frissítse a libxml2 verziót, és indítsa újra a webkiszolgálót.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 szükséges.",
- "Please upgrade your database version." : "Frissítse az adatbázis verzióját."
+ "Please upgrade your database version." : "Frissítse az adatbázis verzióját.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/hu.json b/lib/l10n/hu.json
index 1f4c8021868..6221e5c9a02 100644
--- a/lib/l10n/hu.json
+++ b/lib/l10n/hu.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "A(z) %1$s alkalmazás nincs jelen, vagy a verziója nem kompatibilis ezzel a kiszolgálóval. Ellenőrizze az alkalmazástárat.",
"Sample configuration detected" : "Példabeállítások észlelve",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Úgy tűnik, hogy a példakonfigurációt másolta le. Ez működésképtelenné teheti a telepítést, és nem támogatott. Olvassa el a dokumentációt, mielőtt módosításokat véget a config.php fájlban.",
- "404" : "404",
"The page could not be found on the server." : "Az oldal nem található a kiszolgálón.",
"%s email verification" : "%s e-mail ellenőrzés",
"Email verification" : "E-mail ellenőrzés",
@@ -154,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$s megosztotta Önnel: „%2$s”.",
"Click the button below to open it." : "Kattintson a lenti gombra a megnyitáshoz.",
"The requested share does not exist anymore" : "A kért megosztás már nem létezik",
+ "The requested share comes from a disabled user" : "A kért megosztás letiltott felhasználótól származik",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "A felhasználó nem jött létre, mert elérte a felhasználókorlátot. Nézze meg az értesítéseit, hogy többet tudjon meg.",
"Could not find category \"%s\"" : "Ez a kategória nem található: \"%s\"",
"Sunday" : "Vasárnap",
@@ -259,6 +259,14 @@
"Storage connection error. %s" : "Tároló kapcsolódási hiba. %s",
"Storage is temporarily not available" : "A tároló átmenetileg nem érhető el",
"Storage connection timeout. %s" : "Időtúllépés a tárolókapcsolatban. %s",
+ "Free prompt" : "Szabad prompt",
+ "Runs an arbitrary prompt through the language model." : "Tetszőleges promptot futtat a nyelvi modellen.",
+ "Generate headline" : "Címsor generálás",
+ "Generates a possible headline for a text." : "Lehetséges címsort generál a szövegnek.",
+ "Summarize" : "Összesítés",
+ "Summarizes text by reducing its length without losing key information." : "Összesíti a szöveget a hosszúság csökkentésével anélkül, hogy a kulcs információk elvesznének.",
+ "Extract topics" : "Témák kibontása",
+ "Extracts topics from a text and outputs them separated by commas." : "Kibontja a témákat a szövegből és vesszővel elválasztva megjeleníti",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "A(z) %1$s alkalmazás fájljait helytelenül cserélték le. Ellenőrizze, hogy a verzió kompatibilis-e a kiszolgálóval.",
"Full name" : "Teljes név",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Elérte a felhasználókorlátot, és a felhasználó nem jött létre. Nézze meg az értesítéseit, hogy többet tudjon meg.",
@@ -266,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Legalább libxml2 2.7.0 szükséges. Jelenleg telepített: %s.",
"To fix this issue update your libxml2 version and restart your web server." : "A probléma javításához frissítse a libxml2 verziót, és indítsa újra a webkiszolgálót.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 szükséges.",
- "Please upgrade your database version." : "Frissítse az adatbázis verzióját."
+ "Please upgrade your database version." : "Frissítse az adatbázis verzióját.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/it.js b/lib/l10n/it.js
index 52cf1721362..53082d65cf9 100644
--- a/lib/l10n/it.js
+++ b/lib/l10n/it.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "L'applicazione %1$s non è presente o ha una versione non compatibile con questo server. Controlla l'elenco delle app.",
"Sample configuration detected" : "Configurazione di esempio rilevata",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "È stato rilevato che la configurazione di esempio è stata copiata. Ciò può compromettere la tua installazione e non è supportato. Leggi la documentazione prima di modificare il file config.php",
- "404" : "404",
"The page could not be found on the server." : "Impossibile trovare la pagina sul server.",
"%s email verification" : "Verifica email di %s",
"Email verification" : "Verifica email",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "È richiesta almeno la versione 2.7.0 di libxml2. Quella attualmente installata è la %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Per risolvere questo problema, aggiorna la tua versione di libxml2 e riavvia il server web.",
"PostgreSQL >= 9 required." : "Richiesto PostgreSQL >= 9.",
- "Please upgrade your database version." : "Aggiorna la versione del tuo database."
+ "Please upgrade your database version." : "Aggiorna la versione del tuo database.",
+ "404" : "404"
},
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
diff --git a/lib/l10n/it.json b/lib/l10n/it.json
index 8fb450a83fd..104c01a1439 100644
--- a/lib/l10n/it.json
+++ b/lib/l10n/it.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "L'applicazione %1$s non è presente o ha una versione non compatibile con questo server. Controlla l'elenco delle app.",
"Sample configuration detected" : "Configurazione di esempio rilevata",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "È stato rilevato che la configurazione di esempio è stata copiata. Ciò può compromettere la tua installazione e non è supportato. Leggi la documentazione prima di modificare il file config.php",
- "404" : "404",
"The page could not be found on the server." : "Impossibile trovare la pagina sul server.",
"%s email verification" : "Verifica email di %s",
"Email verification" : "Verifica email",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "È richiesta almeno la versione 2.7.0 di libxml2. Quella attualmente installata è la %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Per risolvere questo problema, aggiorna la tua versione di libxml2 e riavvia il server web.",
"PostgreSQL >= 9 required." : "Richiesto PostgreSQL >= 9.",
- "Please upgrade your database version." : "Aggiorna la versione del tuo database."
+ "Please upgrade your database version." : "Aggiorna la versione del tuo database.",
+ "404" : "404"
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
} \ No newline at end of file
diff --git a/lib/l10n/ja.js b/lib/l10n/ja.js
index a58519f02ca..18cc0e5633c 100644
--- a/lib/l10n/ja.js
+++ b/lib/l10n/ja.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "アプリケーション%1$sが存在しないか、このサーバと互換性のないバージョンがあります。apps ディレクトリを確認してください。",
"Sample configuration detected" : "サンプル設定が見つかりました。",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "サンプル設定がコピーされてそのままです。このままではインストールが失敗し、サポート対象外になります。config.phpを変更する前にドキュメントを確認してください。",
- "404" : "404",
"The page could not be found on the server." : "ページがサーバー上に見つかりませんでした。",
"%s email verification" : "%sメールによる確認",
"Email verification" : "メールによる確認",
@@ -156,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$sが あなたと »%2$s« を共有しました。",
"Click the button below to open it." : "開くには下のボタンをクリック",
"The requested share does not exist anymore" : "この共有はもう存在しません。",
+ "The requested share comes from a disabled user" : "要求された共有は無効化されたユーザーからのものです。",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "登録可能なユーザー数の上限に達したため、ユーザーは作成されませんでした。詳細については通知をご確認ください。",
"Could not find category \"%s\"" : "カテゴリ \"%s\" が見つかりませんでした",
"Sunday" : "日曜日",
@@ -276,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 バージョン 2.7.0 が最低必要です。現在 %s がインストールされています。",
"To fix this issue update your libxml2 version and restart your web server." : "この問題を解決するには、libxml2 を更新して、Webサーバーを再起動してください。",
"PostgreSQL >= 9 required." : "PostgreSQL 9以上が必要です",
- "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。"
+ "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。",
+ "404" : "404"
},
"nplurals=1; plural=0;");
diff --git a/lib/l10n/ja.json b/lib/l10n/ja.json
index f45d9b5e93f..2d62cdecb16 100644
--- a/lib/l10n/ja.json
+++ b/lib/l10n/ja.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "アプリケーション%1$sが存在しないか、このサーバと互換性のないバージョンがあります。apps ディレクトリを確認してください。",
"Sample configuration detected" : "サンプル設定が見つかりました。",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "サンプル設定がコピーされてそのままです。このままではインストールが失敗し、サポート対象外になります。config.phpを変更する前にドキュメントを確認してください。",
- "404" : "404",
"The page could not be found on the server." : "ページがサーバー上に見つかりませんでした。",
"%s email verification" : "%sメールによる確認",
"Email verification" : "メールによる確認",
@@ -154,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$sが あなたと »%2$s« を共有しました。",
"Click the button below to open it." : "開くには下のボタンをクリック",
"The requested share does not exist anymore" : "この共有はもう存在しません。",
+ "The requested share comes from a disabled user" : "要求された共有は無効化されたユーザーからのものです。",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "登録可能なユーザー数の上限に達したため、ユーザーは作成されませんでした。詳細については通知をご確認ください。",
"Could not find category \"%s\"" : "カテゴリ \"%s\" が見つかりませんでした",
"Sunday" : "日曜日",
@@ -274,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 バージョン 2.7.0 が最低必要です。現在 %s がインストールされています。",
"To fix this issue update your libxml2 version and restart your web server." : "この問題を解決するには、libxml2 を更新して、Webサーバーを再起動してください。",
"PostgreSQL >= 9 required." : "PostgreSQL 9以上が必要です",
- "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。"
+ "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。",
+ "404" : "404"
},"pluralForm" :"nplurals=1; plural=0;"
} \ No newline at end of file
diff --git a/lib/l10n/lt_LT.js b/lib/l10n/lt_LT.js
index 676a5bf51dc..0bb3bf18e90 100644
--- a/lib/l10n/lt_LT.js
+++ b/lib/l10n/lt_LT.js
@@ -71,6 +71,7 @@ OC.L10N.register(
"Appearance and accessibility" : "Išvaizda ir prieinamumas",
"Apps" : "Programėlės",
"Personal settings" : "Asmeniniai nustatymai",
+ "Administration settings" : "Administravimo nustatymai",
"Settings" : "Nustatymai",
"Log out" : "Atsijungti",
"Users" : "Naudotojai",
diff --git a/lib/l10n/lt_LT.json b/lib/l10n/lt_LT.json
index 0085d5d35fc..a2ef9c04ee2 100644
--- a/lib/l10n/lt_LT.json
+++ b/lib/l10n/lt_LT.json
@@ -69,6 +69,7 @@
"Appearance and accessibility" : "Išvaizda ir prieinamumas",
"Apps" : "Programėlės",
"Personal settings" : "Asmeniniai nustatymai",
+ "Administration settings" : "Administravimo nustatymai",
"Settings" : "Nustatymai",
"Log out" : "Atsijungti",
"Users" : "Naudotojai",
diff --git a/lib/l10n/lv.js b/lib/l10n/lv.js
index a94b81c32bd..c33558ceb0c 100644
--- a/lib/l10n/lv.js
+++ b/lib/l10n/lv.js
@@ -5,7 +5,6 @@ OC.L10N.register(
"See %s" : "Skatīt %s",
"Sample configuration detected" : "Atrasta konfigurācijas paraugs",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Konstatēts, ka paraug konfigurācija ir nokopēta. Tas var izjaukt jūsu instalāciju un nav atbalstīts. Lūdzu, izlasiet dokumentāciju, pirms veicat izmaiņas config.php",
- "404" : "404",
"%s email verification" : "%s e-pasta pārbaude",
"Email verification" : "E-pasta pārbaude",
"Click the following button to confirm your email." : "Noklikšķiniet uz šīs pogas, lai apstiprinātu savu e-pastu.",
@@ -132,6 +131,7 @@ OC.L10N.register(
"Storage connection error. %s" : "Datu savienojuma kļūda. %s",
"Storage is temporarily not available" : "Glabātuve īslaicīgi nav pieejama",
"Storage connection timeout. %s" : "Datu savienojuma taimauts. %s",
- "Full name" : "Pilns vārds"
+ "Full name" : "Pilns vārds",
+ "404" : "404"
},
"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);");
diff --git a/lib/l10n/lv.json b/lib/l10n/lv.json
index 8df04cbdcc1..b05c244729c 100644
--- a/lib/l10n/lv.json
+++ b/lib/l10n/lv.json
@@ -3,7 +3,6 @@
"See %s" : "Skatīt %s",
"Sample configuration detected" : "Atrasta konfigurācijas paraugs",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Konstatēts, ka paraug konfigurācija ir nokopēta. Tas var izjaukt jūsu instalāciju un nav atbalstīts. Lūdzu, izlasiet dokumentāciju, pirms veicat izmaiņas config.php",
- "404" : "404",
"%s email verification" : "%s e-pasta pārbaude",
"Email verification" : "E-pasta pārbaude",
"Click the following button to confirm your email." : "Noklikšķiniet uz šīs pogas, lai apstiprinātu savu e-pastu.",
@@ -130,6 +129,7 @@
"Storage connection error. %s" : "Datu savienojuma kļūda. %s",
"Storage is temporarily not available" : "Glabātuve īslaicīgi nav pieejama",
"Storage connection timeout. %s" : "Datu savienojuma taimauts. %s",
- "Full name" : "Pilns vārds"
+ "Full name" : "Pilns vārds",
+ "404" : "404"
},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"
} \ No newline at end of file
diff --git a/lib/l10n/mk.js b/lib/l10n/mk.js
index bda43400d66..3c92f5e619e 100644
--- a/lib/l10n/mk.js
+++ b/lib/l10n/mk.js
@@ -7,7 +7,6 @@ OC.L10N.register(
"See %s" : "Види %s",
"Sample configuration detected" : "Детектирана е едноставна конфигурација",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Детектирано е дека едноставната конфигурација е копирана. Ова може да и наштети на вашата инсталација и не е поддржано. Прочитајте ја документацијата пред да правите промени во config.php",
- "404" : "404",
"The page could not be found on the server." : "Страницата не е пронајдена на серверот.",
"%s email verification" : "%s е-пошта верификација",
"Email verification" : "Е-пошта верификација",
@@ -260,6 +259,7 @@ OC.L10N.register(
"Storage connection error. %s" : "Грешка во конекција до складиштето. %s",
"Storage is temporarily not available" : "Складиштето моментално не е достапно",
"Storage connection timeout. %s" : "Поврзувањето со складиштето не успеа. %s",
+ "Generate headline" : "Генерирај заглавие",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Датотеките од аоликацијата %1$s не се преклопени коректно. Проверете дали верзијата е компатибилна со серверот.",
"Full name" : "Цело име",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Максималниот број на корисници е достигнат. Проверете ги вашите известувања за да дознаете повеќе.",
@@ -267,6 +267,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Потербна минимална верзија на libxml2 е 2.7.0. Моментална верзија е %s.",
"To fix this issue update your libxml2 version and restart your web server." : "За да го поправите овој проблем, ажурирајте ја верзијата на libxml2 и рестартирајте го вашиот веб сервер.",
"PostgreSQL >= 9 required." : "Потребно е PostgreSQL >= 9 ",
- "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци"
+ "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци",
+ "404" : "404"
},
"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;");
diff --git a/lib/l10n/mk.json b/lib/l10n/mk.json
index 3177350622d..3599550f3ed 100644
--- a/lib/l10n/mk.json
+++ b/lib/l10n/mk.json
@@ -5,7 +5,6 @@
"See %s" : "Види %s",
"Sample configuration detected" : "Детектирана е едноставна конфигурација",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Детектирано е дека едноставната конфигурација е копирана. Ова може да и наштети на вашата инсталација и не е поддржано. Прочитајте ја документацијата пред да правите промени во config.php",
- "404" : "404",
"The page could not be found on the server." : "Страницата не е пронајдена на серверот.",
"%s email verification" : "%s е-пошта верификација",
"Email verification" : "Е-пошта верификација",
@@ -258,6 +257,7 @@
"Storage connection error. %s" : "Грешка во конекција до складиштето. %s",
"Storage is temporarily not available" : "Складиштето моментално не е достапно",
"Storage connection timeout. %s" : "Поврзувањето со складиштето не успеа. %s",
+ "Generate headline" : "Генерирај заглавие",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Датотеките од аоликацијата %1$s не се преклопени коректно. Проверете дали верзијата е компатибилна со серверот.",
"Full name" : "Цело име",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Максималниот број на корисници е достигнат. Проверете ги вашите известувања за да дознаете повеќе.",
@@ -265,6 +265,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Потербна минимална верзија на libxml2 е 2.7.0. Моментална верзија е %s.",
"To fix this issue update your libxml2 version and restart your web server." : "За да го поправите овој проблем, ажурирајте ја верзијата на libxml2 и рестартирајте го вашиот веб сервер.",
"PostgreSQL >= 9 required." : "Потребно е PostgreSQL >= 9 ",
- "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци"
+ "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"
} \ No newline at end of file
diff --git a/lib/l10n/nb.js b/lib/l10n/nb.js
index 23651c77213..ea0856ccd8a 100644
--- a/lib/l10n/nb.js
+++ b/lib/l10n/nb.js
@@ -5,7 +5,6 @@ OC.L10N.register(
"See %s" : "Se %s",
"Sample configuration detected" : "Eksempeloppsett oppdaget",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det ble oppdaget at eksempeloppsettet er blitt kopiert. Dette kan ødelegge installasjonen din og støttes ikke. Les dokumentasjonen før du gjør endringer i config.php",
- "404" : "404",
"The page could not be found on the server." : "Siden ble ikke funnet på serveren.",
"%s email verification" : "%s e-postbekreftelse",
"Email verification" : "E-postbekreftelse",
@@ -198,6 +197,7 @@ OC.L10N.register(
"Full name" : "Fullt navn",
"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Bare disse tegnene tillates i et brukernavn: \"a-z\", \"A-Z\", \"0-9\" og \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Krever minst libxml2 2.7.0. Per nå er %s installert.",
- "To fix this issue update your libxml2 version and restart your web server." : "For å fikse dette problemet, oppdater din libxml2 versjon og start webserveren på nytt."
+ "To fix this issue update your libxml2 version and restart your web server." : "For å fikse dette problemet, oppdater din libxml2 versjon og start webserveren på nytt.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/nb.json b/lib/l10n/nb.json
index 32b5ae98957..627d3df471d 100644
--- a/lib/l10n/nb.json
+++ b/lib/l10n/nb.json
@@ -3,7 +3,6 @@
"See %s" : "Se %s",
"Sample configuration detected" : "Eksempeloppsett oppdaget",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det ble oppdaget at eksempeloppsettet er blitt kopiert. Dette kan ødelegge installasjonen din og støttes ikke. Les dokumentasjonen før du gjør endringer i config.php",
- "404" : "404",
"The page could not be found on the server." : "Siden ble ikke funnet på serveren.",
"%s email verification" : "%s e-postbekreftelse",
"Email verification" : "E-postbekreftelse",
@@ -196,6 +195,7 @@
"Full name" : "Fullt navn",
"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Bare disse tegnene tillates i et brukernavn: \"a-z\", \"A-Z\", \"0-9\" og \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Krever minst libxml2 2.7.0. Per nå er %s installert.",
- "To fix this issue update your libxml2 version and restart your web server." : "For å fikse dette problemet, oppdater din libxml2 versjon og start webserveren på nytt."
+ "To fix this issue update your libxml2 version and restart your web server." : "For å fikse dette problemet, oppdater din libxml2 versjon og start webserveren på nytt.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/nl.js b/lib/l10n/nl.js
index 068552cebb0..5aa59620e0c 100644
--- a/lib/l10n/nl.js
+++ b/lib/l10n/nl.js
@@ -94,6 +94,7 @@ OC.L10N.register(
"Appearance and accessibility" : "Weergave en toegankelijkheid",
"Apps" : "Apps",
"Personal settings" : "Persoonlijke instellingen",
+ "Administration settings" : "Beheerder instellingen",
"Settings" : "Instellingen",
"Log out" : "Uitloggen",
"Users" : "Gebruikers",
diff --git a/lib/l10n/nl.json b/lib/l10n/nl.json
index a7c9066de3a..c4ab53ebcd1 100644
--- a/lib/l10n/nl.json
+++ b/lib/l10n/nl.json
@@ -92,6 +92,7 @@
"Appearance and accessibility" : "Weergave en toegankelijkheid",
"Apps" : "Apps",
"Personal settings" : "Persoonlijke instellingen",
+ "Administration settings" : "Beheerder instellingen",
"Settings" : "Instellingen",
"Log out" : "Uitloggen",
"Users" : "Gebruikers",
diff --git a/lib/l10n/pl.js b/lib/l10n/pl.js
index 68de01a6171..3dbae410a38 100644
--- a/lib/l10n/pl.js
+++ b/lib/l10n/pl.js
@@ -5,9 +5,9 @@ OC.L10N.register(
"This can usually be fixed by giving the web server write access to the config directory." : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config.",
"But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ale jeśli wolisz, aby plik config.php był tylko do odczytu, ustaw w nim opcję \"config_is_read_only\" na true.",
"See %s" : "Zobacz %s",
+ "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplikacja %1$s nie jest dostępna lub ma wersję niekompatybilną z tym serwerem. Sprawdź katalog aplikacji.",
"Sample configuration detected" : "Wykryto przykładową konfigurację",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Wykryto, że przykładowa konfiguracja została skopiowana. Może to spowodować przerwanie instalacji, która nie jest wspierana. Przeczytaj dokumentację przed dokonaniem zmian w pliku config.php",
- "404" : "404",
"The page could not be found on the server." : "Nie znaleziono strony na serwerze.",
"%s email verification" : "Weryfikacja adresu e-mail %s",
"Email verification" : "Weryfikacja adresu e-mail",
@@ -113,6 +113,7 @@ OC.L10N.register(
"Address" : "Adres",
"Profile picture" : "Zdjęcie profilowe",
"About" : "Informacje",
+ "Display name" : "Wyświetlana nazwa",
"Headline" : "Nagłówek",
"Organisation" : "Organizacja",
"Role" : "Rola społeczna",
@@ -154,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$s udostępnił Tobie »%2$s«.",
"Click the button below to open it." : "Kliknij przycisk poniżej, aby otworzyć.",
"The requested share does not exist anymore" : "Żądane udostępnienie już nie istnieje",
+ "The requested share comes from a disabled user" : "Żądane udostępnienie pochodzi od wyłączonego użytkownika",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Użytkownik nie został utworzony, ponieważ osiągnięto limit użytkowników. Sprawdź swoje powiadomienia, aby dowiedzieć się więcej.",
"Could not find category \"%s\"" : "Nie można znaleźć kategorii \"%s\"",
"Sunday" : "Niedziela",
@@ -259,6 +261,14 @@ OC.L10N.register(
"Storage connection error. %s" : "Błąd połączenia z magazynem. %s",
"Storage is temporarily not available" : "Magazyn jest tymczasowo niedostępny",
"Storage connection timeout. %s" : "Limit czasu połączenia do magazynu. %s",
+ "Free prompt" : "Monit bezpłatny",
+ "Runs an arbitrary prompt through the language model." : "Uruchamia dowolny monit w modelu języka.",
+ "Generate headline" : "Wygeneruj nagłówek",
+ "Generates a possible headline for a text." : "Generuje możliwy nagłówek tekstu.",
+ "Summarize" : "Podsumuj",
+ "Summarizes text by reducing its length without losing key information." : "Podsumowuje tekst, zmniejszając jego długość bez utraty kluczowych informacji.",
+ "Extract topics" : "Wyodrębnij tematy",
+ "Extracts topics from a text and outputs them separated by commas." : "Wyodrębnia tematy z tekstu i wyświetla je oddzielone przecinkami.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Pliki aplikacji %1$s nie zostały poprawnie zastąpione. Upewnij się, że jest to wersja zgodna z serwerem.",
"Full name" : "Pełna nazwa",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Osiągnięto limit użytkowników i użytkownik nie został utworzony. Sprawdź swoje powiadomienia, aby dowiedzieć się więcej.",
@@ -266,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Wymagana wersja dla libxml2 to przynajmniej 2.7.0. Aktualnie zainstalowana jest %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Aby rozwiązać ten problem, zaktualizuj wersję libxml2 i ponownie uruchom serwer WWW.",
"PostgreSQL >= 9 required." : "Wymagany PostgreSQL >= 9",
- "Please upgrade your database version." : "Zaktualizuj wersję bazy danych."
+ "Please upgrade your database version." : "Zaktualizuj wersję bazy danych.",
+ "404" : "404"
},
"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);");
diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json
index ea9d21d3b76..2e5188eba18 100644
--- a/lib/l10n/pl.json
+++ b/lib/l10n/pl.json
@@ -3,9 +3,9 @@
"This can usually be fixed by giving the web server write access to the config directory." : "Zwykle można to naprawić, nadając serwerowi WWW dostęp do zapisu do katalogu config.",
"But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ale jeśli wolisz, aby plik config.php był tylko do odczytu, ustaw w nim opcję \"config_is_read_only\" na true.",
"See %s" : "Zobacz %s",
+ "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplikacja %1$s nie jest dostępna lub ma wersję niekompatybilną z tym serwerem. Sprawdź katalog aplikacji.",
"Sample configuration detected" : "Wykryto przykładową konfigurację",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Wykryto, że przykładowa konfiguracja została skopiowana. Może to spowodować przerwanie instalacji, która nie jest wspierana. Przeczytaj dokumentację przed dokonaniem zmian w pliku config.php",
- "404" : "404",
"The page could not be found on the server." : "Nie znaleziono strony na serwerze.",
"%s email verification" : "Weryfikacja adresu e-mail %s",
"Email verification" : "Weryfikacja adresu e-mail",
@@ -111,6 +111,7 @@
"Address" : "Adres",
"Profile picture" : "Zdjęcie profilowe",
"About" : "Informacje",
+ "Display name" : "Wyświetlana nazwa",
"Headline" : "Nagłówek",
"Organisation" : "Organizacja",
"Role" : "Rola społeczna",
@@ -152,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$s udostępnił Tobie »%2$s«.",
"Click the button below to open it." : "Kliknij przycisk poniżej, aby otworzyć.",
"The requested share does not exist anymore" : "Żądane udostępnienie już nie istnieje",
+ "The requested share comes from a disabled user" : "Żądane udostępnienie pochodzi od wyłączonego użytkownika",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Użytkownik nie został utworzony, ponieważ osiągnięto limit użytkowników. Sprawdź swoje powiadomienia, aby dowiedzieć się więcej.",
"Could not find category \"%s\"" : "Nie można znaleźć kategorii \"%s\"",
"Sunday" : "Niedziela",
@@ -257,6 +259,14 @@
"Storage connection error. %s" : "Błąd połączenia z magazynem. %s",
"Storage is temporarily not available" : "Magazyn jest tymczasowo niedostępny",
"Storage connection timeout. %s" : "Limit czasu połączenia do magazynu. %s",
+ "Free prompt" : "Monit bezpłatny",
+ "Runs an arbitrary prompt through the language model." : "Uruchamia dowolny monit w modelu języka.",
+ "Generate headline" : "Wygeneruj nagłówek",
+ "Generates a possible headline for a text." : "Generuje możliwy nagłówek tekstu.",
+ "Summarize" : "Podsumuj",
+ "Summarizes text by reducing its length without losing key information." : "Podsumowuje tekst, zmniejszając jego długość bez utraty kluczowych informacji.",
+ "Extract topics" : "Wyodrębnij tematy",
+ "Extracts topics from a text and outputs them separated by commas." : "Wyodrębnia tematy z tekstu i wyświetla je oddzielone przecinkami.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Pliki aplikacji %1$s nie zostały poprawnie zastąpione. Upewnij się, że jest to wersja zgodna z serwerem.",
"Full name" : "Pełna nazwa",
"The user limit has been reached and the user was not created. Check your notifications to learn more." : "Osiągnięto limit użytkowników i użytkownik nie został utworzony. Sprawdź swoje powiadomienia, aby dowiedzieć się więcej.",
@@ -264,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Wymagana wersja dla libxml2 to przynajmniej 2.7.0. Aktualnie zainstalowana jest %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Aby rozwiązać ten problem, zaktualizuj wersję libxml2 i ponownie uruchom serwer WWW.",
"PostgreSQL >= 9 required." : "Wymagany PostgreSQL >= 9",
- "Please upgrade your database version." : "Zaktualizuj wersję bazy danych."
+ "Please upgrade your database version." : "Zaktualizuj wersję bazy danych.",
+ "404" : "404"
},"pluralForm" :"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);"
} \ No newline at end of file
diff --git a/lib/l10n/pt_BR.js b/lib/l10n/pt_BR.js
index 265de0ee0a8..114a6e86471 100644
--- a/lib/l10n/pt_BR.js
+++ b/lib/l10n/pt_BR.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplicação %1$s não está presente ou possui uma versão não compatível com este servidor. Verifique o diretório de aplicativos.",
"Sample configuration detected" : "Configuração de exemplo detectada",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Foi detectado que a configuração de exemplo foi copiada. Isso pode terminar sua instalação e não é suportado. Por favor leia a documentação antes de realizar mudanças no config.php",
- "404" : "404",
"The page could not be found on the server." : "A página não pôde ser encontrada no servidor.",
"%s email verification" : "%s e-mail de verificação",
"Email verification" : "E-mail de verificação",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "A libxml2 2.7.0 é a versão mínima necessária. Atualmente a versão %s está instalada.",
"To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema, atualize a versão da sua libxml2 e reinicie seu servidor web.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 é necessário.",
- "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados."
+ "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados.",
+ "404" : "404"
},
"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
diff --git a/lib/l10n/pt_BR.json b/lib/l10n/pt_BR.json
index 2609d133dd4..98571e5a453 100644
--- a/lib/l10n/pt_BR.json
+++ b/lib/l10n/pt_BR.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplicação %1$s não está presente ou possui uma versão não compatível com este servidor. Verifique o diretório de aplicativos.",
"Sample configuration detected" : "Configuração de exemplo detectada",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Foi detectado que a configuração de exemplo foi copiada. Isso pode terminar sua instalação e não é suportado. Por favor leia a documentação antes de realizar mudanças no config.php",
- "404" : "404",
"The page could not be found on the server." : "A página não pôde ser encontrada no servidor.",
"%s email verification" : "%s e-mail de verificação",
"Email verification" : "E-mail de verificação",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "A libxml2 2.7.0 é a versão mínima necessária. Atualmente a versão %s está instalada.",
"To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema, atualize a versão da sua libxml2 e reinicie seu servidor web.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 é necessário.",
- "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados."
+ "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados.",
+ "404" : "404"
},"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
} \ No newline at end of file
diff --git a/lib/l10n/ro.js b/lib/l10n/ro.js
index d08182f5786..50a950320a7 100644
--- a/lib/l10n/ro.js
+++ b/lib/l10n/ro.js
@@ -5,6 +5,7 @@ OC.L10N.register(
"See %s" : "Vezi %s",
"Sample configuration detected" : "A fost detectată o configurație exemplu",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S-a detectat copierea configurației exemplu. Acest lucru poate duce la oprirea instanței tale și nu este suportat. Te rugăm să citești documentația înainte de a face modificări în fișierul config.php",
+ "The page could not be found on the server." : "Pagina nu a fost găsită.",
"Other activities" : "Alte activități",
"%1$s and %2$s" : "%1$s și %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s și %3$s",
@@ -18,6 +19,7 @@ OC.L10N.register(
"Authentication" : "Autentificare",
"Unknown filetype" : "Tip fișier necunoscut",
"Invalid image" : "Imagine invalidă",
+ "Avatar image is not square" : "Imaginea de avatar nu este pătrată",
"Files" : "Fișiere",
"View profile" : "Vezi profilul",
"Local time: %s" : "Timp local: %s",
diff --git a/lib/l10n/ro.json b/lib/l10n/ro.json
index 5793338e064..862d25e9651 100644
--- a/lib/l10n/ro.json
+++ b/lib/l10n/ro.json
@@ -3,6 +3,7 @@
"See %s" : "Vezi %s",
"Sample configuration detected" : "A fost detectată o configurație exemplu",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "S-a detectat copierea configurației exemplu. Acest lucru poate duce la oprirea instanței tale și nu este suportat. Te rugăm să citești documentația înainte de a face modificări în fișierul config.php",
+ "The page could not be found on the server." : "Pagina nu a fost găsită.",
"Other activities" : "Alte activități",
"%1$s and %2$s" : "%1$s și %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s și %3$s",
@@ -16,6 +17,7 @@
"Authentication" : "Autentificare",
"Unknown filetype" : "Tip fișier necunoscut",
"Invalid image" : "Imagine invalidă",
+ "Avatar image is not square" : "Imaginea de avatar nu este pătrată",
"Files" : "Fișiere",
"View profile" : "Vezi profilul",
"Local time: %s" : "Timp local: %s",
diff --git a/lib/l10n/ru.js b/lib/l10n/ru.js
index c4eccdb7313..530edd767a0 100644
--- a/lib/l10n/ru.js
+++ b/lib/l10n/ru.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Приложение %1$s отсутствует или его версия несовместима с этим сервером. Пожалуйста, проверьте каталог приложений.",
"Sample configuration detected" : "Обнаружена конфигурация из примера.",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Была обнаружена конфигурация из примера. Такая конфигурация не поддерживается и может повредить вашей системе. Прочтите документацию перед внесением изменений в файл config.php",
- "404" : "404",
"The page could not be found on the server." : "Страница не найдена на сервере.",
"%s email verification" : "Проверка почтового адреса %s",
"Email verification" : "Подтверждение адреса электронной почты",
@@ -96,7 +95,7 @@ OC.L10N.register(
"Help" : "Помощь",
"Appearance and accessibility" : "Внешний вид и доступность",
"Apps" : "Приложения",
- "Personal settings" : "Параметры пользователя",
+ "Personal settings" : "Личные настройки",
"Administration settings" : "Параметры сервера",
"Settings" : "Настройки",
"Log out" : "Выйти",
@@ -156,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$s предоставил(а) вам доступ к «%2$s».",
"Click the button below to open it." : "Нажмите расположенную ниже кнопку для перехода к полученному общему ресурсу.",
"The requested share does not exist anymore" : "Запрошенный общий ресурс более не существует.",
+ "The requested share comes from a disabled user" : "Запрос на общий доступ поступает от отключенного пользователя",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Пользователь не был создан, достигнуто ограничение количества пользователей. Для получения дополнительных сведений проверьте уведомления.",
"Could not find category \"%s\"" : "Категория «%s» не найдена",
"Sunday" : "Воскресенье",
@@ -261,6 +261,12 @@ OC.L10N.register(
"Storage connection error. %s" : "Ошибка подключения к хранилищу. %s",
"Storage is temporarily not available" : "Хранилище временно недоступно",
"Storage connection timeout. %s" : "Истекло время ожидания подключения к хранилищу. %s",
+ "Free prompt" : "Свободная подсказка",
+ "Runs an arbitrary prompt through the language model." : "Запускает произвольное приглашение через языковую модель.",
+ "Generate headline" : "Сгенерировать заголовок",
+ "Generates a possible headline for a text." : "Генерирует возможный заголовок для текста.",
+ "Summarize" : "Обобщить",
+ "Summarizes text by reducing its length without losing key information." : "Обобщает текст, сокращая его длину без потери ключевой информации.",
"Extract topics" : "Извлечь темы",
"Extracts topics from a text and outputs them separated by commas." : "Извлекает темы из текста и выводит их через запятую.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файлы приложения %1$s не были заменены корректно. Удостоверьтесь, что устанавливаемая версия этого приложения совместима с версией сервера.",
@@ -270,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Требуется как минимум libxml2 версии 2.7.0. На данный момент установлена %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Для исправления этой ошибки обновите версию libxml2 и перезапустите ваш веб-сервер.",
"PostgreSQL >= 9 required." : "Требуется PostgreSQL версии 9 или более новый.",
- "Please upgrade your database version." : "Обновите базу данных."
+ "Please upgrade your database version." : "Обновите базу данных.",
+ "404" : "404"
},
"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);");
diff --git a/lib/l10n/ru.json b/lib/l10n/ru.json
index d6582773e87..2a14e20a3e1 100644
--- a/lib/l10n/ru.json
+++ b/lib/l10n/ru.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Приложение %1$s отсутствует или его версия несовместима с этим сервером. Пожалуйста, проверьте каталог приложений.",
"Sample configuration detected" : "Обнаружена конфигурация из примера.",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Была обнаружена конфигурация из примера. Такая конфигурация не поддерживается и может повредить вашей системе. Прочтите документацию перед внесением изменений в файл config.php",
- "404" : "404",
"The page could not be found on the server." : "Страница не найдена на сервере.",
"%s email verification" : "Проверка почтового адреса %s",
"Email verification" : "Подтверждение адреса электронной почты",
@@ -94,7 +93,7 @@
"Help" : "Помощь",
"Appearance and accessibility" : "Внешний вид и доступность",
"Apps" : "Приложения",
- "Personal settings" : "Параметры пользователя",
+ "Personal settings" : "Личные настройки",
"Administration settings" : "Параметры сервера",
"Settings" : "Настройки",
"Log out" : "Выйти",
@@ -154,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$s предоставил(а) вам доступ к «%2$s».",
"Click the button below to open it." : "Нажмите расположенную ниже кнопку для перехода к полученному общему ресурсу.",
"The requested share does not exist anymore" : "Запрошенный общий ресурс более не существует.",
+ "The requested share comes from a disabled user" : "Запрос на общий доступ поступает от отключенного пользователя",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Пользователь не был создан, достигнуто ограничение количества пользователей. Для получения дополнительных сведений проверьте уведомления.",
"Could not find category \"%s\"" : "Категория «%s» не найдена",
"Sunday" : "Воскресенье",
@@ -259,6 +259,12 @@
"Storage connection error. %s" : "Ошибка подключения к хранилищу. %s",
"Storage is temporarily not available" : "Хранилище временно недоступно",
"Storage connection timeout. %s" : "Истекло время ожидания подключения к хранилищу. %s",
+ "Free prompt" : "Свободная подсказка",
+ "Runs an arbitrary prompt through the language model." : "Запускает произвольное приглашение через языковую модель.",
+ "Generate headline" : "Сгенерировать заголовок",
+ "Generates a possible headline for a text." : "Генерирует возможный заголовок для текста.",
+ "Summarize" : "Обобщить",
+ "Summarizes text by reducing its length without losing key information." : "Обобщает текст, сокращая его длину без потери ключевой информации.",
"Extract topics" : "Извлечь темы",
"Extracts topics from a text and outputs them separated by commas." : "Извлекает темы из текста и выводит их через запятую.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файлы приложения %1$s не были заменены корректно. Удостоверьтесь, что устанавливаемая версия этого приложения совместима с версией сервера.",
@@ -268,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Требуется как минимум libxml2 версии 2.7.0. На данный момент установлена %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Для исправления этой ошибки обновите версию libxml2 и перезапустите ваш веб-сервер.",
"PostgreSQL >= 9 required." : "Требуется PostgreSQL версии 9 или более новый.",
- "Please upgrade your database version." : "Обновите базу данных."
+ "Please upgrade your database version." : "Обновите базу данных.",
+ "404" : "404"
},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
} \ No newline at end of file
diff --git a/lib/l10n/sk.js b/lib/l10n/sk.js
index 4c2843a76aa..8b1de695f91 100644
--- a/lib/l10n/sk.js
+++ b/lib/l10n/sk.js
@@ -7,7 +7,6 @@ OC.L10N.register(
"See %s" : "Pozri %s",
"Sample configuration detected" : "Detekovaná bola vzorová konfigurácia",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Zistilo sa, že konfigurácia bola skopírovaná zo vzorových súborov. Takáto konfigurácia nie je podporovaná a môže poškodiť vašu inštaláciu. Prečítajte si dokumentáciu pred vykonaním zmien v config.php",
- "404" : "404",
"The page could not be found on the server." : "Stránka nebola nájdená na serveri.",
"%s email verification" : "%s overenie e-mailu",
"Email verification" : "Overenie e-mailu",
@@ -57,6 +56,7 @@ OC.L10N.register(
"Avatar image is not square" : "Obrázok avatara nie je štvorcový",
"Files" : "Súbory",
"View profile" : "Zobraziť profil",
+ "Local time: %s" : "Miestny čas: %s",
"today" : "dnes",
"tomorrow" : "zajtra",
"yesterday" : "včera",
@@ -112,7 +112,8 @@ OC.L10N.register(
"Address" : "Adresa",
"Profile picture" : "Profilový obrázok",
"About" : "O aplikácii",
- "Headline" : "Titulok",
+ "Display name" : "Zobrazované meno",
+ "Headline" : "Titul",
"Organisation" : "Organizácia",
"Role" : "Rola",
"Unknown user" : "Neznámy používateľ",
@@ -264,6 +265,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Vyžadovaná verzia libxml2 je 2.7.0 a vyššia. Momentálne je nainštalovaná verzia %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Pre vyriešenie tohto problému aktualizujte prosím verziu libxml2 a reštartujte webový server.",
"PostgreSQL >= 9 required." : "Vyžadované PostgreSQL >= 9.",
- "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy."
+ "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy.",
+ "404" : "404"
},
"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);");
diff --git a/lib/l10n/sk.json b/lib/l10n/sk.json
index 23c783f5e3e..35ed8357bec 100644
--- a/lib/l10n/sk.json
+++ b/lib/l10n/sk.json
@@ -5,7 +5,6 @@
"See %s" : "Pozri %s",
"Sample configuration detected" : "Detekovaná bola vzorová konfigurácia",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Zistilo sa, že konfigurácia bola skopírovaná zo vzorových súborov. Takáto konfigurácia nie je podporovaná a môže poškodiť vašu inštaláciu. Prečítajte si dokumentáciu pred vykonaním zmien v config.php",
- "404" : "404",
"The page could not be found on the server." : "Stránka nebola nájdená na serveri.",
"%s email verification" : "%s overenie e-mailu",
"Email verification" : "Overenie e-mailu",
@@ -55,6 +54,7 @@
"Avatar image is not square" : "Obrázok avatara nie je štvorcový",
"Files" : "Súbory",
"View profile" : "Zobraziť profil",
+ "Local time: %s" : "Miestny čas: %s",
"today" : "dnes",
"tomorrow" : "zajtra",
"yesterday" : "včera",
@@ -110,7 +110,8 @@
"Address" : "Adresa",
"Profile picture" : "Profilový obrázok",
"About" : "O aplikácii",
- "Headline" : "Titulok",
+ "Display name" : "Zobrazované meno",
+ "Headline" : "Titul",
"Organisation" : "Organizácia",
"Role" : "Rola",
"Unknown user" : "Neznámy používateľ",
@@ -262,6 +263,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Vyžadovaná verzia libxml2 je 2.7.0 a vyššia. Momentálne je nainštalovaná verzia %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Pre vyriešenie tohto problému aktualizujte prosím verziu libxml2 a reštartujte webový server.",
"PostgreSQL >= 9 required." : "Vyžadované PostgreSQL >= 9.",
- "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy."
+ "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy.",
+ "404" : "404"
},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);"
} \ No newline at end of file
diff --git a/lib/l10n/sl.js b/lib/l10n/sl.js
index 7de5d7dad30..e212e29c003 100644
--- a/lib/l10n/sl.js
+++ b/lib/l10n/sl.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Program %1$s ni na voljo ali pa je nameščena neskladna različica za ta strežnik. Preverite mapo programov.",
"Sample configuration detected" : "Zaznana je neustrezna vzorčna nastavitev",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "V sistem je bila kopirana datoteka s vzorčnimi nastavitvami. To lahko vpliva na namestitev in zato možnost ni podprta. Pred spremembami datoteke config.php si natančno preberite dokumentacijo.",
- "404" : "404",
"The page could not be found on the server." : "Strani na strežniku ni mogoče najti.",
"Email verification" : "Overjanje elektronskega naslova",
"Click the following link to confirm your email." : "Kliknite na povezavo za potrditev elektronskega naslova.",
@@ -258,6 +257,7 @@ OC.L10N.register(
"Full name" : "Polno ime",
"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V uporabniškem imenu je dovoljeno uporabiti le znake: »a–z«, »A–Z«, »0–9« in »_.@-«\".",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Različica knjižnice libxml2 mora biti 2.7.0 ali višja. Trenutno je nameščena %s.",
- "To fix this issue update your libxml2 version and restart your web server." : "Za rešitev te težave je treba posodobiti knjižnico libxml2 in nato ponovno zagnati spletni strežnik."
+ "To fix this issue update your libxml2 version and restart your web server." : "Za rešitev te težave je treba posodobiti knjižnico libxml2 in nato ponovno zagnati spletni strežnik.",
+ "404" : "404"
},
"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);");
diff --git a/lib/l10n/sl.json b/lib/l10n/sl.json
index 0a2a0b69bea..c6deda5413c 100644
--- a/lib/l10n/sl.json
+++ b/lib/l10n/sl.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Program %1$s ni na voljo ali pa je nameščena neskladna različica za ta strežnik. Preverite mapo programov.",
"Sample configuration detected" : "Zaznana je neustrezna vzorčna nastavitev",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "V sistem je bila kopirana datoteka s vzorčnimi nastavitvami. To lahko vpliva na namestitev in zato možnost ni podprta. Pred spremembami datoteke config.php si natančno preberite dokumentacijo.",
- "404" : "404",
"The page could not be found on the server." : "Strani na strežniku ni mogoče najti.",
"Email verification" : "Overjanje elektronskega naslova",
"Click the following link to confirm your email." : "Kliknite na povezavo za potrditev elektronskega naslova.",
@@ -256,6 +255,7 @@
"Full name" : "Polno ime",
"Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V uporabniškem imenu je dovoljeno uporabiti le znake: »a–z«, »A–Z«, »0–9« in »_.@-«\".",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Različica knjižnice libxml2 mora biti 2.7.0 ali višja. Trenutno je nameščena %s.",
- "To fix this issue update your libxml2 version and restart your web server." : "Za rešitev te težave je treba posodobiti knjižnico libxml2 in nato ponovno zagnati spletni strežnik."
+ "To fix this issue update your libxml2 version and restart your web server." : "Za rešitev te težave je treba posodobiti knjižnico libxml2 in nato ponovno zagnati spletni strežnik.",
+ "404" : "404"
},"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"
} \ No newline at end of file
diff --git a/lib/l10n/sr.js b/lib/l10n/sr.js
index 047cd8d4083..5739978c2e1 100644
--- a/lib/l10n/sr.js
+++ b/lib/l10n/sr.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Не постоји апликација %1$s или њена верзија није компатибилна са овим сервером. Молимо вас да проверите директоријум са апликацијама.",
"Sample configuration detected" : "Откривен је пример подешавања",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Откривено је да је прекопиран пример подешавања. Ово може покварити инсталацију и није подржано. Прочитајте документацију пре вршења промена у фајлу config.php",
- "404" : "404",
"The page could not be found on the server." : "На серверу не може да се пронађе ова страница.",
"%s email verification" : "%s потврђивање и-мејла",
"Email verification" : "Потврђивање и-мејла",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Потребан је бар libxml2 2.7.0. Тренутно је инсталиран %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Да поправите овај проблем, ажурирајте верзију библиотеке libxml2 и рестартујте веб сервер.",
"PostgreSQL >= 9 required." : "Потребан је PostgreSQL >= 9.",
- "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података."
+ "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података.",
+ "404" : "404"
},
"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);");
diff --git a/lib/l10n/sr.json b/lib/l10n/sr.json
index f4da5fe632e..9549cecb2e3 100644
--- a/lib/l10n/sr.json
+++ b/lib/l10n/sr.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Не постоји апликација %1$s или њена верзија није компатибилна са овим сервером. Молимо вас да проверите директоријум са апликацијама.",
"Sample configuration detected" : "Откривен је пример подешавања",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Откривено је да је прекопиран пример подешавања. Ово може покварити инсталацију и није подржано. Прочитајте документацију пре вршења промена у фајлу config.php",
- "404" : "404",
"The page could not be found on the server." : "На серверу не може да се пронађе ова страница.",
"%s email verification" : "%s потврђивање и-мејла",
"Email verification" : "Потврђивање и-мејла",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Потребан је бар libxml2 2.7.0. Тренутно је инсталиран %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Да поправите овај проблем, ажурирајте верзију библиотеке libxml2 и рестартујте веб сервер.",
"PostgreSQL >= 9 required." : "Потребан је PostgreSQL >= 9.",
- "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података."
+ "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података.",
+ "404" : "404"
},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
} \ No newline at end of file
diff --git a/lib/l10n/sv.js b/lib/l10n/sv.js
index bb6aa050151..f0f7081dd6a 100644
--- a/lib/l10n/sv.js
+++ b/lib/l10n/sv.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Appen %1$s finns inte eller har en icke-kompatibel version med denna server. Kontrollera appkatalogen.",
"Sample configuration detected" : "Exempel-konfiguration detekterad",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Läs dokumentationen innan du utför ändringar på config.php",
- "404" : "404",
"The page could not be found on the server." : "Sidan kunde inte hittas på servern.",
"%s email verification" : "%s e-postverifikation",
"Email verification" : "E-postverifikation",
@@ -156,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$s delade »%2$s« med dig.",
"Click the button below to open it." : "Klicka på knappen nedan för att öppna det.",
"The requested share does not exist anymore" : "Den begärda delningen finns inte mer",
+ "The requested share comes from a disabled user" : "Den begärda delningen kommer från en inaktiverad användare",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Användaren skapades inte eftersom användargränsen har nåtts. Kontrollera dina aviseringar för att läsa mer.",
"Could not find category \"%s\"" : "Kunde inte hitta kategorin \"%s\"",
"Sunday" : "Söndag",
@@ -274,6 +274,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 är det minsta som krävs. För närvarande är %s installerat.",
"To fix this issue update your libxml2 version and restart your web server." : "För att åtgärda detta problem uppdatera libxml2 versionen och starta om din webbserver.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 krävs.",
- "Please upgrade your database version." : "Uppgradera din databasversion."
+ "Please upgrade your database version." : "Uppgradera din databasversion.",
+ "404" : "404"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/sv.json b/lib/l10n/sv.json
index 3d0525677e8..11fd22b6b64 100644
--- a/lib/l10n/sv.json
+++ b/lib/l10n/sv.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Appen %1$s finns inte eller har en icke-kompatibel version med denna server. Kontrollera appkatalogen.",
"Sample configuration detected" : "Exempel-konfiguration detekterad",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Läs dokumentationen innan du utför ändringar på config.php",
- "404" : "404",
"The page could not be found on the server." : "Sidan kunde inte hittas på servern.",
"%s email verification" : "%s e-postverifikation",
"Email verification" : "E-postverifikation",
@@ -154,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$s delade »%2$s« med dig.",
"Click the button below to open it." : "Klicka på knappen nedan för att öppna det.",
"The requested share does not exist anymore" : "Den begärda delningen finns inte mer",
+ "The requested share comes from a disabled user" : "Den begärda delningen kommer från en inaktiverad användare",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Användaren skapades inte eftersom användargränsen har nåtts. Kontrollera dina aviseringar för att läsa mer.",
"Could not find category \"%s\"" : "Kunde inte hitta kategorin \"%s\"",
"Sunday" : "Söndag",
@@ -272,6 +272,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 är det minsta som krävs. För närvarande är %s installerat.",
"To fix this issue update your libxml2 version and restart your web server." : "För att åtgärda detta problem uppdatera libxml2 versionen och starta om din webbserver.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 krävs.",
- "Please upgrade your database version." : "Uppgradera din databasversion."
+ "Please upgrade your database version." : "Uppgradera din databasversion.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/th.js b/lib/l10n/th.js
index b78d58379d8..0fea7cc572d 100644
--- a/lib/l10n/th.js
+++ b/lib/l10n/th.js
@@ -2,10 +2,22 @@ OC.L10N.register(
"lib",
{
"Cannot write into \"config\" directory!" : "ไม่สามารถเขียนลงในไดเรกทอรี \"config\"!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "โดยปกติสามารถแก้ไขได้โดยการให้สิทธิ์การเขียนสำหรับเว็บเซิร์ฟเวอร์ไปยังไดเร็กทอรี config",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "แต่หากคุณต้องการให้ไฟล์ config.php อ่านอย่างเดียว ให้ตั้งค่าตัวเลือก \"config_is_read_only\" เป็น true",
"See %s" : "ดู %s",
"Sample configuration detected" : "ตรวจพบการกำหนดค่าตัวอย่าง",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "เราตรวจพบว่าการกำหนดค่าตัวอย่างถูกคัดลอกมา การกำหนดค่าลักษณะนี้สามารถทำลายการติดตั้งของคุณและไม่ได้รับการสนับสนุน โปรดอ่านเอกสารประกอบก่อนที่จะดำเนินการเปลี่ยนแปลงใน config.php",
"The page could not be found on the server." : "ไม่พบหน้านี้บนเซิร์ฟเวอร์",
+ "%s email verification" : "การยืนยันอีเมล %s",
+ "Email verification" : "การยืนยันอีเมล",
+ "Click the following button to confirm your email." : "คลิกปุ่มต่อไปนี้เพื่อยืนยันอีเมลของคุณ",
+ "Click the following link to confirm your email." : "คลิกลิงก์ต่อไปนี้เพื่อยืนยันอีเมลของคุณ",
+ "Confirm your email" : "ยืนยันอีเมล",
+ "Other activities" : "กิจกรรมอื่น ๆ",
+ "%1$s and %2$s" : "%1$s และ %2$s",
+ "%1$s, %2$s and %3$s" : "%1$s, %2$s และ %3$s",
+ "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s และ %4$s",
+ "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s และ %5$s",
"PHP %s or higher is required." : "จำเป็นต้องมี PHP รุ่น %s หรือที่สูงกว่า ",
"PHP with a version lower than %s is required." : "จำเป็นต้องมี PHP รุ่นต่ำกว่า %s",
"%sbit or higher PHP required." : "จำเป็นต้องมี PHP %s บิตหรือสูงกว่า",
@@ -43,13 +55,17 @@ OC.L10N.register(
"Empty filename is not allowed" : "ชื่อไฟล์ห้ามว่างเปล่า",
"App \"%s\" cannot be installed because appinfo file cannot be read." : "แอป \"%s\" ไม่สามารถติดตั้งได้ เพราะไฟล์ appInfo ไม่สามารถอ่านได้",
"App \"%s\" cannot be installed because it is not compatible with this version of the server." : "ไม่สามารถติดตั้งแอป \"%s\" เนื่องจากแอปเข้ากับเซิร์ฟเวอร์รุ่นนี้ไม่ได้",
- "__language_name__" : "ภาษาไทย - Thai",
+ "__language_name__" : "ไทย",
"Help" : "ช่วยเหลือ",
"Apps" : "แอป",
+ "Personal settings" : "การตั้งค่าส่วนบุคคล",
+ "Administration settings" : "การตั้งค่าการดูแลระบบ",
"Settings" : "การตั้งค่า",
"Log out" : "ออกจากระบบ",
"Users" : "ผู้ใช้งาน",
"Email" : "อีเมล",
+ "Fediverse" : "เฟดิเวิร์ส",
+ "View %s on the fediverse" : "ดู %s บนเฟดิเวิร์ส",
"Phone" : "โทรศัพท์",
"Twitter" : "ทวิตเตอร์",
"Website" : "เว็บไซต์",
diff --git a/lib/l10n/th.json b/lib/l10n/th.json
index 30cc82d864d..34be3490322 100644
--- a/lib/l10n/th.json
+++ b/lib/l10n/th.json
@@ -1,9 +1,21 @@
{ "translations": {
"Cannot write into \"config\" directory!" : "ไม่สามารถเขียนลงในไดเรกทอรี \"config\"!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "โดยปกติสามารถแก้ไขได้โดยการให้สิทธิ์การเขียนสำหรับเว็บเซิร์ฟเวอร์ไปยังไดเร็กทอรี config",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "แต่หากคุณต้องการให้ไฟล์ config.php อ่านอย่างเดียว ให้ตั้งค่าตัวเลือก \"config_is_read_only\" เป็น true",
"See %s" : "ดู %s",
"Sample configuration detected" : "ตรวจพบการกำหนดค่าตัวอย่าง",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "เราตรวจพบว่าการกำหนดค่าตัวอย่างถูกคัดลอกมา การกำหนดค่าลักษณะนี้สามารถทำลายการติดตั้งของคุณและไม่ได้รับการสนับสนุน โปรดอ่านเอกสารประกอบก่อนที่จะดำเนินการเปลี่ยนแปลงใน config.php",
"The page could not be found on the server." : "ไม่พบหน้านี้บนเซิร์ฟเวอร์",
+ "%s email verification" : "การยืนยันอีเมล %s",
+ "Email verification" : "การยืนยันอีเมล",
+ "Click the following button to confirm your email." : "คลิกปุ่มต่อไปนี้เพื่อยืนยันอีเมลของคุณ",
+ "Click the following link to confirm your email." : "คลิกลิงก์ต่อไปนี้เพื่อยืนยันอีเมลของคุณ",
+ "Confirm your email" : "ยืนยันอีเมล",
+ "Other activities" : "กิจกรรมอื่น ๆ",
+ "%1$s and %2$s" : "%1$s และ %2$s",
+ "%1$s, %2$s and %3$s" : "%1$s, %2$s และ %3$s",
+ "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s และ %4$s",
+ "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s และ %5$s",
"PHP %s or higher is required." : "จำเป็นต้องมี PHP รุ่น %s หรือที่สูงกว่า ",
"PHP with a version lower than %s is required." : "จำเป็นต้องมี PHP รุ่นต่ำกว่า %s",
"%sbit or higher PHP required." : "จำเป็นต้องมี PHP %s บิตหรือสูงกว่า",
@@ -41,13 +53,17 @@
"Empty filename is not allowed" : "ชื่อไฟล์ห้ามว่างเปล่า",
"App \"%s\" cannot be installed because appinfo file cannot be read." : "แอป \"%s\" ไม่สามารถติดตั้งได้ เพราะไฟล์ appInfo ไม่สามารถอ่านได้",
"App \"%s\" cannot be installed because it is not compatible with this version of the server." : "ไม่สามารถติดตั้งแอป \"%s\" เนื่องจากแอปเข้ากับเซิร์ฟเวอร์รุ่นนี้ไม่ได้",
- "__language_name__" : "ภาษาไทย - Thai",
+ "__language_name__" : "ไทย",
"Help" : "ช่วยเหลือ",
"Apps" : "แอป",
+ "Personal settings" : "การตั้งค่าส่วนบุคคล",
+ "Administration settings" : "การตั้งค่าการดูแลระบบ",
"Settings" : "การตั้งค่า",
"Log out" : "ออกจากระบบ",
"Users" : "ผู้ใช้งาน",
"Email" : "อีเมล",
+ "Fediverse" : "เฟดิเวิร์ส",
+ "View %s on the fediverse" : "ดู %s บนเฟดิเวิร์ส",
"Phone" : "โทรศัพท์",
"Twitter" : "ทวิตเตอร์",
"Website" : "เว็บไซต์",
diff --git a/lib/l10n/tr.js b/lib/l10n/tr.js
index df403564ca0..4d26f0a4786 100644
--- a/lib/l10n/tr.js
+++ b/lib/l10n/tr.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "%1$s uygulaması yok ya da bu sunucuyla uyumlu olmayan bir sürümü var. Lütfen apps klasörünü kontrol edin.",
"Sample configuration detected" : "Örnek yapılandırma algılandı",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Örnek yapılandırmanın kopyalanmış olabileceği tespit edildi. Bu durum kurulumunuzu bozabilir ve desteklenmez. Lütfen config.php dosyasında değişiklik yapmadan önce belgeleri okuyun",
- "404" : "404",
"The page could not be found on the server." : "Sayfa sunucuda bulunamadı.",
"%s email verification" : "%s e-posta doğrulaması",
"Email verification" : "E-posta doğrulaması",
@@ -64,12 +63,12 @@ OC.L10N.register(
"yesterday" : "dün",
"_in %n day_::_in %n days_" : ["%n gün içinde","%n gün içinde"],
"_%n day ago_::_%n days ago_" : ["%n gün önce","%n gün önce"],
- "next month" : "gelecek ay",
- "last month" : "geçen ay",
+ "next month" : "sonraki ay",
+ "last month" : "önceki ay",
"_in %n month_::_in %n months_" : ["%n ay içinde","%n ay içinde"],
"_%n month ago_::_%n months ago_" : ["%n ay önce","%n ay önce"],
- "next year" : "gelecek yıl",
- "last year" : "geçen yıl",
+ "next year" : "sonraki yıl",
+ "last year" : "önceki yıl",
"_in %n year_::_in %n years_" : ["%n yıl içinde","%n yıl içinde"],
"_%n year ago_::_%n years ago_" : ["%n yıl önce","%n yıl önce"],
"_in %n hour_::_in %n hours_" : ["%n saat içinde","%n saat içinde"],
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 sürümü en az 2.7.0 olmalıdır. Şu anda %s kurulu.",
"To fix this issue update your libxml2 version and restart your web server." : "Bu sorunu çözmek için libxml2 sürümünüzü güncelleyin ve site sunucusunu yeniden başlatın.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 gerekli.",
- "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin."
+ "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin.",
+ "404" : "404"
},
"nplurals=2; plural=(n > 1);");
diff --git a/lib/l10n/tr.json b/lib/l10n/tr.json
index 5db37fb770c..a9582fda366 100644
--- a/lib/l10n/tr.json
+++ b/lib/l10n/tr.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "%1$s uygulaması yok ya da bu sunucuyla uyumlu olmayan bir sürümü var. Lütfen apps klasörünü kontrol edin.",
"Sample configuration detected" : "Örnek yapılandırma algılandı",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Örnek yapılandırmanın kopyalanmış olabileceği tespit edildi. Bu durum kurulumunuzu bozabilir ve desteklenmez. Lütfen config.php dosyasında değişiklik yapmadan önce belgeleri okuyun",
- "404" : "404",
"The page could not be found on the server." : "Sayfa sunucuda bulunamadı.",
"%s email verification" : "%s e-posta doğrulaması",
"Email verification" : "E-posta doğrulaması",
@@ -62,12 +61,12 @@
"yesterday" : "dün",
"_in %n day_::_in %n days_" : ["%n gün içinde","%n gün içinde"],
"_%n day ago_::_%n days ago_" : ["%n gün önce","%n gün önce"],
- "next month" : "gelecek ay",
- "last month" : "geçen ay",
+ "next month" : "sonraki ay",
+ "last month" : "önceki ay",
"_in %n month_::_in %n months_" : ["%n ay içinde","%n ay içinde"],
"_%n month ago_::_%n months ago_" : ["%n ay önce","%n ay önce"],
- "next year" : "gelecek yıl",
- "last year" : "geçen yıl",
+ "next year" : "sonraki yıl",
+ "last year" : "önceki yıl",
"_in %n year_::_in %n years_" : ["%n yıl içinde","%n yıl içinde"],
"_%n year ago_::_%n years ago_" : ["%n yıl önce","%n yıl önce"],
"_in %n hour_::_in %n hours_" : ["%n saat içinde","%n saat içinde"],
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 sürümü en az 2.7.0 olmalıdır. Şu anda %s kurulu.",
"To fix this issue update your libxml2 version and restart your web server." : "Bu sorunu çözmek için libxml2 sürümünüzü güncelleyin ve site sunucusunu yeniden başlatın.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 gerekli.",
- "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin."
+ "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin.",
+ "404" : "404"
},"pluralForm" :"nplurals=2; plural=(n > 1);"
} \ No newline at end of file
diff --git a/lib/l10n/uk.js b/lib/l10n/uk.js
index 66cdea90e01..d5277a526a8 100644
--- a/lib/l10n/uk.js
+++ b/lib/l10n/uk.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Застосунок %1$s відсутній або має несумісну з цим сервером версію. Будь ласка, перевірте каталог додатків.",
"Sample configuration detected" : "Виявлено приклад конфігурації",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Було виявлено, що приклад конфігурації було скопійовано. Це може нашкодити вашій системі та не підтримується. Будь ласка, зверніться до документації перед внесенням змін в файл config.php",
- "404" : "404",
"The page could not be found on the server." : "Сторінку не знайдено на сервері.",
"%s email verification" : "%s підтвердження електронної пошти",
"Email verification" : "Підтвердження електронної пошти",
@@ -156,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$s надав(-ла) доступ до \"%2$s\".",
"Click the button below to open it." : "Щоб відкрити файл, натисніть кнопку нижче.",
"The requested share does not exist anymore" : "Запитуваний спільний ресурс більше недоступний",
+ "The requested share comes from a disabled user" : "Отримано запит на спільний доступ від неактивного користувача",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Користувача не створено, оскільки досягнуто обмеження на кількість користувачів. Перевірте сповіщення для докладної інформації.",
"Could not find category \"%s\"" : "Не вдалося знайти категорію \"%s\"",
"Sunday" : "Неділя",
@@ -276,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Необхідно libxml2 версії принаймні 2.7.0. На разі встановлена %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Що виправити це оновіть версію libxml2 та перезапустіть веб-сервер.",
"PostgreSQL >= 9 required." : "Необхідно PostgreSQL >= 9.",
- "Please upgrade your database version." : "Оновіть версію бази даних."
+ "Please upgrade your database version." : "Оновіть версію бази даних.",
+ "404" : "404"
},
"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);");
diff --git a/lib/l10n/uk.json b/lib/l10n/uk.json
index 9c102a738e0..e87a4ff6da6 100644
--- a/lib/l10n/uk.json
+++ b/lib/l10n/uk.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Застосунок %1$s відсутній або має несумісну з цим сервером версію. Будь ласка, перевірте каталог додатків.",
"Sample configuration detected" : "Виявлено приклад конфігурації",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Було виявлено, що приклад конфігурації було скопійовано. Це може нашкодити вашій системі та не підтримується. Будь ласка, зверніться до документації перед внесенням змін в файл config.php",
- "404" : "404",
"The page could not be found on the server." : "Сторінку не знайдено на сервері.",
"%s email verification" : "%s підтвердження електронної пошти",
"Email verification" : "Підтвердження електронної пошти",
@@ -154,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$s надав(-ла) доступ до \"%2$s\".",
"Click the button below to open it." : "Щоб відкрити файл, натисніть кнопку нижче.",
"The requested share does not exist anymore" : "Запитуваний спільний ресурс більше недоступний",
+ "The requested share comes from a disabled user" : "Отримано запит на спільний доступ від неактивного користувача",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Користувача не створено, оскільки досягнуто обмеження на кількість користувачів. Перевірте сповіщення для докладної інформації.",
"Could not find category \"%s\"" : "Не вдалося знайти категорію \"%s\"",
"Sunday" : "Неділя",
@@ -274,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Необхідно libxml2 версії принаймні 2.7.0. На разі встановлена %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Що виправити це оновіть версію libxml2 та перезапустіть веб-сервер.",
"PostgreSQL >= 9 required." : "Необхідно PostgreSQL >= 9.",
- "Please upgrade your database version." : "Оновіть версію бази даних."
+ "Please upgrade your database version." : "Оновіть версію бази даних.",
+ "404" : "404"
},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"
} \ No newline at end of file
diff --git a/lib/l10n/vi.js b/lib/l10n/vi.js
index 7ea1f68dca5..070ab15c3cc 100644
--- a/lib/l10n/vi.js
+++ b/lib/l10n/vi.js
@@ -23,6 +23,7 @@ OC.L10N.register(
"Unknown filetype" : "Không biết kiểu tập tin",
"Invalid image" : "Hình ảnh không hợp lệ",
"Files" : "Tệp",
+ "View profile" : "Xem hồ sơ",
"today" : "hôm nay",
"yesterday" : "hôm qua",
"last month" : "tháng trước",
@@ -36,6 +37,7 @@ OC.L10N.register(
"Templates" : "‎Mẫu‎",
"__language_name__" : "Tiếng Việt",
"Help" : "Giúp đỡ",
+ "Appearance and accessibility" : "Ngoại hình và khả năng tiếp cận",
"Apps" : "Ứng dụng",
"Settings" : "Thiết lập",
"Log out" : "Đăng xuất",
@@ -47,6 +49,9 @@ OC.L10N.register(
"Address" : "Địa chỉ",
"Profile picture" : "Ảnh đại diện",
"About" : "Giới thiệu",
+ "Headline" : "Tiêu đề",
+ "Organisation" : "Tổ chức",
+ "Role" : "Vai trò",
"Unknown user" : "Người dùng không tồn tại",
"Additional settings" : "Cài đặt bổ sung",
"Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Hãy xóa thiết lập open_basedir tại tập tin cấu hình php.ini hoặc chuyển sang dùng PHP 64-bit.",
diff --git a/lib/l10n/vi.json b/lib/l10n/vi.json
index 31603bf9c63..c36f661b169 100644
--- a/lib/l10n/vi.json
+++ b/lib/l10n/vi.json
@@ -21,6 +21,7 @@
"Unknown filetype" : "Không biết kiểu tập tin",
"Invalid image" : "Hình ảnh không hợp lệ",
"Files" : "Tệp",
+ "View profile" : "Xem hồ sơ",
"today" : "hôm nay",
"yesterday" : "hôm qua",
"last month" : "tháng trước",
@@ -34,6 +35,7 @@
"Templates" : "‎Mẫu‎",
"__language_name__" : "Tiếng Việt",
"Help" : "Giúp đỡ",
+ "Appearance and accessibility" : "Ngoại hình và khả năng tiếp cận",
"Apps" : "Ứng dụng",
"Settings" : "Thiết lập",
"Log out" : "Đăng xuất",
@@ -45,6 +47,9 @@
"Address" : "Địa chỉ",
"Profile picture" : "Ảnh đại diện",
"About" : "Giới thiệu",
+ "Headline" : "Tiêu đề",
+ "Organisation" : "Tổ chức",
+ "Role" : "Vai trò",
"Unknown user" : "Người dùng không tồn tại",
"Additional settings" : "Cài đặt bổ sung",
"Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Hãy xóa thiết lập open_basedir tại tập tin cấu hình php.ini hoặc chuyển sang dùng PHP 64-bit.",
diff --git a/lib/l10n/zh_CN.js b/lib/l10n/zh_CN.js
index 834a20822c3..a74a6937ecf 100644
--- a/lib/l10n/zh_CN.js
+++ b/lib/l10n/zh_CN.js
@@ -7,7 +7,6 @@ OC.L10N.register(
"See %s" : "查看 %s",
"Sample configuration detected" : "示例配置检测",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。",
- "404" : "404",
"The page could not be found on the server." : "无法在服务器上找到此页面",
"%s email verification" : "%s 电子邮件验证",
"Email verification" : "电子邮件验证",
@@ -265,6 +264,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "至少需要 libxml2 2.7.0. 当前安装 %s。",
"To fix this issue update your libxml2 version and restart your web server." : "升级您的libxml2版本然后重启Web服务器以解决该问题。",
"PostgreSQL >= 9 required." : "需要 PostgreSQL >= 9。",
- "Please upgrade your database version." : "请升级您的数据库版本。"
+ "Please upgrade your database version." : "请升级您的数据库版本。",
+ "404" : "404"
},
"nplurals=1; plural=0;");
diff --git a/lib/l10n/zh_CN.json b/lib/l10n/zh_CN.json
index 87a73aec9b4..38813c8120d 100644
--- a/lib/l10n/zh_CN.json
+++ b/lib/l10n/zh_CN.json
@@ -5,7 +5,6 @@
"See %s" : "查看 %s",
"Sample configuration detected" : "示例配置检测",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。",
- "404" : "404",
"The page could not be found on the server." : "无法在服务器上找到此页面",
"%s email verification" : "%s 电子邮件验证",
"Email verification" : "电子邮件验证",
@@ -263,6 +262,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "至少需要 libxml2 2.7.0. 当前安装 %s。",
"To fix this issue update your libxml2 version and restart your web server." : "升级您的libxml2版本然后重启Web服务器以解决该问题。",
"PostgreSQL >= 9 required." : "需要 PostgreSQL >= 9。",
- "Please upgrade your database version." : "请升级您的数据库版本。"
+ "Please upgrade your database version." : "请升级您的数据库版本。",
+ "404" : "404"
},"pluralForm" :"nplurals=1; plural=0;"
} \ No newline at end of file
diff --git a/lib/l10n/zh_HK.js b/lib/l10n/zh_HK.js
index 345cf40a584..d5334cbde22 100644
--- a/lib/l10n/zh_HK.js
+++ b/lib/l10n/zh_HK.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "應用程式 %1$s 不存在或有與此伺服器不相容的版本。請檢查應用程式目錄。",
"Sample configuration detected" : "您目前正在使用範例配置",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "系統檢測到示例配置已被複製。這樣做會破壞您的安裝並且不受支持。請在對 config.php 進行更改之前閱讀說明書",
- "404" : "404",
"The page could not be found on the server." : "無法在伺服器上找到此頁面。",
"%s email verification" : "%s 電郵地址驗證",
"Email verification" : "電郵地址驗證",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 版本最低需求為 2.7.0。目前安裝版本為 %s。",
"To fix this issue update your libxml2 version and restart your web server." : "修正方式為更新您的 libxml2 為 2.7.0 以上版本,再重啟網頁伺服器。",
"PostgreSQL >= 9 required." : "需要 PostgreSQL 版本 >= 9",
- "Please upgrade your database version." : "請升級您數據庫的版本。"
+ "Please upgrade your database version." : "請升級您數據庫的版本。",
+ "404" : "404"
},
"nplurals=1; plural=0;");
diff --git a/lib/l10n/zh_HK.json b/lib/l10n/zh_HK.json
index 08e823d5723..4ee6495971b 100644
--- a/lib/l10n/zh_HK.json
+++ b/lib/l10n/zh_HK.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "應用程式 %1$s 不存在或有與此伺服器不相容的版本。請檢查應用程式目錄。",
"Sample configuration detected" : "您目前正在使用範例配置",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "系統檢測到示例配置已被複製。這樣做會破壞您的安裝並且不受支持。請在對 config.php 進行更改之前閱讀說明書",
- "404" : "404",
"The page could not be found on the server." : "無法在伺服器上找到此頁面。",
"%s email verification" : "%s 電郵地址驗證",
"Email verification" : "電郵地址驗證",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 版本最低需求為 2.7.0。目前安裝版本為 %s。",
"To fix this issue update your libxml2 version and restart your web server." : "修正方式為更新您的 libxml2 為 2.7.0 以上版本,再重啟網頁伺服器。",
"PostgreSQL >= 9 required." : "需要 PostgreSQL 版本 >= 9",
- "Please upgrade your database version." : "請升級您數據庫的版本。"
+ "Please upgrade your database version." : "請升級您數據庫的版本。",
+ "404" : "404"
},"pluralForm" :"nplurals=1; plural=0;"
} \ No newline at end of file
diff --git a/lib/l10n/zh_TW.js b/lib/l10n/zh_TW.js
index c950bc7636d..0b1a2c20455 100644
--- a/lib/l10n/zh_TW.js
+++ b/lib/l10n/zh_TW.js
@@ -8,7 +8,6 @@ OC.L10N.register(
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "應用程式 %1$s 不存在或有與此伺服器不相容的版本。請檢查應用程式目錄。",
"Sample configuration detected" : "您目前正在使用範例設定",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改",
- "404" : "404",
"The page could not be found on the server." : "無法在伺服器上找到此頁面。",
"%s email verification" : "%s 電子郵件驗證",
"Email verification" : "電子郵件驗證",
@@ -277,6 +276,7 @@ OC.L10N.register(
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 版本最低需求為 2.7.0。目前安裝版本為 %s 。",
"To fix this issue update your libxml2 version and restart your web server." : "修正方式為更新您的 libxml2 為 2.7.0 以上版本,再重啟網頁伺服器。",
"PostgreSQL >= 9 required." : "需要 PostgreSQL 版本 >= 9。",
- "Please upgrade your database version." : "請升級您的資料庫版本。"
+ "Please upgrade your database version." : "請升級您的資料庫版本。",
+ "404" : "404"
},
"nplurals=1; plural=0;");
diff --git a/lib/l10n/zh_TW.json b/lib/l10n/zh_TW.json
index f767cb5a8b3..6b4047be509 100644
--- a/lib/l10n/zh_TW.json
+++ b/lib/l10n/zh_TW.json
@@ -6,7 +6,6 @@
"Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "應用程式 %1$s 不存在或有與此伺服器不相容的版本。請檢查應用程式目錄。",
"Sample configuration detected" : "您目前正在使用範例設定",
"It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接複製了範例設定來使用,這樣的安裝很可能會無法運作,請閱讀說明文件後對 config.php 進行適當的修改",
- "404" : "404",
"The page could not be found on the server." : "無法在伺服器上找到此頁面。",
"%s email verification" : "%s 電子郵件驗證",
"Email verification" : "電子郵件驗證",
@@ -275,6 +274,7 @@
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 版本最低需求為 2.7.0。目前安裝版本為 %s 。",
"To fix this issue update your libxml2 version and restart your web server." : "修正方式為更新您的 libxml2 為 2.7.0 以上版本,再重啟網頁伺服器。",
"PostgreSQL >= 9 required." : "需要 PostgreSQL 版本 >= 9。",
- "Please upgrade your database version." : "請升級您的資料庫版本。"
+ "Please upgrade your database version." : "請升級您的資料庫版本。",
+ "404" : "404"
},"pluralForm" :"nplurals=1; plural=0;"
} \ No newline at end of file
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
index 9865438161b..3e33e783635 100644
--- a/lib/private/Accounts/AccountManager.php
+++ b/lib/private/Accounts/AccountManager.php
@@ -37,9 +37,6 @@ namespace OC\Accounts;
use Exception;
use InvalidArgumentException;
-use libphonenumber\NumberParseException;
-use libphonenumber\PhoneNumberFormat;
-use libphonenumber\PhoneNumberUtil;
use OC\Profile\TProfileHelper;
use OCP\Accounts\UserUpdatedEvent;
use OCP\Cache\CappedMemoryCache;
@@ -56,6 +53,7 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IL10N;
+use OCP\IPhoneNumberUtil;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
@@ -119,6 +117,7 @@ class AccountManager implements IAccountManager {
private IFactory $l10nFactory,
private IURLGenerator $urlGenerator,
private ICrypto $crypto,
+ private IPhoneNumberUtil $phoneNumberUtil,
) {
$this->internalCache = new CappedMemoryCache();
}
@@ -139,13 +138,9 @@ class AccountManager implements IAccountManager {
$defaultRegion = 'EN';
}
- $phoneUtil = PhoneNumberUtil::getInstance();
- try {
- $phoneNumber = $phoneUtil->parse($input, $defaultRegion);
- if ($phoneUtil->isValidNumber($phoneNumber)) {
- return $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
- }
- } catch (NumberParseException $e) {
+ $phoneNumber = $this->phoneNumberUtil->convertToStandardFormat($input, $defaultRegion);
+ if ($phoneNumber !== null) {
+ return $phoneNumber;
}
throw new InvalidArgumentException(self::PROPERTY_PHONE);
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index 88044fbf7b6..84bc297143a 100644
--- a/lib/private/App/AppManager.php
+++ b/lib/private/App/AppManager.php
@@ -38,6 +38,7 @@
*/
namespace OC\App;
+use InvalidArgumentException;
use OC\AppConfig;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\ServerNotAvailableException;
@@ -822,16 +823,33 @@ class AppManager implements IAppManager {
return $this->defaultEnabled;
}
- public function getDefaultAppForUser(?IUser $user = null): string {
+ public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string {
// Set fallback to always-enabled files app
- $appId = 'files';
- $defaultApps = explode(',', $this->config->getSystemValueString('defaultapp', 'dashboard,files'));
+ $appId = $withFallbacks ? 'files' : '';
+ $defaultApps = explode(',', $this->config->getSystemValueString('defaultapp', ''));
+ $defaultApps = array_filter($defaultApps);
$user ??= $this->userSession->getUser();
if ($user !== null) {
$userDefaultApps = explode(',', $this->config->getUserValue($user->getUID(), 'core', 'defaultapp'));
$defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps));
+ if (empty($defaultApps) && $withFallbacks) {
+ /* Fallback on user defined apporder */
+ $customOrders = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
+ if (!empty($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);
+ }
+ }
+ }
+
+ if (empty($defaultApps) && $withFallbacks) {
+ $defaultApps = ['dashboard','files'];
}
// Find the first app that is enabled for the current user
@@ -845,4 +863,19 @@ class AppManager implements IAppManager {
return $appId;
}
+
+ public function getDefaultApps(): array {
+ return explode(',', $this->config->getSystemValueString('defaultapp', 'dashboard,files'));
+ }
+
+ public function setDefaultApps(array $defaultApps): void {
+ foreach ($defaultApps as $app) {
+ if (!$this->isInstalled($app)) {
+ $this->logger->debug('Can not set not installed app as default app', ['missing_app' => $app]);
+ throw new InvalidArgumentException('App is not installed');
+ }
+ }
+
+ $this->config->setSystemValue('defaultapp', join(',', $defaultApps));
+ }
}
diff --git a/lib/private/App/AppStore/Version/VersionParser.php b/lib/private/App/AppStore/Version/VersionParser.php
index 2b88399b9fd..eac9c935517 100644
--- a/lib/private/App/AppStore/Version/VersionParser.php
+++ b/lib/private/App/AppStore/Version/VersionParser.php
@@ -54,9 +54,9 @@ class VersionParser {
// Count the amount of =, if it is one then it's either maximum or minimum
// version. If it is two then it is maximum and minimum.
$versionElements = explode(' ', $versionSpec);
- $firstVersion = isset($versionElements[0]) ? $versionElements[0] : '';
+ $firstVersion = $versionElements[0] ?? '';
$firstVersionNumber = substr($firstVersion, 2);
- $secondVersion = isset($versionElements[1]) ? $versionElements[1] : '';
+ $secondVersion = $versionElements[1] ?? '';
$secondVersionNumber = substr($secondVersion, 2);
switch (count($versionElements)) {
diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php
index 84f0d5b9e5a..79c650705b2 100644
--- a/lib/private/AppConfig.php
+++ b/lib/private/AppConfig.php
@@ -373,7 +373,7 @@ class AppConfig implements IAppConfig {
} else {
$appIds = $this->getApps();
$values = array_map(function ($appId) use ($key) {
- return isset($this->cache[$appId][$key]) ? $this->cache[$appId][$key] : null;
+ return $this->cache[$appId][$key] ?? null;
}, $appIds);
$result = array_combine($appIds, $values);
diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
index 5aea2a7a744..5ff2dcd7969 100644
--- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php
+++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
@@ -55,6 +55,7 @@ use OCP\Http\WellKnown\IHandler;
use OCP\Notification\INotifier;
use OCP\Profile\ILinkAction;
use OCP\Search\IProvider;
+use OCP\SetupCheck\ISetupCheck;
use OCP\Share\IPublicShareTemplateProvider;
use OCP\Support\CrashReport\IReporter;
use OCP\UserMigration\IMigrator as IUserMigrator;
@@ -137,6 +138,9 @@ class RegistrationContext {
/** @var ServiceRegistration<IReferenceProvider>[] */
private array $referenceProviders = [];
+ /** @var ServiceRegistration<\OCP\TextToImage\IProvider>[] */
+ private $textToImageProviders = [];
+
@@ -146,11 +150,13 @@ class RegistrationContext {
/** @var ServiceRegistration<IPublicShareTemplateProvider>[] */
private $publicShareTemplateProviders = [];
- /** @var LoggerInterface */
- private $logger;
+ private LoggerInterface $logger;
+
+ /** @var ServiceRegistration<ISetupCheck>[] */
+ private array $setupChecks = [];
/** @var PreviewProviderRegistration[] */
- private $previewProviders = [];
+ private array $previewProviders = [];
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
@@ -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,
@@ -372,6 +385,13 @@ class RegistrationContext {
$class
);
}
+
+ public function registerSetupCheck(string $setupCheckClass): void {
+ $this->context->registerSetupCheck(
+ $this->appId,
+ $setupCheckClass
+ );
+ }
};
}
@@ -383,14 +403,14 @@ class RegistrationContext {
}
/**
- * @psalm-param class-string<IReporter> $capability
+ * @psalm-param class-string<IReporter> $reporterClass
*/
public function registerCrashReporter(string $appId, string $reporterClass): void {
$this->crashReporters[] = new ServiceRegistration($appId, $reporterClass);
}
/**
- * @psalm-param class-string<IWidget> $capability
+ * @psalm-param class-string<IWidget> $panelClass
*/
public function registerDashboardPanel(string $appId, string $panelClass): void {
$this->dashboardPanels[] = new ServiceRegistration($appId, $panelClass);
@@ -443,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);
}
@@ -524,6 +548,13 @@ class RegistrationContext {
}
/**
+ * @psalm-param class-string<ISetupCheck> $setupCheckClass
+ */
+ public function registerSetupCheck(string $appId, string $setupCheckClass): void {
+ $this->setupChecks[] = new ServiceRegistration($appId, $setupCheckClass);
+ }
+
+ /**
* @param App[] $apps
*/
public function delegateCapabilityRegistrations(array $apps): void {
@@ -565,9 +596,6 @@ class RegistrationContext {
}
}
- /**
- * @param App[] $apps
- */
public function delegateDashboardPanelRegistrations(IManager $dashboardManager): void {
while (($panel = array_shift($this->dashboardPanels)) !== null) {
try {
@@ -729,6 +757,13 @@ class RegistrationContext {
}
/**
+ * @return ServiceRegistration<\OCP\TextToImage\IProvider>[]
+ */
+ public function getTextToImageProviders(): array {
+ return $this->textToImageProviders;
+ }
+
+ /**
* @return ServiceRegistration<ICustomTemplateProvider>[]
*/
public function getTemplateProviders(): array {
@@ -828,4 +863,11 @@ class RegistrationContext {
public function getPublicShareTemplateProviders(): array {
return $this->publicShareTemplateProviders;
}
+
+ /**
+ * @return ServiceRegistration<ISetupCheck>[]
+ */
+ public function getSetupChecks(): array {
+ return $this->setupChecks;
+ }
}
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index a012d1e8ea6..c342ea236e2 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -404,33 +404,6 @@ class DIContainer extends SimpleContainer implements IAppContainer {
}
/**
- * @deprecated use the ILogger instead
- * @param string $message
- * @param string $level
- * @return mixed
- */
- public function log($message, $level) {
- switch ($level) {
- case 'debug':
- $level = ILogger::DEBUG;
- break;
- case 'info':
- $level = ILogger::INFO;
- break;
- case 'warn':
- $level = ILogger::WARN;
- break;
- case 'fatal':
- $level = ILogger::FATAL;
- break;
- default:
- $level = ILogger::ERROR;
- break;
- }
- \OCP\Util::writeLog($this->getAppName(), $message, $level);
- }
-
- /**
* Register a capability
*
* @param string $serviceName e.g. 'OCA\Files\Capabilities'
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
index 408e88583a0..1186753ac73 100644
--- a/lib/private/AppFramework/Http/Request.php
+++ b/lib/private/AppFramework/Http/Request.php
@@ -193,9 +193,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
- return isset($this->items['parameters'][$offset])
- ? $this->items['parameters'][$offset]
- : null;
+ return $this->items['parameters'][$offset] ?? null;
}
/**
@@ -255,9 +253,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
case 'cookies':
case 'urlParams':
case 'method':
- return isset($this->items[$name])
- ? $this->items[$name]
- : null;
+ return $this->items[$name] ?? null;
case 'parameters':
case 'params':
if ($this->isPutStreamContent()) {
@@ -597,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
@@ -607,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/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
index 8bdacf550b6..f0d6ece8a93 100644
--- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
@@ -38,6 +38,7 @@ use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
+use OCP\ISession;
use OCP\Security\Bruteforce\IThrottler;
use ReflectionMethod;
@@ -91,6 +92,10 @@ class CORSMiddleware extends Middleware {
if ($this->request->passesCSRFCheck()) {
return;
}
+ // Skip CORS check for requests with AppAPI auth.
+ if ($this->session->getSession() instanceof ISession && $this->session->getSession()->get('app_api') === true) {
+ return;
+ }
$this->session->logout();
try {
if ($user === null || $pass === null || !$this->session->logClientIn($user, $pass, $this->request, $this->throttler)) {
diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
index 3232980b7e5..3b2296c145f 100644
--- a/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
+++ b/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index 7aa5cb83926..ecd8485cd7b 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -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/AppFramework/Utility/TimeFactory.php b/lib/private/AppFramework/Utility/TimeFactory.php
index 1e4655dd1cd..2763751132c 100644
--- a/lib/private/AppFramework/Utility/TimeFactory.php
+++ b/lib/private/AppFramework/Utility/TimeFactory.php
@@ -34,7 +34,7 @@ use OCP\AppFramework\Utility\ITimeFactory;
* Use this to get a timestamp or DateTime object in code to remain testable
*
* @since 8.0.0
- * @since 26.0.0 Extends the \Psr\Clock\ClockInterface interface
+ * @since 27.0.0 Implements the \Psr\Clock\ClockInterface interface
* @ref https://www.php-fig.org/psr/psr-20/#21-clockinterface
*/
class TimeFactory implements ITimeFactory {
diff --git a/lib/private/Authentication/Exceptions/ExpiredTokenException.php b/lib/private/Authentication/Exceptions/ExpiredTokenException.php
index 0dc92b45920..15069313712 100644
--- a/lib/private/Authentication/Exceptions/ExpiredTokenException.php
+++ b/lib/private/Authentication/Exceptions/ExpiredTokenException.php
@@ -27,17 +27,19 @@ namespace OC\Authentication\Exceptions;
use OC\Authentication\Token\IToken;
-class ExpiredTokenException extends InvalidTokenException {
- /** @var IToken */
- private $token;
-
- public function __construct(IToken $token) {
- parent::__construct();
-
- $this->token = $token;
+/**
+ * @deprecated 28.0.0 use {@see \OCP\Authentication\Exceptions\ExpiredTokenException} instead
+ */
+class ExpiredTokenException extends \OCP\Authentication\Exceptions\ExpiredTokenException {
+ public function __construct(
+ IToken $token,
+ ) {
+ parent::__construct($token);
}
public function getToken(): IToken {
- return $this->token;
+ $token = parent::getToken();
+ /** @var IToken $token We know that we passed OC interface from constructor */
+ return $token;
}
}
diff --git a/lib/private/Authentication/Exceptions/InvalidTokenException.php b/lib/private/Authentication/Exceptions/InvalidTokenException.php
index acaabff6b88..7de6e1522fa 100644
--- a/lib/private/Authentication/Exceptions/InvalidTokenException.php
+++ b/lib/private/Authentication/Exceptions/InvalidTokenException.php
@@ -24,7 +24,8 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Exceptions;
-use Exception;
-
-class InvalidTokenException extends Exception {
+/**
+ * @deprecated 28.0.0 use OCP version instead
+ */
+class InvalidTokenException extends \OCP\Authentication\Exceptions\InvalidTokenException {
}
diff --git a/lib/private/Authentication/Exceptions/WipeTokenException.php b/lib/private/Authentication/Exceptions/WipeTokenException.php
index 1c60ab9da78..25b7cb74359 100644
--- a/lib/private/Authentication/Exceptions/WipeTokenException.php
+++ b/lib/private/Authentication/Exceptions/WipeTokenException.php
@@ -27,17 +27,19 @@ namespace OC\Authentication\Exceptions;
use OC\Authentication\Token\IToken;
-class WipeTokenException extends InvalidTokenException {
- /** @var IToken */
- private $token;
-
- public function __construct(IToken $token) {
- parent::__construct();
-
- $this->token = $token;
+/**
+ * @deprecated 28.0.0 use {@see \OCP\Authentication\Exceptions\WipeTokenException} instead
+ */
+class WipeTokenException extends \OCP\Authentication\Exceptions\WipeTokenException {
+ public function __construct(
+ IToken $token,
+ ) {
+ parent::__construct($token);
}
public function getToken(): IToken {
- return $this->token;
+ $token = parent::getToken();
+ /** @var IToken $token We know that we passed OC interface from constructor */
+ return $token;
}
}
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/Token/IToken.php b/lib/private/Authentication/Token/IToken.php
index 5ca4eaea843..eb172f33396 100644
--- a/lib/private/Authentication/Token/IToken.php
+++ b/lib/private/Authentication/Token/IToken.php
@@ -26,109 +26,10 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Token;
-use JsonSerializable;
+use OCP\Authentication\Token\IToken as OCPIToken;
-interface IToken extends JsonSerializable {
- public const TEMPORARY_TOKEN = 0;
- public const PERMANENT_TOKEN = 1;
- public const WIPE_TOKEN = 2;
- public const DO_NOT_REMEMBER = 0;
- public const REMEMBER = 1;
-
- /**
- * Get the token ID
- *
- * @return int
- */
- public function getId(): int;
-
- /**
- * Get the user UID
- *
- * @return string
- */
- public function getUID(): string;
-
- /**
- * Get the login name used when generating the token
- *
- * @return string
- */
- public function getLoginName(): string;
-
- /**
- * Get the (encrypted) login password
- *
- * @return string|null
- */
- public function getPassword();
-
- /**
- * Get the timestamp of the last password check
- *
- * @return int
- */
- public function getLastCheck(): int;
-
- /**
- * Set the timestamp of the last password check
- *
- * @param int $time
- */
- public function setLastCheck(int $time);
-
- /**
- * Get the authentication scope for this token
- *
- * @return string
- */
- public function getScope(): string;
-
- /**
- * Get the authentication scope for this token
- *
- * @return array
- */
- public function getScopeAsArray(): array;
-
- /**
- * Set the authentication scope for this token
- *
- * @param array $scope
- */
- public function setScope($scope);
-
- /**
- * Get the name of the token
- * @return string
- */
- public function getName(): string;
-
- /**
- * Get the remember state of the token
- *
- * @return int
- */
- public function getRemember(): int;
-
- /**
- * Set the token
- *
- * @param string $token
- */
- public function setToken(string $token);
-
- /**
- * Set the password
- *
- * @param string $password
- */
- public function setPassword(string $password);
-
- /**
- * Set the expiration time of the token
- *
- * @param int|null $expires
- */
- public function setExpires($expires);
+/**
+ * @deprecated 28.0.0 use {@see \OCP\Authentication\Token\IToken} instead
+ */
+interface IToken extends OCPIToken {
}
diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php
index 45335e17c31..b77a856589d 100644
--- a/lib/private/Authentication/Token/PublicKeyToken.php
+++ b/lib/private/Authentication/Token/PublicKeyToken.php
@@ -137,10 +137,8 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken {
/**
* Get the (encrypted) login password
- *
- * @return string|null
*/
- public function getPassword() {
+ public function getPassword(): ?string {
return parent::getPassword();
}
@@ -165,10 +163,8 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken {
/**
* Get the timestamp of the last password check
- *
- * @param int $time
*/
- public function setLastCheck(int $time) {
+ public function setLastCheck(int $time): void {
parent::setLastCheck($time);
}
@@ -191,7 +187,7 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken {
return $scope;
}
- public function setScope($scope) {
+ public function setScope(array|string|null $scope): void {
if (is_array($scope)) {
parent::setScope(json_encode($scope));
} else {
@@ -211,15 +207,15 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken {
return parent::getRemember();
}
- public function setToken(string $token) {
+ public function setToken(string $token): void {
parent::setToken($token);
}
- public function setPassword(string $password = null) {
+ public function setPassword(string $password = null): void {
parent::setPassword($password);
}
- public function setExpires($expires) {
+ public function setExpires($expires): void {
parent::setExpires($expires);
}
diff --git a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
index 4817c6b8de0..97d6a02b4c4 100644
--- a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
+++ b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
@@ -25,8 +25,6 @@ declare(strict_types=1);
*/
namespace OC\Authentication\TwoFactorAuth\Db;
-use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
-use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use function array_map;
@@ -70,25 +68,24 @@ class ProviderUserAssignmentDao {
* Persist a new/updated (provider_id, uid, enabled) tuple
*/
public function persist(string $providerId, string $uid, int $enabled): void {
- $qb = $this->conn->getQueryBuilder();
-
- try {
- // Insert a new entry
- $insertQuery = $qb->insert(self::TABLE_NAME)->values([
- 'provider_id' => $qb->createNamedParameter($providerId),
- 'uid' => $qb->createNamedParameter($uid),
- 'enabled' => $qb->createNamedParameter($enabled, IQueryBuilder::PARAM_INT),
- ]);
-
- $insertQuery->execute();
- } catch (UniqueConstraintViolationException $ex) {
- // There is already an entry -> update it
- $updateQuery = $qb->update(self::TABLE_NAME)
- ->set('enabled', $qb->createNamedParameter($enabled))
- ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter($providerId)))
- ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
- $updateQuery->execute();
+ $conn = $this->conn;
+
+ // Insert a new entry
+ if ($conn->insertIgnoreConflict(self::TABLE_NAME, [
+ 'provider_id' => $providerId,
+ 'uid' => $uid,
+ 'enabled' => $enabled,
+ ])) {
+ return;
}
+
+ // There is already an entry -> update it
+ $qb = $conn->getQueryBuilder();
+ $updateQuery = $qb->update(self::TABLE_NAME)
+ ->set('enabled', $qb->createNamedParameter($enabled))
+ ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter($providerId)))
+ ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
+ $updateQuery->executeStatement();
}
/**
diff --git a/lib/private/Avatar/AvatarManager.php b/lib/private/Avatar/AvatarManager.php
index 4125c8eb0a8..1e137fa8715 100644
--- a/lib/private/Avatar/AvatarManager.php
+++ b/lib/private/Avatar/AvatarManager.php
@@ -55,59 +55,26 @@ use Psr\Log\LoggerInterface;
* This class implements methods to access Avatar functionality
*/
class AvatarManager implements IAvatarManager {
- /** @var IUserSession */
- private $userSession;
-
- /** @var Manager */
- private $userManager;
-
- /** @var IAppData */
- private $appData;
-
- /** @var IL10N */
- private $l;
-
- /** @var LoggerInterface */
- private $logger;
-
- /** @var IConfig */
- private $config;
-
- /** @var IAccountManager */
- private $accountManager;
-
- /** @var KnownUserService */
- private $knownUserService;
-
public function __construct(
- IUserSession $userSession,
- Manager $userManager,
- IAppData $appData,
- IL10N $l,
- LoggerInterface $logger,
- IConfig $config,
- IAccountManager $accountManager,
- KnownUserService $knownUserService
+ private IUserSession $userSession,
+ private Manager $userManager,
+ private IAppData $appData,
+ private IL10N $l,
+ private LoggerInterface $logger,
+ private IConfig $config,
+ private IAccountManager $accountManager,
+ private KnownUserService $knownUserService,
) {
- $this->userSession = $userSession;
- $this->userManager = $userManager;
- $this->appData = $appData;
- $this->l = $l;
- $this->logger = $logger;
- $this->config = $config;
- $this->accountManager = $accountManager;
- $this->knownUserService = $knownUserService;
}
/**
* return a user specific instance of \OCP\IAvatar
* @see \OCP\IAvatar
* @param string $userId the ownCloud user id
- * @return \OCP\IAvatar
* @throws \Exception In case the username is potentially dangerous
* @throws NotFoundException In case there is no user folder yet
*/
- public function getAvatar(string $userId) : IAvatar {
+ public function getAvatar(string $userId): IAvatar {
$user = $this->userManager->get($userId);
if ($user === null) {
throw new \Exception('user does not exist');
@@ -116,10 +83,7 @@ class AvatarManager implements IAvatarManager {
// sanitize userID - fixes casing issue (needed for the filesystem stuff that is done below)
$userId = $user->getUID();
- $requestingUser = null;
- if ($this->userSession !== null) {
- $requestingUser = $this->userSession->getUser();
- }
+ $requestingUser = $this->userSession->getUser();
try {
$folder = $this->appData->getFolder($userId);
@@ -157,7 +121,7 @@ class AvatarManager implements IAvatarManager {
/**
* Clear generated avatars
*/
- public function clearCachedAvatars() {
+ public function clearCachedAvatars(): void {
$users = $this->config->getUsersForUserValue('avatar', 'generated', 'true');
foreach ($users as $userId) {
// This also bumps the avatar version leading to cache invalidation in browsers
@@ -183,7 +147,6 @@ class AvatarManager implements IAvatarManager {
* Returns a GuestAvatar.
*
* @param string $name The guest name, e.g. "Albert".
- * @return IAvatar
*/
public function getGuestAvatar(string $name): IAvatar {
return new GuestAvatar($name, $this->logger);
diff --git a/lib/private/Avatar/GuestAvatar.php b/lib/private/Avatar/GuestAvatar.php
index 083deb4108f..26614cf6cfa 100644
--- a/lib/private/Avatar/GuestAvatar.php
+++ b/lib/private/Avatar/GuestAvatar.php
@@ -35,18 +35,15 @@ use Psr\Log\LoggerInterface;
*/
class GuestAvatar extends Avatar {
/**
- * Holds the guest user display name.
- */
- private string $userDisplayName;
-
- /**
* GuestAvatar constructor.
*
* @param string $userDisplayName The guest user display name
*/
- public function __construct(string $userDisplayName, LoggerInterface $logger) {
+ public function __construct(
+ private string $userDisplayName,
+ LoggerInterface $logger,
+ ) {
parent::__construct($logger);
- $this->userDisplayName = $userDisplayName;
}
/**
@@ -68,7 +65,6 @@ class GuestAvatar extends Avatar {
* Setting avatars isn't implemented for guests.
*
* @param \OCP\IImage|resource|string $data
- * @return void
*/
public function set($data): void {
// unimplemented for guest user avatars
diff --git a/lib/private/Avatar/PlaceholderAvatar.php b/lib/private/Avatar/PlaceholderAvatar.php
index e7ca89f4d30..d420ebe574a 100644
--- a/lib/private/Avatar/PlaceholderAvatar.php
+++ b/lib/private/Avatar/PlaceholderAvatar.php
@@ -32,9 +32,7 @@ use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
-use OCP\IConfig;
use OCP\IImage;
-use OCP\IL10N;
use Psr\Log\LoggerInterface;
/**
@@ -44,26 +42,12 @@ use Psr\Log\LoggerInterface;
* for faster retrieval, unlike the GuestAvatar.
*/
class PlaceholderAvatar extends Avatar {
- private ISimpleFolder $folder;
- private User $user;
-
- /**
- * UserAvatar constructor.
- *
- * @param IConfig $config The configuration
- * @param ISimpleFolder $folder The avatar files folder
- * @param IL10N $l The localization helper
- * @param User $user The user this class manages the avatar for
- * @param LoggerInterface $logger The logger
- */
public function __construct(
- ISimpleFolder $folder,
- $user,
- LoggerInterface $logger) {
+ private ISimpleFolder $folder,
+ private User $user,
+ LoggerInterface $logger,
+ ) {
parent::__construct($logger);
-
- $this->folder = $folder;
- $this->user = $user;
}
/**
@@ -80,7 +64,6 @@ class PlaceholderAvatar extends Avatar {
* @throws \Exception if the provided file is not a jpg or png image
* @throws \Exception if the provided image is not valid
* @throws NotSquareException if the image is not square
- * @return void
*/
public function set($data): void {
// unimplemented for placeholder avatars
@@ -102,8 +85,6 @@ class PlaceholderAvatar extends Avatar {
*
* If there is no avatar file yet, one is generated.
*
- * @param int $size
- * @return ISimpleFile
* @throws NotFoundException
* @throws \OCP\Files\NotPermittedException
* @throws \OCP\PreConditionNotMetException
diff --git a/lib/private/Avatar/UserAvatar.php b/lib/private/Avatar/UserAvatar.php
index 6d39d5f067d..f96259641f3 100644
--- a/lib/private/Avatar/UserAvatar.php
+++ b/lib/private/Avatar/UserAvatar.php
@@ -44,31 +44,14 @@ use Psr\Log\LoggerInterface;
* This class represents a registered user's avatar.
*/
class UserAvatar extends Avatar {
- private IConfig $config;
- private ISimpleFolder $folder;
- private IL10N $l;
- private User $user;
-
- /**
- * UserAvatar constructor.
- *
- * @param IConfig $config The configuration
- * @param ISimpleFolder $folder The avatar files folder
- * @param IL10N $l The localization helper
- * @param User $user The user this class manages the avatar for
- * @param LoggerInterface $logger The logger
- */
public function __construct(
- ISimpleFolder $folder,
- IL10N $l,
- User $user,
+ private ISimpleFolder $folder,
+ private IL10N $l,
+ private User $user,
LoggerInterface $logger,
- IConfig $config) {
+ private IConfig $config,
+ ) {
parent::__construct($logger);
- $this->folder = $folder;
- $this->l = $l;
- $this->user = $user;
- $this->config = $config;
}
/**
@@ -85,7 +68,6 @@ class UserAvatar extends Avatar {
* @throws \Exception if the provided file is not a jpg or png image
* @throws \Exception if the provided image is not valid
* @throws NotSquareException if the image is not square
- * @return void
*/
public function set($data): void {
$img = $this->getAvatarImage($data);
@@ -113,7 +95,6 @@ class UserAvatar extends Avatar {
* Returns an image from several sources.
*
* @param IImage|resource|string|\GdImage $data An image object, imagedata or path to the avatar
- * @return IImage
*/
private function getAvatarImage($data): IImage {
if ($data instanceof IImage) {
@@ -229,8 +210,6 @@ class UserAvatar extends Avatar {
*
* If there is no avatar file yet, one is generated.
*
- * @param int $size
- * @return ISimpleFile
* @throws NotFoundException
* @throws \OCP\Files\NotPermittedException
* @throws \OCP\PreConditionNotMetException
diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php
index 36cccbd4eab..ab7392522b2 100644
--- a/lib/private/BackgroundJob/JobList.php
+++ b/lib/private/BackgroundJob/JobList.php
@@ -41,6 +41,10 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;
+use function get_class;
+use function json_encode;
+use function md5;
+use function strlen;
class JobList implements IJobList {
protected IDBConnection $connection;
@@ -55,11 +59,10 @@ class JobList implements IJobList {
$this->logger = $logger;
}
- /**
- * @param IJob|class-string<IJob> $job
- * @param mixed $argument
- */
- public function add($job, $argument = null): void {
+ public function add($job, $argument = null, int $firstCheck = null): void {
+ if ($firstCheck === null) {
+ $firstCheck = $this->timeFactory->getTime();
+ }
if ($job instanceof IJob) {
$class = get_class($job);
} else {
@@ -79,18 +82,22 @@ class JobList implements IJobList {
'argument' => $query->createNamedParameter($argumentJson),
'argument_hash' => $query->createNamedParameter(md5($argumentJson)),
'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
- 'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
+ 'last_checked' => $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT),
]);
} else {
$query->update('jobs')
->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
- ->set('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT))
+ ->set('last_checked', $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('class', $query->createNamedParameter($class)))
->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson))));
}
$query->executeStatement();
}
+ public function scheduleAfter(string $job, int $runAfter, $argument = null): void {
+ $this->add($job, $argument, $runAfter);
+ }
+
/**
* @param IJob|string $job
* @param mixed $argument
@@ -406,7 +413,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/Collaboration/AutoComplete/Manager.php b/lib/private/Collaboration/AutoComplete/Manager.php
index cab15baf535..7b40165d4d8 100644
--- a/lib/private/Collaboration/AutoComplete/Manager.php
+++ b/lib/private/Collaboration/AutoComplete/Manager.php
@@ -29,47 +29,46 @@ use OCP\IServerContainer;
class Manager implements IManager {
/** @var string[] */
- protected $sorters = [];
+ protected array $sorters = [];
/** @var ISorter[] */
- protected $sorterInstances = [];
- /** @var IServerContainer */
- private $c;
+ protected array $sorterInstances = [];
- public function __construct(IServerContainer $container) {
- $this->c = $container;
+ public function __construct(
+ private IServerContainer $container,
+ ) {
}
- public function runSorters(array $sorters, array &$sortArray, array $context) {
+ public function runSorters(array $sorters, array &$sortArray, array $context): void {
$sorterInstances = $this->getSorters();
while ($sorter = array_shift($sorters)) {
if (isset($sorterInstances[$sorter])) {
$sorterInstances[$sorter]->sort($sortArray, $context);
} else {
- $this->c->getLogger()->warning('No sorter for ID "{id}", skipping', [
+ $this->container->getLogger()->warning('No sorter for ID "{id}", skipping', [
'app' => 'core', 'id' => $sorter
]);
}
}
}
- public function registerSorter($className) {
+ public function registerSorter($className): void {
$this->sorters[] = $className;
}
- protected function getSorters() {
+ protected function getSorters(): array {
if (count($this->sorterInstances) === 0) {
foreach ($this->sorters as $sorter) {
/** @var ISorter $instance */
- $instance = $this->c->resolve($sorter);
+ $instance = $this->container->resolve($sorter);
if (!$instance instanceof ISorter) {
- $this->c->getLogger()->notice('Skipping sorter which is not an instance of ISorter. Class name: {class}',
+ $this->container->getLogger()->notice('Skipping sorter which is not an instance of ISorter. Class name: {class}',
['app' => 'core', 'class' => $sorter]);
continue;
}
$sorterId = trim($instance->getId());
if (trim($sorterId) === '') {
- $this->c->getLogger()->notice('Skipping sorter with empty ID. Class name: {class}',
+ $this->container->getLogger()->notice('Skipping sorter with empty ID. Class name: {class}',
['app' => 'core', 'class' => $sorter]);
continue;
}
diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php
index 75e52c19e0b..1c98b904e76 100644
--- a/lib/private/Collaboration/Collaborators/GroupPlugin.php
+++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php
@@ -37,34 +37,26 @@ use OCP\IUserSession;
use OCP\Share\IShare;
class GroupPlugin implements ISearchPlugin {
- /** @var bool */
- protected $shareeEnumeration;
- /** @var bool */
- protected $shareWithGroupOnly;
- /** @var bool */
- protected $shareeEnumerationInGroupOnly;
- /** @var bool */
- protected $groupSharingDisabled;
-
- /** @var IGroupManager */
- private $groupManager;
- /** @var IConfig */
- private $config;
- /** @var IUserSession */
- private $userSession;
-
- public function __construct(IConfig $config, IGroupManager $groupManager, IUserSession $userSession) {
- $this->groupManager = $groupManager;
- $this->config = $config;
- $this->userSession = $userSession;
+ protected bool $shareeEnumeration;
+ protected bool $shareWithGroupOnly;
+
+ protected bool $shareeEnumerationInGroupOnly;
+
+ protected bool $groupSharingDisabled;
+
+ public function __construct(
+ private IConfig $config,
+ private IGroupManager $groupManager,
+ private IUserSession $userSession,
+ ) {
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
$this->groupSharingDisabled = $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'no';
}
- public function search($search, $limit, $offset, ISearchResult $searchResult) {
+ public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
if ($this->groupSharingDisabled) {
return false;
}
diff --git a/lib/private/Collaboration/Collaborators/LookupPlugin.php b/lib/private/Collaboration/Collaborators/LookupPlugin.php
index 86ac70ab970..5a03e4f8673 100644
--- a/lib/private/Collaboration/Collaborators/LookupPlugin.php
+++ b/lib/private/Collaboration/Collaborators/LookupPlugin.php
@@ -38,31 +38,21 @@ use OCP\Share\IShare;
use Psr\Log\LoggerInterface;
class LookupPlugin implements ISearchPlugin {
- /** @var IConfig */
- private $config;
- /** @var IClientService */
- private $clientService;
/** @var string remote part of the current user's cloud id */
- private $currentUserRemote;
- /** @var ICloudIdManager */
- private $cloudIdManager;
- /** @var LoggerInterface */
- private $logger;
+ private string $currentUserRemote;
- public function __construct(IConfig $config,
- IClientService $clientService,
- IUserSession $userSession,
- ICloudIdManager $cloudIdManager,
- LoggerInterface $logger) {
- $this->config = $config;
- $this->clientService = $clientService;
- $this->cloudIdManager = $cloudIdManager;
+ public function __construct(
+ private IConfig $config,
+ private IClientService $clientService,
+ IUserSession $userSession,
+ private ICloudIdManager $cloudIdManager,
+ private LoggerInterface $logger,
+ ) {
$currentUserCloudId = $userSession->getUser()->getCloudId();
$this->currentUserRemote = $cloudIdManager->resolveCloudId($currentUserCloudId)->getRemote();
- $this->logger = $logger;
}
- public function search($search, $limit, $offset, ISearchResult $searchResult) {
+ public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
$isGlobalScaleEnabled = $this->config->getSystemValueBool('gs.enabled', false);
$isLookupServerEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes') === 'yes';
$hasInternetConnection = $this->config->getSystemValueBool('has_internet_connection', true);
@@ -103,7 +93,7 @@ class LookupPlugin implements ISearchPlugin {
if ($this->currentUserRemote === $remote) {
continue;
}
- $name = isset($lookup['name']['value']) ? $lookup['name']['value'] : '';
+ $name = $lookup['name']['value'] ?? '';
$label = empty($name) ? $lookup['federationId'] : $name . ' (' . $lookup['federationId'] . ')';
$result[] = [
'label' => $label,
diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php
index aa317ec1720..cbdd84efbb3 100644
--- a/lib/private/Collaboration/Collaborators/MailPlugin.php
+++ b/lib/private/Collaboration/Collaborators/MailPlugin.php
@@ -41,50 +41,27 @@ use OCP\Share\IShare;
use OCP\Mail\IMailer;
class MailPlugin implements ISearchPlugin {
- /* @var bool */
- protected $shareWithGroupOnly;
- /* @var bool */
- protected $shareeEnumeration;
- /* @var bool */
- protected $shareeEnumerationInGroupOnly;
- /* @var bool */
- protected $shareeEnumerationPhone;
- /* @var bool */
- protected $shareeEnumerationFullMatch;
- /* @var bool */
- protected $shareeEnumerationFullMatchEmail;
+ protected bool $shareWithGroupOnly;
- /** @var IManager */
- private $contactsManager;
- /** @var ICloudIdManager */
- private $cloudIdManager;
- /** @var IConfig */
- private $config;
+ protected bool $shareeEnumeration;
- /** @var IGroupManager */
- private $groupManager;
- /** @var KnownUserService */
- private $knownUserService;
- /** @var IUserSession */
- private $userSession;
- /** @var IMailer */
- private $mailer;
+ protected bool $shareeEnumerationInGroupOnly;
- public function __construct(IManager $contactsManager,
- ICloudIdManager $cloudIdManager,
- IConfig $config,
- IGroupManager $groupManager,
- KnownUserService $knownUserService,
- IUserSession $userSession,
- IMailer $mailer) {
- $this->contactsManager = $contactsManager;
- $this->cloudIdManager = $cloudIdManager;
- $this->config = $config;
- $this->groupManager = $groupManager;
- $this->knownUserService = $knownUserService;
- $this->userSession = $userSession;
- $this->mailer = $mailer;
+ protected bool $shareeEnumerationPhone;
+ protected bool $shareeEnumerationFullMatch;
+
+ protected bool $shareeEnumerationFullMatchEmail;
+
+ public function __construct(
+ private IManager $contactsManager,
+ private ICloudIdManager $cloudIdManager,
+ private IConfig $config,
+ private IGroupManager $groupManager,
+ private KnownUserService $knownUserService,
+ private IUserSession $userSession,
+ private IMailer $mailer,
+ ) {
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
@@ -96,7 +73,7 @@ class MailPlugin implements ISearchPlugin {
/**
* {@inheritdoc}
*/
- public function search($search, $limit, $offset, ISearchResult $searchResult) {
+ public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
if ($this->shareeEnumerationFullMatch && !$this->shareeEnumerationFullMatchEmail) {
return false;
}
@@ -120,8 +97,8 @@ class MailPlugin implements ISearchPlugin {
[
'limit' => $limit,
'offset' => $offset,
- 'enumeration' => (bool) $this->shareeEnumeration,
- 'fullmatch' => (bool) $this->shareeEnumerationFullMatch,
+ 'enumeration' => $this->shareeEnumeration,
+ 'fullmatch' => $this->shareeEnumerationFullMatch,
]
);
$lowerSearch = strtolower($search);
@@ -286,6 +263,6 @@ class MailPlugin implements ISearchPlugin {
public function isCurrentUser(ICloudId $cloud): bool {
$currentUser = $this->userSession->getUser();
- return $currentUser instanceof IUser ? $currentUser->getUID() === $cloud->getUser() : false;
+ return $currentUser instanceof IUser && $currentUser->getUID() === $cloud->getUser();
}
}
diff --git a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php
index 413799e52c6..01f25b3d43d 100644
--- a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php
+++ b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php
@@ -33,14 +33,12 @@ use OCP\Share;
use OCP\Share\IShare;
class RemoteGroupPlugin implements ISearchPlugin {
- protected $shareeEnumeration;
+ private bool $enabled = false;
- /** @var ICloudIdManager */
- private $cloudIdManager;
- /** @var bool */
- private $enabled = false;
-
- public function __construct(ICloudFederationProviderManager $cloudFederationProviderManager, ICloudIdManager $cloudIdManager) {
+ public function __construct(
+ ICloudFederationProviderManager $cloudFederationProviderManager,
+ private ICloudIdManager $cloudIdManager,
+ ) {
try {
$fileSharingProvider = $cloudFederationProviderManager->getCloudFederationProvider('file');
$supportedShareTypes = $fileSharingProvider->getSupportedShareTypes();
@@ -50,10 +48,9 @@ class RemoteGroupPlugin implements ISearchPlugin {
} catch (\Exception $e) {
// do nothing, just don't enable federated group shares
}
- $this->cloudIdManager = $cloudIdManager;
}
- public function search($search, $limit, $offset, ISearchResult $searchResult) {
+ public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
$result = ['wide' => [], 'exact' => []];
$resultType = new SearchResultType('remote_groups');
@@ -83,7 +80,7 @@ class RemoteGroupPlugin implements ISearchPlugin {
* @return array [user, remoteURL]
* @throws \InvalidArgumentException
*/
- public function splitGroupRemote($address) {
+ public function splitGroupRemote($address): array {
try {
$cloudId = $this->cloudIdManager->resolveCloudId($address);
return [$cloudId->getUser(), $cloudId->getRemote()];
diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php
index 7d7a013a38c..a0868796689 100644
--- a/lib/private/Collaboration/Collaborators/RemotePlugin.php
+++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php
@@ -37,32 +37,22 @@ use OCP\IUserSession;
use OCP\Share\IShare;
class RemotePlugin implements ISearchPlugin {
- protected $shareeEnumeration;
+ protected bool $shareeEnumeration;
- /** @var IManager */
- private $contactsManager;
- /** @var ICloudIdManager */
- private $cloudIdManager;
- /** @var IConfig */
- private $config;
- /** @var IUserManager */
- private $userManager;
- /** @var string */
- private $userId = '';
+ private string $userId;
- public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) {
- $this->contactsManager = $contactsManager;
- $this->cloudIdManager = $cloudIdManager;
- $this->config = $config;
- $this->userManager = $userManager;
- $user = $userSession->getUser();
- if ($user !== null) {
- $this->userId = $user->getUID();
- }
+ public function __construct(
+ private IManager $contactsManager,
+ private ICloudIdManager $cloudIdManager,
+ private IConfig $config,
+ private IUserManager $userManager,
+ IUserSession $userSession,
+ ) {
+ $this->userId = $userSession->getUser()?->getUID() ?? '';
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
}
- public function search($search, $limit, $offset, ISearchResult $searchResult) {
+ public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
$result = ['wide' => [], 'exact' => []];
$resultType = new SearchResultType('remotes');
@@ -185,7 +175,7 @@ class RemotePlugin implements ISearchPlugin {
* @return array [user, remoteURL]
* @throws \InvalidArgumentException
*/
- public function splitUserRemote($address) {
+ public function splitUserRemote(string $address): array {
try {
$cloudId = $this->cloudIdManager->resolveCloudId($address);
return [$cloudId->getUser(), $cloudId->getRemote()];
diff --git a/lib/private/Collaboration/Collaborators/Search.php b/lib/private/Collaboration/Collaborators/Search.php
index 8d99ed42fcd..6b05d7e674b 100644
--- a/lib/private/Collaboration/Collaborators/Search.php
+++ b/lib/private/Collaboration/Collaborators/Search.php
@@ -35,32 +35,28 @@ use OCP\IContainer;
use OCP\Share;
class Search implements ISearch {
- /** @var IContainer */
- private $c;
+ protected array $pluginList = [];
- protected $pluginList = [];
-
- public function __construct(IContainer $c) {
- $this->c = $c;
+ public function __construct(
+ private IContainer $container,
+ ) {
}
/**
* @param string $search
- * @param array $shareTypes
* @param bool $lookup
* @param int|null $limit
* @param int|null $offset
- * @return array
* @throws \OCP\AppFramework\QueryException
*/
- public function search($search, array $shareTypes, $lookup, $limit, $offset) {
+ public function search($search, array $shareTypes, $lookup, $limit, $offset): array {
$hasMoreResults = false;
// Trim leading and trailing whitespace characters, e.g. when query is copy-pasted
$search = trim($search);
/** @var ISearchResult $searchResult */
- $searchResult = $this->c->resolve(SearchResult::class);
+ $searchResult = $this->container->resolve(SearchResult::class);
foreach ($shareTypes as $type) {
if (!isset($this->pluginList[$type])) {
@@ -68,14 +64,14 @@ class Search implements ISearch {
}
foreach ($this->pluginList[$type] as $plugin) {
/** @var ISearchPlugin $searchPlugin */
- $searchPlugin = $this->c->resolve($plugin);
+ $searchPlugin = $this->container->resolve($plugin);
$hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults;
}
}
// Get from lookup server, not a separate share type
if ($lookup) {
- $searchPlugin = $this->c->resolve(LookupPlugin::class);
+ $searchPlugin = $this->container->resolve(LookupPlugin::class);
$hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults;
}
@@ -105,7 +101,7 @@ class Search implements ISearch {
return [$searchResult->asArray(), $hasMoreResults];
}
- public function registerPlugin(array $pluginInfo) {
+ public function registerPlugin(array $pluginInfo): void {
$shareType = constant(Share::class . '::' . $pluginInfo['shareType']);
if ($shareType === null) {
throw new \InvalidArgumentException('Provided ShareType is invalid');
diff --git a/lib/private/Collaboration/Collaborators/SearchResult.php b/lib/private/Collaboration/Collaborators/SearchResult.php
index 76d78c9c231..524ffba4b9e 100644
--- a/lib/private/Collaboration/Collaborators/SearchResult.php
+++ b/lib/private/Collaboration/Collaborators/SearchResult.php
@@ -28,13 +28,13 @@ use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
class SearchResult implements ISearchResult {
- protected $result = [
+ protected array $result = [
'exact' => [],
];
- protected $exactIdMatches = [];
+ protected array $exactIdMatches = [];
- public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null) {
+ public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null): void {
$type = $type->getLabel();
if (!isset($this->result[$type])) {
$this->result[$type] = [];
@@ -47,15 +47,15 @@ class SearchResult implements ISearchResult {
}
}
- public function markExactIdMatch(SearchResultType $type) {
+ public function markExactIdMatch(SearchResultType $type): void {
$this->exactIdMatches[$type->getLabel()] = 1;
}
- public function hasExactIdMatch(SearchResultType $type) {
+ public function hasExactIdMatch(SearchResultType $type): bool {
return isset($this->exactIdMatches[$type->getLabel()]);
}
- public function hasResult(SearchResultType $type, $collaboratorId) {
+ public function hasResult(SearchResultType $type, $collaboratorId): bool {
$type = $type->getLabel();
if (!isset($this->result[$type])) {
return false;
@@ -73,11 +73,11 @@ class SearchResult implements ISearchResult {
return false;
}
- public function asArray() {
+ public function asArray(): array {
return $this->result;
}
- public function unsetResult(SearchResultType $type) {
+ public function unsetResult(SearchResultType $type): void {
$type = $type->getLabel();
$this->result[$type] = [];
if (isset($this->result['exact'][$type])) {
diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php
index 9beecdaa6cb..1bd6762d2e0 100644
--- a/lib/private/Collaboration/Collaborators/UserPlugin.php
+++ b/lib/private/Collaboration/Collaborators/UserPlugin.php
@@ -44,50 +44,30 @@ use OCP\Share\IShare;
use OCP\UserStatus\IManager as IUserStatusManager;
class UserPlugin implements ISearchPlugin {
- /* @var bool */
- protected $shareWithGroupOnly;
- /* @var bool */
- protected $shareeEnumeration;
- /* @var bool */
- protected $shareeEnumerationInGroupOnly;
- /* @var bool */
- protected $shareeEnumerationPhone;
- /* @var bool */
- protected $shareeEnumerationFullMatch;
- /* @var bool */
- protected $shareeEnumerationFullMatchUserId;
- /* @var bool */
- protected $shareeEnumerationFullMatchEmail;
- /* @var bool */
- protected $shareeEnumerationFullMatchIgnoreSecondDisplayName;
+ protected bool $shareWithGroupOnly;
- /** @var IConfig */
- private $config;
- /** @var IGroupManager */
- private $groupManager;
- /** @var IUserSession */
- private $userSession;
- /** @var IUserManager */
- private $userManager;
- /** @var KnownUserService */
- private $knownUserService;
- /** @var IUserStatusManager */
- private $userStatusManager;
+ protected bool $shareeEnumeration;
- public function __construct(IConfig $config,
- IUserManager $userManager,
- IGroupManager $groupManager,
- IUserSession $userSession,
- KnownUserService $knownUserService,
- IUserStatusManager $userStatusManager) {
- $this->config = $config;
+ protected bool $shareeEnumerationInGroupOnly;
- $this->groupManager = $groupManager;
- $this->userSession = $userSession;
- $this->userManager = $userManager;
- $this->knownUserService = $knownUserService;
- $this->userStatusManager = $userStatusManager;
+ protected bool $shareeEnumerationPhone;
+ protected bool $shareeEnumerationFullMatch;
+
+ protected bool $shareeEnumerationFullMatchUserId;
+
+ protected bool $shareeEnumerationFullMatchEmail;
+
+ protected bool $shareeEnumerationFullMatchIgnoreSecondDisplayName;
+
+ public function __construct(
+ private IConfig $config,
+ private IUserManager $userManager,
+ private IGroupManager $groupManager,
+ private IUserSession $userSession,
+ private KnownUserService $knownUserService,
+ private IUserStatusManager $userStatusManager,
+ ) {
$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
@@ -98,7 +78,7 @@ class UserPlugin implements ISearchPlugin {
$this->shareeEnumerationFullMatchIgnoreSecondDisplayName = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
}
- public function search($search, $limit, $offset, ISearchResult $searchResult) {
+ public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
$result = ['wide' => [], 'exact' => []];
$users = [];
$hasMoreResults = false;
@@ -282,8 +262,6 @@ class UserPlugin implements ISearchPlugin {
}
}
-
-
$type = new SearchResultType('users');
$searchResult->addResultSet($type, $result['wide'], $result['exact']);
if (count($result['exact'])) {
@@ -293,7 +271,7 @@ class UserPlugin implements ISearchPlugin {
return $hasMoreResults;
}
- public function takeOutCurrentUser(array &$users) {
+ public function takeOutCurrentUser(array &$users): void {
$currentUser = $this->userSession->getUser();
if (!is_null($currentUser)) {
if (isset($users[$currentUser->getUID()])) {
diff --git a/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php b/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php
index 1dbe8e3bc35..4277b3837d2 100644
--- a/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php
+++ b/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php
@@ -36,10 +36,9 @@ use OCP\Share\Events\ShareDeletedEvent;
/** @template-implements IEventListener<Event|NodeDeletedEvent|ShareDeletedEvent|ShareCreatedEvent> */
class FileReferenceEventListener implements IEventListener {
- private IReferenceManager $manager;
-
- public function __construct(IReferenceManager $manager) {
- $this->manager = $manager;
+ public function __construct(
+ private IReferenceManager $manager,
+ ) {
}
public static function register(IEventDispatcher $eventDispatcher): void {
diff --git a/lib/private/Collaboration/Reference/File/FileReferenceProvider.php b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php
index d423a830495..5f384213976 100644
--- a/lib/private/Collaboration/Reference/File/FileReferenceProvider.php
+++ b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php
@@ -41,26 +41,18 @@ use OCP\IUserSession;
use OCP\L10N\IFactory;
class FileReferenceProvider extends ADiscoverableReferenceProvider {
- private IURLGenerator $urlGenerator;
- private IRootFolder $rootFolder;
private ?string $userId;
- private IPreview $previewManager;
- private IMimeTypeDetector $mimeTypeDetector;
private IL10N $l10n;
public function __construct(
- IURLGenerator $urlGenerator,
- IRootFolder $rootFolder,
+ private IURLGenerator $urlGenerator,
+ private IRootFolder $rootFolder,
IUserSession $userSession,
- IMimeTypeDetector $mimeTypeDetector,
- IPreview $previewManager,
- IFactory $l10n
+ private IMimeTypeDetector $mimeTypeDetector,
+ private IPreview $previewManager,
+ IFactory $l10n,
) {
- $this->urlGenerator = $urlGenerator;
- $this->rootFolder = $rootFolder;
- $this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null;
- $this->previewManager = $previewManager;
- $this->mimeTypeDetector = $mimeTypeDetector;
+ $this->userId = $userSession->getUser()?->getUID();
$this->l10n = $l10n->get('files');
}
diff --git a/lib/private/Collaboration/Reference/LinkReferenceProvider.php b/lib/private/Collaboration/Reference/LinkReferenceProvider.php
index dbdab75abcb..df6c6cc9da9 100644
--- a/lib/private/Collaboration/Reference/LinkReferenceProvider.php
+++ b/lib/private/Collaboration/Reference/LinkReferenceProvider.php
@@ -54,24 +54,16 @@ class LinkReferenceProvider implements IReferenceProvider {
'image/webp'
];
- private IClientService $clientService;
- private LoggerInterface $logger;
- private SystemConfig $systemConfig;
- private IAppDataFactory $appDataFactory;
- private IURLGenerator $urlGenerator;
- private Limiter $limiter;
- private IUserSession $userSession;
- private IRequest $request;
-
- public function __construct(IClientService $clientService, LoggerInterface $logger, SystemConfig $systemConfig, IAppDataFactory $appDataFactory, IURLGenerator $urlGenerator, Limiter $limiter, IUserSession $userSession, IRequest $request) {
- $this->clientService = $clientService;
- $this->logger = $logger;
- $this->systemConfig = $systemConfig;
- $this->appDataFactory = $appDataFactory;
- $this->urlGenerator = $urlGenerator;
- $this->limiter = $limiter;
- $this->userSession = $userSession;
- $this->request = $request;
+ public function __construct(
+ private IClientService $clientService,
+ private LoggerInterface $logger,
+ private SystemConfig $systemConfig,
+ private IAppDataFactory $appDataFactory,
+ private IURLGenerator $urlGenerator,
+ private Limiter $limiter,
+ private IUserSession $userSession,
+ private IRequest $request,
+ ) {
}
public function matchReference(string $referenceText): bool {
@@ -119,7 +111,7 @@ class LinkReferenceProvider implements IReferenceProvider {
$linkContentType = $headResponse->getHeader('Content-Type');
$expectedContentType = 'text/html';
$suffixedExpectedContentType = $expectedContentType . ';';
- $startsWithSuffixed = substr($linkContentType, 0, strlen($suffixedExpectedContentType)) === $suffixedExpectedContentType;
+ $startsWithSuffixed = str_starts_with($linkContentType, $suffixedExpectedContentType);
// check the header begins with the expected content type
if ($linkContentType !== $expectedContentType && !$startsWithSuffixed) {
$this->logger->debug('Skip resolving links pointing to content type that is not "text/html"');
diff --git a/lib/private/Collaboration/Reference/ReferenceManager.php b/lib/private/Collaboration/Reference/ReferenceManager.php
index 2897410f5d6..1db87a56494 100644
--- a/lib/private/Collaboration/Reference/ReferenceManager.php
+++ b/lib/private/Collaboration/Reference/ReferenceManager.php
@@ -46,33 +46,22 @@ class ReferenceManager implements IReferenceManager {
/** @var IReferenceProvider[]|null */
private ?array $providers = null;
private ICache $cache;
- private Coordinator $coordinator;
- private ContainerInterface $container;
- private LinkReferenceProvider $linkReferenceProvider;
- private LoggerInterface $logger;
- private IConfig $config;
- private IUserSession $userSession;
-
- public function __construct(LinkReferenceProvider $linkReferenceProvider,
- ICacheFactory $cacheFactory,
- Coordinator $coordinator,
- ContainerInterface $container,
- LoggerInterface $logger,
- IConfig $config,
- IUserSession $userSession) {
- $this->linkReferenceProvider = $linkReferenceProvider;
+
+ public function __construct(
+ private LinkReferenceProvider $linkReferenceProvider,
+ ICacheFactory $cacheFactory,
+ private Coordinator $coordinator,
+ private ContainerInterface $container,
+ private LoggerInterface $logger,
+ private IConfig $config,
+ private IUserSession $userSession,
+ ) {
$this->cache = $cacheFactory->createDistributed('reference');
- $this->coordinator = $coordinator;
- $this->container = $container;
- $this->logger = $logger;
- $this->config = $config;
- $this->userSession = $userSession;
}
/**
* Extract a list of URLs from a text
*
- * @param string $text
* @return string[]
*/
public function extractReferences(string $text): array {
@@ -85,9 +74,6 @@ class ReferenceManager implements IReferenceManager {
/**
* Try to get a cached reference object from a reference string
- *
- * @param string $referenceId
- * @return IReference|null
*/
public function getReferenceFromCache(string $referenceId): ?IReference {
$matchedProvider = $this->getMatchedProvider($referenceId);
@@ -102,9 +88,6 @@ class ReferenceManager implements IReferenceManager {
/**
* Try to get a cached reference object from a full cache key
- *
- * @param string $cacheKey
- * @return IReference|null
*/
public function getReferenceByCacheKey(string $cacheKey): ?IReference {
$cached = $this->cache->get($cacheKey);
@@ -118,9 +101,6 @@ class ReferenceManager implements IReferenceManager {
/**
* Get a reference object from a reference string with a matching provider
* Use a cached reference if possible
- *
- * @param string $referenceId
- * @return IReference|null
*/
public function resolveReference(string $referenceId): ?IReference {
$matchedProvider = $this->getMatchedProvider($referenceId);
@@ -148,7 +128,6 @@ class ReferenceManager implements IReferenceManager {
* Try to match a reference string with all the registered providers
* Fallback to the link reference provider (using OpenGraph)
*
- * @param string $referenceId
* @return IReferenceProvider|null the first matching provider
*/
private function getMatchedProvider(string $referenceId): ?IReferenceProvider {
@@ -169,10 +148,6 @@ class ReferenceManager implements IReferenceManager {
/**
* Get a hashed full cache key from a key and prefix given by a provider
- *
- * @param IReferenceProvider $provider
- * @param string $referenceId
- * @return string
*/
private function getFullCacheKey(IReferenceProvider $provider, string $referenceId): string {
$cacheKey = $provider->getCacheKey($referenceId);
@@ -183,10 +158,6 @@ class ReferenceManager implements IReferenceManager {
/**
* Remove a specific cache entry from its key+prefix
- *
- * @param string $cachePrefix
- * @param string|null $cacheKey
- * @return void
*/
public function invalidateCache(string $cachePrefix, ?string $cacheKey = null): void {
if ($cacheKey === null) {
diff --git a/lib/private/Collaboration/Reference/RenderReferenceEventListener.php b/lib/private/Collaboration/Reference/RenderReferenceEventListener.php
index dc2c5612666..ab9b8fd1b63 100644
--- a/lib/private/Collaboration/Reference/RenderReferenceEventListener.php
+++ b/lib/private/Collaboration/Reference/RenderReferenceEventListener.php
@@ -34,12 +34,10 @@ use OCP\IInitialStateService;
/** @template-implements IEventListener<Event|RenderReferenceEvent> */
class RenderReferenceEventListener implements IEventListener {
- private IReferenceManager $manager;
- private IInitialStateService $initialStateService;
-
- public function __construct(IReferenceManager $manager, IInitialStateService $initialStateService) {
- $this->manager = $manager;
- $this->initialStateService = $initialStateService;
+ public function __construct(
+ private IReferenceManager $manager,
+ private IInitialStateService $initialStateService,
+ ) {
}
public static function register(IEventDispatcher $eventDispatcher): void {
diff --git a/lib/private/Collaboration/Resources/Collection.php b/lib/private/Collaboration/Resources/Collection.php
index e34c38a80cd..3a09fe60051 100644
--- a/lib/private/Collaboration/Resources/Collection.php
+++ b/lib/private/Collaboration/Resources/Collection.php
@@ -37,46 +37,21 @@ use OCP\IDBConnection;
use OCP\IUser;
class Collection implements ICollection {
- /** @var Manager */
- protected $manager;
-
- /** @var IDBConnection */
- protected $connection;
-
- /** @var int */
- protected $id;
-
- /** @var string */
- protected $name;
-
- /** @var IUser|null */
- protected $userForAccess;
-
- /** @var bool|null */
- protected $access;
-
/** @var IResource[] */
- protected $resources;
+ protected array $resources = [];
public function __construct(
- IManager $manager,
- IDBConnection $connection,
- int $id,
- string $name,
- ?IUser $userForAccess = null,
- ?bool $access = null
+ /** @var Manager $manager */
+ protected IManager $manager,
+ protected IDBConnection $connection,
+ protected int $id,
+ protected string $name,
+ protected ?IUser $userForAccess = null,
+ protected ?bool $access = null
) {
- $this->manager = $manager;
- $this->connection = $connection;
- $this->id = $id;
- $this->name = $name;
- $this->userForAccess = $userForAccess;
- $this->access = $access;
- $this->resources = [];
}
/**
- * @return int
* @since 16.0.0
*/
public function getId(): int {
@@ -84,7 +59,6 @@ class Collection implements ICollection {
}
/**
- * @return string
* @since 16.0.0
*/
public function getName(): string {
@@ -92,7 +66,6 @@ class Collection implements ICollection {
}
/**
- * @param string $name
* @since 16.0.0
*/
public function setName(string $name): void {
@@ -120,7 +93,6 @@ class Collection implements ICollection {
/**
* Adds a resource to a collection
*
- * @param IResource $resource
* @throws ResourceException when the resource is already part of the collection
* @since 16.0.0
*/
@@ -153,7 +125,6 @@ class Collection implements ICollection {
/**
* Removes a resource from a collection
*
- * @param IResource $resource
* @since 16.0.0
*/
public function removeResource(IResource $resource): void {
@@ -178,8 +149,6 @@ class Collection implements ICollection {
/**
* Can a user/guest access the collection
*
- * @param IUser|null $user
- * @return bool
* @since 16.0.0
*/
public function canAccess(?IUser $user): bool {
diff --git a/lib/private/Collaboration/Resources/Manager.php b/lib/private/Collaboration/Resources/Manager.php
index fc8804e69b4..0f4dbd7cbb7 100644
--- a/lib/private/Collaboration/Resources/Manager.php
+++ b/lib/private/Collaboration/Resources/Manager.php
@@ -45,26 +45,17 @@ class Manager implements IManager {
public const TABLE_RESOURCES = 'collres_resources';
public const TABLE_ACCESS_CACHE = 'collres_accesscache';
- /** @var IDBConnection */
- protected $connection;
- /** @var IProviderManager */
- protected $providerManager;
- /** @var LoggerInterface */
- protected $logger;
-
/** @var string[] */
- protected $providers = [];
-
+ protected array $providers = [];
- public function __construct(IDBConnection $connection, IProviderManager $providerManager, LoggerInterface $logger) {
- $this->connection = $connection;
- $this->providerManager = $providerManager;
- $this->logger = $logger;
+ public function __construct(
+ protected IDBConnection $connection,
+ protected IProviderManager $providerManager,
+ protected LoggerInterface $logger,
+ ) {
}
/**
- * @param int $id
- * @return ICollection
* @throws CollectionException when the collection could not be found
* @since 16.0.0
*/
@@ -85,9 +76,6 @@ class Manager implements IManager {
}
/**
- * @param int $id
- * @param IUser|null $user
- * @return ICollection
* @throws CollectionException when the collection could not be found
* @since 16.0.0
*/
@@ -122,10 +110,6 @@ class Manager implements IManager {
}
/**
- * @param IUser $user
- * @param string $filter
- * @param int $limit
- * @param int $start
* @return ICollection[]
* @since 16.0.0
*/
@@ -173,8 +157,6 @@ class Manager implements IManager {
}
/**
- * @param string $name
- * @return ICollection
* @since 16.0.0
*/
public function newCollection(string $name): ICollection {
@@ -189,9 +171,6 @@ class Manager implements IManager {
}
/**
- * @param string $type
- * @param string $id
- * @return IResource
* @since 16.0.0
*/
public function createResource(string $type, string $id): IResource {
@@ -199,10 +178,6 @@ class Manager implements IManager {
}
/**
- * @param string $type
- * @param string $id
- * @param IUser|null $user
- * @return IResource
* @throws ResourceException
* @since 16.0.0
*/
@@ -239,8 +214,6 @@ class Manager implements IManager {
}
/**
- * @param ICollection $collection
- * @param IUser|null $user
* @return IResource[]
* @since 16.0.0
*/
@@ -274,8 +247,6 @@ class Manager implements IManager {
/**
* Get the rich object data of a resource
*
- * @param IResource $resource
- * @return array
* @since 16.0.0
*/
public function getResourceRichObject(IResource $resource): array {
@@ -294,9 +265,6 @@ class Manager implements IManager {
/**
* Can a user/guest access the collection
*
- * @param IResource $resource
- * @param IUser|null $user
- * @return bool
* @since 16.0.0
*/
public function canAccessResource(IResource $resource, ?IUser $user): bool {
@@ -325,9 +293,6 @@ class Manager implements IManager {
/**
* Can a user/guest access the collection
*
- * @param ICollection $collection
- * @param IUser|null $user
- * @return bool
* @since 16.0.0
*/
public function canAccessCollection(ICollection $collection, ?IUser $user): bool {
@@ -505,9 +470,6 @@ class Manager implements IManager {
$query->execute();
}
- /**
- * @param string $provider
- */
public function registerResourceProvider(string $provider): void {
$this->logger->debug('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated', ['provider' => $provider]);
$this->providerManager->registerResourceProvider($provider);
@@ -516,7 +478,6 @@ class Manager implements IManager {
/**
* Get the resource type of the provider
*
- * @return string
* @since 16.0.0
*/
public function getType(): string {
diff --git a/lib/private/Collaboration/Resources/ProviderManager.php b/lib/private/Collaboration/Resources/ProviderManager.php
index 4f5ed53b162..823d6764f58 100644
--- a/lib/private/Collaboration/Resources/ProviderManager.php
+++ b/lib/private/Collaboration/Resources/ProviderManager.php
@@ -34,20 +34,15 @@ use Psr\Log\LoggerInterface;
class ProviderManager implements IProviderManager {
/** @var string[] */
- protected $providers = [];
+ protected array $providers = [];
/** @var IProvider[] */
- protected $providerInstances = [];
+ protected array $providerInstances = [];
- /** @var IServerContainer */
- protected $serverContainer;
-
- /** @var LoggerInterface */
- protected $logger;
-
- public function __construct(IServerContainer $serverContainer, LoggerInterface $logger) {
- $this->serverContainer = $serverContainer;
- $this->logger = $logger;
+ public function __construct(
+ protected IServerContainer $serverContainer,
+ protected LoggerInterface $logger,
+ ) {
}
public function getResourceProviders(): array {
diff --git a/lib/private/Collaboration/Resources/Resource.php b/lib/private/Collaboration/Resources/Resource.php
index b5e0215cb39..059b1802128 100644
--- a/lib/private/Collaboration/Resources/Resource.php
+++ b/lib/private/Collaboration/Resources/Resource.php
@@ -33,45 +33,19 @@ use OCP\IDBConnection;
use OCP\IUser;
class Resource implements IResource {
- /** @var IManager */
- protected $manager;
-
- /** @var IDBConnection */
- protected $connection;
-
- /** @var string */
- protected $type;
-
- /** @var string */
- protected $id;
-
- /** @var IUser|null */
- protected $userForAccess;
-
- /** @var bool|null */
- protected $access;
-
- /** @var array|null */
- protected $data;
+ protected ?array $data = null;
public function __construct(
- IManager $manager,
- IDBConnection $connection,
- string $type,
- string $id,
- ?IUser $userForAccess = null,
- ?bool $access = null
+ protected IManager $manager,
+ protected IDBConnection $connection,
+ protected string $type,
+ protected string $id,
+ protected ?IUser $userForAccess = null,
+ protected ?bool $access = null
) {
- $this->manager = $manager;
- $this->connection = $connection;
- $this->type = $type;
- $this->id = $id;
- $this->userForAccess = $userForAccess;
- $this->access = $access;
}
/**
- * @return string
* @since 16.0.0
*/
public function getType(): string {
@@ -79,7 +53,6 @@ class Resource implements IResource {
}
/**
- * @return string
* @since 16.0.0
*/
public function getId(): string {
@@ -87,7 +60,6 @@ class Resource implements IResource {
}
/**
- * @return array
* @since 16.0.0
*/
public function getRichObject(): array {
@@ -101,8 +73,6 @@ class Resource implements IResource {
/**
* Can a user/guest access the resource
*
- * @param IUser|null $user
- * @return bool
* @since 16.0.0
*/
public function canAccess(?IUser $user): bool {
diff --git a/lib/private/Command/ClosureJob.php b/lib/private/Command/ClosureJob.php
index 5639852e4db..7216bcc762a 100644
--- a/lib/private/Command/ClosureJob.php
+++ b/lib/private/Command/ClosureJob.php
@@ -24,11 +24,10 @@ namespace OC\Command;
use OC\BackgroundJob\QueuedJob;
use Laravel\SerializableClosure\SerializableClosure as LaravelClosure;
-use Opis\Closure\SerializableClosure as OpisClosure;
class ClosureJob extends QueuedJob {
- protected function run($serializedCallable) {
- $callable = unserialize($serializedCallable, [LaravelClosure::class, OpisClosure::class]);
+ protected function run($argument) {
+ $callable = unserialize($argument, [LaravelClosure::class]);
$callable = $callable->getClosure();
if (is_callable($callable)) {
$callable();
diff --git a/lib/private/Command/CommandJob.php b/lib/private/Command/CommandJob.php
index 5b267162c81..e34ffe9440b 100644
--- a/lib/private/Command/CommandJob.php
+++ b/lib/private/Command/CommandJob.php
@@ -29,8 +29,8 @@ use OCP\Command\ICommand;
* Wrap a command in the background job interface
*/
class CommandJob extends QueuedJob {
- protected function run($serializedCommand) {
- $command = unserialize($serializedCommand);
+ protected function run($argument) {
+ $command = unserialize($argument);
if ($command instanceof ICommand) {
$command->handle();
} else {
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php
index af4fda277d6..725febef85d 100644
--- a/lib/private/Comments/Manager.php
+++ b/lib/private/Comments/Manager.php
@@ -501,6 +501,22 @@ class Manager implements ICommentsManager {
)
);
}
+ } elseif ($lastKnownCommentId > 0) {
+ // We didn't find the "$lastKnownComment" but we still use the ID as an offset.
+ // This is required as a fall-back for expired messages in talk and deleted comments in other apps.
+ if ($sortDirection === 'desc') {
+ if ($includeLastKnown) {
+ $query->andWhere($query->expr()->lte('id', $query->createNamedParameter($lastKnownCommentId)));
+ } else {
+ $query->andWhere($query->expr()->lt('id', $query->createNamedParameter($lastKnownCommentId)));
+ }
+ } else {
+ if ($includeLastKnown) {
+ $query->andWhere($query->expr()->gte('id', $query->createNamedParameter($lastKnownCommentId)));
+ } else {
+ $query->andWhere($query->expr()->gt('id', $query->createNamedParameter($lastKnownCommentId)));
+ }
+ }
}
$resultStatement = $query->execute();
diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php
index 113f0507ef5..a0306c9798c 100644
--- a/lib/private/Console/Application.php
+++ b/lib/private/Console/Application.php
@@ -47,17 +47,12 @@ use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Application {
- /** @var IConfig */
- private $config;
+ private IConfig $config;
private SymfonyApplication $application;
- /** @var IEventDispatcher */
- private $dispatcher;
- /** @var IRequest */
- private $request;
- /** @var LoggerInterface */
- private $logger;
- /** @var MemoryInfo */
- private $memoryInfo;
+ private IEventDispatcher $dispatcher;
+ private IRequest $request;
+ private LoggerInterface $logger;
+ private MemoryInfo $memoryInfo;
public function __construct(IConfig $config,
IEventDispatcher $dispatcher,
@@ -74,8 +69,6 @@ class Application {
}
/**
- * @param InputInterface $input
- * @param ConsoleOutputInterface $output
* @throws \Exception
*/
public function loadCommands(
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/ContactsManager.php b/lib/private/ContactsManager.php
index c39f7c715cc..cfbd4305cd8 100644
--- a/lib/private/ContactsManager.php
+++ b/lib/private/ContactsManager.php
@@ -91,20 +91,20 @@ class ContactsManager implements IManager {
* This function can be used to delete the contact identified by the given id
*
* @param int $id the unique identifier to a contact
- * @param string $address_book_key identifier of the address book in which the contact shall be deleted
+ * @param string $addressBookKey identifier of the address book in which the contact shall be deleted
* @return bool successful or not
*/
- public function delete($id, $address_book_key) {
- $addressBook = $this->getAddressBook($address_book_key);
+ public function delete($id, $addressBookKey) {
+ $addressBook = $this->getAddressBook($addressBookKey);
if (!$addressBook) {
- return null;
+ return false;
}
if ($addressBook->getPermissions() & Constants::PERMISSION_DELETE) {
return $addressBook->delete($id);
}
- return null;
+ return false;
}
/**
@@ -112,11 +112,11 @@ class ContactsManager implements IManager {
* Otherwise the contact will be updated by replacing the entire data set.
*
* @param array $properties this array if key-value-pairs defines a contact
- * @param string $address_book_key identifier of the address book in which the contact shall be created or updated
- * @return array representing the contact just created or updated
+ * @param string $addressBookKey identifier of the address book in which the contact shall be created or updated
+ * @return ?array representing the contact just created or updated
*/
- public function createOrUpdate($properties, $address_book_key) {
- $addressBook = $this->getAddressBook($address_book_key);
+ public function createOrUpdate($properties, $addressBookKey) {
+ $addressBook = $this->getAddressBook($addressBookKey);
if (!$addressBook) {
return null;
}
@@ -133,7 +133,7 @@ class ContactsManager implements IManager {
*
* @return bool true if enabled, false if not
*/
- public function isEnabled() {
+ public function isEnabled(): bool {
return !empty($this->addressBooks) || !empty($this->addressBookLoaders);
}
@@ -192,11 +192,8 @@ class ContactsManager implements IManager {
/**
* Get (and load when needed) the address book for $key
- *
- * @param string $addressBookKey
- * @return IAddressBook
*/
- protected function getAddressBook($addressBookKey) {
+ protected function getAddressBook(string $addressBookKey): ?IAddressBook {
$this->loadAddressBooks();
if (!array_key_exists($addressBookKey, $this->addressBooks)) {
return null;
diff --git a/lib/private/DB/Adapter.php b/lib/private/DB/Adapter.php
index acaa529c0e2..ad232aaabd1 100644
--- a/lib/private/DB/Adapter.php
+++ b/lib/private/DB/Adapter.php
@@ -30,6 +30,7 @@ namespace OC\DB;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OC\DB\Exceptions\DbalException;
/**
* This handles the way we use to write queries, into something that can be
@@ -142,9 +143,12 @@ class Adapter {
foreach ($values as $key => $value) {
$builder->setValue($key, $builder->createNamedParameter($value));
}
- return $builder->execute();
- } catch (UniqueConstraintViolationException $e) {
- return 0;
+ return $builder->executeStatement();
+ } catch (DbalException $e) {
+ if ($e->getReason() === \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
+ return 0;
+ }
+ throw $e;
}
}
}
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index 85c6a72dfdb..df35e0b5e0d 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -42,7 +42,6 @@ use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
-use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\Schema;
@@ -220,7 +219,7 @@ class Connection extends \Doctrine\DBAL\Connection {
* @return Statement The prepared statement.
* @throws Exception
*/
- public function prepare($statement, $limit = null, $offset = null): Statement {
+ public function prepare($sql, $limit = null, $offset = null): Statement {
if ($limit === -1 || $limit === null) {
$limit = null;
} else {
@@ -231,9 +230,9 @@ class Connection extends \Doctrine\DBAL\Connection {
}
if (!is_null($limit)) {
$platform = $this->getDatabasePlatform();
- $statement = $platform->modifyLimitQuery($statement, $limit, $offset);
+ $sql = $platform->modifyLimitQuery($sql, $limit, $offset);
}
- $statement = $this->replaceTablePrefix($statement);
+ $statement = $this->replaceTablePrefix($sql);
$statement = $this->adapter->fixupStatement($statement);
return parent::prepare($statement);
@@ -321,14 +320,14 @@ class Connection extends \Doctrine\DBAL\Connection {
*
* @param string $seqName Name of the sequence object from which the ID should be returned.
*
- * @return string the last inserted ID.
+ * @return int the last inserted ID.
* @throws Exception
*/
- public function lastInsertId($seqName = null) {
- if ($seqName) {
- $seqName = $this->replaceTablePrefix($seqName);
+ public function lastInsertId($name = null): int {
+ if ($name) {
+ $name = $this->replaceTablePrefix($name);
}
- return $this->adapter->lastInsertId($seqName);
+ return $this->adapter->lastInsertId($name);
}
/**
@@ -600,10 +599,6 @@ class Connection extends \Doctrine\DBAL\Connection {
return new SQLiteMigrator($this, $config, $dispatcher);
} elseif ($platform instanceof OraclePlatform) {
return new OracleMigrator($this, $config, $dispatcher);
- } elseif ($platform instanceof MySQLPlatform) {
- return new MySQLMigrator($this, $config, $dispatcher);
- } elseif ($platform instanceof PostgreSQL94Platform) {
- return new PostgreSqlMigrator($this, $config, $dispatcher);
} else {
return new Migrator($this, $config, $dispatcher);
}
diff --git a/lib/private/DB/ConnectionAdapter.php b/lib/private/DB/ConnectionAdapter.php
index a53c7ecd994..e27c98194fb 100644
--- a/lib/private/DB/ConnectionAdapter.php
+++ b/lib/private/DB/ConnectionAdapter.php
@@ -27,6 +27,10 @@ namespace OC\DB;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Platforms\MySQLPlatform;
+use Doctrine\DBAL\Platforms\OraclePlatform;
+use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
+use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Schema\Schema;
use OC\DB\Exceptions\DbalException;
use OCP\DB\IPreparedStatement;
@@ -87,7 +91,7 @@ class ConnectionAdapter implements IDBConnection {
public function lastInsertId(string $table): int {
try {
- return (int)$this->inner->lastInsertId($table);
+ return $this->inner->lastInsertId($table);
} catch (Exception $e) {
throw DbalException::wrap($e);
}
@@ -242,4 +246,19 @@ class ConnectionAdapter implements IDBConnection {
public function getInner(): Connection {
return $this->inner;
}
+
+ public function getDatabaseProvider(): string {
+ $platform = $this->inner->getDatabasePlatform();
+ if ($platform instanceof MySQLPlatform) {
+ return IDBConnection::PLATFORM_MYSQL;
+ } elseif ($platform instanceof OraclePlatform) {
+ return IDBConnection::PLATFORM_ORACLE;
+ } elseif ($platform instanceof PostgreSQLPlatform) {
+ return IDBConnection::PLATFORM_POSTGRES;
+ } elseif ($platform instanceof SqlitePlatform) {
+ return IDBConnection::PLATFORM_SQLITE;
+ } else {
+ throw new \Exception('Database ' . $platform::class . ' not supported');
+ }
+ }
}
diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php
index 1b0ac436364..4b286ff5442 100644
--- a/lib/private/DB/ConnectionFactory.php
+++ b/lib/private/DB/ConnectionFactory.php
@@ -139,7 +139,7 @@ class ConnectionFactory {
$additionalConnectionParams = array_merge($additionalConnectionParams, $additionalConnectionParams['driverOptions']);
}
$host = $additionalConnectionParams['host'];
- $port = isset($additionalConnectionParams['port']) ? $additionalConnectionParams['port'] : null;
+ $port = $additionalConnectionParams['port'] ?? null;
$dbName = $additionalConnectionParams['dbname'];
// we set the connect string as dbname and unset the host to coerce doctrine into using it as connect string
diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php
index 71d7b51d149..60f9b65cd5f 100644
--- a/lib/private/DB/MigrationService.php
+++ b/lib/private/DB/MigrationService.php
@@ -58,7 +58,7 @@ class MigrationService {
/**
* @throws \Exception
*/
- public function __construct($appName, Connection $connection, ?IOutput $output = null, ?AppLocator $appLocator = null) {
+ public function __construct(string $appName, Connection $connection, ?IOutput $output = null, ?AppLocator $appLocator = null) {
$this->appName = $appName;
$this->connection = $connection;
if ($output === null) {
@@ -100,18 +100,15 @@ class MigrationService {
/**
* Returns the name of the app for which this migration is executed
- *
- * @return string
*/
- public function getApp() {
+ public function getApp(): string {
return $this->appName;
}
/**
- * @return bool
* @codeCoverageIgnore - this will implicitly tested on installation
*/
- private function createMigrationTable() {
+ private function createMigrationTable(): bool {
if ($this->migrationTableCreated) {
return false;
}
@@ -188,7 +185,7 @@ class MigrationService {
->where($qb->expr()->eq('app', $qb->createNamedParameter($this->getApp())))
->orderBy('version');
- $result = $qb->execute();
+ $result = $qb->executeQuery();
$rows = $result->fetchAll(\PDO::FETCH_COLUMN);
$result->closeCursor();
@@ -197,15 +194,17 @@ class MigrationService {
/**
* Returns all versions which are available in the migration folder
- *
- * @return array
+ * @return list<string>
*/
- public function getAvailableVersions() {
+ public function getAvailableVersions(): array {
$this->ensureMigrationsAreLoaded();
return array_map('strval', array_keys($this->migrations));
}
- protected function findMigrations() {
+ /**
+ * @return array<string, string>
+ */
+ protected function findMigrations(): array {
$directory = realpath($this->migrationsPath);
if ($directory === false || !file_exists($directory) || !is_dir($directory)) {
return [];
@@ -322,10 +321,9 @@ class MigrationService {
/**
* Return the explicit version for the aliases; current, next, prev, latest
*
- * @param string $alias
* @return mixed|null|string
*/
- public function getMigration($alias) {
+ public function getMigration(string $alias) {
switch ($alias) {
case 'current':
return $this->getCurrentVersion();
@@ -342,29 +340,22 @@ class MigrationService {
return '0';
}
- /**
- * @param string $version
- * @param int $delta
- * @return null|string
- */
- private function getRelativeVersion($version, $delta) {
+ private function getRelativeVersion(string $version, int $delta): ?string {
$this->ensureMigrationsAreLoaded();
$versions = $this->getAvailableVersions();
- array_unshift($versions, 0);
+ array_unshift($versions, '0');
+ /** @var int $offset */
$offset = array_search($version, $versions, true);
if ($offset === false || !isset($versions[$offset + $delta])) {
// Unknown version or delta out of bounds.
return null;
}
- return (string) $versions[$offset + $delta];
+ return (string)$versions[$offset + $delta];
}
- /**
- * @return string
- */
- private function getCurrentVersion() {
+ private function getCurrentVersion(): string {
$m = $this->getMigratedVersions();
if (count($m) === 0) {
return '0';
@@ -374,11 +365,9 @@ class MigrationService {
}
/**
- * @param string $version
- * @return string
* @throws \InvalidArgumentException
*/
- private function getClass($version) {
+ private function getClass(string $version): string {
$this->ensureMigrationsAreLoaded();
if (isset($this->migrations[$version])) {
@@ -390,22 +379,18 @@ class MigrationService {
/**
* Allows to set an IOutput implementation which is used for logging progress and messages
- *
- * @param IOutput $output
*/
- public function setOutput(IOutput $output) {
+ public function setOutput(IOutput $output): void {
$this->output = $output;
}
/**
* Applies all not yet applied versions up to $to
- *
- * @param string $to
- * @param bool $schemaOnly
* @throws \InvalidArgumentException
*/
- public function migrate($to = 'latest', $schemaOnly = false) {
+ public function migrate(string $to = 'latest', bool $schemaOnly = false): void {
if ($schemaOnly) {
+ $this->output->debug('Migrating schema only');
$this->migrateSchemaOnly($to);
return;
}
@@ -425,11 +410,9 @@ class MigrationService {
/**
* Applies all not yet applied versions up to $to
- *
- * @param string $to
* @throws \InvalidArgumentException
*/
- public function migrateSchemaOnly($to = 'latest') {
+ public function migrateSchemaOnly(string $to = 'latest'): void {
// read known migrations
$toBeExecuted = $this->getMigrationsToExecute($to);
@@ -439,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 {
@@ -447,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 74e5a285351..1d960e72dc5 100644
--- a/lib/private/DB/Migrator.php
+++ b/lib/private/DB/Migrator.php
@@ -31,7 +31,6 @@ use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\AbstractAsset;
-use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaDiff;
use Doctrine\DBAL\Types\StringType;
@@ -75,7 +74,7 @@ class Migrator {
$schemaDiff = $this->getDiff($targetSchema, $this->connection);
$script = '';
- $sqls = $schemaDiff->toSql($this->connection->getDatabasePlatform());
+ $sqls = $this->connection->getDatabasePlatform()->getAlterSchemaSQL($schemaDiff);
foreach ($sqls as $sql) {
$script .= $this->convertStatementToScript($sql);
}
@@ -95,18 +94,20 @@ class Migrator {
}
return preg_match($filterExpression, $asset) === 1;
});
- return $this->connection->getSchemaManager()->createSchema();
+ return $this->connection->createSchemaManager()->introspectSchema();
}
/**
* @return SchemaDiff
*/
protected function getDiff(Schema $targetSchema, Connection $connection) {
- // adjust varchar columns with a length higher then getVarcharMaxLength to clob
+ // Adjust STRING columns with a length higher than 4000 to TEXT (clob)
+ // for consistency between the supported databases and
+ // old vs. new installations.
foreach ($targetSchema->getTables() as $table) {
foreach ($table->getColumns() as $column) {
if ($column->getType() instanceof StringType) {
- if ($column->getLength() > $connection->getDatabasePlatform()->getVarcharMaxLength()) {
+ if ($column->getLength() > 4000) {
$column->setType(Type::getType('text'));
$column->setLength(null);
}
@@ -122,7 +123,7 @@ class Migrator {
}
return preg_match($filterExpression, $asset) === 1;
});
- $sourceSchema = $connection->getSchemaManager()->createSchema();
+ $sourceSchema = $connection->createSchemaManager()->introspectSchema();
// remove tables we don't know about
foreach ($sourceSchema->getTables() as $table) {
@@ -137,9 +138,8 @@ class Migrator {
}
}
- /** @psalm-suppress InternalMethod */
- $comparator = new Comparator();
- return $comparator->compare($sourceSchema, $targetSchema);
+ $comparator = $connection->createSchemaManager()->createComparator();
+ return $comparator->compareSchemas($sourceSchema, $targetSchema);
}
/**
@@ -155,11 +155,11 @@ class Migrator {
if (!$connection->getDatabasePlatform() instanceof MySQLPlatform) {
$connection->beginTransaction();
}
- $sqls = $schemaDiff->toSql($connection->getDatabasePlatform());
+ $sqls = $connection->getDatabasePlatform()->getAlterSchemaSQL($schemaDiff);
$step = 0;
foreach ($sqls as $sql) {
$this->emit($sql, $step++, count($sqls));
- $connection->query($sql);
+ $connection->executeQuery($sql);
}
if (!$connection->getDatabasePlatform() instanceof MySQLPlatform) {
$connection->commit();
@@ -178,7 +178,7 @@ class Migrator {
}
protected function getFilterExpression() {
- return '/^' . preg_quote($this->config->getSystemValueString('dbtableprefix', 'oc_')) . '/';
+ return '/^' . preg_quote($this->config->getSystemValueString('dbtableprefix', 'oc_'), '/') . '/';
}
protected function emit(string $sql, int $step, int $max): void {
diff --git a/lib/private/DB/MySQLMigrator.php b/lib/private/DB/MySQLMigrator.php
deleted file mode 100644
index 0f8cbb309f3..00000000000
--- a/lib/private/DB/MySQLMigrator.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Robin Appelman <robin@icewind.nl>
- * @author Thomas Müller <thomas.mueller@tmit.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\DB;
-
-use Doctrine\DBAL\Schema\Schema;
-
-class MySQLMigrator extends Migrator {
- /**
- * @param Schema $targetSchema
- * @param \Doctrine\DBAL\Connection $connection
- * @return \Doctrine\DBAL\Schema\SchemaDiff
- */
- protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
- $platform = $connection->getDatabasePlatform();
- $platform->registerDoctrineTypeMapping('enum', 'string');
- $platform->registerDoctrineTypeMapping('bit', 'string');
-
- $schemaDiff = parent::getDiff($targetSchema, $connection);
-
- // identifiers need to be quoted for mysql
- foreach ($schemaDiff->changedTables as $tableDiff) {
- $tableDiff->name = $this->connection->quoteIdentifier($tableDiff->name);
- foreach ($tableDiff->changedColumns as $column) {
- $column->oldColumnName = $this->connection->quoteIdentifier($column->oldColumnName);
- }
- }
-
- return $schemaDiff;
- }
-}
diff --git a/lib/private/DB/OracleConnection.php b/lib/private/DB/OracleConnection.php
index b7e040965ee..1112d5c450a 100644
--- a/lib/private/DB/OracleConnection.php
+++ b/lib/private/DB/OracleConnection.php
@@ -29,6 +29,8 @@ namespace OC\DB;
class OracleConnection extends Connection {
/**
* Quote the keys of the array
+ * @param array<string, string> $data
+ * @return array<string, string>
*/
private function quoteKeys(array $data) {
$return = [];
diff --git a/lib/private/DB/OracleMigrator.php b/lib/private/DB/OracleMigrator.php
index 18deb97ec26..5abab1a34e2 100644
--- a/lib/private/DB/OracleMigrator.php
+++ b/lib/private/DB/OracleMigrator.php
@@ -1,5 +1,8 @@
<?php
+
+declare(strict_types=1);
/**
+ * @copyright Copyright (c) 2023, Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
@@ -29,176 +32,114 @@
namespace OC\DB;
use Doctrine\DBAL\Exception;
-use Doctrine\DBAL\Schema\Column;
-use Doctrine\DBAL\Schema\ColumnDiff;
-use Doctrine\DBAL\Schema\ForeignKeyConstraint;
-use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
-use Doctrine\DBAL\Schema\Table;
class OracleMigrator extends Migrator {
/**
- * Quote a column's name but changing the name requires recreating
- * the column instance and copying over all properties.
- *
- * @param Column $column old column
- * @return Column new column instance with new name
- */
- protected function quoteColumn(Column $column) {
- $newColumn = new Column(
- $this->connection->quoteIdentifier($column->getName()),
- $column->getType()
- );
- $newColumn->setAutoincrement($column->getAutoincrement());
- $newColumn->setColumnDefinition($column->getColumnDefinition());
- $newColumn->setComment($column->getComment());
- $newColumn->setDefault($column->getDefault());
- $newColumn->setFixed($column->getFixed());
- $newColumn->setLength($column->getLength());
- $newColumn->setNotnull($column->getNotnull());
- $newColumn->setPrecision($column->getPrecision());
- $newColumn->setScale($column->getScale());
- $newColumn->setUnsigned($column->getUnsigned());
- $newColumn->setPlatformOptions($column->getPlatformOptions());
- $newColumn->setCustomSchemaOptions($column->getPlatformOptions());
- return $newColumn;
- }
-
- /**
- * Quote an index's name but changing the name requires recreating
- * the index instance and copying over all properties.
- *
- * @param Index $index old index
- * @return Index new index instance with new name
- */
- protected function quoteIndex($index) {
- return new Index(
- //TODO migrate existing uppercase indexes, then $this->connection->quoteIdentifier($index->getName()),
- $index->getName(),
- array_map(function ($columnName) {
- return $this->connection->quoteIdentifier($columnName);
- }, $index->getColumns()),
- $index->isUnique(),
- $index->isPrimary(),
- $index->getFlags(),
- $index->getOptions()
- );
- }
-
- /**
- * Quote an ForeignKeyConstraint's name but changing the name requires recreating
- * the ForeignKeyConstraint instance and copying over all properties.
- *
- * @param ForeignKeyConstraint $fkc old fkc
- * @return ForeignKeyConstraint new fkc instance with new name
- */
- protected function quoteForeignKeyConstraint($fkc) {
- return new ForeignKeyConstraint(
- array_map(function ($columnName) {
- return $this->connection->quoteIdentifier($columnName);
- }, $fkc->getLocalColumns()),
- $this->connection->quoteIdentifier($fkc->getForeignTableName()),
- array_map(function ($columnName) {
- return $this->connection->quoteIdentifier($columnName);
- }, $fkc->getForeignColumns()),
- $fkc->getName(),
- $fkc->getOptions()
- );
- }
-
- /**
* @param Schema $targetSchema
* @param \Doctrine\DBAL\Connection $connection
* @return \Doctrine\DBAL\Schema\SchemaDiff
* @throws Exception
*/
- protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
- $schemaDiff = parent::getDiff($targetSchema, $connection);
-
+ protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection): \Doctrine\DBAL\Schema\SchemaDiff {
// oracle forces us to quote the identifiers
- $schemaDiff->newTables = array_map(function (Table $table) {
- return new Table(
+ $quotedSchema = new Schema();
+ foreach ($targetSchema->getTables() as $table) {
+ $quotedTable = $quotedSchema->createTable(
$this->connection->quoteIdentifier($table->getName()),
- array_map(function (Column $column) {
- return $this->quoteColumn($column);
- }, $table->getColumns()),
- array_map(function (Index $index) {
- return $this->quoteIndex($index);
- }, $table->getIndexes()),
- [],
- array_map(function (ForeignKeyConstraint $fck) {
- return $this->quoteForeignKeyConstraint($fck);
- }, $table->getForeignKeys()),
- $table->getOptions()
);
- }, $schemaDiff->newTables);
- $schemaDiff->removedTables = array_map(function (Table $table) {
- return new Table(
- $this->connection->quoteIdentifier($table->getName()),
- $table->getColumns(),
- $table->getIndexes(),
- [],
- $table->getForeignKeys(),
- $table->getOptions()
- );
- }, $schemaDiff->removedTables);
-
- foreach ($schemaDiff->changedTables as $tableDiff) {
- $tableDiff->name = $this->connection->quoteIdentifier($tableDiff->name);
-
- $tableDiff->addedColumns = array_map(function (Column $column) {
- return $this->quoteColumn($column);
- }, $tableDiff->addedColumns);
-
- foreach ($tableDiff->changedColumns as $column) {
- $column->oldColumnName = $this->connection->quoteIdentifier($column->oldColumnName);
- // auto increment is not relevant for oracle and can anyhow not be applied on change
- $column->changedProperties = array_diff($column->changedProperties, ['autoincrement', 'unsigned']);
+ foreach ($table->getColumns() as $column) {
+ $newColumn = $quotedTable->addColumn(
+ $this->connection->quoteIdentifier($column->getName()),
+ $column->getType()->getTypeRegistry()->lookupName($column->getType()),
+ );
+ $newColumn->setAutoincrement($column->getAutoincrement());
+ $newColumn->setColumnDefinition($column->getColumnDefinition());
+ $newColumn->setComment($column->getComment());
+ $newColumn->setDefault($column->getDefault());
+ $newColumn->setFixed($column->getFixed());
+ $newColumn->setLength($column->getLength());
+ $newColumn->setNotnull($column->getNotnull());
+ $newColumn->setPrecision($column->getPrecision());
+ $newColumn->setScale($column->getScale());
+ $newColumn->setUnsigned($column->getUnsigned());
+ $newColumn->setPlatformOptions($column->getPlatformOptions());
}
- // remove columns that no longer have changed (because autoincrement and unsigned are not supported)
- $tableDiff->changedColumns = array_filter($tableDiff->changedColumns, function (ColumnDiff $column) {
- return count($column->changedProperties) > 0;
- });
-
- $tableDiff->removedColumns = array_map(function (Column $column) {
- return $this->quoteColumn($column);
- }, $tableDiff->removedColumns);
-
- $tableDiff->renamedColumns = array_map(function (Column $column) {
- return $this->quoteColumn($column);
- }, $tableDiff->renamedColumns);
-
- $tableDiff->addedIndexes = array_map(function (Index $index) {
- return $this->quoteIndex($index);
- }, $tableDiff->addedIndexes);
- $tableDiff->changedIndexes = array_map(function (Index $index) {
- return $this->quoteIndex($index);
- }, $tableDiff->changedIndexes);
-
- $tableDiff->removedIndexes = array_map(function (Index $index) {
- return $this->quoteIndex($index);
- }, $tableDiff->removedIndexes);
+ foreach ($table->getIndexes() as $index) {
+ if ($index->isPrimary()) {
+ $quotedTable->setPrimaryKey(
+ array_map(function ($columnName) {
+ return $this->connection->quoteIdentifier($columnName);
+ }, $index->getColumns()),
+ //TODO migrate existing uppercase indexes, then $this->connection->quoteIdentifier($index->getName()),
+ $index->getName(),
+ );
+ } elseif ($index->isUnique()) {
+ $quotedTable->addUniqueIndex(
+ array_map(function ($columnName) {
+ return $this->connection->quoteIdentifier($columnName);
+ }, $index->getColumns()),
+ //TODO migrate existing uppercase indexes, then $this->connection->quoteIdentifier($index->getName()),
+ $index->getName(),
+ $index->getOptions(),
+ );
+ } else {
+ $quotedTable->addIndex(
+ array_map(function ($columnName) {
+ return $this->connection->quoteIdentifier($columnName);
+ }, $index->getColumns()),
+ //TODO migrate existing uppercase indexes, then $this->connection->quoteIdentifier($index->getName()),
+ $index->getName(),
+ $index->getFlags(),
+ $index->getOptions(),
+ );
+ }
+ }
- $tableDiff->renamedIndexes = array_map(function (Index $index) {
- return $this->quoteIndex($index);
- }, $tableDiff->renamedIndexes);
+ foreach ($table->getUniqueConstraints() as $constraint) {
+ $quotedTable->addUniqueConstraint(
+ array_map(function ($columnName) {
+ return $this->connection->quoteIdentifier($columnName);
+ }, $constraint->getColumns()),
+ $this->connection->quoteIdentifier($constraint->getName()),
+ $constraint->getFlags(),
+ $constraint->getOptions(),
+ );
+ }
- $tableDiff->addedForeignKeys = array_map(function (ForeignKeyConstraint $fkc) {
- return $this->quoteForeignKeyConstraint($fkc);
- }, $tableDiff->addedForeignKeys);
+ foreach ($table->getForeignKeys() as $foreignKey) {
+ $quotedTable->addForeignKeyConstraint(
+ $this->connection->quoteIdentifier($foreignKey->getForeignTableName()),
+ array_map(function ($columnName) {
+ return $this->connection->quoteIdentifier($columnName);
+ }, $foreignKey->getLocalColumns()),
+ array_map(function ($columnName) {
+ return $this->connection->quoteIdentifier($columnName);
+ }, $foreignKey->getForeignColumns()),
+ $foreignKey->getOptions(),
+ $this->connection->quoteIdentifier($foreignKey->getName()),
+ );
+ }
- $tableDiff->changedForeignKeys = array_map(function (ForeignKeyConstraint $fkc) {
- return $this->quoteForeignKeyConstraint($fkc);
- }, $tableDiff->changedForeignKeys);
+ foreach ($table->getOptions() as $option => $value) {
+ $quotedTable->addOption(
+ $option,
+ $value,
+ );
+ }
+ }
- $tableDiff->removedForeignKeys = array_map(function (ForeignKeyConstraint $fkc) {
- return $this->quoteForeignKeyConstraint($fkc);
- }, $tableDiff->removedForeignKeys);
+ foreach ($targetSchema->getSequences() as $sequence) {
+ $quotedSchema->createSequence(
+ $sequence->getName(),
+ $sequence->getAllocationSize(),
+ $sequence->getInitialValue(),
+ );
}
- return $schemaDiff;
+ return parent::getDiff($quotedSchema, $connection);
}
/**
@@ -206,7 +147,7 @@ class OracleMigrator extends Migrator {
* @return string
*/
protected function convertStatementToScript($statement) {
- if (substr($statement, -1) === ';') {
+ if (str_ends_with($statement, ';')) {
return $statement . PHP_EOL . '/' . PHP_EOL;
}
$script = $statement . ';';
diff --git a/lib/private/DB/PostgreSqlMigrator.php b/lib/private/DB/PostgreSqlMigrator.php
deleted file mode 100644
index 92a0842e1a7..00000000000
--- a/lib/private/DB/PostgreSqlMigrator.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Thomas Müller <thomas.mueller@tmit.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\DB;
-
-use Doctrine\DBAL\Schema\Schema;
-
-class PostgreSqlMigrator extends Migrator {
- /**
- * @param Schema $targetSchema
- * @param \Doctrine\DBAL\Connection $connection
- * @return \Doctrine\DBAL\Schema\SchemaDiff
- */
- protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
- $schemaDiff = parent::getDiff($targetSchema, $connection);
-
- foreach ($schemaDiff->changedTables as $tableDiff) {
- // fix default value in brackets - pg 9.4 is returning a negative default value in ()
- // see https://github.com/doctrine/dbal/issues/2427
- foreach ($tableDiff->changedColumns as $column) {
- $column->changedProperties = array_filter($column->changedProperties, function ($changedProperties) use ($column) {
- if ($changedProperties !== 'default') {
- return true;
- }
- $fromDefault = $column->fromColumn->getDefault();
- $toDefault = $column->column->getDefault();
- $fromDefault = trim((string) $fromDefault, '()');
-
- // by intention usage of !=
- return $fromDefault != $toDefault;
- });
- }
- }
-
- return $schemaDiff;
- }
-}
diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php
index 2f97b4a146c..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]);
}
@@ -1202,7 +1202,7 @@ class QueryBuilder implements IQueryBuilder {
* @link http://www.zetacomponents.org
*
* @param mixed $value
- * @param mixed $type
+ * @param IQueryBuilder::PARAM_* $type
* @param string $placeHolder The name to bind with. The string must start with a colon ':'.
*
* @return IParameter the placeholder name used.
@@ -1229,7 +1229,7 @@ class QueryBuilder implements IQueryBuilder {
* </code>
*
* @param mixed $value
- * @param integer $type
+ * @param IQueryBuilder::PARAM_* $type
*
* @return IParameter
*/
diff --git a/lib/private/DB/SQLiteMigrator.php b/lib/private/DB/SQLiteMigrator.php
index cbb39070a48..e0102e105b2 100644
--- a/lib/private/DB/SQLiteMigrator.php
+++ b/lib/private/DB/SQLiteMigrator.php
@@ -24,8 +24,6 @@
namespace OC\DB;
use Doctrine\DBAL\Schema\Schema;
-use Doctrine\DBAL\Types\BigIntType;
-use Doctrine\DBAL\Types\Type;
class SQLiteMigrator extends Migrator {
/**
@@ -34,21 +32,12 @@ class SQLiteMigrator extends Migrator {
* @return \Doctrine\DBAL\Schema\SchemaDiff
*/
protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
- $platform = $connection->getDatabasePlatform();
- $platform->registerDoctrineTypeMapping('tinyint unsigned', 'integer');
- $platform->registerDoctrineTypeMapping('smallint unsigned', 'integer');
- $platform->registerDoctrineTypeMapping('varchar ', 'string');
-
foreach ($targetSchema->getTables() as $table) {
foreach ($table->getColumns() as $column) {
// column comments are not supported on SQLite
if ($column->getComment() !== null) {
$column->setComment(null);
}
- // with sqlite autoincrement columns is of type integer
- if ($column->getType() instanceof BigIntType && $column->getAutoincrement()) {
- $column->setType(Type::getType('integer'));
- }
}
}
diff --git a/lib/private/Dashboard/Manager.php b/lib/private/Dashboard/Manager.php
index 18a66499167..afe28872e69 100644
--- a/lib/private/Dashboard/Manager.php
+++ b/lib/private/Dashboard/Manager.php
@@ -40,8 +40,8 @@ class Manager implements IManager {
/** @var array */
private $lazyWidgets = [];
- /** @var IWidget[] */
- private $widgets = [];
+ /** @var array<string, IWidget> */
+ private array $widgets = [];
private ContainerInterface $serverContainer;
private ?IAppManager $appManager = null;
@@ -134,6 +134,9 @@ class Manager implements IManager {
$this->lazyWidgets = [];
}
+ /**
+ * @return array<string, IWidget>
+ */
public function getWidgets(): array {
$this->loadLazyPanels();
return $this->widgets;
diff --git a/lib/private/DateTimeFormatter.php b/lib/private/DateTimeFormatter.php
index 1c8b4f6d3ab..57c4833a4e3 100644
--- a/lib/private/DateTimeFormatter.php
+++ b/lib/private/DateTimeFormatter.php
@@ -125,7 +125,7 @@ class DateTimeFormatter implements \OCP\IDateTimeFormatter {
* @return string Formatted relative date string
*/
public function formatDateRelativeDay($timestamp, $format = 'long', \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) {
- if (substr($format, -1) !== '*' && substr($format, -1) !== '*') {
+ if (!str_ends_with($format, '^') && !str_ends_with($format, '*')) {
$format .= '^';
}
@@ -289,7 +289,7 @@ class DateTimeFormatter implements \OCP\IDateTimeFormatter {
* @return string Formatted relative date and time string
*/
public function formatDateTimeRelativeDay($timestamp, $formatDate = 'long', $formatTime = 'medium', \DateTimeZone $timeZone = null, \OCP\IL10N $l = null) {
- if (substr($formatDate, -1) !== '^' && substr($formatDate, -1) !== '*') {
+ if (!str_ends_with($formatDate, '^') && !str_ends_with($formatDate, '*')) {
$formatDate .= '^';
}
diff --git a/lib/private/Federation/CloudFederationProviderManager.php b/lib/private/Federation/CloudFederationProviderManager.php
index b11c4060ab4..ea2f0dd7575 100644
--- a/lib/private/Federation/CloudFederationProviderManager.php
+++ b/lib/private/Federation/CloudFederationProviderManager.php
@@ -1,9 +1,13 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org>
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Maxence Lange <maxence@artificial-owl.com>
*
* @license GNU AGPL version 3 or any later version
*
@@ -32,6 +36,9 @@ use OCP\Federation\ICloudFederationProviderManager;
use OCP\Federation\ICloudFederationShare;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\OCM\Exceptions\OCMProviderException;
+use OCP\OCM\IOCMDiscoveryService;
use Psr\Log\LoggerInterface;
/**
@@ -43,40 +50,16 @@ use Psr\Log\LoggerInterface;
*/
class CloudFederationProviderManager implements ICloudFederationProviderManager {
/** @var array list of available cloud federation providers */
- private $cloudFederationProvider;
-
- /** @var IAppManager */
- private $appManager;
-
- /** @var IClientService */
- private $httpClientService;
-
- /** @var ICloudIdManager */
- private $cloudIdManager;
-
- private LoggerInterface $logger;
-
- /** @var array cache OCM end-points */
- private $ocmEndPoints = [];
-
- private $supportedAPIVersion = '1.0-proposal1';
-
- /**
- * CloudFederationProviderManager constructor.
- *
- * @param IAppManager $appManager
- * @param IClientService $httpClientService
- * @param ICloudIdManager $cloudIdManager
- */
- public function __construct(IAppManager $appManager,
- IClientService $httpClientService,
- ICloudIdManager $cloudIdManager,
- LoggerInterface $logger) {
- $this->cloudFederationProvider = [];
- $this->appManager = $appManager;
- $this->httpClientService = $httpClientService;
- $this->cloudIdManager = $cloudIdManager;
- $this->logger = $logger;
+ private array $cloudFederationProvider = [];
+
+ public function __construct(
+ private IConfig $config,
+ private IAppManager $appManager,
+ private IClientService $httpClientService,
+ private ICloudIdManager $cloudIdManager,
+ private IOCMDiscoveryService $discoveryService,
+ private LoggerInterface $logger
+ ) {
}
@@ -130,16 +113,18 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
public function sendShare(ICloudFederationShare $share) {
$cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
- $ocmEndPoint = $this->getOCMEndPoint($cloudID->getRemote());
- if (empty($ocmEndPoint)) {
+ try {
+ $ocmProvider = $this->discoveryService->discover($cloudID->getRemote());
+ } catch (OCMProviderException $e) {
return false;
}
$client = $this->httpClientService->newClient();
try {
- $response = $client->post($ocmEndPoint . '/shares', [
+ $response = $client->post($ocmProvider->getEndPoint() . '/shares', [
'body' => json_encode($share->getShare()),
'headers' => ['content-type' => 'application/json'],
+ 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
'timeout' => 10,
'connect_timeout' => 10,
]);
@@ -168,17 +153,18 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
* @return array|false
*/
public function sendNotification($url, ICloudFederationNotification $notification) {
- $ocmEndPoint = $this->getOCMEndPoint($url);
-
- if (empty($ocmEndPoint)) {
+ try {
+ $ocmProvider = $this->discoveryService->discover($url);
+ } catch (OCMProviderException $e) {
return false;
}
$client = $this->httpClientService->newClient();
try {
- $response = $client->post($ocmEndPoint . '/notifications', [
+ $response = $client->post($ocmProvider->getEndPoint() . '/notifications', [
'body' => json_encode($notification->getMessage()),
'headers' => ['content-type' => 'application/json'],
+ 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
'timeout' => 10,
'connect_timeout' => 10,
]);
@@ -202,36 +188,4 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
public function isReady() {
return $this->appManager->isEnabledForUser('cloud_federation_api');
}
- /**
- * check if server supports the new OCM api and ask for the correct end-point
- *
- * @param string $url full base URL of the cloud server
- * @return string
- */
- protected function getOCMEndPoint($url) {
- if (isset($this->ocmEndPoints[$url])) {
- return $this->ocmEndPoints[$url];
- }
-
- $client = $this->httpClientService->newClient();
- try {
- $response = $client->get($url . '/ocm-provider/', ['timeout' => 10, 'connect_timeout' => 10]);
- } catch (\Exception $e) {
- $this->ocmEndPoints[$url] = '';
- return '';
- }
-
- $result = $response->getBody();
- $result = json_decode($result, true);
-
- $supportedVersion = isset($result['apiVersion']) && $result['apiVersion'] === $this->supportedAPIVersion;
-
- if (isset($result['endPoint']) && $supportedVersion) {
- $this->ocmEndPoints[$url] = $result['endPoint'];
- return $result['endPoint'];
- }
-
- $this->ocmEndPoints[$url] = '';
- return '';
- }
}
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index 67d01bb6999..27db4dfe809 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -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);
}
diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php
index ce9df2823c8..d1a64552fd1 100644
--- a/lib/private/Files/Cache/CacheEntry.php
+++ b/lib/private/Files/Cache/CacheEntry.php
@@ -134,7 +134,7 @@ class CacheEntry implements ICacheEntry {
}
public function getUnencryptedSize(): int {
- if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) {
+ if ($this->data['encrypted'] && isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) {
return $this->data['unencrypted_size'];
} else {
return $this->data['size'] ?? 0;
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 52268032409..074e88e7639 100644
--- a/lib/private/Files/Cache/Scanner.php
+++ b/lib/private/Files/Cache/Scanner.php
@@ -385,10 +385,10 @@ class Scanner extends BasicEmitter implements IScanner {
* @param int $reuse a combination of self::REUSE_*
* @param int $folderId id for the folder to be scanned
* @param bool $lock set to false to disable getting an additional read lock during scanning
- * @param int $oldSize the size of the folder before (re)scanning the children
+ * @param int|float $oldSize the size of the folder before (re)scanning the children
* @return int|float the size of the scanned folder or -1 if the size is unknown at this stage
*/
- protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int $oldSize) {
+ protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize) {
if ($reuse === -1) {
$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
}
@@ -418,7 +418,10 @@ class Scanner extends BasicEmitter implements IScanner {
return $size;
}
- private function handleChildren($path, $recursive, $reuse, $folderId, $lock, &$size) {
+ /**
+ * @param bool|IScanner::SCAN_RECURSIVE_INCOMPLETE $recursive
+ */
+ private function handleChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float &$size): array {
// we put this in it's own function so it cleans up the memory before we start recursing
$existingChildren = $this->getExistingChildren($folderId);
$newChildren = iterator_to_array($this->storage->getDirectoryContent($path));
@@ -436,7 +439,7 @@ class Scanner extends BasicEmitter implements IScanner {
$childQueue = [];
$newChildNames = [];
foreach ($newChildren as $fileMeta) {
- $permissions = isset($fileMeta['scan_permissions']) ? $fileMeta['scan_permissions'] : $fileMeta['permissions'];
+ $permissions = $fileMeta['scan_permissions'] ?? $fileMeta['permissions'];
if ($permissions === 0) {
continue;
}
@@ -453,7 +456,7 @@ class Scanner extends BasicEmitter implements IScanner {
$newChildNames[] = $file;
$child = $path ? $path . '/' . $file : $file;
try {
- $existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : false;
+ $existingData = $existingChildren[$file] ?? false;
$data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock, $fileMeta);
if ($data) {
if ($data['mimetype'] === 'httpd/unix-directory' && $recursive === self::SCAN_RECURSIVE) {
diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php
index b9a70bbd39b..c3699cca63d 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
@@ -76,7 +78,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 +88,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 +114,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 +154,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 +192,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 +210,9 @@ class SearchBuilder {
'favorite' => 'boolean',
'fileid' => 'integer',
'storage' => 'integer',
+ 'share_with' => 'string',
+ 'share_type' => 'integer',
+ 'owner' => 'string',
];
$comparisons = [
'mimetype' => ['eq', 'like'],
@@ -199,6 +225,9 @@ class SearchBuilder {
'favorite' => ['eq'],
'fileid' => ['eq'],
'storage' => ['eq'],
+ 'share_with' => ['eq'],
+ 'share_type' => ['eq'],
+ 'owner' => ['eq'],
];
if (!isset($types[$operator->getField()])) {
@@ -213,6 +242,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 +275,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/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 90f94b6598e..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()
@@ -238,7 +220,7 @@ class UserMountCache implements IUserMountCache {
$row['mount_point'],
$row['mount_provider_class'] ?? '',
$mount_id,
- isset($row['path']) ? $row['path'] : '',
+ $row['path'] ?? '',
);
}
@@ -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];
}
/**
@@ -463,7 +453,7 @@ class UserMountCache implements IUserMountCache {
}, $mounts);
$mounts = array_combine($mountPoints, $mounts);
- $current = $path;
+ $current = rtrim($path, '/');
// walk up the directory tree until we find a path that has a mountpoint set
// the loop will return if a mountpoint is found or break if none are found
while (true) {
diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php
index 3937ee16a7c..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,
+ };
}
/**
@@ -207,7 +202,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
if ($includeMounts) {
$this->updateEntryfromSubMounts();
- if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) {
+ if ($this->isEncrypted() && isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) {
return $this->data['unencrypted_size'];
} else {
return isset($this->data['size']) ? 0 + $this->data['size'] : 0;
@@ -229,11 +224,11 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return bool
*/
public function isEncrypted() {
- return $this->data['encrypted'];
+ return $this->data['encrypted'] ?? false;
}
/**
- * Return the currently version used for the HMAC in the encryption app
+ * Return the current version used for the HMAC in the encryption app
*/
public function getEncryptedVersion(): int {
return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1;
@@ -243,11 +238,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return int
*/
public function getPermissions() {
- $perms = (int) $this->data['permissions'];
- if (\OCP\Util::isSharingDisabledForUser() || ($this->isShared() && !\OC\Share\Share::isResharingAllowed())) {
- $perms = $perms & ~\OCP\Constants::PERMISSION_SHARE;
- }
- return $perms;
+ return (int) $this->data['permissions'];
}
/**
@@ -315,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 +406,16 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
public function getUploadTime(): int {
return (int) $this->data['upload_time'];
}
+
+ 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/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 805cce658a6..94304ff4838 100644
--- a/lib/private/Files/Mount/Manager.php
+++ b/lib/private/Files/Mount/Manager.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Jonas <jonas@freesources.org>
*
* @license AGPL-3.0
*
@@ -33,6 +34,7 @@ use OCP\Cache\CappedMemoryCache;
use OC\Files\Filesystem;
use OC\Files\SetupManager;
use OC\Files\SetupManagerFactory;
+use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
@@ -99,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 . '/';
@@ -115,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)));
}
/**
@@ -226,4 +237,21 @@ class Manager implements IMountManager {
});
}
}
+
+ /**
+ * Return the mount matching a cached mount info (or mount file info)
+ *
+ * @param ICachedMountInfo $info
+ *
+ * @return IMountPoint|null
+ */
+ public function getMountFromMountInfo(ICachedMountInfo $info): ?IMountPoint {
+ $this->setupManager->setupForPath($info->getMountPoint());
+ foreach ($this->mounts as $mount) {
+ if ($mount->getMountPoint() === $info->getMountPoint()) {
+ return $mount;
+ }
+ }
+ return null;
+ }
}
diff --git a/lib/private/Files/Mount/MountPoint.php b/lib/private/Files/Mount/MountPoint.php
index f526928cc15..fe6358b32f1 100644
--- a/lib/private/Files/Mount/MountPoint.php
+++ b/lib/private/Files/Mount/MountPoint.php
@@ -272,7 +272,7 @@ class MountPoint implements IMountPoint {
* @return mixed
*/
public function getOption($name, $default) {
- return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
+ return $this->mountOptions[$name] ?? $default;
}
/**
diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php
index 77912adfd34..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);
}
/**
@@ -122,7 +122,7 @@ class ObjectHomeMountProvider implements IHomeMountProvider {
$config['arguments']['bucket'] = '';
}
$mapper = new \OC\Files\ObjectStore\Mapper($user, $this->config);
- $numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64;
+ $numBuckets = $config['arguments']['num_buckets'] ?? 64;
$config['arguments']['bucket'] .= $mapper->getBucket($numBuckets);
$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $config['arguments']['bucket']);
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/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php
index d495d6f4c57..393b3bbeb06 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
@@ -26,10 +27,13 @@ declare(strict_types=1);
namespace OC\Files\Node;
+use OC\Files\Filesystem;
use OC\Files\Utils\PathHelper;
use OCP\Files\Folder;
use OCP\Constants;
+use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
+use OCP\Files\NotPermittedException;
/**
* Class LazyFolder
@@ -41,23 +45,33 @@ use OCP\Files\Mount\IMountPoint;
*/
class LazyFolder implements Folder {
/** @var \Closure(): Folder */
- private $folderClosure;
-
- /** @var LazyFolder | null */
- protected $folder = null;
-
+ private \Closure $folderClosure;
+ protected ?Folder $folder = null;
+ protected IRootFolder $rootFolder;
protected array $data;
/**
- * LazyFolder constructor.
- *
+ * @param IRootFolder $rootFolder
* @param \Closure(): Folder $folderClosure
+ * @param array $data
*/
- public function __construct(\Closure $folderClosure, array $data = []) {
+ public function __construct(IRootFolder $rootFolder, \Closure $folderClosure, array $data = []) {
+ $this->rootFolder = $rootFolder;
$this->folderClosure = $folderClosure;
$this->data = $data;
}
+ protected function getRootFolder(): IRootFolder {
+ return $this->rootFolder;
+ }
+
+ protected function getRealFolder(): Folder {
+ if ($this->folder === null) {
+ $this->folder = call_user_func($this->folderClosure);
+ }
+ return $this->folder;
+ }
+
/**
* Magic method to first get the real rootFolder and then
* call $method with $args on it
@@ -67,11 +81,7 @@ class LazyFolder implements Folder {
* @return mixed
*/
public function __call($method, $args) {
- if ($this->folder === null) {
- $this->folder = call_user_func($this->folderClosure);
- }
-
- return call_user_func_array([$this->folder, $method], $args);
+ return call_user_func_array([$this->getRealFolder(), $method], $args);
}
/**
@@ -148,7 +158,7 @@ class LazyFolder implements Folder {
* @inheritDoc
*/
public function get($path) {
- return $this->__call(__FUNCTION__, func_get_args());
+ return $this->getRootFolder()->get($this->getFullPath($path));
}
/**
@@ -207,6 +217,9 @@ class LazyFolder implements Folder {
* @inheritDoc
*/
public function getId() {
+ if (isset($this->data['fileid'])) {
+ return $this->data['fileid'];
+ }
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -221,6 +234,9 @@ class LazyFolder implements Folder {
* @inheritDoc
*/
public function getMTime() {
+ if (isset($this->data['mtime'])) {
+ return $this->data['mtime'];
+ }
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -228,6 +244,9 @@ class LazyFolder implements Folder {
* @inheritDoc
*/
public function getSize($includeMounts = true): int|float {
+ if (isset($this->data['size'])) {
+ return $this->data['size'];
+ }
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -235,6 +254,9 @@ class LazyFolder implements Folder {
* @inheritDoc
*/
public function getEtag() {
+ if (isset($this->data['etag'])) {
+ return $this->data['etag'];
+ }
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -299,6 +321,12 @@ class LazyFolder implements Folder {
* @inheritDoc
*/
public function getName() {
+ if (isset($this->data['path'])) {
+ return basename($this->data['path']);
+ }
+ if (isset($this->data['name'])) {
+ return $this->data['name'];
+ }
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -390,6 +418,13 @@ class LazyFolder implements Folder {
* @inheritDoc
*/
public function getFullPath($path) {
+ if (isset($this->data['path'])) {
+ $path = PathHelper::normalizePath($path);
+ if (!Filesystem::isValidPath($path)) {
+ throw new NotPermittedException('Invalid path "' . $path . '"');
+ }
+ return $this->data['path'] . $path;
+ }
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -533,4 +568,19 @@ class LazyFolder implements Folder {
public function getRelativePath($path) {
return PathHelper::getRelativePath($this->getPath(), $path);
}
+
+ public function getParentId(): int {
+ if (isset($this->data['parent'])) {
+ return $this->data['parent'];
+ }
+ 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/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php
index c01b9fdbb83..680e80cb45e 100644
--- a/lib/private/Files/Node/LazyRoot.php
+++ b/lib/private/Files/Node/LazyRoot.php
@@ -22,7 +22,10 @@
*/
namespace OC\Files\Node;
+use OCP\Files\Cache\ICacheEntry;
use OCP\Files\IRootFolder;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Node as INode;
/**
* Class LazyRoot
@@ -33,9 +36,18 @@ use OCP\Files\IRootFolder;
* @package OC\Files\Node
*/
class LazyRoot extends LazyFolder implements IRootFolder {
- /**
- * @inheritDoc
- */
+ public function __construct(\Closure $folderClosure, array $data = []) {
+ parent::__construct($this, $folderClosure, $data);
+ }
+
+ protected function getRootFolder(): IRootFolder {
+ $folder = $this->getRealFolder();
+ if (!$folder instanceof IRootFolder) {
+ throw new \Exception('Lazy root folder closure didn\'t return a root folder');
+ }
+ return $folder;
+ }
+
public function getUserFolder($userId) {
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -43,4 +55,8 @@ class LazyRoot extends LazyFolder implements IRootFolder {
public function getByIdInPath(int $id, string $path) {
return $this->__call(__FUNCTION__, func_get_args());
}
+
+ public function getNodeFromCacheEntryAndMount(ICacheEntry $cacheEntry, IMountPoint $mountPoint): INode {
+ return $this->getRootFolder()->getNodeFromCacheEntryAndMount($cacheEntry, $mountPoint);
+ }
}
diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php
index 8fbdec4b49d..503b0af8921 100644
--- a/lib/private/Files/Node/LazyUserFolder.php
+++ b/lib/private/Files/Node/LazyUserFolder.php
@@ -34,19 +34,17 @@ use OCP\IUser;
use Psr\Log\LoggerInterface;
class LazyUserFolder extends LazyFolder {
- private IRootFolder $root;
private IUser $user;
private string $path;
private IMountManager $mountManager;
public function __construct(IRootFolder $rootFolder, IUser $user, IMountManager $mountManager) {
- $this->root = $rootFolder;
$this->user = $user;
$this->mountManager = $mountManager;
$this->path = '/' . $user->getUID() . '/files';
- parent::__construct(function () use ($user): Folder {
+ parent::__construct($rootFolder, function () use ($user): Folder {
try {
- $node = $this->root->get($this->path);
+ $node = $this->getRootFolder()->get($this->path);
if ($node instanceof File) {
$e = new \RuntimeException();
\OCP\Server::get(LoggerInterface::class)->error('User root storage is not a folder: ' . $this->path, [
@@ -56,21 +54,22 @@ class LazyUserFolder extends LazyFolder {
}
return $node;
} catch (NotFoundException $e) {
- if (!$this->root->nodeExists('/' . $user->getUID())) {
- $this->root->newFolder('/' . $user->getUID());
+ if (!$this->getRootFolder()->nodeExists('/' . $user->getUID())) {
+ $this->getRootFolder()->newFolder('/' . $user->getUID());
}
- return $this->root->newFolder($this->path);
+ return $this->getRootFolder()->newFolder($this->path);
}
}, [
'path' => $this->path,
- 'permissions' => Constants::PERMISSION_ALL,
+ // Sharing user root folder is not allowed
+ 'permissions' => Constants::PERMISSION_ALL ^ Constants::PERMISSION_SHARE,
'type' => FileInfo::TYPE_FOLDER,
'mimetype' => FileInfo::MIMETYPE_FOLDER,
]);
}
public function get($path) {
- return $this->root->get('/' . $this->user->getUID() . '/files/' . ltrim($path, '/'));
+ return $this->getRootFolder()->get('/' . $this->user->getUID() . '/files/' . ltrim($path, '/'));
}
/**
@@ -78,7 +77,7 @@ class LazyUserFolder extends LazyFolder {
* @return \OCP\Files\Node[]
*/
public function getById($id) {
- return $this->root->getByIdInPath((int)$id, $this->getPath());
+ return $this->getRootFolder()->getByIdInPath((int)$id, $this->getPath());
}
public function getMountPoint() {
diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php
index 61ae762638f..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
@@ -59,10 +60,7 @@ class Node implements INode {
protected ?FileInfo $fileInfo;
- /**
- * @var Node|null
- */
- protected $parent;
+ protected ?INode $parent;
private bool $infoHasSubMountsIncluded;
@@ -72,7 +70,7 @@ class Node implements INode {
* @param string $path
* @param FileInfo $fileInfo
*/
- public function __construct(IRootFolder $root, $view, $path, $fileInfo = null, ?Node $parent = null, bool $infoHasSubMountsIncluded = true) {
+ public function __construct(IRootFolder $root, $view, $path, $fileInfo = null, ?INode $parent = null, bool $infoHasSubMountsIncluded = true) {
if (Filesystem::normalizePath($view->getRoot()) !== '/') {
throw new PreConditionNotMetException('The view passed to the node should not have any fake root set');
}
@@ -134,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);
}
}
@@ -300,7 +305,25 @@ class Node implements INode {
return $this->root;
}
- $this->parent = $this->root->get($newPath);
+ // Manually fetch the parent if the current node doesn't have a file info yet
+ try {
+ $fileInfo = $this->getFileInfo();
+ } catch (NotFoundException) {
+ $this->parent = $this->root->get($newPath);
+ /** @var \OCP\Files\Folder $this->parent */
+ return $this->parent;
+ }
+
+ // gather the metadata we already know about our parent
+ $parentData = [
+ 'path' => $newPath,
+ 'fileid' => $fileInfo->getParentId(),
+ ];
+
+ // and create lazy folder with it instead of always querying
+ $this->parent = new LazyFolder($this->root, function () use ($newPath) {
+ return $this->root->get($newPath);
+ }, $parentData);
}
return $this->parent;
@@ -328,13 +351,7 @@ class Node implements INode {
* @return bool
*/
public function isValidPath($path) {
- if (!$path || $path[0] !== '/') {
- $path = '/' . $path;
- }
- if (strstr($path, '/../') || strrchr($path, '/') === '/..') {
- return false;
- }
- return true;
+ return Filesystem::isValidPath($path);
}
public function isMounted() {
@@ -477,4 +494,16 @@ class Node implements INode {
public function getUploadTime(): int {
return $this->getFileInfo()->getUploadTime();
}
+
+ 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 7bd88981ff2..1195b644083 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -41,6 +41,7 @@ use OC\Files\View;
use OC\Hooks\PublicEmitter;
use OC\User\NoUserException;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\Events\Node\FilesystemTornDownEvent;
use OCP\Files\IRootFolder;
@@ -487,4 +488,29 @@ class Root extends Folder implements IRootFolder {
});
return $folders;
}
+
+ public function getNodeFromCacheEntryAndMount(ICacheEntry $cacheEntry, IMountPoint $mountPoint): INode {
+ $path = $cacheEntry->getPath();
+ $fullPath = $mountPoint->getMountPoint() . $path;
+ // todo: LazyNode?
+ $info = new FileInfo($fullPath, $mountPoint->getStorage(), $path, $cacheEntry, $mountPoint);
+ $parentPath = dirname($fullPath);
+ $parent = new LazyFolder($this, function () use ($parentPath) {
+ $parent = $this->get($parentPath);
+ if ($parent instanceof \OCP\Files\Folder) {
+ return $parent;
+ } else {
+ throw new \Exception("parent $parentPath is not a folder");
+ }
+ }, [
+ 'path' => $parentPath,
+ ]);
+ $isDir = $info->getType() === FileInfo::TYPE_FOLDER;
+ $view = new View('');
+ if ($isDir) {
+ return new Folder($this, $view, $path, $info, $parent);
+ } else {
+ return new File($this, $view, $path, $info, $parent);
+ }
+ }
}
diff --git a/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php b/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php
index 824adcc1d0e..b361249ff47 100644
--- a/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php
@@ -26,6 +26,7 @@
namespace OC\Files\ObjectStore;
use OC\User\User;
+use OCP\IUser;
class HomeObjectStoreStorage extends ObjectStoreStorage implements \OCP\Files\IHomeStorage {
/**
@@ -61,7 +62,7 @@ class HomeObjectStoreStorage extends ObjectStoreStorage implements \OCP\Files\IH
* @param string $path, optional
* @return \OC\User\User
*/
- public function getUser($path = null) {
+ public function getUser($path = null): IUser {
return $this->user;
}
}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreScanner.php b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
index f001f90fdaa..d827662ae0b 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreScanner.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
@@ -39,7 +39,7 @@ class ObjectStoreScanner extends Scanner {
return [];
}
- protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int $oldSize) {
+ protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize) {
return 0;
}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index d918bd98729..4dceee9a58b 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -559,6 +559,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
if ($exists) {
+ // Always update the unencrypted size, for encryption the Encryption wrapper will update this afterwards anyways
+ $stat['unencrypted_size'] = $stat['size'];
$this->getCache()->update($fileId, $stat);
} else {
if (!$this->validateWrites || $this->objectStore->objectExists($urn)) {
diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
index 49942b385bc..044c3cdc900 100644
--- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php
+++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
@@ -128,7 +128,7 @@ trait S3ConnectionTrait {
);
$options = [
- 'version' => isset($this->params['version']) ? $this->params['version'] : 'latest',
+ 'version' => $this->params['version'] ?? 'latest',
'credentials' => $provider,
'endpoint' => $base_url,
'region' => $this->params['region'],
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index e0d0f2ce9c7..217e1a1a2ff 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -27,6 +27,7 @@
namespace OC\Files\ObjectStore;
use Aws\S3\Exception\S3MultipartUploadException;
+use Aws\S3\MultipartCopy;
use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
use GuzzleHttp\Psr7;
@@ -189,9 +190,22 @@ trait S3ObjectTrait {
return $this->getConnection()->doesObjectExist($this->bucket, $urn, $this->getSSECParameters());
}
- public function copyObject($from, $to) {
- $this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to, 'private', [
- 'params' => $this->getSSECParameters() + $this->getSSECParameters(true)
- ]);
+ 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
+ ], array_merge([
+ "bucket" => $this->getBucket(),
+ "key" => $to,
+ "acl" => "private",
+ "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 b44ead003a8..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;
@@ -34,9 +34,15 @@ use OC\Files\Storage\Wrapper\Encoding;
use OC\Files\Storage\Wrapper\PermissionsMask;
use OC\Files\Storage\Wrapper\Quota;
use OC\Lockdown\Filesystem\NullStorage;
+use OC\Share\Share;
+use OC\Share20\ShareDisableChecker;
use OC_App;
use OC_Hook;
use OC_Util;
+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;
@@ -64,52 +70,33 @@ use Psr\Log\LoggerInterface;
class SetupManager {
private bool $rootSetup = false;
- private IEventLogger $eventLogger;
- private MountProviderCollection $mountProviderCollection;
- private IMountManager $mountManager;
- private IUserManager $userManager;
// List of users for which at least one mount is setup
private array $setupUsers = [];
// List of users for which all mounts are setup
private array $setupUsersComplete = [];
/** @var array<string, string[]> */
private array $setupUserMountProviders = [];
- private IEventDispatcher $eventDispatcher;
- private IUserMountCache $userMountCache;
- private ILockdownManager $lockdownManager;
- private IUserSession $userSession;
private ICache $cache;
- private LoggerInterface $logger;
- private IConfig $config;
private bool $listeningForProviders;
private array $fullSetupRequired = [];
private bool $setupBuiltinWrappersDone = false;
public function __construct(
- IEventLogger $eventLogger,
- MountProviderCollection $mountProviderCollection,
- IMountManager $mountManager,
- IUserManager $userManager,
- IEventDispatcher $eventDispatcher,
- IUserMountCache $userMountCache,
- ILockdownManager $lockdownManager,
- IUserSession $userSession,
+ private IEventLogger $eventLogger,
+ private MountProviderCollection $mountProviderCollection,
+ private IMountManager $mountManager,
+ private IUserManager $userManager,
+ private IEventDispatcher $eventDispatcher,
+ private IUserMountCache $userMountCache,
+ private ILockdownManager $lockdownManager,
+ private IUserSession $userSession,
ICacheFactory $cacheFactory,
- LoggerInterface $logger,
- IConfig $config
+ private LoggerInterface $logger,
+ private IConfig $config,
+ private ShareDisableChecker $shareDisableChecker,
) {
- $this->eventLogger = $eventLogger;
- $this->mountProviderCollection = $mountProviderCollection;
- $this->mountManager = $mountManager;
- $this->userManager = $userManager;
- $this->eventDispatcher = $eventDispatcher;
- $this->userMountCache = $userMountCache;
- $this->lockdownManager = $lockdownManager;
- $this->logger = $logger;
- $this->userSession = $userSession;
$this->cache = $cacheFactory->createDistributed('setupmanager::');
$this->listeningForProviders = false;
- $this->config = $config;
$this->setupListeners();
}
@@ -133,52 +120,55 @@ 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;
});
- Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if (!$mount->getOption('enable_sharing', true)) {
- return new PermissionsMask([
- 'storage' => $storage,
- 'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
- ]);
+ $reSharingEnabled = Share::isResharingAllowed();
+ $user = $this->userSession->getUser();
+ $sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
+ Filesystem::addStorageWrapper(
+ 'sharing_mask',
+ function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
+ $sharingEnabledForMount = $mount->getOption('enable_sharing', true);
+ $isShared = $mount instanceof ISharedMountPoint;
+ if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
+ return new PermissionsMask([
+ 'storage' => $storage,
+ 'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
+ ]);
+ }
+ return $storage;
}
- return $storage;
- });
+ );
// 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;
@@ -345,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/SetupManagerFactory.php b/lib/private/Files/SetupManagerFactory.php
index 1d9efbd411f..8589cbdea42 100644
--- a/lib/private/Files/SetupManagerFactory.php
+++ b/lib/private/Files/SetupManagerFactory.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace OC\Files;
+use OC\Share20\ShareDisableChecker;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IMountProviderCollection;
@@ -36,40 +37,21 @@ use OCP\Lockdown\ILockdownManager;
use Psr\Log\LoggerInterface;
class SetupManagerFactory {
- private IEventLogger $eventLogger;
- private IMountProviderCollection $mountProviderCollection;
- private IUserManager $userManager;
- private IEventDispatcher $eventDispatcher;
- private IUserMountCache $userMountCache;
- private ILockdownManager $lockdownManager;
- private IUserSession $userSession;
private ?SetupManager $setupManager;
- private ICacheFactory $cacheFactory;
- private LoggerInterface $logger;
- private IConfig $config;
public function __construct(
- IEventLogger $eventLogger,
- IMountProviderCollection $mountProviderCollection,
- IUserManager $userManager,
- IEventDispatcher $eventDispatcher,
- IUserMountCache $userMountCache,
- ILockdownManager $lockdownManager,
- IUserSession $userSession,
- ICacheFactory $cacheFactory,
- LoggerInterface $logger,
- IConfig $config
+ private IEventLogger $eventLogger,
+ private IMountProviderCollection $mountProviderCollection,
+ private IUserManager $userManager,
+ private IEventDispatcher $eventDispatcher,
+ private IUserMountCache $userMountCache,
+ private ILockdownManager $lockdownManager,
+ private IUserSession $userSession,
+ private ICacheFactory $cacheFactory,
+ private LoggerInterface $logger,
+ private IConfig $config,
+ private ShareDisableChecker $shareDisableChecker,
) {
- $this->eventLogger = $eventLogger;
- $this->mountProviderCollection = $mountProviderCollection;
- $this->userManager = $userManager;
- $this->eventDispatcher = $eventDispatcher;
- $this->userMountCache = $userMountCache;
- $this->lockdownManager = $lockdownManager;
- $this->userSession = $userSession;
- $this->cacheFactory = $cacheFactory;
- $this->logger = $logger;
- $this->config = $config;
$this->setupManager = null;
}
@@ -86,7 +68,8 @@ class SetupManagerFactory {
$this->userSession,
$this->cacheFactory,
$this->logger,
- $this->config
+ $this->config,
+ $this->shareDisableChecker,
);
}
return $this->setupManager;
diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php
index 5ab411434d0..3d5a2f098b2 100644
--- a/lib/private/Files/Storage/Common.php
+++ b/lib/private/Files/Storage/Common.php
@@ -601,7 +601,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
* @return mixed
*/
public function getMountOption($name, $default = null) {
- return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
+ return $this->mountOptions[$name] ?? $default;
}
/**
diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php
index 70f22a17034..2d2bb52635b 100644
--- a/lib/private/Files/Storage/DAV.php
+++ b/lib/private/Files/Storage/DAV.php
@@ -120,9 +120,9 @@ class DAV extends Common {
if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
$host = $params['host'];
//remove leading http[s], will be generated in createBaseUri()
- if (substr($host, 0, 8) == "https://") {
+ if (str_starts_with($host, "https://")) {
$host = substr($host, 8);
- } elseif (substr($host, 0, 7) == "http://") {
+ } elseif (str_starts_with($host, "http://")) {
$host = substr($host, 7);
}
$this->host = $host;
diff --git a/lib/private/Files/Storage/Home.php b/lib/private/Files/Storage/Home.php
index 5427bc425c2..5100b15215b 100644
--- a/lib/private/Files/Storage/Home.php
+++ b/lib/private/Files/Storage/Home.php
@@ -26,6 +26,7 @@
namespace OC\Files\Storage;
use OC\Files\Cache\HomePropagator;
+use OCP\IUser;
/**
* Specialized version of Local storage for home directory usage
@@ -94,7 +95,7 @@ class Home extends Local implements \OCP\Files\IHomeStorage {
*
* @return \OC\User\User owner of this home storage
*/
- public function getUser() {
+ public function getUser(): IUser {
return $this->user;
}
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index 02708ed4f7d..0fca853da59 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -51,6 +51,7 @@ use OCP\Files\ForbiddenException;
use OCP\Files\GenericFileException;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\Storage\IStorage;
+use OCP\Files\StorageNotAvailableException;
use OCP\IConfig;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -73,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');
@@ -85,16 +88,23 @@ class Local extends \OC\Files\Storage\Common {
$realPath = realpath($this->datadir) ?: $this->datadir;
$this->realDataDir = rtrim($realPath, '/') . '/';
}
- if (substr($this->datadir, -1) !== '/') {
+ if (!str_ends_with($this->datadir, '/')) {
$this->datadir .= '/';
}
$this->dataDirLength = strlen($this->realDataDir);
$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);
+
+ if (isset($arguments['isExternal']) && $arguments['isExternal'] && !$this->stat('')) {
+ // data dir not accessible or available, can happen when using an external storage of type Local
+ // on an unmounted system mount point
+ throw new StorageNotAvailableException('Local storage path does not exist "' . $this->getSourcePath('') . '"');
+ }
}
public function __destruct() {
@@ -155,13 +165,19 @@ class Local extends \OC\Files\Storage\Common {
}
public function is_dir($path) {
- if (substr($path, -1) == '/') {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
+ if (str_ends_with($path, '/')) {
$path = substr($path, 0, -1);
}
return is_dir($this->getSourcePath($path));
}
public function is_file($path) {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
return is_file($this->getSourcePath($path));
}
@@ -264,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) {
@@ -365,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;
}
@@ -381,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/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php
index d559454fcb7..7ce4338256f 100644
--- a/lib/private/Files/Storage/Wrapper/Encryption.php
+++ b/lib/private/Files/Storage/Wrapper/Encryption.php
@@ -1071,7 +1071,7 @@ class Encryption extends Wrapper {
// object store, stores the size after write and doesn't update this during scan
// manually store the unencrypted size
- if ($result && $this->getWrapperStorage()->instanceOfStorage(ObjectStoreStorage::class)) {
+ if ($result && $this->getWrapperStorage()->instanceOfStorage(ObjectStoreStorage::class) && $this->shouldEncrypt($path)) {
$this->getCache()->put($path, ['unencrypted_size' => $count]);
}
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/Storage/Wrapper/KnownMtime.php b/lib/private/Files/Storage/Wrapper/KnownMtime.php
new file mode 100644
index 00000000000..dde209c44ab
--- /dev/null
+++ b/lib/private/Files/Storage/Wrapper/KnownMtime.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace OC\Files\Storage\Wrapper;
+
+use OCP\Cache\CappedMemoryCache;
+use OCP\Files\Storage\IStorage;
+use Psr\Clock\ClockInterface;
+
+/**
+ * Wrapper that overwrites the mtime return by stat/getMetaData if the returned value
+ * is lower than when we last modified the file.
+ *
+ * This is useful because some storage servers can return an outdated mtime right after writes
+ */
+class KnownMtime extends Wrapper {
+ private CappedMemoryCache $knowMtimes;
+ private ClockInterface $clock;
+
+ public function __construct($arguments) {
+ parent::__construct($arguments);
+ $this->knowMtimes = new CappedMemoryCache();
+ $this->clock = $arguments['clock'];
+ }
+
+ public function file_put_contents($path, $data) {
+ $result = parent::file_put_contents($path, $data);
+ if ($result) {
+ $now = $this->clock->now()->getTimestamp();
+ $this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function stat($path) {
+ $stat = parent::stat($path);
+ if ($stat) {
+ $this->applyKnownMtime($path, $stat);
+ }
+ return $stat;
+ }
+
+ public function getMetaData($path) {
+ $stat = parent::getMetaData($path);
+ if ($stat) {
+ $this->applyKnownMtime($path, $stat);
+ }
+ return $stat;
+ }
+
+ private function applyKnownMtime(string $path, array &$stat) {
+ if (isset($stat['mtime'])) {
+ $knownMtime = $this->knowMtimes->get($path) ?? 0;
+ $stat['mtime'] = max($stat['mtime'], $knownMtime);
+ }
+ }
+
+ public function filemtime($path) {
+ $knownMtime = $this->knowMtimes->get($path) ?? 0;
+ return max(parent::filemtime($path), $knownMtime);
+ }
+
+ public function mkdir($path) {
+ $result = parent::mkdir($path);
+ if ($result) {
+ $this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function rmdir($path) {
+ $result = parent::rmdir($path);
+ if ($result) {
+ $this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function unlink($path) {
+ $result = parent::unlink($path);
+ if ($result) {
+ $this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function rename($source, $target) {
+ $result = parent::rename($source, $target);
+ if ($result) {
+ $this->knowMtimes->set($target, $this->clock->now()->getTimestamp());
+ $this->knowMtimes->set($source, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function copy($source, $target) {
+ $result = parent::copy($source, $target);
+ if ($result) {
+ $this->knowMtimes->set($target, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function fopen($path, $mode) {
+ $result = parent::fopen($path, $mode);
+ if ($result && $mode === 'w') {
+ $this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function touch($path, $mtime = null) {
+ $result = parent::touch($path, $mtime);
+ if ($result) {
+ $this->knowMtimes->set($path, $mtime ?? $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ $result = parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ if ($result) {
+ $this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ $result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ if ($result) {
+ $this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+
+ public function writeStream(string $path, $stream, int $size = null): int {
+ $result = parent::writeStream($path, $stream, $size);
+ if ($result) {
+ $this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
+ }
+ return $result;
+ }
+}
diff --git a/lib/private/Files/Storage/Wrapper/PermissionsMask.php b/lib/private/Files/Storage/Wrapper/PermissionsMask.php
index 0d140e0a39d..a79eaad0569 100644
--- a/lib/private/Files/Storage/Wrapper/PermissionsMask.php
+++ b/lib/private/Files/Storage/Wrapper/PermissionsMask.php
@@ -140,7 +140,7 @@ class PermissionsMask extends Wrapper {
$data = parent::getMetaData($path);
if ($data && isset($data['permissions'])) {
- $data['scan_permissions'] = isset($data['scan_permissions']) ? $data['scan_permissions'] : $data['permissions'];
+ $data['scan_permissions'] = $data['scan_permissions'] ?? $data['permissions'];
$data['permissions'] &= $this->mask;
}
return $data;
@@ -155,7 +155,7 @@ class PermissionsMask extends Wrapper {
public function getDirectoryContent($directory): \Traversable {
foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
- $data['scan_permissions'] = isset($data['scan_permissions']) ? $data['scan_permissions'] : $data['permissions'];
+ $data['scan_permissions'] = $data['scan_permissions'] ?? $data['permissions'];
$data['permissions'] &= $this->mask;
yield $data;
diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php
index bf72e9e23e8..878680caa4e 100644
--- a/lib/private/Files/Template/TemplateManager.php
+++ b/lib/private/Files/Template/TemplateManager.php
@@ -240,7 +240,8 @@ class TemplateManager implements ITemplateManager {
'mime' => $file->getMimetype(),
'size' => $file->getSize(),
'type' => $file->getType(),
- 'hasPreview' => $this->previewManager->isAvailable($file)
+ 'hasPreview' => $this->previewManager->isAvailable($file),
+ 'permissions' => $file->getPermissions(),
];
}
diff --git a/lib/private/Files/Type/Loader.php b/lib/private/Files/Type/Loader.php
index 20c298f21b3..7032e619385 100644
--- a/lib/private/Files/Type/Loader.php
+++ b/lib/private/Files/Type/Loader.php
@@ -116,8 +116,8 @@ class Loader implements IMimeTypeLoader {
* @return int inserted ID
*/
protected function store($mimetype) {
- $mimetypeId = $this->atomic(function () use ($mimetype) {
- try {
+ try {
+ $mimetypeId = $this->atomic(function () use ($mimetype) {
$insert = $this->dbConnection->getQueryBuilder();
$insert->insert('mimetypes')
->values([
@@ -125,26 +125,24 @@ class Loader implements IMimeTypeLoader {
])
->executeStatement();
return $insert->getLastInsertId();
- } catch (DbalException $e) {
- if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
- throw $e;
- }
- $qb = $this->dbConnection->getQueryBuilder();
- $qb->select('id')
- ->from('mimetypes')
- ->where($qb->expr()->eq('mimetype', $qb->createNamedParameter($mimetype)));
- $result = $qb->executeQuery();
- $id = $result->fetchOne();
- $result->closeCursor();
- if ($id !== false) {
- return (int) $id;
- }
+ }, $this->dbConnection);
+ } catch (DbalException $e) {
+ if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
+ throw $e;
+ }
+
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('id')
+ ->from('mimetypes')
+ ->where($qb->expr()->eq('mimetype', $qb->createNamedParameter($mimetype)));
+ $result = $qb->executeQuery();
+ $id = $result->fetchOne();
+ $result->closeCursor();
+ if ($id === false) {
throw new \Exception("Database threw an unique constraint on inserting a new mimetype, but couldn't return the ID for this very mimetype");
}
- }, $this->dbConnection);
- if (!$mimetypeId) {
- throw new \Exception("Failed to get mimetype id for $mimetype after trying to store it");
+ $mimetypeId = (int) $id;
}
$this->mimetypes[$mimetypeId] = $mimetype;
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 71815939310..ec0d037af06 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -56,6 +56,7 @@ use OC\User\Manager as UserManager;
use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\ConnectionLostException;
use OCP\Files\EmptyFileNameException;
use OCP\Files\FileNameTooLongException;
use OCP\Files\InvalidCharacterInPathException;
@@ -397,10 +398,11 @@ class View {
}
$handle = $this->fopen($path, 'rb');
if ($handle) {
- $chunkSize = 524288; // 512 kB chunks
+ $chunkSize = 524288; // 512 kiB chunks
while (!feof($handle)) {
echo fread($handle, $chunkSize);
flush();
+ $this->checkConnectionStatus();
}
fclose($handle);
return $this->filesize($path);
@@ -423,7 +425,7 @@ class View {
}
$handle = $this->fopen($path, 'rb');
if ($handle) {
- $chunkSize = 524288; // 512 kB chunks
+ $chunkSize = 524288; // 512 kiB chunks
$startReading = true;
if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) {
@@ -453,6 +455,7 @@ class View {
}
echo fread($handle, $len);
flush();
+ $this->checkConnectionStatus();
}
return ftell($handle) - $from;
}
@@ -462,6 +465,13 @@ class View {
return false;
}
+ private function checkConnectionStatus(): void {
+ $connectionStatus = \connection_status();
+ if ($connectionStatus !== CONNECTION_NORMAL) {
+ throw new ConnectionLostException("Connection lost. Status: $connectionStatus");
+ }
+ }
+
/**
* @param string $path
* @return mixed
@@ -1515,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..28498af4ab0
--- /dev/null
+++ b/lib/private/FilesMetadata/FilesMetadataManager.php
@@ -0,0 +1,310 @@
+<?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 Psr\Log\LoggerInterface;
+
+/**
+ * @inheritDoc
+ * @since 28.0.0
+ */
+class FilesMetadataManager implements IFilesMetadataManager {
+ public const CONFIG_KEY = 'files_metadata';
+ 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;
+ }
+ }
+
+ /**
+ * @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 {
+ // we don't want to join metadata table if never filled
+ if ($this->config->getAppValue('core', self::CONFIG_KEY, '') === '') {
+ 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);
+ }
+}
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..9c3e63e5e22
--- /dev/null
+++ b/lib/private/FilesMetadata/MetadataQuery.php
@@ -0,0 +1,166 @@
+<?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->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..85874e92d4a
--- /dev/null
+++ b/lib/private/FilesMetadata/Service/MetadataRequestService.php
@@ -0,0 +1,160 @@
+<?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;
+ }
+
+ /**
+ * 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 ef5641d8137..55792ce1dff 100644
--- a/lib/private/Group/Database.php
+++ b/lib/private/Group/Database.php
@@ -33,6 +33,7 @@ use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Group\Backend\ABackend;
use OCP\Group\Backend\IAddToGroupBackend;
+use OCP\Group\Backend\IBatchMethodsBackend;
use OCP\Group\Backend\ICountDisabledInGroup;
use OCP\Group\Backend\ICountUsersBackend;
use OCP\Group\Backend\ICreateGroupBackend;
@@ -61,12 +62,11 @@ class Database extends ABackend implements
IRemoveFromGroupBackend,
ISetDisplayNameBackend,
ISearchableGroupBackend,
+ IBatchMethodsBackend,
INamedBackend {
- /** @var string[] */
+ /** @var array<string, array{gid: string, displayname: string}> */
private $groupCache = [];
-
- /** @var IDBConnection */
- private $dbConn;
+ private ?IDBConnection $dbConn;
/**
* \OC\Group\Database constructor.
@@ -270,7 +270,7 @@ class Database extends ABackend implements
$this->fixDI();
$query = $this->dbConn->getQueryBuilder();
- $query->select('gid')
+ $query->select('gid', 'displayname')
->from('groups')
->orderBy('gid', 'ASC');
@@ -293,6 +293,10 @@ class Database extends ABackend implements
$groups = [];
while ($row = $result->fetch()) {
+ $this->groupCache[$row['gid']] = [
+ 'displayname' => $row['displayname'],
+ 'gid' => $row['gid'],
+ ];
$groups[] = $row['gid'];
}
$result->closeCursor();
@@ -332,6 +336,43 @@ class Database extends ABackend implements
}
/**
+ * {@inheritdoc}
+ */
+ public function groupsExists(array $gids): array {
+ $notFoundGids = [];
+ $existingGroups = [];
+
+ // In case the data is already locally accessible, not need to do SQL query
+ // or do a SQL query but with a smaller in clause
+ foreach ($gids as $gid) {
+ if (isset($this->groupCache[$gid])) {
+ $existingGroups[] = $gid;
+ } else {
+ $notFoundGids[] = $gid;
+ }
+ }
+
+ $qb = $this->dbConn->getQueryBuilder();
+ $qb->select('gid', 'displayname')
+ ->from('groups')
+ ->where($qb->expr()->in('gid', $qb->createParameter('ids')));
+ foreach (array_chunk($notFoundGids, 1000) as $chunk) {
+ $qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
+ $result = $qb->executeQuery();
+ while ($row = $result->fetch()) {
+ $this->groupCache[(string)$row['gid']] = [
+ 'displayname' => (string)$row['displayname'],
+ 'gid' => (string)$row['gid'],
+ ];
+ $existingGroups[] = (string)$row['gid'];
+ }
+ $result->closeCursor();
+ }
+
+ return $existingGroups;
+ }
+
+ /**
* Get a list of all users in a group
* @param string $gid
* @param string $search
@@ -488,6 +529,43 @@ class Database extends ABackend implements
return [];
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroupsDetails(array $gids): array {
+ $notFoundGids = [];
+ $details = [];
+
+ // In case the data is already locally accessible, not need to do SQL query
+ // or do a SQL query but with a smaller in clause
+ foreach ($gids as $gid) {
+ if (isset($this->groupCache[$gid])) {
+ $details[$gid] = ['displayName' => $this->groupCache[$gid]['displayname']];
+ } else {
+ $notFoundGids[] = $gid;
+ }
+ }
+
+ foreach (array_chunk($notFoundGids, 1000) as $chunk) {
+ $query = $this->dbConn->getQueryBuilder();
+ $query->select('gid', 'displayname')
+ ->from('groups')
+ ->where($query->expr()->in('gid', $query->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY)));
+
+ $result = $query->executeQuery();
+ while ($row = $result->fetch()) {
+ $details[(string)$row['gid']] = ['displayName' => (string)$row['displayname']];
+ $this->groupCache[(string)$row['gid']] = [
+ 'displayname' => (string)$row['displayname'],
+ 'gid' => (string)$row['gid'],
+ ];
+ }
+ $result->closeCursor();
+ }
+
+ return $details;
+ }
+
public function setDisplayName(string $gid, string $displayName): bool {
if (!$this->groupExists($gid)) {
return false;
diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php
index 441ee64604d..97521d54ba6 100644
--- a/lib/private/Group/Group.php
+++ b/lib/private/Group/Group.php
@@ -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 c43b5165a79..47475121ea0 100644
--- a/lib/private/Group/Manager.php
+++ b/lib/private/Group/Manager.php
@@ -21,6 +21,7 @@
* @author Vincent Petry <vincent@nextcloud.com>
* @author Vinicius Cubas Brand <vinicius@eita.org.br>
* @author voxsim "Simon Vocella"
+ * @author Carl Schwan <carl@carlschwan.eu>
*
* @license AGPL-3.0
*
@@ -41,6 +42,8 @@ namespace OC\Group;
use OC\Hooks\PublicEmitter;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Group\Backend\IBatchMethodsBackend;
+use OCP\Group\Backend\IGroupDetailsBackend;
use OCP\Group\Events\BeforeGroupCreatedEvent;
use OCP\Group\Events\GroupCreatedEvent;
use OCP\GroupInterface;
@@ -74,10 +77,10 @@ class Manager extends PublicEmitter implements IGroupManager {
private IEventDispatcher $dispatcher;
private LoggerInterface $logger;
- /** @var \OC\Group\Group[] */
+ /** @var array<string, IGroup> */
private $cachedGroups = [];
- /** @var (string[])[] */
+ /** @var array<string, list<string>> */
private $cachedUserGroups = [];
/** @var \OC\SubAdmin */
@@ -185,7 +188,7 @@ class Manager extends PublicEmitter implements IGroupManager {
if ($backend->implementsActions(Backend::GROUP_DETAILS)) {
$groupData = $backend->getGroupDetails($gid);
if (is_array($groupData) && !empty($groupData)) {
- // take the display name from the first backend that has a non-null one
+ // take the display name from the last backend that has a non-null one
if (is_null($displayName) && isset($groupData['displayName'])) {
$displayName = $groupData['displayName'];
}
@@ -198,11 +201,69 @@ class Manager extends PublicEmitter implements IGroupManager {
if (count($backends) === 0) {
return null;
}
+ /** @var GroupInterface[] $backends */
$this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName);
return $this->cachedGroups[$gid];
}
/**
+ * @brief Batch method to create group objects
+ *
+ * @param list<string> $gids List of groupIds for which we want to create a IGroup object
+ * @param array<string, string> $displayNames Array containing already know display name for a groupId
+ * @return array<string, IGroup>
+ */
+ protected function getGroupsObjects(array $gids, array $displayNames = []): array {
+ $backends = [];
+ $groups = [];
+ foreach ($gids as $gid) {
+ $backends[$gid] = [];
+ if (!isset($displayNames[$gid])) {
+ $displayNames[$gid] = null;
+ }
+ }
+ foreach ($this->backends as $backend) {
+ if ($backend instanceof IGroupDetailsBackend || $backend->implementsActions(GroupInterface::GROUP_DETAILS)) {
+ /** @var IGroupDetailsBackend $backend */
+ if ($backend instanceof IBatchMethodsBackend) {
+ $groupDatas = $backend->getGroupsDetails($gids);
+ } else {
+ $groupDatas = [];
+ foreach ($gids as $gid) {
+ $groupDatas[$gid] = $backend->getGroupDetails($gid);
+ }
+ }
+ foreach ($groupDatas as $gid => $groupData) {
+ if (!empty($groupData)) {
+ // take the display name from the last backend that has a non-null one
+ if (isset($groupData['displayName'])) {
+ $displayNames[$gid] = $groupData['displayName'];
+ }
+ $backends[$gid][] = $backend;
+ }
+ }
+ } else {
+ if ($backend instanceof IBatchMethodsBackend) {
+ $existingGroups = $backend->groupsExists($gids);
+ } else {
+ $existingGroups = array_filter($gids, fn (string $gid): bool => $backend->groupExists($gid));
+ }
+ foreach ($existingGroups as $group) {
+ $backends[$group][] = $backend;
+ }
+ }
+ }
+ foreach ($gids as $gid) {
+ if (count($backends[$gid]) === 0) {
+ continue;
+ }
+ $this->cachedGroups[$gid] = new Group($gid, $backends[$gid], $this->dispatcher, $this->userManager, $this, $displayNames[$gid]);
+ $groups[$gid] = $this->cachedGroups[$gid];
+ }
+ return $groups;
+ }
+
+ /**
* @param string $gid
* @return bool
*/
@@ -246,13 +307,9 @@ class Manager extends PublicEmitter implements IGroupManager {
$groups = [];
foreach ($this->backends as $backend) {
$groupIds = $backend->getGroups($search, $limit ?? -1, $offset ?? 0);
- foreach ($groupIds as $groupId) {
- $aGroup = $this->get($groupId);
- if ($aGroup instanceof IGroup) {
- $groups[$groupId] = $aGroup;
- } else {
- $this->logger->debug('Group "' . $groupId . '" was returned by search but not found through direct access', ['app' => 'core']);
- }
+ $newGroups = $this->getGroupsObjects($groupIds);
+ foreach ($newGroups as $groupId => $group) {
+ $groups[$groupId] = $group;
}
if (!is_null($limit) and $limit <= 0) {
return array_values($groups);
diff --git a/lib/private/Http/Client/DnsPinMiddleware.php b/lib/private/Http/Client/DnsPinMiddleware.php
index c6a58972fdd..aecccc6ce97 100644
--- a/lib/private/Http/Client/DnsPinMiddleware.php
+++ b/lib/private/Http/Client/DnsPinMiddleware.php
@@ -55,7 +55,7 @@ class DnsPinMiddleware {
$second = array_pop($labels);
$hostname = $second . '.' . $top;
- $responses = dns_get_record($hostname, DNS_SOA);
+ $responses = $this->dnsGetRecord($hostname, DNS_SOA);
if ($responses === false || count($responses) === 0) {
return null;
@@ -81,7 +81,7 @@ class DnsPinMiddleware {
continue;
}
- $dnsResponses = dns_get_record($target, $dnsType);
+ $dnsResponses = $this->dnsGetRecord($target, $dnsType);
$canHaveCnameRecord = true;
if ($dnsResponses !== false && count($dnsResponses) > 0) {
foreach ($dnsResponses as $dnsResponse) {
@@ -104,6 +104,13 @@ class DnsPinMiddleware {
return $targetIps;
}
+ /**
+ * Wrapper for dns_get_record
+ */
+ protected function dnsGetRecord(string $hostname, int $type): array|false {
+ return \dns_get_record($hostname, $type);
+ }
+
public function addDnsPinning() {
return function (callable $handler) {
return function (
@@ -128,6 +135,10 @@ class DnsPinMiddleware {
$targetIps = $this->dnsResolve(idn_to_utf8($hostName), 0);
+ if (empty($targetIps)) {
+ throw new LocalServerException('No DNS record found for ' . $hostName);
+ }
+
$curlResolves = [];
foreach ($ports as $port) {
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/L10N/Factory.php b/lib/private/L10N/Factory.php
index 778124c4c38..6de620e7ec7 100644
--- a/lib/private/L10N/Factory.php
+++ b/lib/private/L10N/Factory.php
@@ -358,7 +358,7 @@ class Factory implements IFactory {
$files = scandir($dir);
if ($files !== false) {
foreach ($files as $file) {
- if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
+ if (str_ends_with($file, '.json') && !str_starts_with($file, 'l10n')) {
$available[] = substr($file, 0, -5);
}
}
@@ -374,7 +374,7 @@ class Factory implements IFactory {
$files = scandir($themeDir);
if ($files !== false) {
foreach ($files as $file) {
- if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') {
+ if (str_ends_with($file, '.json') && !str_starts_with($file, 'l10n')) {
$available[] = substr($file, 0, -5);
}
}
@@ -490,10 +490,14 @@ class Factory implements IFactory {
[$preferred_language] = explode(';', $preference);
$preferred_language = str_replace('-', '_', $preferred_language);
+ $preferred_language_parts = explode('_', $preferred_language);
foreach ($available as $available_language) {
if ($preferred_language === strtolower($available_language)) {
return $this->respectDefaultLanguage($app, $available_language);
}
+ if ($preferred_language_parts[0].'_'.end($preferred_language_parts) === strtolower($available_language)) {
+ return $available_language;
+ }
}
// Fallback from de_De to de
diff --git a/lib/private/Lock/AbstractLockingProvider.php b/lib/private/Lock/AbstractLockingProvider.php
index 6e8289db12e..604d098fa65 100644
--- a/lib/private/Lock/AbstractLockingProvider.php
+++ b/lib/private/Lock/AbstractLockingProvider.php
@@ -33,14 +33,18 @@ use OCP\Lock\ILockingProvider;
* to release any leftover locks at the end of the request
*/
abstract class AbstractLockingProvider implements ILockingProvider {
- /** how long until we clear stray locks in seconds */
- protected int $ttl;
-
- protected $acquiredLocks = [
+ protected array $acquiredLocks = [
'shared' => [],
'exclusive' => []
];
+ /**
+ *
+ * @param int $ttl how long until we clear stray locks in seconds
+ */
+ public function __construct(protected int $ttl) {
+ }
+
/** @inheritDoc */
protected function hasAcquiredLock(string $path, int $type): bool {
if ($type === self::LOCK_SHARED) {
diff --git a/lib/private/Lock/DBLockingProvider.php b/lib/private/Lock/DBLockingProvider.php
index fb8af8ac55b..087b1287754 100644
--- a/lib/private/Lock/DBLockingProvider.php
+++ b/lib/private/Lock/DBLockingProvider.php
@@ -39,21 +39,15 @@ use OCP\Lock\LockedException;
* Locking provider that stores the locks in the database
*/
class DBLockingProvider extends AbstractLockingProvider {
- private IDBConnection $connection;
- private ITimeFactory $timeFactory;
private array $sharedLocks = [];
- private bool $cacheSharedLocks;
public function __construct(
- IDBConnection $connection,
- ITimeFactory $timeFactory,
+ private IDBConnection $connection,
+ private ITimeFactory $timeFactory,
int $ttl = 3600,
- bool $cacheSharedLocks = true
+ private bool $cacheSharedLocks = true
) {
- $this->connection = $connection;
- $this->timeFactory = $timeFactory;
- $this->ttl = $ttl;
- $this->cacheSharedLocks = $cacheSharedLocks;
+ parent::__construct($ttl);
}
/**
diff --git a/lib/private/Lock/MemcacheLockingProvider.php b/lib/private/Lock/MemcacheLockingProvider.php
index d4eebd7c302..8ad25576084 100644
--- a/lib/private/Lock/MemcacheLockingProvider.php
+++ b/lib/private/Lock/MemcacheLockingProvider.php
@@ -32,11 +32,11 @@ use OCP\IMemcacheTTL;
use OCP\Lock\LockedException;
class MemcacheLockingProvider extends AbstractLockingProvider {
- private IMemcache $memcache;
-
- public function __construct(IMemcache $memcache, int $ttl = 3600) {
- $this->memcache = $memcache;
- $this->ttl = $ttl;
+ public function __construct(
+ private IMemcache $memcache,
+ int $ttl = 3600,
+ ) {
+ parent::__construct($ttl);
}
private function setTTL(string $path): void {
diff --git a/lib/private/Log.php b/lib/private/Log.php
index d6750491d92..1784114911f 100644
--- a/lib/private/Log.php
+++ b/lib/private/Log.php
@@ -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/Log/ErrorHandler.php b/lib/private/Log/ErrorHandler.php
index c4b9631e75a..e5e04182cd0 100644
--- a/lib/private/Log/ErrorHandler.php
+++ b/lib/private/Log/ErrorHandler.php
@@ -36,10 +36,9 @@ use Psr\Log\LoggerInterface;
use Throwable;
class ErrorHandler {
- private LoggerInterface $logger;
-
- public function __construct(LoggerInterface $logger) {
- $this->logger = $logger;
+ public function __construct(
+ private LoggerInterface $logger,
+ ) {
}
/**
@@ -94,20 +93,11 @@ class ErrorHandler {
}
private static function errnoToLogLevel(int $errno): int {
- switch ($errno) {
- case E_USER_WARNING:
- return ILogger::WARN;
-
- case E_DEPRECATED:
- case E_USER_DEPRECATED:
- return ILogger::DEBUG;
-
- case E_USER_NOTICE:
- return ILogger::INFO;
-
- case E_USER_ERROR:
- default:
- return ILogger::ERROR;
- }
+ return match ($errno) {
+ E_USER_WARNING => ILogger::WARN,
+ E_DEPRECATED, E_USER_DEPRECATED => ILogger::DEBUG,
+ E_USER_NOTICE => ILogger::INFO,
+ default => ILogger::ERROR,
+ };
}
}
diff --git a/lib/private/Log/Errorlog.php b/lib/private/Log/Errorlog.php
index 72d11aa098c..aaea8234f27 100644
--- a/lib/private/Log/Errorlog.php
+++ b/lib/private/Log/Errorlog.php
@@ -32,22 +32,19 @@ use OC\SystemConfig;
use OCP\Log\IWriter;
class Errorlog extends LogDetails implements IWriter {
- /** @var string */
- protected $tag;
-
- public function __construct(SystemConfig $config, string $tag = 'nextcloud') {
+ public function __construct(
+ SystemConfig $config,
+ protected string $tag = 'nextcloud',
+ ) {
parent::__construct($config);
- $this->tag = $tag;
}
/**
* Write a message in the log
*
- * @param string $app
* @param string|array $message
- * @param int $level
*/
- public function write(string $app, $message, int $level) {
+ public function write(string $app, $message, int $level): void {
error_log('[' . $this->tag . ']['.$app.']['.$level.'] '.$this->logDetailsAsJSON($app, $message, $level));
}
}
diff --git a/lib/private/Log/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php
index b585461e8d9..8b895bcb6be 100644
--- a/lib/private/Log/ExceptionSerializer.php
+++ b/lib/private/Log/ExceptionSerializer.php
@@ -112,11 +112,9 @@ class ExceptionSerializer {
];
- /** @var SystemConfig */
- private $systemConfig;
-
- public function __construct(SystemConfig $systemConfig) {
- $this->systemConfig = $systemConfig;
+ public function __construct(
+ private SystemConfig $systemConfig,
+ ) {
}
protected array $methodsWithSensitiveParametersByClass = [
@@ -219,7 +217,7 @@ class ExceptionSerializer {
}, $trace);
}
- private function removeValuesFromArgs($args, $values) {
+ private function removeValuesFromArgs($args, $values): array {
$workArgs = [];
foreach ($args as $arg) {
if (in_array($arg, $values, true)) {
@@ -279,7 +277,7 @@ class ExceptionSerializer {
return $arg;
}
- public function serializeException(\Throwable $exception) {
+ public function serializeException(\Throwable $exception): array {
$data = [
'Exception' => get_class($exception),
'Message' => $exception->getMessage(),
diff --git a/lib/private/Log/File.php b/lib/private/Log/File.php
index a33667c9b68..328b0346985 100644
--- a/lib/private/Log/File.php
+++ b/lib/private/Log/File.php
@@ -48,14 +48,15 @@ use OCP\Log\IWriter;
*/
class File extends LogDetails implements IWriter, IFileBased {
- /** @var string */
- protected $logFile;
- /** @var int */
- protected $logFileMode;
- /** @var SystemConfig */
- private $config;
+ protected string $logFile;
- public function __construct(string $path, string $fallbackPath, SystemConfig $config) {
+ protected int $logFileMode;
+
+ public function __construct(
+ string $path,
+ string $fallbackPath,
+ private SystemConfig $config,
+ ) {
parent::__construct($config);
$this->logFile = $path;
if (!file_exists($this->logFile)) {
@@ -69,17 +70,14 @@ class File extends LogDetails implements IWriter, IFileBased {
$this->logFile = $fallbackPath;
}
}
- $this->config = $config;
$this->logFileMode = $config->getValue('logfilemode', 0640);
}
/**
* write a message in the log
- * @param string $app
* @param string|array $message
- * @param int $level
*/
- public function write(string $app, $message, int $level) {
+ public function write(string $app, $message, int $level): void {
$entry = $this->logDetailsAsJSON($app, $message, $level);
$handle = @fopen($this->logFile, 'a');
if ($this->logFileMode > 0 && is_file($this->logFile) && (fileperms($this->logFile) & 0777) != $this->logFileMode) {
@@ -102,11 +100,8 @@ class File extends LogDetails implements IWriter, IFileBased {
/**
* get entries from the log in reverse chronological order
- * @param int $limit
- * @param int $offset
- * @return array
*/
- public function getEntries(int $limit = 50, int $offset = 0):array {
+ public function getEntries(int $limit = 50, int $offset = 0): array {
$minLevel = $this->config->getValue("loglevel", ILogger::WARN);
$entries = [];
$handle = @fopen($this->logFile, 'rb');
@@ -148,9 +143,6 @@ class File extends LogDetails implements IWriter, IFileBased {
return $entries;
}
- /**
- * @return string
- */
public function getLogFilePath():string {
return $this->logFile;
}
diff --git a/lib/private/Log/LogDetails.php b/lib/private/Log/LogDetails.php
index c82904d7cea..ec88aa767fb 100644
--- a/lib/private/Log/LogDetails.php
+++ b/lib/private/Log/LogDetails.php
@@ -28,11 +28,9 @@ namespace OC\Log;
use OC\SystemConfig;
abstract class LogDetails {
- /** @var SystemConfig */
- private $config;
-
- public function __construct(SystemConfig $config) {
- $this->config = $config;
+ public function __construct(
+ private SystemConfig $config,
+ ) {
}
public function logDetails(string $app, $message, int $level): array {
diff --git a/lib/private/Log/LogFactory.php b/lib/private/Log/LogFactory.php
index a5008f5ef77..c395c31eb98 100644
--- a/lib/private/Log/LogFactory.php
+++ b/lib/private/Log/LogFactory.php
@@ -33,57 +33,37 @@ use OCP\Log\IWriter;
use Psr\Log\LoggerInterface;
class LogFactory implements ILogFactory {
- /** @var IServerContainer */
- private $c;
- /** @var SystemConfig */
- private $systemConfig;
-
- public function __construct(IServerContainer $c, SystemConfig $systemConfig) {
- $this->c = $c;
- $this->systemConfig = $systemConfig;
+ public function __construct(
+ private IServerContainer $c,
+ private SystemConfig $systemConfig,
+ ) {
}
/**
* @throws \OCP\AppFramework\QueryException
*/
public function get(string $type):IWriter {
- switch (strtolower($type)) {
- case 'errorlog':
- return new Errorlog($this->systemConfig);
- case 'syslog':
- return $this->c->resolve(Syslog::class);
- case 'systemd':
- return $this->c->resolve(Systemdlog::class);
- case 'file':
- return $this->buildLogFile();
-
- // Backwards compatibility for old and fallback for unknown log types
- case 'owncloud':
- case 'nextcloud':
- default:
- return $this->buildLogFile();
- }
+ return match (strtolower($type)) {
+ 'errorlog' => new Errorlog($this->systemConfig),
+ 'syslog' => $this->c->resolve(Syslog::class),
+ 'systemd' => $this->c->resolve(Systemdlog::class),
+ 'file' => $this->buildLogFile(),
+ default => $this->buildLogFile(),
+ };
}
- public function getCustomLogger(string $path):ILogger {
+ public function getCustomLogger(string $path): ILogger {
$log = $this->buildLogFile($path);
return new Log($log, $this->systemConfig);
}
protected function createNewLogger(string $type, string $tag, string $path): IWriter {
- switch (strtolower($type)) {
- case 'errorlog':
- return new Errorlog($this->systemConfig, $tag);
- case 'syslog':
- return new Syslog($this->systemConfig, $tag);
- case 'systemd':
- return new Systemdlog($this->systemConfig, $tag);
- case 'file':
- case 'owncloud':
- case 'nextcloud':
- default:
- return $this->buildLogFile($path);
- }
+ return match (strtolower($type)) {
+ 'errorlog' => new Errorlog($this->systemConfig, $tag),
+ 'syslog' => new Syslog($this->systemConfig, $tag),
+ 'systemd' => new Systemdlog($this->systemConfig, $tag),
+ default => $this->buildLogFile($path),
+ };
}
public function getCustomPsrLogger(string $path, string $type = 'file', string $tag = 'Nextcloud'): LoggerInterface {
@@ -93,7 +73,7 @@ class LogFactory implements ILogFactory {
);
}
- protected function buildLogFile(string $logFile = ''):File {
+ protected function buildLogFile(string $logFile = ''): File {
$defaultLogFile = $this->systemConfig->getValue('datadirectory', \OC::$SERVERROOT.'/data').'/nextcloud.log';
if ($logFile === '') {
$logFile = $this->systemConfig->getValue('logfile', $defaultLogFile);
diff --git a/lib/private/Log/PsrLoggerAdapter.php b/lib/private/Log/PsrLoggerAdapter.php
index 07a898e2528..12254bfc67f 100644
--- a/lib/private/Log/PsrLoggerAdapter.php
+++ b/lib/private/Log/PsrLoggerAdapter.php
@@ -36,14 +36,12 @@ use function array_key_exists;
use function array_merge;
final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
- /** @var Log */
- private $logger;
-
- public function __construct(Log $logger) {
- $this->logger = $logger;
+ public function __construct(
+ private Log $logger,
+ ) {
}
- public function setEventDispatcher(IEventDispatcher $eventDispatcher) {
+ public function setEventDispatcher(IEventDispatcher $eventDispatcher): void {
$this->logger->setEventDispatcher($eventDispatcher);
}
@@ -55,9 +53,6 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* System is unusable.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
public function emergency($message, array $context = []): void {
if ($this->containsThrowable($context)) {
@@ -80,11 +75,8 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* trigger the SMS alerts and wake you up.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
- public function alert($message, array $context = []) {
+ public function alert($message, array $context = []): void {
if ($this->containsThrowable($context)) {
$this->logger->logException($context['exception'], array_merge(
[
@@ -104,11 +96,8 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
- public function critical($message, array $context = []) {
+ public function critical($message, array $context = []): void {
if ($this->containsThrowable($context)) {
$this->logger->logException($context['exception'], array_merge(
[
@@ -127,11 +116,8 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* be logged and monitored.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
- public function error($message, array $context = []) {
+ public function error($message, array $context = []): void {
if ($this->containsThrowable($context)) {
$this->logger->logException($context['exception'], array_merge(
[
@@ -152,11 +138,8 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* that are not necessarily wrong.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
- public function warning($message, array $context = []) {
+ public function warning($message, array $context = []): void {
if ($this->containsThrowable($context)) {
$this->logger->logException($context['exception'], array_merge(
[
@@ -174,11 +157,8 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* Normal but significant events.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
- public function notice($message, array $context = []) {
+ public function notice($message, array $context = []): void {
if ($this->containsThrowable($context)) {
$this->logger->logException($context['exception'], array_merge(
[
@@ -198,11 +178,8 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* Example: User logs in, SQL logs.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
- public function info($message, array $context = []) {
+ public function info($message, array $context = []): void {
if ($this->containsThrowable($context)) {
$this->logger->logException($context['exception'], array_merge(
[
@@ -220,11 +197,8 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
* Detailed debug information.
*
* @param string $message
- * @param array $context
- *
- * @return void
*/
- public function debug($message, array $context = []) {
+ public function debug($message, array $context = []): void {
if ($this->containsThrowable($context)) {
$this->logger->logException($context['exception'], array_merge(
[
@@ -243,13 +217,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
*
* @param mixed $level
* @param string $message
- * @param array $context
- *
- * @return void
*
* @throws InvalidArgumentException
*/
- public function log($level, $message, array $context = []) {
+ public function log($level, $message, array $context = []): void {
if (!is_int($level) || $level < ILogger::DEBUG || $level > ILogger::FATAL) {
throw new InvalidArgumentException('Nextcloud allows only integer log levels');
}
diff --git a/lib/private/Log/Rotate.php b/lib/private/Log/Rotate.php
index dfb588837f3..4c0e258b2f9 100644
--- a/lib/private/Log/Rotate.php
+++ b/lib/private/Log/Rotate.php
@@ -35,7 +35,7 @@ use OCP\Log\RotationTrait;
class Rotate extends \OCP\BackgroundJob\Job {
use RotationTrait;
- public function run($dummy) {
+ public function run($dummy): void {
$systemConfig = \OC::$server->getSystemConfig();
$this->filePath = $systemConfig->getValue('logfile', $systemConfig->getValue('datadirectory', \OC::$SERVERROOT . '/data') . '/nextcloud.log');
diff --git a/lib/private/Log/Syslog.php b/lib/private/Log/Syslog.php
index f4ba857742f..5f220ee1eb7 100644
--- a/lib/private/Log/Syslog.php
+++ b/lib/private/Log/Syslog.php
@@ -30,7 +30,7 @@ use OCP\ILogger;
use OCP\Log\IWriter;
class Syslog extends LogDetails implements IWriter {
- protected $levels = [
+ protected array $levels = [
ILogger::DEBUG => LOG_DEBUG,
ILogger::INFO => LOG_INFO,
ILogger::WARN => LOG_WARNING,
@@ -38,7 +38,10 @@ class Syslog extends LogDetails implements IWriter {
ILogger::FATAL => LOG_CRIT,
];
- public function __construct(SystemConfig $config, ?string $tag = null) {
+ public function __construct(
+ SystemConfig $config,
+ ?string $tag = null,
+ ) {
parent::__construct($config);
if ($tag === null) {
$tag = $config->getValue('syslog_tag', 'Nextcloud');
@@ -52,11 +55,9 @@ class Syslog extends LogDetails implements IWriter {
/**
* write a message in the log
- * @param string $app
* @param string|array $message
- * @param int $level
*/
- public function write(string $app, $message, int $level) {
+ public function write(string $app, $message, int $level): void {
$syslog_level = $this->levels[$level];
syslog($syslog_level, $this->logDetailsAsJSON($app, $message, $level));
}
diff --git a/lib/private/Log/Systemdlog.php b/lib/private/Log/Systemdlog.php
index 8619cb5e4dd..e4b4ce35c12 100644
--- a/lib/private/Log/Systemdlog.php
+++ b/lib/private/Log/Systemdlog.php
@@ -46,7 +46,7 @@ use OCP\Log\IWriter;
// Syslog compatibility fields
class Systemdlog extends LogDetails implements IWriter {
- protected $levels = [
+ protected array $levels = [
ILogger::DEBUG => 7,
ILogger::INFO => 6,
ILogger::WARN => 4,
@@ -54,9 +54,12 @@ class Systemdlog extends LogDetails implements IWriter {
ILogger::FATAL => 2,
];
- protected $syslogId;
+ protected string $syslogId;
- public function __construct(SystemConfig $config, ?string $tag = null) {
+ public function __construct(
+ SystemConfig $config,
+ ?string $tag = null,
+ ) {
parent::__construct($config);
if (!function_exists('sd_journal_send')) {
throw new HintException(
@@ -71,11 +74,9 @@ class Systemdlog extends LogDetails implements IWriter {
/**
* Write a message to the log.
- * @param string $app
* @param string|array $message
- * @param int $level
*/
- public function write(string $app, $message, int $level) {
+ public function write(string $app, $message, int $level): void {
$journal_level = $this->levels[$level];
sd_journal_send('PRIORITY='.$journal_level,
'SYSLOG_IDENTIFIER='.$this->syslogId,
diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php
index 788a7c2e8c9..16d6ae32f72 100644
--- a/lib/private/Memcache/Factory.php
+++ b/lib/private/Memcache/Factory.php
@@ -31,6 +31,7 @@
*/
namespace OC\Memcache;
+use OCP\Cache\CappedMemoryCache;
use OCP\Profiler\IProfiler;
use OCP\ICache;
use OCP\ICacheFactory;
@@ -184,13 +185,8 @@ class Factory implements ICacheFactory {
return $this->distributedCacheClass !== self::NULL_CACHE;
}
- /**
- * @see \OC\Memcache\Factory::createLocal()
- * @param string $prefix
- * @return ICache
- */
- public function createLowLatency(string $prefix = ''): ICache {
- return $this->createLocal($prefix);
+ public function createInMemory(int $capacity = 512): ICache {
+ return new CappedMemoryCache($capacity);
}
/**
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..f48a62131bd 100644
--- a/lib/private/Migration/BackgroundRepair.php
+++ b/lib/private/Migration/BackgroundRepair.php
@@ -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 56f55e80331..d323b7c1e43 100644
--- a/lib/private/NavigationManager.php
+++ b/lib/private/NavigationManager.php
@@ -65,6 +65,10 @@ 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,
@@ -78,6 +82,8 @@ class NavigationManager implements INavigationManager {
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->config = $config;
+
+ $this->defaultApp = null;
}
/**
@@ -101,7 +107,13 @@ class NavigationManager implements INavigationManager {
}
$id = $entry['id'];
- $entry['unread'] = isset($this->unreadCounters[$id]) ? $this->unreadCounters[$id] : 0;
+ $entry['unread'] = $this->unreadCounters[$id] ?? 0;
+ if ($entry['type'] === 'link') {
+ // 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;
}
@@ -123,26 +135,44 @@ class NavigationManager implements INavigationManager {
});
}
- return $this->proceedNavigation($result);
+ return $this->proceedNavigation($result, $type);
}
/**
- * Sort navigation entries by order, name and set active flag
+ * Sort navigation entries default app is always sorted first, then by order, name and set active flag
*
* @param array $list
* @return array
*/
- private function proceedNavigation(array $list): array {
+ private function proceedNavigation(array $list, string $type): array {
uasort($list, function ($a, $b) {
- if (isset($a['order']) && isset($b['order'])) {
+ if (($a['default'] ?? false) xor ($b['default'] ?? false)) {
+ // Always sort the default app first
+ return ($a['default'] ?? false) ? -1 : 1;
+ } elseif (isset($a['order']) && isset($b['order'])) {
+ // Sort by order
return ($a['order'] < $b['order']) ? -1 : 1;
} elseif (isset($a['order']) || isset($b['order'])) {
+ // Sort the one that has an order property first
return isset($a['order']) ? -1 : 1;
} else {
+ // Sort by name otherwise
return ($a['name'] < $b['name']) ? -1 : 1;
}
});
+ if ($type === 'all' || $type === 'link') {
+ // There might be the case that no default app was set, in this case the first app is the default app.
+ // Otherwise the default app is already the ordered first, so setting the default prop will make no difference.
+ foreach ($list as $index => &$navEntry) {
+ if ($navEntry['type'] === 'link') {
+ $navEntry['default'] = true;
+ break;
+ }
+ }
+ unset($navEntry);
+ }
+
$activeApp = $this->getActiveEntry();
if ($activeApp !== null) {
foreach ($list as $index => &$navEntry) {
@@ -171,8 +201,8 @@ class NavigationManager implements INavigationManager {
/**
* @inheritDoc
*/
- public function setActiveEntry($id) {
- $this->activeEntry = $id;
+ public function setActiveEntry($appId) {
+ $this->activeEntry = $appId;
}
/**
@@ -200,7 +230,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([
@@ -212,6 +260,7 @@ class NavigationManager implements INavigationManager {
'icon' => $this->urlGenerator->imagePath('theming', 'accessibility-dark.svg'),
]);
}
+
if ($this->isAdmin()) {
// App management
$this->add([
@@ -280,14 +329,13 @@ class NavigationManager implements INavigationManager {
}
}
- if ($this->appManager === 'null') {
- return;
- }
-
if ($this->userSession->isLoggedIn()) {
- $apps = $this->appManager->getEnabledAppsForUser($this->userSession->getUser());
+ $user = $this->userSession->getUser();
+ $apps = $this->appManager->getEnabledAppsForUser($user);
+ $this->customAppOrder = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
} else {
$apps = $this->appManager->getInstalledApps();
+ $this->customAppOrder = [];
}
foreach ($apps as $app) {
@@ -309,16 +357,16 @@ class NavigationManager implements INavigationManager {
if (!isset($nav['route']) && $nav['type'] !== 'settings') {
continue;
}
- $role = isset($nav['@attributes']['role']) ? $nav['@attributes']['role'] : 'all';
+ $role = $nav['@attributes']['role'] ?? 'all';
if ($role === 'admin' && !$this->isAdmin()) {
continue;
}
$l = $this->l10nFac->get($app);
$id = $nav['id'] ?? $app . ($key === 0 ? '' : $key);
- $order = isset($nav['order']) ? $nav['order'] : 100;
+ $order = $nav['order'] ?? 100;
$type = $nav['type'];
$route = !empty($nav['route']) ? $this->urlGenerator->linkToRoute($nav['route']) : '';
- $icon = isset($nav['icon']) ? $nav['icon'] : 'app.svg';
+ $icon = $nav['icon'] ?? 'app.svg';
foreach ([$icon, "$app.svg"] as $i) {
try {
$icon = $this->urlGenerator->imagePath($app, $i);
@@ -331,14 +379,24 @@ class NavigationManager implements INavigationManager {
$icon = $this->urlGenerator->imagePath('core', 'default-app-icon');
}
- $this->add([
+ $this->add(array_merge([
+ // Navigation id
'id' => $id,
+ // Order where this entry should be shown
'order' => $order,
+ // Target of the navigation entry
'href' => $route,
+ // The icon used for the naviation entry
'icon' => $icon,
+ // Type of the navigation entry ('link' vs 'settings')
'type' => $type,
+ // Localized name of the navigation entry
'name' => $l->t($nav['name']),
- ]);
+ ], $type === 'link' ? [
+ // App that registered this navigation entry (not necessarly the same as the id)
+ 'app' => $app,
+ ] : []
+ ));
}
}
}
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/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php
new file mode 100644
index 00000000000..084d4f8479d
--- /dev/null
+++ b/lib/private/OCM/Model/OCMProvider.php
@@ -0,0 +1,234 @@
+<?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\OCM\Model;
+
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\OCM\Events\ResourceTypeRegisterEvent;
+use OCP\OCM\Exceptions\OCMArgumentException;
+use OCP\OCM\Exceptions\OCMProviderException;
+use OCP\OCM\IOCMProvider;
+use OCP\OCM\IOCMResource;
+
+/**
+ * @since 28.0.0
+ */
+class OCMProvider implements IOCMProvider {
+ private bool $enabled = false;
+ private string $apiVersion = '';
+ private string $endPoint = '';
+ /** @var IOCMResource[] */
+ private array $resourceTypes = [];
+
+ private bool $emittedEvent = false;
+
+ public function __construct(
+ protected IEventDispatcher $dispatcher,
+ ) {
+ }
+
+ /**
+ * @param bool $enabled
+ *
+ * @return $this
+ */
+ public function setEnabled(bool $enabled): static {
+ $this->enabled = $enabled;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabled(): bool {
+ return $this->enabled;
+ }
+
+ /**
+ * @param string $apiVersion
+ *
+ * @return $this
+ */
+ public function setApiVersion(string $apiVersion): static {
+ $this->apiVersion = $apiVersion;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getApiVersion(): string {
+ return $this->apiVersion;
+ }
+
+ /**
+ * @param string $endPoint
+ *
+ * @return $this
+ */
+ public function setEndPoint(string $endPoint): static {
+ $this->endPoint = $endPoint;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEndPoint(): string {
+ return $this->endPoint;
+ }
+
+ /**
+ * create a new resource to later add it with {@see IOCMProvider::addResourceType()}
+ * @return IOCMResource
+ */
+ public function createNewResourceType(): IOCMResource {
+ return new OCMResource();
+ }
+
+ /**
+ * @param IOCMResource $resource
+ *
+ * @return $this
+ */
+ public function addResourceType(IOCMResource $resource): static {
+ $this->resourceTypes[] = $resource;
+
+ return $this;
+ }
+
+ /**
+ * @param IOCMResource[] $resourceTypes
+ *
+ * @return $this
+ */
+ public function setResourceTypes(array $resourceTypes): static {
+ $this->resourceTypes = $resourceTypes;
+
+ return $this;
+ }
+
+ /**
+ * @return IOCMResource[]
+ */
+ public function getResourceTypes(): array {
+ if (!$this->emittedEvent) {
+ $this->emittedEvent = true;
+ $event = new ResourceTypeRegisterEvent($this);
+ $this->dispatcher->dispatchTyped($event);
+ }
+
+ return $this->resourceTypes;
+ }
+
+ /**
+ * @param string $resourceName
+ * @param string $protocol
+ *
+ * @return string
+ * @throws OCMArgumentException
+ */
+ public function extractProtocolEntry(string $resourceName, string $protocol): string {
+ foreach ($this->getResourceTypes() as $resource) {
+ if ($resource->getName() === $resourceName) {
+ $entry = $resource->getProtocols()[$protocol] ?? null;
+ if (is_null($entry)) {
+ throw new OCMArgumentException('protocol not found');
+ }
+
+ return (string)$entry;
+ }
+ }
+
+ throw new OCMArgumentException('resource not found');
+ }
+
+ /**
+ * import data from an array
+ *
+ * @param array $data
+ *
+ * @return $this
+ * @throws OCMProviderException in case a descent provider cannot be generated from data
+ * @see self::jsonSerialize()
+ */
+ public function import(array $data): static {
+ $this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false)
+ ->setApiVersion((string)($data['apiVersion'] ?? ''))
+ ->setEndPoint($data['endPoint'] ?? '');
+
+ $resources = [];
+ foreach (($data['resourceTypes'] ?? []) as $resourceData) {
+ $resource = new OCMResource();
+ $resources[] = $resource->import($resourceData);
+ }
+ $this->setResourceTypes($resources);
+
+ if (!$this->looksValid()) {
+ throw new OCMProviderException('remote provider does not look valid');
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * @return bool
+ */
+ private function looksValid(): bool {
+ return ($this->getApiVersion() !== '' && $this->getEndPoint() !== '');
+ }
+
+
+ /**
+ * @return array{
+ * enabled: bool,
+ * apiVersion: string,
+ * endPoint: string,
+ * resourceTypes: array{
+ * name: string,
+ * shareTypes: string[],
+ * protocols: array<string, string>
+ * }[]
+ * }
+ */
+ public function jsonSerialize(): array {
+ $resourceTypes = [];
+ foreach ($this->getResourceTypes() as $res) {
+ $resourceTypes[] = $res->jsonSerialize();
+ }
+
+ return [
+ 'enabled' => $this->isEnabled(),
+ 'apiVersion' => $this->getApiVersion(),
+ 'endPoint' => $this->getEndPoint(),
+ 'resourceTypes' => $resourceTypes
+ ];
+ }
+}
diff --git a/lib/private/OCM/Model/OCMResource.php b/lib/private/OCM/Model/OCMResource.php
new file mode 100644
index 00000000000..c4a91f2eabf
--- /dev/null
+++ b/lib/private/OCM/Model/OCMResource.php
@@ -0,0 +1,123 @@
+<?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\OCM\Model;
+
+use OCP\OCM\IOCMResource;
+
+/**
+ * @since 28.0.0
+ */
+class OCMResource implements IOCMResource {
+ private string $name = '';
+ /** @var string[] */
+ private array $shareTypes = [];
+ /** @var array<string, string> */
+ private array $protocols = [];
+
+ /**
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName(string $name): static {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string {
+ return $this->name;
+ }
+
+ /**
+ * @param string[] $shareTypes
+ *
+ * @return $this
+ */
+ public function setShareTypes(array $shareTypes): static {
+ $this->shareTypes = $shareTypes;
+
+ return $this;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getShareTypes(): array {
+ return $this->shareTypes;
+ }
+
+ /**
+ * @param array<string, string> $protocols
+ *
+ * @return $this
+ */
+ public function setProtocols(array $protocols): static {
+ $this->protocols = $protocols;
+
+ return $this;
+ }
+
+ /**
+ * @return array<string, string>
+ */
+ public function getProtocols(): array {
+ return $this->protocols;
+ }
+
+ /**
+ * import data from an array
+ *
+ * @param array $data
+ *
+ * @return $this
+ * @see self::jsonSerialize()
+ */
+ public function import(array $data): static {
+ return $this->setName((string)($data['name'] ?? ''))
+ ->setShareTypes($data['shareTypes'] ?? [])
+ ->setProtocols($data['protocols'] ?? []);
+ }
+
+ /**
+ * @return array{
+ * name: string,
+ * shareTypes: string[],
+ * protocols: array<string, string>
+ * }
+ */
+ public function jsonSerialize(): array {
+ return [
+ 'name' => $this->getName(),
+ 'shareTypes' => $this->getShareTypes(),
+ 'protocols' => $this->getProtocols()
+ ];
+ }
+}
diff --git a/lib/private/OCM/OCMDiscoveryService.php b/lib/private/OCM/OCMDiscoveryService.php
new file mode 100644
index 00000000000..ac9bf2a3965
--- /dev/null
+++ b/lib/private/OCM/OCMDiscoveryService.php
@@ -0,0 +1,137 @@
+<?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\OCM;
+
+use JsonException;
+use OCP\AppFramework\Http;
+use OCP\Http\Client\IClientService;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\OCM\Exceptions\OCMProviderException;
+use OCP\OCM\IOCMDiscoveryService;
+use OCP\OCM\IOCMProvider;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @since 28.0.0
+ */
+class OCMDiscoveryService implements IOCMDiscoveryService {
+ private ICache $cache;
+ private array $supportedAPIVersion =
+ [
+ '1.0-proposal1',
+ '1.0',
+ '1.1'
+ ];
+
+ public function __construct(
+ ICacheFactory $cacheFactory,
+ private IClientService $clientService,
+ private IConfig $config,
+ private IOCMProvider $provider,
+ private LoggerInterface $logger,
+ ) {
+ $this->cache = $cacheFactory->createDistributed('ocm-discovery');
+ }
+
+
+ /**
+ * @param string $remote
+ * @param bool $skipCache
+ *
+ * @return IOCMProvider
+ * @throws OCMProviderException
+ */
+ public function discover(string $remote, bool $skipCache = false): IOCMProvider {
+ $remote = rtrim($remote, '/');
+
+ if (!$skipCache) {
+ try {
+ $this->provider->import(json_decode($this->cache->get($remote) ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []);
+ if ($this->supportedAPIVersion($this->provider->getApiVersion())) {
+ return $this->provider; // if cache looks valid, we use it
+ }
+ } catch (JsonException|OCMProviderException $e) {
+ // we ignore cache on issues
+ }
+ }
+
+ $client = $this->clientService->newClient();
+ try {
+ $response = $client->get(
+ $remote . '/ocm-provider/',
+ [
+ 'timeout' => 10,
+ 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates'),
+ 'connect_timeout' => 10,
+ ]
+ );
+
+ if ($response->getStatusCode() === Http::STATUS_OK) {
+ $body = $response->getBody();
+ // update provider with data returned by the request
+ $this->provider->import(json_decode($body, true, 8, JSON_THROW_ON_ERROR) ?? []);
+ $this->cache->set($remote, $body, 60 * 60 * 24);
+ }
+ } catch (JsonException|OCMProviderException $e) {
+ throw new OCMProviderException('data returned by remote seems invalid - ' . ($body ?? ''));
+ } catch (\Exception $e) {
+ $this->logger->warning('error while discovering ocm provider', [
+ 'exception' => $e,
+ 'remote' => $remote
+ ]);
+ throw new OCMProviderException('error while requesting remote ocm provider');
+ }
+
+ if (!$this->supportedAPIVersion($this->provider->getApiVersion())) {
+ throw new OCMProviderException('API version not supported');
+ }
+
+ return $this->provider;
+ }
+
+ /**
+ * Check the version from remote is supported.
+ * The minor version of the API will be ignored:
+ * 1.0.1 is identified as 1.0
+ *
+ * @param string $version
+ *
+ * @return bool
+ */
+ private function supportedAPIVersion(string $version): bool {
+ $dot1 = strpos($version, '.');
+ $dot2 = strpos($version, '.', $dot1 + 1);
+
+ if ($dot2 > 0) {
+ $version = substr($version, 0, $dot2);
+ }
+
+ return (in_array($version, $this->supportedAPIVersion));
+ }
+}
diff --git a/lib/private/PhoneNumberUtil.php b/lib/private/PhoneNumberUtil.php
new file mode 100644
index 00000000000..a1eb2f13233
--- /dev/null
+++ b/lib/private/PhoneNumberUtil.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+/**
+ *
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.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;
+
+use libphonenumber\NumberParseException;
+use libphonenumber\PhoneNumberFormat;
+use OCP\IPhoneNumberUtil;
+
+/**
+ * @since 28.0.0
+ */
+class PhoneNumberUtil implements IPhoneNumberUtil {
+ /**
+ * {@inheritDoc}
+ */
+ public function getCountryCodeForRegion(string $regionCode): ?int {
+ $phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance();
+ $countryCode = $phoneUtil->getCountryCodeForRegion($regionCode);
+ return $countryCode === 0 ? null : $countryCode;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convertToStandardFormat(string $input, ?string $defaultRegion = null): ?string {
+ $phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance();
+ try {
+ $phoneNumber = $phoneUtil->parse($input, $defaultRegion);
+ if ($phoneUtil->isValidNumber($phoneNumber)) {
+ return $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
+ }
+ } catch (NumberParseException) {
+ }
+
+ return null;
+ }
+}
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 4e4571f0857..695d4a3357f 100644
--- a/lib/private/Preview/Generator.php
+++ b/lib/private/Preview/Generator.php
@@ -139,23 +139,6 @@ class Generator {
$previewVersion = $file->getPreviewVersion() . '-';
}
- // If imaginary is enabled, and we request a small thumbnail,
- // let's not generate the max preview for performance reasons
- if (count($specifications) === 1
- && ($specifications[0]['width'] <= 256 || $specifications[0]['height'] <= 256)
- && preg_match(Imaginary::supportedMimeTypes(), $mimeType)
- && $this->config->getSystemValueString('preview_imaginary_url', 'invalid') !== 'invalid') {
- $crop = $specifications[0]['crop'] ?? false;
- $preview = $this->getSmallImagePreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion, $crop);
-
- if ($preview->getSize() === 0) {
- $preview->delete();
- throw new NotFoundException('Cached preview size 0, invalid!');
- }
-
- return $preview;
- }
-
// Get the max preview and infer the max preview sizes from that
$maxPreview = $this->getMaxPreview($previewFolder, $previewFiles, $file, $mimeType, $previewVersion);
$maxPreviewImage = null; // only load the image when we need it
@@ -232,32 +215,13 @@ class Generator {
}
/**
- * Generate a small image straight away without generating a max preview first
- * Preview generated is 256x256
- *
- * @param ISimpleFile[] $previewFiles
- *
- * @throws NotFoundException
- */
- private function getSmallImagePreview(ISimpleFolder $previewFolder, array $previewFiles, File $file, string $mimeType, string $prefix, bool $crop): ISimpleFile {
- $width = 256;
- $height = 256;
-
- try {
- return $this->getCachedPreview($previewFiles, $width, $height, $crop, $mimeType, $prefix);
- } catch (NotFoundException $e) {
- return $this->generateProviderPreview($previewFolder, $file, $width, $height, $crop, false, $mimeType, $prefix);
- }
- }
-
- /**
* Acquire a semaphore of the specified id and concurrency, blocking if necessary.
* Return an identifier of the semaphore on success, which can be used to release it via
* {@see Generator::unguardWithSemaphore()}.
*
* @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')) {
@@ -276,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);
@@ -293,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;
}
@@ -651,6 +621,8 @@ class Generator {
return 'png';
case 'image/jpeg':
return 'jpg';
+ case 'image/webp':
+ return 'webp';
case 'image/gif':
return 'gif';
default:
diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php
index da4864b1a22..ae2752dd91c 100644
--- a/lib/private/Preview/Imaginary.php
+++ b/lib/private/Preview/Imaginary.php
@@ -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();
@@ -106,6 +109,15 @@ class Imaginary extends ProviderV2 {
$mimeType = 'jpeg';
}
+ $preview_format = $this->config->getSystemValueString('preview_format', 'jpeg');
+
+ switch ($preview_format) { // Change the format to the correct one
+ case 'webp':
+ $mimeType = 'webp';
+ break;
+ default:
+ }
+
$operations = [];
if ($convert) {
@@ -121,7 +133,16 @@ class Imaginary extends ProviderV2 {
];
}
- $quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
+ switch ($mimeType) {
+ case 'jpeg':
+ $quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
+ break;
+ case 'webp':
+ $quality = $this->config->getAppValue('preview', 'webp_quality', '80');
+ break;
+ default:
+ $quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
+ }
$operations[] = [
'operation' => ($crop ? 'smartcrop' : 'fit'),
@@ -147,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/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/EmailAction.php b/lib/private/Profile/Actions/EmailAction.php
index 8ab4939b515..a676f6e228e 100644
--- a/lib/private/Profile/Actions/EmailAction.php
+++ b/lib/private/Profile/Actions/EmailAction.php
@@ -33,26 +33,13 @@ use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class EmailAction implements ILinkAction {
- /** @var string */
- private $value;
-
- /** @var IAccountManager */
- private $accountManager;
-
- /** @var IFactory */
- private $l10nFactory;
-
- /** @var IUrlGenerator */
- private $urlGenerator;
+ private string $value = '';
public function __construct(
- IAccountManager $accountManager,
- IFactory $l10nFactory,
- IURLGenerator $urlGenerator
+ private IAccountManager $accountManager,
+ private IFactory $l10nFactory,
+ private IURLGenerator $urlGenerator,
) {
- $this->accountManager = $accountManager;
- $this->l10nFactory = $l10nFactory;
- $this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
diff --git a/lib/private/Profile/Actions/FediverseAction.php b/lib/private/Profile/Actions/FediverseAction.php
index ed3fcd80b52..f96d2c07de4 100644
--- a/lib/private/Profile/Actions/FediverseAction.php
+++ b/lib/private/Profile/Actions/FediverseAction.php
@@ -26,7 +26,7 @@ declare(strict_types=1);
namespace OC\Profile\Actions;
-use function Safe\substr;
+use function substr;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
@@ -34,19 +34,13 @@ use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class FediverseAction implements ILinkAction {
- private ?string $value = null;
- private IAccountManager $accountManager;
- private IFactory $l10nFactory;
- private IURLGenerator $urlGenerator;
+ private string $value = '';
public function __construct(
- IAccountManager $accountManager,
- IFactory $l10nFactory,
- IURLGenerator $urlGenerator
+ private IAccountManager $accountManager,
+ private IFactory $l10nFactory,
+ private IURLGenerator $urlGenerator,
) {
- $this->accountManager = $accountManager;
- $this->l10nFactory = $l10nFactory;
- $this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
diff --git a/lib/private/Profile/Actions/PhoneAction.php b/lib/private/Profile/Actions/PhoneAction.php
index 6081a04ad7e..6a4b2dd49d4 100644
--- a/lib/private/Profile/Actions/PhoneAction.php
+++ b/lib/private/Profile/Actions/PhoneAction.php
@@ -33,26 +33,13 @@ use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class PhoneAction implements ILinkAction {
- /** @var string */
- private $value;
-
- /** @var IAccountManager */
- private $accountManager;
-
- /** @var IFactory */
- private $l10nFactory;
-
- /** @var IUrlGenerator */
- private $urlGenerator;
+ private string $value = '';
public function __construct(
- IAccountManager $accountManager,
- IFactory $l10nFactory,
- IURLGenerator $urlGenerator
+ private IAccountManager $accountManager,
+ private IFactory $l10nFactory,
+ private IURLGenerator $urlGenerator,
) {
- $this->accountManager = $accountManager;
- $this->l10nFactory = $l10nFactory;
- $this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
diff --git a/lib/private/Profile/Actions/TwitterAction.php b/lib/private/Profile/Actions/TwitterAction.php
index 041da42e539..d63c2d3ee08 100644
--- a/lib/private/Profile/Actions/TwitterAction.php
+++ b/lib/private/Profile/Actions/TwitterAction.php
@@ -26,7 +26,7 @@ declare(strict_types=1);
namespace OC\Profile\Actions;
-use function Safe\substr;
+use function substr;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
@@ -34,26 +34,13 @@ use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class TwitterAction implements ILinkAction {
- /** @var string */
- private $value;
-
- /** @var IAccountManager */
- private $accountManager;
-
- /** @var IFactory */
- private $l10nFactory;
-
- /** @var IUrlGenerator */
- private $urlGenerator;
+ private string $value = '';
public function __construct(
- IAccountManager $accountManager,
- IFactory $l10nFactory,
- IURLGenerator $urlGenerator
+ private IAccountManager $accountManager,
+ private IFactory $l10nFactory,
+ private IURLGenerator $urlGenerator,
) {
- $this->accountManager = $accountManager;
- $this->l10nFactory = $l10nFactory;
- $this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
diff --git a/lib/private/Profile/Actions/WebsiteAction.php b/lib/private/Profile/Actions/WebsiteAction.php
index 6b052be57bd..22e2692c4c5 100644
--- a/lib/private/Profile/Actions/WebsiteAction.php
+++ b/lib/private/Profile/Actions/WebsiteAction.php
@@ -33,26 +33,13 @@ use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class WebsiteAction implements ILinkAction {
- /** @var string */
- private $value;
-
- /** @var IAccountManager */
- private $accountManager;
-
- /** @var IFactory */
- private $l10nFactory;
-
- /** @var IUrlGenerator */
- private $urlGenerator;
+ private string $value = '';
public function __construct(
- IAccountManager $accountManager,
- IFactory $l10nFactory,
- IURLGenerator $urlGenerator
+ private IAccountManager $accountManager,
+ private IFactory $l10nFactory,
+ private IURLGenerator $urlGenerator,
) {
- $this->accountManager = $accountManager;
- $this->l10nFactory = $l10nFactory;
- $this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php
index f20ae74768e..39c51ea0e77 100644
--- a/lib/private/Profile/ProfileManager.php
+++ b/lib/private/Profile/ProfileManager.php
@@ -26,8 +26,9 @@ declare(strict_types=1);
namespace OC\Profile;
-use function Safe\array_flip;
-use function Safe\usort;
+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;
@@ -49,39 +50,12 @@ use OCP\Cache\CappedMemoryCache;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
-class ProfileManager {
- /** @var IAccountManager */
- private $accountManager;
-
- /** @var IAppManager */
- private $appManager;
-
- /** @var IConfig */
- private $config;
-
- /** @var ProfileConfigMapper */
- private $configMapper;
-
- /** @var ContainerInterface */
- private $container;
-
- /** @var KnownUserService */
- private $knownUserService;
-
- /** @var IFactory */
- private $l10nFactory;
-
- /** @var LoggerInterface */
- private $logger;
-
- /** @var Coordinator */
- private $coordinator;
-
+class ProfileManager implements IProfileManager {
/** @var ILinkAction[] */
- private $actions = [];
+ private array $actions = [];
/** @var null|ILinkAction[] */
- private $sortedActions = null;
+ private ?array $sortedActions = null;
/** @var CappedMemoryCache<ProfileConfig> */
private CappedMemoryCache $configCache;
@@ -112,32 +86,23 @@ class ProfileManager {
];
public function __construct(
- IAccountManager $accountManager,
- IAppManager $appManager,
- IConfig $config,
- ProfileConfigMapper $configMapper,
- ContainerInterface $container,
- KnownUserService $knownUserService,
- IFactory $l10nFactory,
- LoggerInterface $logger,
- Coordinator $coordinator
+ private IAccountManager $accountManager,
+ private IAppManager $appManager,
+ private IConfig $config,
+ private ProfileConfigMapper $configMapper,
+ private ContainerInterface $container,
+ private KnownUserService $knownUserService,
+ private IFactory $l10nFactory,
+ private LoggerInterface $logger,
+ private Coordinator $coordinator,
) {
- $this->accountManager = $accountManager;
- $this->appManager = $appManager;
- $this->config = $config;
- $this->configMapper = $configMapper;
- $this->container = $container;
- $this->knownUserService = $knownUserService;
- $this->l10nFactory = $l10nFactory;
- $this->logger = $logger;
- $this->coordinator = $coordinator;
$this->configCache = new CappedMemoryCache();
}
/**
* If no user is passed as an argument return whether profile is enabled globally in `config.php`
*/
- public function isProfileEnabled(?IUser $user = null): ?bool {
+ public function isProfileEnabled(?IUser $user = null): bool {
$profileEnabledGlobally = $this->config->getSystemValueBool('profile.enabled', true);
if (empty($user) || !$profileEnabledGlobally) {
@@ -145,7 +110,7 @@ class ProfileManager {
}
$account = $this->accountManager->getAccount($user);
- return filter_var(
+ return (bool) filter_var(
$account->getProperty(IAccountManager::PROPERTY_PROFILE_ENABLED)->getValue(),
FILTER_VALIDATE_BOOLEAN,
FILTER_NULL_ON_FAILURE,
@@ -229,57 +194,54 @@ class ProfileManager {
* Return whether the profile parameter of the target user
* is visible to the visiting user
*/
- private function isParameterVisible(string $paramId, IUser $targetUser, ?IUser $visitingUser): bool {
+ public function isProfileFieldVisible(string $profileField, IUser $targetUser, ?IUser $visitingUser): bool {
try {
$account = $this->accountManager->getAccount($targetUser);
- $scope = $account->getProperty($paramId)->getScope();
+ $scope = $account->getProperty($profileField)->getScope();
} catch (PropertyDoesNotExistException $e) {
// Allow the exception as not all profile parameters are account properties
}
- $visibility = $this->getProfileConfig($targetUser, $visitingUser)[$paramId]['visibility'];
+ $visibility = $this->getProfileConfig($targetUser, $visitingUser)[$profileField]['visibility'];
// Handle profile visibility and account property scope
- switch ($visibility) {
- case ProfileConfig::VISIBILITY_HIDE:
- return false;
- case ProfileConfig::VISIBILITY_SHOW_USERS_ONLY:
- if (!empty($scope)) {
- switch ($scope) {
- case IAccountManager::SCOPE_PRIVATE:
- return $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID());
- case IAccountManager::SCOPE_LOCAL:
- case IAccountManager::SCOPE_FEDERATED:
- case IAccountManager::SCOPE_PUBLISHED:
- return $visitingUser !== null;
- default:
- return false;
- }
- }
+
+ if ($visibility === self::VISIBILITY_SHOW_USERS_ONLY) {
+ if (empty($scope)) {
return $visitingUser !== null;
- case ProfileConfig::VISIBILITY_SHOW:
- if (!empty($scope)) {
- switch ($scope) {
- case IAccountManager::SCOPE_PRIVATE:
- return $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID());
- case IAccountManager::SCOPE_LOCAL:
- case IAccountManager::SCOPE_FEDERATED:
- case IAccountManager::SCOPE_PUBLISHED:
- return true;
- default:
- return false;
- }
- }
+ }
+
+ return match ($scope) {
+ IAccountManager::SCOPE_PRIVATE => $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID()),
+ IAccountManager::SCOPE_LOCAL,
+ IAccountManager::SCOPE_FEDERATED,
+ IAccountManager::SCOPE_PUBLISHED => $visitingUser !== null,
+ default => false,
+ };
+ }
+
+ if ($visibility === self::VISIBILITY_SHOW) {
+ if (empty($scope)) {
return true;
- default:
- return false;
+ }
+
+ return match ($scope) {
+ IAccountManager::SCOPE_PRIVATE => $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID()),
+ IAccountManager::SCOPE_LOCAL,
+ IAccountManager::SCOPE_FEDERATED,
+ IAccountManager::SCOPE_PUBLISHED => true,
+ default => false,
+ };
}
+
+ return false;
}
/**
* Return the profile parameters of the target user that are visible to the visiting user
* in an associative array
+ * @return array{userId: string, address?: string|null, biography?: string|null, displayname?: string|null, headline?: string|null, isUserAvatarVisible?: bool, organisation?: string|null, role?: string|null, actions: list<array{id: string, icon: string, title: string, target: ?string}>}
*/
- public function getProfileParams(IUser $targetUser, ?IUser $visitingUser): array {
+ public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array {
$account = $this->accountManager->getAccount($targetUser);
// Initialize associative array of profile parameters
@@ -297,14 +259,14 @@ class ProfileManager {
case IAccountManager::PROPERTY_ORGANISATION:
case IAccountManager::PROPERTY_ROLE:
$profileParameters[$property] =
- $this->isParameterVisible($property, $targetUser, $visitingUser)
+ $this->isProfileFieldVisible($property, $targetUser, $visitingUser)
// Explicitly set to null when value is empty string
? ($account->getProperty($property)->getValue() ?: null)
: null;
break;
case IAccountManager::PROPERTY_AVATAR:
// Add avatar visibility
- $profileParameters['isUserAvatarVisible'] = $this->isParameterVisible($property, $targetUser, $visitingUser);
+ $profileParameters['isUserAvatarVisible'] = $this->isProfileFieldVisible($property, $targetUser, $visitingUser);
break;
}
}
@@ -324,7 +286,7 @@ class ProfileManager {
array_filter(
$this->getActions($targetUser, $visitingUser),
function (ILinkAction $action) use ($targetUser, $visitingUser) {
- return $this->isParameterVisible($action->getId(), $targetUser, $visitingUser);
+ return $this->isProfileFieldVisible($action->getId(), $targetUser, $visitingUser);
}
),
)
@@ -356,12 +318,12 @@ class ProfileManager {
// Construct the default config for actions
$actionsConfig = [];
foreach ($this->getActions($targetUser, $visitingUser) as $action) {
- $actionsConfig[$action->getId()] = ['visibility' => ProfileConfig::DEFAULT_VISIBILITY];
+ $actionsConfig[$action->getId()] = ['visibility' => self::DEFAULT_VISIBILITY];
}
// Construct the default config for account properties
$propertiesConfig = [];
- foreach (ProfileConfig::DEFAULT_PROPERTY_VISIBILITY as $property => $visibility) {
+ foreach (self::DEFAULT_PROPERTY_VISIBILITY as $property => $visibility) {
$propertiesConfig[$property] = ['visibility' => $visibility];
}
diff --git a/lib/private/Remote/User.php b/lib/private/Remote/User.php
index 5590fcfba38..d67b279bccb 100644
--- a/lib/private/Remote/User.php
+++ b/lib/private/Remote/User.php
@@ -92,7 +92,7 @@ class User implements IUser {
* @return string
*/
public function getTwitter() {
- return isset($this->data['twitter']) ? $this->data['twitter'] : '';
+ return $this->data['twitter'] ?? '';
}
/**
diff --git a/lib/private/Repair.php b/lib/private/Repair.php
index 05624a2423a..a12e00f071c 100644
--- a/lib/private/Repair.php
+++ b/lib/private/Repair.php
@@ -46,6 +46,7 @@ use OC\DB\Connection;
use OC\DB\ConnectionAdapter;
use OC\Repair\AddBruteForceCleanupJob;
use OC\Repair\AddCleanupUpdaterBackupsJob;
+use OC\Repair\AddMetadataGenerationJob;
use OC\Repair\CleanTags;
use OC\Repair\ClearFrontendCaches;
use OC\Repair\ClearGeneratedAvatarCache;
@@ -84,7 +85,6 @@ use OC\Repair\RemoveLinkShares;
use OC\Repair\RepairDavShares;
use OC\Repair\RepairInvalidShares;
use OC\Repair\RepairMimeTypes;
-use OC\Repair\SqliteAutoincrement;
use OC\Template\JSCombiner;
use Psr\Log\LoggerInterface;
use Throwable;
@@ -212,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),
];
}
@@ -235,14 +236,11 @@ class Repair implements IOutput {
* @return IRepairStep[]
*/
public static function getBeforeUpgradeRepairSteps() {
- /** @var Connection $connection */
- $connection = \OC::$server->get(Connection::class);
/** @var ConnectionAdapter $connectionAdapter */
$connectionAdapter = \OC::$server->get(ConnectionAdapter::class);
$config = \OC::$server->getConfig();
$steps = [
new Collation(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), $connectionAdapter, true),
- new SqliteAutoincrement($connection),
new SaveAccountsTableData($connectionAdapter, $config),
new DropAccountTermsTable($connectionAdapter),
];
@@ -250,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/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php
index ee5a84ad65c..cab8d8ca02a 100644
--- a/lib/private/Repair/RepairMimeTypes.php
+++ b/lib/private/Repair/RepairMimeTypes.php
@@ -229,6 +229,13 @@ class RepairMimeTypes implements IRepairStep {
return $this->updateMimetypes($updatedMimetypes);
}
+ private function introduceEnhancedMetafileFormatType() {
+ $updatedMimetypes = [
+ 'emf' => 'image/emf',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
/**
* Fix mime types
@@ -286,5 +293,9 @@ 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');
+ }
}
}
diff --git a/lib/private/Repair/SqliteAutoincrement.php b/lib/private/Repair/SqliteAutoincrement.php
deleted file mode 100644
index 4a8b2a45d3f..00000000000
--- a/lib/private/Repair/SqliteAutoincrement.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @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\Repair;
-
-use Doctrine\DBAL\Platforms\SqlitePlatform;
-use Doctrine\DBAL\Schema\ColumnDiff;
-use Doctrine\DBAL\Schema\SchemaDiff;
-use Doctrine\DBAL\Schema\SchemaException;
-use Doctrine\DBAL\Schema\TableDiff;
-use OCP\Migration\IOutput;
-use OCP\Migration\IRepairStep;
-
-/**
- * Fixes Sqlite autoincrement by forcing the SQLite table schemas to be
- * altered in order to retrigger SQL schema generation through OCSqlitePlatform.
- */
-class SqliteAutoincrement implements IRepairStep {
- /**
- * @var \OC\DB\Connection
- */
- protected $connection;
-
- /**
- * @param \OC\DB\Connection $connection
- */
- public function __construct($connection) {
- $this->connection = $connection;
- }
-
- public function getName() {
- return 'Repair SQLite autoincrement';
- }
-
- /**
- * Fix mime types
- */
- public function run(IOutput $out) {
- if (!$this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
- return;
- }
-
- $sourceSchema = $this->connection->getSchemaManager()->createSchema();
-
- $schemaDiff = new SchemaDiff();
-
- foreach ($sourceSchema->getTables() as $tableSchema) {
- $primaryKey = $tableSchema->getPrimaryKey();
- if (!$primaryKey) {
- continue;
- }
-
- $columnNames = $primaryKey->getColumns();
-
- // add a column diff for every primary key column,
- // but do not actually change anything, this will
- // force the generation of SQL statements to alter
- // those tables, which will then trigger the
- // specific SQL code from OCSqlitePlatform
- try {
- $tableDiff = new TableDiff($tableSchema->getName());
- $tableDiff->fromTable = $tableSchema;
- foreach ($columnNames as $columnName) {
- $columnSchema = $tableSchema->getColumn($columnName);
- $columnDiff = new ColumnDiff($columnSchema->getName(), $columnSchema);
- $tableDiff->changedColumns[$columnSchema->getName()] = $columnDiff;
- $schemaDiff->changedTables[] = $tableDiff;
- }
- } catch (SchemaException $e) {
- // ignore
- }
- }
-
- $this->connection->beginTransaction();
- foreach ($schemaDiff->toSql($this->connection->getDatabasePlatform()) as $sql) {
- $this->connection->query($sql);
- }
- $this->connection->commit();
- }
-}
diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php
index fe97623176d..5ce6c7c5c8f 100644
--- a/lib/private/Route/Router.php
+++ b/lib/private/Route/Router.php
@@ -247,23 +247,23 @@ class Router implements IRouter {
*/
public function findMatchingRoute(string $url): array {
$this->eventLogger->start('route:match', 'Match route');
- if (substr($url, 0, 6) === '/apps/') {
+ if (str_starts_with($url, '/apps/')) {
// empty string / 'apps' / $app / rest of the route
[, , $app,] = explode('/', $url, 4);
$app = \OC_App::cleanAppId($app);
\OC::$REQUESTEDAPP = $app;
$this->loadRoutes($app);
- } elseif (substr($url, 0, 13) === '/ocsapp/apps/') {
+ } elseif (str_starts_with($url, '/ocsapp/apps/')) {
// empty string / 'ocsapp' / 'apps' / $app / rest of the route
[, , , $app,] = explode('/', $url, 5);
$app = \OC_App::cleanAppId($app);
\OC::$REQUESTEDAPP = $app;
$this->loadRoutes($app);
- } elseif (substr($url, 0, 10) === '/settings/') {
+ } elseif (str_starts_with($url, '/settings/')) {
$this->loadRoutes('settings');
- } elseif (substr($url, 0, 6) === '/core/') {
+ } elseif (str_starts_with($url, '/core/')) {
\OC::$REQUESTEDAPP = $url;
if (!$this->config->getSystemValueBool('maintenance') && !Util::needUpgrade()) {
\OC_App::loadApps();
@@ -277,7 +277,7 @@ class Router implements IRouter {
try {
$parameters = $matcher->match($url);
} catch (ResourceNotFoundException $e) {
- if (substr($url, -1) !== '/') {
+ if (!str_ends_with($url, '/')) {
// We allow links to apps/files? for backwards compatibility reasons
// However, since Symfony does not allow empty route names, the route
// we need to match is '/', so we need to append the '/' here.
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..8c23cc7c110
--- /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\IFilterCollection;
+use OCP\Search\IFilter;
+
+/**
+ * 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..2e96dfb7960
--- /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\Search\FilterDefinition;
+use OCP\Search\IFilter;
+use OCP\IGroupManager;
+use OCP\IUserManager;
+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..22da362bf40 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 OCP\IURLGenerator;
+use OCP\Search\FilterDefinition;
+use OCP\Search\IFilteringProvider;
+use OCP\Search\IInAppSearch;
+use OC\AppFramework\Bootstrap\Coordinator;
use OCP\IUser;
+use OCP\Search\IFilter;
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..aae2044bcd6 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\IFilterCollection;
+use OCP\Search\IFilter;
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/CleanupJob.php b/lib/private/Security/Bruteforce/CleanupJob.php
index 45cfe572acb..13628dd300d 100644
--- a/lib/private/Security/Bruteforce/CleanupJob.php
+++ b/lib/private/Security/Bruteforce/CleanupJob.php
@@ -32,19 +32,18 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class CleanupJob extends TimedJob {
- /** @var IDBConnection */
- private $connection;
-
- public function __construct(ITimeFactory $time, IDBConnection $connection) {
+ public function __construct(
+ ITimeFactory $time,
+ private IDBConnection $connection,
+ ) {
parent::__construct($time);
- $this->connection = $connection;
// Run once a day
$this->setInterval(3600 * 24);
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}
- protected function run($argument) {
+ protected function run($argument): void {
// Delete all entries more than 48 hours old
$time = $this->time->getTime() - (48 * 3600);
diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php
index 2803373e8ba..5316071f25c 100644
--- a/lib/private/Security/Bruteforce/Throttler.php
+++ b/lib/private/Security/Bruteforce/Throttler.php
@@ -106,9 +106,6 @@ class Throttler implements IThrottler {
/**
* Check if the IP is whitelisted
- *
- * @param string $ip
- * @return bool
*/
public function isBypassListed(string $ip): bool {
if (isset($this->ipIsWhitelisted[$ip])) {
diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php
index e2d115cf34e..ee525af4c2a 100644
--- a/lib/private/Security/CSP/ContentSecurityPolicy.php
+++ b/lib/private/Security/CSP/ContentSecurityPolicy.php
@@ -34,33 +34,22 @@ namespace OC\Security\CSP;
* @package OC\Security\CSP
*/
class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy {
- /**
- * @return boolean
- */
public function isInlineScriptAllowed(): bool {
return $this->inlineScriptAllowed;
}
- /**
- * @param boolean $inlineScriptAllowed
- */
- public function setInlineScriptAllowed(bool $inlineScriptAllowed) {
+ public function setInlineScriptAllowed(bool $inlineScriptAllowed): void {
$this->inlineScriptAllowed = $inlineScriptAllowed;
}
- /**
- * @return boolean
- */
public function isEvalScriptAllowed(): bool {
return $this->evalScriptAllowed;
}
/**
- * @param boolean $evalScriptAllowed
- *
* @deprecated 17.0.0 Unsafe eval should not be used anymore.
*/
- public function setEvalScriptAllowed(bool $evalScriptAllowed) {
+ public function setEvalScriptAllowed(bool $evalScriptAllowed): void {
$this->evalScriptAllowed = $evalScriptAllowed;
}
@@ -72,134 +61,79 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
$this->evalWasmAllowed = $evalWasmAllowed;
}
- /**
- * @return array
- */
public function getAllowedScriptDomains(): array {
return $this->allowedScriptDomains;
}
- /**
- * @param array $allowedScriptDomains
- */
- public function setAllowedScriptDomains(array $allowedScriptDomains) {
+ public function setAllowedScriptDomains(array $allowedScriptDomains): void {
$this->allowedScriptDomains = $allowedScriptDomains;
}
- /**
- * @return boolean
- */
public function isInlineStyleAllowed(): bool {
return $this->inlineStyleAllowed;
}
- /**
- * @param boolean $inlineStyleAllowed
- */
- public function setInlineStyleAllowed(bool $inlineStyleAllowed) {
+ public function setInlineStyleAllowed(bool $inlineStyleAllowed): void {
$this->inlineStyleAllowed = $inlineStyleAllowed;
}
- /**
- * @return array
- */
public function getAllowedStyleDomains(): array {
return $this->allowedStyleDomains;
}
- /**
- * @param array $allowedStyleDomains
- */
- public function setAllowedStyleDomains(array $allowedStyleDomains) {
+ public function setAllowedStyleDomains(array $allowedStyleDomains): void {
$this->allowedStyleDomains = $allowedStyleDomains;
}
- /**
- * @return array
- */
public function getAllowedImageDomains(): array {
return $this->allowedImageDomains;
}
- /**
- * @param array $allowedImageDomains
- */
- public function setAllowedImageDomains(array $allowedImageDomains) {
+ public function setAllowedImageDomains(array $allowedImageDomains): void {
$this->allowedImageDomains = $allowedImageDomains;
}
- /**
- * @return array
- */
public function getAllowedConnectDomains(): array {
return $this->allowedConnectDomains;
}
- /**
- * @param array $allowedConnectDomains
- */
- public function setAllowedConnectDomains(array $allowedConnectDomains) {
+ public function setAllowedConnectDomains(array $allowedConnectDomains): void {
$this->allowedConnectDomains = $allowedConnectDomains;
}
- /**
- * @return array
- */
public function getAllowedMediaDomains(): array {
return $this->allowedMediaDomains;
}
- /**
- * @param array $allowedMediaDomains
- */
- public function setAllowedMediaDomains(array $allowedMediaDomains) {
+ public function setAllowedMediaDomains(array $allowedMediaDomains): void {
$this->allowedMediaDomains = $allowedMediaDomains;
}
- /**
- * @return array
- */
public function getAllowedObjectDomains(): array {
return $this->allowedObjectDomains;
}
- /**
- * @param array $allowedObjectDomains
- */
- public function setAllowedObjectDomains(array $allowedObjectDomains) {
+ public function setAllowedObjectDomains(array $allowedObjectDomains): void {
$this->allowedObjectDomains = $allowedObjectDomains;
}
- /**
- * @return array
- */
public function getAllowedFrameDomains(): array {
return $this->allowedFrameDomains;
}
- /**
- * @param array $allowedFrameDomains
- */
- public function setAllowedFrameDomains(array $allowedFrameDomains) {
+ public function setAllowedFrameDomains(array $allowedFrameDomains): void {
$this->allowedFrameDomains = $allowedFrameDomains;
}
- /**
- * @return array
- */
public function getAllowedFontDomains(): array {
return $this->allowedFontDomains;
}
- /**
- * @param array $allowedFontDomains
- */
- public function setAllowedFontDomains($allowedFontDomains) {
+ public function setAllowedFontDomains($allowedFontDomains): void {
$this->allowedFontDomains = $allowedFontDomains;
}
/**
- * @return array
* @deprecated 15.0.0 use FrameDomains and WorkerSrcDomains
*/
public function getAllowedChildSrcDomains(): array {
@@ -210,13 +144,10 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
* @param array $allowedChildSrcDomains
* @deprecated 15.0.0 use FrameDomains and WorkerSrcDomains
*/
- public function setAllowedChildSrcDomains($allowedChildSrcDomains) {
+ public function setAllowedChildSrcDomains($allowedChildSrcDomains): void {
$this->allowedChildSrcDomains = $allowedChildSrcDomains;
}
- /**
- * @return array
- */
public function getAllowedFrameAncestors(): array {
return $this->allowedFrameAncestors;
}
@@ -224,7 +155,7 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
/**
* @param array $allowedFrameAncestors
*/
- public function setAllowedFrameAncestors($allowedFrameAncestors) {
+ public function setAllowedFrameAncestors($allowedFrameAncestors): void {
$this->allowedFrameAncestors = $allowedFrameAncestors;
}
@@ -232,7 +163,7 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
return $this->allowedWorkerSrcDomains;
}
- public function setAllowedWorkerSrcDomains(array $allowedWorkerSrcDomains) {
+ public function setAllowedWorkerSrcDomains(array $allowedWorkerSrcDomains): void {
$this->allowedWorkerSrcDomains = $allowedWorkerSrcDomains;
}
@@ -249,21 +180,23 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
return $this->reportTo;
}
- public function setReportTo(array $reportTo) {
+ public function setReportTo(array $reportTo): void {
$this->reportTo = $reportTo;
}
- /**
- * @return boolean
- */
public function isStrictDynamicAllowed(): bool {
return $this->strictDynamicAllowed;
}
- /**
- * @param boolean $strictDynamicAllowed
- */
- public function setStrictDynamicAllowed(bool $strictDynamicAllowed) {
+ 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/CSP/ContentSecurityPolicyManager.php b/lib/private/Security/CSP/ContentSecurityPolicyManager.php
index 4930dcb759c..503933ef980 100644
--- a/lib/private/Security/CSP/ContentSecurityPolicyManager.php
+++ b/lib/private/Security/CSP/ContentSecurityPolicyManager.php
@@ -35,25 +35,21 @@ use OCP\Security\IContentSecurityPolicyManager;
class ContentSecurityPolicyManager implements IContentSecurityPolicyManager {
/** @var ContentSecurityPolicy[] */
- private $policies = [];
+ private array $policies = [];
- /** @var IEventDispatcher */
- private $dispatcher;
-
- public function __construct(IEventDispatcher $dispatcher) {
- $this->dispatcher = $dispatcher;
+ public function __construct(
+ private IEventDispatcher $dispatcher,
+ ) {
}
/** {@inheritdoc} */
- public function addDefaultPolicy(EmptyContentSecurityPolicy $policy) {
+ public function addDefaultPolicy(EmptyContentSecurityPolicy $policy): void {
$this->policies[] = $policy;
}
/**
* Get the configured default policy. This is not in the public namespace
* as it is only supposed to be used by core itself.
- *
- * @return ContentSecurityPolicy
*/
public function getDefaultPolicy(): ContentSecurityPolicy {
$event = new AddContentSecurityPolicyEvent($this);
@@ -68,13 +64,11 @@ class ContentSecurityPolicyManager implements IContentSecurityPolicyManager {
/**
* Merges the first given policy with the second one
- *
- * @param ContentSecurityPolicy $defaultPolicy
- * @param EmptyContentSecurityPolicy $originalPolicy
- * @return ContentSecurityPolicy
*/
- public function mergePolicies(ContentSecurityPolicy $defaultPolicy,
- EmptyContentSecurityPolicy $originalPolicy): ContentSecurityPolicy {
+ public function mergePolicies(
+ ContentSecurityPolicy $defaultPolicy,
+ EmptyContentSecurityPolicy $originalPolicy,
+ ): ContentSecurityPolicy {
foreach ((object)(array)$originalPolicy as $name => $value) {
$setter = 'set'.ucfirst($name);
if (\is_array($value)) {
diff --git a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php
index 1167b3358d2..6573007a459 100644
--- a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php
+++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php
@@ -38,27 +38,16 @@ use OCP\IRequest;
* @package OC\Security\CSP
*/
class ContentSecurityPolicyNonceManager {
- /** @var CsrfTokenManager */
- private $csrfTokenManager;
- /** @var IRequest */
- private $request;
- /** @var string */
- private $nonce = '';
+ private string $nonce = '';
- /**
- * @param CsrfTokenManager $csrfTokenManager
- * @param IRequest $request
- */
- public function __construct(CsrfTokenManager $csrfTokenManager,
- IRequest $request) {
- $this->csrfTokenManager = $csrfTokenManager;
- $this->request = $request;
+ public function __construct(
+ private CsrfTokenManager $csrfTokenManager,
+ private IRequest $request,
+ ) {
}
/**
- * Returns the current CSP nounce
- *
- * @return string
+ * Returns the current CSP nonce
*/
public function getNonce(): string {
if ($this->nonce === '') {
@@ -74,8 +63,6 @@ class ContentSecurityPolicyNonceManager {
/**
* Check if the browser supports CSP v3
- *
- * @return bool
*/
public function browserSupportsCspV3(): bool {
$browserWhitelist = [
diff --git a/lib/private/Security/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php
index a76e169e5b9..45e628b3f3c 100644
--- a/lib/private/Security/CSRF/CsrfToken.php
+++ b/lib/private/Security/CSRF/CsrfToken.php
@@ -36,23 +36,19 @@ namespace OC\Security\CSRF;
* @package OC\Security\CSRF
*/
class CsrfToken {
- /** @var string */
- private $value;
- /** @var string */
- private $encryptedValue = '';
+ private string $encryptedValue = '';
/**
* @param string $value Value of the token. Can be encrypted or not encrypted.
*/
- public function __construct(string $value) {
- $this->value = $value;
+ public function __construct(
+ private string $value,
+ ) {
}
/**
* Encrypted value of the token. This is used to mitigate BREACH alike
* vulnerabilities. For display measures do use this functionality.
- *
- * @return string
*/
public function getEncryptedValue(): string {
if ($this->encryptedValue === '') {
@@ -66,8 +62,6 @@ class CsrfToken {
/**
* The unencrypted value of the token. Used for decrypting an already
* encrypted token.
- *
- * @return string
*/
public function getDecryptedValue(): string {
$token = explode(':', $this->value);
diff --git a/lib/private/Security/CSRF/CsrfTokenGenerator.php b/lib/private/Security/CSRF/CsrfTokenGenerator.php
index 0576fda9e06..c3d89247de1 100644
--- a/lib/private/Security/CSRF/CsrfTokenGenerator.php
+++ b/lib/private/Security/CSRF/CsrfTokenGenerator.php
@@ -34,21 +34,15 @@ use OCP\Security\ISecureRandom;
* @package OC\Security\CSRF
*/
class CsrfTokenGenerator {
- /** @var ISecureRandom */
- private $random;
-
- /**
- * @param ISecureRandom $random
- */
- public function __construct(ISecureRandom $random) {
- $this->random = $random;
+ public function __construct(
+ private ISecureRandom $random,
+ ) {
}
/**
* Generate a new CSRF token.
*
* @param int $length Length of the token in characters.
- * @return string
*/
public function generateToken(int $length = 32): string {
return $this->random->generate($length);
diff --git a/lib/private/Security/CSRF/CsrfTokenManager.php b/lib/private/Security/CSRF/CsrfTokenManager.php
index 2c6dd45866d..dceacf45e2a 100644
--- a/lib/private/Security/CSRF/CsrfTokenManager.php
+++ b/lib/private/Security/CSRF/CsrfTokenManager.php
@@ -34,27 +34,18 @@ use OC\Security\CSRF\TokenStorage\SessionStorage;
* @package OC\Security\CSRF
*/
class CsrfTokenManager {
- /** @var CsrfTokenGenerator */
- private $tokenGenerator;
- /** @var SessionStorage */
- private $sessionStorage;
- /** @var CsrfToken|null */
- private $csrfToken = null;
+ private SessionStorage $sessionStorage;
+ private ?CsrfToken $csrfToken = null;
- /**
- * @param CsrfTokenGenerator $tokenGenerator
- * @param SessionStorage $storageInterface
- */
- public function __construct(CsrfTokenGenerator $tokenGenerator,
- SessionStorage $storageInterface) {
- $this->tokenGenerator = $tokenGenerator;
+ public function __construct(
+ private CsrfTokenGenerator $tokenGenerator,
+ SessionStorage $storageInterface,
+ ) {
$this->sessionStorage = $storageInterface;
}
/**
* Returns the current CSRF token, if none set it will create a new one.
- *
- * @return CsrfToken
*/
public function getToken(): CsrfToken {
if (!\is_null($this->csrfToken)) {
@@ -74,8 +65,6 @@ class CsrfTokenManager {
/**
* Invalidates any current token and sets a new one.
- *
- * @return CsrfToken
*/
public function refreshToken(): CsrfToken {
$value = $this->tokenGenerator->generateToken();
@@ -87,16 +76,13 @@ class CsrfTokenManager {
/**
* Remove the current token from the storage.
*/
- public function removeToken() {
+ public function removeToken(): void {
$this->csrfToken = null;
$this->sessionStorage->removeToken();
}
/**
* Verifies whether the provided token is valid.
- *
- * @param CsrfToken $token
- * @return bool
*/
public function isTokenValid(CsrfToken $token): bool {
if (!$this->sessionStorage->hasToken()) {
diff --git a/lib/private/Security/CSRF/TokenStorage/SessionStorage.php b/lib/private/Security/CSRF/TokenStorage/SessionStorage.php
index ab05d5b1493..0ffe043e2f9 100644
--- a/lib/private/Security/CSRF/TokenStorage/SessionStorage.php
+++ b/lib/private/Security/CSRF/TokenStorage/SessionStorage.php
@@ -35,27 +35,18 @@ use OCP\ISession;
* @package OC\Security\CSRF\TokenStorage
*/
class SessionStorage {
- /** @var ISession */
- private $session;
-
- /**
- * @param ISession $session
- */
- public function __construct(ISession $session) {
- $this->session = $session;
+ public function __construct(
+ private ISession $session,
+ ) {
}
- /**
- * @param ISession $session
- */
- public function setSession(ISession $session) {
+ public function setSession(ISession $session): void {
$this->session = $session;
}
/**
* Returns the current token or throws an exception if none is found.
*
- * @return string
* @throws \Exception
*/
public function getToken(): string {
@@ -69,23 +60,20 @@ class SessionStorage {
/**
* Set the valid current token to $value.
- *
- * @param string $value
*/
- public function setToken(string $value) {
+ public function setToken(string $value): void {
$this->session->set('requesttoken', $value);
}
/**
* Removes the current token.
*/
- public function removeToken() {
+ public function removeToken(): void {
$this->session->remove('requesttoken');
}
+
/**
* Whether the storage has a storage.
- *
- * @return bool
*/
public function hasToken(): bool {
return $this->session->exists('requesttoken');
diff --git a/lib/private/Security/Certificate.php b/lib/private/Security/Certificate.php
index fb5b9aa8a93..759c71b2eec 100644
--- a/lib/private/Security/Certificate.php
+++ b/lib/private/Security/Certificate.php
@@ -30,25 +30,23 @@ namespace OC\Security;
use OCP\ICertificate;
class Certificate implements ICertificate {
- protected $name;
+ protected string $name;
- protected $commonName;
+ protected ?string $commonName;
- protected $organization;
+ protected ?string $organization;
- protected $serial;
- protected $issueDate;
+ protected \DateTime $issueDate;
- protected $expireDate;
+ protected \DateTime $expireDate;
- protected $issuerName;
+ protected ?string $issuerName;
- protected $issuerOrganization;
+ protected ?string $issuerOrganization;
/**
* @param string $data base64 encoded certificate
- * @param string $name
* @throws \Exception If the certificate could not get parsed
*/
public function __construct(string $data, string $name) {
@@ -66,67 +64,43 @@ class Certificate implements ICertificate {
throw new \Exception('Certificate could not get parsed.');
}
- $this->commonName = isset($info['subject']['CN']) ? $info['subject']['CN'] : null;
- $this->organization = isset($info['subject']['O']) ? $info['subject']['O'] : null;
+ $this->commonName = $info['subject']['CN'] ?? null;
+ $this->organization = $info['subject']['O'] ?? null;
$this->issueDate = new \DateTime('@' . $info['validFrom_time_t'], $gmt);
$this->expireDate = new \DateTime('@' . $info['validTo_time_t'], $gmt);
- $this->issuerName = isset($info['issuer']['CN']) ? $info['issuer']['CN'] : null;
- $this->issuerOrganization = isset($info['issuer']['O']) ? $info['issuer']['O'] : null;
+ $this->issuerName = $info['issuer']['CN'] ?? null;
+ $this->issuerOrganization = $info['issuer']['O'] ?? null;
}
- /**
- * @return string
- */
public function getName(): string {
return $this->name;
}
- /**
- * @return string|null
- */
public function getCommonName(): ?string {
return $this->commonName;
}
- /**
- * @return string|null
- */
public function getOrganization(): ?string {
return $this->organization;
}
- /**
- * @return \DateTime
- */
public function getIssueDate(): \DateTime {
return $this->issueDate;
}
- /**
- * @return \DateTime
- */
public function getExpireDate(): \DateTime {
return $this->expireDate;
}
- /**
- * @return bool
- */
public function isExpired(): bool {
$now = new \DateTime();
return $this->issueDate > $now or $now > $this->expireDate;
}
- /**
- * @return string|null
- */
public function getIssuerName(): ?string {
return $this->issuerName;
}
- /**
- * @return string|null
- */
public function getIssuerOrganization(): ?string {
return $this->issuerOrganization;
}
diff --git a/lib/private/Security/CertificateManager.php b/lib/private/Security/CertificateManager.php
index 3a87b7f1a00..cf5f0f41d56 100644
--- a/lib/private/Security/CertificateManager.php
+++ b/lib/private/Security/CertificateManager.php
@@ -44,21 +44,14 @@ use Psr\Log\LoggerInterface;
* Manage trusted certificates for users
*/
class CertificateManager implements ICertificateManager {
- protected View $view;
- protected IConfig $config;
- protected LoggerInterface $logger;
- protected ISecureRandom $random;
-
private ?string $bundlePath = null;
- public function __construct(View $view,
- IConfig $config,
- LoggerInterface $logger,
- ISecureRandom $random) {
- $this->view = $view;
- $this->config = $config;
- $this->logger = $logger;
- $this->random = $random;
+ public function __construct(
+ protected View $view,
+ protected IConfig $config,
+ protected LoggerInterface $logger,
+ protected ISecureRandom $random,
+ ) {
}
/**
@@ -178,7 +171,6 @@ class CertificateManager implements ICertificateManager {
*
* @param string $certificate the certificate data
* @param string $name the filename for the certificate
- * @return \OCP\ICertificate
* @throws \Exception If the certificate could not get added
*/
public function addCertificate(string $certificate, string $name): ICertificate {
@@ -205,9 +197,6 @@ class CertificateManager implements ICertificateManager {
/**
* Remove the certificate and re-generate the certificate bundle
- *
- * @param string $name
- * @return bool
*/
public function removeCertificate(string $name): bool {
if (!Filesystem::isValidPath($name)) {
@@ -225,8 +214,6 @@ class CertificateManager implements ICertificateManager {
/**
* Get the path to the certificate bundle
- *
- * @return string
*/
public function getCertificateBundle(): string {
return $this->getPathToCertificates() . 'rootcerts.crt';
@@ -267,8 +254,6 @@ class CertificateManager implements ICertificateManager {
/**
* Check if we need to re-bundle the certificates because one of the sources has updated
- *
- * @return bool
*/
private function needsRebundling(): bool {
$targetBundle = $this->getCertificateBundle();
@@ -282,8 +267,6 @@ class CertificateManager implements ICertificateManager {
/**
* get mtime of ca-bundle shipped by Nextcloud
- *
- * @return int
*/
protected function getFilemtimeOfCaBundle(): int {
return filemtime(\OC::$SERVERROOT . '/resources/config/ca-bundle.crt');
diff --git a/lib/private/Security/CredentialsManager.php b/lib/private/Security/CredentialsManager.php
index 0bddaeda1b0..ea59a24d646 100644
--- a/lib/private/Security/CredentialsManager.php
+++ b/lib/private/Security/CredentialsManager.php
@@ -40,26 +40,16 @@ use OCP\Security\ICrypto;
class CredentialsManager implements ICredentialsManager {
public const DB_TABLE = 'storages_credentials';
- /** @var ICrypto */
- protected $crypto;
-
- /** @var IDBConnection */
- protected $dbConnection;
-
- /**
- * @param ICrypto $crypto
- * @param IDBConnection $dbConnection
- */
- public function __construct(ICrypto $crypto, IDBConnection $dbConnection) {
- $this->crypto = $crypto;
- $this->dbConnection = $dbConnection;
+ public function __construct(
+ protected ICrypto $crypto,
+ protected IDBConnection $dbConnection,
+ ) {
}
/**
* Store a set of credentials
*
* @param string $userId empty string for system-wide credentials
- * @param string $identifier
* @param mixed $credentials
*/
public function store(string $userId, string $identifier, $credentials): void {
@@ -77,10 +67,8 @@ class CredentialsManager implements ICredentialsManager {
* Retrieve a set of credentials
*
* @param string $userId empty string for system-wide credentials
- * @param string $identifier
- * @return mixed
*/
- public function retrieve(string $userId, string $identifier) {
+ public function retrieve(string $userId, string $identifier): mixed {
$qb = $this->dbConnection->getQueryBuilder();
$qb->select('credentials')
->from(self::DB_TABLE)
@@ -108,7 +96,6 @@ class CredentialsManager implements ICredentialsManager {
* Delete a set of credentials
*
* @param string $userId empty string for system-wide credentials
- * @param string $identifier
* @return int rows removed
*/
public function delete(string $userId, string $identifier): int {
@@ -128,7 +115,6 @@ class CredentialsManager implements ICredentialsManager {
/**
* Erase all credentials stored for a user
*
- * @param string $userId
* @return int rows removed
*/
public function erase(string $userId): int {
diff --git a/lib/private/Security/Crypto.php b/lib/private/Security/Crypto.php
index 2a7905376ef..033456f3f2e 100644
--- a/lib/private/Security/Crypto.php
+++ b/lib/private/Security/Crypto.php
@@ -32,7 +32,6 @@ namespace OC\Security;
use Exception;
use OCP\IConfig;
use OCP\Security\ICrypto;
-use OCP\Security\ISecureRandom;
use phpseclib\Crypt\AES;
use phpseclib\Crypt\Hash;
@@ -47,20 +46,13 @@ use phpseclib\Crypt\Hash;
* @package OC\Security
*/
class Crypto implements ICrypto {
- /** @var AES $cipher */
- private $cipher;
- /** @var int */
- private $ivLength = 16;
- /** @var IConfig */
- private $config;
+ private AES $cipher;
+ private int $ivLength = 16;
- /**
- * @param IConfig $config
- * @param ISecureRandom $random
- */
- public function __construct(IConfig $config) {
+ public function __construct(
+ private IConfig $config,
+ ) {
$this->cipher = new AES();
- $this->config = $config;
}
/**
@@ -84,7 +76,6 @@ class Crypto implements ICrypto {
/**
* Encrypts a value and adds an HMAC (Encrypt-Then-MAC)
*
- * @param string $plaintext
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
* @return string Authenticated ciphertext
* @throws Exception if it was not possible to gather sufficient entropy
@@ -115,9 +106,7 @@ class Crypto implements ICrypto {
/**
* Decrypts a value and verifies the HMAC (Encrypt-Then-Mac)
- * @param string $authenticatedCiphertext
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
- * @return string plaintext
* @throws Exception If the HMAC does not match
* @throws Exception If the decryption failed
*/
diff --git a/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php b/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php
index 3aa93ac3da4..bb9fc41332f 100644
--- a/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php
+++ b/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php
@@ -32,13 +32,11 @@ use OCP\Security\FeaturePolicy\AddFeaturePolicyEvent;
class FeaturePolicyManager {
/** @var EmptyFeaturePolicy[] */
- private $policies = [];
+ private array $policies = [];
- /** @var IEventDispatcher */
- private $dispatcher;
-
- public function __construct(IEventDispatcher $dispatcher) {
- $this->dispatcher = $dispatcher;
+ public function __construct(
+ private IEventDispatcher $dispatcher,
+ ) {
}
public function addDefaultPolicy(EmptyFeaturePolicy $policy): void {
@@ -60,8 +58,10 @@ class FeaturePolicyManager {
* Merges the first given policy with the second one
*
*/
- public function mergePolicies(FeaturePolicy $defaultPolicy,
- EmptyFeaturePolicy $originalPolicy): FeaturePolicy {
+ public function mergePolicies(
+ FeaturePolicy $defaultPolicy,
+ EmptyFeaturePolicy $originalPolicy,
+ ): FeaturePolicy {
foreach ((object)(array)$originalPolicy as $name => $value) {
$setter = 'set' . ucfirst($name);
if (\is_array($value)) {
diff --git a/lib/private/Security/Hasher.php b/lib/private/Security/Hasher.php
index 85f69263925..23747751053 100644
--- a/lib/private/Security/Hasher.php
+++ b/lib/private/Security/Hasher.php
@@ -51,19 +51,14 @@ use OCP\Security\IHasher;
* @package OC\Security
*/
class Hasher implements IHasher {
- /** @var IConfig */
- private $config;
- /** @var array Options passed to password_hash and password_needs_rehash */
- private $options = [];
- /** @var string Salt used for legacy passwords */
- private $legacySalt = null;
-
- /**
- * @param IConfig $config
- */
- public function __construct(IConfig $config) {
- $this->config = $config;
-
+ /** Options passed to password_hash and password_needs_rehash */
+ private array $options = [];
+ /** Salt used for legacy passwords */
+ private ?string $legacySalt = null;
+
+ public function __construct(
+ private IConfig $config,
+ ) {
if (\defined('PASSWORD_ARGON2ID') || \defined('PASSWORD_ARGON2I')) {
// password_hash fails, when the minimum values are undershot.
// In this case, apply minimum.
@@ -106,7 +101,7 @@ class Hasher implements IHasher {
* @param string $prefixedHash
* @return null|array Null if the hash is not prefixed, otherwise array('version' => 1, 'hash' => 'foo')
*/
- protected function splitHash(string $prefixedHash) {
+ protected function splitHash(string $prefixedHash): ?array {
$explodedString = explode('|', $prefixedHash, 2);
if (\count($explodedString) === 2) {
if ((int)$explodedString[0] > 0) {
@@ -198,7 +193,7 @@ class Hasher implements IHasher {
return password_needs_rehash($hash, $algorithm, $this->options);
}
- private function getPrefferedAlgorithm() {
+ private function getPrefferedAlgorithm(): string {
$default = PASSWORD_BCRYPT;
if (\defined('PASSWORD_ARGON2I')) {
$default = PASSWORD_ARGON2I;
diff --git a/lib/private/Security/Normalizer/IpAddress.php b/lib/private/Security/Normalizer/IpAddress.php
index 98d85ce07a1..f8e55370da7 100644
--- a/lib/private/Security/Normalizer/IpAddress.php
+++ b/lib/private/Security/Normalizer/IpAddress.php
@@ -37,43 +37,18 @@ namespace OC\Security\Normalizer;
* @package OC\Security\Normalizer
*/
class IpAddress {
- /** @var string */
- private $ip;
-
/**
- * @param string $ip IP to normalized
+ * @param string $ip IP to normalize
*/
- public function __construct(string $ip) {
- $this->ip = $ip;
+ public function __construct(
+ private string $ip,
+ ) {
}
/**
- * Return the given subnet for an IPv4 address and mask bits
- *
- * @param string $ip
- * @param int $maskBits
- * @return string
- */
- 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
- *
- * @param string $ip
- * @param int $maskBits
- * @return string
+ * Return the given subnet for an IPv6 address (64 first 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);
}
@@ -81,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';
}
/**
@@ -103,58 +74,34 @@ 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));
}
/**
* Gets either the /32 (IPv4) or the /64 (IPv6) subnet of an IP address
- *
- * @return string
*/
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);
}
/**
* Returns the specified IP address
- *
- * @return string
*/
public function __toString(): string {
return $this->ip;
diff --git a/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php b/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php
index 08091e997ca..baf74927886 100644
--- a/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php
+++ b/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php
@@ -27,8 +27,9 @@ namespace OC\Security\RateLimiting\Exception;
use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
use OCP\AppFramework\Http;
+use OCP\Security\RateLimiting\IRateLimitExceededException;
-class RateLimitExceededException extends SecurityException {
+class RateLimitExceededException extends SecurityException implements IRateLimitExceededException {
public function __construct() {
parent::__construct('Rate limit exceeded', Http::STATUS_TOO_MANY_REQUESTS);
}
diff --git a/lib/private/Security/RateLimiting/Limiter.php b/lib/private/Security/RateLimiting/Limiter.php
index c8c0e2ce101..689e7b14558 100644
--- a/lib/private/Security/RateLimiting/Limiter.php
+++ b/lib/private/Security/RateLimiting/Limiter.php
@@ -30,8 +30,9 @@ use OC\Security\Normalizer\IpAddress;
use OC\Security\RateLimiting\Backend\IBackend;
use OC\Security\RateLimiting\Exception\RateLimitExceededException;
use OCP\IUser;
+use OCP\Security\RateLimiting\ILimiter;
-class Limiter {
+class Limiter implements ILimiter {
public function __construct(
private IBackend $backend,
) {
diff --git a/lib/private/Security/RemoteHostValidator.php b/lib/private/Security/RemoteHostValidator.php
index 38129fbd81b..385b38cff98 100644
--- a/lib/private/Security/RemoteHostValidator.php
+++ b/lib/private/Security/RemoteHostValidator.php
@@ -38,19 +38,12 @@ use function urldecode;
* @internal
*/
final class RemoteHostValidator implements IRemoteHostValidator {
- private IConfig $config;
- private HostnameClassifier $hostnameClassifier;
- private IpAddressClassifier $ipAddressClassifier;
- private LoggerInterface $logger;
-
- public function __construct(IConfig $config,
- HostnameClassifier $hostnameClassifier,
- IpAddressClassifier $ipAddressClassifier,
- LoggerInterface $logger) {
- $this->config = $config;
- $this->hostnameClassifier = $hostnameClassifier;
- $this->ipAddressClassifier = $ipAddressClassifier;
- $this->logger = $logger;
+ public function __construct(
+ private IConfig $config,
+ private HostnameClassifier $hostnameClassifier,
+ private IpAddressClassifier $ipAddressClassifier,
+ private LoggerInterface $logger,
+ ) {
}
public function isValid(string $host): bool {
diff --git a/lib/private/Security/SecureRandom.php b/lib/private/Security/SecureRandom.php
index cbd1dc8db6d..f5bc5ddfb5e 100644
--- a/lib/private/Security/SecureRandom.php
+++ b/lib/private/Security/SecureRandom.php
@@ -44,11 +44,12 @@ class SecureRandom implements ISecureRandom {
* @param int $length The length of the generated string
* @param string $characters An optional list of characters to use if no character list is
* specified all valid base64 characters are used.
- * @return string
* @throws \LengthException if an invalid length is requested
*/
- public function generate(int $length,
- string $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'): string {
+ public function generate(
+ int $length,
+ string $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+ ): string {
if ($length <= 0) {
throw new \LengthException('Invalid length specified: ' . $length . ' must be bigger than 0');
}
diff --git a/lib/private/Security/TrustedDomainHelper.php b/lib/private/Security/TrustedDomainHelper.php
index ca6a5cba073..e91f230a9c9 100644
--- a/lib/private/Security/TrustedDomainHelper.php
+++ b/lib/private/Security/TrustedDomainHelper.php
@@ -34,19 +34,13 @@ use OCP\IConfig;
use OCP\Security\ITrustedDomainHelper;
class TrustedDomainHelper implements ITrustedDomainHelper {
- /** @var IConfig */
- private $config;
-
- /**
- * @param IConfig $config
- */
- public function __construct(IConfig $config) {
- $this->config = $config;
+ public function __construct(
+ private IConfig $config,
+ ) {
}
/**
* Strips a potential port from a domain (in format domain:port)
- * @param string $host
* @return string $host without appended port
*/
private function getDomainWithoutPort(string $host): string {
diff --git a/lib/private/Server.php b/lib/private/Server.php
index e8ade23d8fe..2dd97412eba 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;
@@ -120,14 +121,14 @@ 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;
use OC\OCS\DiscoveryService;
use OC\Preview\GeneratorHelper;
use OC\Preview\IMagickSupport;
use OC\Preview\MimeIconProvider;
+use OC\Profile\ProfileManager;
use OC\Remote\Api\ApiFactory;
use OC\Remote\InstanceFactory;
use OC\RichObjectStrings\Validator;
@@ -142,11 +143,14 @@ use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OC\Security\CSRF\CsrfTokenManager;
use OC\Security\CSRF\TokenStorage\SessionStorage;
use OC\Security\Hasher;
+use OC\Security\RateLimiting\Limiter;
use OC\Security\SecureRandom;
use OC\Security\TrustedDomainHelper;
use OC\Security\VerificationToken\VerificationToken;
use OC\Session\CryptoWrapper;
+use OC\SetupCheck\SetupCheckManager;
use OC\Share20\ProviderFactory;
+use OC\Share20\ShareDisableChecker;
use OC\Share20\ShareHelper;
use OC\SpeechToText\SpeechToTextManager;
use OC\SystemTag\ManagerFactory as SystemTagManagerFactory;
@@ -154,6 +158,7 @@ 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;
@@ -190,6 +195,7 @@ 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;
@@ -209,6 +215,7 @@ use OCP\IInitialStateService;
use OCP\IL10N;
use OCP\ILogger;
use OCP\INavigationManager;
+use OCP\IPhoneNumberUtil;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IRequestId;
@@ -227,6 +234,9 @@ use OCP\Lock\ILockingProvider;
use OCP\Lockdown\ILockdownManager;
use OCP\Log\ILogFactory;
use OCP\Mail\IMailer;
+use OCP\OCM\IOCMDiscoveryService;
+use OCP\OCM\IOCMProvider;
+use OCP\Profile\IProfileManager;
use OCP\Remote\Api\IApiFactory;
use OCP\Remote\IInstanceFactory;
use OCP\RichObjectStrings\IValidator;
@@ -238,7 +248,9 @@ use OCP\Security\ICrypto;
use OCP\Security\IHasher;
use OCP\Security\ISecureRandom;
use OCP\Security\ITrustedDomainHelper;
+use OCP\Security\RateLimiting\ILimiter;
use OCP\Security\VerificationToken\IVerificationToken;
+use OCP\SetupCheck\ISetupCheckManager;
use OCP\Share\IShareHelper;
use OCP\SpeechToText\ISpeechToTextManager;
use OCP\SystemTag\ISystemTagManager;
@@ -254,6 +266,7 @@ 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;
@@ -785,8 +798,8 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerDeprecatedAlias('Search', ISearch::class);
$this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) {
- $cacheFactory = $c->get(ICacheFactory::class);
- if ($cacheFactory->isAvailable()) {
+ $config = $c->get(\OCP\IConfig::class);
+ if (ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
$backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend(
$c->get(AllConfig::class),
$this->get(ICacheFactory::class),
@@ -1122,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 */
@@ -1167,7 +1177,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->getAppDataDir('theming'),
$c->get(IURLGenerator::class),
$this->get(ICacheFactory::class),
- $this->get(ILogger::class),
+ $this->get(LoggerInterface::class),
$this->get(ITempManager::class)
);
return new ThemingDefaults(
@@ -1252,7 +1262,8 @@ class Server extends ServerContainer implements IServerContainer {
$c->get('ThemingDefaults'),
$c->get(IEventDispatcher::class),
$c->get(IUserSession::class),
- $c->get(KnownUserService::class)
+ $c->get(KnownUserService::class),
+ $c->get(ShareDisableChecker::class)
);
return $manager;
@@ -1304,6 +1315,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(IClientService::class)
);
});
+ $this->registerAlias(IOCMDiscoveryService::class, OCMDiscoveryService::class);
$this->registerService(ICloudIdManager::class, function (ContainerInterface $c) {
return new CloudIdManager(
@@ -1319,9 +1331,11 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerService(ICloudFederationProviderManager::class, function (ContainerInterface $c) {
return new CloudFederationProviderManager(
+ $c->get(\OCP\IConfig::class),
$c->get(IAppManager::class),
$c->get(IClientService::class),
$c->get(ICloudIdManager::class),
+ $c->get(IOCMDiscoveryService::class),
$c->get(LoggerInterface::class)
);
});
@@ -1385,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);
@@ -1396,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);
@@ -1412,6 +1425,20 @@ 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);
+
+ $this->registerAlias(IOCMProvider::class, OCMProvider::class);
+
+ $this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class);
+
+ $this->registerAlias(IProfileManager::class, ProfileManager::class);
+
+ $this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class);
+
$this->connectDispatcher();
}
@@ -1452,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 1eb6987fc18..ae4b80209d5 100644
--- a/lib/private/Session/CryptoSessionData.php
+++ b/lib/private/Session/CryptoSessionData.php
@@ -32,6 +32,8 @@ namespace OC\Session;
use OCP\ISession;
use OCP\Security\ICrypto;
use OCP\Session\Exceptions\SessionNotAvailableException;
+use function json_decode;
+use function OCP\Log\logger;
/**
* Class CryptoSessionData
@@ -79,14 +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
- );
- } catch (\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/Setup.php b/lib/private/Setup.php
index 0993fe54f47..4fea5d72591 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -60,6 +60,7 @@ 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,13 +539,13 @@ 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";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/ocs/v(1|2)\\.php";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/robots\\.txt";
- $content .= "\n RewriteCond %{REQUEST_FILENAME} !/(ocm-provider|ocs-provider|updater)/";
+ $content .= "\n RewriteCond %{REQUEST_FILENAME} !/(ocs-provider|updater)/";
$content .= "\n RewriteCond %{REQUEST_URI} !^/\\.well-known/(acme-challenge|pki-validation)/.*";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/richdocumentscode(_arm64)?/proxy.php$";
$content .= "\n RewriteRule . index.php [PT,E=PATH_INFO:$1]";
@@ -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 9ec4137cdef..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;
@@ -88,7 +89,7 @@ abstract class AbstractDatabase {
$dbName = $config['dbname'];
$dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost';
$dbPort = !empty($config['dbport']) ? $config['dbport'] : '';
- $dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_';
+ $dbTablePrefix = $config['dbtableprefix'] ?? 'oc_';
$createUserConfig = $this->config->getValue("setup_create_db_user", true);
// accept `false` both as bool and string, since setting config values from env will result in a string
@@ -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/SetupCheck/SetupCheckManager.php b/lib/private/SetupCheck/SetupCheckManager.php
new file mode 100644
index 00000000000..b8b6cfa11e7
--- /dev/null
+++ b/lib/private/SetupCheck/SetupCheckManager.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu>
+ *
+ * @author Carl Schwan <carl@carlschwan.eu>
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @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\SetupCheck;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OCP\Server;
+use OCP\SetupCheck\ISetupCheck;
+use OCP\SetupCheck\ISetupCheckManager;
+use Psr\Log\LoggerInterface;
+
+class SetupCheckManager implements ISetupCheckManager {
+ public function __construct(
+ private Coordinator $coordinator,
+ private LoggerInterface $logger,
+ ) {
+ }
+
+ public function runAll(): array {
+ $results = [];
+ $setupChecks = $this->coordinator->getRegistrationContext()->getSetupChecks();
+ foreach ($setupChecks as $setupCheck) {
+ /** @var ISetupCheck $setupCheckObject */
+ $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::class] = $setupResult;
+ }
+ return $results;
+ }
+}
diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php
index 5201cf074b1..55ac3eda644 100644
--- a/lib/private/Share20/DefaultShareProvider.php
+++ b/lib/private/Share20/DefaultShareProvider.php
@@ -38,12 +38,12 @@ use OC\Files\Cache\Cache;
use OC\Share20\Exception\BackendError;
use OC\Share20\Exception\InvalidShare;
use OC\Share20\Exception\ProviderException;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Defaults;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
-use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IURLGenerator;
@@ -90,19 +90,19 @@ class DefaultShareProvider implements IShareProvider {
/** @var IURLGenerator */
private $urlGenerator;
- /** @var IConfig */
- private $config;
+ private ITimeFactory $timeFactory;
public function __construct(
- IDBConnection $connection,
- IUserManager $userManager,
- IGroupManager $groupManager,
- IRootFolder $rootFolder,
- IMailer $mailer,
- Defaults $defaults,
- IFactory $l10nFactory,
- IURLGenerator $urlGenerator,
- IConfig $config) {
+ IDBConnection $connection,
+ IUserManager $userManager,
+ IGroupManager $groupManager,
+ IRootFolder $rootFolder,
+ IMailer $mailer,
+ Defaults $defaults,
+ IFactory $l10nFactory,
+ IURLGenerator $urlGenerator,
+ ITimeFactory $timeFactory,
+ ) {
$this->dbConn = $connection;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
@@ -111,7 +111,7 @@ class DefaultShareProvider implements IShareProvider {
$this->defaults = $defaults;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
- $this->config = $config;
+ $this->timeFactory = $timeFactory;
}
/**
@@ -216,32 +216,22 @@ class DefaultShareProvider implements IShareProvider {
}
// Set the time this share was created
- $qb->setValue('stime', $qb->createNamedParameter(time()));
+ $shareTime = $this->timeFactory->now();
+ $qb->setValue('stime', $qb->createNamedParameter($shareTime->getTimestamp()));
// insert the data and fetch the id of the share
- $this->dbConn->beginTransaction();
- $qb->execute();
- $id = $this->dbConn->lastInsertId('*PREFIX*share');
-
- // Now fetch the inserted share and create a complete share object
- $qb = $this->dbConn->getQueryBuilder();
- $qb->select('*')
- ->from('share')
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
+ $qb->executeStatement();
- $cursor = $qb->execute();
- $data = $cursor->fetch();
- $this->dbConn->commit();
- $cursor->closeCursor();
+ // Update mandatory data
+ $id = $qb->getLastInsertId();
+ $share->setId((string)$id);
+ $share->setProviderId($this->identifier());
- if ($data === false) {
- throw new ShareNotFound('Newly created share could not be found');
- }
+ $share->setShareTime(\DateTime::createFromImmutable($shareTime));
$mailSendValue = $share->getMailSend();
- $data['mail_send'] = ($mailSendValue === null) ? true : $mailSendValue;
+ $share->setMailSend(($mailSendValue === null) ? true : $mailSendValue);
- $share = $this->createShare($data);
return $share;
}
@@ -1374,7 +1364,7 @@ class DefaultShareProvider implements IShareProvider {
$type = (int)$row['share_type'];
if ($type === IShare::TYPE_USER) {
$uid = $row['share_with'];
- $users[$uid] = isset($users[$uid]) ? $users[$uid] : [];
+ $users[$uid] = $users[$uid] ?? [];
$users[$uid][$row['id']] = $row;
} elseif ($type === IShare::TYPE_GROUP) {
$gid = $row['share_with'];
@@ -1387,14 +1377,14 @@ class DefaultShareProvider implements IShareProvider {
$userList = $group->getUsers();
foreach ($userList as $user) {
$uid = $user->getUID();
- $users[$uid] = isset($users[$uid]) ? $users[$uid] : [];
+ $users[$uid] = $users[$uid] ?? [];
$users[$uid][$row['id']] = $row;
}
} elseif ($type === IShare::TYPE_LINK) {
$link = true;
} elseif ($type === IShare::TYPE_USERGROUP && $currentAccess === true) {
$uid = $row['share_with'];
- $users[$uid] = isset($users[$uid]) ? $users[$uid] : [];
+ $users[$uid] = $users[$uid] ?? [];
$users[$uid][$row['id']] = $row;
}
}
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index b03608f9872..4606101b7e6 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -41,7 +41,6 @@
*/
namespace OC\Share20;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Mount\MoveableMount;
use OC\KnownUser\KnownUserService;
use OC\Share20\Exception\ProviderException;
@@ -106,8 +105,6 @@ class Manager implements IManager {
private $userManager;
/** @var IRootFolder */
private $rootFolder;
- /** @var CappedMemoryCache */
- private $sharingDisabledForUsersCache;
/** @var LegacyHooks */
private $legacyHooks;
/** @var IMailer */
@@ -122,6 +119,7 @@ class Manager implements IManager {
private $userSession;
/** @var KnownUserService */
private $knownUserService;
+ private ShareDisableChecker $shareDisableChecker;
public function __construct(
LoggerInterface $logger,
@@ -140,7 +138,8 @@ class Manager implements IManager {
\OC_Defaults $defaults,
IEventDispatcher $dispatcher,
IUserSession $userSession,
- KnownUserService $knownUserService
+ KnownUserService $knownUserService,
+ ShareDisableChecker $shareDisableChecker
) {
$this->logger = $logger;
$this->config = $config;
@@ -153,7 +152,6 @@ class Manager implements IManager {
$this->factory = $factory;
$this->userManager = $userManager;
$this->rootFolder = $rootFolder;
- $this->sharingDisabledForUsersCache = new CappedMemoryCache();
// The constructor of LegacyHooks registers the listeners of share events
// do not remove if those are not properly migrated
$this->legacyHooks = new LegacyHooks($dispatcher);
@@ -163,6 +161,7 @@ class Manager implements IManager {
$this->dispatcher = $dispatcher;
$this->userSession = $userSession;
$this->knownUserService = $knownUserService;
+ $this->shareDisableChecker = $shareDisableChecker;
}
/**
@@ -2025,37 +2024,7 @@ class Manager implements IManager {
* @return bool
*/
public function sharingDisabledForUser($userId) {
- if ($userId === null) {
- return false;
- }
-
- if (isset($this->sharingDisabledForUsersCache[$userId])) {
- return $this->sharingDisabledForUsersCache[$userId];
- }
-
- if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
- $groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
- $excludedGroups = json_decode($groupsList);
- if (is_null($excludedGroups)) {
- $excludedGroups = explode(',', $groupsList);
- $newValue = json_encode($excludedGroups);
- $this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
- }
- $user = $this->userManager->get($userId);
- $usersGroups = $this->groupManager->getUserGroupIds($user);
- if (!empty($usersGroups)) {
- $remainingGroups = array_diff($usersGroups, $excludedGroups);
- // if the user is only in groups which are disabled for sharing then
- // sharing is also disabled for the user
- if (empty($remainingGroups)) {
- $this->sharingDisabledForUsersCache[$userId] = true;
- return true;
- }
- }
- }
-
- $this->sharingDisabledForUsersCache[$userId] = false;
- return false;
+ return $this->shareDisableChecker->sharingDisabledForUser($userId);
}
/**
diff --git a/lib/private/Share20/ProviderFactory.php b/lib/private/Share20/ProviderFactory.php
index aaab5a3fd88..dbf1b21dabe 100644
--- a/lib/private/Share20/ProviderFactory.php
+++ b/lib/private/Share20/ProviderFactory.php
@@ -41,6 +41,7 @@ use OCA\FederatedFileSharing\TokenHandler;
use OCA\ShareByMail\Settings\SettingsManager;
use OCA\ShareByMail\ShareByMailProvider;
use OCA\Talk\Share\RoomShareProvider;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IServerContainer;
@@ -104,7 +105,7 @@ class ProviderFactory implements IProviderFactory {
$this->serverContainer->query(Defaults::class),
$this->serverContainer->getL10NFactory(),
$this->serverContainer->getURLGenerator(),
- $this->serverContainer->getConfig()
+ $this->serverContainer->query(ITimeFactory::class),
);
}
diff --git a/lib/private/Share20/ShareDisableChecker.php b/lib/private/Share20/ShareDisableChecker.php
new file mode 100644
index 00000000000..9d0c2b8c2b4
--- /dev/null
+++ b/lib/private/Share20/ShareDisableChecker.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace OC\Share20;
+
+use OCP\Cache\CappedMemoryCache;
+use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\IUserManager;
+
+/**
+ * split of from the share manager to allow using it with minimal DI
+ */
+class ShareDisableChecker {
+ private CappedMemoryCache $sharingDisabledForUsersCache;
+
+ public function __construct(
+ private IConfig $config,
+ private IUserManager $userManager,
+ private IGroupManager $groupManager,
+ ) {
+ $this->sharingDisabledForUsersCache = new CappedMemoryCache();
+ }
+
+
+ /**
+ * @param ?string $userId
+ * @return bool
+ */
+ public function sharingDisabledForUser(?string $userId) {
+ if ($userId === null) {
+ return false;
+ }
+
+ if (isset($this->sharingDisabledForUsersCache[$userId])) {
+ return $this->sharingDisabledForUsersCache[$userId];
+ }
+
+ if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
+ $groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
+ $excludedGroups = json_decode($groupsList);
+ if (is_null($excludedGroups)) {
+ $excludedGroups = explode(',', $groupsList);
+ $newValue = json_encode($excludedGroups);
+ $this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
+ }
+ $user = $this->userManager->get($userId);
+ if (!$user) {
+ return false;
+ }
+ $usersGroups = $this->groupManager->getUserGroupIds($user);
+ if (!empty($usersGroups)) {
+ $remainingGroups = array_diff($usersGroups, $excludedGroups);
+ // if the user is only in groups which are disabled for sharing then
+ // sharing is also disabled for the user
+ if (empty($remainingGroups)) {
+ $this->sharingDisabledForUsersCache[$userId] = true;
+ return true;
+ }
+ }
+ }
+
+ $this->sharingDisabledForUsersCache[$userId] = false;
+ return false;
+ }
+}
diff --git a/lib/private/SystemConfig.php b/lib/private/SystemConfig.php
index a18c4c2a138..c104f001809 100644
--- a/lib/private/SystemConfig.php
+++ b/lib/private/SystemConfig.php
@@ -118,6 +118,9 @@ class SystemConfig {
],
],
],
+ 'onlyoffice' => [
+ 'jwt_secret' => true,
+ ],
];
/** @var Config */
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/Tags.php b/lib/private/Tags.php
index 8da1e7edc3b..766166beadd 100644
--- a/lib/private/Tags.php
+++ b/lib/private/Tags.php
@@ -529,7 +529,7 @@ class Tags implements ITags {
if (is_string($tag) && !is_numeric($tag)) {
$tag = trim($tag);
if ($tag === '') {
- \OCP\Util::writeLog('core', __METHOD__.', Cannot add an empty tag', ILogger::DEBUG);
+ $this->logger->debug(__METHOD__.', Cannot add an empty tag');
return false;
}
if (!$this->hasTag($tag)) {
@@ -569,7 +569,7 @@ class Tags implements ITags {
if (is_string($tag) && !is_numeric($tag)) {
$tag = trim($tag);
if ($tag === '') {
- \OCP\Util::writeLog('core', __METHOD__.', Tag name is empty', ILogger::DEBUG);
+ $this->logger->debug(__METHOD__.', Tag name is empty');
return false;
}
$tagId = $this->getTagId($tag);
@@ -609,8 +609,7 @@ class Tags implements ITags {
$names = array_map('trim', $names);
array_filter($names);
- \OCP\Util::writeLog('core', __METHOD__ . ', before: '
- . print_r($this->tags, true), ILogger::DEBUG);
+ $this->logger->debug(__METHOD__ . ', before: ' . print_r($this->tags, true));
foreach ($names as $name) {
$id = null;
@@ -625,8 +624,7 @@ class Tags implements ITags {
unset($this->tags[$key]);
$this->mapper->delete($tag);
} else {
- \OCP\Util::writeLog('core', __METHOD__ . 'Cannot delete tag ' . $name
- . ': not found.', ILogger::ERROR);
+ $this->logger->error(__METHOD__ . 'Cannot delete tag ' . $name . ': not found.');
}
if (!is_null($id) && $id !== false) {
try {
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 658a85152bf..2adf3c5e692 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,18 @@ 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');
-
+ /*
+ * NB : Unified search enabled, defaults to true since new advanced search is
+ * unstable. Once we think otherwise, the default should be false.
+ */
+ if ($this->config->getSystemValueBool('unified_search.enabled', 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', 'unified-search', 'core');
+ } else {
+ Util::addScript('core', 'global-search', 'core');
+ }
// Set body data-theme
$this->assign('enabledThemes', []);
if (\OC::$server->getAppManager()->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
@@ -189,13 +198,31 @@ class TemplateLayout extends \OC_Template {
$this->assign('appid', $appId);
$this->assign('bodyid', 'body-public');
+ // Set logo link target
+ $logoUrl = $this->config->getSystemValueString('logo_url', '');
+ $this->assign('logoUrl', $logoUrl);
+
/** @var IRegistry $subscription */
$subscription = \OCP\Server::get(IRegistry::class);
$showSimpleSignup = $this->config->getSystemValueBool('simpleSignUpLink.shown', true);
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');
}
@@ -279,7 +306,7 @@ class TemplateLayout extends \OC_Template {
$web = $info[1];
$file = $info[2];
- if (substr($file, -strlen('print.css')) === 'print.css') {
+ if (str_ends_with($file, 'print.css')) {
$this->append('printcssfiles', $web.'/'.$file . $this->getVersionHashSuffix());
} else {
$suffix = $this->getVersionHashSuffix($web, $file);
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 b9cb06c298e..c98ff893143 100644
--- a/lib/private/TextProcessing/Manager.php
+++ b/lib/private/TextProcessing/Manager.php
@@ -28,6 +28,8 @@ namespace OC\TextProcessing;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\TextProcessing\Db\Task as DbTask;
use OCP\IConfig;
+use OCP\TextProcessing\Exception\TaskFailureException;
+use OCP\TextProcessing\IProviderWithExpectedRuntime;
use OCP\TextProcessing\Task;
use OCP\TextProcessing\Task as OCPTask;
use OC\TextProcessing\Db\TaskMapper;
@@ -114,26 +116,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, fn ($provider) => $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());
@@ -145,31 +137,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());
@@ -181,6 +179,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);
@@ -253,4 +270,26 @@ 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, fn ($provider) => $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..f68f657277f
--- /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 OCP\Files\AppData\IAppDataFactory;
+use OCP\Files\IAppData;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IConfig;
+use OCP\TextToImage\Exception\TaskFailureException;
+use OCP\TextToImage\Exception\TaskNotFoundException;
+use OCP\TextToImage\IManager;
+use OCP\TextToImage\Task;
+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\IServerContainer;
+use OCP\TextToImage\IProvider;
+use OCP\PreConditionNotMetException;
+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 3a52b99889c..4e9a23b3eae 100644
--- a/lib/private/URLGenerator.php
+++ b/lib/private/URLGenerator.php
@@ -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);
}
@@ -147,7 +156,7 @@ class URLGenerator implements IURLGenerator {
$app_path = $this->getAppManager()->getAppPath($appName);
// Check if the app is in the app folder
if (file_exists($app_path . '/' . $file)) {
- if (substr($file, -3) === 'php') {
+ if (str_ends_with($file, 'php')) {
$urlLinkTo = \OC::$WEBROOT . '/index.php/apps/' . $appName;
if ($frontControllerActive) {
$urlLinkTo = \OC::$WEBROOT . '/apps/' . $appName;
diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php
index 2aab260716a..97f770b6998 100644
--- a/lib/private/Updater/VersionCheck.php
+++ b/lib/private/Updater/VersionCheck.php
@@ -31,6 +31,7 @@ use OCP\IConfig;
use OCP\IUserManager;
use OCP\Support\Subscription\IRegistry;
use OCP\Util;
+use Psr\Log\LoggerInterface;
class VersionCheck {
public function __construct(
@@ -38,6 +39,7 @@ class VersionCheck {
private IConfig $config,
private IUserManager $userManager,
private IRegistry $registry,
+ private LoggerInterface $logger,
) {
}
@@ -86,6 +88,8 @@ class VersionCheck {
try {
$xml = $this->getUrlContent($url);
} catch (\Exception $e) {
+ $this->logger->info('Version could not be fetched from updater server: ' . $url, ['exception' => $e]);
+
return false;
}
diff --git a/lib/private/User/AvailabilityCoordinator.php b/lib/private/User/AvailabilityCoordinator.php
new file mode 100644
index 00000000000..8e6b73bd56d
--- /dev/null
+++ b/lib/private/User/AvailabilityCoordinator.php
@@ -0,0 +1,122 @@
+<?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\Db\AbsenceMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+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 AbsenceMapper $absenceMapper,
+ private IConfig $config,
+ private LoggerInterface $logger,
+ ) {
+ $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 {
+ $cachedData = $this->getCachedOutOfOfficeData($user);
+ if ($cachedData !== null) {
+ return $cachedData;
+ }
+
+ try {
+ $absenceData = $this->absenceMapper->findByUserId($user->getUID());
+ } catch (DoesNotExistException $e) {
+ return null;
+ }
+
+ $data = $absenceData->toOutOufOfficeData($user);
+ $this->setCachedOutOfOfficeData($data);
+ return $data;
+ }
+}
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php
index fb1afb65825..8ec8ef0c4be 100644
--- a/lib/private/User/Manager.php
+++ b/lib/private/User/Manager.php
@@ -52,6 +52,7 @@ use OCP\User\Backend\IGetRealUIDBackend;
use OCP\User\Backend\ISearchKnownUsersBackend;
use OCP\User\Backend\ICheckPasswordBackend;
use OCP\User\Backend\ICountUsersBackend;
+use OCP\User\Backend\IProvideEnabledStateBackend;
use OCP\User\Events\BeforeUserCreatedEvent;
use OCP\User\Events\UserCreatedEvent;
use OCP\UserInterface;
@@ -338,6 +339,35 @@ class Manager extends PublicEmitter implements IUserManager {
}
/**
+ * @return IUser[]
+ */
+ public function getDisabledUsers(?int $limit = null, int $offset = 0): array {
+ $users = $this->config->getUsersForUserValue('core', 'enabled', 'false');
+ $users = array_combine(
+ $users,
+ array_map(
+ fn (string $uid): IUser => new LazyUser($uid, $this),
+ $users
+ )
+ );
+
+ $tempLimit = ($limit === null ? null : $limit + $offset);
+ foreach ($this->backends as $backend) {
+ if (($tempLimit !== null) && (count($users) >= $tempLimit)) {
+ break;
+ }
+ if ($backend instanceof IProvideEnabledStateBackend) {
+ $backendUsers = $backend->getDisabledUserList(($tempLimit === null ? null : $tempLimit - count($users)));
+ foreach ($backendUsers as $uid) {
+ $users[$uid] = new LazyUser($uid, $this, null, $backend);
+ }
+ }
+ }
+
+ return array_slice($users, $offset, $limit);
+ }
+
+ /**
* Search known users (from phonebook sync) by displayName
*
* @param string $searcher
diff --git a/lib/private/User/OutOfOfficeData.php b/lib/private/User/OutOfOfficeData.php
new file mode 100644
index 00000000000..12b7e03a0ae
--- /dev/null
+++ b/lib/private/User/OutOfOfficeData.php
@@ -0,0 +1,63 @@
+<?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;
+ }
+}
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index 82887f8d029..f8578ee616c 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -783,6 +783,11 @@ class Session implements IUserSession, Emitter {
try {
$dbToken = $this->tokenProvider->getToken($token);
} catch (InvalidTokenException $ex) {
+ $this->logger->debug('Session token is invalid because it does not exist', [
+ 'app' => 'core',
+ 'user' => $user,
+ 'exception' => $ex,
+ ]);
return false;
}
@@ -794,12 +799,18 @@ class Session implements IUserSession, Emitter {
$this->logger->error('App token login name does not match', [
'tokenLoginName' => $dbToken->getLoginName(),
'sessionLoginName' => $user,
+ 'app' => 'core',
+ 'user' => $dbToken->getUID(),
]);
return false;
}
if (!$this->checkTokenCredentials($dbToken, $token)) {
+ $this->logger->warning('Session token credentials are invalid', [
+ 'app' => 'core',
+ 'user' => $user,
+ ]);
return false;
}
@@ -875,9 +886,9 @@ class Session implements IUserSession, Emitter {
$tokens = $this->config->getUserKeys($uid, 'login_token');
// test cookies token against stored tokens
if (!in_array($currentToken, $tokens, true)) {
- $this->logger->info('Tried to log in {uid} but could not verify token', [
+ $this->logger->info('Tried to log in but could not verify token', [
'app' => 'core',
- 'uid' => $uid,
+ 'user' => $uid,
]);
return false;
}
@@ -885,18 +896,31 @@ class Session implements IUserSession, Emitter {
$this->config->deleteUserValue($uid, 'login_token', $currentToken);
$newToken = $this->random->generate(32);
$this->config->setUserValue($uid, 'login_token', $newToken, (string)$this->timeFactory->getTime());
+ $this->logger->debug('Remember-me token replaced', [
+ 'app' => 'core',
+ 'user' => $uid,
+ ]);
try {
$sessionId = $this->session->getId();
$token = $this->tokenProvider->renewSessionToken($oldSessionId, $sessionId);
+ $this->logger->debug('Session token replaced', [
+ 'app' => 'core',
+ 'user' => $uid,
+ ]);
} catch (SessionNotAvailableException $ex) {
- $this->logger->warning('Could not renew session token for {uid} because the session is unavailable', [
+ $this->logger->critical('Could not renew session token for {uid} because the session is unavailable', [
'app' => 'core',
'uid' => $uid,
+ 'user' => $uid,
]);
return false;
} catch (InvalidTokenException $ex) {
- $this->logger->warning('Renewing session token failed', ['app' => 'core']);
+ $this->logger->error('Renewing session token failed: ' . $ex->getMessage(), [
+ 'app' => 'core',
+ 'user' => $uid,
+ 'exception' => $ex,
+ ]);
return false;
}
@@ -935,10 +959,17 @@ class Session implements IUserSession, Emitter {
$this->manager->emit('\OC\User', 'logout', [$user]);
if ($user !== null) {
try {
- $this->tokenProvider->invalidateToken($this->session->getId());
+ $token = $this->session->getId();
+ $this->tokenProvider->invalidateToken($token);
+ $this->logger->debug('Session token invalidated before logout', [
+ 'user' => $user->getUID(),
+ ]);
} catch (SessionNotAvailableException $ex) {
}
}
+ $this->logger->debug('Logging out', [
+ 'user' => $user === null ? null : $user->getUID(),
+ ]);
$this->setUser(null);
$this->setLoginName(null);
$this->setToken(null);
diff --git a/lib/private/User/User.php b/lib/private/User/User.php
index 3362367fff1..c62d14d9450 100644
--- a/lib/private/User/User.php
+++ b/lib/private/User/User.php
@@ -597,7 +597,7 @@ class User implements IUser {
public function getCloudId() {
$uid = $this->getUID();
$server = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
- if (substr($server, -10) === '/index.php') {
+ if (str_ends_with($server, '/index.php')) {
$server = substr($server, 0, -10);
}
$server = $this->removeProtocolFromUrl($server);
diff --git a/lib/private/legacy/OC_API.php b/lib/private/legacy/OC_API.php
index 275e02986c4..862e73e6edd 100644
--- a/lib/private/legacy/OC_API.php
+++ b/lib/private/legacy/OC_API.php
@@ -130,7 +130,7 @@ class OC_API {
protected static function isV2(\OCP\IRequest $request) {
$script = $request->getScriptName();
- return substr($script, -11) === '/ocs/v2.php';
+ return str_ends_with($script, '/ocs/v2.php');
}
/**
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index ac449a62a4f..23e0b099e91 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -56,7 +56,6 @@ use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
use OCP\Authentication\IAlternativeLogin;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\ILogger;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\App\DependencyAnalyzer;
use OC\App\Platform;
@@ -292,7 +291,7 @@ class OC_App {
}
}
- \OCP\Util::writeLog('core', 'No application directories are marked as writable.', ILogger::ERROR);
+ \OCP\Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']);
return null;
}
@@ -391,7 +390,7 @@ class OC_App {
public static function getAppVersionByPath(string $path): string {
$infoFile = $path . '/appinfo/info.xml';
$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
- return isset($appData['version']) ? $appData['version'] : '';
+ return $appData['version'] ?? '';
}
/**
@@ -517,7 +516,7 @@ class OC_App {
foreach (OC::$APPSROOTS as $apps_dir) {
if (!is_readable($apps_dir['path'])) {
- \OCP\Util::writeLog('core', 'unable to read app folder : ' . $apps_dir['path'], ILogger::WARN);
+ \OCP\Server::get(LoggerInterface::class)->warning('unable to read app folder : ' . $apps_dir['path'], ['app' => 'core']);
continue;
}
$dh = opendir($apps_dir['path']);
@@ -568,12 +567,12 @@ class OC_App {
if (array_search($app, $blacklist) === false) {
$info = $appManager->getAppInfo($app, false, $langCode);
if (!is_array($info)) {
- \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', ILogger::ERROR);
+ \OCP\Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
continue;
}
if (!isset($info['name'])) {
- \OCP\Util::writeLog('core', 'App id "' . $app . '" has no name in appinfo', ILogger::ERROR);
+ \OCP\Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
continue;
}
@@ -870,11 +869,11 @@ class OC_App {
}
return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
} else {
- \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', ILogger::ERROR);
+ \OCP\Server::get(LoggerInterface::class)->error('Can\'t get app storage, app ' . $appId . ', user not logged in', ['app' => 'core']);
return false;
}
} else {
- \OCP\Util::writeLog('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', ILogger::ERROR);
+ \OCP\Server::get(LoggerInterface::class)->error('Can\'t get app storage, app ' . $appId . ' not enabled', ['app' => 'core']);
return false;
}
}
diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php
index 7bc1fab94b6..ac0a2bbd0e9 100644
--- a/lib/private/legacy/OC_Files.php
+++ b/lib/private/legacy/OC_Files.php
@@ -76,7 +76,6 @@ class OC_Files {
private static function sendHeaders($filename, $name, array $rangeArray): void {
OC_Response::setContentDispositionHeader($name, 'attachment');
header('Content-Transfer-Encoding: binary', true);
- header('Pragma: public');// enable caching in IE
header('Expires: 0');
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
$fileSize = \OC\Files\Filesystem::filesize($filename);
@@ -230,6 +229,10 @@ class OC_Files {
OC::$server->getLogger()->logException($ex);
$l = \OC::$server->getL10N('lib');
\OC_Template::printErrorPage($l->t('Cannot download file'), $ex->getMessage(), 200);
+ } catch (\OCP\Files\ConnectionLostException $ex) {
+ self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
+ OC::$server->getLogger()->logException($ex, ['level' => \OCP\ILogger::DEBUG]);
+ \OC_Template::printErrorPage('Connection lost', $ex->getMessage(), 200);
} catch (\Exception $ex) {
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
OC::$server->getLogger()->logException($ex);
diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php
index caa4f5dca65..5751b813f2c 100644
--- a/lib/private/legacy/OC_User.php
+++ b/lib/private/legacy/OC_User.php
@@ -38,10 +38,10 @@
use OC\User\LoginException;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\ILogger;
use OCP\IUserManager;
use OCP\User\Events\BeforeUserLoggedInEvent;
use OCP\User\Events\UserLoggedInEvent;
+use Psr\Log\LoggerInterface;
/**
* This class provides wrapper methods for user management. Multiple backends are
@@ -93,7 +93,7 @@ class OC_User {
case 'database':
case 'mysql':
case 'sqlite':
- \OCP\Util::writeLog('core', 'Adding user backend ' . $backend . '.', ILogger::DEBUG);
+ \OCP\Server::get(LoggerInterface::class)->debug('Adding user backend ' . $backend . '.', ['app' => 'core']);
self::$_usedBackends[$backend] = new \OC\User\Database();
\OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]);
break;
@@ -102,7 +102,7 @@ class OC_User {
\OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]);
break;
default:
- \OCP\Util::writeLog('core', 'Adding default user backend ' . $backend . '.', ILogger::DEBUG);
+ \OCP\Server::get(LoggerInterface::class)->debug('Adding default user backend ' . $backend . '.', ['app' => 'core']);
$className = 'OC_USER_' . strtoupper($backend);
self::$_usedBackends[$backend] = new $className();
\OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]);
@@ -147,10 +147,10 @@ class OC_User {
self::useBackend($backend);
self::$_setupedBackends[] = $i;
} else {
- \OCP\Util::writeLog('core', 'User backend ' . $class . ' already initialized.', ILogger::DEBUG);
+ \OCP\Server::get(LoggerInterface::class)->debug('User backend ' . $class . ' already initialized.', ['app' => 'core']);
}
} else {
- \OCP\Util::writeLog('core', 'User backend ' . $class . ' not found.', ILogger::ERROR);
+ \OCP\Server::get(LoggerInterface::class)->error('User backend ' . $class . ' not found.', ['app' => 'core']);
}
}
}
diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php
index 9d62c46137e..f82ddcc78ee 100644
--- a/lib/private/legacy/OC_Util.php
+++ b/lib/private/legacy/OC_Util.php
@@ -325,9 +325,10 @@ class OC_Util {
return;
}
+ $timestamp = filemtime(OC::$SERVERROOT . '/version.php');
require OC::$SERVERROOT . '/version.php';
/** @var int $timestamp */
- self::$versionCache['OC_Version_Timestamp'] = \OC::$VERSION_MTIME;
+ self::$versionCache['OC_Version_Timestamp'] = $timestamp;
/** @var string $OC_Version */
self::$versionCache['OC_Version'] = $OC_Version;
/** @var string $OC_VersionString */
diff --git a/lib/public/App/IAppManager.php b/lib/public/App/IAppManager.php
index 2497544dcbe..4667cf13f0f 100644
--- a/lib/public/App/IAppManager.php
+++ b/lib/public/App/IAppManager.php
@@ -248,7 +248,30 @@ interface IAppManager {
*
* If `user` is not passed, the currently logged in user will be used
*
+ * @param ?IUser $user User to query default app for
+ * @param bool $withFallbacks Include fallback values if no default app was configured manually
+ * Before falling back to predefined default apps,
+ * the user defined app order is considered and the first app would be used as the fallback.
+ *
* @since 25.0.6
+ * @since 28.0.0 Added optional $withFallbacks parameter
+ */
+ public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string;
+
+ /**
+ * Get the global default apps with fallbacks
+ *
+ * @return string[] The default applications
+ * @since 28.0.0
+ */
+ public function getDefaultApps(): array;
+
+ /**
+ * Set the global default apps with fallbacks
+ *
+ * @param string[] $appId
+ * @throws \InvalidArgumentException If any of the apps is not installed
+ * @since 28.0.0
*/
- public function getDefaultAppForUser(?IUser $user = null): string;
+ public function setDefaultApps(array $defaultApps): void;
}
diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
index 720803a78d1..3153b556a5c 100644
--- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
+++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
@@ -38,6 +38,7 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Template\ICustomTemplateProvider;
use OCP\IContainer;
use OCP\TextProcessing\IProvider as ITextProcessingProvider;
+use OCP\TextToImage\IProvider as ITextToImageProvider;
use OCP\Notification\INotifier;
use OCP\Preview\IProviderV2;
use OCP\SpeechToText\ISpeechToTextProvider;
@@ -129,7 +130,7 @@ interface IRegistrationContext {
* @param string $event preferably the fully-qualified class name of the Event sub class to listen for
* @psalm-param string|class-string<T> $event preferably the fully-qualified class name of the Event sub class to listen for
* @param string $listener fully qualified class name (or ::class notation) of a \OCP\EventDispatcher\IEventListener that can be built by the DI container
- * @psalm-param class-string<\OCP\EventDispatcher\IEventListener> $listener fully qualified class name that can be built by the DI container
+ * @psalm-param class-string<\OCP\EventDispatcher\IEventListener<T>> $listener fully qualified class name that can be built by the DI container
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*
@@ -231,6 +232,16 @@ interface IRegistrationContext {
public function registerTextProcessingProvider(string $providerClass): void;
/**
+ * Register a custom text2image provider class that provides the possibility to generate images
+ * through the OCP\TextToImage APIs
+ *
+ * @param string $providerClass
+ * @psalm-param class-string<ITextToImageProvider> $providerClass
+ * @since 28.0.0
+ */
+ public function registerTextToImageProvider(string $providerClass): void;
+
+ /**
* Register a custom template provider class that is able to inject custom templates
* in addition to the user defined ones
*
@@ -371,4 +382,13 @@ interface IRegistrationContext {
* @since 26.0.0
*/
public function registerPublicShareTemplateProvider(string $class): void;
+
+ /**
+ * Register an implementation of \OCP\SetupCheck\ISetupCheck that
+ * will handle the implementation of a setup check
+ *
+ * @param class-string<\OCP\SetupCheck\ISetupCheck> $setupCheckClass
+ * @since 28.0.0
+ */
+ public function registerSetupCheck(string $setupCheckClass): void;
}
diff --git a/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php b/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php
index 31ccd014321..4802ea5f1fd 100644
--- a/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php
+++ b/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php
@@ -31,6 +31,7 @@ use Attribute;
* Attribute for controller methods that should be ignored when generating OpenAPI documentation
*
* @since 28.0.0
+ * @deprecated 28.0.0 Use {@see OpenAPI} with {@see OpenAPI::SCOPE_IGNORE} instead: `#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]`
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
class IgnoreOpenAPI {
diff --git a/lib/public/AppFramework/Http/Attribute/OpenAPI.php b/lib/public/AppFramework/Http/Attribute/OpenAPI.php
new file mode 100644
index 00000000000..c5b3bcf5dda
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/OpenAPI.php
@@ -0,0 +1,99 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.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 OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * With this attribute a controller or a method can be moved into a different
+ * scope or tag. Scopes should be seen as API consumers, tags can be used to group
+ * different routes inside the same scope.
+ *
+ * @since 28.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+class OpenAPI {
+ /**
+ * APIs used for normal user facing interaction with your app,
+ * e.g. when you would implement a mobile client or standalone frontend.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_DEFAULT = 'default';
+
+ /**
+ * APIs used to administrate your app's configuration on an administrative level.
+ * Will be set automatically when admin permissions are required to access the route.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_ADMINISTRATION = 'administration';
+
+ /**
+ * APIs used by servers to federate with each other.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_FEDERATION = 'federation';
+
+ /**
+ * Ignore this controller or method in all generated OpenAPI specifications.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_IGNORE = 'ignore';
+
+ /**
+ * @param self::SCOPE_*|string $scope Scopes are used to define different clients.
+ * It is recommended to go with the scopes available as self::SCOPE_* constants,
+ * but in exotic cases other APIs might need documentation as well,
+ * then a free string can be provided (but it should be `a-z` only).
+ * @param ?list<string> $tags Tags can be used to group routes inside a scope
+ * for easier implementation and reviewing of the API specification.
+ * It defaults to the controller name in snake_case (should be `a-z` and underscore only).
+ * @since 28.0.0
+ */
+ public function __construct(
+ protected string $scope = self::SCOPE_DEFAULT,
+ protected ?array $tags = null,
+ ) {
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getScope(): string {
+ return $this->scope;
+ }
+
+ /**
+ * @return ?list<string>
+ * @since 28.0.0
+ */
+ public function getTags(): ?array {
+ return $this->tags;
+ }
+}
diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
index f17dd9bd270..7f93f7004d9 100644
--- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
@@ -48,6 +48,8 @@ class ContentSecurityPolicy extends EmptyContentSecurityPolicy {
protected ?bool $evalWasmAllowed = false;
/** @var bool Whether strict-dynamic should be set */
protected $strictDynamicAllowed = false;
+ /** @var bool Whether strict-dynamic should be set for 'script-src-elem' */
+ protected $strictDynamicAllowedOnScripts = true;
/** @var array Domains from which scripts can get loaded */
protected $allowedScriptDomains = [
'\'self\'',
diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
index 7e1de2ef2eb..aeee4a4ee74 100644
--- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
@@ -37,10 +37,12 @@ namespace OCP\AppFramework\Http;
* @since 9.0.0
*/
class EmptyContentSecurityPolicy {
- /** @var string Whether JS nonces should be used */
- protected $useJsNonce = null;
+ /** @var string JS nonce to be used */
+ protected $jsNonce = null;
/** @var bool Whether strict-dynamic should be used */
protected $strictDynamicAllowed = null;
+ /** @var bool Whether strict-dynamic should be used on script-src-elem */
+ protected $strictDynamicAllowedOnScripts = null;
/**
* @var bool Whether eval in JS scripts is allowed
* TODO: Disallow per default
@@ -94,6 +96,18 @@ class EmptyContentSecurityPolicy {
}
/**
+ * In contrast to `useStrictDynamic` this only sets strict-dynamic on script-src-elem
+ * Meaning only grants trust to all imports of scripts that were loaded in `<script>` tags, and thus weakens less the CSP.
+ * @param bool $state
+ * @return EmptyContentSecurityPolicy
+ * @since 28.0.0
+ */
+ public function useStrictDynamicOnScripts(bool $state = false): self {
+ $this->strictDynamicAllowedOnScripts = $state;
+ return $this;
+ }
+
+ /**
* Use the according JS nonce
* This method is only for CSPMiddleware, custom values are ignored in mergePolicies of ContentSecurityPolicyManager
*
@@ -102,7 +116,7 @@ class EmptyContentSecurityPolicy {
* @since 11.0.0
*/
public function useJsNonce($nonce) {
- $this->useJsNonce = $nonce;
+ $this->jsNonce = $nonce;
return $this;
}
@@ -448,27 +462,35 @@ class EmptyContentSecurityPolicy {
if (!empty($this->allowedScriptDomains) || $this->evalScriptAllowed || $this->evalWasmAllowed) {
$policy .= 'script-src ';
- if (is_string($this->useJsNonce)) {
+ $scriptSrc = '';
+ if (is_string($this->jsNonce)) {
if ($this->strictDynamicAllowed) {
- $policy .= '\'strict-dynamic\' ';
+ $scriptSrc .= '\'strict-dynamic\' ';
}
- $policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\'';
+ $scriptSrc .= '\'nonce-'.base64_encode($this->jsNonce).'\'';
$allowedScriptDomains = array_flip($this->allowedScriptDomains);
unset($allowedScriptDomains['\'self\'']);
$this->allowedScriptDomains = array_flip($allowedScriptDomains);
if (count($allowedScriptDomains) !== 0) {
- $policy .= ' ';
+ $scriptSrc .= ' ';
}
}
if (is_array($this->allowedScriptDomains)) {
- $policy .= implode(' ', $this->allowedScriptDomains);
+ $scriptSrc .= implode(' ', $this->allowedScriptDomains);
}
if ($this->evalScriptAllowed) {
- $policy .= ' \'unsafe-eval\'';
+ $scriptSrc .= ' \'unsafe-eval\'';
}
if ($this->evalWasmAllowed) {
- $policy .= ' \'wasm-unsafe-eval\'';
+ $scriptSrc .= ' \'wasm-unsafe-eval\'';
}
+ $policy .= $scriptSrc . ';';
+ }
+
+ // We only need to set this if 'strictDynamicAllowed' is not set because otherwise we can simply fall back to script-src
+ if ($this->strictDynamicAllowedOnScripts && is_string($this->jsNonce) && !$this->strictDynamicAllowed) {
+ $policy .= 'script-src-elem \'strict-dynamic\' ';
+ $policy .= $scriptSrc ?? '';
$policy .= ';';
}
diff --git a/lib/public/AppFramework/Http/Response.php b/lib/public/AppFramework/Http/Response.php
index dd4f2c53418..d28f45f4c60 100644
--- a/lib/public/AppFramework/Http/Response.php
+++ b/lib/public/AppFramework/Http/Response.php
@@ -112,9 +112,8 @@ class Response {
*/
public function cacheFor(int $cacheSeconds, bool $public = false, bool $immutable = false) {
if ($cacheSeconds > 0) {
- $pragma = $public ? 'public' : 'private';
- $this->addHeader('Cache-Control', sprintf('%s, max-age=%s, %s', $pragma, $cacheSeconds, ($immutable ? 'immutable' : 'must-revalidate')));
- $this->addHeader('Pragma', $pragma);
+ $cacheStore = $public ? 'public' : 'private';
+ $this->addHeader('Cache-Control', sprintf('%s, max-age=%s, %s', $cacheStore, $cacheSeconds, ($immutable ? 'immutable' : 'must-revalidate')));
// Set expires header
$expires = new \DateTime();
@@ -125,7 +124,7 @@ class Response {
$this->addHeader('Expires', $expires->format(\DateTimeInterface::RFC2822));
} else {
$this->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
- unset($this->headers['Expires'], $this->headers['Pragma']);
+ unset($this->headers['Expires']);
}
return $this;
diff --git a/lib/public/AppFramework/Utility/ITimeFactory.php b/lib/public/AppFramework/Utility/ITimeFactory.php
index d4f74c9d107..23f67d3dc38 100644
--- a/lib/public/AppFramework/Utility/ITimeFactory.php
+++ b/lib/public/AppFramework/Utility/ITimeFactory.php
@@ -33,7 +33,7 @@ use Psr\Clock\ClockInterface;
* Use this to get a timestamp or DateTime object in code to remain testable
*
* @since 8.0.0
- * @since 26.0.0 Extends the \Psr\Clock\ClockInterface interface
+ * @since 27.0.0 Extends the \Psr\Clock\ClockInterface interface
* @ref https://www.php-fig.org/psr/psr-20/#21-clockinterface
*/
diff --git a/lib/public/Authentication/Exceptions/ExpiredTokenException.php b/lib/public/Authentication/Exceptions/ExpiredTokenException.php
new file mode 100644
index 00000000000..5c1f4a30541
--- /dev/null
+++ b/lib/public/Authentication/Exceptions/ExpiredTokenException.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.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 OCP\Authentication\Exceptions;
+
+use OCP\Authentication\Token\IToken;
+
+/**
+ * @since 28.0.0
+ */
+class ExpiredTokenException extends InvalidTokenException {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(
+ private IToken $token,
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getToken(): IToken {
+ return $this->token;
+ }
+}
diff --git a/lib/public/Authentication/Exceptions/InvalidTokenException.php b/lib/public/Authentication/Exceptions/InvalidTokenException.php
new file mode 100644
index 00000000000..4869cbd6465
--- /dev/null
+++ b/lib/public/Authentication/Exceptions/InvalidTokenException.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 OCP\Authentication\Exceptions;
+
+use Exception;
+
+/**
+ * @since 28.0.0
+ */
+class InvalidTokenException extends Exception {
+}
diff --git a/lib/public/Authentication/Exceptions/WipeTokenException.php b/lib/public/Authentication/Exceptions/WipeTokenException.php
new file mode 100644
index 00000000000..81ea2dc57ad
--- /dev/null
+++ b/lib/public/Authentication/Exceptions/WipeTokenException.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.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 OCP\Authentication\Exceptions;
+
+use OCP\Authentication\Token\IToken;
+
+/**
+ * @since 28.0.0
+ */
+class WipeTokenException extends InvalidTokenException {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(
+ private IToken $token,
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getToken(): IToken {
+ return $this->token;
+ }
+}
diff --git a/lib/public/Authentication/Token/IProvider.php b/lib/public/Authentication/Token/IProvider.php
index da2e400eb79..59d2b8f3649 100644
--- a/lib/public/Authentication/Token/IProvider.php
+++ b/lib/public/Authentication/Token/IProvider.php
@@ -24,6 +24,10 @@ declare(strict_types=1);
*/
namespace OCP\Authentication\Token;
+use OCP\Authentication\Exceptions\ExpiredTokenException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\Authentication\Exceptions\WipeTokenException;
+
/**
* @since 24.0.8
*/
@@ -38,4 +42,15 @@ interface IProvider {
* @return void
*/
public function invalidateTokensOfUser(string $uid, ?string $clientName);
+
+ /**
+ * Get a token by token string id
+ *
+ * @since 28.0.0
+ * @throws InvalidTokenException
+ * @throws ExpiredTokenException
+ * @throws WipeTokenException
+ * @return IToken
+ */
+ public function getToken(string $tokenId): IToken;
}
diff --git a/lib/public/Authentication/Token/IToken.php b/lib/public/Authentication/Token/IToken.php
new file mode 100644
index 00000000000..7b6ce8327c6
--- /dev/null
+++ b/lib/public/Authentication/Token/IToken.php
@@ -0,0 +1,139 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Robin Appelman <robin@icewind.nl>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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 OCP\Authentication\Token;
+
+use JsonSerializable;
+
+/**
+ * @since 28.0.0
+ */
+interface IToken extends JsonSerializable {
+ /**
+ * @since 28.0.0
+ */
+ public const TEMPORARY_TOKEN = 0;
+ /**
+ * @since 28.0.0
+ */
+ public const PERMANENT_TOKEN = 1;
+ /**
+ * @since 28.0.0
+ */
+ public const WIPE_TOKEN = 2;
+ /**
+ * @since 28.0.0
+ */
+ public const DO_NOT_REMEMBER = 0;
+ /**
+ * @since 28.0.0
+ */
+ public const REMEMBER = 1;
+
+ /**
+ * Get the token ID
+ * @since 28.0.0
+ */
+ public function getId(): int;
+
+ /**
+ * Get the user UID
+ * @since 28.0.0
+ */
+ public function getUID(): string;
+
+ /**
+ * Get the login name used when generating the token
+ * @since 28.0.0
+ */
+ public function getLoginName(): string;
+
+ /**
+ * Get the (encrypted) login password
+ * @since 28.0.0
+ */
+ public function getPassword(): ?string;
+
+ /**
+ * Get the timestamp of the last password check
+ * @since 28.0.0
+ */
+ public function getLastCheck(): int;
+
+ /**
+ * Set the timestamp of the last password check
+ * @since 28.0.0
+ */
+ public function setLastCheck(int $time): void;
+
+ /**
+ * Get the authentication scope for this token
+ * @since 28.0.0
+ */
+ public function getScope(): string;
+
+ /**
+ * Get the authentication scope for this token
+ * @since 28.0.0
+ */
+ public function getScopeAsArray(): array;
+
+ /**
+ * Set the authentication scope for this token
+ * @since 28.0.0
+ */
+ public function setScope(array $scope): void;
+
+ /**
+ * Get the name of the token
+ * @since 28.0.0
+ */
+ public function getName(): string;
+
+ /**
+ * Get the remember state of the token
+ * @since 28.0.0
+ */
+ public function getRemember(): int;
+
+ /**
+ * Set the token
+ * @since 28.0.0
+ */
+ public function setToken(string $token): void;
+
+ /**
+ * Set the password
+ * @since 28.0.0
+ */
+ public function setPassword(string $password): void;
+
+ /**
+ * Set the expiration time of the token
+ * @since 28.0.0
+ */
+ public function setExpires(?int $expires): void;
+}
diff --git a/lib/public/BackgroundJob/IJobList.php b/lib/public/BackgroundJob/IJobList.php
index 65e2f5b6250..0b00326ca1a 100644
--- a/lib/public/BackgroundJob/IJobList.php
+++ b/lib/public/BackgroundJob/IJobList.php
@@ -58,6 +58,19 @@ interface IJobList {
public function add($job, $argument = null): void;
/**
+ * Add a job to the list but only run it after the given timestamp
+ *
+ * For cron background jobs this means the job will likely run shortly after the timestamp
+ * has been reached. For ajax background jobs the job might only run when users are active
+ * on the instance again.
+ *
+ * @param class-string<IJob> $job
+ * @param mixed $argument The serializable argument to be passed to $job->run() when the job is executed
+ * @since 28.0.0
+ */
+ public function scheduleAfter(string $job, int $runAfter, $argument = null): void;
+
+ /**
* Remove a job from the list
*
* @param IJob|class-string<IJob> $job
@@ -147,7 +160,8 @@ interface IJobList {
public function resetBackgroundJob(IJob $job): void;
/**
- * Checks whether a job of the passed class is reserved to run
+ * Checks whether a job of the passed class was reserved to run
+ * in the last 6h
*
* @param string|null $className
* @return bool
diff --git a/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php b/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php
index 5206dc37115..23879b93fa1 100644
--- a/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php
+++ b/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php
@@ -29,6 +29,7 @@ use OCP\EventDispatcher\GenericEvent;
/**
* @since 16.0.0
+ * @deprecated Use {@see AutoCompleteFilterEvent} instead
*/
class AutoCompleteEvent extends GenericEvent {
/**
diff --git a/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php b/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php
new file mode 100644
index 00000000000..fd1bec42abf
--- /dev/null
+++ b/lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php
@@ -0,0 +1,107 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.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 OCP\Collaboration\AutoComplete;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 28.0.0
+ */
+class AutoCompleteFilterEvent extends Event {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(
+ protected array $results,
+ protected string $search,
+ protected ?string $itemType,
+ protected ?string $itemId,
+ protected ?string $sorter,
+ protected array $shareTypes,
+ protected int $limit,
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getResults(): array {
+ return $this->results;
+ }
+
+ /**
+ * @param array $results
+ * @since 28.0.0
+ */
+ public function setResults(array $results): void {
+ $this->results = $results;
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getSearchTerm(): string {
+ return $this->search;
+ }
+
+ /**
+ * @return int[] List of `\OCP\Share\IShare::TYPE_*` constants
+ * @since 28.0.0
+ */
+ public function getShareTypes(): array {
+ return $this->shareTypes;
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getItemType(): ?string {
+ return $this->itemType;
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getItemId(): ?string {
+ return $this->itemId;
+ }
+
+ /**
+ * @return ?string List of desired sort identifiers, top priority first. When multiple are given they are joined with a pipe: `commenters|share-recipients`
+ * @since 28.0.0
+ */
+ public function getSorter(): ?string {
+ return $this->sorter;
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getLimit(): int {
+ return $this->limit;
+ }
+}
diff --git a/lib/public/Collaboration/Reference/IReference.php b/lib/public/Collaboration/Reference/IReference.php
index 1b9157fd9b1..c0cfa72c36d 100644
--- a/lib/public/Collaboration/Reference/IReference.php
+++ b/lib/public/Collaboration/Reference/IReference.php
@@ -126,4 +126,11 @@ interface IReference extends JsonSerializable {
* @since 25.0.0
*/
public function getOpenGraphObject(): array;
+
+ /**
+ * @return array{richObjectType: string, richObject: array<string, mixed>, openGraphObject: array{id: string, name: string, description: ?string, thumb: ?string, link: string}, accessible: bool}
+ *
+ * @since 25.0.0
+ */
+ public function jsonSerialize(): array;
}
diff --git a/lib/public/Collaboration/Reference/Reference.php b/lib/public/Collaboration/Reference/Reference.php
index 0dcb665713c..8dc88edbeac 100644
--- a/lib/public/Collaboration/Reference/Reference.php
+++ b/lib/public/Collaboration/Reference/Reference.php
@@ -242,7 +242,7 @@ class Reference implements IReference {
* @since 25.0.0
* @return array{richObjectType: string, richObject: array<string, mixed>, openGraphObject: OpenGraphObject, accessible: bool}
*/
- public function jsonSerialize() {
+ public function jsonSerialize(): array {
return [
'richObjectType' => $this->getRichObjectType(),
'richObject' => $this->getRichObject(),
diff --git a/lib/public/Contacts/ContactsMenu/IBulkProvider.php b/lib/public/Contacts/ContactsMenu/IBulkProvider.php
new file mode 100644
index 00000000000..43d727e2ec3
--- /dev/null
+++ b/lib/public/Contacts/ContactsMenu/IBulkProvider.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 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 OCP\Contacts\ContactsMenu;
+
+/**
+ * Process contacts menu entries in bulk
+ *
+ * @since 28.0
+ */
+interface IBulkProvider {
+ /**
+ * @since 28.0
+ * @param list<IEntry> $entries
+ * @return void
+ */
+ public function process(array $entries): void;
+}
diff --git a/lib/public/Contacts/ContactsMenu/IEntry.php b/lib/public/Contacts/ContactsMenu/IEntry.php
index 9d78b0c8f57..1307e2c74f7 100644
--- a/lib/public/Contacts/ContactsMenu/IEntry.php
+++ b/lib/public/Contacts/ContactsMenu/IEntry.php
@@ -54,6 +54,20 @@ interface IEntry extends JsonSerializable {
public function addAction(IAction $action): void;
/**
+ * Set the (system) contact's user status
+ *
+ * @since 28.0
+ * @param string $status
+ * @param string $statusMessage
+ * @param string|null $icon
+ * @return void
+ */
+ public function setStatus(string $status,
+ string $statusMessage = null,
+ int $statusMessageTimestamp = null,
+ string $icon = null): void;
+
+ /**
* Get an arbitrary property from the contact
*
* @since 12.0
diff --git a/lib/public/Contacts/ContactsMenu/IProvider.php b/lib/public/Contacts/ContactsMenu/IProvider.php
index c05f2707c18..200ee0b1fea 100644
--- a/lib/public/Contacts/ContactsMenu/IProvider.php
+++ b/lib/public/Contacts/ContactsMenu/IProvider.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
*
@@ -23,6 +26,10 @@
namespace OCP\Contacts\ContactsMenu;
/**
+ * Process contacts menu entries
+ *
+ * @see IBulkProvider for providers that work with the full dataset at once
+ *
* @since 12.0
*/
interface IProvider {
diff --git a/lib/public/Contacts/IManager.php b/lib/public/Contacts/IManager.php
index 6ca349b95d0..97fa2e61529 100644
--- a/lib/public/Contacts/IManager.php
+++ b/lib/public/Contacts/IManager.php
@@ -107,22 +107,22 @@ interface IManager {
* This function can be used to delete the contact identified by the given id
*
* @param int $id the unique identifier to a contact
- * @param string $address_book_key identifier of the address book in which the contact shall be deleted
+ * @param string $addressBookKey identifier of the address book in which the contact shall be deleted
* @return bool successful or not
* @since 6.0.0
*/
- public function delete($id, $address_book_key);
+ public function delete($id, $addressBookKey);
/**
* This function is used to create a new contact if 'id' is not given or not present.
* Otherwise the contact will be updated by replacing the entire data set.
*
* @param array $properties this array if key-value-pairs defines a contact
- * @param string $address_book_key identifier of the address book in which the contact shall be created or updated
- * @return array an array representing the contact just created or updated
+ * @param string $addressBookKey identifier of the address book in which the contact shall be created or updated
+ * @return ?array an array representing the contact just created or updated
* @since 6.0.0
*/
- public function createOrUpdate($properties, $address_book_key);
+ public function createOrUpdate($properties, $addressBookKey);
/**
* Check if contacts are available (e.g. contacts app enabled)
@@ -135,20 +135,19 @@ interface IManager {
/**
* Registers an address book
*
- * @param \OCP\IAddressBook $address_book
* @return void
* @since 6.0.0
*/
- public function registerAddressBook(\OCP\IAddressBook $address_book);
+ public function registerAddressBook(\OCP\IAddressBook $addressBook);
/**
* Unregisters an address book
*
- * @param \OCP\IAddressBook $address_book
+ * @param \OCP\IAddressBook $addressBook
* @return void
* @since 6.0.0
*/
- public function unregisterAddressBook(\OCP\IAddressBook $address_book);
+ public function unregisterAddressBook(\OCP\IAddressBook $addressBook);
/**
* In order to improve lazy loading a closure can be registered which will be called in case
diff --git a/lib/public/DB/QueryBuilder/IQueryBuilder.php b/lib/public/DB/QueryBuilder/IQueryBuilder.php
index 63fdfb65971..dba5f390d6e 100644
--- a/lib/public/DB/QueryBuilder/IQueryBuilder.php
+++ b/lib/public/DB/QueryBuilder/IQueryBuilder.php
@@ -27,6 +27,7 @@
*/
namespace OCP\DB\QueryBuilder;
+use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
use OCP\DB\Exception;
@@ -72,11 +73,11 @@ interface IQueryBuilder {
/**
* @since 9.0.0
*/
- public const PARAM_INT_ARRAY = Connection::PARAM_INT_ARRAY;
+ public const PARAM_INT_ARRAY = ArrayParameterType::INTEGER;
/**
* @since 9.0.0
*/
- public const PARAM_STR_ARRAY = Connection::PARAM_STR_ARRAY;
+ public const PARAM_STR_ARRAY = ArrayParameterType::STRING;
/**
* @since 24.0.0 Indicates how many rows can be deleted at once with MySQL
@@ -907,7 +908,7 @@ interface IQueryBuilder {
* @link http://www.zetacomponents.org
*
* @param mixed $value
- * @param mixed $type
+ * @param self::PARAM_* $type
* @param string $placeHolder The name to bind with. The string must start with a colon ':'.
*
* @return IParameter
@@ -935,7 +936,7 @@ interface IQueryBuilder {
* </code>
*
* @param mixed $value
- * @param integer $type
+ * @param self::PARAM_* $type
*
* @return IParameter
* @since 8.2.0
diff --git a/lib/public/Dashboard/IManager.php b/lib/public/Dashboard/IManager.php
index 77bff7b34ff..135fd4b4514 100644
--- a/lib/public/Dashboard/IManager.php
+++ b/lib/public/Dashboard/IManager.php
@@ -40,7 +40,7 @@ interface IManager {
/**
* @since 20.0.0
*
- * @return IWidget[]
+ * @return array<string, IWidget>
*/
public function getWidgets(): array;
}
diff --git a/lib/public/Federation/ICloudFederationNotification.php b/lib/public/Federation/ICloudFederationNotification.php
index 2b51b2bc8db..26a98027d5e 100644
--- a/lib/public/Federation/ICloudFederationNotification.php
+++ b/lib/public/Federation/ICloudFederationNotification.php
@@ -34,8 +34,8 @@ interface ICloudFederationNotification {
*
* @param string $notificationType (e.g. SHARE_ACCEPTED)
* @param string $resourceType (e.g. file, calendar, contact,...)
- * @param $providerId id of the share
- * @param array $notification , payload of the notification
+ * @param string $providerId id of the share
+ * @param array $notification payload of the notification
*
* @since 14.0.0
*/
diff --git a/lib/public/Files/Config/ICachedMountInfo.php b/lib/public/Files/Config/ICachedMountInfo.php
index dafd2423fdc..78a92874275 100644
--- a/lib/public/Files/Config/ICachedMountInfo.php
+++ b/lib/public/Files/Config/ICachedMountInfo.php
@@ -84,4 +84,12 @@ interface ICachedMountInfo {
* @since 24.0.0
*/
public function getMountProvider(): string;
+
+ /**
+ * Get a key that uniquely identifies the mount
+ *
+ * @return string
+ * @since 28.0.0
+ */
+ public function getKey(): string;
}
diff --git a/lib/public/Files/ConnectionLostException.php b/lib/public/Files/ConnectionLostException.php
new file mode 100644
index 00000000000..8e5deb99b46
--- /dev/null
+++ b/lib/public/Files/ConnectionLostException.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @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 OCP\Files;
+
+/**
+ * Exception for lost connection with the
+ * @since 25.0.11
+ */
+class ConnectionLostException extends \RuntimeException {
+}
diff --git a/lib/public/Files/FileInfo.php b/lib/public/Files/FileInfo.php
index 83ae4adef92..817b03dfc65 100644
--- a/lib/public/Files/FileInfo.php
+++ b/lib/public/Files/FileInfo.php
@@ -6,6 +6,7 @@
* @author Felix Heidecke <felix@heidecke.me>
* @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>
@@ -299,4 +300,21 @@ interface FileInfo {
* @since 18.0.0
*/
public function getUploadTime(): int;
+
+ /**
+ * Get the fileid or the parent folder
+ * or -1 if this item has no parent folder (because it is the root)
+ *
+ * @return int
+ * @since 28.0.0
+ */
+ public function getParentId(): int;
+
+ /**
+ * Get the metadata, if available
+ *
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ * @since 28.0.0
+ */
+ public function getMetadata(): array;
}
diff --git a/lib/public/Files/IHomeStorage.php b/lib/public/Files/IHomeStorage.php
index 7eb3ffc4a24..1fea80f2d87 100644
--- a/lib/public/Files/IHomeStorage.php
+++ b/lib/public/Files/IHomeStorage.php
@@ -27,6 +27,7 @@
namespace OCP\Files;
use OCP\Files\Storage\IStorage;
+use OCP\IUser;
/**
* Interface IHomeStorage
@@ -34,4 +35,11 @@ use OCP\Files\Storage\IStorage;
* @since 7.0.0
*/
interface IHomeStorage extends IStorage {
+ /**
+ * Get the user for this home storage
+ *
+ * @return IUser
+ * @since 28.0.0
+ */
+ public function getUser(): IUser;
}
diff --git a/lib/public/Files/IRootFolder.php b/lib/public/Files/IRootFolder.php
index 1fee0b3595e..44f0ba5f2e1 100644
--- a/lib/public/Files/IRootFolder.php
+++ b/lib/public/Files/IRootFolder.php
@@ -26,7 +26,9 @@ namespace OCP\Files;
use OC\Hooks\Emitter;
use OC\User\NoUserException;
+use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Node as INode;
/**
* Interface IRootFolder
@@ -65,6 +67,16 @@ interface IRootFolder extends Folder, Emitter {
public function getMountsIn(string $mountPoint): array;
/**
+ * Create a `Node` for a file or folder from the cache entry and mountpoint
+ *
+ * @param ICacheEntry $cacheEntry
+ * @param IMountPoint $mountPoint
+ * @return Node
+ * @since 28.0.0
+ */
+ public function getNodeFromCacheEntryAndMount(ICacheEntry $cacheEntry, IMountPoint $mountPoint): INode;
+
+ /**
* @since 28.0.0
*/
public function getMount(string $mountPoint): IMountPoint;
diff --git a/lib/public/Files/Mount/IMountManager.php b/lib/public/Files/Mount/IMountManager.php
index a55e5758199..df2cc4c6209 100644
--- a/lib/public/Files/Mount/IMountManager.php
+++ b/lib/public/Files/Mount/IMountManager.php
@@ -26,6 +26,8 @@ declare(strict_types=1);
*/
namespace OCP\Files\Mount;
+use OCP\Files\Config\ICachedMountInfo;
+
/**
* Interface IMountManager
*
@@ -106,4 +108,14 @@ interface IMountManager {
* @since 8.2.0
*/
public function findByNumericId(int $id): array;
+
+ /**
+ * Return the mount matching a cached mount info (or mount file info)
+ *
+ * @param ICachedMountInfo $info
+ *
+ * @return IMountPoint|null
+ * @since 28.0.0
+ */
+ public function getMountFromMountInfo(ICachedMountInfo $info): ?IMountPoint;
}
diff --git a/lib/public/Files/Search/ISearchComparison.php b/lib/public/Files/Search/ISearchComparison.php
index 8ebaeced304..d7313fbaf2a 100644
--- a/lib/public/Files/Search/ISearchComparison.php
+++ b/lib/public/Files/Search/ISearchComparison.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>
*
* @license GNU AGPL version 3 or any later version
@@ -43,7 +44,7 @@ interface ISearchComparison extends ISearchOperator {
* @return string
* @since 12.0.0
*/
- public function getType();
+ public function getType(): string;
/**
* Get the name of the field to compare with
@@ -53,13 +54,21 @@ interface ISearchComparison extends ISearchOperator {
* @return string
* @since 12.0.0
*/
- public function getField();
+ public function getField(): string;
+
+ /**
+ * extra means data are not related to the main files table
+ *
+ * @return string
+ * @since 28.0.0
+ */
+ public function getExtra(): string;
/**
* Get the value to compare the field with
*
- * @return string|integer|\DateTime
+ * @return string|integer|bool|\DateTime
* @since 12.0.0
*/
- public function getValue();
+ public function getValue(): string|int|bool|\DateTime;
}
diff --git a/lib/public/Files/Search/ISearchOrder.php b/lib/public/Files/Search/ISearchOrder.php
index 3b9e6e6713a..5b73e7b102c 100644
--- a/lib/public/Files/Search/ISearchOrder.php
+++ b/lib/public/Files/Search/ISearchOrder.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>
*
* @license GNU AGPL version 3 or any later version
@@ -38,7 +39,7 @@ interface ISearchOrder {
* @return string
* @since 12.0.0
*/
- public function getDirection();
+ public function getDirection(): string;
/**
* The field to sort on
@@ -46,7 +47,15 @@ interface ISearchOrder {
* @return string
* @since 12.0.0
*/
- public function getField();
+ public function getField(): string;
+
+ /**
+ * extra means data are not related to the main files table
+ *
+ * @return string
+ * @since 28.0.0
+ */
+ public function getExtra(): string;
/**
* Apply the sorting on 2 FileInfo objects
diff --git a/lib/public/Files/Template/TemplateFileCreator.php b/lib/public/Files/Template/TemplateFileCreator.php
index 43e96b6f21b..0c2f7a115a2 100644
--- a/lib/public/Files/Template/TemplateFileCreator.php
+++ b/lib/public/Files/Template/TemplateFileCreator.php
@@ -42,6 +42,7 @@ final class TemplateFileCreator implements \JsonSerializable {
protected $order = 100;
/**
* @since 27.0.0
+ * @deprecated 28.0.0
*/
protected string $actionLabel = '';
diff --git a/lib/public/FilesMetadata/AMetadataEvent.php b/lib/public/FilesMetadata/AMetadataEvent.php
new file mode 100644
index 00000000000..8cb8ea8d1b8
--- /dev/null
+++ b/lib/public/FilesMetadata/AMetadataEvent.php
@@ -0,0 +1,68 @@
+<?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 OCP\FilesMetadata;
+
+use OCP\EventDispatcher\Event;
+use OCP\Files\Node;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+
+/**
+ * @since 28.0.0
+ */
+abstract class AMetadataEvent extends Event {
+ /**
+ * @param Node $node
+ * @param IFilesMetadata $metadata
+ * @since 28.0.0
+ */
+ public function __construct(
+ protected Node $node,
+ protected IFilesMetadata $metadata
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * returns related node
+ *
+ * @return Node
+ * @since 28.0.0
+ */
+ public function getNode(): Node {
+ return $this->node;
+ }
+
+ /**
+ * returns metadata. if known, it already contains data from the database.
+ * If the object is modified using its setters, changes are stored in database at the end of the event.
+ *
+ * @return IFilesMetadata
+ * @since 28.0.0
+ */
+ public function getMetadata(): IFilesMetadata {
+ return $this->metadata;
+ }
+}
diff --git a/lib/public/FilesMetadata/Event/MetadataBackgroundEvent.php b/lib/public/FilesMetadata/Event/MetadataBackgroundEvent.php
new file mode 100644
index 00000000000..3d175994c32
--- /dev/null
+++ b/lib/public/FilesMetadata/Event/MetadataBackgroundEvent.php
@@ -0,0 +1,40 @@
+<?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 OCP\FilesMetadata\Event;
+
+use OCP\FilesMetadata\AMetadataEvent;
+
+/**
+ * MetadataBackgroundEvent is an event similar to MetadataLiveEvent but dispatched
+ * on a background thread instead of live thread. Meaning there is no limit to
+ * the time required for the generation of your metadata.
+ *
+ * @see AMetadataEvent::getMetadata()
+ * @see AMetadataEvent::getNode()
+ * @since 28.0.0
+ */
+class MetadataBackgroundEvent extends AMetadataEvent {
+}
diff --git a/lib/public/FilesMetadata/Event/MetadataLiveEvent.php b/lib/public/FilesMetadata/Event/MetadataLiveEvent.php
new file mode 100644
index 00000000000..a89692fde92
--- /dev/null
+++ b/lib/public/FilesMetadata/Event/MetadataLiveEvent.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 OCP\FilesMetadata\Event;
+
+use OCP\FilesMetadata\AMetadataEvent;
+
+/**
+ * MetadataLiveEvent is an event initiated when a file is created or updated.
+ * The app contains the Node related to the created/updated file, and a FilesMetadata that already
+ * contains the currently known metadata.
+ *
+ * Setting new metadata, or modifying already existing metadata with different value, will trigger
+ * the save of the metadata in the database.
+ *
+ * @see AMetadataEvent::getMetadata()
+ * @see AMetadataEvent::getNode()
+ * @see MetadataLiveEvent::requestBackgroundJob()
+ * @since 28.0.0
+ */
+class MetadataLiveEvent extends AMetadataEvent {
+ private bool $runAsBackgroundJob = false;
+
+ /**
+ * For heavy process, call this method if your app prefers to update metadata on a
+ * background/cron job, instead of the live process.
+ * A similar MetadataBackgroundEvent will be broadcast on next cron tick.
+ *
+ * @return void
+ * @since 28.0.0
+ */
+ public function requestBackgroundJob(): void {
+ $this->runAsBackgroundJob = true;
+ }
+
+ /**
+ * return true if any app that catch this event requested a re-run as background job
+ *
+ * @return bool
+ * @since 28.0.0
+ */
+ public function isRunAsBackgroundJobRequested(): bool {
+ return $this->runAsBackgroundJob;
+ }
+}
diff --git a/lib/public/FilesMetadata/Event/MetadataNamedEvent.php b/lib/public/FilesMetadata/Event/MetadataNamedEvent.php
new file mode 100644
index 00000000000..f8cfcf9bd01
--- /dev/null
+++ b/lib/public/FilesMetadata/Event/MetadataNamedEvent.php
@@ -0,0 +1,74 @@
+<?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 OCP\FilesMetadata\Event;
+
+use OCP\Files\Node;
+use OCP\FilesMetadata\AMetadataEvent;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+
+/**
+ * MetadataNamedEvent is an event similar to MetadataBackgroundEvent completed with a target name,
+ * used to limit the refresh of metadata only listeners capable of filtering themselves out.
+ *
+ * Meaning that when using this event, your app must implement a filter on the event's registered
+ * name returned by getName()
+ *
+ * This event is mostly triggered when a registered name is added to the files scan
+ * i.e. ./occ files:scan --generate-metadata [name]
+ *
+ * @see AMetadataEvent::getMetadata()
+ * @see AMetadataEvent::getNode()
+ * @see MetadataNamedEvent::getName()
+ * @since 28.0.0
+ */
+class MetadataNamedEvent extends AMetadataEvent {
+ /**
+ * @param Node $node
+ * @param IFilesMetadata $metadata
+ * @param string $name name assigned to the event
+ *
+ * @since 28.0.0
+ */
+ public function __construct(
+ Node $node,
+ IFilesMetadata $metadata,
+ private string $name = ''
+ ) {
+ parent::__construct($node, $metadata);
+ }
+
+ /**
+ * get the assigned name for the event.
+ * This is used to know if your app is the called one when running the
+ * ./occ files:scan --generate-metadata [name]
+ *
+ * @return string
+ * @since 28.0.0
+ */
+ public function getName(): string {
+ return $this->name;
+ }
+}
diff --git a/lib/public/FilesMetadata/Exceptions/FilesMetadataException.php b/lib/public/FilesMetadata/Exceptions/FilesMetadataException.php
new file mode 100644
index 00000000000..e3f75a7a7af
--- /dev/null
+++ b/lib/public/FilesMetadata/Exceptions/FilesMetadataException.php
@@ -0,0 +1,34 @@
+<?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 OCP\FilesMetadata\Exceptions;
+
+use Exception;
+
+/**
+ * @since 28.0.0
+ */
+class FilesMetadataException extends Exception {
+}
diff --git a/lib/public/FilesMetadata/Exceptions/FilesMetadataKeyFormatException.php b/lib/public/FilesMetadata/Exceptions/FilesMetadataKeyFormatException.php
new file mode 100644
index 00000000000..0083e985a5e
--- /dev/null
+++ b/lib/public/FilesMetadata/Exceptions/FilesMetadataKeyFormatException.php
@@ -0,0 +1,32 @@
+<?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 OCP\FilesMetadata\Exceptions;
+
+/**
+ * @since 28.0.0
+ */
+class FilesMetadataKeyFormatException extends FilesMetadataException {
+}
diff --git a/lib/public/FilesMetadata/Exceptions/FilesMetadataNotFoundException.php b/lib/public/FilesMetadata/Exceptions/FilesMetadataNotFoundException.php
new file mode 100644
index 00000000000..9c03c5ba370
--- /dev/null
+++ b/lib/public/FilesMetadata/Exceptions/FilesMetadataNotFoundException.php
@@ -0,0 +1,32 @@
+<?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 OCP\FilesMetadata\Exceptions;
+
+/**
+ * @since 28.0.0
+ */
+class FilesMetadataNotFoundException extends FilesMetadataException {
+}
diff --git a/lib/public/FilesMetadata/Exceptions/FilesMetadataTypeException.php b/lib/public/FilesMetadata/Exceptions/FilesMetadataTypeException.php
new file mode 100644
index 00000000000..1d134c67ecf
--- /dev/null
+++ b/lib/public/FilesMetadata/Exceptions/FilesMetadataTypeException.php
@@ -0,0 +1,32 @@
+<?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 OCP\FilesMetadata\Exceptions;
+
+/**
+ * @since 28.0.0
+ */
+class FilesMetadataTypeException extends FilesMetadataException {
+}
diff --git a/lib/public/FilesMetadata/IFilesMetadataManager.php b/lib/public/FilesMetadata/IFilesMetadataManager.php
new file mode 100644
index 00000000000..de6fc62ba94
--- /dev/null
+++ b/lib/public/FilesMetadata/IFilesMetadataManager.php
@@ -0,0 +1,158 @@
+<?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 OCP\FilesMetadata;
+
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Files\Node;
+use OCP\FilesMetadata\Exceptions\FilesMetadataException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+
+/**
+ * Manager for FilesMetadata; manage files' metadata.
+ *
+ * @since 28.0.0
+ */
+interface IFilesMetadataManager {
+ /** @since 28.0.0 */
+ public const PROCESS_LIVE = 1;
+ /** @since 28.0.0 */
+ public const PROCESS_BACKGROUND = 2;
+ /** @since 28.0.0 */
+ public const PROCESS_NAMED = 4;
+
+ /**
+ * initiate the process of refreshing the metadata in relation to a node
+ * usually, this process:
+ * - get current metadata from database, if available, or create a new one
+ * - dispatch a MetadataLiveEvent,
+ * - save new metadata in database, if metadata have been changed during the event
+ * - refresh metadata indexes if needed,
+ * - prep a new cronjob if an app request it during the event,
+ *
+ * @param Node $node related node
+ * @param int $process type of process
+ * @param string $namedEvent limit process to a named event
+ *
+ * @return IFilesMetadata
+ * @see self::PROCESS_BACKGROUND
+ * @see self::PROCESS_LIVE
+ * @see self::PROCESS_NAMED
+ * @since 28.0.0
+ */
+ public function refreshMetadata(
+ Node $node,
+ int $process = self::PROCESS_LIVE,
+ string $namedEvent = ''
+ ): IFilesMetadata;
+
+ /**
+ * returns metadata from a file id
+ *
+ * @param int $fileId file id
+ * @param boolean $generate Generate if metadata does not exist
+ *
+ * @return IFilesMetadata
+ * @throws FilesMetadataNotFoundException if not found
+ * @since 28.0.0
+ */
+ public function getMetadata(int $fileId, bool $generate = false): IFilesMetadata;
+
+ /**
+ * save metadata to database and refresh indexes.
+ * metadata are saved if new data are available.
+ * on update, a check on syncToken is done to avoid conflict (race condition)
+ *
+ * @param IFilesMetadata $filesMetadata
+ *
+ * @throws FilesMetadataException if metadata seems malformed
+ * @since 28.0.0
+ */
+ public function saveMetadata(IFilesMetadata $filesMetadata): void;
+
+ /**
+ * delete metadata and its indexes
+ *
+ * @param int $fileId file id
+ *
+ * @return void
+ * @since 28.0.0
+ */
+ public function deleteMetadata(int $fileId): void;
+
+ /**
+ * generate and return a MetadataQuery to help building sql queries
+ *
+ * @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
+ *
+ * @return IMetadataQuery|null NULL if table are not set yet or never used
+ * @see IMetadataQuery
+ * @since 28.0.0
+ */
+ public function getMetadataQuery(
+ IQueryBuilder $qb,
+ string $fileTableAlias,
+ string $fileIdField
+ ): ?IMetadataQuery;
+
+ /**
+ * returns all type of metadata currently available.
+ * The list is stored in a IFilesMetadata with null values but correct type.
+ *
+ * @return IFilesMetadata
+ * @since 28.0.0
+ */
+ public function getKnownMetadata(): IFilesMetadata;
+
+ /**
+ * initiate a metadata key with its type.
+ * The call is mandatory before using the metadata property in a webdav request.
+ * It is not needed to only use this method when the app is enabled: the method can be
+ * called each time during the app loading as the metadata will only be initiated if not known
+ *
+ * @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
+ *
+ * @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
+ * @since 28.0.0
+ */
+ public function initMetadata(string $key, string $type, bool $indexed, int $editPermission): void;
+}
diff --git a/lib/public/FilesMetadata/IMetadataQuery.php b/lib/public/FilesMetadata/IMetadataQuery.php
new file mode 100644
index 00000000000..c1c649ac243
--- /dev/null
+++ b/lib/public/FilesMetadata/IMetadataQuery.php
@@ -0,0 +1,92 @@
+<?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 OCP\FilesMetadata;
+
+use OCP\FilesMetadata\Model\IFilesMetadata;
+
+/**
+ * Model that help building queries with metadata and metadata indexes
+ *
+ * @since 28.0.0
+ */
+interface IMetadataQuery {
+ /** @since 28.0.0 */
+ public const EXTRA = 'metadata';
+
+ /**
+ * Add metadata linked to file id to the query
+ *
+ * @see self::extractMetadata()
+ * @since 28.0.0
+ */
+ public function retrieveMetadata(): void;
+
+ /**
+ * extract metadata from a result row
+ *
+ * @param array $row result row
+ *
+ * @return IFilesMetadata metadata
+ * @see self::retrieveMetadata()
+ * @since 28.0.0
+ */
+ public function extractMetadata(array $row): IFilesMetadata;
+
+ /**
+ * join the metadata_index table, based on a metadataKey.
+ * This will prep the query for condition based on this specific metadataKey.
+ * If a link to the metadataKey already exists, returns known alias.
+ *
+ * TODO: investigate how to support a search done on multiple values for same key (AND).
+ *
+ * @param string $metadataKey metadata key
+ * @param bool $enforce limit the request only to existing metadata
+ *
+ * @return string generated table alias
+ * @since 28.0.0
+ */
+ public function joinIndex(string $metadataKey, bool $enforce = false): string;
+
+ /**
+ * returns the name of the field for metadata key to be used in query expressions
+ *
+ * @param string $metadataKey metadata key
+ *
+ * @return string table field
+ * @since 28.0.0
+ */
+ public function getMetadataKeyField(string $metadataKey): string;
+
+ /**
+ * returns the name of the field for metadata string value to be used in query expressions
+ *
+ * @param string $metadataKey metadata key
+ *
+ * @return string table field
+ * @since 28.0.0
+ */
+ public function getMetadataValueField(string $metadataKey): string;
+}
diff --git a/lib/public/FilesMetadata/Model/IFilesMetadata.php b/lib/public/FilesMetadata/Model/IFilesMetadata.php
new file mode 100644
index 00000000000..7697a2f37ad
--- /dev/null
+++ b/lib/public/FilesMetadata/Model/IFilesMetadata.php
@@ -0,0 +1,367 @@
+<?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 OCP\FilesMetadata\Model;
+
+use JsonSerializable;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+
+/**
+ * Model that represent metadata linked to a specific file.
+ *
+ * Example of json stored in the database
+ * {
+ * "mymeta": {
+ * "value": "this is a test",
+ * "type": "string",
+ * "indexed": false,
+ * "editPermission": 1
+ * },
+ * "myapp-anothermeta": {
+ * "value": 42,
+ * "type": "int",
+ * "indexed": true,
+ * "editPermission": 0
+ * }
+ * }
+ *
+ * @see IMetadataValueWrapper
+ * @since 28.0.0
+ */
+interface IFilesMetadata extends JsonSerializable {
+ /**
+ * returns the file id linked to this metadata
+ *
+ * @return int related file id
+ * @since 28.0.0
+ */
+ public function getFileId(): int;
+
+ /**
+ * returns last time metadata were updated in the database
+ *
+ * @return int timestamp
+ * @since 28.0.0
+ */
+ public function lastUpdateTimestamp(): int;
+
+ /**
+ * returns the token known at the time the metadata were extracted from database
+ *
+ * @return string token
+ * @since 28.0.0
+ */
+ public function getSyncToken(): string;
+
+ /**
+ * returns all current metadata keys
+ *
+ * @return string[] list of keys
+ * @since 28.0.0
+ */
+ public function getKeys(): array;
+
+ /**
+ * returns true if search metadata key exists
+ *
+ * @param string $needle metadata key to search
+ *
+ * @return bool TRUE if key exist
+ * @since 28.0.0
+ */
+ public function hasKey(string $needle): bool;
+
+ /**
+ * return the list of metadata keys set as indexed
+ *
+ * @return string[] list of indexes
+ * @since 28.0.0
+ */
+ public function getIndexes(): array;
+
+ /**
+ * returns true if key exists and is set as indexed
+ *
+ * @param string $key metadata key
+ *
+ * @return bool
+ * @since 28.0.0
+ */
+ public function isIndex(string $key): bool;
+
+ /**
+ * set remote edit permission
+ * (Webdav PROPPATCH)
+ *
+ * @param string $key metadata key
+ * @param int $permission remote edit permission
+ *
+ * @since 28.0.0
+ */
+ public function setEditPermission(string $key, int $permission): void;
+
+ /**
+ * returns remote edit permission
+ * (Webdav PROPPATCH)
+ *
+ * @param string $key metadata key
+ *
+ * @return int
+ * @since 28.0.0
+ */
+ public function getEditPermission(string $key): int;
+
+ /**
+ * returns string value for a metadata key
+ *
+ * @param string $key metadata key
+ *
+ * @return string metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getString(string $key): string;
+
+ /**
+ * returns int value for a metadata key
+ *
+ * @param string $key metadata key
+ *
+ * @return int metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getInt(string $key): int;
+
+ /**
+ * returns float value for a metadata key
+ *
+ * @param string $key metadata key
+ *
+ * @return float metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getFloat(string $key): float;
+
+ /**
+ * returns bool value for a metadata key
+ *
+ * @param string $key metadata key
+ *
+ * @return bool metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getBool(string $key): bool;
+
+ /**
+ * returns array for a metadata key
+ *
+ * @param string $key metadata key
+ *
+ * @return array metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getArray(string $key): array;
+
+ /**
+ * returns string[] value for a metadata key
+ *
+ * @param string $key metadata key
+ *
+ * @return string[] metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getStringList(string $key): array;
+
+ /**
+ * returns int[] value for a metadata key
+ *
+ * @param string $key metadata key
+ *
+ * @return int[] metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getIntList(string $key): array;
+
+ /**
+ * returns the value type of the metadata (string, int, ...)
+ *
+ * @param string $key metadata key
+ *
+ * @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;
+
+ /**
+ * set a metadata key/value pair for string value
+ *
+ * @param string $key metadata key
+ * @param string $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setString(string $key, string $value, bool $index = false): self;
+
+ /**
+ * set a metadata key/value pair for int value
+ *
+ * @param string $key metadata key
+ * @param int $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setInt(string $key, int $value, bool $index = false): self;
+
+ /**
+ * set a metadata key/value pair for float value
+ *
+ * @param string $key metadata key
+ * @param float $value metadata value
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setFloat(string $key, float $value): self;
+
+ /**
+ * set a metadata key/value pair for bool value
+ *
+ * @param string $key metadata key
+ * @param bool $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setBool(string $key, bool $value, bool $index = false): self;
+
+ /**
+ * set a metadata key/value pair for array
+ *
+ * @param string $key metadata key
+ * @param array $value metadata value
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setArray(string $key, array $value): self;
+
+ /**
+ * set a metadata key/value pair for list of string
+ *
+ * @param string $key metadata key
+ * @param string[] $value metadata value
+ * @param bool $index set TRUE if each values from the list must be indexed
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setStringList(string $key, array $value, bool $index = false): self;
+
+ /**
+ * set a metadata key/value pair for list of int
+ *
+ * @param string $key metadata key
+ * @param int[] $value metadata value
+ * @param bool $index set TRUE if each values from the list must be indexed
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setIntList(string $key, array $value, bool $index = false): self;
+
+ /**
+ * unset a metadata
+ *
+ * @param string $key metadata key
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function unset(string $key): self;
+
+ /**
+ * unset metadata with key starting with prefix
+ *
+ * @param string $keyPrefix metadata key prefix
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function removeStartsWith(string $keyPrefix): self;
+
+ /**
+ * returns true if object have been updated since last import
+ *
+ * @return bool TRUE if metadata have been modified
+ * @since 28.0.0
+ */
+ public function updated(): bool;
+
+ /**
+ * returns metadata in a simple array with METADATA_KEY => METADATA_VALUE
+ *
+ * @return array metadata
+ * @since 28.0.0
+ */
+ public function asArray(): array;
+
+ /**
+ * deserialize the object from a json
+ *
+ * @param array $data serialized version of the object
+ *
+ * @return self
+ * @see jsonSerialize
+ * @since 28.0.0
+ */
+ public function import(array $data): self;
+}
diff --git a/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php b/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php
new file mode 100644
index 00000000000..d34cd070c8b
--- /dev/null
+++ b/lib/public/FilesMetadata/Model/IMetadataValueWrapper.php
@@ -0,0 +1,334 @@
+<?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 OCP\FilesMetadata\Model;
+
+use JsonSerializable;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+
+/**
+ * Model that store the value of a single metadata.
+ * It stores the value, its type and the index status.
+ *
+ * @see IFilesMetadata
+ * @since 28.0.0
+ */
+interface IMetadataValueWrapper extends JsonSerializable {
+ /** @since 28.0.0 */
+ public const TYPE_STRING = 'string';
+ /** @since 28.0.0 */
+ public const TYPE_INT = 'int';
+ /** @since 28.0.0 */
+ public const TYPE_FLOAT = 'float';
+ /** @since 28.0.0 */
+ public const TYPE_BOOL = 'bool';
+ /** @since 28.0.0 */
+ public const TYPE_ARRAY = 'array';
+ /** @since 28.0.0 */
+ public const TYPE_STRING_LIST = 'string[]';
+ /** @since 28.0.0 */
+ public const TYPE_INT_LIST = 'int[]';
+
+ /** @since 28.0.0 */
+ public const EDIT_FORBIDDEN = 0;
+ /** @since 28.0.0 */
+ public const EDIT_REQ_OWNERSHIP = 1;
+ /** @since 28.0.0 */
+ public const EDIT_REQ_WRITE_PERMISSION = 2;
+ /** @since 28.0.0 */
+ public const EDIT_REQ_READ_PERMISSION = 3;
+
+
+ /**
+ * Unless a call of import() to deserialize an object is expected, a valid value type is needed here.
+ *
+ * @param string $type 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 __construct(string $type);
+
+ /**
+ * returns the value type
+ *
+ * @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;
+
+ /**
+ * returns if the set value type is the one expected
+ *
+ * @param string $type value type
+ *
+ * @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;
+
+ /**
+ * throws an exception if the type is not correctly set
+ *
+ * @param string $type value type
+ *
+ * @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;
+
+ /**
+ * set a string value
+ *
+ * @param string $value string to be set as value
+ *
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string
+ * @since 28.0.0
+ */
+ public function setValueString(string $value): self;
+
+ /**
+ * set a int value
+ *
+ * @param int $value int to be set as value
+ *
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int
+ * @since 28.0.0
+ */
+ public function setValueInt(int $value): self;
+
+ /**
+ * set a float value
+ *
+ * @param float $value float to be set as value
+ *
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a float
+ * @since 28.0.0
+ */
+ public function setValueFloat(float $value): self;
+
+ /**
+ * set a bool value
+ *
+ * @param bool $value bool to be set as value
+ *
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a bool
+ * @since 28.0.0
+ */
+ public function setValueBool(bool $value): self;
+
+ /**
+ * set an array value
+ *
+ * @param array $value array to be set as value
+ *
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an array
+ * @since 28.0.0
+ */
+ public function setValueArray(array $value): self;
+
+ /**
+ * set a string list value
+ *
+ * @param string[] $value string list to be set as value
+ *
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string list
+ * @since 28.0.0
+ */
+ public function setValueStringList(array $value): self;
+
+ /**
+ * set an int list value
+ *
+ * @param int[] $value int list to be set as value
+ *
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int list
+ * @since 28.0.0
+ */
+ public function setValueIntList(array $value): self;
+
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * get stored value
+ *
+ * @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;
+
+ /**
+ * @param bool $indexed TRUE to set the stored value as an indexed value
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setIndexed(bool $indexed): self;
+
+ /**
+ * returns if value is an indexed value
+ *
+ * @return bool TRUE if value is an indexed value
+ * @since 28.0.0
+ */
+ public function isIndexed(): bool;
+
+ /**
+ * set remote edit permission
+ * (Webdav PROPPATCH)
+ *
+ * @param int $permission edit permission
+ *
+ * @return self
+ * @since 28.0.0
+ */
+ public function setEditPermission(int $permission): self;
+
+ /**
+ * get remote edit permission
+ * (Webdav PROPPATCH)
+ *
+ * @return int edit permission
+ * @since 28.0.0
+ */
+ public function getEditPermission(): int;
+
+ /**
+ * deserialize the object from a json
+ *
+ * @param array $data serialized version of the object
+ *
+ * @return self
+ * @see jsonSerialize
+ * @since 28.0.0
+ */
+ public function import(array $data): self;
+}
diff --git a/lib/public/Group/Backend/ABackend.php b/lib/public/Group/Backend/ABackend.php
index 7f5cf732335..274b98655e4 100644
--- a/lib/public/Group/Backend/ABackend.php
+++ b/lib/public/Group/Backend/ABackend.php
@@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Carl Schwan <carl@carlschwan.eu>
*
* @license GNU AGPL version 3 or any later version
*
@@ -30,7 +31,7 @@ use OCP\GroupInterface;
/**
* @since 14.0.0
*/
-abstract class ABackend implements GroupInterface {
+abstract class ABackend implements GroupInterface, IBatchMethodsBackend {
/**
* @deprecated 14.0.0
* @since 14.0.0
@@ -65,4 +66,29 @@ abstract class ABackend implements GroupInterface {
return (bool)($actions & $implements);
}
+
+ /**
+ * @since 28.0.0
+ */
+ public function groupsExists(array $gids): array {
+ return array_values(array_filter(
+ $gids,
+ fn (string $gid): bool => $this->groupExists($gid),
+ ));
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getGroupsDetails(array $gids): array {
+ if (!($this instanceof IGroupDetailsBackend || $this->implementsActions(GroupInterface::GROUP_DETAILS))) {
+ throw new \Exception("Should not have been called");
+ }
+ /** @var IGroupDetailsBackend $this */
+ $groupData = [];
+ foreach ($gids as $gid) {
+ $groupData[$gid] = $this->getGroupDetails($gid);
+ }
+ return $groupData;
+ }
}
diff --git a/lib/public/Group/Backend/IBatchMethodsBackend.php b/lib/public/Group/Backend/IBatchMethodsBackend.php
new file mode 100644
index 00000000000..2af00e42825
--- /dev/null
+++ b/lib/public/Group/Backend/IBatchMethodsBackend.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Carl Schwan <carl@carlschwan.eu>
+ * @author Côme Chilliet <come.chilliet@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 OCP\Group\Backend;
+
+/**
+ * @brief Optional interface for group backends
+ * @since 28.0.0
+ */
+interface IBatchMethodsBackend {
+ /**
+ * @brief Batch method to check if a list of groups exists
+ *
+ * The default implementation in ABackend will just call groupExists in
+ * a loop. But a GroupBackend implementation should provides a more optimized
+ * override this method to provide a more optimized way to execute this operation.
+ *
+ * @param list<string> $gids
+ * @return list<string> the list of group that exists
+ * @since 28.0.0
+ */
+ public function groupsExists(array $gids): array;
+
+ /**
+ * @brief Batch method to get the group details of a list of groups
+ *
+ * The default implementation in ABackend will just call getGroupDetails in
+ * a loop. But a GroupBackend implementation should override this method
+ * to provide a more optimized way to execute this operation.
+ *
+ * @throw \RuntimeException if called on a backend that doesn't implements IGroupDetailsBackend
+ *
+ * @return array<string, array{displayName?: string}>
+ * @since 28.0.0
+ */
+ public function getGroupsDetails(array $gids): array;
+}
diff --git a/lib/public/Group/Backend/IGroupDetailsBackend.php b/lib/public/Group/Backend/IGroupDetailsBackend.php
index 4852f978195..851c10388e0 100644
--- a/lib/public/Group/Backend/IGroupDetailsBackend.php
+++ b/lib/public/Group/Backend/IGroupDetailsBackend.php
@@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Carl Schwan <carl@carlschwan.eu>
*
* @license GNU AGPL version 3 or any later version
*
@@ -26,10 +27,17 @@ declare(strict_types=1);
namespace OCP\Group\Backend;
/**
+ * @brief Optional interface for group backends
* @since 14.0.0
*/
interface IGroupDetailsBackend {
/**
+ * @brief Get additional details for a group, for example the display name.
+ *
+ * The array returned can be empty when no additional information is available
+ * for the group.
+ *
+ * @return array{displayName?: string}
* @since 14.0.0
*/
public function getGroupDetails(string $gid): array;
diff --git a/lib/public/GroupInterface.php b/lib/public/GroupInterface.php
index a18d38df002..599a0eb2ff0 100644
--- a/lib/public/GroupInterface.php
+++ b/lib/public/GroupInterface.php
@@ -86,7 +86,8 @@ interface GroupInterface {
public function getUserGroups($uid);
/**
- * get a list of all groups
+ * @brief Get a list of all groups
+ *
* @param string $search
* @param int $limit
* @param int $offset
@@ -98,7 +99,8 @@ interface GroupInterface {
public function getGroups(string $search = '', int $limit = -1, int $offset = 0);
/**
- * check if a group exists
+ * @brief Check if a group exists
+ *
* @param string $gid
* @return bool
* @since 4.5.0
diff --git a/lib/public/ICacheFactory.php b/lib/public/ICacheFactory.php
index d70a836aa52..70ad955849d 100644
--- a/lib/public/ICacheFactory.php
+++ b/lib/public/ICacheFactory.php
@@ -75,4 +75,20 @@ interface ICacheFactory {
* @since 13.0.0
*/
public function createLocal(string $prefix = ''): ICache;
+
+ /**
+ * Create an in-memory cache instance
+ *
+ * Useful for remembering values inside one process. Cache memory is cleared
+ * when the object is garbage-collected. Implementation may also expire keys
+ * earlier when the TTL is reached or too much memory is consumed.
+ *
+ * Cache keys are local to the cache object. When building two in-memory
+ * caches, there is no data exchange between the instances.
+ *
+ * @param int $capacity maximum number of cache keys
+ * @return ICache
+ * @since 28.0.0
+ */
+ public function createInMemory(int $capacity = 512): ICache;
}
diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php
index fe0267facc5..a528bb1a57b 100644
--- a/lib/public/IDBConnection.php
+++ b/lib/public/IDBConnection.php
@@ -45,6 +45,18 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
* @since 6.0.0
*/
interface IDBConnection {
+ /* @since 28.0.0 */
+ public const PLATFORM_MYSQL = 'mysql';
+
+ /* @since 28.0.0 */
+ public const PLATFORM_ORACLE = 'oracle';
+
+ /* @since 28.0.0 */
+ public const PLATFORM_POSTGRES = 'postgres';
+
+ /* @since 28.0.0 */
+ public const PLATFORM_SQLITE = 'sqlite';
+
/**
* Gets the QueryBuilder for the connection.
*
@@ -339,4 +351,12 @@ interface IDBConnection {
* @since 13.0.0
*/
public function migrateToSchema(Schema $toSchema): void;
+
+ /**
+ * Returns the database provider name
+ * @link https://github.com/nextcloud/server/issues/30877
+ * @since 28.0.0
+ * @return IDBConnection::PLATFORM_*
+ */
+ public function getDatabaseProvider(): string;
}
diff --git a/lib/public/IGroup.php b/lib/public/IGroup.php
index ec26cc55b69..51417641e26 100644
--- a/lib/public/IGroup.php
+++ b/lib/public/IGroup.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
@@ -38,7 +41,7 @@ interface IGroup {
* @return string
* @since 8.0.0
*/
- public function getGID();
+ public function getGID(): string;
/**
* Returns the group display name
@@ -46,7 +49,7 @@ interface IGroup {
* @return string
* @since 12.0.0
*/
- public function getDisplayName();
+ public function getDisplayName(): string;
/**
* Set the group display name
@@ -60,43 +63,47 @@ interface IGroup {
/**
* get all users in the group
*
- * @return \OCP\IUser[]
+ * @return IUser[]
* @since 8.0.0
*/
- public function getUsers();
+ public function getUsers(): array;
/**
* check if a user is in the group
*
- * @param \OCP\IUser $user
+ * @param IUser $user
+ *
* @return bool
* @since 8.0.0
*/
- public function inGroup(IUser $user);
+ public function inGroup(IUser $user): bool;
/**
* add a user to the group
*
- * @param \OCP\IUser $user
+ * @param IUser $user
+ *
* @since 8.0.0
*/
- public function addUser(IUser $user);
+ public function addUser(IUser $user): void;
/**
- * remove a user from the group
+ * Remove a user from the group
+ *
+ * @param IUser $user
*
- * @param \OCP\IUser $user
* @since 8.0.0
*/
- public function removeUser($user);
+ public function removeUser(IUser $user): void;
/**
* search for users in the group by userid
*
* @param string $search
- * @param int $limit
- * @param int $offset
- * @return \OCP\IUser[]
+ * @param int|null $limit
+ * @param int|null $offset
+ *
+ * @return IUser[]
* @since 8.0.0
*/
public function searchUsers(string $search, ?int $limit = null, ?int $offset = null): array;
@@ -108,7 +115,7 @@ interface IGroup {
* @return int|bool
* @since 8.0.0
*/
- public function count($search = '');
+ public function count(string $search = ''): int|bool;
/**
* returns the number of disabled users
@@ -116,18 +123,19 @@ interface IGroup {
* @return int|bool
* @since 14.0.0
*/
- public function countDisabled();
+ public function countDisabled(): int|bool;
/**
- * search for users in the group by displayname
+ * Search for users in the group by displayname
*
* @param string $search
- * @param int $limit
- * @param int $offset
- * @return \OCP\IUser[]
+ * @param int|null $limit
+ * @param int|null $offset
+ *
+ * @return IUser[]
* @since 8.0.0
*/
- public function searchDisplayName($search, $limit = null, $offset = null);
+ public function searchDisplayName(string $search, int $limit = null, int $offset = null): array;
/**
* Get the names of the backends the group is connected to
@@ -135,27 +143,27 @@ interface IGroup {
* @return string[]
* @since 22.0.0
*/
- public function getBackendNames();
+ public function getBackendNames(): array;
/**
- * delete the group
+ * Delete the group
*
* @return bool
* @since 8.0.0
*/
- public function delete();
+ public function delete(): bool;
/**
* @return bool
* @since 14.0.0
*/
- public function canRemoveUser();
+ public function canRemoveUser(): bool;
/**
* @return bool
* @since 14.0.0
*/
- public function canAddUser();
+ public function canAddUser(): bool;
/**
* @return bool
diff --git a/lib/public/INavigationManager.php b/lib/public/INavigationManager.php
index 710cbd1248d..36f80c3293f 100644
--- a/lib/public/INavigationManager.php
+++ b/lib/public/INavigationManager.php
@@ -33,6 +33,10 @@
namespace OCP;
/**
+ * @psalm-type NavigationEntry = array{id: string, order: int, href: string, name: string, app?: string, icon?: string, classes?: string, type?: string}
+ */
+
+/**
* Manages the ownCloud navigation
* @since 6.0.0
*/
@@ -58,9 +62,11 @@ interface INavigationManager {
/**
* Creates a new navigation entry
*
- * @param array|\Closure $entry Array containing: id, name, order, icon and href key
+ * @param array array|\Closure $entry Array containing: id, name, order, icon and href key
+ * If a menu entry (type = 'link') is added, you shall also set app to the app that added the entry.
* The use of a closure is preferred, because it will avoid
* loading the routing of your app, unless required.
+ * @psalm-param NavigationEntry|callable():NavigationEntry $entry
* @return void
* @since 6.0.0
*/
diff --git a/lib/public/IPhoneNumberUtil.php b/lib/public/IPhoneNumberUtil.php
new file mode 100644
index 00000000000..733de0e35a6
--- /dev/null
+++ b/lib/public/IPhoneNumberUtil.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+/**
+ *
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.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 OCP;
+
+/**
+ * @since 28.0.0
+ */
+interface IPhoneNumberUtil {
+ /**
+ * Returns the country code for a specific region
+ *
+ * For example, this would be `41` for Switzerland and `49` for Germany.
+ * Returns null when the region is invalid.
+ *
+ * @param string $regionCode Two-letter region code of ISO 3166-1
+ * @return int|null Null when invalid/unsupported, the phone country code otherwise
+ * @since 28.0.0
+ */
+ public function getCountryCodeForRegion(string $regionCode): ?int;
+
+ /**
+ * Converts a given input into an E164 formatted phone number
+ *
+ * E164 is the international format without any formatting characters or spaces.
+ * E.g. +41446681800 where +41 is the region code.
+ *
+ * @param string $input Input phone number can contain formatting spaces, slashes and dashes
+ * @param string|null $defaultRegion Two-letter region code of ISO 3166-1
+ * @return string|null Null when the input is invalid for the given region or requires a region.
+ * @since 28.0.0
+ */
+ public function convertToStandardFormat(string $input, ?string $defaultRegion = null): ?string;
+}
diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php
index 1efb3d5f0c2..0a94c5ad928 100644
--- a/lib/public/IUserManager.php
+++ b/lib/public/IUserManager.php
@@ -140,6 +140,12 @@ interface IUserManager {
public function searchDisplayName($pattern, $limit = null, $offset = null);
/**
+ * @return IUser[]
+ * @since 28.0.0
+ */
+ public function getDisabledUsers(?int $limit = null, int $offset = 0): array;
+
+ /**
* Search known users (from phonebook sync) by displayName
*
* @param string $searcher
diff --git a/lib/public/Migration/IOutput.php b/lib/public/Migration/IOutput.php
index 70fb56b6bdd..97b0e15b9b5 100644
--- a/lib/public/Migration/IOutput.php
+++ b/lib/public/Migration/IOutput.php
@@ -32,6 +32,13 @@ interface IOutput {
/**
* @param string $message
* @return void
+ * @since 28.0.0
+ */
+ public function debug(string $message): void;
+
+ /**
+ * @param string $message
+ * @return void
* @since 9.1.0
*/
public function info($message);
diff --git a/lib/public/OCM/Events/ResourceTypeRegisterEvent.php b/lib/public/OCM/Events/ResourceTypeRegisterEvent.php
new file mode 100644
index 00000000000..1048d8d0d49
--- /dev/null
+++ b/lib/public/OCM/Events/ResourceTypeRegisterEvent.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+/*
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.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 OCP\OCM\Events;
+
+use OCP\EventDispatcher\Event;
+use OCP\OCM\IOCMProvider;
+
+/**
+ * Use this event to register additional OCM resources before the API returns
+ * them in the OCM provider list and capability
+ *
+ * @since 28.0.0
+ */
+class ResourceTypeRegisterEvent extends Event {
+ /**
+ * @param IOCMProvider $provider
+ * @since 28.0.0
+ */
+ public function __construct(
+ protected IOCMProvider $provider,
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * @param string $name
+ * @param list<string> $shareTypes List of supported share recipients, e.g. 'user', 'group', …
+ * @param array<string, string> $protocols List of supported protocols and their location,
+ * e.g. ['webdav' => '/remote.php/webdav/']
+ * @since 28.0.0
+ */
+ public function registerResourceType(string $name, array $shareTypes, array $protocols): void {
+ $resourceType = $this->provider->createNewResourceType();
+ $resourceType->setName($name)
+ ->setShareTypes($shareTypes)
+ ->setProtocols($protocols);
+ $this->provider->addResourceType($resourceType);
+ }
+}
diff --git a/lib/public/OCM/Exceptions/OCMArgumentException.php b/lib/public/OCM/Exceptions/OCMArgumentException.php
new file mode 100644
index 00000000000..e3abd7bf26b
--- /dev/null
+++ b/lib/public/OCM/Exceptions/OCMArgumentException.php
@@ -0,0 +1,34 @@
+<?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 OCP\OCM\Exceptions;
+
+use Exception;
+
+/**
+ * @since 28.0.0
+ */
+class OCMArgumentException extends Exception {
+}
diff --git a/lib/public/OCM/Exceptions/OCMProviderException.php b/lib/public/OCM/Exceptions/OCMProviderException.php
new file mode 100644
index 00000000000..32dab10dc68
--- /dev/null
+++ b/lib/public/OCM/Exceptions/OCMProviderException.php
@@ -0,0 +1,34 @@
+<?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 OCP\OCM\Exceptions;
+
+use Exception;
+
+/**
+ * @since 28.0.0
+ */
+class OCMProviderException extends Exception {
+}
diff --git a/lib/public/OCM/IOCMDiscoveryService.php b/lib/public/OCM/IOCMDiscoveryService.php
new file mode 100644
index 00000000000..2407e7b24e8
--- /dev/null
+++ b/lib/public/OCM/IOCMDiscoveryService.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 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 OCP\OCM;
+
+use OCP\OCM\Exceptions\OCMProviderException;
+
+/**
+ * Discover remote OCM services
+ *
+ * @since 28.0.0
+ */
+interface IOCMDiscoveryService {
+ /**
+ * Discover remote OCM services
+ *
+ * @param string $remote address of the remote provider
+ * @param bool $skipCache ignore cache, refresh data
+ *
+ * @return IOCMProvider
+ * @throws OCMProviderException if no valid discovery data can be returned
+ * @since 28.0.0
+ */
+ public function discover(string $remote, bool $skipCache = false): IOCMProvider;
+}
diff --git a/lib/public/OCM/IOCMProvider.php b/lib/public/OCM/IOCMProvider.php
new file mode 100644
index 00000000000..6df7eed370c
--- /dev/null
+++ b/lib/public/OCM/IOCMProvider.php
@@ -0,0 +1,165 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 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 OCP\OCM;
+
+use JsonSerializable;
+use OCP\OCM\Exceptions\OCMArgumentException;
+use OCP\OCM\Exceptions\OCMProviderException;
+
+/**
+ * Model based on the Open Cloud Mesh Discovery API
+ * @link https://github.com/cs3org/OCM-API/
+ * @since 28.0.0
+ */
+interface IOCMProvider extends JsonSerializable {
+ /**
+ * enable OCM
+ *
+ * @param bool $enabled
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function setEnabled(bool $enabled): static;
+
+ /**
+ * is set as enabled ?
+ *
+ * @return bool
+ * @since 28.0.0
+ */
+ public function isEnabled(): bool;
+
+ /**
+ * get set API Version
+ *
+ * @param string $apiVersion
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function setApiVersion(string $apiVersion): static;
+
+ /**
+ * returns API version
+ *
+ * @return string
+ * @since 28.0.0
+ */
+ public function getApiVersion(): string;
+
+ /**
+ * configure endpoint
+ *
+ * @param string $endPoint
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function setEndPoint(string $endPoint): static;
+
+ /**
+ * get configured endpoint
+ *
+ * @return string
+ * @since 28.0.0
+ */
+ public function getEndPoint(): string;
+
+ /**
+ * create a new resource to later add it with {@see addResourceType()}
+ * @return IOCMResource
+ * @since 28.0.0
+ */
+ public function createNewResourceType(): IOCMResource;
+
+ /**
+ * add a single resource to the object
+ *
+ * @param IOCMResource $resource
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function addResourceType(IOCMResource $resource): static;
+
+ /**
+ * set resources
+ *
+ * @param IOCMResource[] $resourceTypes
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function setResourceTypes(array $resourceTypes): static;
+
+ /**
+ * get all set resources
+ *
+ * @return IOCMResource[]
+ * @since 28.0.0
+ */
+ public function getResourceTypes(): array;
+
+ /**
+ * extract a specific string value from the listing of protocols, based on resource-name and protocol-name
+ *
+ * @param string $resourceName
+ * @param string $protocol
+ *
+ * @return string
+ * @throws OCMArgumentException
+ * @since 28.0.0
+ */
+ public function extractProtocolEntry(string $resourceName, string $protocol): string;
+
+ /**
+ * import data from an array
+ *
+ * @param array<string, int|string|bool|array> $data
+ *
+ * @return $this
+ * @throws OCMProviderException in case a descent provider cannot be generated from data
+ * @since 28.0.0
+ */
+ public function import(array $data): static;
+
+ /**
+ * @return array{
+ * enabled: bool,
+ * apiVersion: string,
+ * endPoint: string,
+ * resourceTypes: array{
+ * name: string,
+ * shareTypes: string[],
+ * protocols: array<string, string>
+ * }[]
+ * }
+ * @since 28.0.0
+ */
+ public function jsonSerialize(): array;
+}
diff --git a/lib/public/OCM/IOCMResource.php b/lib/public/OCM/IOCMResource.php
new file mode 100644
index 00000000000..242c77116f1
--- /dev/null
+++ b/lib/public/OCM/IOCMResource.php
@@ -0,0 +1,111 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 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 OCP\OCM;
+
+use JsonSerializable;
+
+/**
+ * Model based on the Open Cloud Mesh Discovery API
+ *
+ * @link https://github.com/cs3org/OCM-API/
+ * @since 28.0.0
+ */
+interface IOCMResource extends JsonSerializable {
+ /**
+ * set name of the resource
+ *
+ * @param string $name
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function setName(string $name): static;
+
+ /**
+ * get name of the resource
+ *
+ * @return string
+ * @since 28.0.0
+ */
+ public function getName(): string;
+
+ /**
+ * set share types
+ *
+ * @param string[] $shareTypes
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function setShareTypes(array $shareTypes): static;
+
+ /**
+ * get share types
+ *
+ * @return string[]
+ * @since 28.0.0
+ */
+ public function getShareTypes(): array;
+
+ /**
+ * set available protocols
+ *
+ * @param array<string, string> $protocols
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function setProtocols(array $protocols): static;
+
+ /**
+ * get configured protocols
+ *
+ * @return array<string, string>
+ * @since 28.0.0
+ */
+ public function getProtocols(): array;
+
+ /**
+ * import data from an array
+ *
+ * @param array $data
+ *
+ * @return $this
+ * @since 28.0.0
+ */
+ public function import(array $data): static;
+
+ /**
+ * @return array{
+ * name: string,
+ * shareTypes: string[],
+ * protocols: array<string, string>
+ * }
+ * @since 28.0.0
+ */
+ public function jsonSerialize(): array;
+}
diff --git a/lib/public/Profile/IProfileManager.php b/lib/public/Profile/IProfileManager.php
new file mode 100644
index 00000000000..996e49d116e
--- /dev/null
+++ b/lib/public/Profile/IProfileManager.php
@@ -0,0 +1,106 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.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 OCP\Profile;
+
+use OCP\Accounts\IAccountManager;
+use OCP\IUser;
+
+/**
+ * @since 28.0.0
+ */
+interface IProfileManager {
+ /**
+ * Visible to users, guests, and public access
+ *
+ * @since 28.0.0
+ */
+ public const VISIBILITY_SHOW = 'show';
+
+ /**
+ * Visible to users and guests
+ *
+ * @since 28.0.0
+ */
+ public const VISIBILITY_SHOW_USERS_ONLY = 'show_users_only';
+
+ /**
+ * Visible to nobody
+ *
+ * @since 28.0.0
+ */
+ public const VISIBILITY_HIDE = 'hide';
+
+ /**
+ * Default account property visibility
+ *
+ * @since 28.0.0
+ */
+ public const DEFAULT_PROPERTY_VISIBILITY = [
+ IAccountManager::PROPERTY_ADDRESS => self::VISIBILITY_SHOW_USERS_ONLY,
+ IAccountManager::PROPERTY_AVATAR => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_BIOGRAPHY => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_DISPLAYNAME => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_HEADLINE => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_ORGANISATION => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_ROLE => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_EMAIL => self::VISIBILITY_SHOW_USERS_ONLY,
+ IAccountManager::PROPERTY_PHONE => self::VISIBILITY_SHOW_USERS_ONLY,
+ IAccountManager::PROPERTY_TWITTER => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_WEBSITE => self::VISIBILITY_SHOW,
+ ];
+
+ /**
+ * Default visibility
+ *
+ * @since 28.0.0
+ */
+ public const DEFAULT_VISIBILITY = self::VISIBILITY_SHOW_USERS_ONLY;
+
+ /**
+ * If no user is passed as an argument return whether profile is enabled globally in `config.php`
+ *
+ * @since 28.0.0
+ */
+ public function isProfileEnabled(?IUser $user = null): bool;
+
+ /**
+ * Return whether the profile parameter of the target user
+ * is visible to the visiting user
+ *
+ * @since 28.0.0
+ */
+ public function isProfileFieldVisible(string $profileField, IUser $targetUser, ?IUser $visitingUser): bool;
+
+ /**
+ * Return the profile parameters of the target user that are visible to the visiting user
+ * in an associative array
+ *
+ * @return array{userId: string, address?: ?string, biography?: ?string, displayname?: ?string, headline?: ?string, isUserAvatarVisible?: bool, organisation?: ?string, role?: ?string, actions: list<array{id: string, icon: string, title: string, target: ?string}>}
+ * @since 28.0.0
+ */
+ public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array;
+}
diff --git a/lib/public/Search/FilterDefinition.php b/lib/public/Search/FilterDefinition.php
new file mode 100644
index 00000000000..7e1538acedb
--- /dev/null
+++ b/lib/public/Search/FilterDefinition.php
@@ -0,0 +1,101 @@
+<?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 OCP\Search;
+
+use InvalidArgumentException;
+
+/**
+ * Filter definition
+ *
+ * Describe filter attributes
+ *
+ * @since 28.0.0
+ */
+class FilterDefinition {
+ public const TYPE_BOOL = 'bool';
+ public const TYPE_INT = 'int';
+ public const TYPE_FLOAT = 'float';
+ public const TYPE_STRING = 'string';
+ public const TYPE_STRINGS = 'strings';
+ public const TYPE_DATETIME = 'datetime';
+ public const TYPE_PERSON = 'person';
+ public const TYPE_NC_USER = 'nc-user';
+ public const TYPE_NC_GROUP = 'nc-group';
+
+ /**
+ * Build filter definition
+ *
+ * @param self::TYPE_* $type
+ * @param bool $exclusive If true, all providers not supporting this filter will be ignored when this filter is provided
+ * @throw InvalidArgumentException in case of invalid name. Allowed characters are -, 0-9, a-z.
+ * @since 28.0.0
+ */
+ public function __construct(
+ private string $name,
+ private string $type = self::TYPE_STRING,
+ private bool $exclusive = true,
+ ) {
+ if (!preg_match('/[-0-9a-z]+/Au', $name)) {
+ throw new InvalidArgumentException('Invalid filter name. Allowed characters are [-0-9a-z]');
+ }
+ }
+
+ /**
+ * Filter name
+ *
+ * Name is used in query string and for advanced syntax `name: <value>`
+ *
+ * @since 28.0.0
+ */
+ public function name(): string {
+ return $this->name;
+ }
+
+ /**
+ * Filter type
+ *
+ * Expected type of value for the filter
+ *
+ * @return self::TYPE_*
+ * @since 28.0.0
+ */
+ public function type(): string {
+ return $this->type;
+ }
+
+ /**
+ * Is filter exclusive?
+ *
+ * If exclusive, only provider with support for this filter will receive the query.
+ * Example: if an exclusive filter `mimetype` is declared, a search with this term will not
+ * be send to providers like `settings` that doesn't support it.
+ *
+ * @since 28.0.0
+ */
+ public function exclusive(): bool {
+ return $this->exclusive;
+ }
+}
diff --git a/lib/public/Search/IFilter.php b/lib/public/Search/IFilter.php
new file mode 100644
index 00000000000..6065622cb71
--- /dev/null
+++ b/lib/public/Search/IFilter.php
@@ -0,0 +1,55 @@
+<?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 OCP\Search;
+
+/**
+ * Interface for search filters
+ *
+ * @since 28.0.0
+ */
+interface IFilter {
+ /** @since 28.0.0 */
+ public const BUILTIN_TERM = 'term';
+ /** @since 28.0.0 */
+ public const BUILTIN_SINCE = 'since';
+ /** @since 28.0.0 */
+ public const BUILTIN_UNTIL = 'until';
+ /** @since 28.0.0 */
+ public const BUILTIN_PERSON = 'person';
+ /** @since 28.0.0 */
+ public const BUILTIN_TITLE_ONLY = 'title-only';
+ /** @since 28.0.0 */
+ public const BUILTIN_PLACES = 'places';
+ /** @since 28.0.0 */
+ public const BUILTIN_PROVIDER = 'provider';
+
+ /**
+ * Get filter value
+ *
+ * @since 28.0.0
+ */
+ public function get(): mixed;
+}
diff --git a/lib/public/Search/IFilterCollection.php b/lib/public/Search/IFilterCollection.php
new file mode 100644
index 00000000000..6ca53a1c628
--- /dev/null
+++ b/lib/public/Search/IFilterCollection.php
@@ -0,0 +1,57 @@
+<?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 OCP\Search;
+
+use IteratorAggregate;
+
+/**
+ * Interface for search filters
+ *
+ * @since 28.0.0
+ * @extends IteratorAggregate<string, \OCP\Search\IFilter>
+ */
+interface IFilterCollection extends IteratorAggregate {
+ /**
+ * Check if a filter exits
+ *
+ * @since 28.0.0
+ */
+ public function has(string $name): bool;
+
+ /**
+ * Get a filter by name
+ *
+ * @since 28.0.0
+ */
+ public function get(string $name): ?IFilter;
+
+ /**
+ * Return Iterator of filters
+ *
+ * @since 28.0.0
+ */
+ public function getIterator(): \Traversable;
+}
diff --git a/lib/public/Search/IFilteringProvider.php b/lib/public/Search/IFilteringProvider.php
new file mode 100644
index 00000000000..dbe1044a539
--- /dev/null
+++ b/lib/public/Search/IFilteringProvider.php
@@ -0,0 +1,72 @@
+<?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 OCP\Search;
+
+/**
+ * Interface for advanced search providers
+ *
+ * These providers will be implemented in apps, so they can participate in the
+ * global search results of Nextcloud. If an app provides more than one type of
+ * resource, e.g. contacts and address books in Nextcloud Contacts, it should
+ * register one provider per group.
+ *
+ * @since 28.0.0
+ */
+interface IFilteringProvider extends IProvider {
+ /**
+ * Return the names of filters supported by the application
+ *
+ * If a filter sent by client is not in this list,
+ * the current provider will be ignored.
+ * Example:
+ * array('term', 'since', 'custom-filter');
+ *
+ * @since 28.0.0
+ * @return string[] Name of supported filters (default or defined by application)
+ */
+ public function getSupportedFilters(): array;
+
+ /**
+ * Get alternate IDs handled by this provider
+ *
+ * A search provider can complete results from other search providers.
+ * For example, files and full-text-search can search in files.
+ * If you use `in:files` in a search, provider files will be invoked,
+ * with all other providers declaring `files` in this method
+ *
+ * @since 28.0.0
+ * @return string[] IDs
+ */
+ public function getAlternateIds(): array;
+
+ /**
+ * Allows application to declare custom filters
+ *
+ * @since 28.0.0
+ * @return list<FilterDefinition>
+ */
+ public function getCustomFilters(): array;
+}
diff --git a/lib/public/Search/IInAppSearch.php b/lib/public/Search/IInAppSearch.php
new file mode 100644
index 00000000000..9e1e294bf4d
--- /dev/null
+++ b/lib/public/Search/IInAppSearch.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 OCP\Search;
+
+/**
+ * Interface for search providers supporting in-app search
+ *
+ * @since 28.0.0
+ */
+interface IInAppSearch extends IProvider {
+}
diff --git a/lib/public/Search/IProvider.php b/lib/public/Search/IProvider.php
index 61655c47367..95d7a1b163a 100644
--- a/lib/public/Search/IProvider.php
+++ b/lib/public/Search/IProvider.php
@@ -68,15 +68,17 @@ interface IProvider {
/**
* Get the search provider order
* The lower the int, the higher it will be sorted (0 will be before 10)
+ * If null, the search provider will be hidden in the UI and the API not called
*
* @param string $route the route the user is currently at, e.g. files.view.index
* @param array $routeParameters the parameters of the route the user is currently at, e.g. [fileId = 982, dir = "/"]
*
- * @return int
+ * @return int|null
*
* @since 20.0.0
+ * @since 28.0.0 Can return null
*/
- public function getOrder(string $route, array $routeParameters): int;
+ public function getOrder(string $route, array $routeParameters): ?int;
/**
* Find matching search entries in an app
diff --git a/lib/public/Search/ISearchQuery.php b/lib/public/Search/ISearchQuery.php
index a545d1dbccb..75d95b45c4c 100644
--- a/lib/public/Search/ISearchQuery.php
+++ b/lib/public/Search/ISearchQuery.php
@@ -52,6 +52,20 @@ interface ISearchQuery {
public function getTerm(): string;
/**
+ * Get a single request filter
+ *
+ * @since 28.0.0
+ */
+ public function getFilter(string $name): ?IFilter;
+
+ /**
+ * Get request filters
+ *
+ * @since 28.0.0
+ */
+ public function getFilters(): IFilterCollection;
+
+ /**
* Get the sort order of results as defined as SORT_* constants on this interface
*
* @return int
diff --git a/lib/public/Security/RateLimiting/ILimiter.php b/lib/public/Security/RateLimiting/ILimiter.php
new file mode 100644
index 00000000000..275746b0b49
--- /dev/null
+++ b/lib/public/Security/RateLimiting/ILimiter.php
@@ -0,0 +1,72 @@
+<?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 OCP\Security\RateLimiting;
+
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
+use OCP\IUser;
+
+/**
+ * Programmatic rate limiter for web requests that are not handled by an app framework controller
+ *
+ * @see AnonRateLimit
+ * @see UserRateLimit
+ *
+ * @since 28.0.0
+ */
+interface ILimiter {
+ /**
+ * Registers attempt for an anonymous request
+ *
+ * @param string $identifier
+ * @param int $anonLimit
+ * @param int $anonPeriod in seconds
+ * @param string $ip
+ * @throws IRateLimitExceededException if limits are reached, which should cause a HTTP 429 response
+ * @since 28.0.0
+ *
+ */
+ public function registerAnonRequest(string $identifier,
+ int $anonLimit,
+ int $anonPeriod,
+ string $ip): void;
+
+ /**
+ * Registers attempt for an authenticated request
+ *
+ * @param string $identifier
+ * @param int $userLimit
+ * @param int $userPeriod in seconds
+ * @param IUser $user the acting user
+ * @throws IRateLimitExceededException if limits are reached, which should cause a HTTP 429 response
+ * @since 28.0.0
+ *
+ */
+ public function registerUserRequest(string $identifier,
+ int $userLimit,
+ int $userPeriod,
+ IUser $user): void;
+}
diff --git a/lib/public/Security/RateLimiting/IRateLimitExceededException.php b/lib/public/Security/RateLimiting/IRateLimitExceededException.php
new file mode 100644
index 00000000000..9bc8c22a67c
--- /dev/null
+++ b/lib/public/Security/RateLimiting/IRateLimitExceededException.php
@@ -0,0 +1,36 @@
+<?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 OCP\Security\RateLimiting;
+
+use Throwable;
+
+/**
+ * Thrown if the (anonymous) user has exceeded a rate limit
+ *
+ * @since 28.0.0
+ */
+interface IRateLimitExceededException extends Throwable {
+}
diff --git a/lib/public/SetupCheck/ISetupCheck.php b/lib/public/SetupCheck/ISetupCheck.php
new file mode 100644
index 00000000000..96eb6ddd7da
--- /dev/null
+++ b/lib/public/SetupCheck/ISetupCheck.php
@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu>
+ *
+ * @author Carl Schwan <carl@carlschwan.eu>
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @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 OCP\SetupCheck;
+
+/**
+ * This interface needs to be implemented if you want to provide custom
+ * setup checks in your application. The results of these checks will them
+ * be displayed in the admin overview.
+ *
+ * @since 28.0.0
+ */
+interface ISetupCheck {
+ /**
+ * @since 28.0.0
+ * @return string Category id, one of security/system/accounts, or a custom one which will be merged in system
+ */
+ public function getCategory(): string;
+
+ /**
+ * @since 28.0.0
+ * @return string Translated name to display to the user
+ */
+ public function getName(): string;
+
+ /**
+ * @since 28.0.0
+ */
+ public function run(): SetupResult;
+}
diff --git a/lib/private/Metadata/Capabilities.php b/lib/public/SetupCheck/ISetupCheckManager.php
index 2fa0006f581..4b963e7c6b8 100644
--- a/lib/private/Metadata/Capabilities.php
+++ b/lib/public/SetupCheck/ISetupCheckManager.php
@@ -3,8 +3,11 @@
declare(strict_types=1);
/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @license AGPL-3.0-or-later
+ * @copyright Copyright (c) 2023 Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @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,
@@ -20,25 +23,15 @@ declare(strict_types=1);
*
*/
-namespace OC\Metadata;
-
-use OCP\Capabilities\IPublicCapability;
-use OCP\IConfig;
-
-class Capabilities implements IPublicCapability {
- private IMetadataManager $manager;
- private IConfig $config;
+namespace OCP\SetupCheck;
- 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 [];
- }
+/**
+ * @since 28.0.0
+ */
+interface ISetupCheckManager {
+ /**
+ * @since 28.0.0
+ * @return array<string,array<string,SetupResult>> Result of each check, first level key is category, second level key is title
+ */
+ public function runAll(): array;
}
diff --git a/lib/public/SetupCheck/SetupResult.php b/lib/public/SetupCheck/SetupResult.php
new file mode 100644
index 00000000000..e4a7744178a
--- /dev/null
+++ b/lib/public/SetupCheck/SetupResult.php
@@ -0,0 +1,156 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu>
+ *
+ * @author Carl Schwan <carl@carlschwan.eu>
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @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 OCP\SetupCheck;
+
+/**
+ * @brief This class is used for storing the result of a setup check
+ *
+ * @since 28.0.0
+ */
+class SetupResult implements \JsonSerializable {
+ public const SUCCESS = 'success';
+ public const INFO = 'info';
+ public const WARNING = 'warning';
+ public const ERROR = 'error';
+
+ /**
+ * @param string $name Translated name to display to the user
+ */
+ private ?string $name = null;
+
+ /**
+ * @brief Private constructor, use success()/info()/warning()/error() instead
+ * @param self::SUCCESS|self::INFO|self::WARNING|self::ERROR $severity
+ * @since 28.0.0
+ */
+ private function __construct(
+ private string $severity,
+ private ?string $description = null,
+ private ?string $linkToDoc = null,
+ ) {
+ }
+
+ /**
+ * @brief Create a success result object
+ * @param ?string $description Translated detailed description to display to the user
+ * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project
+ * @since 28.0.0
+ */
+ public static function success(?string $description = null, ?string $linkToDoc = null): self {
+ return new self(self::SUCCESS, $description, $linkToDoc);
+ }
+
+ /**
+ * @brief Create an info result object
+ * @param ?string $description Translated detailed description to display to the user
+ * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project
+ * @since 28.0.0
+ */
+ public static function info(?string $description = null, ?string $linkToDoc = null): self {
+ return new self(self::INFO, $description, $linkToDoc);
+ }
+
+ /**
+ * @brief Create a warning result object
+ * @param ?string $description Translated detailed description to display to the user
+ * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project
+ * @since 28.0.0
+ */
+ public static function warning(?string $description = null, ?string $linkToDoc = null): self {
+ return new self(self::WARNING, $description, $linkToDoc);
+ }
+
+ /**
+ * @brief Create an error result object
+ * @param ?string $description Translated detailed description to display to the user
+ * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project
+ * @since 28.0.0
+ */
+ public static function error(?string $description = null, ?string $linkToDoc = null): self {
+ return new self(self::ERROR, $description, $linkToDoc);
+ }
+
+ /**
+ * @brief Get the severity for the setup check result
+ *
+ * @return self::SUCCESS|self::INFO|self::WARNING|self::ERROR
+ * @since 28.0.0
+ */
+ public function getSeverity(): string {
+ return $this->severity;
+ }
+
+ /**
+ * @brief Get the description for the setup check result
+ *
+ * @since 28.0.0
+ */
+ public function getDescription(): ?string {
+ return $this->description;
+ }
+
+ /**
+ * @brief Get the name for the setup check
+ *
+ * @since 28.0.0
+ */
+ public function getName(): ?string {
+ return $this->name;
+ }
+
+ /**
+ * @brief Set the name from the setup check
+ *
+ * @since 28.0.0
+ */
+ public function setName(string $name): void {
+ $this->name = $name;
+ }
+
+ /**
+ * @brief Get a link to the doc for the explanation.
+ *
+ * @since 28.0.0
+ */
+ public function getLinkToDoc(): ?string {
+ return $this->linkToDoc;
+ }
+
+ /**
+ * @brief Get an array representation of the result for API responses
+ *
+ * @since 28.0.0
+ */
+ public function jsonSerialize(): array {
+ return [
+ 'name' => $this->name,
+ 'severity' => $this->severity,
+ 'description' => $this->description,
+ 'linkToDoc' => $this->linkToDoc,
+ ];
+ }
+}
diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php
index 40548c6c73d..e5a943b0bac 100644
--- a/lib/public/Share/IShare.php
+++ b/lib/public/Share/IShare.php
@@ -394,7 +394,7 @@ interface IShare {
/**
* Get the expiration date
*
- * @return \DateTime
+ * @return null|\DateTime
* @since 9.0.0
*/
public function getExpirationDate();
diff --git a/lib/public/TextProcessing/Exception/TaskFailureException.php b/lib/public/TextProcessing/Exception/TaskFailureException.php
new file mode 100644
index 00000000000..300864711e7
--- /dev/null
+++ b/lib/public/TextProcessing/Exception/TaskFailureException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace OCP\TextProcessing\Exception;
+
+/**
+ * Exception thrown when a task failed
+ * @since 28.0.0
+ */
+class TaskFailureException extends \RuntimeException {
+}
diff --git a/lib/public/TextProcessing/IManager.php b/lib/public/TextProcessing/IManager.php
index dec0baba4bb..ff1222b094d 100644
--- a/lib/public/TextProcessing/IManager.php
+++ b/lib/public/TextProcessing/IManager.php
@@ -27,7 +27,9 @@ declare(strict_types=1);
namespace OCP\TextProcessing;
use OCP\Common\Exception\NotFoundException;
+use OCP\DB\Exception;
use OCP\PreConditionNotMetException;
+use OCP\TextProcessing\Exception\TaskFailureException;
use RuntimeException;
/**
@@ -56,7 +58,7 @@ interface IManager {
/**
* @param Task $task The task to run
* @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called
- * @throws RuntimeException If something else failed
+ * @throws TaskFailureException If running the task failed
* @since 27.1.0
*/
public function runTask(Task $task): string;
@@ -68,11 +70,26 @@ interface IManager {
*
* @param Task $task The task to schedule
* @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called
+ * @throws Exception storing the task in the database failed
* @since 27.1.0
*/
public function scheduleTask(Task $task) : void;
/**
+ * If the designated provider for the passed task provides an expected average runtime, we check if the runtime fits into the
+ * max execution time of this php process and run it synchronously if it does, if it doesn't fit (or the provider doesn't provide that information)
+ * execution is deferred to a background job
+ *
+ * @param Task $task The task to schedule
+ * @returns bool A boolean indicating whether the task was run synchronously (`true`) or offloaded to a background job (`false`)
+ * @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called
+ * @throws TaskFailureException If running the task failed
+ * @throws Exception storing the task in the database failed
+ * @since 28.0.0
+ */
+ public function runOrScheduleTask(Task $task): bool;
+
+ /**
* Delete a task that has been scheduled before
*
* @param Task $task The task to delete
diff --git a/lib/public/TextProcessing/IProviderWithExpectedRuntime.php b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php
new file mode 100644
index 00000000000..17767fc02d4
--- /dev/null
+++ b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php
@@ -0,0 +1,41 @@
+<?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 OCP\TextProcessing;
+
+/**
+ * This interface allows the system to learn the provider's expected runtime
+ * @since 28.0.0
+ * @template T of ITaskType
+ * @template-extends IProvider<T>
+ */
+interface IProviderWithExpectedRuntime extends IProvider {
+ /**
+ * @return int The expected average runtime of a task in seconds
+ * @since 28.0.0
+ */
+ public function getExpectedRuntime(): int;
+}
diff --git a/lib/public/TextProcessing/IProviderWithUserId.php b/lib/public/TextProcessing/IProviderWithUserId.php
new file mode 100644
index 00000000000..0a01a4c56c4
--- /dev/null
+++ b/lib/public/TextProcessing/IProviderWithUserId.php
@@ -0,0 +1,41 @@
+<?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 OCP\TextProcessing;
+
+/**
+ * This interface allows providers to access the user that initiated the task being run.
+ * @since 28.0.0
+ * @template T of ITaskType
+ * @template-extends IProvider<T>
+ */
+interface IProviderWithUserId extends IProvider {
+ /**
+ * @param ?string $userId the current user's id
+ * @since 28.0.0
+ */
+ public function setUserId(?string $userId): void;
+}
diff --git a/lib/public/TextProcessing/Task.php b/lib/public/TextProcessing/Task.php
index 446e414cb04..25b7132ee31 100644
--- a/lib/public/TextProcessing/Task.php
+++ b/lib/public/TextProcessing/Task.php
@@ -35,6 +35,7 @@ namespace OCP\TextProcessing;
final class Task implements \JsonSerializable {
protected ?int $id = null;
protected ?string $output = null;
+ private ?\DateTime $completionExpectedAt = null;
/**
* @since 27.1.0
@@ -98,6 +99,9 @@ final class Task implements \JsonSerializable {
*/
public function visitProvider(IProvider $provider): string {
if ($this->canUseProvider($provider)) {
+ if ($provider instanceof IProviderWithUserId) {
+ $provider->setUserId($this->getUserId());
+ }
return $provider->process($this->getInput());
} else {
throw new \RuntimeException('Task of type ' . $this->getType() . ' cannot visit provider with task type ' . $provider->getTaskType());
@@ -203,7 +207,7 @@ final class Task implements \JsonSerializable {
}
/**
- * @psalm-return array{id: ?int, type: S, status: 0|1|2|3|4, userId: ?string, appId: string, input: string, output: ?string, identifier: string}
+ * @psalm-return array{id: ?int, type: S, status: 0|1|2|3|4, userId: ?string, appId: string, input: string, output: ?string, identifier: string, completionExpectedAt: ?int}
* @since 27.1.0
*/
public function jsonSerialize(): array {
@@ -216,6 +220,24 @@ final class Task implements \JsonSerializable {
'input' => $this->getInput(),
'output' => $this->getOutput(),
'identifier' => $this->getIdentifier(),
+ 'completionExpectedAt' => $this->getCompletionExpectedAt()?->getTimestamp(),
];
}
+
+ /**
+ * @param null|\DateTime $completionExpectedAt
+ * @return void
+ * @since 28.0.0
+ */
+ final public function setCompletionExpectedAt(?\DateTime $completionExpectedAt): void {
+ $this->completionExpectedAt = $completionExpectedAt;
+ }
+
+ /**
+ * @return \DateTime|null
+ * @since 28.0.0
+ */
+ final public function getCompletionExpectedAt(): ?\DateTime {
+ return $this->completionExpectedAt;
+ }
}
diff --git a/lib/public/TextToImage/Events/AbstractTextToImageEvent.php b/lib/public/TextToImage/Events/AbstractTextToImageEvent.php
new file mode 100644
index 00000000000..56c68195602
--- /dev/null
+++ b/lib/public/TextToImage/Events/AbstractTextToImageEvent.php
@@ -0,0 +1,52 @@
+<?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 OCP\TextToImage\Events;
+
+use OCP\EventDispatcher\Event;
+use OCP\TextToImage\Task;
+
+/**
+ * @since 28.0.0
+ */
+abstract class AbstractTextToImageEvent extends Event {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(
+ private Task $task
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * @return Task
+ * @since 28.0.0
+ */
+ public function getTask(): Task {
+ return $this->task;
+ }
+}
diff --git a/lib/public/TextToImage/Events/TaskFailedEvent.php b/lib/public/TextToImage/Events/TaskFailedEvent.php
new file mode 100644
index 00000000000..0d91b3a4f67
--- /dev/null
+++ b/lib/public/TextToImage/Events/TaskFailedEvent.php
@@ -0,0 +1,54 @@
+<?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 OCP\TextToImage\Events;
+
+use OCP\TextToImage\Task;
+
+/**
+ * @since 28.0.0
+ */
+class TaskFailedEvent extends AbstractTextToImageEvent {
+ /**
+ * @param Task $task
+ * @param string $errorMessage
+ * @since 28.0.0
+ */
+ public function __construct(
+ Task $task,
+ private string $errorMessage,
+ ) {
+ parent::__construct($task);
+ }
+
+ /**
+ * @return string
+ * @since 28.0.0
+ */
+ public function getErrorMessage(): string {
+ return $this->errorMessage;
+ }
+}
diff --git a/lib/public/TextToImage/Events/TaskSuccessfulEvent.php b/lib/public/TextToImage/Events/TaskSuccessfulEvent.php
new file mode 100644
index 00000000000..15d263c0ff0
--- /dev/null
+++ b/lib/public/TextToImage/Events/TaskSuccessfulEvent.php
@@ -0,0 +1,33 @@
+<?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 OCP\TextToImage\Events;
+
+/**
+ * @since 28.0.0
+ */
+class TaskSuccessfulEvent extends AbstractTextToImageEvent {
+}
diff --git a/lib/public/TextToImage/Exception/TaskFailureException.php b/lib/public/TextToImage/Exception/TaskFailureException.php
new file mode 100644
index 00000000000..a640fdff2e8
--- /dev/null
+++ b/lib/public/TextToImage/Exception/TaskFailureException.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @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 OCP\TextToImage\Exception;
+
+/**
+ * @since 28.0.0
+ */
+class TaskFailureException extends TextToImageException {
+}
diff --git a/lib/public/TextToImage/Exception/TaskNotFoundException.php b/lib/public/TextToImage/Exception/TaskNotFoundException.php
new file mode 100644
index 00000000000..bd713fe3905
--- /dev/null
+++ b/lib/public/TextToImage/Exception/TaskNotFoundException.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @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 OCP\TextToImage\Exception;
+
+/**
+ * @since 28.0.0
+ */
+class TaskNotFoundException extends TextToImageException {
+}
diff --git a/lib/public/TextToImage/Exception/TextToImageException.php b/lib/public/TextToImage/Exception/TextToImageException.php
new file mode 100644
index 00000000000..3d4fd192c94
--- /dev/null
+++ b/lib/public/TextToImage/Exception/TextToImageException.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @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 OCP\TextToImage\Exception;
+
+/**
+ * @since 28.0.0
+ */
+class TextToImageException extends \Exception {
+}
diff --git a/lib/public/TextToImage/IManager.php b/lib/public/TextToImage/IManager.php
new file mode 100644
index 00000000000..30b88217690
--- /dev/null
+++ b/lib/public/TextToImage/IManager.php
@@ -0,0 +1,116 @@
+<?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 OCP\TextToImage;
+
+use OCP\DB\Exception;
+use OCP\PreConditionNotMetException;
+use OCP\TextToImage\Exception\TaskFailureException;
+use OCP\TextToImage\Exception\TaskNotFoundException;
+use RuntimeException;
+
+/**
+ * API surface for apps interacting with and making use of TextToImage providers
+ * without knowing which providers are installed
+ * @since 28.0.0
+ */
+interface IManager {
+ /**
+ * @since 28.0.0
+ */
+ public function hasProviders(): bool;
+
+ /**
+ * @since 28.0.0
+ * @return IProvider[]
+ */
+ public function getProviders(): array;
+
+ /**
+ * @param Task $task The task to run
+ * @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called
+ * @throws TaskFailureException If something else failed. When this is thrown task status was already set to failure.
+ * @since 28.0.0
+ */
+ public function runTask(Task $task): void;
+
+ /**
+ * Will schedule a TextToImage process in the background. The result will become available
+ * with the \OCP\TextToImage\TaskSuccessfulEvent
+ * If inference fails a \OCP\TextToImage\Events\TaskFailedEvent will be dispatched instead
+ *
+ * @param Task $task The task to schedule
+ * @throws PreConditionNotMetException If no provider was registered but this method was still called
+ * @throws Exception If there was a problem inserting the task into the database
+ * @since 28.0.0
+ */
+ public function scheduleTask(Task $task) : void;
+
+ /**
+ * @throws Exception if there was a problem inserting the task into the database
+ * @throws PreConditionNotMetException if no provider is registered
+ * @throws TaskFailureException If the task run failed
+ * @since 28.0.0
+ */
+ public function runOrScheduleTask(Task $task) : void;
+
+ /**
+ * Delete a task that has been scheduled before
+ *
+ * @param Task $task The task to delete
+ * @since 28.0.0
+ */
+ public function deleteTask(Task $task): void;
+
+ /**
+ * @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
+ * @since 28.0.0
+ */
+ public function getTask(int $id): Task;
+
+ /**
+ * @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
+ * @since 28.0.0
+ */
+ public function getUserTask(int $id, ?string $userId): Task;
+
+ /**
+ * @param ?string $userId
+ * @param string $appId
+ * @param string|null $identifier
+ * @return Task[]
+ * @since 28.0.0
+ * @throws RuntimeException If the query failed
+ */
+ public function getUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array;
+}
diff --git a/lib/public/TextToImage/IProvider.php b/lib/public/TextToImage/IProvider.php
new file mode 100644
index 00000000000..9f2ff51f599
--- /dev/null
+++ b/lib/public/TextToImage/IProvider.php
@@ -0,0 +1,64 @@
+<?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 OCP\TextToImage;
+
+use RuntimeException;
+
+/**
+ * This is the interface that is implemented by apps that
+ * implement a text to image provider
+ * @since 28.0.0
+ */
+interface IProvider {
+ /**
+ * An arbitrary unique text string identifying this provider
+ * @since 28.0.0
+ */
+ public function getId(): string;
+
+ /**
+ * The localized name of this provider
+ * @since 28.0.0
+ */
+ public function getName(): string;
+
+ /**
+ * Processes a text
+ *
+ * @param string $prompt The input text
+ * @param resource[] $resources The file resources to write the images to
+ * @return void
+ * @since 28.0.0
+ * @throws RuntimeException If the text could not be processed
+ */
+ public function generate(string $prompt, array $resources): void;
+
+ /**
+ * The expected runtime for one task with this provider in seconds
+ * @since 28.0.0
+ */
+ public function getExpectedRuntime(): int;
+}
diff --git a/lib/public/TextToImage/Task.php b/lib/public/TextToImage/Task.php
new file mode 100644
index 00000000000..e610af6aa96
--- /dev/null
+++ b/lib/public/TextToImage/Task.php
@@ -0,0 +1,212 @@
+<?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 OCP\TextToImage;
+
+use DateTime;
+use OCP\Files\AppData\IAppDataFactory;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IImage;
+use OCP\Image;
+
+/**
+ * This is a text to image task
+ *
+ * @since 28.0.0
+ */
+final class Task implements \JsonSerializable {
+ protected ?int $id = null;
+
+ protected ?DateTime $completionExpectedAt = null;
+
+ /**
+ * @since 28.0.0
+ */
+ public const STATUS_FAILED = 4;
+ /**
+ * @since 28.0.0
+ */
+ public const STATUS_SUCCESSFUL = 3;
+ /**
+ * @since 28.0.0
+ */
+ public const STATUS_RUNNING = 2;
+ /**
+ * @since 28.0.0
+ */
+ public const STATUS_SCHEDULED = 1;
+ /**
+ * @since 28.0.0
+ */
+ public const STATUS_UNKNOWN = 0;
+
+ /**
+ * @psalm-var self::STATUS_*
+ */
+ protected int $status = self::STATUS_UNKNOWN;
+
+ /**
+ * @param string $input
+ * @param string $appId
+ * @param int $numberOfImages
+ * @param string|null $userId
+ * @param null|string $identifier An arbitrary identifier for this task. max length: 255 chars
+ * @since 28.0.0
+ */
+ final public function __construct(
+ protected string $input,
+ protected string $appId,
+ protected int $numberOfImages,
+ protected ?string $userId,
+ protected ?string $identifier = '',
+ ) {
+ }
+
+ /**
+ * @return IImage[]|null
+ * @since 28.0.0
+ */
+ final public function getOutputImages(): ?array {
+ $appData = \OCP\Server::get(IAppDataFactory::class)->get('core');
+ try {
+ $folder = $appData->getFolder('text2image')->getFolder((string)$this->getId());
+ $images = [];
+ for ($i = 0; $i < $this->getNumberOfImages(); $i++) {
+ $image = new Image();
+ $image->loadFromFileHandle($folder->getFile((string) $i)->read());
+ $images[] = $image;
+ }
+ return $images;
+ } catch (NotFoundException|NotPermittedException) {
+ return null;
+ }
+ }
+
+ /**
+ * @return int
+ * @since 28.0.0
+ */
+ final public function getNumberOfImages(): int {
+ return $this->numberOfImages;
+ }
+
+ /**
+ * @psalm-return self::STATUS_*
+ * @since 28.0.0
+ */
+ final public function getStatus(): int {
+ return $this->status;
+ }
+
+ /**
+ * @psalm-param self::STATUS_* $status
+ * @since 28.0.0
+ */
+ final public function setStatus(int $status): void {
+ $this->status = $status;
+ }
+
+ /**
+ * @param ?DateTime $at
+ * @since 28.0.0
+ */
+ final public function setCompletionExpectedAt(?DateTime $at): void {
+ $this->completionExpectedAt = $at;
+ }
+
+ /**
+ * @return ?DateTime
+ * @since 28.0.0
+ */
+ final public function getCompletionExpectedAt(): ?DateTime {
+ return $this->completionExpectedAt;
+ }
+
+ /**
+ * @return int|null
+ * @since 28.0.0
+ */
+ final public function getId(): ?int {
+ return $this->id;
+ }
+
+ /**
+ * @param int|null $id
+ * @since 28.0.0
+ */
+ final public function setId(?int $id): void {
+ $this->id = $id;
+ }
+
+ /**
+ * @return string
+ * @since 28.0.0
+ */
+ final public function getInput(): string {
+ return $this->input;
+ }
+
+ /**
+ * @return string
+ * @since 28.0.0
+ */
+ final public function getAppId(): string {
+ return $this->appId;
+ }
+
+ /**
+ * @return null|string
+ * @since 28.0.0
+ */
+ final public function getIdentifier(): ?string {
+ return $this->identifier;
+ }
+
+ /**
+ * @return string|null
+ * @since 28.0.0
+ */
+ final public function getUserId(): ?string {
+ return $this->userId;
+ }
+
+ /**
+ * @psalm-return array{id: ?int, status: self::STATUS_*, userId: ?string, appId: string, input: string, identifier: ?string, numberOfImages: int, completionExpectedAt: ?int}
+ * @since 28.0.0
+ */
+ public function jsonSerialize(): array {
+ return [
+ 'id' => $this->getId(),
+ 'status' => $this->getStatus(),
+ 'userId' => $this->getUserId(),
+ 'appId' => $this->getAppId(),
+ 'numberOfImages' => $this->getNumberOfImages(),
+ 'input' => $this->getInput(),
+ 'identifier' => $this->getIdentifier(),
+ 'completionExpectedAt' => $this->getCompletionExpectedAt()->getTimestamp(),
+ ];
+ }
+}
diff --git a/lib/public/User/Events/OutOfOfficeChangedEvent.php b/lib/public/User/Events/OutOfOfficeChangedEvent.php
new file mode 100644
index 00000000000..5e5753b7202
--- /dev/null
+++ b/lib/public/User/Events/OutOfOfficeChangedEvent.php
@@ -0,0 +1,50 @@
+<?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 OCP\User\Events;
+
+use OCP\EventDispatcher\Event;
+use OCP\User\IOutOfOfficeData;
+
+/**
+ * Emitted when a user's out-of-office period has changed
+ *
+ * @since 28.0.0
+ */
+class OutOfOfficeChangedEvent extends Event {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(private IOutOfOfficeData $data) {
+ parent::__construct();
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getData(): IOutOfOfficeData {
+ return $this->data;
+ }
+}
diff --git a/lib/public/User/Events/OutOfOfficeClearedEvent.php b/lib/public/User/Events/OutOfOfficeClearedEvent.php
new file mode 100644
index 00000000000..48a77c77023
--- /dev/null
+++ b/lib/public/User/Events/OutOfOfficeClearedEvent.php
@@ -0,0 +1,50 @@
+<?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 OCP\User\Events;
+
+use OCP\EventDispatcher\Event;
+use OCP\User\IOutOfOfficeData;
+
+/**
+ * Emitted when a user's out-of-office period is cleared
+ *
+ * @since 28.0.0
+ */
+class OutOfOfficeClearedEvent extends Event {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(private IOutOfOfficeData $data) {
+ parent::__construct();
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getData(): IOutOfOfficeData {
+ return $this->data;
+ }
+}
diff --git a/lib/public/User/Events/OutOfOfficeScheduledEvent.php b/lib/public/User/Events/OutOfOfficeScheduledEvent.php
new file mode 100644
index 00000000000..2bcbec63478
--- /dev/null
+++ b/lib/public/User/Events/OutOfOfficeScheduledEvent.php
@@ -0,0 +1,50 @@
+<?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 OCP\User\Events;
+
+use OCP\EventDispatcher\Event;
+use OCP\User\IOutOfOfficeData;
+
+/**
+ * Emitted when a user's out-of-office period is scheduled
+ *
+ * @since 28.0.0
+ */
+class OutOfOfficeScheduledEvent extends Event {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(private IOutOfOfficeData $data) {
+ parent::__construct();
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getData(): IOutOfOfficeData {
+ return $this->data;
+ }
+}
diff --git a/lib/public/User/IAvailabilityCoordinator.php b/lib/public/User/IAvailabilityCoordinator.php
new file mode 100644
index 00000000000..749241f13bc
--- /dev/null
+++ b/lib/public/User/IAvailabilityCoordinator.php
@@ -0,0 +1,51 @@
+<?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 OCP\User;
+
+use OCP\IUser;
+
+/**
+ * Coordinator for availability and out-of-office messages
+ *
+ * @since 28.0.0
+ */
+interface IAvailabilityCoordinator {
+ /**
+ * Check if the feature is enabled on this instance
+ *
+ * @return bool
+ *
+ * @since 28.0.0
+ */
+ public function isEnabled(): bool;
+
+ /**
+ * Get the user's out-of-office message, if any
+ *
+ * @since 28.0.0
+ */
+ public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData;
+}
diff --git a/lib/public/User/IOutOfOfficeData.php b/lib/public/User/IOutOfOfficeData.php
new file mode 100644
index 00000000000..03444449d58
--- /dev/null
+++ b/lib/public/User/IOutOfOfficeData.php
@@ -0,0 +1,77 @@
+<?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 OCP\User;
+
+use OCP\IUser;
+
+/**
+ * DTO to hold out-of-office information of a user
+ *
+ * @since 28.0.0
+ */
+interface IOutOfOfficeData {
+ /**
+ * Get the unique token assigned to the current out-of-office event
+ *
+ * @since 28.0.0
+ */
+ public function getId(): string;
+
+ /**
+ * @since 28.0.0
+ */
+ public function getUser(): IUser;
+
+ /**
+ * Get the accurate out-of-office start date
+ *
+ * This event is not guaranteed to be emitted exactly at start date
+ *
+ * @since 28.0.0
+ */
+ public function getStartDate(): int;
+
+ /**
+ * Get the (preliminary) out-of-office end date
+ *
+ * @since 28.0.0
+ */
+ public function getEndDate(): int;
+
+ /**
+ * Get the short summary text displayed in the user status and similar
+ *
+ * @since 28.0.0
+ */
+ public function getShortMessage(): string;
+
+ /**
+ * Get the long out-of-office message for auto responders and similar
+ *
+ * @since 28.0.0
+ */
+ public function getMessage(): string;
+}
diff --git a/lib/public/UserStatus/IUserStatus.php b/lib/public/UserStatus/IUserStatus.php
index 74c54cc9da2..c96d07d298b 100644
--- a/lib/public/UserStatus/IUserStatus.php
+++ b/lib/public/UserStatus/IUserStatus.php
@@ -53,6 +53,12 @@ interface IUserStatus {
/**
* @var string
+ * @since 28.0.0
+ */
+ public const BUSY = 'busy';
+
+ /**
+ * @var string
* @since 20.0.0
*/
public const OFFLINE = 'offline';
@@ -76,6 +82,18 @@ interface IUserStatus {
public const MESSAGE_AVAILABILITY = 'availability';
/**
+ * @var string
+ * @since 28.0.0
+ */
+ public const MESSAGE_CALENDAR_BUSY = 'meeting';
+
+ /**
+ * @var string
+ * @since 28.0.0
+ */
+ public const MESSAGE_CALENDAR_BUSY_TENTATIVE = 'busy-tentative';
+
+ /**
* Get the user this status is connected to
*
* @return string
diff --git a/lib/public/Util.php b/lib/public/Util.php
index bff8038b3dd..cabb84c0cf6 100644
--- a/lib/public/Util.php
+++ b/lib/public/Util.php
@@ -49,6 +49,7 @@ namespace OCP;
use OC\AppScriptDependency;
use OC\AppScriptSort;
use bantu\IniGetWrapper\IniGetWrapper;
+use OCP\Share\IManager;
use Psr\Container\ContainerExceptionInterface;
/**
@@ -57,17 +58,11 @@ use Psr\Container\ContainerExceptionInterface;
* @since 4.0.0
*/
class Util {
- /** @var \OCP\Share\IManager */
- private static $shareManager;
+ private static ?IManager $shareManager = null;
- /** @var array */
- private static $scripts = [];
-
- /** @var array */
- private static $scriptDeps = [];
-
- /** @var array */
- private static $sortedScriptDeps = [];
+ private static array $scriptsInit = [];
+ private static array $scripts = [];
+ private static array $scriptDeps = [];
/**
* get the current installed version of Nextcloud
@@ -110,19 +105,6 @@ class Util {
}
/**
- * write a message in the log
- * @param string $app
- * @param string $message
- * @param int $level
- * @since 4.0.0
- * @deprecated 13.0.0 use log of \OCP\ILogger
- */
- public static function writeLog($app, $message, $level) {
- $context = ['app' => $app];
- \OC::$server->getLogger()->log($level, $message, $context);
- }
-
- /**
* check if sharing is disabled for the current user
*
* @return boolean
@@ -164,6 +146,25 @@ class Util {
}
/**
+ * Add a standalone init js file that is loaded for initialization
+ *
+ * Be careful loading scripts using this method as they are loaded early
+ * and block the initial page rendering. They should not have dependencies
+ * on any other scripts than core-common and core-main.
+ *
+ * @since 28.0.0
+ */
+ public static function addInitScript(string $application, string $file): void {
+ if (!empty($application)) {
+ $path = "$application/js/$file";
+ } else {
+ $path = "js/$file";
+ }
+
+ self::$scriptsInit[] = $path;
+ }
+
+ /**
* add a javascript file
*
* @param string $application
@@ -214,7 +215,8 @@ class Util {
$sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps);
// Flatten array and remove duplicates
- $sortedScripts = $sortedScripts ? array_merge(...array_values(($sortedScripts))) : [];
+ $sortedScripts = array_merge([self::$scriptsInit], $sortedScripts);
+ $sortedScripts = array_merge(...array_values($sortedScripts));
// Override core-common and core-main order
if (in_array('core/js/main', $sortedScripts)) {
diff --git a/lib/versioncheck.php b/lib/versioncheck.php
index 8869fe95453..29c278370cd 100644
--- a/lib/versioncheck.php
+++ b/lib/versioncheck.php
@@ -33,10 +33,10 @@ if (PHP_VERSION_ID < 80000) {
exit(1);
}
-// Show warning if >= PHP 8.3 is used as Nextcloud is not compatible with >= PHP 8.3 for now
-if (PHP_VERSION_ID >= 80300) {
+// Show warning if >= PHP 8.4 is used as Nextcloud is not compatible with >= PHP 8.4 for now
+if (PHP_VERSION_ID >= 80400) {
http_response_code(500);
- echo 'This version of Nextcloud is not compatible with PHP>=8.3.<br/>';
+ echo 'This version of Nextcloud is not compatible with PHP>=8.4.<br/>';
echo 'You are currently running ' . PHP_VERSION . '.';
exit(1);
}