aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRobin Appelman <robin@icewind.nl>2024-01-31 19:42:58 +0100
committerGitHub <noreply@github.com>2024-01-31 19:42:58 +0100
commit3066687c7da2835e8b14e5b73e3a9a0987f236c7 (patch)
tree1bf26bebd26650910f2ddc8617bb9c9c6a5ea6ae /lib
parenta8e6d89d3be51296199055df1539714e5a4203ef (diff)
parentaff861f4e78d7ed668bb07eaf0cc065446569e3c (diff)
downloadnextcloud-server-3066687c7da2835e8b14e5b73e3a9a0987f236c7.tar.gz
nextcloud-server-3066687c7da2835e8b14e5b73e3a9a0987f236c7.zip
Merge branch 'master' into background-scan-catch-storage-error
Diffstat (limited to 'lib')
-rw-r--r--lib/base.php14
-rw-r--r--lib/composer/composer/InstalledVersions.php17
-rw-r--r--lib/composer/composer/autoload_classmap.php110
-rw-r--r--lib/composer/composer/autoload_static.php110
-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/az.js2
-rw-r--r--lib/l10n/az.json2
-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.js8
-rw-r--r--lib/l10n/cs.json8
-rw-r--r--lib/l10n/da.js6
-rw-r--r--lib/l10n/da.json6
-rw-r--r--lib/l10n/de.js20
-rw-r--r--lib/l10n/de.json20
-rw-r--r--lib/l10n/de_DE.js4
-rw-r--r--lib/l10n/de_DE.json4
-rw-r--r--lib/l10n/el.js1
-rw-r--r--lib/l10n/el.json1
-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.js8
-rw-r--r--lib/l10n/es.json8
-rw-r--r--lib/l10n/es_419.js1
-rw-r--r--lib/l10n/es_419.json1
-rw-r--r--lib/l10n/es_AR.js1
-rw-r--r--lib/l10n/es_AR.json1
-rw-r--r--lib/l10n/es_CL.js1
-rw-r--r--lib/l10n/es_CL.json1
-rw-r--r--lib/l10n/es_CO.js1
-rw-r--r--lib/l10n/es_CO.json1
-rw-r--r--lib/l10n/es_CR.js1
-rw-r--r--lib/l10n/es_CR.json1
-rw-r--r--lib/l10n/es_DO.js1
-rw-r--r--lib/l10n/es_DO.json1
-rw-r--r--lib/l10n/es_EC.js4
-rw-r--r--lib/l10n/es_EC.json4
-rw-r--r--lib/l10n/es_GT.js1
-rw-r--r--lib/l10n/es_GT.json1
-rw-r--r--lib/l10n/es_HN.js1
-rw-r--r--lib/l10n/es_HN.json1
-rw-r--r--lib/l10n/es_MX.js1
-rw-r--r--lib/l10n/es_MX.json1
-rw-r--r--lib/l10n/es_NI.js1
-rw-r--r--lib/l10n/es_NI.json1
-rw-r--r--lib/l10n/es_PA.js1
-rw-r--r--lib/l10n/es_PA.json1
-rw-r--r--lib/l10n/es_PE.js1
-rw-r--r--lib/l10n/es_PE.json1
-rw-r--r--lib/l10n/es_PR.js1
-rw-r--r--lib/l10n/es_PR.json1
-rw-r--r--lib/l10n/es_PY.js1
-rw-r--r--lib/l10n/es_PY.json1
-rw-r--r--lib/l10n/es_SV.js1
-rw-r--r--lib/l10n/es_SV.json1
-rw-r--r--lib/l10n/es_UY.js1
-rw-r--r--lib/l10n/es_UY.json1
-rw-r--r--lib/l10n/et_EE.js1
-rw-r--r--lib/l10n/et_EE.json1
-rw-r--r--lib/l10n/eu.js4
-rw-r--r--lib/l10n/eu.json4
-rw-r--r--lib/l10n/fa.js6
-rw-r--r--lib/l10n/fa.json6
-rw-r--r--lib/l10n/fi.js4
-rw-r--r--lib/l10n/fi.json4
-rw-r--r--lib/l10n/fr.js41
-rw-r--r--lib/l10n/fr.json41
-rw-r--r--lib/l10n/gl.js36
-rw-r--r--lib/l10n/gl.json36
-rw-r--r--lib/l10n/he.js1
-rw-r--r--lib/l10n/he.json1
-rw-r--r--lib/l10n/hr.js1
-rw-r--r--lib/l10n/hr.json1
-rw-r--r--lib/l10n/hu.js14
-rw-r--r--lib/l10n/hu.json14
-rw-r--r--lib/l10n/id.js3
-rw-r--r--lib/l10n/id.json3
-rw-r--r--lib/l10n/is.js77
-rw-r--r--lib/l10n/is.json77
-rw-r--r--lib/l10n/it.js14
-rw-r--r--lib/l10n/it.json14
-rw-r--r--lib/l10n/ja.js6
-rw-r--r--lib/l10n/ja.json6
-rw-r--r--lib/l10n/ka.js275
-rw-r--r--lib/l10n/ka.json275
-rw-r--r--lib/l10n/ka_GE.js1
-rw-r--r--lib/l10n/ka_GE.json1
-rw-r--r--lib/l10n/ko.js79
-rw-r--r--lib/l10n/ko.json79
-rw-r--r--lib/l10n/lt_LT.js2
-rw-r--r--lib/l10n/lt_LT.json2
-rw-r--r--lib/l10n/lv.js2
-rw-r--r--lib/l10n/lv.json2
-rw-r--r--lib/l10n/mk.js8
-rw-r--r--lib/l10n/mk.json8
-rw-r--r--lib/l10n/nb.js3
-rw-r--r--lib/l10n/nb.json3
-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/pt_PT.js1
-rw-r--r--lib/l10n/pt_PT.json1
-rw-r--r--lib/l10n/ro.js143
-rw-r--r--lib/l10n/ro.json143
-rw-r--r--lib/l10n/ru.js6
-rw-r--r--lib/l10n/ru.json6
-rw-r--r--lib/l10n/sc.js2
-rw-r--r--lib/l10n/sc.json2
-rw-r--r--lib/l10n/sk.js5
-rw-r--r--lib/l10n/sk.json5
-rw-r--r--lib/l10n/sl.js3
-rw-r--r--lib/l10n/sl.json3
-rw-r--r--lib/l10n/sq.js1
-rw-r--r--lib/l10n/sq.json1
-rw-r--r--lib/l10n/sr.js8
-rw-r--r--lib/l10n/sr.json8
-rw-r--r--lib/l10n/sv.js4
-rw-r--r--lib/l10n/sv.json4
-rw-r--r--lib/l10n/th.js1
-rw-r--r--lib/l10n/th.json1
-rw-r--r--lib/l10n/tr.js24
-rw-r--r--lib/l10n/tr.json24
-rw-r--r--lib/l10n/uk.js20
-rw-r--r--lib/l10n/uk.json20
-rw-r--r--lib/l10n/vi.js5
-rw-r--r--lib/l10n/vi.json5
-rw-r--r--lib/l10n/zh_CN.js16
-rw-r--r--lib/l10n/zh_CN.json16
-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.php4
-rw-r--r--lib/private/Activity/Manager.php10
-rw-r--r--lib/private/AllConfig.php24
-rw-r--r--lib/private/App/AppManager.php56
-rw-r--r--lib/private/App/AppStore/Fetcher/AppFetcher.php12
-rw-r--r--lib/private/App/AppStore/Fetcher/CategoryFetcher.php10
-rw-r--r--lib/private/App/AppStore/Fetcher/Fetcher.php21
-rw-r--r--lib/private/App/Platform.php2
-rw-r--r--lib/private/AppConfig.php1562
-rw-r--r--lib/private/AppFramework/App.php8
-rw-r--r--lib/private/AppFramework/Bootstrap/Coordinator.php10
-rw-r--r--lib/private/AppFramework/Bootstrap/EventListenerRegistration.php6
-rw-r--r--lib/private/AppFramework/Bootstrap/ParameterRegistration.php4
-rw-r--r--lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php4
-rw-r--r--lib/private/AppFramework/Bootstrap/RegistrationContext.php72
-rw-r--r--lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php4
-rw-r--r--lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php6
-rw-r--r--lib/private/AppFramework/Http/Dispatcher.php38
-rw-r--r--lib/private/AppFramework/Http/Request.php35
-rw-r--r--lib/private/AppFramework/Http/RequestId.php2
-rw-r--r--lib/private/AppFramework/Middleware/MiddlewareDispatcher.php12
-rw-r--r--lib/private/AppFramework/Middleware/Security/CORSMiddleware.php6
-rw-r--r--lib/private/AppFramework/Middleware/Security/CSPMiddleware.php4
-rw-r--r--lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php6
-rw-r--r--lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php2
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php24
-rw-r--r--lib/private/AppFramework/Middleware/SessionMiddleware.php2
-rw-r--r--lib/private/AppFramework/OCS/BaseResponse.php8
-rw-r--r--lib/private/AppFramework/ScopedPsrLogger.php2
-rw-r--r--lib/private/AppFramework/Utility/ControllerMethodReflector.php33
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php7
-rw-r--r--lib/private/AppScriptSort.php8
-rw-r--r--lib/private/Archive/TAR.php4
-rw-r--r--lib/private/Authentication/Events/AppPasswordCreatedEvent.php10
-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/Listeners/RemoteWipeActivityListener.php2
-rw-r--r--lib/private/Authentication/Listeners/RemoteWipeEmailListener.php6
-rw-r--r--lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php2
-rw-r--r--lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php1
-rw-r--r--lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php2
-rw-r--r--lib/private/Authentication/Listeners/UserDeletedWebAuthnCleanupListener.php1
-rw-r--r--lib/private/Authentication/Login/Chain.php22
-rw-r--r--lib/private/Authentication/Login/CreateSessionTokenCommand.php2
-rw-r--r--lib/private/Authentication/Login/LoggedInCheckCommand.php2
-rw-r--r--lib/private/Authentication/Login/LoginData.php10
-rw-r--r--lib/private/Authentication/Login/LoginResult.php5
-rw-r--r--lib/private/Authentication/Login/SetUserTimezoneCommand.php2
-rw-r--r--lib/private/Authentication/Login/TwoFactorCommand.php8
-rw-r--r--lib/private/Authentication/Login/UserDisabledCheckCommand.php2
-rw-r--r--lib/private/Authentication/Login/WebAuthnChain.php18
-rw-r--r--lib/private/Authentication/LoginCredentials/Store.php6
-rw-r--r--lib/private/Authentication/Token/IProvider.php61
-rw-r--r--lib/private/Authentication/Token/IToken.php109
-rw-r--r--lib/private/Authentication/Token/Manager.php68
-rw-r--r--lib/private/Authentication/Token/PublicKeyToken.php16
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php71
-rw-r--r--lib/private/Authentication/Token/RemoteWipe.php12
-rw-r--r--lib/private/Authentication/TwoFactorAuth/EnforcementState.php4
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Manager.php24
-rw-r--r--lib/private/Authentication/TwoFactorAuth/ProviderSet.php2
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Registry.php2
-rw-r--r--lib/private/Authentication/WebAuthn/Manager.php2
-rw-r--r--lib/private/Avatar/GuestAvatar.php2
-rw-r--r--lib/private/BackgroundJob/JobList.php4
-rw-r--r--lib/private/BinaryFinder.php2
-rw-r--r--lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php164
-rw-r--r--lib/private/CapabilitiesManager.php2
-rw-r--r--lib/private/Collaboration/Collaborators/GroupPlugin.php8
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php11
-rw-r--r--lib/private/Collaboration/Collaborators/UserPlugin.php9
-rw-r--r--lib/private/Command/AsyncBus.php2
-rw-r--r--lib/private/Command/ClosureJob.php2
-rw-r--r--lib/private/Command/CronBus.php2
-rw-r--r--lib/private/Comments/Comment.php29
-rw-r--r--lib/private/Comments/Manager.php64
-rw-r--r--lib/private/Console/Application.php8
-rw-r--r--lib/private/Console/TimestampFormatter.php19
-rw-r--r--lib/private/Contacts/ContactsMenu/ActionProviderStore.php13
-rw-r--r--lib/private/Contacts/ContactsMenu/ContactsStore.php110
-rw-r--r--lib/private/Contacts/ContactsMenu/Entry.php44
-rw-r--r--lib/private/Contacts/ContactsMenu/Manager.php28
-rw-r--r--lib/private/DB/Connection.php115
-rw-r--r--lib/private/DB/ConnectionFactory.php18
-rw-r--r--lib/private/DB/MigrationService.php6
-rw-r--r--lib/private/DB/Migrator.php6
-rw-r--r--lib/private/DB/MissingColumnInformation.php2
-rw-r--r--lib/private/DB/MissingIndexInformation.php10
-rw-r--r--lib/private/DB/MissingPrimaryKeyInformation.php4
-rw-r--r--lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php37
-rw-r--r--lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php73
-rw-r--r--lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php35
-rw-r--r--lib/private/DB/QueryBuilder/QueryBuilder.php2
-rw-r--r--lib/private/DB/SetTransactionIsolationLevel.php10
-rw-r--r--lib/private/Dashboard/Manager.php4
-rw-r--r--lib/private/DirectEditing/Manager.php4
-rw-r--r--lib/private/Encryption/EncryptionWrapper.php4
-rw-r--r--lib/private/Encryption/File.php6
-rw-r--r--lib/private/Encryption/HookManager.php2
-rw-r--r--lib/private/Encryption/Update.php16
-rw-r--r--lib/private/EventDispatcher/EventDispatcher.php22
-rw-r--r--lib/private/EventDispatcher/ServiceEventListener.php4
-rw-r--r--lib/private/Federation/CloudFederationShare.php20
-rw-r--r--lib/private/Files/AppData/AppData.php6
-rw-r--r--lib/private/Files/AppData/Factory.php2
-rw-r--r--lib/private/Files/Cache/Cache.php26
-rw-r--r--lib/private/Files/Cache/CacheQueryBuilder.php27
-rw-r--r--lib/private/Files/Cache/QuerySearchHelper.php79
-rw-r--r--lib/private/Files/Cache/Scanner.php42
-rw-r--r--lib/private/Files/Cache/SearchBuilder.php107
-rw-r--r--lib/private/Files/Cache/Updater.php26
-rw-r--r--lib/private/Files/Cache/Watcher.php2
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheJail.php2
-rw-r--r--lib/private/Files/Config/CachedMountInfo.php6
-rw-r--r--lib/private/Files/Config/LazyStorageMountInfo.php8
-rw-r--r--lib/private/Files/Config/MountProviderCollection.php5
-rw-r--r--lib/private/Files/Config/UserMountCache.php106
-rw-r--r--lib/private/Files/FileInfo.php44
-rw-r--r--lib/private/Files/Filesystem.php2
-rw-r--r--lib/private/Files/Mount/HomeMountPoint.php49
-rw-r--r--lib/private/Files/Mount/LocalHomeMountProvider.php2
-rw-r--r--lib/private/Files/Mount/Manager.php32
-rw-r--r--lib/private/Files/Mount/ObjectHomeMountProvider.php2
-rw-r--r--lib/private/Files/Node/Folder.php2
-rw-r--r--lib/private/Files/Node/HookConnector.php4
-rw-r--r--lib/private/Files/Node/LazyFolder.php11
-rw-r--r--lib/private/Files/Node/LazyUserFolder.php6
-rw-r--r--lib/private/Files/Node/Node.php20
-rw-r--r--lib/private/Files/Node/Root.php2
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreScanner.php2
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php7
-rw-r--r--lib/private/Files/ObjectStore/S3ConnectionTrait.php7
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php34
-rw-r--r--lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php7
-rw-r--r--lib/private/Files/Search/SearchComparison.php44
-rw-r--r--lib/private/Files/Search/SearchOrder.php30
-rw-r--r--lib/private/Files/SetupManager.php41
-rw-r--r--lib/private/Files/SimpleFS/SimpleFolder.php2
-rw-r--r--lib/private/Files/Storage/DAV.php4
-rw-r--r--lib/private/Files/Storage/Local.php27
-rw-r--r--lib/private/Files/Storage/Wrapper/Encoding.php2
-rw-r--r--lib/private/Files/Storage/Wrapper/Jail.php5
-rw-r--r--lib/private/Files/Stream/Encryption.php24
-rw-r--r--lib/private/Files/Template/TemplateManager.php7
-rw-r--r--lib/private/Files/Type/Detection.php10
-rw-r--r--lib/private/Files/Utils/Scanner.php4
-rw-r--r--lib/private/Files/View.php14
-rw-r--r--lib/private/FilesMetadata/FilesMetadataManager.php348
-rw-r--r--lib/private/FilesMetadata/Job/UpdateSingleMetadata.php67
-rw-r--r--lib/private/FilesMetadata/Listener/MetadataDelete.php61
-rw-r--r--lib/private/FilesMetadata/Listener/MetadataUpdate.php64
-rw-r--r--lib/private/FilesMetadata/MetadataQuery.php167
-rw-r--r--lib/private/FilesMetadata/Model/FilesMetadata.php621
-rw-r--r--lib/private/FilesMetadata/Model/MetadataValueWrapper.php421
-rw-r--r--lib/private/FilesMetadata/Service/IndexRequestService.php195
-rw-r--r--lib/private/FilesMetadata/Service/MetadataRequestService.php194
-rw-r--r--lib/private/Group/Database.php4
-rw-r--r--lib/private/Group/DisplayNameCache.php1
-rw-r--r--lib/private/Group/Group.php44
-rw-r--r--lib/private/Group/Manager.php8
-rw-r--r--lib/private/Group/MetaData.php10
-rw-r--r--lib/private/Hooks/EmitterTrait.php2
-rw-r--r--lib/private/Http/CookieHelper.php14
-rw-r--r--lib/private/Http/WellKnown/RequestManager.php4
-rw-r--r--lib/private/Installer.php118
-rw-r--r--lib/private/IntegrityCheck/Checker.php26
-rw-r--r--lib/private/Lock/MemcacheLockingProvider.php78
-rw-r--r--lib/private/Log.php6
-rw-r--r--lib/private/Log/Rotate.php2
-rw-r--r--lib/private/Memcache/Factory.php2
-rw-r--r--lib/private/Memcache/LoggerWrapperCache.php10
-rw-r--r--lib/private/Memcache/ProfilerWrapperCache.php10
-rw-r--r--lib/private/Memcache/Redis.php15
-rw-r--r--lib/private/Metadata/FileEventListener.php110
-rw-r--r--lib/private/Metadata/FileMetadata.php51
-rw-r--r--lib/private/Metadata/FileMetadataMapper.php177
-rw-r--r--lib/private/Metadata/IMetadataManager.php35
-rw-r--r--lib/private/Metadata/IMetadataProvider.php41
-rw-r--r--lib/private/Metadata/MetadataManager.php100
-rw-r--r--lib/private/Metadata/Provider/ExifProvider.php142
-rw-r--r--lib/private/Migration/BackgroundRepair.php24
-rw-r--r--lib/private/Migration/ConsoleOutput.php23
-rw-r--r--lib/private/Migration/SimpleOutput.php22
-rw-r--r--lib/private/NavigationManager.php102
-rw-r--r--lib/private/Net/HostnameClassifier.php4
-rw-r--r--lib/private/Net/IpAddressClassifier.php4
-rw-r--r--lib/private/Notification/Action.php18
-rw-r--r--lib/private/Notification/Manager.php49
-rw-r--r--lib/private/Notification/Notification.php111
-rw-r--r--lib/private/OCS/DiscoveryService.php2
-rw-r--r--lib/private/OCS/Provider.php4
-rw-r--r--lib/private/Preview/BackgroundCleanupJob.php8
-rw-r--r--lib/private/Preview/EMF.php33
-rw-r--r--lib/private/Preview/Generator.php18
-rw-r--r--lib/private/Preview/Imaginary.php9
-rw-r--r--lib/private/Preview/Office.php60
-rw-r--r--lib/private/Preview/Watcher.php3
-rw-r--r--lib/private/Preview/WatcherConnector.php2
-rw-r--r--lib/private/PreviewManager.php60
-rw-r--r--lib/private/Profile/Actions/FediverseAction.php2
-rw-r--r--lib/private/Profile/Actions/TwitterAction.php2
-rw-r--r--lib/private/Profile/ProfileManager.php40
-rw-r--r--lib/private/Profiler/Profiler.php6
-rw-r--r--lib/private/Repair.php21
-rw-r--r--lib/private/Repair/AddMetadataGenerationJob.php43
-rw-r--r--lib/private/Repair/AddRemoveOldTasksBackgroundJob.php8
-rw-r--r--lib/private/Repair/ClearFrontendCaches.php2
-rw-r--r--lib/private/Repair/ClearGeneratedAvatarCache.php2
-rw-r--r--lib/private/Repair/ClearGeneratedAvatarCacheJob.php4
-rw-r--r--lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php2
-rw-r--r--lib/private/Repair/NC20/EncryptionLegacyCipher.php2
-rw-r--r--lib/private/Repair/NC20/EncryptionMigration.php2
-rw-r--r--lib/private/Repair/NC21/ValidatePhoneNumber.php4
-rw-r--r--lib/private/Repair/Owncloud/CleanPreviews.php4
-rw-r--r--lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php8
-rw-r--r--lib/private/Repair/Owncloud/MigrateOauthTables.php4
-rw-r--r--lib/private/Repair/Owncloud/MoveAvatars.php2
-rw-r--r--lib/private/Repair/Owncloud/UpdateLanguageCodes.php2
-rw-r--r--lib/private/Repair/RemoveLinkShares.php8
-rw-r--r--lib/private/Repair/RepairMimeTypes.php26
-rw-r--r--lib/private/RichObjectStrings/Validator.php2
-rw-r--r--lib/private/Route/Router.php10
-rw-r--r--lib/private/Search/Filter/BooleanFilter.php46
-rw-r--r--lib/private/Search/Filter/DateTimeFilter.php46
-rw-r--r--lib/private/Search/Filter/FloatFilter.php45
-rw-r--r--lib/private/Search/Filter/GroupFilter.php51
-rw-r--r--lib/private/Search/Filter/IntegerFilter.php45
-rw-r--r--lib/private/Search/Filter/StringFilter.php44
-rw-r--r--lib/private/Search/Filter/StringsFilter.php51
-rw-r--r--lib/private/Search/Filter/UserFilter.php51
-rw-r--r--lib/private/Search/FilterCollection.php60
-rw-r--r--lib/private/Search/FilterFactory.php60
-rw-r--r--lib/private/Search/SearchComposer.php252
-rw-r--r--lib/private/Search/SearchQuery.php80
-rw-r--r--lib/private/Search/UnsupportedFilter.php34
-rw-r--r--lib/private/Security/Bruteforce/Capabilities.php2
-rw-r--r--lib/private/Security/Bruteforce/Throttler.php4
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicy.php8
-rw-r--r--lib/private/Security/Normalizer/IpAddress.php69
-rw-r--r--lib/private/Security/RemoteHostValidator.php4
-rw-r--r--lib/private/Security/VerificationToken/CleanUpJob.php4
-rw-r--r--lib/private/Server.php54
-rw-r--r--lib/private/Session/CryptoSessionData.php34
-rw-r--r--lib/private/Session/CryptoWrapper.php6
-rw-r--r--lib/private/Session/Internal.php2
-rw-r--r--lib/private/Settings/Manager.php39
-rw-r--r--lib/private/Setup.php247
-rw-r--r--lib/private/Setup/AbstractDatabase.php11
-rw-r--r--lib/private/Setup/MySQL.php2
-rw-r--r--lib/private/SetupCheck/SetupCheckManager.php57
-rw-r--r--lib/private/Share/Share.php6
-rw-r--r--lib/private/Share20/Manager.php45
-rw-r--r--lib/private/Share20/PublicShareTemplateFactory.php2
-rw-r--r--lib/private/Share20/Share.php2
-rw-r--r--lib/private/SpeechToText/SpeechToTextManager.php10
-rw-r--r--lib/private/StreamImage.php2
-rw-r--r--lib/private/SubAdmin.php6
-rw-r--r--lib/private/Support/Subscription/Registry.php8
-rw-r--r--lib/private/SystemConfig.php9
-rw-r--r--lib/private/SystemTag/ManagerFactory.php16
-rw-r--r--lib/private/SystemTag/SystemTag.php53
-rw-r--r--lib/private/SystemTag/SystemTagManager.php17
-rw-r--r--lib/private/SystemTag/SystemTagObjectMapper.php7
-rw-r--r--lib/private/SystemTag/SystemTagsInFilesDetector.php4
-rw-r--r--lib/private/TagManager.php2
-rw-r--r--lib/private/Tagging/TagMapper.php2
-rw-r--r--lib/private/Talk/Broker.php8
-rw-r--r--lib/private/Template/JSCombiner.php8
-rw-r--r--lib/private/Template/JSConfigHelper.php25
-rw-r--r--lib/private/Template/JSResourceLocator.php9
-rw-r--r--lib/private/TemplateLayout.php28
-rw-r--r--lib/private/TextProcessing/Db/Task.php10
-rw-r--r--lib/private/TextProcessing/Manager.php101
-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.php341
-rw-r--r--lib/private/TextToImage/RemoveOldTasksBackgroundJob.php78
-rw-r--r--lib/private/TextToImage/TaskBackgroundJob.php63
-rw-r--r--lib/private/Translation/TranslationManager.php18
-rw-r--r--lib/private/URLGenerator.php23
-rw-r--r--lib/private/Updater.php20
-rw-r--r--lib/private/Updater/VersionCheck.php4
-rw-r--r--lib/private/User/AvailabilityCoordinator.php139
-rw-r--r--lib/private/User/DisplayNameCache.php1
-rw-r--r--lib/private/User/Listeners/BeforeUserDeletedListener.php2
-rw-r--r--lib/private/User/Listeners/UserChangedListener.php2
-rw-r--r--lib/private/User/Manager.php8
-rw-r--r--lib/private/User/OutOfOfficeData.php74
-rw-r--r--lib/private/User/Session.php88
-rw-r--r--lib/private/User/User.php12
-rw-r--r--lib/private/UserStatus/ISettableProvider.php3
-rw-r--r--lib/private/UserStatus/Manager.php6
-rw-r--r--lib/private/legacy/OC_App.php26
-rw-r--r--lib/private/legacy/OC_Files.php6
-rw-r--r--lib/private/legacy/OC_Helper.php2
-rw-r--r--lib/private/legacy/OC_User.php2
-rw-r--r--lib/private/legacy/OC_Util.php10
-rw-r--r--lib/public/App/IAppManager.php31
-rw-r--r--lib/public/AppFramework/ApiController.php8
-rw-r--r--lib/public/AppFramework/AuthPublicShareController.php6
-rw-r--r--lib/public/AppFramework/Bootstrap/IRegistrationContext.php22
-rw-r--r--lib/public/AppFramework/Controller.php2
-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/ParameterOutOfRangeException.php79
-rw-r--r--lib/public/AppFramework/Http/TooManyRequestsResponse.php2
-rw-r--r--lib/public/AppFramework/OCSController.php8
-rw-r--r--lib/public/AppFramework/PublicShareController.php6
-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.php3
-rw-r--r--lib/public/BackgroundJob/Job.php7
-rw-r--r--lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php1
-rw-r--r--lib/public/Collaboration/AutoComplete/AutoCompleteFilterEvent.php107
-rw-r--r--lib/public/Comments/IComment.php19
-rw-r--r--lib/public/Comments/ICommentsManager.php10
-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/Dashboard/Model/WidgetItem.php10
-rw-r--r--lib/public/Exceptions/AppConfigException.php34
-rw-r--r--lib/public/Exceptions/AppConfigIncorrectTypeException.php32
-rw-r--r--lib/public/Exceptions/AppConfigTypeConflictException.php32
-rw-r--r--lib/public/Exceptions/AppConfigUnknownKeyException.php32
-rw-r--r--lib/public/Federation/ICloudFederationNotification.php4
-rw-r--r--lib/public/Files/Cache/ICacheEntry.php4
-rw-r--r--lib/public/Files/Cache/IUpdater.php2
-rw-r--r--lib/public/Files/Config/ICachedMountInfo.php8
-rw-r--r--lib/public/Files/Events/FileCacheUpdated.php2
-rw-r--r--lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php23
-rw-r--r--lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php23
-rw-r--r--lib/public/Files/Events/NodeAddedToCache.php2
-rw-r--r--lib/public/Files/Events/NodeRemovedFromCache.php2
-rw-r--r--lib/public/Files/FileInfo.php9
-rw-r--r--lib/public/Files/Mount/IMountManager.php12
-rw-r--r--lib/public/Files/Node.php2
-rw-r--r--lib/public/Files/Search/ISearchComparison.php18
-rw-r--r--lib/public/Files/Search/ISearchOrder.php13
-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.php169
-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/Http/WellKnown/JrdResponse.php8
-rw-r--r--lib/public/IAppConfig.php483
-rw-r--r--lib/public/IConfig.php6
-rw-r--r--lib/public/IGroup.php60
-rw-r--r--lib/public/IMemcacheTTL.php19
-rw-r--r--lib/public/INavigationManager.php8
-rw-r--r--lib/public/Log/Audit/CriticalActionPerformedEvent.php4
-rw-r--r--lib/public/Migration/IOutput.php7
-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/Search/SearchResult.php10
-rw-r--r--lib/public/Search/SearchResultEntry.php10
-rw-r--r--lib/public/Security/ISecureRandom.php2
-rw-r--r--lib/public/Security/RateLimiting/ILimiter.php12
-rw-r--r--lib/public/Settings/IManager.php38
-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.php189
-rw-r--r--lib/public/Share.php2
-rw-r--r--lib/public/Share/Events/VerifyMountPointEvent.php4
-rw-r--r--lib/public/Share/IManager.php9
-rw-r--r--lib/public/Share/IShare.php4
-rw-r--r--lib/public/Share/IShareProvider.php2
-rw-r--r--lib/public/SpeechToText/ISpeechToTextProviderWithId.php14
-rw-r--r--lib/public/Talk/IBroker.php4
-rw-r--r--lib/public/Talk/ITalkBackend.php4
-rw-r--r--lib/public/TextProcessing/Exception/TaskFailureException.php10
-rw-r--r--lib/public/TextProcessing/IManager.php21
-rw-r--r--lib/public/TextProcessing/IProvider.php2
-rw-r--r--lib/public/TextProcessing/IProviderWithExpectedRuntime.php41
-rw-r--r--lib/public/TextProcessing/IProviderWithId.php39
-rw-r--r--lib/public/TextProcessing/IProviderWithUserId.php41
-rw-r--r--lib/public/TextProcessing/Task.php36
-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/IProviderWithUserId.php15
-rw-r--r--lib/public/TextToImage/Task.php212
-rw-r--r--lib/public/Translation/ITranslationProviderWithId.php37
-rw-r--r--lib/public/Translation/ITranslationProviderWithUserId.php38
-rw-r--r--lib/public/User/Backend/IProvideEnabledStateBackend.php2
-rw-r--r--lib/public/User/Events/BeforePasswordUpdatedEvent.php4
-rw-r--r--lib/public/User/Events/BeforeUserCreatedEvent.php2
-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/OutOfOfficeEndedEvent.php51
-rw-r--r--lib/public/User/Events/OutOfOfficeScheduledEvent.php50
-rw-r--r--lib/public/User/Events/OutOfOfficeStartedEvent.php51
-rw-r--r--lib/public/User/Events/PasswordUpdatedEvent.php4
-rw-r--r--lib/public/User/Events/UserChangedEvent.php6
-rw-r--r--lib/public/User/Events/UserCreatedEvent.php2
-rw-r--r--lib/public/User/Events/UserLiveStatusEvent.php4
-rw-r--r--lib/public/User/IAvailabilityCoordinator.php67
-rw-r--r--lib/public/User/IOutOfOfficeData.php94
-rw-r--r--lib/public/UserStatus/IManager.php4
-rw-r--r--lib/public/UserStatus/IUserStatus.php30
-rw-r--r--lib/public/Util.php18
567 files changed, 14950 insertions, 3629 deletions
diff --git a/lib/base.php b/lib/base.php
index af41b050f52..e4fdb8efb44 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -988,17 +988,7 @@ 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),
- $logger,
- Server::get(\OCP\Security\ISecureRandom::class),
- Server::get(\OC\Installer::class)
- );
- $controller = new OC\Core\Controller\SetupController($setupHelper, $logger);
+ $controller = Server::get(\OC\Core\Controller\SetupController::class);
$controller->run($_POST);
exit();
}
@@ -1121,7 +1111,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
);
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 4387852556d..2fffd0c2f2d 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',
@@ -65,6 +66,7 @@ return array(
'OCP\\AppFramework\\Http\\IOutput' => $baseDir . '/lib/public/AppFramework/Http/IOutput.php',
'OCP\\AppFramework\\Http\\JSONResponse' => $baseDir . '/lib/public/AppFramework/Http/JSONResponse.php',
'OCP\\AppFramework\\Http\\NotFoundResponse' => $baseDir . '/lib/public/AppFramework/Http/NotFoundResponse.php',
+ 'OCP\\AppFramework\\Http\\ParameterOutOfRangeException' => $baseDir . '/lib/public/AppFramework/Http/ParameterOutOfRangeException.php',
'OCP\\AppFramework\\Http\\RedirectResponse' => $baseDir . '/lib/public/AppFramework/Http/RedirectResponse.php',
'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => $baseDir . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php',
'OCP\\AppFramework\\Http\\Response' => $baseDir . '/lib/public/AppFramework/Http/Response.php',
@@ -106,13 +108,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 +171,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 +213,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',
@@ -265,6 +273,10 @@ return array(
'OCP\\EventDispatcher\\GenericEvent' => $baseDir . '/lib/public/EventDispatcher/GenericEvent.php',
'OCP\\EventDispatcher\\IEventDispatcher' => $baseDir . '/lib/public/EventDispatcher/IEventDispatcher.php',
'OCP\\EventDispatcher\\IEventListener' => $baseDir . '/lib/public/EventDispatcher/IEventListener.php',
+ 'OCP\\Exceptions\\AppConfigException' => $baseDir . '/lib/public/Exceptions/AppConfigException.php',
+ 'OCP\\Exceptions\\AppConfigIncorrectTypeException' => $baseDir . '/lib/public/Exceptions/AppConfigIncorrectTypeException.php',
+ 'OCP\\Exceptions\\AppConfigTypeConflictException' => $baseDir . '/lib/public/Exceptions/AppConfigTypeConflictException.php',
+ 'OCP\\Exceptions\\AppConfigUnknownKeyException' => $baseDir . '/lib/public/Exceptions/AppConfigUnknownKeyException.php',
'OCP\\Federation\\Events\\TrustedServerRemovedEvent' => $baseDir . '/lib/public/Federation/Events/TrustedServerRemovedEvent.php',
'OCP\\Federation\\Exceptions\\ActionNotSupportedException' => $baseDir . '/lib/public/Federation/Exceptions/ActionNotSupportedException.php',
'OCP\\Federation\\Exceptions\\AuthenticationFailedException' => $baseDir . '/lib/public/Federation/Exceptions/AuthenticationFailedException.php',
@@ -280,6 +292,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',
@@ -552,6 +576,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',
@@ -570,6 +595,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',
@@ -601,6 +631,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',
@@ -629,6 +662,7 @@ return array(
'OCP\\SpeechToText\\Events\\TranscriptionSuccessfulEvent' => $baseDir . '/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php',
'OCP\\SpeechToText\\ISpeechToTextManager' => $baseDir . '/lib/public/SpeechToText/ISpeechToTextManager.php',
'OCP\\SpeechToText\\ISpeechToTextProvider' => $baseDir . '/lib/public/SpeechToText/ISpeechToTextProvider.php',
+ 'OCP\\SpeechToText\\ISpeechToTextProviderWithId' => $baseDir . '/lib/public/SpeechToText/ISpeechToTextProviderWithId.php',
'OCP\\Support\\CrashReport\\ICollectBreadcrumbs' => $baseDir . '/lib/public/Support/CrashReport/ICollectBreadcrumbs.php',
'OCP\\Support\\CrashReport\\IMessageReporter' => $baseDir . '/lib/public/Support/CrashReport/IMessageReporter.php',
'OCP\\Support\\CrashReport\\IRegistry' => $baseDir . '/lib/public/Support/CrashReport/IRegistry.php',
@@ -656,18 +690,34 @@ 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\\IProviderWithId' => $baseDir . '/lib/public/TextProcessing/IProviderWithId.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\\IProviderWithUserId' => $baseDir . '/lib/public/TextToImage/IProviderWithUserId.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',
'OCP\\Translation\\ITranslationProvider' => $baseDir . '/lib/public/Translation/ITranslationProvider.php',
+ 'OCP\\Translation\\ITranslationProviderWithId' => $baseDir . '/lib/public/Translation/ITranslationProviderWithId.php',
+ 'OCP\\Translation\\ITranslationProviderWithUserId' => $baseDir . '/lib/public/Translation/ITranslationProviderWithUserId.php',
'OCP\\Translation\\LanguageTuple' => $baseDir . '/lib/public/Translation/LanguageTuple.php',
'OCP\\UserInterface' => $baseDir . '/lib/public/UserInterface.php',
'OCP\\UserMigration\\IExportDestination' => $baseDir . '/lib/public/UserMigration/IExportDestination.php',
@@ -700,6 +750,11 @@ 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\\OutOfOfficeEndedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeEndedEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeStartedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeStartedEvent.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',
@@ -711,6 +766,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',
@@ -903,6 +960,7 @@ return array(
'OC\\BackgroundJob\\QueuedJob' => $baseDir . '/lib/private/BackgroundJob/QueuedJob.php',
'OC\\BackgroundJob\\TimedJob' => $baseDir . '/lib/private/BackgroundJob/TimedJob.php',
'OC\\BinaryFinder' => $baseDir . '/lib/private/BinaryFinder.php',
+ 'OC\\Blurhash\\Listener\\GenerateBlurhashMetadata' => $baseDir . '/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php',
'OC\\Broadcast\\Events\\BroadcastEvent' => $baseDir . '/lib/private/Broadcast/Events/BroadcastEvent.php',
'OC\\Cache\\CappedMemoryCache' => $baseDir . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php',
@@ -958,6 +1016,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',
@@ -969,6 +1028,7 @@ return array(
'OC\\Core\\Command\\Background\\Ajax' => $baseDir . '/core/Command/Background/Ajax.php',
'OC\\Core\\Command\\Background\\Base' => $baseDir . '/core/Command/Background/Base.php',
'OC\\Core\\Command\\Background\\Cron' => $baseDir . '/core/Command/Background/Cron.php',
+ 'OC\\Core\\Command\\Background\\Delete' => $baseDir . '/core/Command/Background/Delete.php',
'OC\\Core\\Command\\Background\\Job' => $baseDir . '/core/Command/Background/Job.php',
'OC\\Core\\Command\\Background\\ListCommand' => $baseDir . '/core/Command/Background/ListCommand.php',
'OC\\Core\\Command\\Background\\WebCron' => $baseDir . '/core/Command/Background/WebCron.php',
@@ -1005,6 +1065,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',
@@ -1040,6 +1101,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',
@@ -1094,6 +1156,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',
@@ -1175,6 +1238,14 @@ 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\\Migrations\\Version28000Date20231126110901' => $baseDir . '/core/Migrations/Version28000Date20231126110901.php',
+ 'OC\\Core\\Migrations\\Version29000Date20231126110901' => $baseDir . '/core/Migrations/Version29000Date20231126110901.php',
+ 'OC\\Core\\Migrations\\Version29000Date20231213104850' => $baseDir . '/core/Migrations/Version29000Date20231213104850.php',
+ 'OC\\Core\\Migrations\\Version29000Date20240124132201' => $baseDir . '/core/Migrations/Version29000Date20240124132201.php',
+ 'OC\\Core\\Migrations\\Version29000Date20240124132202' => $baseDir . '/core/Migrations/Version29000Date20240124132202.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',
@@ -1259,6 +1330,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',
@@ -1292,6 +1372,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',
@@ -1451,14 +1532,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',
@@ -1487,6 +1560,7 @@ return array(
'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',
@@ -1550,6 +1624,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',
@@ -1598,6 +1673,16 @@ return array(
'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',
@@ -1605,6 +1690,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',
@@ -1653,6 +1739,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',
@@ -1710,6 +1797,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',
@@ -1719,6 +1811,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',
@@ -1728,6 +1821,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 9b33577d66a..d38db8050cc 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',
@@ -98,6 +99,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\AppFramework\\Http\\IOutput' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/IOutput.php',
'OCP\\AppFramework\\Http\\JSONResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/JSONResponse.php',
'OCP\\AppFramework\\Http\\NotFoundResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/NotFoundResponse.php',
+ 'OCP\\AppFramework\\Http\\ParameterOutOfRangeException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ParameterOutOfRangeException.php',
'OCP\\AppFramework\\Http\\RedirectResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/RedirectResponse.php',
'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php',
'OCP\\AppFramework\\Http\\Response' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Response.php',
@@ -139,13 +141,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 +204,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 +246,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',
@@ -298,6 +306,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\EventDispatcher\\GenericEvent' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/GenericEvent.php',
'OCP\\EventDispatcher\\IEventDispatcher' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/IEventDispatcher.php',
'OCP\\EventDispatcher\\IEventListener' => __DIR__ . '/../../..' . '/lib/public/EventDispatcher/IEventListener.php',
+ 'OCP\\Exceptions\\AppConfigException' => __DIR__ . '/../../..' . '/lib/public/Exceptions/AppConfigException.php',
+ 'OCP\\Exceptions\\AppConfigIncorrectTypeException' => __DIR__ . '/../../..' . '/lib/public/Exceptions/AppConfigIncorrectTypeException.php',
+ 'OCP\\Exceptions\\AppConfigTypeConflictException' => __DIR__ . '/../../..' . '/lib/public/Exceptions/AppConfigTypeConflictException.php',
+ 'OCP\\Exceptions\\AppConfigUnknownKeyException' => __DIR__ . '/../../..' . '/lib/public/Exceptions/AppConfigUnknownKeyException.php',
'OCP\\Federation\\Events\\TrustedServerRemovedEvent' => __DIR__ . '/../../..' . '/lib/public/Federation/Events/TrustedServerRemovedEvent.php',
'OCP\\Federation\\Exceptions\\ActionNotSupportedException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/ActionNotSupportedException.php',
'OCP\\Federation\\Exceptions\\AuthenticationFailedException' => __DIR__ . '/../../..' . '/lib/public/Federation/Exceptions/AuthenticationFailedException.php',
@@ -313,6 +325,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',
@@ -585,6 +609,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',
@@ -603,6 +628,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',
@@ -634,6 +664,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',
@@ -662,6 +695,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\SpeechToText\\Events\\TranscriptionSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php',
'OCP\\SpeechToText\\ISpeechToTextManager' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/ISpeechToTextManager.php',
'OCP\\SpeechToText\\ISpeechToTextProvider' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/ISpeechToTextProvider.php',
+ 'OCP\\SpeechToText\\ISpeechToTextProviderWithId' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/ISpeechToTextProviderWithId.php',
'OCP\\Support\\CrashReport\\ICollectBreadcrumbs' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/ICollectBreadcrumbs.php',
'OCP\\Support\\CrashReport\\IMessageReporter' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/IMessageReporter.php',
'OCP\\Support\\CrashReport\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/IRegistry.php',
@@ -689,18 +723,34 @@ 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\\IProviderWithId' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithId.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\\IProviderWithUserId' => __DIR__ . '/../../..' . '/lib/public/TextToImage/IProviderWithUserId.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',
'OCP\\Translation\\ITranslationProvider' => __DIR__ . '/../../..' . '/lib/public/Translation/ITranslationProvider.php',
+ 'OCP\\Translation\\ITranslationProviderWithId' => __DIR__ . '/../../..' . '/lib/public/Translation/ITranslationProviderWithId.php',
+ 'OCP\\Translation\\ITranslationProviderWithUserId' => __DIR__ . '/../../..' . '/lib/public/Translation/ITranslationProviderWithUserId.php',
'OCP\\Translation\\LanguageTuple' => __DIR__ . '/../../..' . '/lib/public/Translation/LanguageTuple.php',
'OCP\\UserInterface' => __DIR__ . '/../../..' . '/lib/public/UserInterface.php',
'OCP\\UserMigration\\IExportDestination' => __DIR__ . '/../../..' . '/lib/public/UserMigration/IExportDestination.php',
@@ -733,6 +783,11 @@ 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\\OutOfOfficeEndedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeEndedEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
+ 'OCP\\User\\Events\\OutOfOfficeStartedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeStartedEvent.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',
@@ -744,6 +799,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',
@@ -936,6 +993,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\BackgroundJob\\QueuedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/QueuedJob.php',
'OC\\BackgroundJob\\TimedJob' => __DIR__ . '/../../..' . '/lib/private/BackgroundJob/TimedJob.php',
'OC\\BinaryFinder' => __DIR__ . '/../../..' . '/lib/private/BinaryFinder.php',
+ 'OC\\Blurhash\\Listener\\GenerateBlurhashMetadata' => __DIR__ . '/../../..' . '/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php',
'OC\\Broadcast\\Events\\BroadcastEvent' => __DIR__ . '/../../..' . '/lib/private/Broadcast/Events/BroadcastEvent.php',
'OC\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php',
@@ -991,6 +1049,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',
@@ -1002,6 +1061,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Command\\Background\\Ajax' => __DIR__ . '/../../..' . '/core/Command/Background/Ajax.php',
'OC\\Core\\Command\\Background\\Base' => __DIR__ . '/../../..' . '/core/Command/Background/Base.php',
'OC\\Core\\Command\\Background\\Cron' => __DIR__ . '/../../..' . '/core/Command/Background/Cron.php',
+ 'OC\\Core\\Command\\Background\\Delete' => __DIR__ . '/../../..' . '/core/Command/Background/Delete.php',
'OC\\Core\\Command\\Background\\Job' => __DIR__ . '/../../..' . '/core/Command/Background/Job.php',
'OC\\Core\\Command\\Background\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/Background/ListCommand.php',
'OC\\Core\\Command\\Background\\WebCron' => __DIR__ . '/../../..' . '/core/Command/Background/WebCron.php',
@@ -1038,6 +1098,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',
@@ -1073,6 +1134,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',
@@ -1127,6 +1189,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',
@@ -1208,6 +1271,14 @@ 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\\Migrations\\Version28000Date20231126110901' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20231126110901.php',
+ 'OC\\Core\\Migrations\\Version29000Date20231126110901' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20231126110901.php',
+ 'OC\\Core\\Migrations\\Version29000Date20231213104850' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20231213104850.php',
+ 'OC\\Core\\Migrations\\Version29000Date20240124132201' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240124132201.php',
+ 'OC\\Core\\Migrations\\Version29000Date20240124132202' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240124132202.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',
@@ -1292,6 +1363,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',
@@ -1325,6 +1405,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',
@@ -1484,14 +1565,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',
@@ -1520,6 +1593,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'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',
@@ -1583,6 +1657,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',
@@ -1631,6 +1706,16 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'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',
@@ -1638,6 +1723,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',
@@ -1686,6 +1772,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',
@@ -1743,6 +1830,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',
@@ -1752,6 +1844,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',
@@ -1761,6 +1854,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..ada5df14e4f 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' => '559a758533026559cf632ed1b3d74f6b1ebfb481',
'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' => '559a758533026559cf632ed1b3d74f6b1ebfb481',
'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..ef0519ab7ab 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" : "التحقّق من الإيميل",
@@ -271,9 +270,8 @@ OC.L10N.register(
"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 لم يتم استبدالها مؤخّراً. تأكد من تطابق إصدارها مع الخادوم.",
+ "404" : "404",
"Full name" : "الاسم الكامل",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "بسبب الوصول إلى الحدّ الأقصى من عدد المستخدمين، لم يتم إنشا المستخدم. رجاءً، راجع إشعاراتك لمزيد المعلومات.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "مسموح باستخدام الأحرف التالية فقط في اسم المستخدم: \"a-z\" و \"A-Z\" و \"0-9\" و \"_. @ - '\"",
"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 مطلوبة.",
diff --git a/lib/l10n/ar.json b/lib/l10n/ar.json
index 5ac53142dee..f08c7d59fb9 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" : "التحقّق من الإيميل",
@@ -269,9 +268,8 @@
"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 لم يتم استبدالها مؤخّراً. تأكد من تطابق إصدارها مع الخادوم.",
+ "404" : "404",
"Full name" : "الاسم الكامل",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "بسبب الوصول إلى الحدّ الأقصى من عدد المستخدمين، لم يتم إنشا المستخدم. رجاءً، راجع إشعاراتك لمزيد المعلومات.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "مسموح باستخدام الأحرف التالية فقط في اسم المستخدم: \"a-z\" و \"A-Z\" و \"0-9\" و \"_. @ - '\"",
"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 مطلوبة.",
diff --git a/lib/l10n/ast.js b/lib/l10n/ast.js
index 7635cc90e60..e403438926c 100644
--- a/lib/l10n/ast.js
+++ b/lib/l10n/ast.js
@@ -4,15 +4,19 @@ OC.L10N.register(
"Other activities" : "Otres actividaes",
"The library %s is not available." : "La biblioteca «%s» nun ta disponible.",
"Authentication" : "Autenticación",
+ "Files" : "Ficheros",
+ "View profile" : "Ver el perfil",
"today" : "güei",
"tomorrow" : "mañana",
"yesterday" : "ayeri",
+ "seconds ago" : "hai segundos",
"Templates" : "Plantíes",
"Help" : "Ayuda",
"Appearance and accessibility" : "Aspeutu ya accesibilidá",
"Apps" : "Aplicaciones",
"Personal settings" : "Configuración personal",
"Settings" : "Configuración",
+ "Log out" : "Zarrar la sesión",
"Fediverse" : "Fediversu",
"Twitter" : "Twitter",
"Website" : "Sitiu web",
diff --git a/lib/l10n/ast.json b/lib/l10n/ast.json
index af224611393..d9d3425cc64 100644
--- a/lib/l10n/ast.json
+++ b/lib/l10n/ast.json
@@ -2,15 +2,19 @@
"Other activities" : "Otres actividaes",
"The library %s is not available." : "La biblioteca «%s» nun ta disponible.",
"Authentication" : "Autenticación",
+ "Files" : "Ficheros",
+ "View profile" : "Ver el perfil",
"today" : "güei",
"tomorrow" : "mañana",
"yesterday" : "ayeri",
+ "seconds ago" : "hai segundos",
"Templates" : "Plantíes",
"Help" : "Ayuda",
"Appearance and accessibility" : "Aspeutu ya accesibilidá",
"Apps" : "Aplicaciones",
"Personal settings" : "Configuración personal",
"Settings" : "Configuración",
+ "Log out" : "Zarrar la sesión",
"Fediverse" : "Fediversu",
"Twitter" : "Twitter",
"Website" : "Sitiu web",
diff --git a/lib/l10n/az.js b/lib/l10n/az.js
index 20d1ce300a1..59b0dfcbba8 100644
--- a/lib/l10n/az.js
+++ b/lib/l10n/az.js
@@ -4,10 +4,12 @@ OC.L10N.register(
"Cannot write into \"config\" directory!" : "\"configurasiya\" direktoriyasının daxilində yazmaq mümkün deyil",
"See %s" : "Bax %s",
"Sample configuration detected" : "Konfiqurasiya nüsxəsi təyin edildi",
+ "Authentication" : "Autentifikasiya",
"Unknown filetype" : "Fayl tipi bəlli deyil.",
"Invalid image" : "Yalnış şəkil",
"Files" : "Fayllar",
"today" : "Bu gün",
+ "yesterday" : "dünən",
"seconds ago" : "saniyələr öncə",
"__language_name__" : "Azərbaycan dili",
"Help" : "Kömək",
diff --git a/lib/l10n/az.json b/lib/l10n/az.json
index b88fbb1337d..f0f3b8a5e05 100644
--- a/lib/l10n/az.json
+++ b/lib/l10n/az.json
@@ -2,10 +2,12 @@
"Cannot write into \"config\" directory!" : "\"configurasiya\" direktoriyasının daxilində yazmaq mümkün deyil",
"See %s" : "Bax %s",
"Sample configuration detected" : "Konfiqurasiya nüsxəsi təyin edildi",
+ "Authentication" : "Autentifikasiya",
"Unknown filetype" : "Fayl tipi bəlli deyil.",
"Invalid image" : "Yalnış şəkil",
"Files" : "Fayllar",
"today" : "Bu gün",
+ "yesterday" : "dünən",
"seconds ago" : "saniyələr öncə",
"__language_name__" : "Azərbaycan dili",
"Help" : "Kömək",
diff --git a/lib/l10n/bg.js b/lib/l10n/bg.js
index a6532e5be26..fa4775849ad 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" : "Имейл потвърждение",
@@ -261,9 +260,8 @@ OC.L10N.register(
"Storage is temporarily not available" : "Временно хранилището не е налично",
"Storage connection timeout. %s" : "Време за изчакване при свързването с хранилище. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файловете на приложението %1$s не бяха заменени правилно. Уверете се, че версията е съвместима със сървъра.",
+ "404" : "404",
"Full name" : "Пълно име",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Потребителският лимит е достигнат и потребителят не е създаден. Проверете вашите известия, за да научите повече.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Потребителските имена може да съдържат следните знаци: \"a-z\", \"A-Z\", \"0-9\" и \"_.@-'\"",
"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",
diff --git a/lib/l10n/bg.json b/lib/l10n/bg.json
index 6bc6ae317c6..2cceb6a3614 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" : "Имейл потвърждение",
@@ -259,9 +258,8 @@
"Storage is temporarily not available" : "Временно хранилището не е налично",
"Storage connection timeout. %s" : "Време за изчакване при свързването с хранилище. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файловете на приложението %1$s не бяха заменени правилно. Уверете се, че версията е съвместима със сървъра.",
+ "404" : "404",
"Full name" : "Пълно име",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Потребителският лимит е достигнат и потребителят не е създаден. Проверете вашите известия, за да научите повече.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Потребителските имена може да съдържат следните знаци: \"a-z\", \"A-Z\", \"0-9\" и \"_.@-'\"",
"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",
diff --git a/lib/l10n/ca.js b/lib/l10n/ca.js
index 421f7e69652..74d84e04d48 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",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Extreu els temes",
"Extracts topics from a text and outputs them separated by commas." : "Extreu els temes d'un text i els retorna separats per comes.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Els fitxers de l'aplicació %1$s no s'han substituït correctament. Assegureu-vos que sigui una versió compatible amb el servidor.",
+ "404" : "404",
"Full name" : "Nom complet",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "S'ha assolit el límit d'usuaris i no s'ha creat l'usuari. Consulteu les notificacions per a obtenir més informació.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Només es permeten els caràcters següents en un nom d'usuari: «a-z», «A-Z», «0-9» i «_.@-'»",
"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.",
diff --git a/lib/l10n/ca.json b/lib/l10n/ca.json
index e18f5be8c9d..12f2368d028 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",
@@ -269,9 +268,8 @@
"Extract topics" : "Extreu els temes",
"Extracts topics from a text and outputs them separated by commas." : "Extreu els temes d'un text i els retorna separats per comes.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Els fitxers de l'aplicació %1$s no s'han substituït correctament. Assegureu-vos que sigui una versió compatible amb el servidor.",
+ "404" : "404",
"Full name" : "Nom complet",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "S'ha assolit el límit d'usuaris i no s'ha creat l'usuari. Consulteu les notificacions per a obtenir més informació.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Només es permeten els caràcters següents en un nom d'usuari: «a-z», «A-Z», «0-9» i «_.@-'»",
"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.",
diff --git a/lib/l10n/cs.js b/lib/l10n/cs.js
index deeb3f3b996..de7ac919319 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á",
@@ -241,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.",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Vyzískat témata",
"Extracts topics from a text and outputs them separated by commas." : "Vyzíská témata z textu a vypíše je oddělované čárkami.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Soubory aplikace %1$s nebyly nahrazeny řádně. Ověřte, že se jedná o verzi, která je kompatibilní se serverem.",
+ "404" : "404",
"Full name" : "Celé jméno",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Bylo dosaženo limitu počtu uživatelů a uživatel proto nebyl vytvořen. Podrobnosti viz upozornění pro vás.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Pouze následující znaky jsou povoleny pro uživatelské jméno: „a-z“, „A-Z“, „0-9“, a „_.@-'“",
"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ší.",
diff --git a/lib/l10n/cs.json b/lib/l10n/cs.json
index 0fa73b1568e..ad244acd656 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á",
@@ -239,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.",
@@ -269,9 +268,8 @@
"Extract topics" : "Vyzískat témata",
"Extracts topics from a text and outputs them separated by commas." : "Vyzíská témata z textu a vypíše je oddělované čárkami.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Soubory aplikace %1$s nebyly nahrazeny řádně. Ověřte, že se jedná o verzi, která je kompatibilní se serverem.",
+ "404" : "404",
"Full name" : "Celé jméno",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Bylo dosaženo limitu počtu uživatelů a uživatel proto nebyl vytvořen. Podrobnosti viz upozornění pro vás.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Pouze následující znaky jsou povoleny pro uživatelské jméno: „a-z“, „A-Z“, „0-9“, a „_.@-'“",
"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ší.",
diff --git a/lib/l10n/da.js b/lib/l10n/da.js
index c549f68b3f3..26becdb373b 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",
@@ -270,9 +269,8 @@ OC.L10N.register(
"Extract topics" : "Uddrag emner",
"Extracts topics from a text and outputs them separated by commas." : "Uddrager emner fra en tekst og skriver dem adskilt af kommaer.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerne tilhørende appen %1$s blev ikke erstattet korrekt. Check at versionen er kompatibel med serveren.",
+ "404" : "404",
"Full name" : "Fulde navn",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Grænsen for brugere er nået, og den nye bruger er ikke blevet oprettet. Læs dine notifikationer for at lære mere.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kun følgende tegn kan indgå i et brugernavn: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/da.json b/lib/l10n/da.json
index 24b42a7ac5e..d58800be580 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",
@@ -268,9 +267,8 @@
"Extract topics" : "Uddrag emner",
"Extracts topics from a text and outputs them separated by commas." : "Uddrager emner fra en tekst og skriver dem adskilt af kommaer.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerne tilhørende appen %1$s blev ikke erstattet korrekt. Check at versionen er kompatibel med serveren.",
+ "404" : "404",
"Full name" : "Fulde navn",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Grænsen for brugere er nået, og den nye bruger er ikke blevet oprettet. Læs dine notifikationer for at lære mere.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kun følgende tegn kan indgå i et brugernavn: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/de.js b/lib/l10n/de.js
index 4adbe25257c..d9eeb02812d 100644
--- a/lib/l10n/de.js
+++ b/lib/l10n/de.js
@@ -5,9 +5,9 @@ OC.L10N.register(
"This can usually be fixed by giving the web server write access to the config directory." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird.",
"But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Wenn du jedoch möchtest dass die Datei config.php schreibgeschützt bleiben soll, dann setze die Option \"config_is_read_only\" in der Datei auf true.",
"See %s" : "Siehe %s",
+ "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üfe 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 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",
@@ -96,7 +96,7 @@ OC.L10N.register(
"Appearance and accessibility" : "Erscheinungsbild und Barrierefreiheit",
"Apps" : "Apps",
"Personal settings" : "Persönliche Einstellungen",
- "Administration settings" : "Verwaltungs-Einstellungen",
+ "Administration settings" : "Verwaltungseinstellungen",
"Settings" : "Einstellungen",
"Log out" : "Abmelden",
"Users" : "Benutzer",
@@ -106,8 +106,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",
@@ -155,6 +155,7 @@ OC.L10N.register(
"%1$s shared »%2$s« with you." : "%1$s hat »%2$s« mit dir geteilt.",
"Click the button below to open it." : "Klicke zum Öffnen auf die untere Schaltfläche.",
"The requested share does not exist anymore" : "Die angeforderte Freigabe existiert nicht mehr",
+ "The requested share comes from a disabled user" : "Die angeforderte Freigabe stammt von einem deaktivierten Benutzer",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Der Benutzer wurde nicht erstellt, da das Benutzerlimit erreicht wurde. Überprüfe deine Benachrichtigungen, um mehr zu erfahren.",
"Could not find category \"%s\"" : "Die Kategorie \"%s“ konnte nicht gefunden werden",
"Sunday" : "Sonntag",
@@ -260,10 +261,17 @@ OC.L10N.register(
"Storage connection error. %s" : "Verbindungsfehler zum Speicherplatz. %s",
"Storage is temporarily not available" : "Speicher ist vorübergehend nicht verfügbar",
"Storage connection timeout. %s" : "Zeitüberschreitung der Verbindung zum Speicherplatz. %s",
+ "Free prompt" : "Freie Eingabeaufforderung",
+ "Runs an arbitrary prompt through the language model." : "Führt eine beliebige Eingabeaufforderung über das Sprachmodell aus.",
+ "Generate headline" : "Überschrift erzeugen",
+ "Generates a possible headline for a text." : "Erzeugt eine mögliche Überschrift für einen Text.",
+ "Summarize" : "Zusammenfassen",
+ "Summarizes text by reducing its length without losing key information." : "Fasst Text zusammen, indem die Länge reduziert wird, ohne dass wichtige Informationen verloren gehen.",
+ "Extract topics" : "Themen extrahieren",
+ "Extracts topics from a text and outputs them separated by commas." : "Extrahiert Themen aus einem Text und gibt sie durch Kommas getrennt aus.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$swurden nicht korrekt ersetzt. Stelle sicher, dass es sich um eine mit dem Server kompatible Version handelt.",
+ "404" : "404",
"Full name" : "Vollständiger Name",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Das Benutzerlimit wurde erreicht und der Benutzer wurde nicht erstellt. Überprüfe deine Benachrichtigungen, um mehr zu erfahren.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“",
"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",
diff --git a/lib/l10n/de.json b/lib/l10n/de.json
index 1356295ad32..8cd0a9ef87d 100644
--- a/lib/l10n/de.json
+++ b/lib/l10n/de.json
@@ -3,9 +3,9 @@
"This can usually be fixed by giving the web server write access to the config directory." : "Dies kann normalerweise behoben werden, indem dem Webserver Schreibzugriff auf das config-Verzeichnis gegeben wird.",
"But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Wenn du jedoch möchtest dass die Datei config.php schreibgeschützt bleiben soll, dann setze die Option \"config_is_read_only\" in der Datei auf true.",
"See %s" : "Siehe %s",
+ "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üfe 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 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",
@@ -94,7 +94,7 @@
"Appearance and accessibility" : "Erscheinungsbild und Barrierefreiheit",
"Apps" : "Apps",
"Personal settings" : "Persönliche Einstellungen",
- "Administration settings" : "Verwaltungs-Einstellungen",
+ "Administration settings" : "Verwaltungseinstellungen",
"Settings" : "Einstellungen",
"Log out" : "Abmelden",
"Users" : "Benutzer",
@@ -104,8 +104,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",
@@ -153,6 +153,7 @@
"%1$s shared »%2$s« with you." : "%1$s hat »%2$s« mit dir geteilt.",
"Click the button below to open it." : "Klicke zum Öffnen auf die untere Schaltfläche.",
"The requested share does not exist anymore" : "Die angeforderte Freigabe existiert nicht mehr",
+ "The requested share comes from a disabled user" : "Die angeforderte Freigabe stammt von einem deaktivierten Benutzer",
"The user was not created because the user limit has been reached. Check your notifications to learn more." : "Der Benutzer wurde nicht erstellt, da das Benutzerlimit erreicht wurde. Überprüfe deine Benachrichtigungen, um mehr zu erfahren.",
"Could not find category \"%s\"" : "Die Kategorie \"%s“ konnte nicht gefunden werden",
"Sunday" : "Sonntag",
@@ -258,10 +259,17 @@
"Storage connection error. %s" : "Verbindungsfehler zum Speicherplatz. %s",
"Storage is temporarily not available" : "Speicher ist vorübergehend nicht verfügbar",
"Storage connection timeout. %s" : "Zeitüberschreitung der Verbindung zum Speicherplatz. %s",
+ "Free prompt" : "Freie Eingabeaufforderung",
+ "Runs an arbitrary prompt through the language model." : "Führt eine beliebige Eingabeaufforderung über das Sprachmodell aus.",
+ "Generate headline" : "Überschrift erzeugen",
+ "Generates a possible headline for a text." : "Erzeugt eine mögliche Überschrift für einen Text.",
+ "Summarize" : "Zusammenfassen",
+ "Summarizes text by reducing its length without losing key information." : "Fasst Text zusammen, indem die Länge reduziert wird, ohne dass wichtige Informationen verloren gehen.",
+ "Extract topics" : "Themen extrahieren",
+ "Extracts topics from a text and outputs them separated by commas." : "Extrahiert Themen aus einem Text und gibt sie durch Kommas getrennt aus.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$swurden nicht korrekt ersetzt. Stelle sicher, dass es sich um eine mit dem Server kompatible Version handelt.",
+ "404" : "404",
"Full name" : "Vollständiger Name",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Das Benutzerlimit wurde erreicht und der Benutzer wurde nicht erstellt. Überprüfe deine Benachrichtigungen, um mehr zu erfahren.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“",
"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",
diff --git a/lib/l10n/de_DE.js b/lib/l10n/de_DE.js
index 4b1a4c4c274..8371cf8ecd3 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",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Themen extrahieren",
"Extracts topics from a text and outputs them separated by commas." : "Extrahiert Themen aus einem Text und gibt sie durch Kommas getrennt aus.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$s wurden nicht korrekt ersetzt. Stellen Sie sicher, dass es sich um eine mit dem Server kompatible Version handelt.",
+ "404" : "404",
"Full name" : "Vollständiger Name",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Das Benutzerlimit wurde erreicht und der Benutzer wurde nicht erstellt. Überprüfen Sie Ihre Benachrichtigungen, um mehr zu erfahren.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“",
"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.",
diff --git a/lib/l10n/de_DE.json b/lib/l10n/de_DE.json
index 98681f76384..d217e7fe881 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",
@@ -269,9 +268,8 @@
"Extract topics" : "Themen extrahieren",
"Extracts topics from a text and outputs them separated by commas." : "Extrahiert Themen aus einem Text und gibt sie durch Kommas getrennt aus.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Die Dateien der App %1$s wurden nicht korrekt ersetzt. Stellen Sie sicher, dass es sich um eine mit dem Server kompatible Version handelt.",
+ "404" : "404",
"Full name" : "Vollständiger Name",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Das Benutzerlimit wurde erreicht und der Benutzer wurde nicht erstellt. Überprüfen Sie Ihre Benachrichtigungen, um mehr zu erfahren.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Folgende Zeichen sind im Benutzernamen erlaubt: „a-z“, „A-Z“, „0-9“ und „_.@-'“",
"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.",
diff --git a/lib/l10n/el.js b/lib/l10n/el.js
index fe2cea83496..60dcf04f384 100644
--- a/lib/l10n/el.js
+++ b/lib/l10n/el.js
@@ -251,7 +251,6 @@ OC.L10N.register(
"Storage connection timeout. %s" : "Λήξη χρονικού ορίου σύνδεσης με αποθηκευτικό χώρο.%s",
"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" : "Πλήρες όνομα",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Μόνο οι ακόλουθοι χαρακτήρες επιτρέπονται στο όνομα χρήστη; \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/el.json b/lib/l10n/el.json
index 5d8e3e87a85..033f476f6e6 100644
--- a/lib/l10n/el.json
+++ b/lib/l10n/el.json
@@ -249,7 +249,6 @@
"Storage connection timeout. %s" : "Λήξη χρονικού ορίου σύνδεσης με αποθηκευτικό χώρο.%s",
"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" : "Πλήρες όνομα",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Μόνο οι ακόλουθοι χαρακτήρες επιτρέπονται στο όνομα χρήστη; \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/en_GB.js b/lib/l10n/en_GB.js
index ed51e1fd5fc..b8264f5cf9b 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",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Extract topics",
"Extracts topics from a text and outputs them separated by commas." : "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." : "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server.",
+ "404" : "404",
"Full name" : "Full name",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "The user limit has been reached and the user was not created. Check your notifications to learn more.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/en_GB.json b/lib/l10n/en_GB.json
index 40563e1980e..9a1e921efd4 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",
@@ -269,9 +268,8 @@
"Extract topics" : "Extract topics",
"Extracts topics from a text and outputs them separated by commas." : "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." : "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server.",
+ "404" : "404",
"Full name" : "Full name",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "The user limit has been reached and the user was not created. Check your notifications to learn more.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/eo.js b/lib/l10n/eo.js
index 1c901f1febd..c6e4ee1153f 100644
--- a/lib/l10n/eo.js
+++ b/lib/l10n/eo.js
@@ -207,7 +207,6 @@ OC.L10N.register(
"Storage connection timeout. %s" : "Konekto al konservejo eltempiĝis. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "La dosieroj de la aplikaĵo %1$s ne estis ĝuste anstataŭigitaj. Certigu, ke tiu aplikaĵa versio kongruas la servilon.",
"Full name" : "Plena nomo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Nur sekvaj signoj estas permesitaj en uzantnomo: \"a-z“, \"A-Z“, \"0-9“ kaj \"_“ (substreko), \"@“ (ĉe), \"-“ (streketo), \"'“ (apostrofo), \".“ (punkto)",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 almenaŭ necesas. Nun %s estas instalita.",
"To fix this issue update your libxml2 version and restart your web server." : "Por ripari tiun problemon, ĝisdatigu vian version de libxml2, kaj restartigu la TTT-servilon."
},
diff --git a/lib/l10n/eo.json b/lib/l10n/eo.json
index c4bd6cdee15..be6de6aebd9 100644
--- a/lib/l10n/eo.json
+++ b/lib/l10n/eo.json
@@ -205,7 +205,6 @@
"Storage connection timeout. %s" : "Konekto al konservejo eltempiĝis. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "La dosieroj de la aplikaĵo %1$s ne estis ĝuste anstataŭigitaj. Certigu, ke tiu aplikaĵa versio kongruas la servilon.",
"Full name" : "Plena nomo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Nur sekvaj signoj estas permesitaj en uzantnomo: \"a-z“, \"A-Z“, \"0-9“ kaj \"_“ (substreko), \"@“ (ĉe), \"-“ (streketo), \"'“ (apostrofo), \".“ (punkto)",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 almenaŭ necesas. Nun %s estas instalita.",
"To fix this issue update your libxml2 version and restart your web server." : "Por ripari tiun problemon, ĝisdatigu vian version de libxml2, kaj restartigu la TTT-servilon."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
diff --git a/lib/l10n/es.js b/lib/l10n/es.js
index b80753e6e10..237fb7df4de 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",
@@ -238,7 +237,7 @@ OC.L10N.register(
"PHP setting \"%s\" is not set to \"%s\"." : "La opción PHP \"%s\" no es \"%s\".",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta configuración en php.ini hará que Nextcloud funcione de nuevo",
"<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> está establecida como <code>%s</code> en lugar del valor esperado: <code>0</code>.",
- "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Para arreglar este problema, establece <code>mbstring.func_overload</code> en<code>0</code> en tu php.ini.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Para arreglar este problema, establezca <code>mbstring.func_overload</code> en<code>0</code> en su php.ini.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado para eliminar bloques de documentos en línea. Esto hará que varias aplicaciones principales estén inaccesibles.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Probablemente esto venga a causa de la caché o un acelerador, tales como Zend OPcache o eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Los módulos PHP se han instalado, pero aparecen listados como si faltaran",
@@ -271,11 +270,10 @@ OC.L10N.register(
"Extract topics" : "Extraer tópicos",
"Extracts topics from a text and outputs them separated by commas." : "Extrae los tópicos de un texto y genera una salida separada por comas. ",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la app %1$s no se han reemplazado correctamente. Asegúrate de que es una versión compatible con el servidor.",
+ "404" : "404",
"Full name" : "Nombre completo",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "El límite de usuarios fue alcanzado y el usuario no fue creado. Compruebe sus notificaciones para aprender más.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo los siguientes caracteres están permitidos en un nombre de usuario: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
+ "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este problema, actualice su versión de libxml2 y reinicie 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."
},
diff --git a/lib/l10n/es.json b/lib/l10n/es.json
index a4870ed898d..26023daed56 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",
@@ -236,7 +235,7 @@
"PHP setting \"%s\" is not set to \"%s\"." : "La opción PHP \"%s\" no es \"%s\".",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustar esta configuración en php.ini hará que Nextcloud funcione de nuevo",
"<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> está establecida como <code>%s</code> en lugar del valor esperado: <code>0</code>.",
- "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Para arreglar este problema, establece <code>mbstring.func_overload</code> en<code>0</code> en tu php.ini.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Para arreglar este problema, establezca <code>mbstring.func_overload</code> en<code>0</code> en su php.ini.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP está aparentemente configurado para eliminar bloques de documentos en línea. Esto hará que varias aplicaciones principales estén inaccesibles.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Probablemente esto venga a causa de la caché o un acelerador, tales como Zend OPcache o eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Los módulos PHP se han instalado, pero aparecen listados como si faltaran",
@@ -269,11 +268,10 @@
"Extract topics" : "Extraer tópicos",
"Extracts topics from a text and outputs them separated by commas." : "Extrae los tópicos de un texto y genera una salida separada por comas. ",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la app %1$s no se han reemplazado correctamente. Asegúrate de que es una versión compatible con el servidor.",
+ "404" : "404",
"Full name" : "Nombre completo",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "El límite de usuarios fue alcanzado y el usuario no fue creado. Compruebe sus notificaciones para aprender más.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo los siguientes caracteres están permitidos en un nombre de usuario: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
+ "To fix this issue update your libxml2 version and restart your web server." : "Para corregir este problema, actualice su versión de libxml2 y reinicie 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."
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_419.js b/lib/l10n/es_419.js
index 86516d683c5..0158f81181b 100644
--- a/lib/l10n/es_419.js
+++ b/lib/l10n/es_419.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_419.json b/lib/l10n/es_419.json
index b28ccc86df2..6329d0fc4ab 100644
--- a/lib/l10n/es_419.json
+++ b/lib/l10n/es_419.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_AR.js b/lib/l10n/es_AR.js
index 04215531e00..f43b06eda69 100644
--- a/lib/l10n/es_AR.js
+++ b/lib/l10n/es_AR.js
@@ -152,7 +152,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "Se agotó el tiempo de conexión del almacenamiento. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el nombre de usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s esta instalado. ",
"To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, favor de actualizar la versión de su libxml2 y reinicie su servidor web. "
},
diff --git a/lib/l10n/es_AR.json b/lib/l10n/es_AR.json
index 70d428a358a..db1390419af 100644
--- a/lib/l10n/es_AR.json
+++ b/lib/l10n/es_AR.json
@@ -150,7 +150,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "Se agotó el tiempo de conexión del almacenamiento. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el nombre de usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Se requiere de por lo menos libxml2 2.7.0. Actualmente %s esta instalado. ",
"To fix this issue update your libxml2 version and restart your web server." : "Para corregir este tema, favor de actualizar la versión de su libxml2 y reinicie su servidor web. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_CL.js b/lib/l10n/es_CL.js
index c2e7ed869a7..f1818744036 100644
--- a/lib/l10n/es_CL.js
+++ b/lib/l10n/es_CL.js
@@ -167,7 +167,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_CL.json b/lib/l10n/es_CL.json
index 3588d3dc997..dda2c9b6742 100644
--- a/lib/l10n/es_CL.json
+++ b/lib/l10n/es_CL.json
@@ -165,7 +165,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_CO.js b/lib/l10n/es_CO.js
index 5ed6e885be4..f380d43e612 100644
--- a/lib/l10n/es_CO.js
+++ b/lib/l10n/es_CO.js
@@ -167,7 +167,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_CO.json b/lib/l10n/es_CO.json
index 8a09a43010e..f39e1db30d2 100644
--- a/lib/l10n/es_CO.json
+++ b/lib/l10n/es_CO.json
@@ -165,7 +165,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_CR.js b/lib/l10n/es_CR.js
index dfe7bf48830..7010e22f6cc 100644
--- a/lib/l10n/es_CR.js
+++ b/lib/l10n/es_CR.js
@@ -167,7 +167,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_CR.json b/lib/l10n/es_CR.json
index 3965d35193b..0093ba7d79c 100644
--- a/lib/l10n/es_CR.json
+++ b/lib/l10n/es_CR.json
@@ -165,7 +165,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_DO.js b/lib/l10n/es_DO.js
index d283c462ee5..5abc9e9a23c 100644
--- a/lib/l10n/es_DO.js
+++ b/lib/l10n/es_DO.js
@@ -167,7 +167,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_DO.json b/lib/l10n/es_DO.json
index 9fe1811cf68..65f7f9bd9dd 100644
--- a/lib/l10n/es_DO.json
+++ b/lib/l10n/es_DO.json
@@ -165,7 +165,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_EC.js b/lib/l10n/es_EC.js
index dd170b3d912..0a8cc39fe7b 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",
@@ -262,9 +261,8 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la aplicación %1$s no se reemplazaron correctamente. Asegúrate de que sea una versión compatible con el servidor.",
+ "404" : "404",
"Full name" : "Nombre completo",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Se ha alcanzado el límite de usuarios y no se creó el usuario. Consulta tus notificaciones para obtener más información.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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.",
diff --git a/lib/l10n/es_EC.json b/lib/l10n/es_EC.json
index 55b7dcd4d84..21bc698d220 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",
@@ -260,9 +259,8 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Los archivos de la aplicación %1$s no se reemplazaron correctamente. Asegúrate de que sea una versión compatible con el servidor.",
+ "404" : "404",
"Full name" : "Nombre completo",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Se ha alcanzado el límite de usuarios y no se creó el usuario. Consulta tus notificaciones para obtener más información.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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.",
diff --git a/lib/l10n/es_GT.js b/lib/l10n/es_GT.js
index fa48c1305cf..d9378f41152 100644
--- a/lib/l10n/es_GT.js
+++ b/lib/l10n/es_GT.js
@@ -167,7 +167,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_GT.json b/lib/l10n/es_GT.json
index a00bb9cdb3f..d25ffbb1373 100644
--- a/lib/l10n/es_GT.json
+++ b/lib/l10n/es_GT.json
@@ -165,7 +165,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_HN.js b/lib/l10n/es_HN.js
index a45690ad4df..1b9cfa0a62c 100644
--- a/lib/l10n/es_HN.js
+++ b/lib/l10n/es_HN.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_HN.json b/lib/l10n/es_HN.json
index b98ec283c56..ad051d0398c 100644
--- a/lib/l10n/es_HN.json
+++ b/lib/l10n/es_HN.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_MX.js b/lib/l10n/es_MX.js
index 4aeedbb5b2b..2006abb8f08 100644
--- a/lib/l10n/es_MX.js
+++ b/lib/l10n/es_MX.js
@@ -168,7 +168,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_MX.json b/lib/l10n/es_MX.json
index 3e336401cee..8e64cdb87e5 100644
--- a/lib/l10n/es_MX.json
+++ b/lib/l10n/es_MX.json
@@ -166,7 +166,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_NI.js b/lib/l10n/es_NI.js
index 0a969595b2e..1a8504fccb2 100644
--- a/lib/l10n/es_NI.js
+++ b/lib/l10n/es_NI.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_NI.json b/lib/l10n/es_NI.json
index d3bb152cf38..e82d9462465 100644
--- a/lib/l10n/es_NI.json
+++ b/lib/l10n/es_NI.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_PA.js b/lib/l10n/es_PA.js
index d8fa9ad67d6..4208f1f50fe 100644
--- a/lib/l10n/es_PA.js
+++ b/lib/l10n/es_PA.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_PA.json b/lib/l10n/es_PA.json
index 8083d0164d1..4d7e4df076a 100644
--- a/lib/l10n/es_PA.json
+++ b/lib/l10n/es_PA.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_PE.js b/lib/l10n/es_PE.js
index 558959bf2f6..9341f68455e 100644
--- a/lib/l10n/es_PE.js
+++ b/lib/l10n/es_PE.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_PE.json b/lib/l10n/es_PE.json
index 20e5f5f30d5..69be5d87aa6 100644
--- a/lib/l10n/es_PE.json
+++ b/lib/l10n/es_PE.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_PR.js b/lib/l10n/es_PR.js
index b1639dc9ffc..043b3762a92 100644
--- a/lib/l10n/es_PR.js
+++ b/lib/l10n/es_PR.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_PR.json b/lib/l10n/es_PR.json
index 017cfd67d22..4ffffd379e4 100644
--- a/lib/l10n/es_PR.json
+++ b/lib/l10n/es_PR.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_PY.js b/lib/l10n/es_PY.js
index 0de414992a7..d7979eb26d0 100644
--- a/lib/l10n/es_PY.js
+++ b/lib/l10n/es_PY.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_PY.json b/lib/l10n/es_PY.json
index b2fa427189b..dfee50f68e8 100644
--- a/lib/l10n/es_PY.json
+++ b/lib/l10n/es_PY.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_SV.js b/lib/l10n/es_SV.js
index e5d0f41c17b..1edd6ee6594 100644
--- a/lib/l10n/es_SV.js
+++ b/lib/l10n/es_SV.js
@@ -167,7 +167,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_SV.json b/lib/l10n/es_SV.json
index 4d8379f16b5..e8b7e814efe 100644
--- a/lib/l10n/es_SV.json
+++ b/lib/l10n/es_SV.json
@@ -165,7 +165,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/es_UY.js b/lib/l10n/es_UY.js
index cf73ba599d3..39decb0428d 100644
--- a/lib/l10n/es_UY.js
+++ b/lib/l10n/es_UY.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},
diff --git a/lib/l10n/es_UY.json b/lib/l10n/es_UY.json
index b1630748484..fbfc7d7a936 100644
--- a/lib/l10n/es_UY.json
+++ b/lib/l10n/es_UY.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "El almacenamieto se encuentra temporalmente no disponible",
"Storage connection timeout. %s" : "El tiempo de la conexión del almacenamiento se agotó. %s",
"Full name" : "Nombre completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Sólo se permiten los siguientes caracteres en el usuario: \"a-z\", \"A-Z\", \"0-9\" y \"_.@-'\"",
"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. "
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
diff --git a/lib/l10n/et_EE.js b/lib/l10n/et_EE.js
index ab62f94437e..991b0d9f689 100644
--- a/lib/l10n/et_EE.js
+++ b/lib/l10n/et_EE.js
@@ -152,7 +152,6 @@ OC.L10N.register(
"Could not obtain lock type %d on \"%s\"." : "Ei suutnud hankida %d tüüpi lukustust \"%s\".",
"Storage is temporarily not available" : "Salvestusruum pole ajutiselt kättesaadav",
"Full name" : "Täisnimi",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kasutajanimes on lubatud ainult järgmised sümbolid: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Vaja on vähemalt libxml2 2.7.0. Hetkel on installitud %s.",
"Please upgrade your database version." : "Palun uuenda oma andmebaasi versioon"
},
diff --git a/lib/l10n/et_EE.json b/lib/l10n/et_EE.json
index 27abedc8f33..8fc5045ad9f 100644
--- a/lib/l10n/et_EE.json
+++ b/lib/l10n/et_EE.json
@@ -150,7 +150,6 @@
"Could not obtain lock type %d on \"%s\"." : "Ei suutnud hankida %d tüüpi lukustust \"%s\".",
"Storage is temporarily not available" : "Salvestusruum pole ajutiselt kättesaadav",
"Full name" : "Täisnimi",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kasutajanimes on lubatud ainult järgmised sümbolid: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Vaja on vähemalt libxml2 2.7.0. Hetkel on installitud %s.",
"Please upgrade your database version." : "Palun uuenda oma andmebaasi versioon"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
diff --git a/lib/l10n/eu.js b/lib/l10n/eu.js
index 5b80a7c1a09..71293e24543 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",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Atera gaiak",
"Extracts topics from a text and outputs them separated by commas." : "Gaiak ateratzen ditu testu batetik eta komaz banatuta erakusten ditu.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s aplikazioaren fitxategiak ez dira behar bezala ordezkatu. Ziurtatu zerbitzariarekin bateragarria den bertsioa dela.",
+ "404" : "404",
"Full name" : "Izen osoa",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Ezin izan da erabiltzailea sortu, erabiltzaile muga gainditu delako. Egiaztatu zure jakinarazpenak gehiago jakiteko.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Erabiltzaile-izenean karaktere hauek soilik erabil daitezke: \"a-z\", \"A-Z\", \"0-9\", eta \"_.@-'\"",
"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",
diff --git a/lib/l10n/eu.json b/lib/l10n/eu.json
index 1f63ae8b053..993f78d836c 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",
@@ -269,9 +268,8 @@
"Extract topics" : "Atera gaiak",
"Extracts topics from a text and outputs them separated by commas." : "Gaiak ateratzen ditu testu batetik eta komaz banatuta erakusten ditu.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s aplikazioaren fitxategiak ez dira behar bezala ordezkatu. Ziurtatu zerbitzariarekin bateragarria den bertsioa dela.",
+ "404" : "404",
"Full name" : "Izen osoa",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Ezin izan da erabiltzailea sortu, erabiltzaile muga gainditu delako. Egiaztatu zure jakinarazpenak gehiago jakiteko.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Erabiltzaile-izenean karaktere hauek soilik erabil daitezke: \"a-z\", \"A-Z\", \"0-9\", eta \"_.@-'\"",
"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",
diff --git a/lib/l10n/fa.js b/lib/l10n/fa.js
index 6c69b85d691..f45a33ffcf8 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",
@@ -57,7 +56,7 @@ OC.L10N.register(
"Invalid image" : "عکس نامعتبر",
"Avatar image is not square" : "تصویر آواتار مربع نیست",
"Files" : "پوشه‌ها",
- "View profile" : "مشاهده پروفایل",
+ "View profile" : "مشاهدهٔ نمایه",
"Local time: %s" : "Local time: %s",
"today" : "امروز",
"tomorrow" : "فردا",
@@ -270,9 +269,8 @@ OC.L10N.register(
"Extract topics" : "Extract topics",
"Extracts topics from a text and outputs them separated by commas." : "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به درستی تعویض نشد. اطمینان حاصل کنید که این یک نسخه سازگار با سرور است.",
+ "404" : "۴۰۴",
"Full name" : "نام کامل",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "The user limit has been reached and the user was not created. Check your notifications to learn more.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "تنها نویسه‌ها زیر در نام کاربری مجازند: \"a-z\" ، \"A-Z\" ، \"0-9\" و \"_. @ - '\"",
"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.",
diff --git a/lib/l10n/fa.json b/lib/l10n/fa.json
index a5024351f34..cf6b800b850 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",
@@ -55,7 +54,7 @@
"Invalid image" : "عکس نامعتبر",
"Avatar image is not square" : "تصویر آواتار مربع نیست",
"Files" : "پوشه‌ها",
- "View profile" : "مشاهده پروفایل",
+ "View profile" : "مشاهدهٔ نمایه",
"Local time: %s" : "Local time: %s",
"today" : "امروز",
"tomorrow" : "فردا",
@@ -268,9 +267,8 @@
"Extract topics" : "Extract topics",
"Extracts topics from a text and outputs them separated by commas." : "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به درستی تعویض نشد. اطمینان حاصل کنید که این یک نسخه سازگار با سرور است.",
+ "404" : "۴۰۴",
"Full name" : "نام کامل",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "The user limit has been reached and the user was not created. Check your notifications to learn more.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "تنها نویسه‌ها زیر در نام کاربری مجازند: \"a-z\" ، \"A-Z\" ، \"0-9\" و \"_. @ - '\"",
"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.",
diff --git a/lib/l10n/fi.js b/lib/l10n/fi.js
index 7eb441edd14..b904c582b3c 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.",
@@ -226,9 +225,8 @@ OC.L10N.register(
"Storage is temporarily not available" : "Tallennustila on tilapäisesti pois käytöstä",
"Storage connection timeout. %s" : "Tallennustilan yhteyden aikakatkaisu. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Sovelluksen %1$s tiedostoja ei korvattu oikein Varmista, että sen versio on yhteensopiva palvelimen kanssa.",
+ "404" : "404",
"Full name" : "Koko nimi",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Käyttäjää ei luotu, koska käyttäjäraja on tullut täyteen. Tarkista ilmoitukset saadaksesi lisätietoja.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Vain seuraavat merkit ovat sallittuja käyttäjätunnuksessa: \"a-z\", \"A-Z\", \"0-9\" ja \"_.@-'\"",
"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.",
diff --git a/lib/l10n/fi.json b/lib/l10n/fi.json
index 3ec59a0eff9..701aed759bf 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.",
@@ -224,9 +223,8 @@
"Storage is temporarily not available" : "Tallennustila on tilapäisesti pois käytöstä",
"Storage connection timeout. %s" : "Tallennustilan yhteyden aikakatkaisu. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Sovelluksen %1$s tiedostoja ei korvattu oikein Varmista, että sen versio on yhteensopiva palvelimen kanssa.",
+ "404" : "404",
"Full name" : "Koko nimi",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Käyttäjää ei luotu, koska käyttäjäraja on tullut täyteen. Tarkista ilmoitukset saadaksesi lisätietoja.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Vain seuraavat merkit ovat sallittuja käyttäjätunnuksessa: \"a-z\", \"A-Z\", \"0-9\" ja \"_.@-'\"",
"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.",
diff --git a/lib/l10n/fr.js b/lib/l10n/fr.js
index 7f2c41885b5..604a60ac333 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",
@@ -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,7 +130,7 @@ 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é.",
+ "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.",
@@ -149,7 +148,7 @@ 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",
@@ -158,7 +157,7 @@ OC.L10N.register(
"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",
@@ -213,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",
@@ -222,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",
@@ -235,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.",
@@ -256,13 +255,14 @@ 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",
@@ -270,9 +270,8 @@ OC.L10N.register(
"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.",
+ "404" : "404",
"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.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Seuls les caractères suivants sont autorisés dans un nom d'utilisateur : \"a-z\", \"A-Z\", \"0-9\", \"_@-\" et \".\" (le point)",
"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.",
diff --git a/lib/l10n/fr.json b/lib/l10n/fr.json
index ee4b2af4947..9fc505c49a0 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",
@@ -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,7 +128,7 @@
"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é.",
+ "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.",
@@ -147,7 +146,7 @@
"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",
@@ -156,7 +155,7 @@
"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",
@@ -211,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",
@@ -220,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",
@@ -233,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.",
@@ -254,13 +253,14 @@
"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",
@@ -268,9 +268,8 @@
"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.",
+ "404" : "404",
"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.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Seuls les caractères suivants sont autorisés dans un nom d'utilisateur : \"a-z\", \"A-Z\", \"0-9\", \"_@-\" et \".\" (le point)",
"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.",
diff --git a/lib/l10n/gl.js b/lib/l10n/gl.js
index 46440e27bcc..5cff04260bb 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",
@@ -63,19 +62,19 @@ OC.L10N.register(
"tomorrow" : "mañá",
"yesterday" : "onte",
"_in %n day_::_in %n days_" : ["en %n día","en %n días"],
- "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n días"],
+ "_%n day ago_::_%n days ago_" : ["Hai %n día","Hai %n días"],
"next month" : "o vindeiro mes",
"last month" : "o mes pasado",
"_in %n month_::_in %n months_" : ["en %n mes","en %n meses"],
- "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"],
+ "_%n month ago_::_%n months ago_" : ["Hai %n mes","Hai %n meses"],
"next year" : "o vindeiro ano",
"last year" : "o ano pasado",
"_in %n year_::_in %n years_" : ["en %n ano","en %n anos"],
- "_%n year ago_::_%n years ago_" : ["hai %n ano","hai %n anos"],
+ "_%n year ago_::_%n years ago_" : ["Hai %n ano","Hai %n anos"],
"_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"],
- "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n horas"],
+ "_%n hour ago_::_%n hours ago_" : ["Hai %n hora","Hai %n horas"],
"_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"],
- "_%n minute ago_::_%n minutes ago_" : ["hai %n minuto","hai %n minutos"],
+ "_%n minute ago_::_%n minutes ago_" : ["Hai %n minuto","Hai %n minutos"],
"in a few seconds" : "en poucos segundos",
"seconds ago" : "segundos atrás",
"Empty file" : "Ficheiro baleiro",
@@ -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.",
@@ -271,12 +270,11 @@ OC.L10N.register(
"Extract topics" : "Extraer temas",
"Extracts topics from a text and outputs them separated by commas." : "Extrae temas dun texto e amósaos separados por comas.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os ficheiros da aplicación %1$s non foron substituídos correctamente. Asegúrese que é unha versión compatíbel co servidor.",
+ "404" : "404",
"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.",
+ "PostgreSQL >= 9 required." : "Precísase de PostgreSQL >= 9.",
"Please upgrade your database version." : "Anove a versión da súa base de datos"
},
"nplurals=2; plural=(n != 1);");
diff --git a/lib/l10n/gl.json b/lib/l10n/gl.json
index 982199d4f7c..8f052ed1510 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",
@@ -61,19 +60,19 @@
"tomorrow" : "mañá",
"yesterday" : "onte",
"_in %n day_::_in %n days_" : ["en %n día","en %n días"],
- "_%n day ago_::_%n days ago_" : ["hai %n día","hai %n días"],
+ "_%n day ago_::_%n days ago_" : ["Hai %n día","Hai %n días"],
"next month" : "o vindeiro mes",
"last month" : "o mes pasado",
"_in %n month_::_in %n months_" : ["en %n mes","en %n meses"],
- "_%n month ago_::_%n months ago_" : ["hai %n mes","hai %n meses"],
+ "_%n month ago_::_%n months ago_" : ["Hai %n mes","Hai %n meses"],
"next year" : "o vindeiro ano",
"last year" : "o ano pasado",
"_in %n year_::_in %n years_" : ["en %n ano","en %n anos"],
- "_%n year ago_::_%n years ago_" : ["hai %n ano","hai %n anos"],
+ "_%n year ago_::_%n years ago_" : ["Hai %n ano","Hai %n anos"],
"_in %n hour_::_in %n hours_" : ["en %n hora","en %n horas"],
- "_%n hour ago_::_%n hours ago_" : ["hai %n hora","hai %n horas"],
+ "_%n hour ago_::_%n hours ago_" : ["Hai %n hora","Hai %n horas"],
"_in %n minute_::_in %n minutes_" : ["en %n minuto","en %n minutos"],
- "_%n minute ago_::_%n minutes ago_" : ["hai %n minuto","hai %n minutos"],
+ "_%n minute ago_::_%n minutes ago_" : ["Hai %n minuto","Hai %n minutos"],
"in a few seconds" : "en poucos segundos",
"seconds ago" : "segundos atrás",
"Empty file" : "Ficheiro baleiro",
@@ -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.",
@@ -269,12 +268,11 @@
"Extract topics" : "Extraer temas",
"Extracts topics from a text and outputs them separated by commas." : "Extrae temas dun texto e amósaos separados por comas.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os ficheiros da aplicación %1$s non foron substituídos correctamente. Asegúrese que é unha versión compatíbel co servidor.",
+ "404" : "404",
"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.",
+ "PostgreSQL >= 9 required." : "Precísase de PostgreSQL >= 9.",
"Please upgrade your database version." : "Anove a versión da súa base de datos"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/lib/l10n/he.js b/lib/l10n/he.js
index 1d153513026..1df3c86e215 100644
--- a/lib/l10n/he.js
+++ b/lib/l10n/he.js
@@ -182,7 +182,6 @@ OC.L10N.register(
"Storage connection timeout. %s" : "פסק זמן חיבור אחסון. %s",
"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" : "שם מלא",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "רק התווים הבאים מאושרים לשם משתמש: \"a-z\", \"A-Z\", \"0-9\", וגם \"_.@-'\"",
"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 שלך ולהפעיל מחדש את שרת האינטרנט שלך."
},
diff --git a/lib/l10n/he.json b/lib/l10n/he.json
index 2bcce522fcc..c1d3fe3e3e7 100644
--- a/lib/l10n/he.json
+++ b/lib/l10n/he.json
@@ -180,7 +180,6 @@
"Storage connection timeout. %s" : "פסק זמן חיבור אחסון. %s",
"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" : "שם מלא",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "רק התווים הבאים מאושרים לשם משתמש: \"a-z\", \"A-Z\", \"0-9\", וגם \"_.@-'\"",
"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 שלך ולהפעיל מחדש את שרת האינטרנט שלך."
},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;"
diff --git a/lib/l10n/hr.js b/lib/l10n/hr.js
index 8885c82fb35..bc50a83b63b 100644
--- a/lib/l10n/hr.js
+++ b/lib/l10n/hr.js
@@ -227,7 +227,6 @@ OC.L10N.register(
"Storage connection timeout. %s" : "Istek veze pohrane. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke aplikacije %1$s nisu ispravno zamijenjene. Provjerite je li inačica kompatibilna s poslužiteljem.",
"Full name" : "Puno ime",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "U korisničkom imenu dopušteni su samo sljedeći znakovi: „a – z”, „A – Z”, „0 – 9” i „_.@-'”",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Potreban je barem Libxml2 2.7.0. Trenutno je instalirana inačica %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Kako biste riješili ovaj problem, ažurirajte svoju inačicu libxml2 i ponovno pokrenite web poslužitelj."
},
diff --git a/lib/l10n/hr.json b/lib/l10n/hr.json
index 5417bf218ae..83006ef7ce1 100644
--- a/lib/l10n/hr.json
+++ b/lib/l10n/hr.json
@@ -225,7 +225,6 @@
"Storage connection timeout. %s" : "Istek veze pohrane. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke aplikacije %1$s nisu ispravno zamijenjene. Provjerite je li inačica kompatibilna s poslužiteljem.",
"Full name" : "Puno ime",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "U korisničkom imenu dopušteni su samo sljedeći znakovi: „a – z”, „A – Z”, „0 – 9” i „_.@-'”",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Potreban je barem Libxml2 2.7.0. Trenutno je instalirana inačica %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Kako biste riješili ovaj problem, ažurirajte svoju inačicu libxml2 i ponovno pokrenite web poslužitelj."
},"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;"
diff --git a/lib/l10n/hu.js b/lib/l10n/hu.js
index 6631c56a18a..f1b679d1577 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",
@@ -264,16 +263,15 @@ OC.L10N.register(
"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.",
+ "Generate headline" : "Címsor előállítása",
+ "Generates a possible headline for a text." : "Egy lehetséges címsort állít elő egy szöveghez.",
"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",
+ "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 kulcsinformációk elvesznének.",
+ "Extract topics" : "Témák kinyerése",
+ "Extracts topics from a text and outputs them separated by commas." : "Kinyeri a témákat a szövegből, és vesszőkkel 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.",
+ "404" : "404",
"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.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "A felhasználónévben csak a következő karakterek engedélyezettek: „a-z”, „A-Z”, „0-9”, és „_.@-'”",
"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.",
diff --git a/lib/l10n/hu.json b/lib/l10n/hu.json
index 9a08f4f048e..8aad272c59f 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",
@@ -262,16 +261,15 @@
"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.",
+ "Generate headline" : "Címsor előállítása",
+ "Generates a possible headline for a text." : "Egy lehetséges címsort állít elő egy szöveghez.",
"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",
+ "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 kulcsinformációk elvesznének.",
+ "Extract topics" : "Témák kinyerése",
+ "Extracts topics from a text and outputs them separated by commas." : "Kinyeri a témákat a szövegből, és vesszőkkel 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.",
+ "404" : "404",
"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.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "A felhasználónévben csak a következő karakterek engedélyezettek: „a-z”, „A-Z”, „0-9”, és „_.@-'”",
"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.",
diff --git a/lib/l10n/id.js b/lib/l10n/id.js
index 22c945924d7..2db32b3692f 100644
--- a/lib/l10n/id.js
+++ b/lib/l10n/id.js
@@ -129,7 +129,7 @@ OC.L10N.register(
"Token expired. Please reload page." : "Token sudah kedaluwarsa. Silakan muat ulang halaman.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Tidak ada driver (sqlite, mysql, or postgresql) yang terinstal.",
"PHP module %s not installed." : "Module PHP %s tidak terinstal.",
- "Please ask your server administrator to install the module." : "Mohon tanyakan administrator Anda untuk menginstal module.",
+ "Please ask your server administrator to install the module." : "Mohon tanyakan administrator Anda untuk menginstal modul.",
"PHP setting \"%s\" is not set to \"%s\"." : "Pengaturan PHP \"%s\" tidak diatur ke \"%s\".",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Menyesuaikan pengaturan ini di php.ini akan membuat Nextcloud berjalan kembali",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Tampaknya PHP diatur untuk memotong inline doc blocks. Hal ini akan menyebabkan beberapa aplikasi inti menjadi tidak dapat diakses.",
@@ -144,7 +144,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "Penyimpanan sementara tidak tersedia",
"Storage connection timeout. %s" : "Koneksi penyimpanan waktu-habis. %s",
"Full name" : "Nama lengkap",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Hanya karakter ini yang diizinkan dalam nama pengguna: \"a-z\", \"A-Z\", \"0-9\", dan \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Setidaknya libxml2 2.7.0 dibutuhkan. Saat ini %s dipasang.",
"To fix this issue update your libxml2 version and restart your web server." : "Untuk mengatasi masalah ini, perbarui versi libxml2 Anda dan mulai-ulang server web Anda."
},
diff --git a/lib/l10n/id.json b/lib/l10n/id.json
index 0950b536d82..4e39125f693 100644
--- a/lib/l10n/id.json
+++ b/lib/l10n/id.json
@@ -127,7 +127,7 @@
"Token expired. Please reload page." : "Token sudah kedaluwarsa. Silakan muat ulang halaman.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Tidak ada driver (sqlite, mysql, or postgresql) yang terinstal.",
"PHP module %s not installed." : "Module PHP %s tidak terinstal.",
- "Please ask your server administrator to install the module." : "Mohon tanyakan administrator Anda untuk menginstal module.",
+ "Please ask your server administrator to install the module." : "Mohon tanyakan administrator Anda untuk menginstal modul.",
"PHP setting \"%s\" is not set to \"%s\"." : "Pengaturan PHP \"%s\" tidak diatur ke \"%s\".",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Menyesuaikan pengaturan ini di php.ini akan membuat Nextcloud berjalan kembali",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "Tampaknya PHP diatur untuk memotong inline doc blocks. Hal ini akan menyebabkan beberapa aplikasi inti menjadi tidak dapat diakses.",
@@ -142,7 +142,6 @@
"Storage is temporarily not available" : "Penyimpanan sementara tidak tersedia",
"Storage connection timeout. %s" : "Koneksi penyimpanan waktu-habis. %s",
"Full name" : "Nama lengkap",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Hanya karakter ini yang diizinkan dalam nama pengguna: \"a-z\", \"A-Z\", \"0-9\", dan \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Setidaknya libxml2 2.7.0 dibutuhkan. Saat ini %s dipasang.",
"To fix this issue update your libxml2 version and restart your web server." : "Untuk mengatasi masalah ini, perbarui versi libxml2 Anda dan mulai-ulang server web Anda."
},"pluralForm" :"nplurals=1; plural=0;"
diff --git a/lib/l10n/is.js b/lib/l10n/is.js
index 512e2d5d831..88b58843bf2 100644
--- a/lib/l10n/is.js
+++ b/lib/l10n/is.js
@@ -2,9 +2,19 @@ OC.L10N.register(
"lib",
{
"Cannot write into \"config\" directory!" : "Get ekki skrifað í \"config\" möppuna!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "En ef þú vilt halda config.php skránni einungis til lesanlegri, skaltu setja valkostinn \"config_is_read_only\" á 'true' í henni.",
"See %s" : "Skoðaðu %s",
+ "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Forritið %1$s er ekki til staðar eða er af útgáfu sem ekki er samhæfð þessum netþjóni. Endilega skoðaðu í forritamöppuna.",
"Sample configuration detected" : "Fann sýnisuppsetningu",
"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" : "Komið hefur í ljós að sýniuppsetningin var afrituð. Þetta getur skemmt uppsetninguna og er ekki stutt. Endilega lestu hjálparskjölin áður en þú gerir breytingar á config.php",
+ "The page could not be found on the server." : "Síðan fannst ekki á netþjóninum.",
+ "%s email verification" : "Sannvottun tölvupósts fyrir %s",
+ "Email verification" : "Sannvottun tölvupósts",
+ "Click the following button to confirm your email." : "Smelltu á eftirfarandi hnapp til að staðfesta tölvupóstfangið þitt.",
+ "Click the following link to confirm your email." : "Smelltu á eftirfarandi tengil til að staðfesta tölvupóstfangið þitt.",
+ "Confirm your email" : "Staðfestu tölvupóstfangið þitt",
+ "Other activities" : "Aðrar athafnir",
"%1$s and %2$s" : "%1$s og %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s",
"%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s",
@@ -12,16 +22,21 @@ OC.L10N.register(
"Education Edition" : "Kennsluútgáfa",
"Enterprise bundle" : "Fyrirtækjavöndull",
"Groupware bundle" : "Hópvinnsluvöndull",
+ "Hub bundle" : "Tengivöndull",
"Social sharing bundle" : "Deilivöndull fyrir samfélagsmiðla",
"PHP %s or higher is required." : "Krafist er PHP %s eða hærra.",
"PHP with a version lower than %s is required." : "Krafist er PHP útgáfu %s eða lægri.",
"%sbit or higher PHP required." : "Krafist er PHP %sbita eða hærra.",
+ "The following architectures are supported: %s" : "Eftirfarandi tölvukerfi eru studd: %s",
+ "The following databases are supported: %s" : "Eftirfarandi gagnagrunnar eru studdir: %s",
"The command line tool %s could not be found" : "Skipanalínutólið \"%s\" fannst ekki",
"The library %s is not available." : "Aðgerðasafnið %s er ekki tiltækt.",
"Library %1$s with a version higher than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu hærri en %2$s - tiltæk útgáfa er %3$s.",
"Library %1$s with a version lower than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu lægri en %2$s - tiltæk útgáfa er %3$s.",
+ "The following platforms are supported: %s" : "Eftirfarandi stýrikerfi eru studd: %s",
"Server version %s or higher is required." : "Krafist er þjóns af útgáfu %s eða hærra.",
"Server version %s or lower is required." : "Krafist er þjóns af útgáfu %s eða lægri.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "Innskráður notandi verður að vera kerfisstjóri eða undirstjórnandi eða hafa fengið sérstaka aðgangsheimild fyrir þessa stillingu",
"Logged in user must be an admin or sub admin" : "Innskráður notandi verður að vera kerfisstjóri eða undirstjórnandi",
"Logged in user must be an admin" : "Innskráður notandi verður að vera stjórnandi",
"Wiping of device %s has started" : "Útþurrkun af tækinu %s er byrjuð",
@@ -41,6 +56,8 @@ OC.L10N.register(
"Invalid image" : "Ógild mynd",
"Avatar image is not square" : "Auðkennismynd er ekki ferningslaga",
"Files" : "Skrár",
+ "View profile" : "Skoða notandasnið",
+ "Local time: %s" : "Staðartími: %s",
"today" : "í dag",
"tomorrow" : "á morgun",
"yesterday" : "í gær",
@@ -60,8 +77,11 @@ OC.L10N.register(
"_%n minute ago_::_%n minutes ago_" : ["fyrir %n mínútu síðan","fyrir %n mínútum síðan"],
"in a few seconds" : "eftir örfáar sekúndur",
"seconds ago" : "sekúndum síðan",
+ "Empty file" : "Tóm skrá",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Eining með auðkenni: %s er ekki til. Virkjaðu hana í forritastillingum eða hafðu samband við kerfisstjóra.",
"File already exists" : "Skrá er þegar til",
+ "Invalid path" : "Ógild slóð",
+ "Failed to create file from template" : "Mistókst að búa til skrá út frá sniðmáti",
"Templates" : "Sniðmát",
"File name is a reserved word" : "Skráarheiti er þegar frátekið orð",
"File name contains at least one invalid character" : "Skráarheitið inniheldur að minnsta kosti einn ógildan staf",
@@ -73,21 +93,37 @@ OC.L10N.register(
"__language_name__" : "Íslenska",
"This is an automatically sent email, please do not reply." : "Þetta er sjálfvirk tölvupóstsending, ekki svara þessu.",
"Help" : "Hjálp",
+ "Appearance and accessibility" : "Útlit og aðgengi",
"Apps" : "Forrit",
+ "Personal settings" : "Persónulegar stillingar",
+ "Administration settings" : "Stillingar stjórnunar",
"Settings" : "Stillingar",
"Log out" : "Skrá út",
"Users" : "Notendur",
"Email" : "Tölvupóstur",
+ "Mail %s" : "Póstur %s",
+ "Fediverse" : "Skýjasamband",
+ "View %s on the fediverse" : "Skoða %s á skýjasambandi (fediverse)",
"Phone" : "Sími",
+ "Call %s" : "Hringja í %s",
"Twitter" : "Twitter",
+ "View %s on Twitter" : "Skoða %s á Twitter",
"Website" : "Vefsvæði",
+ "Visit %s" : "Heimsækja %s",
"Address" : "Vistfang",
"Profile picture" : "Einkennismynd",
"About" : "Um hugbúnaðinn",
+ "Display name" : "Birtingarnafn",
"Headline" : "Fyrirsögn",
+ "Organisation" : "Stofnun/Félag/Fyrirtæki",
"Role" : "Role",
"Unknown user" : "Óþekktur notandi",
"Additional settings" : "Valfrjálsar stillingar",
+ "Enter the database username and name for %s" : "Settu inn notandanafn og nafn í gagnagrunni fyrir %s",
+ "Enter the database username for %s" : "Settu inn notandanafn í gagnagrunni fyrir %s",
+ "Enter the database name for %s" : "Settu inn nafn á gagnagrunni fyrir %s",
+ "You cannot use dots in the database name %s" : "Þú mátt ekki nota punkta í gagnagrunnsheitinu %s",
+ "MySQL username and/or password not valid" : "Notandanafn eða lykilorð MySQL er ekki gilt",
"You need to enter details of an existing account." : "Þú verður að setja inn auðkenni fyrirliggjandi notandaaðgangs.",
"Oracle connection could not be established" : "Ekki tókst að koma tengingu á við Oracle",
"Oracle username and/or password not valid" : "Notandanafn eða lykilorð Oracle er ekki gilt",
@@ -98,6 +134,7 @@ OC.L10N.register(
"Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjarlægðu stillinguna open_basedir úr php.ini eða skiptu yfir í 64-bita PHP.",
"Set an admin username." : "Stilltu notandanafn kerfisstjóra.",
"Set an admin password." : "Stilltu lykilorð kerfisstjóra.",
+ "Cannot create or write into the data directory %s" : "Gat ekki búið til eða skrifað í gagnamöppuna %s",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "Deilingarbakendinn %s verður að vera settur upp fyrir viðmótið OCP\\Share_Backend",
"Sharing backend %s not found" : "Deilingarbakendinn %s fannst ekki",
"Sharing backend for %s not found" : "Deilingarbakendi fyrir %s fannst ekki",
@@ -108,12 +145,18 @@ OC.L10N.register(
"%1$s via %2$s" : "%1$s með %2$s",
"You are not allowed to share %s" : "Þú hefur ekki heimild til að deila %s",
"Cannot increase permissions of %s" : "Get ekki aukið aðgangsheimildir %s",
+ "Files cannot be shared with delete permissions" : "Ekki er hægt að deila skrá með eyða-heimildum",
+ "Files cannot be shared with create permissions" : "Ekki er hægt að deila skrá með búa-til-heimildum",
"Expiration date is in the past" : "Gildistíminn er þegar runninn út",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Ekki er hægt að setja lokadagsetningu meira en %n dag fram í tímann","Ekki er hægt að setja lokadagsetningu meira en %n daga fram í tímann"],
+ "Sharing is only allowed with group members" : "Deiling er aðeins leyfð með meðlimum hópsins",
"Sharing %s failed, because this item is already shared with user %s" : "Deiling %s mistókst, því þessu atriði er þegar deilt með notandanum %s",
"%1$s shared »%2$s« with you" : "%1$s deildi »%2$s« með þér",
"%1$s shared »%2$s« with you." : "%1$s deildi »%2$s« með þér.",
"Click the button below to open it." : "Smelltu á hnappinn hér fyrir neðan til að opna það.",
"The requested share does not exist anymore" : "Umbeðin sameign er ekki lengur til",
+ "The requested share comes from a disabled user" : "Umbeðin sameign kemur frá notanda sem er óvirkur",
+ "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Notandinn var ekki búinn til þar sem takmörkum á fjölda notenda var náð. Skoðaðu tilkynningarnar þínar til að sjá meira.",
"Could not find category \"%s\"" : "Fann ekki flokkinn \"%s\"",
"Sunday" : "Sunnudagur",
"Monday" : "Mánudagur",
@@ -163,6 +206,7 @@ OC.L10N.register(
"A valid password must be provided" : "Skráðu inn gilt lykilorð",
"The username is already being used" : "Notandanafnið er þegar í notkun",
"Could not create user" : "Gat ekki búið til notanda",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Einungis eru leyfilegir eftirfarandi stafir í notandanafni: \"a-z\", \"A-Z\", \"0-9\", bil og \"_.@-'\"",
"A valid username must be provided" : "Skráðu inn gilt notandanafn",
"Username contains whitespace at the beginning or at the end" : "Notandanafnið inniheldur orðabil í upphafi eða enda",
"Username must not consist of dots only" : "Notandanafn má ekki einungis samanstanda af punktum",
@@ -172,20 +216,39 @@ OC.L10N.register(
"App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Ekki var hægt að setja upp \"%1$s\" forritið þar sem eftirfarandi kerfiskröfur eru ekki uppfylltar: %2$s",
"a safe home for all your data" : "öruggur staður fyrir öll gögnin þín",
"File is currently busy, please try again later" : "Skráin er upptekin í augnablikinu, reyndu aftur síðar",
+ "Cannot download file" : "Get ekki sótt skrá",
"Application is not enabled" : "Forrit ekki virkt",
"Authentication error" : "Villa við auðkenningu",
"Token expired. Please reload page." : "Kenniteikn er útrunnið. Þú ættir að hlaða síðunni aftur inn.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Engir reklar fyrir gagnagrunn eru uppsettir (sqlite, mysql eða postgresql).",
+ "Cannot write into \"config\" directory." : "Get ekki skrifað í \"config\" möppuna.",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna. Skoðaðu %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" : "Eða, ef þú vilt halda config.php skránni aðeins til lestrar, settu valkostinn \"config_is_read_only\" á 'true' í henni. Skoðaðu %s",
+ "Cannot write into \"apps\" directory." : "Get ekki skrifað í \"apps\" möppuna.",
+ "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." : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í forritamöppuna eða gera App Store forritabúðina óvirka í stillingaskránni. ",
+ "Cannot create \"data\" directory." : "Get ekki búið til \"data\" möppu.",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Skoðaðu %s",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Heimildir er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Skoðaðu %s.",
+ "Your data directory is not writable." : "Gagnamappn þín er ekki lesanleg.",
+ "Setting locale to %s failed." : "Mistókst að setja upp staðfærsluna %s.",
+ "Please install one of these locales on your system and restart your web server." : "Settu upp eina af þessum staðfærslum og endurræstu vefþjóninn.",
"PHP module %s not installed." : "PHP-einingin %s er ekki uppsett.",
"Please ask your server administrator to install the module." : "Biddu kerfisstjórann þinn um að setja eininguna upp.",
"PHP setting \"%s\" is not set to \"%s\"." : "PHP-stillingin \"%s\" er ekki sett á \"%s\".",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Ef þessi stilling er löguð í php.ini mun Nextcloud keyra aftur",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> er stillt á <code>%s</code> í stað gildisins \"<code>0</code> eins og vænst var.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Til að laga þetta vandamál ættirðu að setja <code>mbstring.func_overload</code> sem <code>0</code> í php.ini.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP virðist vera sett upp to fjarlægja innantextablokkir (inline doc blocks). Þetta mun gera ýmis kjarnaforrit óaðgengileg.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Þessu veldur væntanlega biðminni/hraðall á borð við Zend OPcache eða eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Búið er að setja upp PHP-einingar, en eru þær ennþá taldar upp eins og þær vanti?",
"Please ask your server administrator to restart the web server." : "Biddu kerfisstjórann þinn um að endurræsa vefþjóninn.",
+ "The required %s config variable is not configured in the config.php file." : "Nauðsynleg %s stillingabreyta er ekki stillt í config.php file.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "Biddu kerfisstjórann þinn um að athuga uppsetninguna á Nextcloud.",
+ "Your data directory is readable by other users." : "Gagnamappn þín er lesanleg fyrir aðra notendur.",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Endilega breyttu heimildunum í 0770 svo að aðrir notendur geti ekki listað upp innihald hennar.",
+ "Your data directory must be an absolute path." : "Gagnamappan þín verður að vera með algilda slóð.",
+ "Check the value of \"datadirectory\" in your configuration." : "Athugaðu gildi \"datadirectory\" í uppsetningunni þinni.",
+ "Your data directory is invalid." : "Gagnamappan þín er ógild.",
"Ensure there is a file called \".ocdata\" in the root of the data directory." : "Gakktu úr skugga um að til staðar sé skrá með heitinu \".ocdata\" í rót gagnageymslunnar.",
"Action \"%s\" not supported or implemented." : "Aðgerðin \"%s\" er ekki studd eða útfærð.",
"Authentication failed, wrong token or provider ID given" : "Auðkenning mistókst, uppgefið rangt teikn eða auðkenni þjónustuveitu",
@@ -198,10 +261,20 @@ OC.L10N.register(
"Storage connection error. %s" : "Villa í tengingu við gagnageymslu. %s",
"Storage is temporarily not available" : "Gagnageymsla ekki tiltæk í augnablikinu",
"Storage connection timeout. %s" : "Gagnageymsla féll á tíma. %s",
+ "Free prompt" : "Frjáls kvaðning",
+ "Runs an arbitrary prompt through the language model." : "Keyrir óreglulega kvaðningu (prompt) í gegnum tungumálslíkanið.",
+ "Generate headline" : "Útbúa fyrirsögn",
+ "Generates a possible headline for a text." : "Útbýr mögulega fyrirsögn fyrir texta.",
+ "Summarize" : "Gera samantekt",
+ "Summarizes text by reducing its length without losing key information." : "Tekur saman aðalatriði texta með því að stytta hann án þess að tapa mikilvægustu upplýsingum.",
+ "Extract topics" : "Taka út efnisflokka",
+ "Extracts topics from a text and outputs them separated by commas." : "Greinir efnisflokka úr texta og aðskilur þá með kommum.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Skrám forritsins %$1s var ekki rétt skipt út. Gakktu úr skugga um að þetta sé útgáfa sem sé samhæfð útgáfu vefþjónsins.",
+ "404" : "404",
"Full name" : "Fullt nafn",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Einungis eru leyfilegir eftirfarandi stafir í notandanafni: \"a-z\", \"A-Z\", \"0-9\", og \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Krafist er libxml2 2.7.0 hið minnsta. Núna er %s uppsett.",
- "To fix this issue update your libxml2 version and restart your web server." : "Til að laga þetta vandamál ættirðu að uppfæra útgáfu þína af libxml2 og endurræsa vefþjóninn."
+ "To fix this issue update your libxml2 version and restart your web server." : "Til að laga þetta vandamál ættirðu að uppfæra útgáfu þína af libxml2 og endurræsa vefþjóninn.",
+ "PostgreSQL >= 9 required." : "Krefst PostgreSQL >= 9.",
+ "Please upgrade your database version." : "Uppfærðu útgáfu gagnagrunnsins."
},
"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);");
diff --git a/lib/l10n/is.json b/lib/l10n/is.json
index 372efc31675..eaceaade5da 100644
--- a/lib/l10n/is.json
+++ b/lib/l10n/is.json
@@ -1,8 +1,18 @@
{ "translations": {
"Cannot write into \"config\" directory!" : "Get ekki skrifað í \"config\" möppuna!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "En ef þú vilt halda config.php skránni einungis til lesanlegri, skaltu setja valkostinn \"config_is_read_only\" á 'true' í henni.",
"See %s" : "Skoðaðu %s",
+ "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Forritið %1$s er ekki til staðar eða er af útgáfu sem ekki er samhæfð þessum netþjóni. Endilega skoðaðu í forritamöppuna.",
"Sample configuration detected" : "Fann sýnisuppsetningu",
"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" : "Komið hefur í ljós að sýniuppsetningin var afrituð. Þetta getur skemmt uppsetninguna og er ekki stutt. Endilega lestu hjálparskjölin áður en þú gerir breytingar á config.php",
+ "The page could not be found on the server." : "Síðan fannst ekki á netþjóninum.",
+ "%s email verification" : "Sannvottun tölvupósts fyrir %s",
+ "Email verification" : "Sannvottun tölvupósts",
+ "Click the following button to confirm your email." : "Smelltu á eftirfarandi hnapp til að staðfesta tölvupóstfangið þitt.",
+ "Click the following link to confirm your email." : "Smelltu á eftirfarandi tengil til að staðfesta tölvupóstfangið þitt.",
+ "Confirm your email" : "Staðfestu tölvupóstfangið þitt",
+ "Other activities" : "Aðrar athafnir",
"%1$s and %2$s" : "%1$s og %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s og %3$s",
"%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s og %4$s",
@@ -10,16 +20,21 @@
"Education Edition" : "Kennsluútgáfa",
"Enterprise bundle" : "Fyrirtækjavöndull",
"Groupware bundle" : "Hópvinnsluvöndull",
+ "Hub bundle" : "Tengivöndull",
"Social sharing bundle" : "Deilivöndull fyrir samfélagsmiðla",
"PHP %s or higher is required." : "Krafist er PHP %s eða hærra.",
"PHP with a version lower than %s is required." : "Krafist er PHP útgáfu %s eða lægri.",
"%sbit or higher PHP required." : "Krafist er PHP %sbita eða hærra.",
+ "The following architectures are supported: %s" : "Eftirfarandi tölvukerfi eru studd: %s",
+ "The following databases are supported: %s" : "Eftirfarandi gagnagrunnar eru studdir: %s",
"The command line tool %s could not be found" : "Skipanalínutólið \"%s\" fannst ekki",
"The library %s is not available." : "Aðgerðasafnið %s er ekki tiltækt.",
"Library %1$s with a version higher than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu hærri en %2$s - tiltæk útgáfa er %3$s.",
"Library %1$s with a version lower than %2$s is required - available version %3$s." : "Krafist er aðgerðasafns %1$s með útgáfu lægri en %2$s - tiltæk útgáfa er %3$s.",
+ "The following platforms are supported: %s" : "Eftirfarandi stýrikerfi eru studd: %s",
"Server version %s or higher is required." : "Krafist er þjóns af útgáfu %s eða hærra.",
"Server version %s or lower is required." : "Krafist er þjóns af útgáfu %s eða lægri.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "Innskráður notandi verður að vera kerfisstjóri eða undirstjórnandi eða hafa fengið sérstaka aðgangsheimild fyrir þessa stillingu",
"Logged in user must be an admin or sub admin" : "Innskráður notandi verður að vera kerfisstjóri eða undirstjórnandi",
"Logged in user must be an admin" : "Innskráður notandi verður að vera stjórnandi",
"Wiping of device %s has started" : "Útþurrkun af tækinu %s er byrjuð",
@@ -39,6 +54,8 @@
"Invalid image" : "Ógild mynd",
"Avatar image is not square" : "Auðkennismynd er ekki ferningslaga",
"Files" : "Skrár",
+ "View profile" : "Skoða notandasnið",
+ "Local time: %s" : "Staðartími: %s",
"today" : "í dag",
"tomorrow" : "á morgun",
"yesterday" : "í gær",
@@ -58,8 +75,11 @@
"_%n minute ago_::_%n minutes ago_" : ["fyrir %n mínútu síðan","fyrir %n mínútum síðan"],
"in a few seconds" : "eftir örfáar sekúndur",
"seconds ago" : "sekúndum síðan",
+ "Empty file" : "Tóm skrá",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Eining með auðkenni: %s er ekki til. Virkjaðu hana í forritastillingum eða hafðu samband við kerfisstjóra.",
"File already exists" : "Skrá er þegar til",
+ "Invalid path" : "Ógild slóð",
+ "Failed to create file from template" : "Mistókst að búa til skrá út frá sniðmáti",
"Templates" : "Sniðmát",
"File name is a reserved word" : "Skráarheiti er þegar frátekið orð",
"File name contains at least one invalid character" : "Skráarheitið inniheldur að minnsta kosti einn ógildan staf",
@@ -71,21 +91,37 @@
"__language_name__" : "Íslenska",
"This is an automatically sent email, please do not reply." : "Þetta er sjálfvirk tölvupóstsending, ekki svara þessu.",
"Help" : "Hjálp",
+ "Appearance and accessibility" : "Útlit og aðgengi",
"Apps" : "Forrit",
+ "Personal settings" : "Persónulegar stillingar",
+ "Administration settings" : "Stillingar stjórnunar",
"Settings" : "Stillingar",
"Log out" : "Skrá út",
"Users" : "Notendur",
"Email" : "Tölvupóstur",
+ "Mail %s" : "Póstur %s",
+ "Fediverse" : "Skýjasamband",
+ "View %s on the fediverse" : "Skoða %s á skýjasambandi (fediverse)",
"Phone" : "Sími",
+ "Call %s" : "Hringja í %s",
"Twitter" : "Twitter",
+ "View %s on Twitter" : "Skoða %s á Twitter",
"Website" : "Vefsvæði",
+ "Visit %s" : "Heimsækja %s",
"Address" : "Vistfang",
"Profile picture" : "Einkennismynd",
"About" : "Um hugbúnaðinn",
+ "Display name" : "Birtingarnafn",
"Headline" : "Fyrirsögn",
+ "Organisation" : "Stofnun/Félag/Fyrirtæki",
"Role" : "Role",
"Unknown user" : "Óþekktur notandi",
"Additional settings" : "Valfrjálsar stillingar",
+ "Enter the database username and name for %s" : "Settu inn notandanafn og nafn í gagnagrunni fyrir %s",
+ "Enter the database username for %s" : "Settu inn notandanafn í gagnagrunni fyrir %s",
+ "Enter the database name for %s" : "Settu inn nafn á gagnagrunni fyrir %s",
+ "You cannot use dots in the database name %s" : "Þú mátt ekki nota punkta í gagnagrunnsheitinu %s",
+ "MySQL username and/or password not valid" : "Notandanafn eða lykilorð MySQL er ekki gilt",
"You need to enter details of an existing account." : "Þú verður að setja inn auðkenni fyrirliggjandi notandaaðgangs.",
"Oracle connection could not be established" : "Ekki tókst að koma tengingu á við Oracle",
"Oracle username and/or password not valid" : "Notandanafn eða lykilorð Oracle er ekki gilt",
@@ -96,6 +132,7 @@
"Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Fjarlægðu stillinguna open_basedir úr php.ini eða skiptu yfir í 64-bita PHP.",
"Set an admin username." : "Stilltu notandanafn kerfisstjóra.",
"Set an admin password." : "Stilltu lykilorð kerfisstjóra.",
+ "Cannot create or write into the data directory %s" : "Gat ekki búið til eða skrifað í gagnamöppuna %s",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "Deilingarbakendinn %s verður að vera settur upp fyrir viðmótið OCP\\Share_Backend",
"Sharing backend %s not found" : "Deilingarbakendinn %s fannst ekki",
"Sharing backend for %s not found" : "Deilingarbakendi fyrir %s fannst ekki",
@@ -106,12 +143,18 @@
"%1$s via %2$s" : "%1$s með %2$s",
"You are not allowed to share %s" : "Þú hefur ekki heimild til að deila %s",
"Cannot increase permissions of %s" : "Get ekki aukið aðgangsheimildir %s",
+ "Files cannot be shared with delete permissions" : "Ekki er hægt að deila skrá með eyða-heimildum",
+ "Files cannot be shared with create permissions" : "Ekki er hægt að deila skrá með búa-til-heimildum",
"Expiration date is in the past" : "Gildistíminn er þegar runninn út",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Ekki er hægt að setja lokadagsetningu meira en %n dag fram í tímann","Ekki er hægt að setja lokadagsetningu meira en %n daga fram í tímann"],
+ "Sharing is only allowed with group members" : "Deiling er aðeins leyfð með meðlimum hópsins",
"Sharing %s failed, because this item is already shared with user %s" : "Deiling %s mistókst, því þessu atriði er þegar deilt með notandanum %s",
"%1$s shared »%2$s« with you" : "%1$s deildi »%2$s« með þér",
"%1$s shared »%2$s« with you." : "%1$s deildi »%2$s« með þér.",
"Click the button below to open it." : "Smelltu á hnappinn hér fyrir neðan til að opna það.",
"The requested share does not exist anymore" : "Umbeðin sameign er ekki lengur til",
+ "The requested share comes from a disabled user" : "Umbeðin sameign kemur frá notanda sem er óvirkur",
+ "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Notandinn var ekki búinn til þar sem takmörkum á fjölda notenda var náð. Skoðaðu tilkynningarnar þínar til að sjá meira.",
"Could not find category \"%s\"" : "Fann ekki flokkinn \"%s\"",
"Sunday" : "Sunnudagur",
"Monday" : "Mánudagur",
@@ -161,6 +204,7 @@
"A valid password must be provided" : "Skráðu inn gilt lykilorð",
"The username is already being used" : "Notandanafnið er þegar í notkun",
"Could not create user" : "Gat ekki búið til notanda",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Einungis eru leyfilegir eftirfarandi stafir í notandanafni: \"a-z\", \"A-Z\", \"0-9\", bil og \"_.@-'\"",
"A valid username must be provided" : "Skráðu inn gilt notandanafn",
"Username contains whitespace at the beginning or at the end" : "Notandanafnið inniheldur orðabil í upphafi eða enda",
"Username must not consist of dots only" : "Notandanafn má ekki einungis samanstanda af punktum",
@@ -170,20 +214,39 @@
"App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Ekki var hægt að setja upp \"%1$s\" forritið þar sem eftirfarandi kerfiskröfur eru ekki uppfylltar: %2$s",
"a safe home for all your data" : "öruggur staður fyrir öll gögnin þín",
"File is currently busy, please try again later" : "Skráin er upptekin í augnablikinu, reyndu aftur síðar",
+ "Cannot download file" : "Get ekki sótt skrá",
"Application is not enabled" : "Forrit ekki virkt",
"Authentication error" : "Villa við auðkenningu",
"Token expired. Please reload page." : "Kenniteikn er útrunnið. Þú ættir að hlaða síðunni aftur inn.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Engir reklar fyrir gagnagrunn eru uppsettir (sqlite, mysql eða postgresql).",
+ "Cannot write into \"config\" directory." : "Get ekki skrifað í \"config\" möppuna.",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í stillingamöppuna. Skoðaðu %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" : "Eða, ef þú vilt halda config.php skránni aðeins til lestrar, settu valkostinn \"config_is_read_only\" á 'true' í henni. Skoðaðu %s",
+ "Cannot write into \"apps\" directory." : "Get ekki skrifað í \"apps\" möppuna.",
+ "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." : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í forritamöppuna eða gera App Store forritabúðina óvirka í stillingaskránni. ",
+ "Cannot create \"data\" directory." : "Get ekki búið til \"data\" möppu.",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Þetta er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Skoðaðu %s",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Heimildir er venjulega hægt að laga með því að gefa vefþjóninum skrifréttindi í rótarmöppuna. Skoðaðu %s.",
+ "Your data directory is not writable." : "Gagnamappn þín er ekki lesanleg.",
+ "Setting locale to %s failed." : "Mistókst að setja upp staðfærsluna %s.",
+ "Please install one of these locales on your system and restart your web server." : "Settu upp eina af þessum staðfærslum og endurræstu vefþjóninn.",
"PHP module %s not installed." : "PHP-einingin %s er ekki uppsett.",
"Please ask your server administrator to install the module." : "Biddu kerfisstjórann þinn um að setja eininguna upp.",
"PHP setting \"%s\" is not set to \"%s\"." : "PHP-stillingin \"%s\" er ekki sett á \"%s\".",
"Adjusting this setting in php.ini will make Nextcloud run again" : "Ef þessi stilling er löguð í php.ini mun Nextcloud keyra aftur",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> er stillt á <code>%s</code> í stað gildisins \"<code>0</code> eins og vænst var.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Til að laga þetta vandamál ættirðu að setja <code>mbstring.func_overload</code> sem <code>0</code> í php.ini.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP virðist vera sett upp to fjarlægja innantextablokkir (inline doc blocks). Þetta mun gera ýmis kjarnaforrit óaðgengileg.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Þessu veldur væntanlega biðminni/hraðall á borð við Zend OPcache eða eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Búið er að setja upp PHP-einingar, en eru þær ennþá taldar upp eins og þær vanti?",
"Please ask your server administrator to restart the web server." : "Biddu kerfisstjórann þinn um að endurræsa vefþjóninn.",
+ "The required %s config variable is not configured in the config.php file." : "Nauðsynleg %s stillingabreyta er ekki stillt í config.php file.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "Biddu kerfisstjórann þinn um að athuga uppsetninguna á Nextcloud.",
+ "Your data directory is readable by other users." : "Gagnamappn þín er lesanleg fyrir aðra notendur.",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Endilega breyttu heimildunum í 0770 svo að aðrir notendur geti ekki listað upp innihald hennar.",
+ "Your data directory must be an absolute path." : "Gagnamappan þín verður að vera með algilda slóð.",
+ "Check the value of \"datadirectory\" in your configuration." : "Athugaðu gildi \"datadirectory\" í uppsetningunni þinni.",
+ "Your data directory is invalid." : "Gagnamappan þín er ógild.",
"Ensure there is a file called \".ocdata\" in the root of the data directory." : "Gakktu úr skugga um að til staðar sé skrá með heitinu \".ocdata\" í rót gagnageymslunnar.",
"Action \"%s\" not supported or implemented." : "Aðgerðin \"%s\" er ekki studd eða útfærð.",
"Authentication failed, wrong token or provider ID given" : "Auðkenning mistókst, uppgefið rangt teikn eða auðkenni þjónustuveitu",
@@ -196,10 +259,20 @@
"Storage connection error. %s" : "Villa í tengingu við gagnageymslu. %s",
"Storage is temporarily not available" : "Gagnageymsla ekki tiltæk í augnablikinu",
"Storage connection timeout. %s" : "Gagnageymsla féll á tíma. %s",
+ "Free prompt" : "Frjáls kvaðning",
+ "Runs an arbitrary prompt through the language model." : "Keyrir óreglulega kvaðningu (prompt) í gegnum tungumálslíkanið.",
+ "Generate headline" : "Útbúa fyrirsögn",
+ "Generates a possible headline for a text." : "Útbýr mögulega fyrirsögn fyrir texta.",
+ "Summarize" : "Gera samantekt",
+ "Summarizes text by reducing its length without losing key information." : "Tekur saman aðalatriði texta með því að stytta hann án þess að tapa mikilvægustu upplýsingum.",
+ "Extract topics" : "Taka út efnisflokka",
+ "Extracts topics from a text and outputs them separated by commas." : "Greinir efnisflokka úr texta og aðskilur þá með kommum.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Skrám forritsins %$1s var ekki rétt skipt út. Gakktu úr skugga um að þetta sé útgáfa sem sé samhæfð útgáfu vefþjónsins.",
+ "404" : "404",
"Full name" : "Fullt nafn",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Einungis eru leyfilegir eftirfarandi stafir í notandanafni: \"a-z\", \"A-Z\", \"0-9\", og \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Krafist er libxml2 2.7.0 hið minnsta. Núna er %s uppsett.",
- "To fix this issue update your libxml2 version and restart your web server." : "Til að laga þetta vandamál ættirðu að uppfæra útgáfu þína af libxml2 og endurræsa vefþjóninn."
+ "To fix this issue update your libxml2 version and restart your web server." : "Til að laga þetta vandamál ættirðu að uppfæra útgáfu þína af libxml2 og endurræsa vefþjóninn.",
+ "PostgreSQL >= 9 required." : "Krefst PostgreSQL >= 9.",
+ "Please upgrade your database version." : "Uppfærðu útgáfu gagnagrunnsins."
},"pluralForm" :"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);"
} \ No newline at end of file
diff --git a/lib/l10n/it.js b/lib/l10n/it.js
index 52cf1721362..32a3237a846 100644
--- a/lib/l10n/it.js
+++ b/lib/l10n/it.js
@@ -5,15 +5,14 @@ OC.L10N.register(
"This can usually be fixed by giving the web server write access to the config directory." : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella config.",
"But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ma, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true.",
"See %s" : "Vedi %s",
- "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.",
+ "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 applicazioni.",
"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",
- "Click the following button to confirm your email." : "Clicca il pulsante seguente per confermare la tua email.",
- "Click the following link to confirm your email." : "Clicca il collegamento seguente per confermare la tua email.",
+ "Click the following button to confirm your email." : "Fai clic sul pulsante seguente per confermare la tua email.",
+ "Click the following link to confirm your email." : "Fai clic sul collegamento seguente per confermare la tua email.",
"Confirm your email" : "Conferma la tua email",
"Other activities" : "Altre attività",
"%1$s and %2$s" : "%1$s e %2$s",
@@ -218,7 +217,7 @@ OC.L10N.register(
"a safe home for all your data" : "un posto sicuro per tutti i tuoi dati",
"File is currently busy, please try again later" : "Il file è attualmente occupato, riprova più tardi",
"Cannot download file" : "Impossibile scaricare il file",
- "Application is not enabled" : "L'applicazione non è abilitata",
+ "Application is not enabled" : "L'applicazione non è abilitata",
"Authentication error" : "Errore di autenticazione",
"Token expired. Please reload page." : "Token scaduto. Ricarica la pagina.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Nessun driver di database (sqlite, mysql o postgresql) installato",
@@ -226,7 +225,7 @@ OC.L10N.register(
"This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella config. Vedi %s",
"Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true. Vedi %s",
"Cannot write into \"apps\" directory." : "Impossibile scrivere nella cartella \"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." : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella apps o disattivando il negozio delle applicazioni nel file di configurazione.",
+ "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." : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella delle applicazioni o disattivando il negozio delle applicazioni nel file di configurazione.",
"Cannot create \"data\" directory." : "Impossibile creare la cartella \"data\".",
"This can usually be fixed by giving the web server write access to the root directory. See %s" : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella radice. Vedi %s",
"Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "I permessi possono essere corretti di solito fornendo al server web accesso in scrittura alla cartella radice. Vedi %s.",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Estrai argomenti",
"Extracts topics from a text and outputs them separated by commas." : "Estrae gli argomenti da un testo e li elenca separati da virgole.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "I file dell'applicazione %1$s non sono stati sostituiti correttamente. Assicurati che sia una versione compatibile con il server.",
+ "404" : "404",
"Full name" : "Nome completo",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "È stato raggiunto il limite di utenti e l'utente non è stato creato. Controlla le notifiche per maggiori informazioni.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo i seguenti caratteri sono consentiti in un nome utente: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"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.",
diff --git a/lib/l10n/it.json b/lib/l10n/it.json
index 8fb450a83fd..9d32ca8c58a 100644
--- a/lib/l10n/it.json
+++ b/lib/l10n/it.json
@@ -3,15 +3,14 @@
"This can usually be fixed by giving the web server write access to the config directory." : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella config.",
"But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Ma, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true.",
"See %s" : "Vedi %s",
- "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.",
+ "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 applicazioni.",
"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",
- "Click the following button to confirm your email." : "Clicca il pulsante seguente per confermare la tua email.",
- "Click the following link to confirm your email." : "Clicca il collegamento seguente per confermare la tua email.",
+ "Click the following button to confirm your email." : "Fai clic sul pulsante seguente per confermare la tua email.",
+ "Click the following link to confirm your email." : "Fai clic sul collegamento seguente per confermare la tua email.",
"Confirm your email" : "Conferma la tua email",
"Other activities" : "Altre attività",
"%1$s and %2$s" : "%1$s e %2$s",
@@ -216,7 +215,7 @@
"a safe home for all your data" : "un posto sicuro per tutti i tuoi dati",
"File is currently busy, please try again later" : "Il file è attualmente occupato, riprova più tardi",
"Cannot download file" : "Impossibile scaricare il file",
- "Application is not enabled" : "L'applicazione non è abilitata",
+ "Application is not enabled" : "L'applicazione non è abilitata",
"Authentication error" : "Errore di autenticazione",
"Token expired. Please reload page." : "Token scaduto. Ricarica la pagina.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "Nessun driver di database (sqlite, mysql o postgresql) installato",
@@ -224,7 +223,7 @@
"This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella config. Vedi %s",
"Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "O, se preferisci mantenere il file config.php in sola lettura, imposta l'opzione \"config_is_read_only\" a true. Vedi %s",
"Cannot write into \"apps\" directory." : "Impossibile scrivere nella cartella \"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." : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella apps o disattivando il negozio delle applicazioni nel file di configurazione.",
+ "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." : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella delle applicazioni o disattivando il negozio delle applicazioni nel file di configurazione.",
"Cannot create \"data\" directory." : "Impossibile creare la cartella \"data\".",
"This can usually be fixed by giving the web server write access to the root directory. See %s" : "Ciò può essere corretto di solito fornendo al server web accesso in scrittura alla cartella radice. Vedi %s",
"Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "I permessi possono essere corretti di solito fornendo al server web accesso in scrittura alla cartella radice. Vedi %s.",
@@ -269,9 +268,8 @@
"Extract topics" : "Estrai argomenti",
"Extracts topics from a text and outputs them separated by commas." : "Estrae gli argomenti da un testo e li elenca separati da virgole.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "I file dell'applicazione %1$s non sono stati sostituiti correttamente. Assicurati che sia una versione compatibile con il server.",
+ "404" : "404",
"Full name" : "Nome completo",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "È stato raggiunto il limite di utenti e l'utente non è stato creato. Controlla le notifiche per maggiori informazioni.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Solo i seguenti caratteri sono consentiti in un nome utente: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"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.",
diff --git a/lib/l10n/ja.js b/lib/l10n/ja.js
index 8dee8ebbc49..9c38f82f070 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" : "メールによる確認",
@@ -262,7 +261,7 @@ OC.L10N.register(
"Storage connection error. %s" : "ストレージへの接続エラー。 %s",
"Storage is temporarily not available" : "ストレージは一時的に利用できません",
"Storage connection timeout. %s" : "ストレージへの接続がタイムアウト。 %s",
- "Free prompt" : "無料プロンプト",
+ "Free prompt" : "任意のプロンプト",
"Runs an arbitrary prompt through the language model." : "言語モデルを通じて任意のプロンプトを実行",
"Generate headline" : "見出しの生成",
"Generates a possible headline for a text." : "テキストの見出しの候補を生成",
@@ -271,9 +270,8 @@ OC.L10N.register(
"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 のファイルが正しく置き換えられませんでした。サーバーと互換性のあるバージョンであることを確認してください。",
+ "404" : "404",
"Full name" : "フルネーム",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "ユーザー制限に達したため、ユーザーは作成されませんでした。詳細については、通知を確認してください。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ユーザー名で利用できる文字列: \"a-z\", \"A-Z\", \"0-9\", \"_.@-\"",
"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以上が必要です",
diff --git a/lib/l10n/ja.json b/lib/l10n/ja.json
index 7d8beb60f63..6388c6ecdbe 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" : "メールによる確認",
@@ -260,7 +259,7 @@
"Storage connection error. %s" : "ストレージへの接続エラー。 %s",
"Storage is temporarily not available" : "ストレージは一時的に利用できません",
"Storage connection timeout. %s" : "ストレージへの接続がタイムアウト。 %s",
- "Free prompt" : "無料プロンプト",
+ "Free prompt" : "任意のプロンプト",
"Runs an arbitrary prompt through the language model." : "言語モデルを通じて任意のプロンプトを実行",
"Generate headline" : "見出しの生成",
"Generates a possible headline for a text." : "テキストの見出しの候補を生成",
@@ -269,9 +268,8 @@
"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 のファイルが正しく置き換えられませんでした。サーバーと互換性のあるバージョンであることを確認してください。",
+ "404" : "404",
"Full name" : "フルネーム",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "ユーザー制限に達したため、ユーザーは作成されませんでした。詳細については、通知を確認してください。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ユーザー名で利用できる文字列: \"a-z\", \"A-Z\", \"0-9\", \"_.@-\"",
"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以上が必要です",
diff --git a/lib/l10n/ka.js b/lib/l10n/ka.js
index 044dbf40d14..9bcd9b3a8bf 100644
--- a/lib/l10n/ka.js
+++ b/lib/l10n/ka.js
@@ -1,7 +1,280 @@
OC.L10N.register(
"lib",
{
+ "Cannot write into \"config\" directory!" : "Cannot write into \"config\" directory!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "This can usually be fixed by giving the web server write access to the config directory.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it.",
+ "See %s" : "See %s",
+ "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",
+ "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",
+ "Click the following button to confirm your email." : "Click the following button to confirm your email.",
+ "Click the following link to confirm your email." : "Click the following link to confirm your email.",
+ "Confirm your email" : "Confirm your email",
+ "Other activities" : "Other activities",
+ "%1$s and %2$s" : "%1$s and %2$s",
+ "%1$s, %2$s and %3$s" : "%1$s, %2$s and %3$s",
+ "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s and %4$s",
+ "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s and %5$s",
+ "Education Edition" : "Education Edition",
+ "Enterprise bundle" : "Enterprise bundle",
+ "Groupware bundle" : "Groupware bundle",
+ "Hub bundle" : "Hub bundle",
+ "Social sharing bundle" : "Social sharing bundle",
+ "PHP %s or higher is required." : "PHP %s or higher is required.",
+ "PHP with a version lower than %s is required." : "PHP with a version lower than %s is required.",
+ "%sbit or higher PHP required." : "%sbit or higher PHP required.",
+ "The following architectures are supported: %s" : "The following architectures are supported: %s",
+ "The following databases are supported: %s" : "The following databases are supported: %s",
+ "The command line tool %s could not be found" : "The command line tool %s could not be found",
+ "The library %s is not available." : "The library %s is not available.",
+ "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Library %1$s with a version higher than %2$s is required - available version %3$s.",
+ "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Library %1$s with a version lower than %2$s is required - available version %3$s.",
+ "The following platforms are supported: %s" : "The following platforms are supported: %s",
+ "Server version %s or higher is required." : "Server version %s or higher is required.",
+ "Server version %s or lower is required." : "Server version %s or lower is required.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "Logged in user must be an admin, a sub admin or gotten special right to access this setting",
+ "Logged in user must be an admin or sub admin" : "Logged in user must be an admin or sub admin",
+ "Logged in user must be an admin" : "Logged in user must be an admin",
+ "Wiping of device %s has started" : "Wiping of device %s has started",
+ "Wiping of device »%s« has started" : "Wiping of device »%s« has started",
+ "»%s« started remote wipe" : "»%s« started remote wipe",
+ "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished",
+ "Wiping of device %s has finished" : "Wiping of device %s has finished",
+ "Wiping of device »%s« has finished" : "Wiping of device »%s« has finished",
+ "»%s« finished remote wipe" : "»%s« finished remote wipe",
+ "Device or application »%s« has finished the remote wipe process." : "Device or application »%s« has finished the remote wipe process.",
+ "Remote wipe started" : "Remote wipe started",
+ "A remote wipe was started on device %s" : "A remote wipe was started on device %s",
+ "Remote wipe finished" : "Remote wipe finished",
+ "The remote wipe on %s has finished" : "The remote wipe on %s has finished",
+ "Authentication" : "Authentication",
+ "Unknown filetype" : "Unknown filetype",
+ "Invalid image" : "Invalid image",
+ "Avatar image is not square" : "Avatar image is not square",
"Files" : "ფაილები",
- "__language_name__" : "ქართული ენა"
+ "View profile" : "View profile",
+ "Local time: %s" : "Local time: %s",
+ "today" : "today",
+ "tomorrow" : "tomorrow",
+ "yesterday" : "yesterday",
+ "_in %n day_::_in %n days_" : ["in %n day","in %n days"],
+ "_%n day ago_::_%n days ago_" : ["%n day ago","%n days ago"],
+ "next month" : "next month",
+ "last month" : "last month",
+ "_in %n month_::_in %n months_" : ["in %n month","in %n months"],
+ "_%n month ago_::_%n months ago_" : ["%n month ago","%n months ago"],
+ "next year" : "next year",
+ "last year" : "last year",
+ "_in %n year_::_in %n years_" : ["in %n year","in %n years"],
+ "_%n year ago_::_%n years ago_" : ["%n year ago","%n years ago"],
+ "_in %n hour_::_in %n hours_" : ["in %n hour","in %n hours"],
+ "_%n hour ago_::_%n hours ago_" : ["%n hour ago","%n hours ago"],
+ "_in %n minute_::_in %n minutes_" : ["in %n minute","in %n minutes"],
+ "_%n minute ago_::_%n minutes ago_" : ["%n minute ago","%n minutes ago"],
+ "in a few seconds" : "in a few seconds",
+ "seconds ago" : "seconds ago",
+ "Empty file" : "Empty file",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator.",
+ "File already exists" : "File already exists",
+ "Invalid path" : "Invalid path",
+ "Failed to create file from template" : "Failed to create file from template",
+ "Templates" : "Templates",
+ "File name is a reserved word" : "File name is a reserved word",
+ "File name contains at least one invalid character" : "File name contains at least one invalid character",
+ "File name is too long" : "File name is too long",
+ "Dot files are not allowed" : "Dot files are not allowed",
+ "Empty filename is not allowed" : "Empty filename is not allowed",
+ "App \"%s\" cannot be installed because appinfo file cannot be read." : "App \"%s\" cannot be installed because appinfo file cannot be read.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "App \"%s\" cannot be installed because it is not compatible with this version of the server.",
+ "__language_name__" : "ქართული ენა",
+ "This is an automatically sent email, please do not reply." : "This is an automatically sent email, please do not reply.",
+ "Help" : "Help",
+ "Appearance and accessibility" : "Appearance and accessibility",
+ "Apps" : "Apps",
+ "Personal settings" : "Personal settings",
+ "Administration settings" : "Administration settings",
+ "Settings" : "Settings",
+ "Log out" : "Log out",
+ "Users" : "Users",
+ "Email" : "Email",
+ "Mail %s" : "Mail %s",
+ "Fediverse" : "Fediverse",
+ "View %s on the fediverse" : "View %s on the fediverse",
+ "Phone" : "Phone",
+ "Call %s" : "Call %s",
+ "Twitter" : "Twitter",
+ "View %s on Twitter" : "View %s on Twitter",
+ "Website" : "Website",
+ "Visit %s" : "Visit %s",
+ "Address" : "Address",
+ "Profile picture" : "Profile picture",
+ "About" : "About",
+ "Display name" : "Display name",
+ "Headline" : "Headline",
+ "Organisation" : "Organisation",
+ "Role" : "Role",
+ "Unknown user" : "Unknown user",
+ "Additional settings" : "Additional settings",
+ "Enter the database username and name for %s" : "Enter the database username and name for %s",
+ "Enter the database username for %s" : "Enter the database username for %s",
+ "Enter the database name for %s" : "Enter the database name for %s",
+ "You cannot use dots in the database name %s" : "You cannot use dots in the database name %s",
+ "MySQL username and/or password not valid" : "MySQL username and/or password not valid",
+ "You need to enter details of an existing account." : "You need to enter details of an existing account.",
+ "Oracle connection could not be established" : "Oracle connection could not be established",
+ "Oracle username and/or password not valid" : "Oracle username and/or password not valid",
+ "PostgreSQL username and/or password not valid" : "PostgreSQL username and/or password not valid",
+ "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! ",
+ "For the best results, please consider using a GNU/Linux server instead." : "For the best results, please consider using a GNU/Linux server instead.",
+ "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged.",
+ "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.",
+ "Set an admin username." : "Set an admin username.",
+ "Set an admin password." : "Set an admin password.",
+ "Cannot create or write into the data directory %s" : "Cannot create or write into the data directory %s",
+ "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Sharing backend %s must implement the interface OCP\\Share_Backend",
+ "Sharing backend %s not found" : "Sharing backend %s not found",
+ "Sharing backend for %s not found" : "Sharing backend for %s not found",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s shared »%2$s« with you and wants to add:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s shared »%2$s« with you and wants to add",
+ "»%s« added a note to a file shared with you" : "»%s« added a note to a file shared with you",
+ "Open »%s«" : "Open »%s«",
+ "%1$s via %2$s" : "%1$s via %2$s",
+ "You are not allowed to share %s" : "You are not allowed to share %s",
+ "Cannot increase permissions of %s" : "Cannot increase permissions of %s",
+ "Files cannot be shared with delete permissions" : "Files cannot be shared with delete permissions",
+ "Files cannot be shared with create permissions" : "Files cannot be shared with create permissions",
+ "Expiration date is in the past" : "Expiration date is in the past",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Cannot set expiration date more than %n day in the future","Cannot set expiration date more than %n days in the future"],
+ "Sharing is only allowed with group members" : "Sharing is only allowed with group members",
+ "Sharing %s failed, because this item is already shared with user %s" : "Sharing %s failed, because this item is already shared with user %s",
+ "%1$s shared »%2$s« with you" : "%1$s shared »%2$s« with you",
+ "%1$s shared »%2$s« with you." : "%1$s shared »%2$s« with you.",
+ "Click the button below to open it." : "Click the button below to open it.",
+ "The requested share does not exist anymore" : "The requested share does not exist anymore",
+ "The requested share comes from a disabled user" : "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." : "The user was not created because the user limit has been reached. Check your notifications to learn more.",
+ "Could not find category \"%s\"" : "Could not find category \"%s\"",
+ "Sunday" : "Sunday",
+ "Monday" : "Monday",
+ "Tuesday" : "Tuesday",
+ "Wednesday" : "Wednesday",
+ "Thursday" : "Thursday",
+ "Friday" : "Friday",
+ "Saturday" : "Saturday",
+ "Sun." : "Sun.",
+ "Mon." : "Mon.",
+ "Tue." : "Tue.",
+ "Wed." : "Wed.",
+ "Thu." : "Thu.",
+ "Fri." : "Fri.",
+ "Sat." : "Sat.",
+ "Su" : "Su",
+ "Mo" : "Mo",
+ "Tu" : "Tu",
+ "We" : "We",
+ "Th" : "Th",
+ "Fr" : "Fr",
+ "Sa" : "Sa",
+ "January" : "January",
+ "February" : "February",
+ "March" : "March",
+ "April" : "April",
+ "May" : "May",
+ "June" : "June",
+ "July" : "July",
+ "August" : "August",
+ "September" : "September",
+ "October" : "October",
+ "November" : "November",
+ "December" : "December",
+ "Jan." : "Jan.",
+ "Feb." : "Feb.",
+ "Mar." : "Mar.",
+ "Apr." : "Apr.",
+ "May." : "May.",
+ "Jun." : "Jun.",
+ "Jul." : "Jul.",
+ "Aug." : "Aug.",
+ "Sep." : "Sep.",
+ "Oct." : "Oct.",
+ "Nov." : "Nov.",
+ "Dec." : "Dec.",
+ "A valid password must be provided" : "A valid password must be provided",
+ "The username is already being used" : "The username is already being used",
+ "Could not create user" : "Could not create user",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"",
+ "A valid username must be provided" : "A valid username must be provided",
+ "Username contains whitespace at the beginning or at the end" : "Username contains whitespace at the beginning or at the end",
+ "Username must not consist of dots only" : "Username must not consist of dots only",
+ "Username is invalid because files already exist for this user" : "Username is invalid because files already exist for this user",
+ "User disabled" : "User disabled",
+ "Login canceled by app" : "Login canceled by app",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s",
+ "a safe home for all your data" : "a safe home for all your data",
+ "File is currently busy, please try again later" : "File is currently busy, please try again later",
+ "Cannot download file" : "Cannot download file",
+ "Application is not enabled" : "Application is not enabled",
+ "Authentication error" : "Authentication error",
+ "Token expired. Please reload page." : "Token expired. Please reload page.",
+ "No database drivers (sqlite, mysql, or postgresql) installed." : "No database drivers (sqlite, mysql, or postgresql) installed.",
+ "Cannot write into \"config\" directory." : "Cannot write into \"config\" directory.",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "This can usually be fixed by giving the web server write access to the config directory. See %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" : "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s",
+ "Cannot write into \"apps\" directory." : "Cannot write into \"apps\" directory.",
+ "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." : "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.",
+ "Cannot create \"data\" directory." : "Cannot create \"data\" directory.",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "This can usually be fixed by giving the web server write access to the root directory. See %s",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Permissions can usually be fixed by giving the web server write access to the root directory. See %s.",
+ "Your data directory is not writable." : "Your data directory is not writable.",
+ "Setting locale to %s failed." : "Setting locale to %s failed.",
+ "Please install one of these locales on your system and restart your web server." : "Please install one of these locales on your system and restart your web server.",
+ "PHP module %s not installed." : "PHP module %s not installed.",
+ "Please ask your server administrator to install the module." : "Please ask your server administrator to install the module.",
+ "PHP setting \"%s\" is not set to \"%s\"." : "PHP setting \"%s\" is not set to \"%s\".",
+ "Adjusting this setting in php.ini will make Nextcloud run again" : "Adjusting this setting in php.ini will make Nextcloud run again",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.",
+ "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.",
+ "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.",
+ "PHP modules have been installed, but they are still listed as missing?" : "PHP modules have been installed, but they are still listed as missing?",
+ "Please ask your server administrator to restart the web server." : "Please ask your server administrator to restart the web server.",
+ "The required %s config variable is not configured in the config.php file." : "The required %s config variable is not configured in the config.php file.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "Please ask your server administrator to check the Nextcloud configuration.",
+ "Your data directory is readable by other users." : "Your data directory is readable by other users.",
+ "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Please change the permissions to 0770 so that the directory cannot be listed by other users.",
+ "Your data directory must be an absolute path." : "Your data directory must be an absolute path.",
+ "Check the value of \"datadirectory\" in your configuration." : "Check the value of \"datadirectory\" in your configuration.",
+ "Your data directory is invalid." : "Your data directory is invalid.",
+ "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Ensure there is a file called \".ocdata\" in the root of the data directory.",
+ "Action \"%s\" not supported or implemented." : "Action \"%s\" not supported or implemented.",
+ "Authentication failed, wrong token or provider ID given" : "Authentication failed, wrong token or provider ID given",
+ "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parameters missing in order to complete the request. Missing Parameters: \"%s\"",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" already used by cloud federation provider \"%2$s\"",
+ "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud Federation Provider with ID: \"%s\" does not exist.",
+ "Could not obtain lock type %d on \"%s\"." : "Could not obtain lock type %d on \"%s\".",
+ "Storage unauthorized. %s" : "Storage unauthorized. %s",
+ "Storage incomplete configuration. %s" : "Storage incomplete configuration. %s",
+ "Storage connection error. %s" : "Storage connection error. %s",
+ "Storage is temporarily not available" : "Storage is temporarily not available",
+ "Storage connection timeout. %s" : "Storage connection timeout. %s",
+ "Free prompt" : "Free prompt",
+ "Runs an arbitrary prompt through the language model." : "Runs an arbitrary prompt through the language model.",
+ "Generate headline" : "Generate headline",
+ "Generates a possible headline for a text." : "Generates a possible headline for a text.",
+ "Summarize" : "Summarize",
+ "Summarizes text by reducing its length without losing key information." : "Summarizes text by reducing its length without losing key information.",
+ "Extract topics" : "Extract topics",
+ "Extracts topics from a text and outputs them separated by commas." : "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." : "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server.",
+ "404" : "404",
+ "Full name" : "Full name",
+ "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."
},
"nplurals=2; plural=(n!=1);");
diff --git a/lib/l10n/ka.json b/lib/l10n/ka.json
index deb9b82e1d4..c2915ffb84b 100644
--- a/lib/l10n/ka.json
+++ b/lib/l10n/ka.json
@@ -1,5 +1,278 @@
{ "translations": {
+ "Cannot write into \"config\" directory!" : "Cannot write into \"config\" directory!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "This can usually be fixed by giving the web server write access to the config directory.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it.",
+ "See %s" : "See %s",
+ "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",
+ "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",
+ "Click the following button to confirm your email." : "Click the following button to confirm your email.",
+ "Click the following link to confirm your email." : "Click the following link to confirm your email.",
+ "Confirm your email" : "Confirm your email",
+ "Other activities" : "Other activities",
+ "%1$s and %2$s" : "%1$s and %2$s",
+ "%1$s, %2$s and %3$s" : "%1$s, %2$s and %3$s",
+ "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s and %4$s",
+ "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s and %5$s",
+ "Education Edition" : "Education Edition",
+ "Enterprise bundle" : "Enterprise bundle",
+ "Groupware bundle" : "Groupware bundle",
+ "Hub bundle" : "Hub bundle",
+ "Social sharing bundle" : "Social sharing bundle",
+ "PHP %s or higher is required." : "PHP %s or higher is required.",
+ "PHP with a version lower than %s is required." : "PHP with a version lower than %s is required.",
+ "%sbit or higher PHP required." : "%sbit or higher PHP required.",
+ "The following architectures are supported: %s" : "The following architectures are supported: %s",
+ "The following databases are supported: %s" : "The following databases are supported: %s",
+ "The command line tool %s could not be found" : "The command line tool %s could not be found",
+ "The library %s is not available." : "The library %s is not available.",
+ "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Library %1$s with a version higher than %2$s is required - available version %3$s.",
+ "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Library %1$s with a version lower than %2$s is required - available version %3$s.",
+ "The following platforms are supported: %s" : "The following platforms are supported: %s",
+ "Server version %s or higher is required." : "Server version %s or higher is required.",
+ "Server version %s or lower is required." : "Server version %s or lower is required.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "Logged in user must be an admin, a sub admin or gotten special right to access this setting",
+ "Logged in user must be an admin or sub admin" : "Logged in user must be an admin or sub admin",
+ "Logged in user must be an admin" : "Logged in user must be an admin",
+ "Wiping of device %s has started" : "Wiping of device %s has started",
+ "Wiping of device »%s« has started" : "Wiping of device »%s« has started",
+ "»%s« started remote wipe" : "»%s« started remote wipe",
+ "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished",
+ "Wiping of device %s has finished" : "Wiping of device %s has finished",
+ "Wiping of device »%s« has finished" : "Wiping of device »%s« has finished",
+ "»%s« finished remote wipe" : "»%s« finished remote wipe",
+ "Device or application »%s« has finished the remote wipe process." : "Device or application »%s« has finished the remote wipe process.",
+ "Remote wipe started" : "Remote wipe started",
+ "A remote wipe was started on device %s" : "A remote wipe was started on device %s",
+ "Remote wipe finished" : "Remote wipe finished",
+ "The remote wipe on %s has finished" : "The remote wipe on %s has finished",
+ "Authentication" : "Authentication",
+ "Unknown filetype" : "Unknown filetype",
+ "Invalid image" : "Invalid image",
+ "Avatar image is not square" : "Avatar image is not square",
"Files" : "ფაილები",
- "__language_name__" : "ქართული ენა"
+ "View profile" : "View profile",
+ "Local time: %s" : "Local time: %s",
+ "today" : "today",
+ "tomorrow" : "tomorrow",
+ "yesterday" : "yesterday",
+ "_in %n day_::_in %n days_" : ["in %n day","in %n days"],
+ "_%n day ago_::_%n days ago_" : ["%n day ago","%n days ago"],
+ "next month" : "next month",
+ "last month" : "last month",
+ "_in %n month_::_in %n months_" : ["in %n month","in %n months"],
+ "_%n month ago_::_%n months ago_" : ["%n month ago","%n months ago"],
+ "next year" : "next year",
+ "last year" : "last year",
+ "_in %n year_::_in %n years_" : ["in %n year","in %n years"],
+ "_%n year ago_::_%n years ago_" : ["%n year ago","%n years ago"],
+ "_in %n hour_::_in %n hours_" : ["in %n hour","in %n hours"],
+ "_%n hour ago_::_%n hours ago_" : ["%n hour ago","%n hours ago"],
+ "_in %n minute_::_in %n minutes_" : ["in %n minute","in %n minutes"],
+ "_%n minute ago_::_%n minutes ago_" : ["%n minute ago","%n minutes ago"],
+ "in a few seconds" : "in a few seconds",
+ "seconds ago" : "seconds ago",
+ "Empty file" : "Empty file",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator.",
+ "File already exists" : "File already exists",
+ "Invalid path" : "Invalid path",
+ "Failed to create file from template" : "Failed to create file from template",
+ "Templates" : "Templates",
+ "File name is a reserved word" : "File name is a reserved word",
+ "File name contains at least one invalid character" : "File name contains at least one invalid character",
+ "File name is too long" : "File name is too long",
+ "Dot files are not allowed" : "Dot files are not allowed",
+ "Empty filename is not allowed" : "Empty filename is not allowed",
+ "App \"%s\" cannot be installed because appinfo file cannot be read." : "App \"%s\" cannot be installed because appinfo file cannot be read.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "App \"%s\" cannot be installed because it is not compatible with this version of the server.",
+ "__language_name__" : "ქართული ენა",
+ "This is an automatically sent email, please do not reply." : "This is an automatically sent email, please do not reply.",
+ "Help" : "Help",
+ "Appearance and accessibility" : "Appearance and accessibility",
+ "Apps" : "Apps",
+ "Personal settings" : "Personal settings",
+ "Administration settings" : "Administration settings",
+ "Settings" : "Settings",
+ "Log out" : "Log out",
+ "Users" : "Users",
+ "Email" : "Email",
+ "Mail %s" : "Mail %s",
+ "Fediverse" : "Fediverse",
+ "View %s on the fediverse" : "View %s on the fediverse",
+ "Phone" : "Phone",
+ "Call %s" : "Call %s",
+ "Twitter" : "Twitter",
+ "View %s on Twitter" : "View %s on Twitter",
+ "Website" : "Website",
+ "Visit %s" : "Visit %s",
+ "Address" : "Address",
+ "Profile picture" : "Profile picture",
+ "About" : "About",
+ "Display name" : "Display name",
+ "Headline" : "Headline",
+ "Organisation" : "Organisation",
+ "Role" : "Role",
+ "Unknown user" : "Unknown user",
+ "Additional settings" : "Additional settings",
+ "Enter the database username and name for %s" : "Enter the database username and name for %s",
+ "Enter the database username for %s" : "Enter the database username for %s",
+ "Enter the database name for %s" : "Enter the database name for %s",
+ "You cannot use dots in the database name %s" : "You cannot use dots in the database name %s",
+ "MySQL username and/or password not valid" : "MySQL username and/or password not valid",
+ "You need to enter details of an existing account." : "You need to enter details of an existing account.",
+ "Oracle connection could not be established" : "Oracle connection could not be established",
+ "Oracle username and/or password not valid" : "Oracle username and/or password not valid",
+ "PostgreSQL username and/or password not valid" : "PostgreSQL username and/or password not valid",
+ "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! ",
+ "For the best results, please consider using a GNU/Linux server instead." : "For the best results, please consider using a GNU/Linux server instead.",
+ "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged.",
+ "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.",
+ "Set an admin username." : "Set an admin username.",
+ "Set an admin password." : "Set an admin password.",
+ "Cannot create or write into the data directory %s" : "Cannot create or write into the data directory %s",
+ "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Sharing backend %s must implement the interface OCP\\Share_Backend",
+ "Sharing backend %s not found" : "Sharing backend %s not found",
+ "Sharing backend for %s not found" : "Sharing backend for %s not found",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s shared »%2$s« with you and wants to add:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s shared »%2$s« with you and wants to add",
+ "»%s« added a note to a file shared with you" : "»%s« added a note to a file shared with you",
+ "Open »%s«" : "Open »%s«",
+ "%1$s via %2$s" : "%1$s via %2$s",
+ "You are not allowed to share %s" : "You are not allowed to share %s",
+ "Cannot increase permissions of %s" : "Cannot increase permissions of %s",
+ "Files cannot be shared with delete permissions" : "Files cannot be shared with delete permissions",
+ "Files cannot be shared with create permissions" : "Files cannot be shared with create permissions",
+ "Expiration date is in the past" : "Expiration date is in the past",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Cannot set expiration date more than %n day in the future","Cannot set expiration date more than %n days in the future"],
+ "Sharing is only allowed with group members" : "Sharing is only allowed with group members",
+ "Sharing %s failed, because this item is already shared with user %s" : "Sharing %s failed, because this item is already shared with user %s",
+ "%1$s shared »%2$s« with you" : "%1$s shared »%2$s« with you",
+ "%1$s shared »%2$s« with you." : "%1$s shared »%2$s« with you.",
+ "Click the button below to open it." : "Click the button below to open it.",
+ "The requested share does not exist anymore" : "The requested share does not exist anymore",
+ "The requested share comes from a disabled user" : "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." : "The user was not created because the user limit has been reached. Check your notifications to learn more.",
+ "Could not find category \"%s\"" : "Could not find category \"%s\"",
+ "Sunday" : "Sunday",
+ "Monday" : "Monday",
+ "Tuesday" : "Tuesday",
+ "Wednesday" : "Wednesday",
+ "Thursday" : "Thursday",
+ "Friday" : "Friday",
+ "Saturday" : "Saturday",
+ "Sun." : "Sun.",
+ "Mon." : "Mon.",
+ "Tue." : "Tue.",
+ "Wed." : "Wed.",
+ "Thu." : "Thu.",
+ "Fri." : "Fri.",
+ "Sat." : "Sat.",
+ "Su" : "Su",
+ "Mo" : "Mo",
+ "Tu" : "Tu",
+ "We" : "We",
+ "Th" : "Th",
+ "Fr" : "Fr",
+ "Sa" : "Sa",
+ "January" : "January",
+ "February" : "February",
+ "March" : "March",
+ "April" : "April",
+ "May" : "May",
+ "June" : "June",
+ "July" : "July",
+ "August" : "August",
+ "September" : "September",
+ "October" : "October",
+ "November" : "November",
+ "December" : "December",
+ "Jan." : "Jan.",
+ "Feb." : "Feb.",
+ "Mar." : "Mar.",
+ "Apr." : "Apr.",
+ "May." : "May.",
+ "Jun." : "Jun.",
+ "Jul." : "Jul.",
+ "Aug." : "Aug.",
+ "Sep." : "Sep.",
+ "Oct." : "Oct.",
+ "Nov." : "Nov.",
+ "Dec." : "Dec.",
+ "A valid password must be provided" : "A valid password must be provided",
+ "The username is already being used" : "The username is already being used",
+ "Could not create user" : "Could not create user",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"",
+ "A valid username must be provided" : "A valid username must be provided",
+ "Username contains whitespace at the beginning or at the end" : "Username contains whitespace at the beginning or at the end",
+ "Username must not consist of dots only" : "Username must not consist of dots only",
+ "Username is invalid because files already exist for this user" : "Username is invalid because files already exist for this user",
+ "User disabled" : "User disabled",
+ "Login canceled by app" : "Login canceled by app",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s",
+ "a safe home for all your data" : "a safe home for all your data",
+ "File is currently busy, please try again later" : "File is currently busy, please try again later",
+ "Cannot download file" : "Cannot download file",
+ "Application is not enabled" : "Application is not enabled",
+ "Authentication error" : "Authentication error",
+ "Token expired. Please reload page." : "Token expired. Please reload page.",
+ "No database drivers (sqlite, mysql, or postgresql) installed." : "No database drivers (sqlite, mysql, or postgresql) installed.",
+ "Cannot write into \"config\" directory." : "Cannot write into \"config\" directory.",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "This can usually be fixed by giving the web server write access to the config directory. See %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" : "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s",
+ "Cannot write into \"apps\" directory." : "Cannot write into \"apps\" directory.",
+ "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." : "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.",
+ "Cannot create \"data\" directory." : "Cannot create \"data\" directory.",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "This can usually be fixed by giving the web server write access to the root directory. See %s",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Permissions can usually be fixed by giving the web server write access to the root directory. See %s.",
+ "Your data directory is not writable." : "Your data directory is not writable.",
+ "Setting locale to %s failed." : "Setting locale to %s failed.",
+ "Please install one of these locales on your system and restart your web server." : "Please install one of these locales on your system and restart your web server.",
+ "PHP module %s not installed." : "PHP module %s not installed.",
+ "Please ask your server administrator to install the module." : "Please ask your server administrator to install the module.",
+ "PHP setting \"%s\" is not set to \"%s\"." : "PHP setting \"%s\" is not set to \"%s\".",
+ "Adjusting this setting in php.ini will make Nextcloud run again" : "Adjusting this setting in php.ini will make Nextcloud run again",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.",
+ "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.",
+ "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.",
+ "PHP modules have been installed, but they are still listed as missing?" : "PHP modules have been installed, but they are still listed as missing?",
+ "Please ask your server administrator to restart the web server." : "Please ask your server administrator to restart the web server.",
+ "The required %s config variable is not configured in the config.php file." : "The required %s config variable is not configured in the config.php file.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "Please ask your server administrator to check the Nextcloud configuration.",
+ "Your data directory is readable by other users." : "Your data directory is readable by other users.",
+ "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Please change the permissions to 0770 so that the directory cannot be listed by other users.",
+ "Your data directory must be an absolute path." : "Your data directory must be an absolute path.",
+ "Check the value of \"datadirectory\" in your configuration." : "Check the value of \"datadirectory\" in your configuration.",
+ "Your data directory is invalid." : "Your data directory is invalid.",
+ "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Ensure there is a file called \".ocdata\" in the root of the data directory.",
+ "Action \"%s\" not supported or implemented." : "Action \"%s\" not supported or implemented.",
+ "Authentication failed, wrong token or provider ID given" : "Authentication failed, wrong token or provider ID given",
+ "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parameters missing in order to complete the request. Missing Parameters: \"%s\"",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" already used by cloud federation provider \"%2$s\"",
+ "Cloud Federation Provider with ID: \"%s\" does not exist." : "Cloud Federation Provider with ID: \"%s\" does not exist.",
+ "Could not obtain lock type %d on \"%s\"." : "Could not obtain lock type %d on \"%s\".",
+ "Storage unauthorized. %s" : "Storage unauthorized. %s",
+ "Storage incomplete configuration. %s" : "Storage incomplete configuration. %s",
+ "Storage connection error. %s" : "Storage connection error. %s",
+ "Storage is temporarily not available" : "Storage is temporarily not available",
+ "Storage connection timeout. %s" : "Storage connection timeout. %s",
+ "Free prompt" : "Free prompt",
+ "Runs an arbitrary prompt through the language model." : "Runs an arbitrary prompt through the language model.",
+ "Generate headline" : "Generate headline",
+ "Generates a possible headline for a text." : "Generates a possible headline for a text.",
+ "Summarize" : "Summarize",
+ "Summarizes text by reducing its length without losing key information." : "Summarizes text by reducing its length without losing key information.",
+ "Extract topics" : "Extract topics",
+ "Extracts topics from a text and outputs them separated by commas." : "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." : "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server.",
+ "404" : "404",
+ "Full name" : "Full name",
+ "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."
},"pluralForm" :"nplurals=2; plural=(n!=1);"
} \ No newline at end of file
diff --git a/lib/l10n/ka_GE.js b/lib/l10n/ka_GE.js
index a06bae10006..f82c4678090 100644
--- a/lib/l10n/ka_GE.js
+++ b/lib/l10n/ka_GE.js
@@ -167,7 +167,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "საცავი დროებით ხელმიუწვდომელია",
"Storage connection timeout. %s" : "საცავის კავშირის დროის ამოწურვა. %s",
"Full name" : "სრული სახელი",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "მომხმარებლის სახელში დაშვებულია მხოლოდ შემდეგი ნიშნები: \"a-z\", \"A-Z\", \"0-9\", და \"_.@-'\"",
"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 ვერსია და გადატვირთეთ თქვენი ვებ-სერვერი."
},
diff --git a/lib/l10n/ka_GE.json b/lib/l10n/ka_GE.json
index 4ffc6bfd2ef..6f3cdcd7c33 100644
--- a/lib/l10n/ka_GE.json
+++ b/lib/l10n/ka_GE.json
@@ -165,7 +165,6 @@
"Storage is temporarily not available" : "საცავი დროებით ხელმიუწვდომელია",
"Storage connection timeout. %s" : "საცავის კავშირის დროის ამოწურვა. %s",
"Full name" : "სრული სახელი",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "მომხმარებლის სახელში დაშვებულია მხოლოდ შემდეგი ნიშნები: \"a-z\", \"A-Z\", \"0-9\", და \"_.@-'\"",
"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 ვერსია და გადატვირთეთ თქვენი ვებ-სერვერი."
},"pluralForm" :"nplurals=2; plural=(n!=1);"
diff --git a/lib/l10n/ko.js b/lib/l10n/ko.js
index 00377f3d7e3..5075cd053e6 100644
--- a/lib/l10n/ko.js
+++ b/lib/l10n/ko.js
@@ -2,10 +2,17 @@ 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 보기",
+ "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를 변경하기 전 문서를 읽어 보십시오",
"The page could not be found on the server." : "페이지를 서버에서 찾을 수 없습니다.",
+ "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",
@@ -14,26 +21,42 @@ OC.L10N.register(
"Education Edition" : "교육용 에디션",
"Enterprise bundle" : "엔터프라이즈 번들",
"Groupware bundle" : "그룹웨어 번들",
+ "Hub bundle" : "Hub 번들",
"Social sharing bundle" : "소셜 공유 번들",
"PHP %s or higher is required." : "PHP 버전 %s 이상이 필요합니다.",
"PHP with a version lower than %s is required." : "PHP 버전 %s 미만이 필요합니다.",
"%sbit or higher PHP required." : "%s비트 이상의 PHP가 필요합니다.",
+ "The following architectures are supported: %s" : "다음 아키텍쳐를 지원합니다: %s",
+ "The following databases are supported: %s" : "다음 데이터베이스를 지원합니다: %s",
"The command line tool %s could not be found" : "명령행 도구 %s을(를) 찾을 수 없습니다",
"The library %s is not available." : "%s 라이브러리를 사용할 수 없습니다.",
"Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.",
"Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.",
+ "The following platforms are supported: %s" : "다음 플랫폼을 지원합니다: %s",
"Server version %s or higher is required." : "서버 버전 %s 이상이 필요합니다.",
"Server version %s or lower is required." : "서버 버전 %s 미만이 필요합니다.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "로그인된 사용자가 관리자, 부 관리자, 또는 이 설정에 접근할 수 있는 권한을 부여받은 사용자일 것입니다.",
"Logged in user must be an admin or sub admin" : "로그인한 사용자는 관리자 또는 부 관리자여야 합니다.",
"Logged in user must be an admin" : "로그인한 사용자는 관리자여야 합니다.",
"Wiping of device %s has started" : "디바이스 %s의 완전 삭제가 시작되었습니다.",
"Wiping of device »%s« has started" : "디바이스 »%s«의 완전 삭제가 시작되었습니다.",
+ "»%s« started remote wipe" : "»%s«에서 원격 제거를 시작했습니다",
+ "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "기기 또는 앱 »%s«에서 원격 제거 과정을 시작했습니다. 작업이 완료될 경우 추가로 이메일을 발송합니다.",
+ "Wiping of device %s has finished" : "기기 %s의 완전 제거가 완료됨",
+ "Wiping of device »%s« has finished" : "기기 »%s«의 완전 제거가 완료됨",
+ "»%s« finished remote wipe" : "»%s«이(가) 원격 제거를 완료함",
+ "Device or application »%s« has finished the remote wipe process." : "기기 또는 앱 »%s«이(가) 원격 제거 작업을 완료했습니다.",
+ "Remote wipe started" : "원격 제거가 시작됨",
+ "A remote wipe was started on device %s" : "원격 제거가 기기 %s에서 시작됨",
+ "Remote wipe finished" : "원격 제거가 완료됨",
+ "The remote wipe on %s has finished" : "%s에서의 원격 제거가 완료됨",
"Authentication" : "인증",
"Unknown filetype" : "알 수 없는 파일 형식",
"Invalid image" : "잘못된 사진",
"Avatar image is not square" : "아바타 사진이 정사각형이 아님",
"Files" : "파일",
"View profile" : "프로필 보기",
+ "Local time: %s" : "현지 시간: %s",
"today" : "오늘",
"tomorrow" : "내일",
"yesterday" : "어제",
@@ -56,6 +79,8 @@ OC.L10N.register(
"Empty file" : "빈 파일",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ID: %s인 모듈이 존재하지 않습니다. 앱 설정에서 확인하거나 시스템 관리자에게 연락하십시오.",
"File already exists" : "파일이 이미 있습니다.",
+ "Invalid path" : "잘못된 경로",
+ "Failed to create file from template" : "템플릿으로 새 파일을 만들 수 없음",
"Templates" : "템플릿",
"File name is a reserved word" : "파일 이름이 예약된 단어임",
"File name contains at least one invalid character" : "파일 이름에 잘못된 글자가 한 자 이상 있음",
@@ -75,17 +100,29 @@ OC.L10N.register(
"Log out" : "로그아웃",
"Users" : "사용자",
"Email" : "이메일",
+ "Mail %s" : "%s에게 메일 보내기",
+ "Fediverse" : "Fediverse",
+ "View %s on the fediverse" : "Fediverse에서 %s 보기",
"Phone" : "전화 번호",
+ "Call %s" : "%s에게 전화하기",
"Twitter" : "Twitter",
+ "View %s on Twitter" : "Twitter에서 %s 보기",
"Website" : "웹 사이트",
+ "Visit %s" : "%s 방문하기",
"Address" : "주소",
"Profile picture" : "프로필 사진",
"About" : "정보",
+ "Display name" : "표시 이름",
"Headline" : "표제",
"Organisation" : "조직",
"Role" : "직책",
"Unknown user" : "알려지지 않은 사용자",
"Additional settings" : "고급 설정",
+ "Enter the database username and name for %s" : "%s에 대한 데이터베이스 사용자 이름과 이름을 입력하십시오",
+ "Enter the database username for %s" : "%s에 대한 데이터베이스 사용자 이름을 입력하십시오",
+ "Enter the database name for %s" : "%s에 대한 데이터베이스 이름을 입력하십시오",
+ "You cannot use dots in the database name %s" : "데이터베이스 이름에 점(.)을 사용할 수 없습니다 %s",
+ "MySQL username and/or password not valid" : "MySQL 사용자 이름 또는 암호가 잘못되었습니다",
"You need to enter details of an existing account." : "존재하는 계정 정보를 입력해야 합니다.",
"Oracle connection could not be established" : "Oracle 연결을 수립할 수 없습니다.",
"Oracle username and/or password not valid" : "Oracle 사용자 이름이나 암호가 잘못되었습니다.",
@@ -96,6 +133,7 @@ OC.L10N.register(
"Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "php.ini의 open_basedir 설정을 삭제하거나 64비트 PHP로 전환하십시오.",
"Set an admin username." : "관리자의 사용자 이름을 설정합니다.",
"Set an admin password." : "관리자의 암호를 설정합니다.",
+ "Cannot create or write into the data directory %s" : "데이터 디렉토리 %s을(를) 만들거나 쓰기 작업을 할 수 없음",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "공유 백엔드 %s에서 OCP\\Share_Backend 인터페이스를 구현해야 함",
"Sharing backend %s not found" : "공유 백엔드 %s을(를) 찾을 수 없음",
"Sharing backend for %s not found" : "%s의 공유 백엔드를 찾을 수 없음",
@@ -106,10 +144,18 @@ OC.L10N.register(
"%1$s via %2$s" : "%1$s(%2$s 경유)",
"You are not allowed to share %s" : "%s을(를) 공유할 수 있는 권한이 없습니다",
"Cannot increase permissions of %s" : "%s의 권한을 늘릴 수 없습니다.",
+ "Files cannot be shared with delete permissions" : "파일을 삭제 권한으로 공유할 수 없습니다",
+ "Files cannot be shared with create permissions" : "파일을 생성 권한으로 공유할 수 없습니다",
"Expiration date is in the past" : "만료 날짜가 과거입니다",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["만료 날짜는 최대 %s일까지 설정할 수 있습니다"],
+ "Sharing is only allowed with group members" : "같은 그룹 사용자에게만 공유할 수 있습니다",
"Sharing %s failed, because this item is already shared with user %s" : "%s을(를) 공유할 수 없습니다. 이 항목을 이미 %s 님과 공유하고 있습니다",
+ "%1$s shared »%2$s« with you" : "%1$s 님이 »%2$s« 항목을 공유했습니다",
+ "%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" : "일요일",
"Monday" : "월요일",
@@ -159,32 +205,54 @@ OC.L10N.register(
"A valid password must be provided" : "올바른 암호를 입력해야 합니다",
"The username is already being used" : "사용자 이름이 이미 존재합니다",
"Could not create user" : "사용자를 만들 수 없습니다",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "사용자 이름에는 다음 문자만 사용할 수 있습니다: \"a-z, \"A-Z\", \"0-9\", 공백, \"_.@-'\"",
"A valid username must be provided" : "올바른 사용자 이름을 입력해야 합니다",
"Username contains whitespace at the beginning or at the end" : "사용자 이름의 시작이나 끝에 공백이 있습니다",
"Username must not consist of dots only" : "사용자 이름에 마침표만 있으면 안 됩니다",
"Username is invalid because files already exist for this user" : "무효한 사용자이름. 이미 존재함",
"User disabled" : "사용자 비활성화됨",
"Login canceled by app" : "앱에서 로그인 취소함",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "앱 \"%1$s\"이(가) 다음 의존성을 만족하지 않아 설치될 수 없습니다: %2$s",
"a safe home for all your data" : "내 모든 데이터의 안전한 저장소",
"File is currently busy, please try again later" : "파일이 현재 사용 중, 나중에 다시 시도하십시오",
+ "Cannot download file" : "파일을 다운로드할 수 없음",
"Application is not enabled" : "앱이 활성화되지 않았습니다",
"Authentication error" : "인증 오류",
"Token expired. Please reload page." : "토큰이 만료되었습니다. 페이지를 새로 고치십시오.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "데이터베이스 드라이버(sqlite, mysql, postgresql)가 설치되지 않았습니다.",
+ "Cannot write into \"config\" directory." : "\"config\" 디렉토리에 기록할 수 없습니다",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "config 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %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" : "config.php 파일을 읽기 전용으로 하시려는 경우, 설정의 \"config_is_read_only\"를 true로 하십시오. %s를 참조하십시오.",
+ "Cannot write into \"apps\" directory." : "\"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." : "apps 디렉토리에 웹 서버의 쓰기 권한을 부여하거나 설정 파일에서 앱 스토어를 비활성화하면 해결됩니다.",
+ "Cannot create \"data\" directory." : "\"data\" 디렉토리를 만들 수 없음",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "루트 디렉토리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 을(를) 참조하십시오",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "루트 디렉토리에 웹 서버의 쓰기 권한을 부여해서 권한 문제를 해결할 수 있습니다. %s을(를) 참조하십시오.",
+ "Your data directory is not writable." : "서버의 데이터 디렉토리에 쓰기 작업을 할 수 없습니다.",
+ "Setting locale to %s failed." : "지역을 %s(으)로 설정할 수 없음",
+ "Please install one of these locales on your system and restart your web server." : "다음 중 하나 이상의 로캘을 시스템에 설치하고 웹 서버를 다시 시작하십시오.",
"PHP module %s not installed." : "PHP 모듈 %s이(가) 설치되지 않았습니다.",
"Please ask your server administrator to install the module." : "서버 관리자에게 모듈 설치를 요청하십시오.",
"PHP setting \"%s\" is not set to \"%s\"." : "PHP 설정 \"%s\"이(가) \"%s\"(으)로 설정되어 있지 않습니다.",
"Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini 파일에서 설정을 변경하면 Nextcloud가 다시 실행됩니다",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "현재 <code>mbstring.func_overload</code>가 <code>%s</code>(으)로 설정되어 있으나, 올바른 값은 <code>0</code>입니다.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "이 문제를 해결하려면 php.ini 에서 <code>mbstring.func_overload</code>를 <code>0</code>으로 설정하십시오.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP에서 인라인 문서 블록을 삭제하도록 설정되어 있습니다. 일부 코어 앱을 사용하지 못할 수도 있습니다.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Zend OPcache, eAccelerator 같은 캐시/가속기 문제일 수도 있습니다.",
"PHP modules have been installed, but they are still listed as missing?" : "PHP 모듈이 설치되었지만 여전히 없는 것으로 나타납니까?",
"Please ask your server administrator to restart the web server." : "서버 관리자에게 웹 서버 재시작을 요청하십시오.",
+ "The required %s config variable is not configured in the config.php file." : "필수 설정 변수 %s이(가) config.php 파일에서 설정되지 않았습니다.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "서버 관리자에게 Nextcloud 설정에 대한 확인을 요청하십시오.",
+ "Your data directory is readable by other users." : "현재 데이터 디렉토리를 다른 사람이 읽을 수 있습니다.",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "권한을 0770으로 변경하여 다른 사용자가 읽을 수 없도록 하십시오.",
+ "Your data directory must be an absolute path." : "데이터 디렉토리는 절대경로여야 합니다.",
+ "Check the value of \"datadirectory\" in your configuration." : "설정에서 \"datadirectory\"의 값을 확인하십시오.",
+ "Your data directory is invalid." : "데이터 디렉토리가 올바르지 않습니다.",
"Ensure there is a file called \".ocdata\" in the root of the data directory." : "데이터 디렉터리의 최상위 디렉터리에 \".ocdata\" 파일이 있는지 확인하십시오.",
"Action \"%s\" not supported or implemented." : "동작 \"%s\"을(를) 지원하지 않거나 사용할 수 없습니다. ",
"Authentication failed, wrong token or provider ID given" : "인증이 실패하였습니다. 토큰이나 프로바이더 ID가 틀렸습니다.",
"Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "요청을 완료하기 위한 매개변수가 누락되었습니다. 누락된 매개변수: \"%s\"",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\"은(는) 클라우드 연합 제공자 \"%2$s\"이(가) 이미 사용 중입니다.",
"Cloud Federation Provider with ID: \"%s\" does not exist." : "ID가 \"%s\"인 클라우드 연합 제공자가 없습니다.",
"Could not obtain lock type %d on \"%s\"." : "잠금 형식 %d을(를) \"%s\"에 대해 얻을 수 없습니다.",
"Storage unauthorized. %s" : "저장소가 인증되지 않았습니다. %s",
@@ -192,10 +260,17 @@ OC.L10N.register(
"Storage connection error. %s" : "저장소 연결 오류입니다. %s",
"Storage is temporarily not available" : "저장소를 임시로 사용할 수 없음",
"Storage connection timeout. %s" : "저장소 연결 시간이 초과되었습니다. %s",
+ "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의 파일이 올바르게 교체되지 않았습니다. 서버와 호환되는 버전인지 확인하십시오.",
"Full name" : "전체 이름",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "다음 문자만 이름에 사용할 수 있습니다: \"a-z\", \"A-Z\", \"0-9\", 및 \"_.@-'\"",
"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 버전을 업데이트하고 웹 서버를 다시 시작하십시오."
+ "To fix this issue update your libxml2 version and restart your web server." : "이 문제를 해결하려면 libxml2 버전을 업데이트하고 웹 서버를 다시 시작하십시오.",
+ "PostgreSQL >= 9 required." : "PostgreSQL 버전 9 이상이 필요합니다",
+ "Please upgrade your database version." : "데이터베이스 버전을 업그레이드 하십시오"
},
"nplurals=1; plural=0;");
diff --git a/lib/l10n/ko.json b/lib/l10n/ko.json
index 0fb317916e3..d222ae80913 100644
--- a/lib/l10n/ko.json
+++ b/lib/l10n/ko.json
@@ -1,9 +1,16 @@
{ "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 보기",
+ "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를 변경하기 전 문서를 읽어 보십시오",
"The page could not be found on the server." : "페이지를 서버에서 찾을 수 없습니다.",
+ "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",
@@ -12,26 +19,42 @@
"Education Edition" : "교육용 에디션",
"Enterprise bundle" : "엔터프라이즈 번들",
"Groupware bundle" : "그룹웨어 번들",
+ "Hub bundle" : "Hub 번들",
"Social sharing bundle" : "소셜 공유 번들",
"PHP %s or higher is required." : "PHP 버전 %s 이상이 필요합니다.",
"PHP with a version lower than %s is required." : "PHP 버전 %s 미만이 필요합니다.",
"%sbit or higher PHP required." : "%s비트 이상의 PHP가 필요합니다.",
+ "The following architectures are supported: %s" : "다음 아키텍쳐를 지원합니다: %s",
+ "The following databases are supported: %s" : "다음 데이터베이스를 지원합니다: %s",
"The command line tool %s could not be found" : "명령행 도구 %s을(를) 찾을 수 없습니다",
"The library %s is not available." : "%s 라이브러리를 사용할 수 없습니다.",
"Library %1$s with a version higher than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.",
"Library %1$s with a version lower than %2$s is required - available version %3$s." : "%1$s 라이브러리의 버전 %2$s 이상이 필요합니다. 사용 가능한 버전은 %3$s입니다.",
+ "The following platforms are supported: %s" : "다음 플랫폼을 지원합니다: %s",
"Server version %s or higher is required." : "서버 버전 %s 이상이 필요합니다.",
"Server version %s or lower is required." : "서버 버전 %s 미만이 필요합니다.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "로그인된 사용자가 관리자, 부 관리자, 또는 이 설정에 접근할 수 있는 권한을 부여받은 사용자일 것입니다.",
"Logged in user must be an admin or sub admin" : "로그인한 사용자는 관리자 또는 부 관리자여야 합니다.",
"Logged in user must be an admin" : "로그인한 사용자는 관리자여야 합니다.",
"Wiping of device %s has started" : "디바이스 %s의 완전 삭제가 시작되었습니다.",
"Wiping of device »%s« has started" : "디바이스 »%s«의 완전 삭제가 시작되었습니다.",
+ "»%s« started remote wipe" : "»%s«에서 원격 제거를 시작했습니다",
+ "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "기기 또는 앱 »%s«에서 원격 제거 과정을 시작했습니다. 작업이 완료될 경우 추가로 이메일을 발송합니다.",
+ "Wiping of device %s has finished" : "기기 %s의 완전 제거가 완료됨",
+ "Wiping of device »%s« has finished" : "기기 »%s«의 완전 제거가 완료됨",
+ "»%s« finished remote wipe" : "»%s«이(가) 원격 제거를 완료함",
+ "Device or application »%s« has finished the remote wipe process." : "기기 또는 앱 »%s«이(가) 원격 제거 작업을 완료했습니다.",
+ "Remote wipe started" : "원격 제거가 시작됨",
+ "A remote wipe was started on device %s" : "원격 제거가 기기 %s에서 시작됨",
+ "Remote wipe finished" : "원격 제거가 완료됨",
+ "The remote wipe on %s has finished" : "%s에서의 원격 제거가 완료됨",
"Authentication" : "인증",
"Unknown filetype" : "알 수 없는 파일 형식",
"Invalid image" : "잘못된 사진",
"Avatar image is not square" : "아바타 사진이 정사각형이 아님",
"Files" : "파일",
"View profile" : "프로필 보기",
+ "Local time: %s" : "현지 시간: %s",
"today" : "오늘",
"tomorrow" : "내일",
"yesterday" : "어제",
@@ -54,6 +77,8 @@
"Empty file" : "빈 파일",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "ID: %s인 모듈이 존재하지 않습니다. 앱 설정에서 확인하거나 시스템 관리자에게 연락하십시오.",
"File already exists" : "파일이 이미 있습니다.",
+ "Invalid path" : "잘못된 경로",
+ "Failed to create file from template" : "템플릿으로 새 파일을 만들 수 없음",
"Templates" : "템플릿",
"File name is a reserved word" : "파일 이름이 예약된 단어임",
"File name contains at least one invalid character" : "파일 이름에 잘못된 글자가 한 자 이상 있음",
@@ -73,17 +98,29 @@
"Log out" : "로그아웃",
"Users" : "사용자",
"Email" : "이메일",
+ "Mail %s" : "%s에게 메일 보내기",
+ "Fediverse" : "Fediverse",
+ "View %s on the fediverse" : "Fediverse에서 %s 보기",
"Phone" : "전화 번호",
+ "Call %s" : "%s에게 전화하기",
"Twitter" : "Twitter",
+ "View %s on Twitter" : "Twitter에서 %s 보기",
"Website" : "웹 사이트",
+ "Visit %s" : "%s 방문하기",
"Address" : "주소",
"Profile picture" : "프로필 사진",
"About" : "정보",
+ "Display name" : "표시 이름",
"Headline" : "표제",
"Organisation" : "조직",
"Role" : "직책",
"Unknown user" : "알려지지 않은 사용자",
"Additional settings" : "고급 설정",
+ "Enter the database username and name for %s" : "%s에 대한 데이터베이스 사용자 이름과 이름을 입력하십시오",
+ "Enter the database username for %s" : "%s에 대한 데이터베이스 사용자 이름을 입력하십시오",
+ "Enter the database name for %s" : "%s에 대한 데이터베이스 이름을 입력하십시오",
+ "You cannot use dots in the database name %s" : "데이터베이스 이름에 점(.)을 사용할 수 없습니다 %s",
+ "MySQL username and/or password not valid" : "MySQL 사용자 이름 또는 암호가 잘못되었습니다",
"You need to enter details of an existing account." : "존재하는 계정 정보를 입력해야 합니다.",
"Oracle connection could not be established" : "Oracle 연결을 수립할 수 없습니다.",
"Oracle username and/or password not valid" : "Oracle 사용자 이름이나 암호가 잘못되었습니다.",
@@ -94,6 +131,7 @@
"Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "php.ini의 open_basedir 설정을 삭제하거나 64비트 PHP로 전환하십시오.",
"Set an admin username." : "관리자의 사용자 이름을 설정합니다.",
"Set an admin password." : "관리자의 암호를 설정합니다.",
+ "Cannot create or write into the data directory %s" : "데이터 디렉토리 %s을(를) 만들거나 쓰기 작업을 할 수 없음",
"Sharing backend %s must implement the interface OCP\\Share_Backend" : "공유 백엔드 %s에서 OCP\\Share_Backend 인터페이스를 구현해야 함",
"Sharing backend %s not found" : "공유 백엔드 %s을(를) 찾을 수 없음",
"Sharing backend for %s not found" : "%s의 공유 백엔드를 찾을 수 없음",
@@ -104,10 +142,18 @@
"%1$s via %2$s" : "%1$s(%2$s 경유)",
"You are not allowed to share %s" : "%s을(를) 공유할 수 있는 권한이 없습니다",
"Cannot increase permissions of %s" : "%s의 권한을 늘릴 수 없습니다.",
+ "Files cannot be shared with delete permissions" : "파일을 삭제 권한으로 공유할 수 없습니다",
+ "Files cannot be shared with create permissions" : "파일을 생성 권한으로 공유할 수 없습니다",
"Expiration date is in the past" : "만료 날짜가 과거입니다",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["만료 날짜는 최대 %s일까지 설정할 수 있습니다"],
+ "Sharing is only allowed with group members" : "같은 그룹 사용자에게만 공유할 수 있습니다",
"Sharing %s failed, because this item is already shared with user %s" : "%s을(를) 공유할 수 없습니다. 이 항목을 이미 %s 님과 공유하고 있습니다",
+ "%1$s shared »%2$s« with you" : "%1$s 님이 »%2$s« 항목을 공유했습니다",
+ "%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" : "일요일",
"Monday" : "월요일",
@@ -157,32 +203,54 @@
"A valid password must be provided" : "올바른 암호를 입력해야 합니다",
"The username is already being used" : "사용자 이름이 이미 존재합니다",
"Could not create user" : "사용자를 만들 수 없습니다",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "사용자 이름에는 다음 문자만 사용할 수 있습니다: \"a-z, \"A-Z\", \"0-9\", 공백, \"_.@-'\"",
"A valid username must be provided" : "올바른 사용자 이름을 입력해야 합니다",
"Username contains whitespace at the beginning or at the end" : "사용자 이름의 시작이나 끝에 공백이 있습니다",
"Username must not consist of dots only" : "사용자 이름에 마침표만 있으면 안 됩니다",
"Username is invalid because files already exist for this user" : "무효한 사용자이름. 이미 존재함",
"User disabled" : "사용자 비활성화됨",
"Login canceled by app" : "앱에서 로그인 취소함",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "앱 \"%1$s\"이(가) 다음 의존성을 만족하지 않아 설치될 수 없습니다: %2$s",
"a safe home for all your data" : "내 모든 데이터의 안전한 저장소",
"File is currently busy, please try again later" : "파일이 현재 사용 중, 나중에 다시 시도하십시오",
+ "Cannot download file" : "파일을 다운로드할 수 없음",
"Application is not enabled" : "앱이 활성화되지 않았습니다",
"Authentication error" : "인증 오류",
"Token expired. Please reload page." : "토큰이 만료되었습니다. 페이지를 새로 고치십시오.",
"No database drivers (sqlite, mysql, or postgresql) installed." : "데이터베이스 드라이버(sqlite, mysql, postgresql)가 설치되지 않았습니다.",
+ "Cannot write into \"config\" directory." : "\"config\" 디렉토리에 기록할 수 없습니다",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "config 디렉터리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %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" : "config.php 파일을 읽기 전용으로 하시려는 경우, 설정의 \"config_is_read_only\"를 true로 하십시오. %s를 참조하십시오.",
+ "Cannot write into \"apps\" directory." : "\"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." : "apps 디렉토리에 웹 서버의 쓰기 권한을 부여하거나 설정 파일에서 앱 스토어를 비활성화하면 해결됩니다.",
+ "Cannot create \"data\" directory." : "\"data\" 디렉토리를 만들 수 없음",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "루트 디렉토리에 웹 서버의 쓰기 권한을 부여해서 해결할 수 있습니다. %s 을(를) 참조하십시오",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "루트 디렉토리에 웹 서버의 쓰기 권한을 부여해서 권한 문제를 해결할 수 있습니다. %s을(를) 참조하십시오.",
+ "Your data directory is not writable." : "서버의 데이터 디렉토리에 쓰기 작업을 할 수 없습니다.",
+ "Setting locale to %s failed." : "지역을 %s(으)로 설정할 수 없음",
+ "Please install one of these locales on your system and restart your web server." : "다음 중 하나 이상의 로캘을 시스템에 설치하고 웹 서버를 다시 시작하십시오.",
"PHP module %s not installed." : "PHP 모듈 %s이(가) 설치되지 않았습니다.",
"Please ask your server administrator to install the module." : "서버 관리자에게 모듈 설치를 요청하십시오.",
"PHP setting \"%s\" is not set to \"%s\"." : "PHP 설정 \"%s\"이(가) \"%s\"(으)로 설정되어 있지 않습니다.",
"Adjusting this setting in php.ini will make Nextcloud run again" : "php.ini 파일에서 설정을 변경하면 Nextcloud가 다시 실행됩니다",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "현재 <code>mbstring.func_overload</code>가 <code>%s</code>(으)로 설정되어 있으나, 올바른 값은 <code>0</code>입니다.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "이 문제를 해결하려면 php.ini 에서 <code>mbstring.func_overload</code>를 <code>0</code>으로 설정하십시오.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP에서 인라인 문서 블록을 삭제하도록 설정되어 있습니다. 일부 코어 앱을 사용하지 못할 수도 있습니다.",
"This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Zend OPcache, eAccelerator 같은 캐시/가속기 문제일 수도 있습니다.",
"PHP modules have been installed, but they are still listed as missing?" : "PHP 모듈이 설치되었지만 여전히 없는 것으로 나타납니까?",
"Please ask your server administrator to restart the web server." : "서버 관리자에게 웹 서버 재시작을 요청하십시오.",
+ "The required %s config variable is not configured in the config.php file." : "필수 설정 변수 %s이(가) config.php 파일에서 설정되지 않았습니다.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "서버 관리자에게 Nextcloud 설정에 대한 확인을 요청하십시오.",
+ "Your data directory is readable by other users." : "현재 데이터 디렉토리를 다른 사람이 읽을 수 있습니다.",
"Please change the permissions to 0770 so that the directory cannot be listed by other users." : "권한을 0770으로 변경하여 다른 사용자가 읽을 수 없도록 하십시오.",
+ "Your data directory must be an absolute path." : "데이터 디렉토리는 절대경로여야 합니다.",
+ "Check the value of \"datadirectory\" in your configuration." : "설정에서 \"datadirectory\"의 값을 확인하십시오.",
+ "Your data directory is invalid." : "데이터 디렉토리가 올바르지 않습니다.",
"Ensure there is a file called \".ocdata\" in the root of the data directory." : "데이터 디렉터리의 최상위 디렉터리에 \".ocdata\" 파일이 있는지 확인하십시오.",
"Action \"%s\" not supported or implemented." : "동작 \"%s\"을(를) 지원하지 않거나 사용할 수 없습니다. ",
"Authentication failed, wrong token or provider ID given" : "인증이 실패하였습니다. 토큰이나 프로바이더 ID가 틀렸습니다.",
"Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "요청을 완료하기 위한 매개변수가 누락되었습니다. 누락된 매개변수: \"%s\"",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\"은(는) 클라우드 연합 제공자 \"%2$s\"이(가) 이미 사용 중입니다.",
"Cloud Federation Provider with ID: \"%s\" does not exist." : "ID가 \"%s\"인 클라우드 연합 제공자가 없습니다.",
"Could not obtain lock type %d on \"%s\"." : "잠금 형식 %d을(를) \"%s\"에 대해 얻을 수 없습니다.",
"Storage unauthorized. %s" : "저장소가 인증되지 않았습니다. %s",
@@ -190,10 +258,17 @@
"Storage connection error. %s" : "저장소 연결 오류입니다. %s",
"Storage is temporarily not available" : "저장소를 임시로 사용할 수 없음",
"Storage connection timeout. %s" : "저장소 연결 시간이 초과되었습니다. %s",
+ "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의 파일이 올바르게 교체되지 않았습니다. 서버와 호환되는 버전인지 확인하십시오.",
"Full name" : "전체 이름",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "다음 문자만 이름에 사용할 수 있습니다: \"a-z\", \"A-Z\", \"0-9\", 및 \"_.@-'\"",
"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 버전을 업데이트하고 웹 서버를 다시 시작하십시오."
+ "To fix this issue update your libxml2 version and restart your web server." : "이 문제를 해결하려면 libxml2 버전을 업데이트하고 웹 서버를 다시 시작하십시오.",
+ "PostgreSQL >= 9 required." : "PostgreSQL 버전 9 이상이 필요합니다",
+ "Please upgrade your database version." : "데이터베이스 버전을 업그레이드 하십시오"
},"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 0bb3bf18e90..72dc4935acc 100644
--- a/lib/l10n/lt_LT.js
+++ b/lib/l10n/lt_LT.js
@@ -5,6 +5,7 @@ OC.L10N.register(
"See %s" : "Žiūrėkite %s",
"Sample configuration detected" : "Aptiktas konfigūracijos pavyzdys",
"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" : "Pastebėta, kad nukopijuota pavyzdinė konfigūracija. Tai gali pažeisti jūsų diegimą ir yra nepalaikoma. Prieš atliekant pakeitimus config.php faile, prašome perskaityti dokumentaciją.",
+ "The page could not be found on the server." : "Šio puslapio nepavyko rasti serveryje.",
"Other activities" : "Kitos veiklos",
"%1$s and %2$s" : "%1$s ir %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s ir %3$s",
@@ -191,7 +192,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "Saugykla yra laikinai neprieinama",
"Storage connection timeout. %s" : "Sujungimo su saugykla laikas baigėsi. %s",
"Full name" : "Vardas, pavardė",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Naudotojo varde leidžiama naudoti tik šiuos simbolius: „a-z“, „A-Z“, „0-9“, ir „_.@-'“",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Reikalinga ne mažesnė nei libxml2 2.7.0 versija. Šiuo metu yra instaliuota %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Atnaujinkite libxml2 versiją ir perkraukite žiniatinklio serverį, kad sutvarkytumėte šią problemą."
},
diff --git a/lib/l10n/lt_LT.json b/lib/l10n/lt_LT.json
index a2ef9c04ee2..bb4ccc9e355 100644
--- a/lib/l10n/lt_LT.json
+++ b/lib/l10n/lt_LT.json
@@ -3,6 +3,7 @@
"See %s" : "Žiūrėkite %s",
"Sample configuration detected" : "Aptiktas konfigūracijos pavyzdys",
"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" : "Pastebėta, kad nukopijuota pavyzdinė konfigūracija. Tai gali pažeisti jūsų diegimą ir yra nepalaikoma. Prieš atliekant pakeitimus config.php faile, prašome perskaityti dokumentaciją.",
+ "The page could not be found on the server." : "Šio puslapio nepavyko rasti serveryje.",
"Other activities" : "Kitos veiklos",
"%1$s and %2$s" : "%1$s ir %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s ir %3$s",
@@ -189,7 +190,6 @@
"Storage is temporarily not available" : "Saugykla yra laikinai neprieinama",
"Storage connection timeout. %s" : "Sujungimo su saugykla laikas baigėsi. %s",
"Full name" : "Vardas, pavardė",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Naudotojo varde leidžiama naudoti tik šiuos simbolius: „a-z“, „A-Z“, „0-9“, ir „_.@-'“",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Reikalinga ne mažesnė nei libxml2 2.7.0 versija. Šiuo metu yra instaliuota %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Atnaujinkite libxml2 versiją ir perkraukite žiniatinklio serverį, kad sutvarkytumėte šią problemą."
},"pluralForm" :"nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);"
diff --git a/lib/l10n/lv.js b/lib/l10n/lv.js
index a94b81c32bd..5ed4ea6e4a5 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",
+ "404" : "404",
"Full name" : "Pilns vārds"
},
"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..10912106676 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",
+ "404" : "404",
"Full name" : "Pilns vārds"
},"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 ed86eaa14f8..c948ee8d4de 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" : "Е-пошта верификација",
@@ -65,8 +64,8 @@ OC.L10N.register(
"_%n day ago_::_%n days ago_" : ["пред 1 ден","пред %n дена"],
"next month" : "следниот месец",
"last month" : "предходниот месец",
- "_in %n month_::_in %n months_" : ["за 1 месец","за %n месеца"],
- "_%n month ago_::_%n months ago_" : ["пред 1 месец","пред %n месеци"],
+ "_in %n month_::_in %n months_" : ["за %n месец","за %n месеца"],
+ "_%n month ago_::_%n months ago_" : ["пред %n месец","пред %n месеци"],
"next year" : "следниот месец",
"last year" : "предходната година",
"_in %n year_::_in %n years_" : ["за 1 година","за %n години"],
@@ -262,9 +261,8 @@ OC.L10N.register(
"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 не се преклопени коректно. Проверете дали верзијата е компатибилна со серверот.",
+ "404" : "404",
"Full name" : "Цело име",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Максималниот број на корисници е достигнат. Проверете ги вашите известувања за да дознаете повеќе.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Само следниве карактери се дозволени во корисничкото име:: \"a-z\", \"A-Z\", \"0-9\", и \"_.@-'\"",
"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 ",
diff --git a/lib/l10n/mk.json b/lib/l10n/mk.json
index e75fcec8743..19a77b4749e 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" : "Е-пошта верификација",
@@ -63,8 +62,8 @@
"_%n day ago_::_%n days ago_" : ["пред 1 ден","пред %n дена"],
"next month" : "следниот месец",
"last month" : "предходниот месец",
- "_in %n month_::_in %n months_" : ["за 1 месец","за %n месеца"],
- "_%n month ago_::_%n months ago_" : ["пред 1 месец","пред %n месеци"],
+ "_in %n month_::_in %n months_" : ["за %n месец","за %n месеца"],
+ "_%n month ago_::_%n months ago_" : ["пред %n месец","пред %n месеци"],
"next year" : "следниот месец",
"last year" : "предходната година",
"_in %n year_::_in %n years_" : ["за 1 година","за %n години"],
@@ -260,9 +259,8 @@
"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 не се преклопени коректно. Проверете дали верзијата е компатибилна со серверот.",
+ "404" : "404",
"Full name" : "Цело име",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Максималниот број на корисници е достигнат. Проверете ги вашите известувања за да дознаете повеќе.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Само следниве карактери се дозволени во корисничкото име:: \"a-z\", \"A-Z\", \"0-9\", и \"_.@-'\"",
"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 ",
diff --git a/lib/l10n/nb.js b/lib/l10n/nb.js
index 23651c77213..e7386d3abd5 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",
@@ -195,8 +194,8 @@ OC.L10N.register(
"Storage connection error. %s" : "Tilkoblingsfeil for lager. %s",
"Storage is temporarily not available" : "Lagring er midlertidig utilgjengelig",
"Storage connection timeout. %s" : "Tidsavbrudd ved tilkobling av lager: %s",
+ "404" : "404",
"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."
},
diff --git a/lib/l10n/nb.json b/lib/l10n/nb.json
index 32b5ae98957..115e4c2730d 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",
@@ -193,8 +192,8 @@
"Storage connection error. %s" : "Tilkoblingsfeil for lager. %s",
"Storage is temporarily not available" : "Lagring er midlertidig utilgjengelig",
"Storage connection timeout. %s" : "Tidsavbrudd ved tilkobling av lager: %s",
+ "404" : "404",
"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."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
diff --git a/lib/l10n/nl.js b/lib/l10n/nl.js
index 5aa59620e0c..45d097a562a 100644
--- a/lib/l10n/nl.js
+++ b/lib/l10n/nl.js
@@ -248,7 +248,6 @@ OC.L10N.register(
"Storage connection timeout. %s" : "Opslag verbinding time-out. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "De bestanden van app %1$szijn niet correct vervangen. Zorg ervoor dat de versie compatible is met de server.",
"Full name" : "Volledige naam",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Alleen de volgende tekens zijn toegestaan in een gebruikersnaam: \"a-z\", \"A-Z\", \"0-9\", en \"_.@-\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "De minimale versie van libxml2 versie is 2.7.0. Momenteel is versie%s geïnstalleerd.",
"To fix this issue update your libxml2 version and restart your web server." : "Om dit probleem op te lossen, moet je de libxml2 versie bijwerken en je webserver herstarten.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 is vereist.",
diff --git a/lib/l10n/nl.json b/lib/l10n/nl.json
index c4ab53ebcd1..f3bbde75c7a 100644
--- a/lib/l10n/nl.json
+++ b/lib/l10n/nl.json
@@ -246,7 +246,6 @@
"Storage connection timeout. %s" : "Opslag verbinding time-out. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "De bestanden van app %1$szijn niet correct vervangen. Zorg ervoor dat de versie compatible is met de server.",
"Full name" : "Volledige naam",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Alleen de volgende tekens zijn toegestaan in een gebruikersnaam: \"a-z\", \"A-Z\", \"0-9\", en \"_.@-\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "De minimale versie van libxml2 versie is 2.7.0. Momenteel is versie%s geïnstalleerd.",
"To fix this issue update your libxml2 version and restart your web server." : "Om dit probleem op te lossen, moet je de libxml2 versie bijwerken en je webserver herstarten.",
"PostgreSQL >= 9 required." : "PostgreSQL >= 9 is vereist.",
diff --git a/lib/l10n/pl.js b/lib/l10n/pl.js
index 68de01a6171..7dc10139dd0 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,10 +261,17 @@ 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.",
+ "404" : "404",
"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.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "W nazwie użytkownika dozwolone są tylko następujące znaki : \"a-z\", \"A-Z\", \"0-9\" i \"_.@-'\"",
"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",
diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json
index ea9d21d3b76..ed456f68784 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,10 +259,17 @@
"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.",
+ "404" : "404",
"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.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "W nazwie użytkownika dozwolone są tylko następujące znaki : \"a-z\", \"A-Z\", \"0-9\" i \"_.@-'\"",
"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",
diff --git a/lib/l10n/pt_BR.js b/lib/l10n/pt_BR.js
index 265de0ee0a8..cc2e191a85d 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",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Extrair tópicos",
"Extracts topics from a text and outputs them separated by commas." : "Extrai tópicos de um texto e os gera separados por vírgulas.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os arquivos do aplicativo %1$s não foram instalados corretamente. Certifique-se que é uma versão compatível com seu servidor.",
+ "404" : "404",
"Full name" : "Nome completo ",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "O limite de usuários foi atingido e o usuário não foi criado. Verifique suas notificações para saber mais.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Somente os seguintes caracteres são permitidos em um nome de usuário: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"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.",
diff --git a/lib/l10n/pt_BR.json b/lib/l10n/pt_BR.json
index 2609d133dd4..0589fed24a1 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",
@@ -269,9 +268,8 @@
"Extract topics" : "Extrair tópicos",
"Extracts topics from a text and outputs them separated by commas." : "Extrai tópicos de um texto e os gera separados por vírgulas.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Os arquivos do aplicativo %1$s não foram instalados corretamente. Certifique-se que é uma versão compatível com seu servidor.",
+ "404" : "404",
"Full name" : "Nome completo ",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "O limite de usuários foi atingido e o usuário não foi criado. Verifique suas notificações para saber mais.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Somente os seguintes caracteres são permitidos em um nome de usuário: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"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.",
diff --git a/lib/l10n/pt_PT.js b/lib/l10n/pt_PT.js
index c30c214db40..0e6a5c66d05 100644
--- a/lib/l10n/pt_PT.js
+++ b/lib/l10n/pt_PT.js
@@ -200,7 +200,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "Armazenamento temporariamente indisponível",
"Storage connection timeout. %s" : "Tempo de ligação ao armazenamento expirou. %s",
"Full name" : "Nome completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Apenas os seguintes caracteres são permitidos num nome de utilizador: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Necessária pelo menos libxml2 2.7.0. Atualmente %s está instalada.",
"To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema actualize a versão da libxml2 e reinicie o seu servidor web.",
"PostgreSQL >= 9 required." : "Necessário PostgreSQL >= 9",
diff --git a/lib/l10n/pt_PT.json b/lib/l10n/pt_PT.json
index a2df26d40e8..3ed3a6fa046 100644
--- a/lib/l10n/pt_PT.json
+++ b/lib/l10n/pt_PT.json
@@ -198,7 +198,6 @@
"Storage is temporarily not available" : "Armazenamento temporariamente indisponível",
"Storage connection timeout. %s" : "Tempo de ligação ao armazenamento expirou. %s",
"Full name" : "Nome completo",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Apenas os seguintes caracteres são permitidos num nome de utilizador: \"a-z\", \"A-Z\", \"0-9\", e \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Necessária pelo menos libxml2 2.7.0. Atualmente %s está instalada.",
"To fix this issue update your libxml2 version and restart your web server." : "Para corrigir este problema actualize a versão da libxml2 e reinicie o seu servidor web.",
"PostgreSQL >= 9 required." : "Necessário PostgreSQL >= 9",
diff --git a/lib/l10n/ro.js b/lib/l10n/ro.js
index d08182f5786..e80d8f55a47 100644
--- a/lib/l10n/ro.js
+++ b/lib/l10n/ro.js
@@ -2,75 +2,159 @@ OC.L10N.register(
"lib",
{
"Cannot write into \"config\" directory!" : "Nu se poate scrie în folderul \"config\"!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "Aceasta se poate rezolva de obicei dând acces în scriere serverului în directorul configurației.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Dar, dacă preferați să păstrați config.php doar în citire, setați opțiunea \"config_is_read_only\" ca true.",
"See %s" : "Vezi %s",
+ "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplicația %1$s nu este prezentă sau are o versiune incompatibilă cu acest server. Verificați directorul aplicațiilor.",
"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ă.",
+ "%s email verification" : "%s verificare email",
+ "Email verification" : "Verificare email",
+ "Click the following button to confirm your email." : "Apăsați butonul următor pentru confirmare email.",
+ "Click the following link to confirm your email." : "Click pe linkul următor pentru confirmare email.",
+ "Confirm your email" : "Confirmați emailul",
"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",
"%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s și %4$s",
"%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s și %5$s",
+ "Education Edition" : "Ediția pentru educație",
+ "Enterprise bundle" : "Pachetul enterprise",
+ "Groupware bundle" : "Pachetul Groupware",
+ "Hub bundle" : "Pachetul central",
+ "Social sharing bundle" : "Pachetul de partajare",
"PHP %s or higher is required." : "Versiunea PHP %s sau mai mare este necesară.",
"PHP with a version lower than %s is required." : "Este necesară o versiune PHP mai mică decât %s",
"%sbit or higher PHP required." : "Este necesar PHP %sbit sau mai mare.",
+ "The following architectures are supported: %s" : "Sunt suportate următoarele arhitecturi: %s",
+ "The following databases are supported: %s" : "Sunt suportate următoarele baze de date: %s",
"The command line tool %s could not be found" : "Unealta în linie de comandă %s nu a fost găsită",
"The library %s is not available." : "Biblioteca %s nu este disponibilă.",
+ "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Biblioteca %1$s cu o versiune superioară %2$s este necesară - versiunea disponibilă %3$s.",
+ "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Biblioteca %1$s cu o versiune inferioară %2$s este necesară - versiunea disponibilă %3$s.",
+ "The following platforms are supported: %s" : "Sunt suportate următoarele platforme: %s",
+ "Server version %s or higher is required." : "Este necesară versiunea %s a serverului sau superioară.",
+ "Server version %s or lower is required." : "Este necesară versiunea %s a serverului, sau inferioară.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "Utilizatorul trebuie să fie administrator, sub-administrator sau să aibă permisiunea specială de a accesa această setare",
+ "Logged in user must be an admin or sub admin" : "Utilizatorul trebuie să fie administrator sau sub-administrator",
+ "Logged in user must be an admin" : "Utilizatorul trebuie să fie administrator",
+ "Wiping of device %s has started" : "A început curățarea dispozitivului %s ",
+ "Wiping of device »%s« has started" : "A început curățarea dispozitivului »%s« ",
+ "»%s« started remote wipe" : "»%s« a pornit curățarea la distanță",
+ "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Dispozitivul sau aplicația »%s« a inițiat procesul de ștergere la distanță. Veți primi un alt mail când procesul se va finaliza",
+ "Wiping of device %s has finished" : "Curățarea dispozitivului %s s-a finalizat",
+ "Wiping of device »%s« has finished" : "Curățarea dispozitivului »%s« s-a finalizat",
+ "»%s« finished remote wipe" : "»%s« a finalizat curățarea la distanță",
+ "Device or application »%s« has finished the remote wipe process." : "Dispozitivul sau aplicația »%s« a finalizat procesul de curățare la distanță.",
+ "Remote wipe started" : "Curățarea la distanță a fost inițiată",
+ "A remote wipe was started on device %s" : "A fost inițiată o curățare la distanță a dispozitivului %s",
+ "Remote wipe finished" : "Curățarea la distanță a fost finalizată",
+ "The remote wipe on %s has finished" : "Curățarea la distanță a %s a fost finalizată",
"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",
"today" : "astăzi",
"tomorrow" : "mâine",
"yesterday" : "ieri",
- "_%n day ago_::_%n days ago_" : ["Acum o zi","Acum %n zile","Acum %n zile"],
+ "_in %n day_::_in %n days_" : ["în %n zi","în %n zile","în %n zile"],
+ "_%n day ago_::_%n days ago_" : ["%n zi în rumă","Acum %n zile","Acum %n zile"],
"next month" : "luna viitoare",
"last month" : "ultima lună",
+ "_in %n month_::_in %n months_" : ["în %n lună","în %n luni","în %n luni"],
"_%n month ago_::_%n months ago_" : ["%n lună în urmă","%n luni în urmă","%n luni în urmă"],
"next year" : "anul viitor",
"last year" : "ultimul an",
+ "_in %n year_::_in %n years_" : ["în %n an","în %n ani","în %n ani"],
"_%n year ago_::_%n years ago_" : ["%n an în urmă","%n ani în urmâ","%n ani în urmâ"],
+ "_in %n hour_::_in %n hours_" : ["în %n oră","în %n ore","în %n ore"],
"_%n hour ago_::_%n hours ago_" : ["%n oră în urmă","%n ore în urmă","%n ore în urmă"],
+ "_in %n minute_::_in %n minutes_" : ["în %n minut","în %n minute","în %n minute"],
+ "_%n minute ago_::_%n minutes ago_" : ["%n minut în urmă","%n minute în urmă","%n minute în urmă"],
"in a few seconds" : "în câteva secunde",
"seconds ago" : "secunde în urmă",
+ "Empty file" : "Fișier gol",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulul cu ID: %s nu există. Activați-l din setările aplicației sau contactați administratorul.",
"File already exists" : "Fișierul există deja",
+ "Invalid path" : "Cale invalidă",
+ "Failed to create file from template" : "Eroare la crearea fișierului pe baza șablonului",
"Templates" : "Șabloane",
"File name is a reserved word" : "Numele fișierului este un cuvânt rezervat",
"File name contains at least one invalid character" : "Numele fișierului conține cel puțin un caracter invalid",
"File name is too long" : "Numele fișierului este prea lung",
"Dot files are not allowed" : "Fișierele care încep cu caracterul punct nu sunt permise",
"Empty filename is not allowed" : "Nu este permis fișier fără nume",
+ "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplicația \"%s\" nu poate fi instalată deoarece fișierul appinfo nu poate fi citit.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplicația \"%s\" nu poate fi instalată deoarece nu este compatibilă cu versiunea serverului.",
"__language_name__" : "Română",
+ "This is an automatically sent email, please do not reply." : "Acesta este un email automat, nu răspundeți.",
"Help" : "Ajutor",
+ "Appearance and accessibility" : "Aspect și accesibilitate",
"Apps" : "Aplicații",
+ "Personal settings" : "Setări personale",
+ "Administration settings" : "Setări de administrare",
"Settings" : "Setări",
"Log out" : "Ieșire",
"Users" : "Utilizatori",
"Email" : "E-mail",
+ "View %s on the fediverse" : "Vedeți %s pe fediverse",
"Phone" : "Telefon",
+ "Call %s" : "Apel %s",
"Twitter" : "Twitter",
+ "View %s on Twitter" : "Vedeți %s pe X",
"Website" : "Site web",
+ "Visit %s" : "Vizitați %s",
"Address" : "Adresă",
"Profile picture" : "Imagine de profil",
"About" : "Despre",
+ "Display name" : "Nume afișat",
+ "Headline" : "Titlu",
+ "Organisation" : "Organizație",
"Role" : "Rol",
"Unknown user" : "Utilizator necunoscut",
"Additional settings" : "Setări adiționale",
+ "Enter the database username and name for %s" : "Introduceți utilizatorul pentru baza de date și numele pentru %s",
+ "Enter the database username for %s" : "Introduceți utilizatorul pentru %s",
+ "Enter the database name for %s" : "Introduceți numele bazei de date pentru %s",
+ "You cannot use dots in the database name %s" : "Nu puteți folosi puncte în numele bazei de date %s",
+ "MySQL username and/or password not valid" : "Utilizatorul sau/și parola MySQL invalide",
+ "You need to enter details of an existing account." : "Sunt necesare detaliile unui cont existent.",
"Oracle connection could not be established" : "Conexiunea Oracle nu a putut fi stabilită",
"Oracle username and/or password not valid" : "Numele de utilizator sau / și parola Oracle nu sunt valide",
"PostgreSQL username and/or password not valid" : "Nume utilizator și/sau parolă PostgreSQL greșită",
+ "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nu este suportat și %s nu va funcționa corect pe această platformă. Continuați pe propriul risc! ",
"For the best results, please consider using a GNU/Linux server instead." : "Pentru cele mai bune rezultate, ia în calcul folosirea unui server care rulează un sistem de operare 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." : "Se pare că această instanță a %s rulează o versiune 32-bit a PHP și open_basedir a fost configurat în php.ini. Aceasta va conduce la probleme cu fișierele mai mari de 4 GB și nu este deloc recomandată.",
+ "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Eliminați setarea open_basedir din php.ini utilizați versiunea 64-bit a PHP.",
"Set an admin username." : "Setează un nume de administrator.",
"Set an admin password." : "Setează o parolă de administrator.",
+ "Cannot create or write into the data directory %s" : "Nu se poate crea sau scrie în dosarul pentru date %s",
+ "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Backend-ul de partajare %s trebuie să implementeze interfața OCP\\Share_Backend",
+ "Sharing backend %s not found" : "Backend-ul de partajare %s nu există",
+ "Sharing backend for %s not found" : "Backend-ul de partajare pentru %s nu a fost găsit",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s a partajat »%2$s« cu tine și vrea să adauge:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s a partajat »%2$s« cu tine și vrea să adauge",
"»%s« added a note to a file shared with you" : "%s« a adaugat un comentariu la un fișier partajat cu tine",
"Open »%s«" : "Deschide »%s«",
"%1$s via %2$s" : "%1$sprin %2$s",
"You are not allowed to share %s" : "Nu există permisiunea de partajare %s",
+ "Cannot increase permissions of %s" : "Nu se pot extinde permisiunile pentru %s",
+ "Files cannot be shared with delete permissions" : "Fișierele nu pot fi partajate cu permisiunea de ștergere",
+ "Files cannot be shared with create permissions" : "Fișierele nu pot fi partajate cu permisiunea de creare",
"Expiration date is in the past" : "Data expirării este în trecut",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Data expirării nu poate fi mai mult de %n zi în viitor","Data expirării nu poate fi mai mult de %n zile în viitor","Data expirării nu poate fi mai mult de %n zile în viitor"],
+ "Sharing is only allowed with group members" : "Partajarea este permisă doar cu membrii grupului",
"Sharing %s failed, because this item is already shared with user %s" : "Partajarea %s a eșuat deoarece acest element este deja partajat cu utilizatorul %s",
+ "%1$s shared »%2$s« with you" : "%1$s a partajat »%2$s« cu tine",
"%1$s shared »%2$s« with you." : "%1$sa partajat »%2$s« cu tine.",
"Click the button below to open it." : "Apasă pe butonul de jos pentru a deschide.",
+ "The requested share does not exist anymore" : "Partajarea solicitată nu mai există",
+ "The requested share comes from a disabled user" : "Partajarea solicitată provine de la un utilizator dezactivat",
+ "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Utilizatorul nu a fost creat deoarece s-a atins limita acestui număr. Verificați notificările pentru a afla mai mult.",
"Could not find category \"%s\"" : "Cloud nu a gasit categoria \"%s\"",
"Sunday" : "Duminică",
"Monday" : "Luni",
@@ -120,19 +204,74 @@ OC.L10N.register(
"A valid password must be provided" : "Trebuie să furnizaţi o parolă validă",
"The username is already being used" : "Numele de utilizator este deja folosit",
"Could not create user" : "Nu s-a putut crea utilizatorul",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Doar următoarele caractere sunt permise în numele de utilizatori: \"a-z\", \"A-Z\", \"0-9\", spații și \"_.@-'\"",
"A valid username must be provided" : "Trebuie să furnizaţi un nume de utilizator valid",
"Username contains whitespace at the beginning or at the end" : "Utilizatorul contine spațiu liber la început sau la sfârșit",
"Username must not consist of dots only" : "Numele utilizatorului nu poate conține numai puncte",
+ "Username is invalid because files already exist for this user" : "Numele utilizatorului este invalid deoarece există deja fișiere pentru acesta",
"User disabled" : "Utilizator dezactivat",
+ "Login canceled by app" : "Autentificare anulată de aplicație",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplicația \"%1$s\" nu poate fi instalată deoarece următoarele dependențe nu sunt satisfăcute: %2$s",
+ "a safe home for all your data" : "o casă sigură pentru toate datele dumneavoastră",
+ "File is currently busy, please try again later" : "Fișierul este blocat momentan, încercați din nou mai târziu",
+ "Cannot download file" : "Fișierul nu se poate descărca",
"Application is not enabled" : "Aplicația nu este activată",
"Authentication error" : "Eroare la autentificare",
"Token expired. Please reload page." : "Token expirat. Te rugăm să reîncarci pagina.",
+ "No database drivers (sqlite, mysql, or postgresql) installed." : "Nu sunt instalate drivere pentru baze de date (sqlite, mysql sau postgresql).",
+ "Cannot write into \"config\" directory." : "Nu se poate scrie în directorul \"config\".",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Aceasta se poate remedia de obicei prin asigurarea accesului în scriere serverului la directorul configurației. Vedeți %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" : "Sau, dacă preferați să păstrați config.php doar în citire, setați opțiunea \"config_is_read_only\" ca true. Vedeți %s",
+ "Cannot write into \"apps\" directory." : "Nu se poate scrie în directorul \"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." : "Aceasta se poate remedia permițând serverului web să acceseze directorul aplicațiilor sau dezactivând App Store în fișierul de configurație.",
+ "Cannot create \"data\" directory." : "Nu se poate crea directorul \"data\".",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Aceasta se remediază de obicei permițând serverului web accesul în scriere în directorul rădăcină. Vedeți %s",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Permisiunile se rezolvă de obicei dând serverului web acces în scriere în directorul rădăcină. Vedeți %s.",
+ "Your data directory is not writable." : "Directorul data nu are acces în scriere.",
+ "Setting locale to %s failed." : "Setarea localizării la %s a eșuat.",
+ "Please install one of these locales on your system and restart your web server." : "Instalați una din aceste localizări pe sistem și reporniți serverul web.",
"PHP module %s not installed." : "Modulul PHP %s nu este instalat.",
+ "Please ask your server administrator to install the module." : "Solicitați administratorului să instaleze modulul.",
"PHP setting \"%s\" is not set to \"%s\"." : "Setarea PHP \"%s\" nu este setată la \"%s\".",
+ "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustând aceste setări în php.ini va permite ca Nextcloud să funcționeze din nou",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> este setată la <code>%s</code> în locul valorii așteptate <code>0</code>.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pentru a remedia problema setați <code>mbstring.func_overload</code> la <code>0</code> în php.ini.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP este aparent configurat pentru a elimina blocurile de documente inline. Acest lucru va face mai multe aplicații de bază inaccesibile.",
+ "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Aceasta se datorează probabil unui accelerator/cache precum Zend OPcache sau eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Modulele PHP au fost instalate, dar apar ca lipsind?",
+ "Please ask your server administrator to restart the web server." : "Solicitați administratorului să restarteze serverul web.",
+ "The required %s config variable is not configured in the config.php file." : "Variabila de configurație %s nu este prezentă în config.php.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "Solicitați administratorului să verifice configurația Nextcloud. ",
+ "Your data directory is readable by other users." : "Directorul de date este accesibil altor utilizatori.",
+ "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Setați permisiunea 0770 astfel ca directorul să nu poată fi parcurs de alți utilizatori.",
+ "Your data directory must be an absolute path." : "Directorul de date trebuie să fie o cale absolută.",
+ "Check the value of \"datadirectory\" in your configuration." : "Verificați valoarea \"datadirectory\" în configurație.",
+ "Your data directory is invalid." : "Directorul de date este invalid.",
+ "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asigurați-vă că fișierul \".ocdata\" există în rădăcina directorului de date.",
+ "Action \"%s\" not supported or implemented." : "Acțiunea \"%s\" nu este suportată sau implementată.",
+ "Authentication failed, wrong token or provider ID given" : "Autentificare eșuată, token greșit sau ID provider eronat",
+ "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametri lipsă pentru îndeplinirea solicitării. Parametrii lipsă: \"%s\"",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" deja folosit de providerul cloud federation \"%2$s\"",
+ "Cloud Federation Provider with ID: \"%s\" does not exist." : "Providerul Cloud Federation cu ID: \"%s\" nu există.",
+ "Could not obtain lock type %d on \"%s\"." : "Nu se poate aplica tipul %d de blocare pe \"%s\".",
+ "Storage unauthorized. %s" : "Spațiu de stocare neautorizat. %s",
+ "Storage incomplete configuration. %s" : "Configurație incompletă a spațiului de stocare. %s",
+ "Storage connection error. %s" : "Eroare conexiune cu spațiul de stocare. %s",
"Storage is temporarily not available" : "Spațiu de stocare este indisponibil temporar",
+ "Storage connection timeout. %s" : "Timeout la conexiunea cu spațiul de stocare. %s",
+ "Free prompt" : "Eliberează prompt-ul",
+ "Runs an arbitrary prompt through the language model." : "Rulează un prompt arbitrar prin modelul lingvistic.",
+ "Generate headline" : "Generează titlu",
+ "Generates a possible headline for a text." : "Generează un posibil titlu pentru text",
+ "Summarize" : "Rezumă",
+ "Summarizes text by reducing its length without losing key information." : "Rezumă textul prin reducerea lungimii acestuia, fără a pierde informațiile cheie.",
+ "Extract topics" : "Extrage subiecte",
+ "Extracts topics from a text and outputs them separated by commas." : "Extrage subiecte din text și le furnizează separate prin virgulă.",
+ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Fișierele aplicației %1$s nu au fost înlocuite corect. Asigurați-vă că versiunea este compatibilă cu cea a serverului. ",
"Full name" : "Nume complet",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Numai următoarele caractere sunt permise în numele utilizatorului: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\""
+ "libxml2 2.7.0 is at least required. Currently %s is installed." : "Este necesar libxml2 2.7.0, cel puțin. Acum este instalat %s.",
+ "To fix this issue update your libxml2 version and restart your web server." : "Pentru remediere actualizați versiunea libxml2 și reporniți serverul web.",
+ "PostgreSQL >= 9 required." : "Este necesar PostgreSQL >= 9 .",
+ "Please upgrade your database version." : "Faceți upgrade la versiunea bazei de date."
},
"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));");
diff --git a/lib/l10n/ro.json b/lib/l10n/ro.json
index 5793338e064..8aedd951148 100644
--- a/lib/l10n/ro.json
+++ b/lib/l10n/ro.json
@@ -1,74 +1,158 @@
{ "translations": {
"Cannot write into \"config\" directory!" : "Nu se poate scrie în folderul \"config\"!",
+ "This can usually be fixed by giving the web server write access to the config directory." : "Aceasta se poate rezolva de obicei dând acces în scriere serverului în directorul configurației.",
+ "But, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it." : "Dar, dacă preferați să păstrați config.php doar în citire, setați opțiunea \"config_is_read_only\" ca true.",
"See %s" : "Vezi %s",
+ "Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory." : "Aplicația %1$s nu este prezentă sau are o versiune incompatibilă cu acest server. Verificați directorul aplicațiilor.",
"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ă.",
+ "%s email verification" : "%s verificare email",
+ "Email verification" : "Verificare email",
+ "Click the following button to confirm your email." : "Apăsați butonul următor pentru confirmare email.",
+ "Click the following link to confirm your email." : "Click pe linkul următor pentru confirmare email.",
+ "Confirm your email" : "Confirmați emailul",
"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",
"%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s și %4$s",
"%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s și %5$s",
+ "Education Edition" : "Ediția pentru educație",
+ "Enterprise bundle" : "Pachetul enterprise",
+ "Groupware bundle" : "Pachetul Groupware",
+ "Hub bundle" : "Pachetul central",
+ "Social sharing bundle" : "Pachetul de partajare",
"PHP %s or higher is required." : "Versiunea PHP %s sau mai mare este necesară.",
"PHP with a version lower than %s is required." : "Este necesară o versiune PHP mai mică decât %s",
"%sbit or higher PHP required." : "Este necesar PHP %sbit sau mai mare.",
+ "The following architectures are supported: %s" : "Sunt suportate următoarele arhitecturi: %s",
+ "The following databases are supported: %s" : "Sunt suportate următoarele baze de date: %s",
"The command line tool %s could not be found" : "Unealta în linie de comandă %s nu a fost găsită",
"The library %s is not available." : "Biblioteca %s nu este disponibilă.",
+ "Library %1$s with a version higher than %2$s is required - available version %3$s." : "Biblioteca %1$s cu o versiune superioară %2$s este necesară - versiunea disponibilă %3$s.",
+ "Library %1$s with a version lower than %2$s is required - available version %3$s." : "Biblioteca %1$s cu o versiune inferioară %2$s este necesară - versiunea disponibilă %3$s.",
+ "The following platforms are supported: %s" : "Sunt suportate următoarele platforme: %s",
+ "Server version %s or higher is required." : "Este necesară versiunea %s a serverului sau superioară.",
+ "Server version %s or lower is required." : "Este necesară versiunea %s a serverului, sau inferioară.",
+ "Logged in user must be an admin, a sub admin or gotten special right to access this setting" : "Utilizatorul trebuie să fie administrator, sub-administrator sau să aibă permisiunea specială de a accesa această setare",
+ "Logged in user must be an admin or sub admin" : "Utilizatorul trebuie să fie administrator sau sub-administrator",
+ "Logged in user must be an admin" : "Utilizatorul trebuie să fie administrator",
+ "Wiping of device %s has started" : "A început curățarea dispozitivului %s ",
+ "Wiping of device »%s« has started" : "A început curățarea dispozitivului »%s« ",
+ "»%s« started remote wipe" : "»%s« a pornit curățarea la distanță",
+ "Device or application »%s« has started the remote wipe process. You will receive another email once the process has finished" : "Dispozitivul sau aplicația »%s« a inițiat procesul de ștergere la distanță. Veți primi un alt mail când procesul se va finaliza",
+ "Wiping of device %s has finished" : "Curățarea dispozitivului %s s-a finalizat",
+ "Wiping of device »%s« has finished" : "Curățarea dispozitivului »%s« s-a finalizat",
+ "»%s« finished remote wipe" : "»%s« a finalizat curățarea la distanță",
+ "Device or application »%s« has finished the remote wipe process." : "Dispozitivul sau aplicația »%s« a finalizat procesul de curățare la distanță.",
+ "Remote wipe started" : "Curățarea la distanță a fost inițiată",
+ "A remote wipe was started on device %s" : "A fost inițiată o curățare la distanță a dispozitivului %s",
+ "Remote wipe finished" : "Curățarea la distanță a fost finalizată",
+ "The remote wipe on %s has finished" : "Curățarea la distanță a %s a fost finalizată",
"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",
"today" : "astăzi",
"tomorrow" : "mâine",
"yesterday" : "ieri",
- "_%n day ago_::_%n days ago_" : ["Acum o zi","Acum %n zile","Acum %n zile"],
+ "_in %n day_::_in %n days_" : ["în %n zi","în %n zile","în %n zile"],
+ "_%n day ago_::_%n days ago_" : ["%n zi în rumă","Acum %n zile","Acum %n zile"],
"next month" : "luna viitoare",
"last month" : "ultima lună",
+ "_in %n month_::_in %n months_" : ["în %n lună","în %n luni","în %n luni"],
"_%n month ago_::_%n months ago_" : ["%n lună în urmă","%n luni în urmă","%n luni în urmă"],
"next year" : "anul viitor",
"last year" : "ultimul an",
+ "_in %n year_::_in %n years_" : ["în %n an","în %n ani","în %n ani"],
"_%n year ago_::_%n years ago_" : ["%n an în urmă","%n ani în urmâ","%n ani în urmâ"],
+ "_in %n hour_::_in %n hours_" : ["în %n oră","în %n ore","în %n ore"],
"_%n hour ago_::_%n hours ago_" : ["%n oră în urmă","%n ore în urmă","%n ore în urmă"],
+ "_in %n minute_::_in %n minutes_" : ["în %n minut","în %n minute","în %n minute"],
+ "_%n minute ago_::_%n minutes ago_" : ["%n minut în urmă","%n minute în urmă","%n minute în urmă"],
"in a few seconds" : "în câteva secunde",
"seconds ago" : "secunde în urmă",
+ "Empty file" : "Fișier gol",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Modulul cu ID: %s nu există. Activați-l din setările aplicației sau contactați administratorul.",
"File already exists" : "Fișierul există deja",
+ "Invalid path" : "Cale invalidă",
+ "Failed to create file from template" : "Eroare la crearea fișierului pe baza șablonului",
"Templates" : "Șabloane",
"File name is a reserved word" : "Numele fișierului este un cuvânt rezervat",
"File name contains at least one invalid character" : "Numele fișierului conține cel puțin un caracter invalid",
"File name is too long" : "Numele fișierului este prea lung",
"Dot files are not allowed" : "Fișierele care încep cu caracterul punct nu sunt permise",
"Empty filename is not allowed" : "Nu este permis fișier fără nume",
+ "App \"%s\" cannot be installed because appinfo file cannot be read." : "Aplicația \"%s\" nu poate fi instalată deoarece fișierul appinfo nu poate fi citit.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Aplicația \"%s\" nu poate fi instalată deoarece nu este compatibilă cu versiunea serverului.",
"__language_name__" : "Română",
+ "This is an automatically sent email, please do not reply." : "Acesta este un email automat, nu răspundeți.",
"Help" : "Ajutor",
+ "Appearance and accessibility" : "Aspect și accesibilitate",
"Apps" : "Aplicații",
+ "Personal settings" : "Setări personale",
+ "Administration settings" : "Setări de administrare",
"Settings" : "Setări",
"Log out" : "Ieșire",
"Users" : "Utilizatori",
"Email" : "E-mail",
+ "View %s on the fediverse" : "Vedeți %s pe fediverse",
"Phone" : "Telefon",
+ "Call %s" : "Apel %s",
"Twitter" : "Twitter",
+ "View %s on Twitter" : "Vedeți %s pe X",
"Website" : "Site web",
+ "Visit %s" : "Vizitați %s",
"Address" : "Adresă",
"Profile picture" : "Imagine de profil",
"About" : "Despre",
+ "Display name" : "Nume afișat",
+ "Headline" : "Titlu",
+ "Organisation" : "Organizație",
"Role" : "Rol",
"Unknown user" : "Utilizator necunoscut",
"Additional settings" : "Setări adiționale",
+ "Enter the database username and name for %s" : "Introduceți utilizatorul pentru baza de date și numele pentru %s",
+ "Enter the database username for %s" : "Introduceți utilizatorul pentru %s",
+ "Enter the database name for %s" : "Introduceți numele bazei de date pentru %s",
+ "You cannot use dots in the database name %s" : "Nu puteți folosi puncte în numele bazei de date %s",
+ "MySQL username and/or password not valid" : "Utilizatorul sau/și parola MySQL invalide",
+ "You need to enter details of an existing account." : "Sunt necesare detaliile unui cont existent.",
"Oracle connection could not be established" : "Conexiunea Oracle nu a putut fi stabilită",
"Oracle username and/or password not valid" : "Numele de utilizator sau / și parola Oracle nu sunt valide",
"PostgreSQL username and/or password not valid" : "Nume utilizator și/sau parolă PostgreSQL greșită",
+ "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X nu este suportat și %s nu va funcționa corect pe această platformă. Continuați pe propriul risc! ",
"For the best results, please consider using a GNU/Linux server instead." : "Pentru cele mai bune rezultate, ia în calcul folosirea unui server care rulează un sistem de operare 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." : "Se pare că această instanță a %s rulează o versiune 32-bit a PHP și open_basedir a fost configurat în php.ini. Aceasta va conduce la probleme cu fișierele mai mari de 4 GB și nu este deloc recomandată.",
+ "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Eliminați setarea open_basedir din php.ini utilizați versiunea 64-bit a PHP.",
"Set an admin username." : "Setează un nume de administrator.",
"Set an admin password." : "Setează o parolă de administrator.",
+ "Cannot create or write into the data directory %s" : "Nu se poate crea sau scrie în dosarul pentru date %s",
+ "Sharing backend %s must implement the interface OCP\\Share_Backend" : "Backend-ul de partajare %s trebuie să implementeze interfața OCP\\Share_Backend",
+ "Sharing backend %s not found" : "Backend-ul de partajare %s nu există",
+ "Sharing backend for %s not found" : "Backend-ul de partajare pentru %s nu a fost găsit",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s a partajat »%2$s« cu tine și vrea să adauge:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s a partajat »%2$s« cu tine și vrea să adauge",
"»%s« added a note to a file shared with you" : "%s« a adaugat un comentariu la un fișier partajat cu tine",
"Open »%s«" : "Deschide »%s«",
"%1$s via %2$s" : "%1$sprin %2$s",
"You are not allowed to share %s" : "Nu există permisiunea de partajare %s",
+ "Cannot increase permissions of %s" : "Nu se pot extinde permisiunile pentru %s",
+ "Files cannot be shared with delete permissions" : "Fișierele nu pot fi partajate cu permisiunea de ștergere",
+ "Files cannot be shared with create permissions" : "Fișierele nu pot fi partajate cu permisiunea de creare",
"Expiration date is in the past" : "Data expirării este în trecut",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Data expirării nu poate fi mai mult de %n zi în viitor","Data expirării nu poate fi mai mult de %n zile în viitor","Data expirării nu poate fi mai mult de %n zile în viitor"],
+ "Sharing is only allowed with group members" : "Partajarea este permisă doar cu membrii grupului",
"Sharing %s failed, because this item is already shared with user %s" : "Partajarea %s a eșuat deoarece acest element este deja partajat cu utilizatorul %s",
+ "%1$s shared »%2$s« with you" : "%1$s a partajat »%2$s« cu tine",
"%1$s shared »%2$s« with you." : "%1$sa partajat »%2$s« cu tine.",
"Click the button below to open it." : "Apasă pe butonul de jos pentru a deschide.",
+ "The requested share does not exist anymore" : "Partajarea solicitată nu mai există",
+ "The requested share comes from a disabled user" : "Partajarea solicitată provine de la un utilizator dezactivat",
+ "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Utilizatorul nu a fost creat deoarece s-a atins limita acestui număr. Verificați notificările pentru a afla mai mult.",
"Could not find category \"%s\"" : "Cloud nu a gasit categoria \"%s\"",
"Sunday" : "Duminică",
"Monday" : "Luni",
@@ -118,19 +202,74 @@
"A valid password must be provided" : "Trebuie să furnizaţi o parolă validă",
"The username is already being used" : "Numele de utilizator este deja folosit",
"Could not create user" : "Nu s-a putut crea utilizatorul",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Doar următoarele caractere sunt permise în numele de utilizatori: \"a-z\", \"A-Z\", \"0-9\", spații și \"_.@-'\"",
"A valid username must be provided" : "Trebuie să furnizaţi un nume de utilizator valid",
"Username contains whitespace at the beginning or at the end" : "Utilizatorul contine spațiu liber la început sau la sfârșit",
"Username must not consist of dots only" : "Numele utilizatorului nu poate conține numai puncte",
+ "Username is invalid because files already exist for this user" : "Numele utilizatorului este invalid deoarece există deja fișiere pentru acesta",
"User disabled" : "Utilizator dezactivat",
+ "Login canceled by app" : "Autentificare anulată de aplicație",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Aplicația \"%1$s\" nu poate fi instalată deoarece următoarele dependențe nu sunt satisfăcute: %2$s",
+ "a safe home for all your data" : "o casă sigură pentru toate datele dumneavoastră",
+ "File is currently busy, please try again later" : "Fișierul este blocat momentan, încercați din nou mai târziu",
+ "Cannot download file" : "Fișierul nu se poate descărca",
"Application is not enabled" : "Aplicația nu este activată",
"Authentication error" : "Eroare la autentificare",
"Token expired. Please reload page." : "Token expirat. Te rugăm să reîncarci pagina.",
+ "No database drivers (sqlite, mysql, or postgresql) installed." : "Nu sunt instalate drivere pentru baze de date (sqlite, mysql sau postgresql).",
+ "Cannot write into \"config\" directory." : "Nu se poate scrie în directorul \"config\".",
+ "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Aceasta se poate remedia de obicei prin asigurarea accesului în scriere serverului la directorul configurației. Vedeți %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" : "Sau, dacă preferați să păstrați config.php doar în citire, setați opțiunea \"config_is_read_only\" ca true. Vedeți %s",
+ "Cannot write into \"apps\" directory." : "Nu se poate scrie în directorul \"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." : "Aceasta se poate remedia permițând serverului web să acceseze directorul aplicațiilor sau dezactivând App Store în fișierul de configurație.",
+ "Cannot create \"data\" directory." : "Nu se poate crea directorul \"data\".",
+ "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Aceasta se remediază de obicei permițând serverului web accesul în scriere în directorul rădăcină. Vedeți %s",
+ "Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Permisiunile se rezolvă de obicei dând serverului web acces în scriere în directorul rădăcină. Vedeți %s.",
+ "Your data directory is not writable." : "Directorul data nu are acces în scriere.",
+ "Setting locale to %s failed." : "Setarea localizării la %s a eșuat.",
+ "Please install one of these locales on your system and restart your web server." : "Instalați una din aceste localizări pe sistem și reporniți serverul web.",
"PHP module %s not installed." : "Modulul PHP %s nu este instalat.",
+ "Please ask your server administrator to install the module." : "Solicitați administratorului să instaleze modulul.",
"PHP setting \"%s\" is not set to \"%s\"." : "Setarea PHP \"%s\" nu este setată la \"%s\".",
+ "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajustând aceste setări în php.ini va permite ca Nextcloud să funcționeze din nou",
+ "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> este setată la <code>%s</code> în locul valorii așteptate <code>0</code>.",
+ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pentru a remedia problema setați <code>mbstring.func_overload</code> la <code>0</code> în php.ini.",
"PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP este aparent configurat pentru a elimina blocurile de documente inline. Acest lucru va face mai multe aplicații de bază inaccesibile.",
+ "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Aceasta se datorează probabil unui accelerator/cache precum Zend OPcache sau eAccelerator.",
"PHP modules have been installed, but they are still listed as missing?" : "Modulele PHP au fost instalate, dar apar ca lipsind?",
+ "Please ask your server administrator to restart the web server." : "Solicitați administratorului să restarteze serverul web.",
+ "The required %s config variable is not configured in the config.php file." : "Variabila de configurație %s nu este prezentă în config.php.",
+ "Please ask your server administrator to check the Nextcloud configuration." : "Solicitați administratorului să verifice configurația Nextcloud. ",
+ "Your data directory is readable by other users." : "Directorul de date este accesibil altor utilizatori.",
+ "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Setați permisiunea 0770 astfel ca directorul să nu poată fi parcurs de alți utilizatori.",
+ "Your data directory must be an absolute path." : "Directorul de date trebuie să fie o cale absolută.",
+ "Check the value of \"datadirectory\" in your configuration." : "Verificați valoarea \"datadirectory\" în configurație.",
+ "Your data directory is invalid." : "Directorul de date este invalid.",
+ "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Asigurați-vă că fișierul \".ocdata\" există în rădăcina directorului de date.",
+ "Action \"%s\" not supported or implemented." : "Acțiunea \"%s\" nu este suportată sau implementată.",
+ "Authentication failed, wrong token or provider ID given" : "Autentificare eșuată, token greșit sau ID provider eronat",
+ "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Parametri lipsă pentru îndeplinirea solicitării. Parametrii lipsă: \"%s\"",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "ID \"%1$s\" deja folosit de providerul cloud federation \"%2$s\"",
+ "Cloud Federation Provider with ID: \"%s\" does not exist." : "Providerul Cloud Federation cu ID: \"%s\" nu există.",
+ "Could not obtain lock type %d on \"%s\"." : "Nu se poate aplica tipul %d de blocare pe \"%s\".",
+ "Storage unauthorized. %s" : "Spațiu de stocare neautorizat. %s",
+ "Storage incomplete configuration. %s" : "Configurație incompletă a spațiului de stocare. %s",
+ "Storage connection error. %s" : "Eroare conexiune cu spațiul de stocare. %s",
"Storage is temporarily not available" : "Spațiu de stocare este indisponibil temporar",
+ "Storage connection timeout. %s" : "Timeout la conexiunea cu spațiul de stocare. %s",
+ "Free prompt" : "Eliberează prompt-ul",
+ "Runs an arbitrary prompt through the language model." : "Rulează un prompt arbitrar prin modelul lingvistic.",
+ "Generate headline" : "Generează titlu",
+ "Generates a possible headline for a text." : "Generează un posibil titlu pentru text",
+ "Summarize" : "Rezumă",
+ "Summarizes text by reducing its length without losing key information." : "Rezumă textul prin reducerea lungimii acestuia, fără a pierde informațiile cheie.",
+ "Extract topics" : "Extrage subiecte",
+ "Extracts topics from a text and outputs them separated by commas." : "Extrage subiecte din text și le furnizează separate prin virgulă.",
+ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Fișierele aplicației %1$s nu au fost înlocuite corect. Asigurați-vă că versiunea este compatibilă cu cea a serverului. ",
"Full name" : "Nume complet",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Numai următoarele caractere sunt permise în numele utilizatorului: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\""
+ "libxml2 2.7.0 is at least required. Currently %s is installed." : "Este necesar libxml2 2.7.0, cel puțin. Acum este instalat %s.",
+ "To fix this issue update your libxml2 version and restart your web server." : "Pentru remediere actualizați versiunea libxml2 și reporniți serverul web.",
+ "PostgreSQL >= 9 required." : "Este necesar PostgreSQL >= 9 .",
+ "Please upgrade your database version." : "Faceți upgrade la versiunea bazei de date."
},"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"
} \ No newline at end of file
diff --git a/lib/l10n/ru.js b/lib/l10n/ru.js
index a2dad125c09..911774d5abb 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" : "Выйти",
@@ -271,9 +270,8 @@ OC.L10N.register(
"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 не были заменены корректно. Удостоверьтесь, что устанавливаемая версия этого приложения совместима с версией сервера.",
+ "404" : "404",
"Full name" : "Полное имя",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Пользователь не был создан, достигнуто ограничение количества пользователей. Для получения дополнительных сведений проверьте уведомления.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "В составе имени пользователя допускаются следующие символы: «a–z», «A–Z», «0–9» и «_.@-'»",
"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 или более новый.",
diff --git a/lib/l10n/ru.json b/lib/l10n/ru.json
index 3c15ec3a58d..96676b22331 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" : "Выйти",
@@ -269,9 +268,8 @@
"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 не были заменены корректно. Удостоверьтесь, что устанавливаемая версия этого приложения совместима с версией сервера.",
+ "404" : "404",
"Full name" : "Полное имя",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Пользователь не был создан, достигнуто ограничение количества пользователей. Для получения дополнительных сведений проверьте уведомления.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "В составе имени пользователя допускаются следующие символы: «a–z», «A–Z», «0–9» и «_.@-'»",
"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 или более новый.",
diff --git a/lib/l10n/sc.js b/lib/l10n/sc.js
index 68f638c9bd8..e4fa1ebda45 100644
--- a/lib/l10n/sc.js
+++ b/lib/l10n/sc.js
@@ -6,6 +6,7 @@ OC.L10N.register(
"See %s" : "Càstia %s",
"Sample configuration detected" : "S'at agatadu una cunfiguratzione de esèmpiu",
"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" : "Est istadu iscobertu ca sa cunfiguratzione de esèmpiu est istada copiada. Custu podet blocare s'installatzione e no est suportadu. Leghe sa documentatzione in config.php antis de fàghere càmbios.",
+ "The page could not be found on the server." : "Impossìbile agatare custa pàgina in su serbidore.",
"Other activities" : "Àteras atividades",
"%1$s and %2$s" : "%1$s e %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s",
@@ -216,7 +217,6 @@ OC.L10N.register(
"Storage connection timeout. %s" : "Tempus de connessione a s'archiviatzione iscadidu. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Is archìvios de s'aplicatzione %1$s no sunt istados cambiados in manera curreta. Assegura•ti chi b'apat una versione cumpatìbile cun su serbidore.",
"Full name" : "Nùmene cumpletu",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Isceti custos caràteres sunt permìtidos in unu nùmene utente: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Est rechèdida a su mancu sa versione libxml2 2.7.0. Pro immoe cussa installada est sa%s.",
"To fix this issue update your libxml2 version and restart your web server." : "Pro assentare custu problema, agiorna sa versione libxml2 e torra a aviare su serbidore ìnternet."
},
diff --git a/lib/l10n/sc.json b/lib/l10n/sc.json
index 87e608c2ea9..e70d274c472 100644
--- a/lib/l10n/sc.json
+++ b/lib/l10n/sc.json
@@ -4,6 +4,7 @@
"See %s" : "Càstia %s",
"Sample configuration detected" : "S'at agatadu una cunfiguratzione de esèmpiu",
"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" : "Est istadu iscobertu ca sa cunfiguratzione de esèmpiu est istada copiada. Custu podet blocare s'installatzione e no est suportadu. Leghe sa documentatzione in config.php antis de fàghere càmbios.",
+ "The page could not be found on the server." : "Impossìbile agatare custa pàgina in su serbidore.",
"Other activities" : "Àteras atividades",
"%1$s and %2$s" : "%1$s e %2$s",
"%1$s, %2$s and %3$s" : "%1$s, %2$s e %3$s",
@@ -214,7 +215,6 @@
"Storage connection timeout. %s" : "Tempus de connessione a s'archiviatzione iscadidu. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Is archìvios de s'aplicatzione %1$s no sunt istados cambiados in manera curreta. Assegura•ti chi b'apat una versione cumpatìbile cun su serbidore.",
"Full name" : "Nùmene cumpletu",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Isceti custos caràteres sunt permìtidos in unu nùmene utente: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Est rechèdida a su mancu sa versione libxml2 2.7.0. Pro immoe cussa installada est sa%s.",
"To fix this issue update your libxml2 version and restart your web server." : "Pro assentare custu problema, agiorna sa versione libxml2 e torra a aviare su serbidore ìnternet."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
diff --git a/lib/l10n/sk.js b/lib/l10n/sk.js
index dd09293354e..e5c440a33c7 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",
@@ -259,9 +259,8 @@ OC.L10N.register(
"Storage is temporarily not available" : "Úložisko je dočasne nedostupné",
"Storage connection timeout. %s" : "Vypršanie pripojenia k úložisku. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Súbory aplikácie %1$s neboli správne nahradené. Uistite sa, že to je verzia kompatibilná so serverom.",
+ "404" : "404",
"Full name" : "Celé meno",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Bol dosiahnutý limit používateľov a používateľ nebol vytvorený. Pozrite sa do upozornení pre viac informácií.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V mene používateľa je možné použiť iba nasledovné znaky: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/sk.json b/lib/l10n/sk.json
index 67e07efd340..d16b77d6949 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",
@@ -257,9 +257,8 @@
"Storage is temporarily not available" : "Úložisko je dočasne nedostupné",
"Storage connection timeout. %s" : "Vypršanie pripojenia k úložisku. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Súbory aplikácie %1$s neboli správne nahradené. Uistite sa, že to je verzia kompatibilná so serverom.",
+ "404" : "404",
"Full name" : "Celé meno",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Bol dosiahnutý limit používateľov a používateľ nebol vytvorený. Pozrite sa do upozornení pre viac informácií.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "V mene používateľa je možné použiť iba nasledovné znaky: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/sl.js b/lib/l10n/sl.js
index 7de5d7dad30..68da66e2ccf 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.",
@@ -255,8 +254,8 @@ OC.L10N.register(
"Storage is temporarily not available" : "Shramba trenutno ni na voljo",
"Storage connection timeout. %s" : "Povezava do shrambe je časovno potekla. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke programa %1$s niso bile zamenjane na pravi način. Prepričajte se, da je na strežniku nameščena podprta različica.",
+ "404" : "404",
"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."
},
diff --git a/lib/l10n/sl.json b/lib/l10n/sl.json
index 0a2a0b69bea..944f3f8c5f8 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.",
@@ -253,8 +252,8 @@
"Storage is temporarily not available" : "Shramba trenutno ni na voljo",
"Storage connection timeout. %s" : "Povezava do shrambe je časovno potekla. %s",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Datoteke programa %1$s niso bile zamenjane na pravi način. Prepričajte se, da je na strežniku nameščena podprta različica.",
+ "404" : "404",
"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."
},"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"
diff --git a/lib/l10n/sq.js b/lib/l10n/sq.js
index 67c7f8148db..2f44f014af9 100644
--- a/lib/l10n/sq.js
+++ b/lib/l10n/sq.js
@@ -156,7 +156,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "Hapsira ruajtëse nuk është në dispozicion përkohësisht",
"Storage connection timeout. %s" : "Mbarim kohe lidhjeje për depozitën. %s",
"Full name" : "Emri i plotë",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Në një emër përdoruesi lejohen vetëm shenjat vijuese: \"a-z\", \"A-Z\", \"0-9\", dhe \"_.@-\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Lypset të paktën libxml2 2.7.0. Hëpërhë e instaluar është %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Për të ndrequr këtë problem, përditësoni libxml2 dhe rinisni shërbyesin tuaj web."
},
diff --git a/lib/l10n/sq.json b/lib/l10n/sq.json
index 98ea9064e36..6902da2ee3f 100644
--- a/lib/l10n/sq.json
+++ b/lib/l10n/sq.json
@@ -154,7 +154,6 @@
"Storage is temporarily not available" : "Hapsira ruajtëse nuk është në dispozicion përkohësisht",
"Storage connection timeout. %s" : "Mbarim kohe lidhjeje për depozitën. %s",
"Full name" : "Emri i plotë",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Në një emër përdoruesi lejohen vetëm shenjat vijuese: \"a-z\", \"A-Z\", \"0-9\", dhe \"_.@-\"",
"libxml2 2.7.0 is at least required. Currently %s is installed." : "Lypset të paktën libxml2 2.7.0. Hëpërhë e instaluar është %s.",
"To fix this issue update your libxml2 version and restart your web server." : "Për të ndrequr këtë problem, përditësoni libxml2 dhe rinisni shërbyesin tuaj web."
},"pluralForm" :"nplurals=2; plural=(n != 1);"
diff --git a/lib/l10n/sr.js b/lib/l10n/sr.js
index 047cd8d4083..412c105f73b 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" : "Потврђивање и-мејла",
@@ -262,8 +261,8 @@ 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." : "Извршава произвољни одзив кроз језички модел.",
+ "Free prompt" : "Произвољни захтев",
+ "Runs an arbitrary prompt through the language model." : "Извршава произвољни захтев кроз језички модел.",
"Generate headline" : "Генериши линију наслова",
"Generates a possible headline for a text." : "Генерише могућу насловну линију текста.",
"Summarize" : "Резимирај",
@@ -271,9 +270,8 @@ OC.L10N.register(
"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“ нису правилно замењени. Проверите да ли је верзија компатибилна са сервером.",
+ "404" : "404",
"Full name" : "Пуно име",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Достигнуто је ограничење броја корисника и корисник није креиран. За више детаља погледајте своја обавештења.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "У корисничком имену су дозвољени само следећи карактери: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/sr.json b/lib/l10n/sr.json
index f4da5fe632e..0f11702d9c8 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" : "Потврђивање и-мејла",
@@ -260,8 +259,8 @@
"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." : "Извршава произвољни одзив кроз језички модел.",
+ "Free prompt" : "Произвољни захтев",
+ "Runs an arbitrary prompt through the language model." : "Извршава произвољни захтев кроз језички модел.",
"Generate headline" : "Генериши линију наслова",
"Generates a possible headline for a text." : "Генерише могућу насловну линију текста.",
"Summarize" : "Резимирај",
@@ -269,9 +268,8 @@
"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“ нису правилно замењени. Проверите да ли је верзија компатибилна са сервером.",
+ "404" : "404",
"Full name" : "Пуно име",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Достигнуто је ограничење броја корисника и корисник није креиран. За више детаља погледајте своја обавештења.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "У корисничком имену су дозвољени само следећи карактери: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/sv.js b/lib/l10n/sv.js
index de470185b61..a4cd1a0dbe8 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",
@@ -269,9 +268,8 @@ OC.L10N.register(
"Extract topics" : "Extrahera ämnen",
"Extracts topics from a text and outputs them separated by commas." : "Extraherar ämnen från en text och matar ut dem separerade med kommatecken.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerna i appen %1$s ersattes inte korrekt. Kontrollera att det är en version som är kompatibel med servern.",
+ "404" : "404",
"Full name" : "Fullständigt namn",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Användargränsen har nåtts och användaren skapades inte. Kontrollera dina aviseringar om du vill veta mer.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Endast följande tecken är tillåtna i användarnamnet: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/sv.json b/lib/l10n/sv.json
index aea308ffdc2..a1f0a007e63 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",
@@ -267,9 +266,8 @@
"Extract topics" : "Extrahera ämnen",
"Extracts topics from a text and outputs them separated by commas." : "Extraherar ämnen från en text och matar ut dem separerade med kommatecken.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerna i appen %1$s ersattes inte korrekt. Kontrollera att det är en version som är kompatibel med servern.",
+ "404" : "404",
"Full name" : "Fullständigt namn",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Användargränsen har nåtts och användaren skapades inte. Kontrollera dina aviseringar om du vill veta mer.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Endast följande tecken är tillåtna i användarnamnet: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"",
"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.",
diff --git a/lib/l10n/th.js b/lib/l10n/th.js
index 0fea7cc572d..218b8654656 100644
--- a/lib/l10n/th.js
+++ b/lib/l10n/th.js
@@ -166,7 +166,6 @@ OC.L10N.register(
"Storage is temporarily not available" : "พื้นที่จัดเก็บข้อมูลไม่สามารถใช้งานได้ชั่วคราว",
"Storage connection timeout. %s" : "หมดเวลาการเชื่อมต่อพื้นที่จัดเก็บข้อมูล %s",
"Full name" : "ชื่อเต็ม",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ชื่อผู้ใช้จะใช้ได้แค่อักษรดังต่อไปนี้: \"a-z\", \"A-Z\", \"0-9\" และ \"_.@-'\"",
"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 และรีสตาร์ทเว็บเซิร์ฟเวอร์ของคุณ"
},
diff --git a/lib/l10n/th.json b/lib/l10n/th.json
index 34be3490322..71e2ed31bfb 100644
--- a/lib/l10n/th.json
+++ b/lib/l10n/th.json
@@ -164,7 +164,6 @@
"Storage is temporarily not available" : "พื้นที่จัดเก็บข้อมูลไม่สามารถใช้งานได้ชั่วคราว",
"Storage connection timeout. %s" : "หมดเวลาการเชื่อมต่อพื้นที่จัดเก็บข้อมูล %s",
"Full name" : "ชื่อเต็ม",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "ชื่อผู้ใช้จะใช้ได้แค่อักษรดังต่อไปนี้: \"a-z\", \"A-Z\", \"0-9\" และ \"_.@-'\"",
"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 และรีสตาร์ทเว็บเซิร์ฟเวอร์ของคุณ"
},"pluralForm" :"nplurals=1; plural=0;"
diff --git a/lib/l10n/tr.js b/lib/l10n/tr.js
index df403564ca0..8c2d550d78b 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"],
@@ -79,7 +78,7 @@ OC.L10N.register(
"in a few seconds" : "bir kaç saniye içinde",
"seconds ago" : "saniyeler önce",
"Empty file" : "Dosya boş",
- "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "%s kodlu modül bulunamadı. Lütfen uygulamalarınız içinden modülü etkinleştirin ya da BT yöneticinizle görüşün.",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "%s kimlikli modül bulunamadı. Lütfen uygulamalarınız içinden modülü etkinleştirin ya da BT yöneticinizle görüşün.",
"File already exists" : "Dosya zaten var",
"Invalid path" : "Yol geçersiz",
"Failed to create file from template" : "Kalıptan dosya oluşturulamadı",
@@ -148,8 +147,8 @@ OC.L10N.register(
"Cannot increase permissions of %s" : "%s izinleri yükseltilemedi",
"Files cannot be shared with delete permissions" : "Silme izni ile dosya paylaşılamaz",
"Files cannot be shared with create permissions" : "Ekleme izni ile dosya paylaşılamaz",
- "Expiration date is in the past" : "Son kullanma tarihi geçmişte",
- "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Paylaşımların son kullanım süreleri, gelecekte %n günden fazla olamaz","Paylaşımların son kullanım süreleri, gelecekte %n günden fazla olamaz"],
+ "Expiration date is in the past" : "Geçerlilik sonu tarihi geçmişte",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Paylaşımların geçerlilik süreleri, gelecekte %n günden fazla olamaz","Paylaşımların geçerlilik süreleri, gelecekte %n günden fazla olamaz"],
"Sharing is only allowed with group members" : "Paylaşım yalnızca grup üyeleri ile yapılabilir",
"Sharing %s failed, because this item is already shared with user %s" : "%s paylaşılamadı. Bu öge zaten %s kullanıcısı ile paylaşılmış",
"%1$s shared »%2$s« with you" : "%1$s, sizinle »%2$s« ögesini paylaştı",
@@ -252,10 +251,10 @@ OC.L10N.register(
"Your data directory is invalid." : "Veri klasörünüz geçersiz.",
"Ensure there is a file called \".ocdata\" in the root of the data directory." : "Veri klasörü kökünde \".ocdata\" adında bir dosya bulunduğundan emin olun.",
"Action \"%s\" not supported or implemented." : "\"%s\" işlemi desteklenmiyor ya da henüz kullanılamıyor.",
- "Authentication failed, wrong token or provider ID given" : "Kimlik doğrulanamadı. Belirtilen kod ya da hizmet sağlayıcı kodu hatalı",
+ "Authentication failed, wrong token or provider ID given" : "Kimlik doğrulanamadı. Belirtilen kod ya da hizmet sağlayıcı kimliği hatalı",
"Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "İsteğin tamamlanması için gerekli parametreler eksik: \"%s\"",
- "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "\"%1$s\" kodu zaten \"%2$s\" birleşik hizmet sağlayıcısı tarafından kullanılıyor",
- "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" kodlu birleşik bulut hizmeti sağlayıcısı bulunamadı.",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "\"%1$s\" kimliği zaten \"%2$s\" birleşik hizmet sağlayıcısı tarafından kullanılıyor",
+ "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" kimlikli birleşik bulut hizmeti sağlayıcısı bulunamadı.",
"Could not obtain lock type %d on \"%s\"." : "\"%s\" için %d kilit türü alınamadı.",
"Storage unauthorized. %s" : "Depolamaya erişim izni yok. %s",
"Storage incomplete configuration. %s" : "Depolama yapılandırması tamamlanmamış. %s",
@@ -271,9 +270,8 @@ OC.L10N.register(
"Extract topics" : "Başlıklar ayıklansın",
"Extracts topics from a text and outputs them separated by commas." : "Bir metindeki konuları ayıklar ve bunları virgül ile ayırarak sıralar.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s uygulamasının dosyaları doğru şekilde değiştirilmedi. Sunucu ile uyumlu dosyaların yüklü olduğundan emin olun.",
+ "404" : "404",
"Full name" : "Tam ad",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Kullanıcı sayısı sınırına ulaşıldığından kullanıcı eklenemedi. Ayrıntılı bilgi almak için bildirimlerinize bakın.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kullanıcı adında yalnızca şu karakterler kullanılabilir: \"a-z\", \"A-Z\", \"0-9\", ve \"_.@-'\"",
"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.",
diff --git a/lib/l10n/tr.json b/lib/l10n/tr.json
index 5db37fb770c..7d744fd1417 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"],
@@ -77,7 +76,7 @@
"in a few seconds" : "bir kaç saniye içinde",
"seconds ago" : "saniyeler önce",
"Empty file" : "Dosya boş",
- "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "%s kodlu modül bulunamadı. Lütfen uygulamalarınız içinden modülü etkinleştirin ya da BT yöneticinizle görüşün.",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "%s kimlikli modül bulunamadı. Lütfen uygulamalarınız içinden modülü etkinleştirin ya da BT yöneticinizle görüşün.",
"File already exists" : "Dosya zaten var",
"Invalid path" : "Yol geçersiz",
"Failed to create file from template" : "Kalıptan dosya oluşturulamadı",
@@ -146,8 +145,8 @@
"Cannot increase permissions of %s" : "%s izinleri yükseltilemedi",
"Files cannot be shared with delete permissions" : "Silme izni ile dosya paylaşılamaz",
"Files cannot be shared with create permissions" : "Ekleme izni ile dosya paylaşılamaz",
- "Expiration date is in the past" : "Son kullanma tarihi geçmişte",
- "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Paylaşımların son kullanım süreleri, gelecekte %n günden fazla olamaz","Paylaşımların son kullanım süreleri, gelecekte %n günden fazla olamaz"],
+ "Expiration date is in the past" : "Geçerlilik sonu tarihi geçmişte",
+ "_Cannot set expiration date more than %n day in the future_::_Cannot set expiration date more than %n days in the future_" : ["Paylaşımların geçerlilik süreleri, gelecekte %n günden fazla olamaz","Paylaşımların geçerlilik süreleri, gelecekte %n günden fazla olamaz"],
"Sharing is only allowed with group members" : "Paylaşım yalnızca grup üyeleri ile yapılabilir",
"Sharing %s failed, because this item is already shared with user %s" : "%s paylaşılamadı. Bu öge zaten %s kullanıcısı ile paylaşılmış",
"%1$s shared »%2$s« with you" : "%1$s, sizinle »%2$s« ögesini paylaştı",
@@ -250,10 +249,10 @@
"Your data directory is invalid." : "Veri klasörünüz geçersiz.",
"Ensure there is a file called \".ocdata\" in the root of the data directory." : "Veri klasörü kökünde \".ocdata\" adında bir dosya bulunduğundan emin olun.",
"Action \"%s\" not supported or implemented." : "\"%s\" işlemi desteklenmiyor ya da henüz kullanılamıyor.",
- "Authentication failed, wrong token or provider ID given" : "Kimlik doğrulanamadı. Belirtilen kod ya da hizmet sağlayıcı kodu hatalı",
+ "Authentication failed, wrong token or provider ID given" : "Kimlik doğrulanamadı. Belirtilen kod ya da hizmet sağlayıcı kimliği hatalı",
"Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "İsteğin tamamlanması için gerekli parametreler eksik: \"%s\"",
- "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "\"%1$s\" kodu zaten \"%2$s\" birleşik hizmet sağlayıcısı tarafından kullanılıyor",
- "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" kodlu birleşik bulut hizmeti sağlayıcısı bulunamadı.",
+ "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "\"%1$s\" kimliği zaten \"%2$s\" birleşik hizmet sağlayıcısı tarafından kullanılıyor",
+ "Cloud Federation Provider with ID: \"%s\" does not exist." : "\"%s\" kimlikli birleşik bulut hizmeti sağlayıcısı bulunamadı.",
"Could not obtain lock type %d on \"%s\"." : "\"%s\" için %d kilit türü alınamadı.",
"Storage unauthorized. %s" : "Depolamaya erişim izni yok. %s",
"Storage incomplete configuration. %s" : "Depolama yapılandırması tamamlanmamış. %s",
@@ -269,9 +268,8 @@
"Extract topics" : "Başlıklar ayıklansın",
"Extracts topics from a text and outputs them separated by commas." : "Bir metindeki konuları ayıklar ve bunları virgül ile ayırarak sıralar.",
"The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "%1$s uygulamasının dosyaları doğru şekilde değiştirilmedi. Sunucu ile uyumlu dosyaların yüklü olduğundan emin olun.",
+ "404" : "404",
"Full name" : "Tam ad",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Kullanıcı sayısı sınırına ulaşıldığından kullanıcı eklenemedi. Ayrıntılı bilgi almak için bildirimlerinize bakın.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Kullanıcı adında yalnızca şu karakterler kullanılabilir: \"a-z\", \"A-Z\", \"0-9\", ve \"_.@-'\"",
"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.",
diff --git a/lib/l10n/uk.js b/lib/l10n/uk.js
index 59b14f26d15..b77910e36a4 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" : "Підтвердження електронної пошти",
@@ -79,7 +78,7 @@ OC.L10N.register(
"in a few seconds" : "через кілька секунд",
"seconds ago" : "кілька секунд тому",
"Empty file" : "Порожній файл",
- "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модуль з ID: %s не існує. Будь ласка, увімкніть це в налаштуваннях програми або зверніться до адміністратора.",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модуль з ID: %s не існує. Будь ласка, увімкніть це в налаштуваннях застосунку або зверніться до адміністратора.",
"File already exists" : "Файл вже існує",
"Invalid path" : "Недійсний шлях",
"Failed to create file from template" : "Не вдалося створити файл із шаблону",
@@ -90,9 +89,9 @@ OC.L10N.register(
"Dot files are not allowed" : "Файли які починаються з крапки не допустимі",
"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\" неможливо встановити, оскільки вона не сумісна з цією версією сервера.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Застосунок \"%s\" неможливо встановити, оскільки він не сумісний з цією версією сервера.",
"__language_name__" : "Українська",
- "This is an automatically sent email, please do not reply." : "Це автоматично надісланий електронний лист, будь ласка, не відповідайте.",
+ "This is an automatically sent email, please do not reply." : "Це автоматично надісланий електронний лист, будь ласка, не відповідайте на нього.",
"Help" : "Допомога",
"Appearance and accessibility" : "Тема та вигляд",
"Apps" : "Застосунки",
@@ -103,7 +102,7 @@ OC.L10N.register(
"Users" : "Користувачі",
"Email" : "Електронна пошта",
"Mail %s" : "Пошта %s",
- "Fediverse" : "Федіверс",
+ "Fediverse" : "Fediverse",
"View %s on the fediverse" : "Переглянути %s у Fediverse",
"Phone" : "Телефон",
"Call %s" : "Телефонуйте %s",
@@ -214,7 +213,7 @@ OC.L10N.register(
"Username is invalid because files already exist for this user" : "Ім'я користувача недійсне, оскільки файли для цього користувача вже існують",
"User disabled" : "Користувач виключений",
"Login canceled by app" : "Вхід скасовано застосунком",
- "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Програму \"%1$s\" неможливо встановити, оскільки не виконано такі залежності: %2$s",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Застосунок \"%1$s\" неможливо встановити, оскільки не виконано такі залежності: %2$s",
"a safe home for all your data" : "безпечний дім для ваших даних",
"File is currently busy, please try again later" : "Файл на разі зайнятий, будь ласка, спробуйте пізніше",
"Cannot download file" : "Неможливо завантажити файл",
@@ -225,8 +224,8 @@ OC.L10N.register(
"Cannot write into \"config\" directory." : "Не вдається записати в каталог \"config\".",
"This can usually be fixed by giving the web server write access to the config directory. See %s" : "Зазвичай це можна виправити, надавши веб-серверу доступ для запису до каталогу конфігурації. Побачити %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" : "Або, якщо ви віддаєте перевагу файлу config.php лише для читання, встановіть для параметра \"config_is_read_only\" значення true. Побачити %s",
- "Cannot write into \"apps\" directory." : "Не вдається записати в каталог \"програми\".",
- "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." : "Зазвичай це можна виправити, надавши веб-серверу доступ для запису до каталогу програм або вимкнувши App Store у конфігураційному файлі.",
+ "Cannot write into \"apps\" directory." : "Відсутній доступ на запис до каталогу застосунків \"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." : "Зазвичай це можна виправити, якщо надати вебсерверу доступ для запису до каталогу застосунків або вимкнути App Store у конфігураційному файлі.",
"Cannot create \"data\" directory." : "Неможливо створити каталог \"data\".",
"This can usually be fixed by giving the web server write access to the root directory. See %s" : "Зазвичай це можна виправити, надавши веб-серверу доступ на запис до кореневого каталогу. Побачити %s",
"Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Дозволи зазвичай можна виправити, надавши веб-серверу доступ на запис до кореневого каталогу. Побачити %s. ",
@@ -270,10 +269,9 @@ OC.L10N.register(
"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 замінено неправильно. Переконайтеся, що це версія, сумісна з сервером.",
+ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файли застосунку %1$s не було коректно оновлено. Переконайтеся, що ця версія, сумісна із сервером.",
+ "404" : "404",
"Full name" : "Повна назва",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Досягнуто обмеження на кількість користувачів, користувача не було створено. Перевірте сповіщення для докладної інформації.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Тільки такі символи допускаються в імені користувача: \"a-z\", \"A-Z\", \"0-9\", і \"_.@-'\"",
"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.",
diff --git a/lib/l10n/uk.json b/lib/l10n/uk.json
index 84b907a5e9e..63a608516d5 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" : "Підтвердження електронної пошти",
@@ -77,7 +76,7 @@
"in a few seconds" : "через кілька секунд",
"seconds ago" : "кілька секунд тому",
"Empty file" : "Порожній файл",
- "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модуль з ID: %s не існує. Будь ласка, увімкніть це в налаштуваннях програми або зверніться до адміністратора.",
+ "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модуль з ID: %s не існує. Будь ласка, увімкніть це в налаштуваннях застосунку або зверніться до адміністратора.",
"File already exists" : "Файл вже існує",
"Invalid path" : "Недійсний шлях",
"Failed to create file from template" : "Не вдалося створити файл із шаблону",
@@ -88,9 +87,9 @@
"Dot files are not allowed" : "Файли які починаються з крапки не допустимі",
"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\" неможливо встановити, оскільки вона не сумісна з цією версією сервера.",
+ "App \"%s\" cannot be installed because it is not compatible with this version of the server." : "Застосунок \"%s\" неможливо встановити, оскільки він не сумісний з цією версією сервера.",
"__language_name__" : "Українська",
- "This is an automatically sent email, please do not reply." : "Це автоматично надісланий електронний лист, будь ласка, не відповідайте.",
+ "This is an automatically sent email, please do not reply." : "Це автоматично надісланий електронний лист, будь ласка, не відповідайте на нього.",
"Help" : "Допомога",
"Appearance and accessibility" : "Тема та вигляд",
"Apps" : "Застосунки",
@@ -101,7 +100,7 @@
"Users" : "Користувачі",
"Email" : "Електронна пошта",
"Mail %s" : "Пошта %s",
- "Fediverse" : "Федіверс",
+ "Fediverse" : "Fediverse",
"View %s on the fediverse" : "Переглянути %s у Fediverse",
"Phone" : "Телефон",
"Call %s" : "Телефонуйте %s",
@@ -212,7 +211,7 @@
"Username is invalid because files already exist for this user" : "Ім'я користувача недійсне, оскільки файли для цього користувача вже існують",
"User disabled" : "Користувач виключений",
"Login canceled by app" : "Вхід скасовано застосунком",
- "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Програму \"%1$s\" неможливо встановити, оскільки не виконано такі залежності: %2$s",
+ "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Застосунок \"%1$s\" неможливо встановити, оскільки не виконано такі залежності: %2$s",
"a safe home for all your data" : "безпечний дім для ваших даних",
"File is currently busy, please try again later" : "Файл на разі зайнятий, будь ласка, спробуйте пізніше",
"Cannot download file" : "Неможливо завантажити файл",
@@ -223,8 +222,8 @@
"Cannot write into \"config\" directory." : "Не вдається записати в каталог \"config\".",
"This can usually be fixed by giving the web server write access to the config directory. See %s" : "Зазвичай це можна виправити, надавши веб-серверу доступ для запису до каталогу конфігурації. Побачити %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" : "Або, якщо ви віддаєте перевагу файлу config.php лише для читання, встановіть для параметра \"config_is_read_only\" значення true. Побачити %s",
- "Cannot write into \"apps\" directory." : "Не вдається записати в каталог \"програми\".",
- "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." : "Зазвичай це можна виправити, надавши веб-серверу доступ для запису до каталогу програм або вимкнувши App Store у конфігураційному файлі.",
+ "Cannot write into \"apps\" directory." : "Відсутній доступ на запис до каталогу застосунків \"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." : "Зазвичай це можна виправити, якщо надати вебсерверу доступ для запису до каталогу застосунків або вимкнути App Store у конфігураційному файлі.",
"Cannot create \"data\" directory." : "Неможливо створити каталог \"data\".",
"This can usually be fixed by giving the web server write access to the root directory. See %s" : "Зазвичай це можна виправити, надавши веб-серверу доступ на запис до кореневого каталогу. Побачити %s",
"Permissions can usually be fixed by giving the web server write access to the root directory. See %s." : "Дозволи зазвичай можна виправити, надавши веб-серверу доступ на запис до кореневого каталогу. Побачити %s. ",
@@ -268,10 +267,9 @@
"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 замінено неправильно. Переконайтеся, що це версія, сумісна з сервером.",
+ "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Файли застосунку %1$s не було коректно оновлено. Переконайтеся, що ця версія, сумісна із сервером.",
+ "404" : "404",
"Full name" : "Повна назва",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Досягнуто обмеження на кількість користувачів, користувача не було створено. Перевірте сповіщення для докладної інформації.",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Тільки такі символи допускаються в імені користувача: \"a-z\", \"A-Z\", \"0-9\", і \"_.@-'\"",
"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.",
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..82929d9ffde 100644
--- a/lib/l10n/zh_CN.js
+++ b/lib/l10n/zh_CN.js
@@ -5,9 +5,9 @@ OC.L10N.register(
"This can usually be fixed by giving the web server write access to the config directory." : "通常可以为 Web 服务器授予对 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",
+ "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 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。",
- "404" : "404",
"The page could not be found on the server." : "无法在服务器上找到此页面",
"%s email verification" : "%s 电子邮件验证",
"Email verification" : "电子邮件验证",
@@ -155,6 +155,8 @@ 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" : "星期日",
"Monday" : "星期一",
@@ -204,6 +206,7 @@ OC.L10N.register(
"A valid password must be provided" : "必须提供合法的密码",
"The username is already being used" : "用户名已被使用",
"Could not create user" : "无法创建用户",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "在用户名中只允许使用以下字符:“a-z”、“A-Z”、“0-9”、空格和 \"_.@-'\"",
"A valid username must be provided" : "必须提供合法的用户名",
"Username contains whitespace at the beginning or at the end" : "用户名在开头或结尾处包含空格",
"Username must not consist of dots only" : "用户名不能仅由点组成",
@@ -258,10 +261,17 @@ 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的文件替换不正确。请确认版本与当前服务器兼容。",
+ "404" : "404",
"Full name" : "全名",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "已达到用户上限,未创建该用户。请检查您的通知以了解更多。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "在用户名中只允许使用以下字符:“a-z”、“A-Z”、“0-9” 和 \"_.@-'\"",
"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。",
diff --git a/lib/l10n/zh_CN.json b/lib/l10n/zh_CN.json
index 87a73aec9b4..87e5c9b4021 100644
--- a/lib/l10n/zh_CN.json
+++ b/lib/l10n/zh_CN.json
@@ -3,9 +3,9 @@
"This can usually be fixed by giving the web server write access to the config directory." : "通常可以为 Web 服务器授予对 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",
+ "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 的样例文件直接复制使用。这可能会破坏您的安装。在对 config.php 进行修改之前请先阅读相关文档。",
- "404" : "404",
"The page could not be found on the server." : "无法在服务器上找到此页面",
"%s email verification" : "%s 电子邮件验证",
"Email verification" : "电子邮件验证",
@@ -153,6 +153,8 @@
"%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" : "星期日",
"Monday" : "星期一",
@@ -202,6 +204,7 @@
"A valid password must be provided" : "必须提供合法的密码",
"The username is already being used" : "用户名已被使用",
"Could not create user" : "无法创建用户",
+ "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "在用户名中只允许使用以下字符:“a-z”、“A-Z”、“0-9”、空格和 \"_.@-'\"",
"A valid username must be provided" : "必须提供合法的用户名",
"Username contains whitespace at the beginning or at the end" : "用户名在开头或结尾处包含空格",
"Username must not consist of dots only" : "用户名不能仅由点组成",
@@ -256,10 +259,17 @@
"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的文件替换不正确。请确认版本与当前服务器兼容。",
+ "404" : "404",
"Full name" : "全名",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "已达到用户上限,未创建该用户。请检查您的通知以了解更多。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "在用户名中只允许使用以下字符:“a-z”、“A-Z”、“0-9” 和 \"_.@-'\"",
"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。",
diff --git a/lib/l10n/zh_HK.js b/lib/l10n/zh_HK.js
index 345cf40a584..21e2e4453c7 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" : "電郵地址驗證",
@@ -271,9 +270,8 @@ OC.L10N.register(
"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 的檔案。請確保它們的版本與伺服器的版本兼容。",
+ "404" : "404",
"Full name" : "全名",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "用戶數量已達上限,無法創建新用戶。請查看您的通知以獲取更多資料。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "用戶名稱當中只能包含下列字元:\"a-z\", \"A-Z\", \"0-9\", 和 \"_.@-'\"",
"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",
diff --git a/lib/l10n/zh_HK.json b/lib/l10n/zh_HK.json
index 08e823d5723..11bbc622b69 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" : "電郵地址驗證",
@@ -269,9 +268,8 @@
"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 的檔案。請確保它們的版本與伺服器的版本兼容。",
+ "404" : "404",
"Full name" : "全名",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "用戶數量已達上限,無法創建新用戶。請查看您的通知以獲取更多資料。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "用戶名稱當中只能包含下列字元:\"a-z\", \"A-Z\", \"0-9\", 和 \"_.@-'\"",
"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",
diff --git a/lib/l10n/zh_TW.js b/lib/l10n/zh_TW.js
index c950bc7636d..6b98fd06acf 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" : "電子郵件驗證",
@@ -271,9 +270,8 @@ OC.L10N.register(
"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 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。",
+ "404" : "404",
"Full name" : "全名",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "已達使用者限制,所以未建立使用者。請檢查您的通知以取得更多資訊。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "使用者名稱當中只能包含下列字元:\"a-z\", \"A-Z\", \"0-9\", 和 \"_.@-'\"",
"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。",
diff --git a/lib/l10n/zh_TW.json b/lib/l10n/zh_TW.json
index f767cb5a8b3..fa4e390d81f 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" : "電子郵件驗證",
@@ -269,9 +268,8 @@
"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 中的檔案沒有被正確取代,請確認它的版本與伺服器相容。",
+ "404" : "404",
"Full name" : "全名",
- "The user limit has been reached and the user was not created. Check your notifications to learn more." : "已達使用者限制,所以未建立使用者。請檢查您的通知以取得更多資訊。",
- "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "使用者名稱當中只能包含下列字元:\"a-z\", \"A-Z\", \"0-9\", 和 \"_.@-'\"",
"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。",
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
index 3e33e783635..97156a027e6 100644
--- a/lib/private/Accounts/AccountManager.php
+++ b/lib/private/Accounts/AccountManager.php
@@ -38,15 +38,15 @@ namespace OC\Accounts;
use Exception;
use InvalidArgumentException;
use OC\Profile\TProfileHelper;
-use OCP\Accounts\UserUpdatedEvent;
-use OCP\Cache\CappedMemoryCache;
use OCA\Settings\BackgroundJobs\VerifyUserData;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\IAccountProperty;
use OCP\Accounts\IAccountPropertyCollection;
use OCP\Accounts\PropertyDoesNotExistException;
+use OCP\Accounts\UserUpdatedEvent;
use OCP\BackgroundJob\IJobList;
+use OCP\Cache\CappedMemoryCache;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php
index a7d24510d53..14069260c6c 100644
--- a/lib/private/Activity/Manager.php
+++ b/lib/private/Activity/Manager.php
@@ -70,11 +70,11 @@ class Manager implements IManager {
protected $l10n;
public function __construct(
- IRequest $request,
- IUserSession $session,
- IConfig $config,
- IValidator $validator,
- IL10N $l10n
+ IRequest $request,
+ IUserSession $session,
+ IConfig $config,
+ IValidator $validator,
+ IL10N $l10n
) {
$this->request = $request;
$this->session = $session;
diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php
index 2a0e8f53b14..ab4359c798f 100644
--- a/lib/private/AllConfig.php
+++ b/lib/private/AllConfig.php
@@ -32,6 +32,7 @@
*/
namespace OC;
+use Doctrine\DBAL\Platforms\OraclePlatform;
use OCP\Cache\CappedMemoryCache;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
@@ -42,7 +43,6 @@ use OCP\PreConditionNotMetException;
* Class to combine all the configuration options ownCloud offers
*/
class AllConfig implements IConfig {
- private SystemConfig $systemConfig;
private ?IDBConnection $connection = null;
/**
@@ -67,9 +67,10 @@ class AllConfig implements IConfig {
*/
private CappedMemoryCache $userCache;
- public function __construct(SystemConfig $systemConfig) {
+ public function __construct(
+ private SystemConfig $systemConfig
+ ) {
$this->userCache = new CappedMemoryCache();
- $this->systemConfig = $systemConfig;
}
/**
@@ -189,6 +190,7 @@ class AllConfig implements IConfig {
*
* @param string $appName the appName that we stored the value under
* @return string[] the keys stored for the app
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function getAppKeys($appName) {
return \OC::$server->get(AppConfig::class)->getKeys($appName);
@@ -200,6 +202,7 @@ class AllConfig implements IConfig {
* @param string $appName the appName that we want to store the value under
* @param string $key the key of the value, under which will be saved
* @param string|float|int $value the value that should be stored
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function setAppValue($appName, $key, $value) {
\OC::$server->get(AppConfig::class)->setValue($appName, $key, $value);
@@ -212,6 +215,7 @@ class AllConfig implements IConfig {
* @param string $key the key of the value, under which it was saved
* @param string $default the default value to be returned if the value isn't set
* @return string the saved value
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function getAppValue($appName, $key, $default = '') {
return \OC::$server->get(AppConfig::class)->getValue($appName, $key, $default);
@@ -222,6 +226,7 @@ class AllConfig implements IConfig {
*
* @param string $appName the appName that we stored the value under
* @param string $key the key of the value, under which it was saved
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function deleteAppValue($appName, $key) {
\OC::$server->get(AppConfig::class)->deleteKey($appName, $key);
@@ -231,6 +236,7 @@ class AllConfig implements IConfig {
* Removes all keys in appconfig belonging to the app
*
* @param string $appName the appName the configs are stored under
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function deleteAppValues($appName) {
\OC::$server->get(AppConfig::class)->deleteApp($appName);
@@ -490,12 +496,15 @@ class AllConfig implements IConfig {
$this->fixDIInit();
$qb = $this->connection->getQueryBuilder();
+ $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform)
+ ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)
+ : 'configvalue';
$result = $qb->select('userid')
->from('preferences')
->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq(
- $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR),
+ $configValueColumn,
$qb->createNamedParameter($value, IQueryBuilder::PARAM_STR))
)->orderBy('userid')
->executeQuery();
@@ -524,13 +533,18 @@ class AllConfig implements IConfig {
// Email address is always stored lowercase in the database
return $this->getUsersForUserValue($appName, $key, strtolower($value));
}
+
$qb = $this->connection->getQueryBuilder();
+ $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform)
+ ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)
+ : 'configvalue';
+
$result = $qb->select('userid')
->from('preferences')
->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq(
- $qb->func()->lower($qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)),
+ $qb->func()->lower($configValueColumn),
$qb->createNamedParameter(strtolower($value), IQueryBuilder::PARAM_STR))
)->orderBy('userid')
->executeQuery();
diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php
index ccbb2143133..60e55a314d6 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;
@@ -47,10 +48,10 @@ use OCP\App\Events\AppDisableEvent;
use OCP\App\Events\AppEnableEvent;
use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
-use OCP\EventDispatcher\IEventDispatcher;
use OCP\Collaboration\AutoComplete\IManager as IAutoCompleteManager;
use OCP\Collaboration\Collaborators\ISearch as ICollaboratorSearch;
use OCP\Diagnostics\IEventLogger;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IGroup;
@@ -104,12 +105,12 @@ class AppManager implements IAppManager {
private array $loadedApps = [];
public function __construct(IUserSession $userSession,
- IConfig $config,
- AppConfig $appConfig,
- IGroupManager $groupManager,
- ICacheFactory $memCacheFactory,
- IEventDispatcher $dispatcher,
- LoggerInterface $logger) {
+ IConfig $config,
+ AppConfig $appConfig,
+ IGroupManager $groupManager,
+ ICacheFactory $memCacheFactory,
+ IEventDispatcher $dispatcher,
+ LoggerInterface $logger) {
$this->userSession = $userSession;
$this->config = $config;
$this->appConfig = $appConfig;
@@ -288,7 +289,7 @@ class AppManager implements IAppManager {
* Check if an app is enabled for user
*
* @param string $appId
- * @param \OCP\IUser $user (optional) if not defined, the currently logged in user will be used
+ * @param \OCP\IUser|null $user (optional) if not defined, the currently logged in user will be used
* @return bool
*/
public function isEnabledForUser($appId, $user = null) {
@@ -701,10 +702,7 @@ class AppManager implements IAppManager {
/**
* Returns the app information from "appinfo/info.xml".
*
- * @param string $appId app id
- *
- * @param bool $path
- * @param null $lang
+ * @param string|null $lang
* @return array|null app info
*/
public function getAppInfo(string $appId, bool $path = false, $lang = null) {
@@ -816,15 +814,15 @@ class AppManager implements IAppManager {
/**
* @inheritdoc
*/
- public function getDefaultEnabledApps():array {
+ public function getDefaultEnabledApps(): array {
$this->loadShippedJson();
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';
+ $appId = $withFallbacks ? 'files' : '';
$defaultApps = explode(',', $this->config->getSystemValueString('defaultapp', ''));
$defaultApps = array_filter($defaultApps);
@@ -833,18 +831,21 @@ class AppManager implements IAppManager {
if ($user !== null) {
$userDefaultApps = explode(',', $this->config->getUserValue($user->getUID(), 'core', 'defaultapp'));
$defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps));
- if (empty($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)) {
- $customOrders = array_map('min', $customOrders);
- asort($customOrders);
- $defaultApps = array_keys($customOrders);
+ // filter only entries with app key (when added using closures or NavigationManager::add the app is not guranteed to be set)
+ $customOrders = array_filter($customOrders, fn ($entry) => isset($entry['app']));
+ // sort apps by order
+ usort($customOrders, fn ($a, $b) => $a['order'] - $b['order']);
+ // set default apps to sorted apps
+ $defaultApps = array_map(fn ($entry) => $entry['app'], $customOrders);
}
}
}
- if (empty($defaultApps)) {
+ if (empty($defaultApps) && $withFallbacks) {
$defaultApps = ['dashboard','files'];
}
@@ -859,4 +860,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/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php
index 47bdece372d..f9fbd05855b 100644
--- a/lib/private/App/AppStore/Fetcher/AppFetcher.php
+++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php
@@ -49,12 +49,12 @@ class AppFetcher extends Fetcher {
private $ignoreMaxVersion;
public function __construct(Factory $appDataFactory,
- IClientService $clientService,
- ITimeFactory $timeFactory,
- IConfig $config,
- CompareVersion $compareVersion,
- LoggerInterface $logger,
- IRegistry $registry) {
+ IClientService $clientService,
+ ITimeFactory $timeFactory,
+ IConfig $config,
+ CompareVersion $compareVersion,
+ LoggerInterface $logger,
+ IRegistry $registry) {
parent::__construct(
$appDataFactory,
$clientService,
diff --git a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php
index afe051e6281..d1bbe4f7b04 100644
--- a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php
+++ b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php
@@ -35,11 +35,11 @@ use Psr\Log\LoggerInterface;
class CategoryFetcher extends Fetcher {
public function __construct(Factory $appDataFactory,
- IClientService $clientService,
- ITimeFactory $timeFactory,
- IConfig $config,
- LoggerInterface $logger,
- IRegistry $registry) {
+ IClientService $clientService,
+ ITimeFactory $timeFactory,
+ IConfig $config,
+ LoggerInterface $logger,
+ IRegistry $registry) {
parent::__construct(
$appDataFactory,
$clientService,
diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php
index 095b026cb44..a693804f50f 100644
--- a/lib/private/App/AppStore/Fetcher/Fetcher.php
+++ b/lib/private/App/AppStore/Fetcher/Fetcher.php
@@ -68,11 +68,11 @@ abstract class Fetcher {
protected $channel = null;
public function __construct(Factory $appDataFactory,
- IClientService $clientService,
- ITimeFactory $timeFactory,
- IConfig $config,
- LoggerInterface $logger,
- IRegistry $registry) {
+ IClientService $clientService,
+ ITimeFactory $timeFactory,
+ IConfig $config,
+ LoggerInterface $logger,
+ IRegistry $registry) {
$this->appData = $appDataFactory->get('appstore');
$this->clientService = $clientService;
$this->timeFactory = $timeFactory;
@@ -109,10 +109,13 @@ abstract class Fetcher {
];
}
- // If we have a valid subscription key, send it to the appstore
- $subscriptionKey = $this->config->getAppValue('support', 'subscription_key');
- if ($this->registry->delegateHasValidSubscription() && $subscriptionKey) {
- $options['headers']['X-NC-Subscription-Key'] = $subscriptionKey;
+ if ($this->config->getSystemValueString('appstoreurl', 'https://apps.nextcloud.com/api/v1') === 'https://apps.nextcloud.com/api/v1') {
+ // If we have a valid subscription key, send it to the appstore
+ $subscriptionKey = $this->config->getAppValue('support', 'subscription_key');
+ if ($this->registry->delegateHasValidSubscription() && $subscriptionKey) {
+ $options['headers'] ??= [];
+ $options['headers']['X-NC-Subscription-Key'] = $subscriptionKey;
+ }
}
$client = $this->clientService->newClient();
diff --git a/lib/private/App/Platform.php b/lib/private/App/Platform.php
index 1cab740bebb..daff247d1bd 100644
--- a/lib/private/App/Platform.php
+++ b/lib/private/App/Platform.php
@@ -25,8 +25,8 @@
*/
namespace OC\App;
-use OCP\IConfig;
use OCP\IBinaryFinder;
+use OCP\IConfig;
/**
* Class Platform
diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php
index 79c650705b2..8064fe186f8 100644
--- a/lib/private/AppConfig.php
+++ b/lib/private/AppConfig.php
@@ -1,4 +1,6 @@
<?php
+
+declare(strict_types=1);
/**
* @copyright Copyright (c) 2017, Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2016, ownCloud, Inc.
@@ -9,6 +11,7 @@
* @author Jakob Sack <mail@jakobsack.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author michaelletzgus <michaelletzgus@users.noreply.github.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
@@ -30,413 +33,1414 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
+
namespace OC;
-use OC\DB\Connection;
-use OC\DB\OracleConnection;
+use InvalidArgumentException;
+use JsonException;
+use OCP\DB\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Exceptions\AppConfigIncorrectTypeException;
+use OCP\Exceptions\AppConfigTypeConflictException;
+use OCP\Exceptions\AppConfigUnknownKeyException;
use OCP\IAppConfig;
use OCP\IConfig;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
/**
* This class provides an easy way for apps to store config values in the
* database.
+ *
+ * **Note:** since 29.0.0, it supports **lazy loading**
+ *
+ * ### What is lazy loading ?
+ * In order to avoid loading useless config values into memory for each request,
+ * only non-lazy values are now loaded.
+ *
+ * Once a value that is lazy is requested, all lazy values will be loaded.
+ *
+ * Similarly, some methods from this class are marked with a warning about ignoring
+ * lazy loading. Use them wisely and only on parts of the code that are called
+ * during specific requests or actions to avoid loading the lazy values all the time.
+ *
+ * @since 7.0.0
+ * @since 29.0.0 - Supporting types and lazy loading
*/
class AppConfig implements IAppConfig {
- /** @var array[] */
- protected $sensitiveValues = [
- 'circles' => [
- '/^key_pairs$/',
- '/^local_gskey$/',
- ],
- 'external' => [
- '/^sites$/',
- ],
- 'integration_discourse' => [
- '/^private_key$/',
- '/^public_key$/',
- ],
- 'integration_dropbox' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_github' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_gitlab' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'integration_google' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_jira' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^forced_instance_url$/',
- ],
- 'integration_onedrive' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_openproject' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'integration_reddit' => [
- '/^client_id$/',
- '/^client_secret$/',
- ],
- 'integration_suitecrm' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'integration_twitter' => [
- '/^consumer_key$/',
- '/^consumer_secret$/',
- '/^followed_user$/',
- ],
- 'integration_zammad' => [
- '/^client_id$/',
- '/^client_secret$/',
- '/^oauth_instance_url$/',
- ],
- 'notify_push' => [
- '/^cookie$/',
- ],
- 'spreed' => [
- '/^bridge_bot_password$/',
- '/^hosted-signaling-server-(.*)$/',
- '/^recording_servers$/',
- '/^signaling_servers$/',
- '/^signaling_ticket_secret$/',
- '/^signaling_token_privkey_(.*)$/',
- '/^signaling_token_pubkey_(.*)$/',
- '/^sip_bridge_dialin_info$/',
- '/^sip_bridge_shared_secret$/',
- '/^stun_servers$/',
- '/^turn_servers$/',
- '/^turn_server_secret$/',
- ],
- 'support' => [
- '/^last_response$/',
- '/^potential_subscription_key$/',
- '/^subscription_key$/',
- ],
- 'theming' => [
- '/^imprintUrl$/',
- '/^privacyUrl$/',
- '/^slogan$/',
- '/^url$/',
- ],
- 'user_ldap' => [
- '/^(s..)?ldap_agent_password$/',
- ],
- 'user_saml' => [
- '/^idp-x509cert$/',
- ],
- ];
-
- /** @var Connection */
- protected $conn;
-
- /** @var array[] */
- private $cache = [];
-
- /** @var bool */
- private $configLoaded = false;
-
- /**
- * @param Connection $conn
- */
- public function __construct(Connection $conn) {
- $this->conn = $conn;
+ private const APP_MAX_LENGTH = 32;
+ private const KEY_MAX_LENGTH = 64;
+
+ /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
+ private array $fastCache = []; // cache for normal config keys
+ /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
+ private array $lazyCache = []; // cache for lazy config keys
+ /** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
+ private array $valueTypes = []; // type for all config values
+ private bool $fastLoaded = false;
+ private bool $lazyLoaded = false;
+
+ /**
+ * $migrationCompleted is only needed to manage the previous structure
+ * of the database during the upgrading process to nc29.
+ *
+ * only when upgrading from a version prior 28.0.2
+ *
+ * @TODO: remove this value in Nextcloud 30+
+ */
+ private bool $migrationCompleted = true;
+
+ public function __construct(
+ protected IDBConnection $connection,
+ private LoggerInterface $logger,
+ ) {
}
/**
- * @param string $app
- * @return array
+ * @inheritDoc
+ *
+ * @return string[] list of app ids
+ * @since 7.0.0
+ */
+ public function getApps(): array {
+ $this->loadConfigAll();
+ $apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
+ sort($apps);
+
+ return array_values(array_unique($apps));
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ *
+ * @return string[] list of stored config keys
+ * @since 29.0.0
*/
- private function getAppValues($app) {
- $this->loadConfigValues();
+ public function getKeys(string $app): array {
+ $this->assertParams($app);
+ $this->loadConfigAll();
+ $keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
+ sort($keys);
- if (isset($this->cache[$app])) {
- return $this->cache[$app];
+ return array_values(array_unique($keys));
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
+ *
+ * @return bool TRUE if key exists
+ * @since 7.0.0
+ * @since 29.0.0 Added the $lazy argument
+ */
+ public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
+ $this->assertParams($app, $key);
+ $this->loadConfig($lazy);
+
+ if ($lazy === null) {
+ $appCache = $this->getAllValues($app);
+ return isset($appCache[$key]);
+ }
+
+ if ($lazy) {
+ return isset($this->lazyCache[$app][$key]);
+ }
+
+ return isset($this->fastCache[$app][$key]);
+ }
+
+ /**
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
+ *
+ * @return bool
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @since 29.0.0
+ */
+ public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
+ $this->assertParams($app, $key);
+ $this->loadConfig($lazy);
+
+ if (!isset($this->valueTypes[$app][$key])) {
+ throw new AppConfigUnknownKeyException('unknown config key');
}
- return [];
+ return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
}
/**
- * Get all apps using the config
+ * @inheritDoc
*
- * @return string[] an array of app ids
+ * @param string $app if of the app
+ * @param string $key config key
*
- * This function returns a list of all apps that have at least one
- * entry in the appconfig table.
+ * @return bool TRUE if config is lazy loaded
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @see IAppConfig for details about lazy loading
+ * @since 29.0.0
*/
- public function getApps() {
- $this->loadConfigValues();
+ public function isLazy(string $app, string $key): bool {
+ // there is a huge probability the non-lazy config are already loaded
+ if ($this->hasKey($app, $key, false)) {
+ return false;
+ }
+
+ // key not found, we search in the lazy config
+ if ($this->hasKey($app, $key, true)) {
+ return true;
+ }
- return $this->getSortedKeys($this->cache);
+ throw new AppConfigUnknownKeyException('unknown config key');
}
+
/**
- * Get the available keys for an app
+ * @inheritDoc
*
- * @param string $app the app we are looking for
- * @return array an array of key names
+ * @param string $app id of the app
+ * @param string $key config keys prefix to search
+ * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
*
- * This function gets all keys of an app. Please note that the values are
- * not returned.
+ * @return array<string, string> [configKey => configValue]
+ * @since 29.0.0
*/
- public function getKeys($app) {
- $this->loadConfigValues();
+ public function getAllValues(string $app, string $key = '', bool $filtered = false): array {
+ $this->assertParams($app, $key);
+ // if we want to filter values, we need to get sensitivity
+ $this->loadConfigAll();
+ // array_merge() will remove numeric keys (here config keys), so addition arrays instead
+ $values = ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []);
+
+ if (!$filtered) {
+ return $values;
+ }
- if (isset($this->cache[$app])) {
- return $this->getSortedKeys($this->cache[$app]);
+ /**
+ * Using the old (deprecated) list of sensitive values.
+ */
+ foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
+ $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
+ foreach ($sensitiveKeys as $sensitiveKey) {
+ $this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
+ }
}
- return [];
+ $result = [];
+ foreach ($values as $key => $value) {
+ $result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
+ }
+
+ return $result;
}
- public function getSortedKeys($data) {
- $keys = array_keys($data);
- sort($keys);
- return $keys;
+ /**
+ * @inheritDoc
+ *
+ * @param string $key config key
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return array<string, string> [appId => configValue]
+ * @since 29.0.0
+ */
+ public function searchValues(string $key, bool $lazy = false): array {
+ $this->assertParams('', $key, true);
+ $this->loadConfig($lazy);
+ $values = [];
+
+ /** @var array<array-key, array<array-key, mixed>> $cache */
+ if ($lazy) {
+ $cache = $this->lazyCache;
+ } else {
+ $cache = $this->fastCache;
+ }
+
+ foreach (array_keys($cache) as $app) {
+ if (isset($cache[$app][$key])) {
+ $values[$app] = $cache[$app][$key];
+ }
+ }
+
+ return $values;
}
+
/**
- * Gets the config value
+ * Get the config value as string.
+ * If the value does not exist the given default will be returned.
+ *
+ * Set lazy to `null` to ignore it and get the value from either source.
+ *
+ * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $default config value
+ * @param null|bool $lazy get config as lazy loaded or not. can be NULL
*
- * @param string $app app
- * @param string $key key
- * @param string $default = null, default value if the key does not exist
* @return string the value or $default
+ * @internal
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueInt()
+ * @see getValueFloat()
+ * @see getValueBool()
+ * @see getValueArray()
+ */
+ public function getValueMixed(
+ string $app,
+ string $key,
+ string $default = '',
+ ?bool $lazy = false
+ ): string {
+ try {
+ $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
+ } catch (AppConfigUnknownKeyException $e) {
+ return $default;
+ }
+
+ return $this->getTypedValue(
+ $app,
+ $key,
+ $default,
+ $lazy,
+ self::VALUE_MIXED
+ );
+ }
+
+ /**
+ * @inheritDoc
*
- * This function gets a value from the appconfig table. If the key does
- * not exist the default value will be returned
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return string stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
*/
- public function getValue($app, $key, $default = null) {
- $this->loadConfigValues();
+ public function getValueString(
+ string $app,
+ string $key,
+ string $default = '',
+ bool $lazy = false
+ ): string {
+ return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return int stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function getValueInt(
+ string $app,
+ string $key,
+ int $default = 0,
+ bool $lazy = false
+ ): int {
+ return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param float $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return float stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
+ return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
+ }
- if ($this->hasKey($app, $key)) {
- return $this->cache[$app][$key];
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
+ $b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
+ return in_array($b, ['1', 'true', 'yes', 'on']);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param array $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return array stored config value or $default if not set in database
+ * @throws InvalidArgumentException if one of the argument format is invalid
+ * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function getValueArray(
+ string $app,
+ string $key,
+ array $default = [],
+ bool $lazy = false
+ ): array {
+ try {
+ $defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
+ $value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
+
+ return is_array($value) ? $value : [];
+ } catch (JsonException) {
+ return [];
}
+ }
- return $default;
+ /**
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $default default value
+ * @param bool $lazy search within lazy loaded config
+ * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
+ *
+ * @return string
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @throws InvalidArgumentException
+ */
+ private function getTypedValue(
+ string $app,
+ string $key,
+ string $default,
+ bool $lazy,
+ int $type
+ ): string {
+ $this->assertParams($app, $key, valueType: $type);
+ $this->loadConfig($lazy);
+
+ /**
+ * We ignore check if mixed type is requested.
+ * If type of stored value is set as mixed, we don't filter.
+ * If type of stored value is defined, we compare with the one requested.
+ */
+ $knownType = $this->valueTypes[$app][$key] ?? 0;
+ if (!$this->isTyped(self::VALUE_MIXED, $type)
+ && $knownType > 0
+ && !$this->isTyped(self::VALUE_MIXED, $knownType)
+ && !$this->isTyped($type, $knownType)) {
+ $this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
+ throw new AppConfigTypeConflictException('conflict with value type from database');
+ }
+
+ if ($lazy) {
+ return $this->lazyCache[$app][$key] ?? $default;
+ }
+
+ return $this->fastCache[$app][$key] ?? $default;
}
/**
- * check if a key is set in the appconfig
+ * @inheritDoc
*
- * @param string $app
- * @param string $key
- * @return bool
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @return int type of the value
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @since 29.0.0
+ * @see VALUE_STRING
+ * @see VALUE_INT
+ * @see VALUE_FLOAT
+ * @see VALUE_BOOL
+ * @see VALUE_ARRAY
*/
- public function hasKey($app, $key) {
- $this->loadConfigValues();
+ public function getValueType(string $app, string $key): int {
+ $this->assertParams($app, $key);
+ $this->loadConfigAll();
- return isset($this->cache[$app][$key]);
+ if (!isset($this->valueTypes[$app][$key])) {
+ throw new AppConfigUnknownKeyException('unknown config key');
+ }
+
+ $type = $this->valueTypes[$app][$key];
+ $type &= ~self::VALUE_SENSITIVE;
+ return $type;
}
+
/**
- * Sets a value. If the key did not exist before it will be created.
+ * Store a config key and its value in database as VALUE_MIXED
*
- * @param string $app app
- * @param string $key key
- * @param string|float|int $value value
- * @return bool True if the value was inserted or updated, false if the value was the same
+ * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
+ * @internal
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueInt()
+ * @see setValueFloat()
+ * @see setValueBool()
+ * @see setValueArray()
*/
- public function setValue($app, $key, $value) {
- if (!$this->hasKey($app, $key)) {
- $inserted = (bool) $this->conn->insertIfNotExist('*PREFIX*appconfig', [
- 'appid' => $app,
- 'configkey' => $key,
- 'configvalue' => $value,
- ], [
- 'appid',
- 'configkey',
- ]);
-
- if ($inserted) {
- if (!isset($this->cache[$app])) {
- $this->cache[$app] = [];
- }
+ public function setValueMixed(
+ string $app,
+ string $key,
+ string $value,
+ bool $lazy = false,
+ bool $sensitive = false
+ ): bool {
+ return $this->setTypedValue(
+ $app,
+ $key,
+ $value,
+ $lazy,
+ self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
+ );
+ }
- $this->cache[$app][$key] = $value;
- return true;
- }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function setValueString(
+ string $app,
+ string $key,
+ string $value,
+ bool $lazy = false,
+ bool $sensitive = false
+ ): bool {
+ return $this->setTypedValue(
+ $app,
+ $key,
+ $value,
+ $lazy,
+ self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
+ );
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function setValueInt(
+ string $app,
+ string $key,
+ int $value,
+ bool $lazy = false,
+ bool $sensitive = false
+ ): bool {
+ if ($value > 2000000000) {
+ $this->logger->debug('You are trying to store an integer value around/above 2,147,483,647. This is a reminder that reaching this theoretical limit on 32 bits system will throw an exception.');
}
- $sql = $this->conn->getQueryBuilder();
- $sql->update('appconfig')
- ->set('configvalue', $sql->createNamedParameter($value))
- ->where($sql->expr()->eq('appid', $sql->createNamedParameter($app)))
- ->andWhere($sql->expr()->eq('configkey', $sql->createNamedParameter($key)));
+ return $this->setTypedValue(
+ $app,
+ $key,
+ (string)$value,
+ $lazy,
+ self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
+ );
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param float $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function setValueFloat(
+ string $app,
+ string $key,
+ float $value,
+ bool $lazy = false,
+ bool $sensitive = false
+ ): bool {
+ return $this->setTypedValue(
+ $app,
+ $key,
+ (string)$value,
+ $lazy,
+ self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
+ );
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $value config value
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function setValueBool(
+ string $app,
+ string $key,
+ bool $value,
+ bool $lazy = false
+ ): bool {
+ return $this->setTypedValue(
+ $app,
+ $key,
+ ($value) ? '1' : '0',
+ $lazy,
+ self::VALUE_BOOL
+ );
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param array $value config value
+ * @param bool $lazy set config as lazy loaded
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @throws JsonException
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ */
+ public function setValueArray(
+ string $app,
+ string $key,
+ array $value,
+ bool $lazy = false,
+ bool $sensitive = false
+ ): bool {
+ try {
+ return $this->setTypedValue(
+ $app,
+ $key,
+ json_encode($value, JSON_THROW_ON_ERROR),
+ $lazy,
+ self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
+ );
+ } catch (JsonException $e) {
+ $this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
+ throw $e;
+ }
+ }
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value and same sensitive/lazy status, the
+ * database is not updated. If config value was previously stored as sensitive, status will not be
+ * altered.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $lazy config set as lazy loaded
+ * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
+ *
+ * @return bool TRUE if value was updated in database
+ * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
+ * @see IAppConfig for explanation about lazy loading
+ */
+ private function setTypedValue(
+ string $app,
+ string $key,
+ string $value,
+ bool $lazy,
+ int $type
+ ): bool {
+ $this->assertParams($app, $key);
+ $this->loadConfig($lazy);
+
+ $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
/*
- * Only limit to the existing value for non-Oracle DBs:
- * http://docs.oracle.com/cd/E11882_01/server.112/e26088/conditions002.htm#i1033286
- * > Large objects (LOBs) are not supported in comparison conditions.
+ * no update if key is already known with set lazy status, or value is
+ * different, or sensitivity switched from false to true.
*/
- if (!($this->conn instanceof OracleConnection)) {
- /*
- * Only update the value when it is not the same
- * Note that NULL requires some special handling. Since comparing
- * against null can have special results.
+ if ($this->hasKey($app, $key, $lazy)
+ && $value === $this->getTypedValue($app, $key, $value, $lazy, $type)
+ && (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
+ return false;
+ }
+
+ $refreshCache = false;
+ $insert = $this->connection->getQueryBuilder();
+ $insert->insert('appconfig')
+ ->setValue('appid', $insert->createNamedParameter($app))
+ ->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
+ ->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
+ ->setValue('configkey', $insert->createNamedParameter($key))
+ ->setValue('configvalue', $insert->createNamedParameter($value));
+ try {
+ $insert->executeStatement();
+ } catch (DBException $e) {
+ if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
+ throw $e; // TODO: throw exception or just log and returns false !?
+ }
+
+ $currType = $this->valueTypes[$app][$key] ?? 0;
+ if ($currType === 0) { // this might happen when switching lazy loading status
+ $this->loadConfigAll();
+ $currType = $this->valueTypes[$app][$key] ?? 0;
+ }
+
+ /**
+ * This should only happen during the upgrade process from 28 to 29.
+ * We only log a warning and set it to VALUE_MIXED.
*/
+ if ($currType === 0) {
+ $this->logger->warning('Value type is set to zero (0) in database. This is fine only during the upgrade process from 28 to 29.', ['app' => $app, 'key' => $key]);
+ $currType = self::VALUE_MIXED;
+ }
- if ($value === null) {
- $sql->andWhere(
- $sql->expr()->isNotNull('configvalue')
- );
- } else {
- $sql->andWhere(
- $sql->expr()->orX(
- $sql->expr()->isNull('configvalue'),
- $sql->expr()->neq('configvalue', $sql->createNamedParameter($value), IQueryBuilder::PARAM_STR)
- )
- );
+ /**
+ * we only accept a different type from the one stored in database
+ * if the one stored in database is not-defined (VALUE_MIXED)
+ */
+ if (!$this->isTyped(self::VALUE_MIXED, $currType) &&
+ ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
+ try {
+ $currType = $this->convertTypeToString($currType);
+ $type = $this->convertTypeToString($type);
+ } catch (AppConfigIncorrectTypeException) {
+ // can be ignored, this was just needed for a better exception message.
+ }
+ throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
+ }
+
+ // we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
+ if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
+ $type = $type | self::VALUE_SENSITIVE;
}
+
+ if ($lazy !== $this->isLazy($app, $key)) {
+ $refreshCache = true;
+ }
+
+ $update = $this->connection->getQueryBuilder();
+ $update->update('appconfig')
+ ->set('configvalue', $update->createNamedParameter($value))
+ ->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
+ ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
+ ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
+ ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
+
+ $update->executeStatement();
}
- $changedRow = (bool) $sql->execute();
+ if ($refreshCache) {
+ $this->clearCache();
+ return true;
+ }
- $this->cache[$app][$key] = $value;
+ // update local cache
+ if ($lazy) {
+ $cache = &$this->lazyCache;
+ } else {
+ $cache = &$this->fastCache;
+ }
+ $cache[$app][$key] = $value;
+ $this->valueTypes[$app][$key] = $type;
- return $changedRow;
+ return true;
}
/**
- * Deletes a key
+ * Change the type of config value.
*
- * @param string $app app
- * @param string $key key
- * @return boolean
+ * **WARNING:** Method is internal and **MUST** not be used as it may break things.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
+ *
+ * @return bool TRUE if database update were necessary
+ * @throws AppConfigUnknownKeyException if $key is now known in database
+ * @throws AppConfigIncorrectTypeException if $type is not valid
+ * @internal
+ * @since 29.0.0
*/
- public function deleteKey($app, $key) {
- $this->loadConfigValues();
+ public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
+ $this->assertParams($app, $key);
+ $this->loadConfigAll();
+ $lazy = $this->isLazy($app, $key);
- $sql = $this->conn->getQueryBuilder();
- $sql->delete('appconfig')
- ->where($sql->expr()->eq('appid', $sql->createParameter('app')))
- ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
- ->setParameter('app', $app)
- ->setParameter('configkey', $key);
- $sql->execute();
+ if (!$this->hasKey($app, $key, $lazy)) {
+ throw new AppConfigUnknownKeyException('Unknown config key');
+ }
- unset($this->cache[$app][$key]);
- return false;
+ // type can only be one type
+ if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
+ throw new AppConfigIncorrectTypeException('Unknown value type');
+ }
+
+ $currType = $this->valueTypes[$app][$key];
+ if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
+ return false;
+ }
+
+ // we complete with sensitive flag if the stored value is set as sensitive
+ if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
+ $type = $type | self::VALUE_SENSITIVE;
+ }
+
+ $update = $this->connection->getQueryBuilder();
+ $update->update('appconfig')
+ ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
+ ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
+ ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
+ $update->executeStatement();
+ $this->valueTypes[$app][$key] = $type;
+
+ return true;
}
+
/**
- * Remove app from appconfig
+ * @inheritDoc
*
- * @param string $app app
- * @return boolean
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
*
- * Removes all keys in appconfig belonging to the app.
+ * @return bool TRUE if database update were necessary
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @since 29.0.0
*/
- public function deleteApp($app) {
- $this->loadConfigValues();
+ public function updateSensitive(string $app, string $key, bool $sensitive): bool {
+ $this->assertParams($app, $key);
+ $this->loadConfigAll();
- $sql = $this->conn->getQueryBuilder();
- $sql->delete('appconfig')
- ->where($sql->expr()->eq('appid', $sql->createParameter('app')))
- ->setParameter('app', $app);
- $sql->execute();
+ if ($sensitive === $this->isSensitive($app, $key, null)) {
+ return false;
+ }
+
+ /**
+ * type returned by getValueType() is already cleaned from sensitive flag
+ * we just need to update it based on $sensitive and store it in database
+ */
+ $type = $this->getValueType($app, $key);
+ if ($sensitive) {
+ $type = $type | self::VALUE_SENSITIVE;
+ }
+
+ $update = $this->connection->getQueryBuilder();
+ $update->update('appconfig')
+ ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
+ ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
+ ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
+ $update->executeStatement();
- unset($this->cache[$app]);
- return false;
+ $this->valueTypes[$app][$key] = $type;
+
+ return true;
}
/**
- * get multiple values, either the app or key can be used as wildcard by setting it to false
+ * @inheritDoc
*
- * @param string|false $app
- * @param string|false $key
- * @return array|false
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
+ *
+ * @return bool TRUE if database update was necessary
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @since 29.0.0
*/
- public function getValues($app, $key) {
- if (($app !== false) === ($key !== false)) {
+ public function updateLazy(string $app, string $key, bool $lazy): bool {
+ $this->assertParams($app, $key);
+ $this->loadConfigAll();
+
+ if ($lazy === $this->isLazy($app, $key)) {
return false;
}
- if ($key === false) {
- return $this->getAppValues($app);
+ $update = $this->connection->getQueryBuilder();
+ $update->update('appconfig')
+ ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
+ ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
+ ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
+ $update->executeStatement();
+
+ // At this point, it is a lot safer to clean cache
+ $this->clearCache();
+
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @return array
+ * @throws AppConfigUnknownKeyException if config key is not known in database
+ * @since 29.0.0
+ */
+ public function getDetails(string $app, string $key): array {
+ $this->assertParams($app, $key);
+ $this->loadConfigAll();
+ $lazy = $this->isLazy($app, $key);
+
+ if ($lazy) {
+ $cache = $this->lazyCache;
} else {
- $appIds = $this->getApps();
- $values = array_map(function ($appId) use ($key) {
- return $this->cache[$appId][$key] ?? null;
- }, $appIds);
- $result = array_combine($appIds, $values);
+ $cache = $this->fastCache;
+ }
+
+ $type = $this->getValueType($app, $key);
+ try {
+ $typeString = $this->convertTypeToString($type);
+ } catch (AppConfigIncorrectTypeException $e) {
+ $this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
+ $typeString = (string)$type;
+ }
- return array_filter($result);
+ if (!isset($cache[$app][$key])) {
+ throw new AppConfigUnknownKeyException('unknown config key');
}
+
+ return [
+ 'app' => $app,
+ 'key' => $key,
+ 'value' => $cache[$app][$key],
+ 'type' => $type,
+ 'lazy' => $lazy,
+ 'typeString' => $typeString,
+ 'sensitive' => $this->isSensitive($app, $key, null)
+ ];
}
/**
- * get all values of the app or and filters out sensitive data
+ * @param string $type
+ *
+ * @return int
+ * @throws AppConfigIncorrectTypeException
+ * @since 29.0.0
+ */
+ public function convertTypeToInt(string $type): int {
+ return match (strtolower($type)) {
+ 'mixed' => IAppConfig::VALUE_MIXED,
+ 'string' => IAppConfig::VALUE_STRING,
+ 'integer' => IAppConfig::VALUE_INT,
+ 'float' => IAppConfig::VALUE_FLOAT,
+ 'boolean' => IAppConfig::VALUE_BOOL,
+ 'array' => IAppConfig::VALUE_ARRAY,
+ default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
+ };
+ }
+
+ /**
+ * @param int $type
+ *
+ * @return string
+ * @throws AppConfigIncorrectTypeException
+ * @since 29.0.0
+ */
+ public function convertTypeToString(int $type): string {
+ $type &= ~self::VALUE_SENSITIVE;
+
+ return match ($type) {
+ IAppConfig::VALUE_MIXED => 'mixed',
+ IAppConfig::VALUE_STRING => 'string',
+ IAppConfig::VALUE_INT => 'integer',
+ IAppConfig::VALUE_FLOAT => 'float',
+ IAppConfig::VALUE_BOOL => 'boolean',
+ IAppConfig::VALUE_ARRAY => 'array',
+ default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
+ };
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @since 29.0.0
+ */
+ public function deleteKey(string $app, string $key): void {
+ $this->assertParams($app, $key);
+ $qb = $this->connection->getQueryBuilder();
+ $qb->delete('appconfig')
+ ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
+ ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
+ $qb->executeStatement();
+
+ unset($this->lazyCache[$app][$key]);
+ unset($this->fastCache[$app][$key]);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $app id of the app
+ *
+ * @since 29.0.0
+ */
+ public function deleteApp(string $app): void {
+ $this->assertParams($app);
+ $qb = $this->connection->getQueryBuilder();
+ $qb->delete('appconfig')
+ ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
+ $qb->executeStatement();
+
+ $this->clearCache();
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param bool $reload set to TRUE to refill cache instantly after clearing it
+ *
+ * @since 29.0.0
+ */
+ public function clearCache(bool $reload = false): void {
+ $this->lazyLoaded = $this->fastLoaded = false;
+ $this->lazyCache = $this->fastCache = $this->valueTypes = [];
+
+ if (!$reload) {
+ return;
+ }
+
+ $this->loadConfigAll();
+ }
+
+
+ /**
+ * For debug purpose.
+ * Returns the cached data.
*
- * @param string $app
* @return array
+ * @since 29.0.0
+ * @internal
*/
- public function getFilteredValues($app) {
- $values = $this->getValues($app, false);
+ public function statusCache(): array {
+ return [
+ 'fastLoaded' => $this->fastLoaded,
+ 'fastCache' => $this->fastCache,
+ 'lazyLoaded' => $this->lazyLoaded,
+ 'lazyCache' => $this->lazyCache,
+ ];
+ }
- if (isset($this->sensitiveValues[$app])) {
- foreach ($this->sensitiveValues[$app] as $sensitiveKeyExp) {
- $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
- foreach ($sensitiveKeys as $sensitiveKey) {
- $values[$sensitiveKey] = IConfig::SENSITIVE_VALUE;
- }
+ /**
+ * @param int $needle bitflag to search
+ * @param int $type known value
+ *
+ * @return bool TRUE if bitflag $needle is set in $type
+ */
+ private function isTyped(int $needle, int $type): bool {
+ return (($needle & $type) !== 0);
+ }
+
+ /**
+ * Confirm the string set for app and key fit the database description
+ *
+ * @param string $app assert $app fit in database
+ * @param string $configKey assert config key fit in database
+ * @param bool $allowEmptyApp $app can be empty string
+ * @param int $valueType assert value type is only one type
+ *
+ * @throws InvalidArgumentException
+ */
+ private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
+ if (!$allowEmptyApp && $app === '') {
+ throw new InvalidArgumentException('app cannot be an empty string');
+ }
+ if (strlen($app) > self::APP_MAX_LENGTH) {
+ throw new InvalidArgumentException(
+ 'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
+ );
+ }
+ if (strlen($configKey) > self::KEY_MAX_LENGTH) {
+ throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
+ }
+ if ($valueType > -1) {
+ $valueType &= ~self::VALUE_SENSITIVE;
+ if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
+ throw new InvalidArgumentException('Unknown value type');
}
}
+ }
- return $values;
+ private function loadConfigAll(): void {
+ $this->loadConfig(null);
}
/**
- * Load all the app config values
+ * Load normal config or config set as lazy loaded
+ *
+ * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
*/
- protected function loadConfigValues() {
- if ($this->configLoaded) {
+ private function loadConfig(?bool $lazy = false): void {
+ if ($this->isLoaded($lazy)) {
return;
}
- $this->cache = [];
+ $qb = $this->connection->getQueryBuilder();
+ $qb->from('appconfig');
- $sql = $this->conn->getQueryBuilder();
- $sql->select('*')
- ->from('appconfig');
- $result = $sql->execute();
+ /**
+ * The use of $this->>migrationCompleted is only needed to manage the
+ * database during the upgrading process to nc29.
+ */
+ if (!$this->migrationCompleted) {
+ $qb->select('appid', 'configkey', 'configvalue');
+ } else {
+ // we only need value from lazy when loadConfig does not specify it
+ $qb->select('appid', 'configkey', 'configvalue', 'type');
+
+ if ($lazy !== null) {
+ $qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
+ } else {
+ $qb->addSelect('lazy');
+ }
+ }
+
+ try {
+ $result = $qb->executeQuery();
+ } catch (DBException $e) {
+ /**
+ * in case of issue with field name, it means that migration is not completed.
+ * Falling back to a request without select on lazy.
+ * This whole try/catch and the migrationCompleted variable can be removed in NC30.
+ */
+ if ($e->getReason() !== DBException::REASON_INVALID_FIELD_NAME) {
+ throw $e;
+ }
+
+ $this->migrationCompleted = false;
+ $this->loadConfig($lazy);
+
+ return;
+ }
- // we are going to store the result in memory anyway
$rows = $result->fetchAll();
foreach ($rows as $row) {
- if (!isset($this->cache[$row['appid']])) {
- $this->cache[(string)$row['appid']] = [];
+ // most of the time, 'lazy' is not in the select because its value is already known
+ if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
+ $cache = &$this->lazyCache;
+ } else {
+ $cache = &$this->fastCache;
}
-
- $this->cache[(string)$row['appid']][(string)$row['configkey']] = (string)$row['configvalue'];
+ $cache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
+ $this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
}
$result->closeCursor();
+ $this->setAsLoaded($lazy);
+ }
+
+ /**
+ * if $lazy is:
+ * - false: will returns true if fast config is loaded
+ * - true : will returns true if lazy config is loaded
+ * - null : will returns true if both config are loaded
+ *
+ * @param bool $lazy
+ *
+ * @return bool
+ */
+ private function isLoaded(?bool $lazy): bool {
+ if ($lazy === null) {
+ return $this->lazyLoaded && $this->fastLoaded;
+ }
+
+ return $lazy ? $this->lazyLoaded : $this->fastLoaded;
+ }
+
+ /**
+ * if $lazy is:
+ * - false: set fast config as loaded
+ * - true : set lazy config as loaded
+ * - null : set both config as loaded
+ *
+ * @param bool $lazy
+ */
+ private function setAsLoaded(?bool $lazy): void {
+ if ($lazy === null) {
+ $this->fastLoaded = true;
+ $this->lazyLoaded = true;
+
+ return;
+ }
+
+ if ($lazy) {
+ $this->lazyLoaded = true;
+ } else {
+ $this->fastLoaded = true;
+ }
+ }
+
+ /**
+ * Gets the config value
+ *
+ * @param string $app app
+ * @param string $key key
+ * @param string $default = null, default value if the key does not exist
+ *
+ * @return string the value or $default
+ * @deprecated - use getValue*()
+ *
+ * This function gets a value from the appconfig table. If the key does
+ * not exist the default value will be returned
+ */
+ public function getValue($app, $key, $default = null) {
+ $this->loadConfig();
- $this->configLoaded = true;
+ return $this->fastCache[$app][$key] ?? $default;
+ }
+
+ /**
+ * Sets a value. If the key did not exist before it will be created.
+ *
+ * @param string $app app
+ * @param string $key key
+ * @param string|float|int $value value
+ *
+ * @return bool True if the value was inserted or updated, false if the value was the same
+ * @throws AppConfigTypeConflictException
+ * @throws AppConfigUnknownKeyException
+ * @deprecated
+ */
+ public function setValue($app, $key, $value) {
+ /**
+ * TODO: would it be overkill, or decently improve performance, to catch
+ * call to this method with $key='enabled' and 'hide' config value related
+ * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
+ * or enabled (lazy=lazy-2)
+ *
+ * this solution would remove the loading of config values from disabled app
+ * unless calling the method {@see loadConfigAll()}
+ */
+ return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
}
/**
+ * get multiple values, either the app or key can be used as wildcard by setting it to false
+ *
+ * @param string|false $app
+ * @param string|false $key
+ *
+ * @return array|false
+ * @deprecated 29.0.0 use getAllValues()
+ */
+ public function getValues($app, $key) {
+ if (($app !== false) === ($key !== false)) {
+ return false;
+ }
+
+ $key = ($key === false) ? '' : $key;
+ if (!$app) {
+ return $this->searchValues($key);
+ } else {
+ return $this->getAllValues($app, $key);
+ }
+ }
+
+ /**
+ * get all values of the app or and filters out sensitive data
+ *
+ * @param string $app
+ *
+ * @return array
+ * @deprecated 29.0.0 use getAllValues()
+ */
+ public function getFilteredValues($app) {
+ return $this->getAllValues($app, filtered: true);
+ }
+
+ /**
+ * @param string $app
+ *
+ * @return string[]
+ * @deprecated data sensitivity should be set when calling setValue*()
+ */
+ private function getSensitiveKeys(string $app): array {
+ $sensitiveValues = [
+ 'circles' => [
+ '/^key_pairs$/',
+ '/^local_gskey$/',
+ ],
+ 'external' => [
+ '/^sites$/',
+ ],
+ 'integration_discourse' => [
+ '/^private_key$/',
+ '/^public_key$/',
+ ],
+ 'integration_dropbox' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ ],
+ 'integration_github' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ ],
+ 'integration_gitlab' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ '/^oauth_instance_url$/',
+ ],
+ 'integration_google' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ ],
+ 'integration_jira' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ '/^forced_instance_url$/',
+ ],
+ 'integration_onedrive' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ ],
+ 'integration_openproject' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ '/^oauth_instance_url$/',
+ ],
+ 'integration_reddit' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ ],
+ 'integration_suitecrm' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ '/^oauth_instance_url$/',
+ ],
+ 'integration_twitter' => [
+ '/^consumer_key$/',
+ '/^consumer_secret$/',
+ '/^followed_user$/',
+ ],
+ 'integration_zammad' => [
+ '/^client_id$/',
+ '/^client_secret$/',
+ '/^oauth_instance_url$/',
+ ],
+ 'notify_push' => [
+ '/^cookie$/',
+ ],
+ 'serverinfo' => [
+ '/^token$/',
+ ],
+ 'spreed' => [
+ '/^bridge_bot_password$/',
+ '/^hosted-signaling-server-(.*)$/',
+ '/^recording_servers$/',
+ '/^signaling_servers$/',
+ '/^signaling_ticket_secret$/',
+ '/^signaling_token_privkey_(.*)$/',
+ '/^signaling_token_pubkey_(.*)$/',
+ '/^sip_bridge_dialin_info$/',
+ '/^sip_bridge_shared_secret$/',
+ '/^stun_servers$/',
+ '/^turn_servers$/',
+ '/^turn_server_secret$/',
+ ],
+ 'support' => [
+ '/^last_response$/',
+ '/^potential_subscription_key$/',
+ '/^subscription_key$/',
+ ],
+ 'theming' => [
+ '/^imprintUrl$/',
+ '/^privacyUrl$/',
+ '/^slogan$/',
+ '/^url$/',
+ ],
+ 'user_ldap' => [
+ '/^(s..)?ldap_agent_password$/',
+ ],
+ 'user_saml' => [
+ '/^idp-x509cert$/',
+ ],
+ ];
+
+ return $sensitiveValues[$app] ?? [];
+ }
+
+ /**
* Clear all the cached app config values
* New cache will be generated next time a config value is retrieved
+ *
+ * @deprecated use {@see clearCache()}
*/
public function clearCachedConfig(): void {
- $this->configLoaded = false;
+ $this->clearCache();
}
}
diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php
index ffd77da888e..b18c95a2f0d 100644
--- a/lib/private/AppFramework/App.php
+++ b/lib/private/AppFramework/App.php
@@ -34,16 +34,16 @@ namespace OC\AppFramework;
use OC\AppFramework\DependencyInjection\DIContainer;
use OC\AppFramework\Http\Dispatcher;
use OC\AppFramework\Http\Request;
-use OCP\App\IAppManager;
-use OCP\Profiler\IProfiler;
use OC\Profiler\RoutingDataCollector;
-use OCP\AppFramework\QueryException;
+use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\ICallbackResponse;
use OCP\AppFramework\Http\IOutput;
+use OCP\AppFramework\QueryException;
use OCP\Diagnostics\IEventLogger;
use OCP\HintException;
use OCP\IRequest;
+use OCP\Profiler\IProfiler;
/**
* Entry point for every request in your app. You can consider this as your
@@ -257,7 +257,7 @@ class App {
* @param DIContainer $container an instance of a pimple container.
*/
public static function part(string $controllerName, string $methodName, array $urlParams,
- DIContainer $container) {
+ DIContainer $container) {
$container['urlParams'] = $urlParams;
$controller = $container[$controllerName];
diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php
index f41b734a25b..8526a3dc1a1 100644
--- a/lib/private/AppFramework/Bootstrap/Coordinator.php
+++ b/lib/private/AppFramework/Bootstrap/Coordinator.php
@@ -30,20 +30,20 @@ declare(strict_types=1);
namespace OC\AppFramework\Bootstrap;
-use OCP\Diagnostics\IEventLogger;
-use function class_exists;
-use function class_implements;
-use function in_array;
-use OC_App;
use OC\Support\CrashReport\Registry;
+use OC_App;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\QueryException;
use OCP\Dashboard\IManager;
+use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IServerContainer;
use Psr\Log\LoggerInterface;
use Throwable;
+use function class_exists;
+use function class_implements;
+use function in_array;
class Coordinator {
/** @var IServerContainer */
diff --git a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
index 2ad410be26f..12801e62763 100644
--- a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php
@@ -37,9 +37,9 @@ class EventListenerRegistration extends ServiceRegistration {
private $priority;
public function __construct(string $appId,
- string $event,
- string $service,
- int $priority) {
+ string $event,
+ string $service,
+ int $priority) {
parent::__construct($appId, $service);
$this->event = $event;
$this->priority = $priority;
diff --git a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
index b501a757abd..958f24cb600 100644
--- a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php
@@ -36,8 +36,8 @@ final class ParameterRegistration extends ARegistration {
private $value;
public function __construct(string $appId,
- string $name,
- $value) {
+ string $name,
+ $value) {
parent::__construct($appId);
$this->name = $name;
$this->value = $value;
diff --git a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
index 36c5cae7db3..e4d75f75bc8 100644
--- a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php
@@ -34,8 +34,8 @@ class PreviewProviderRegistration extends ServiceRegistration {
private $mimeTypeRegex;
public function __construct(string $appId,
- string $service,
- string $mimeTypeRegex) {
+ string $service,
+ string $mimeTypeRegex) {
parent::__construct($appId, $service);
$this->mimeTypeRegex = $mimeTypeRegex;
}
diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
index b3ef3ee65fb..120ee7ea9fa 100644
--- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php
+++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php
@@ -30,15 +30,6 @@ declare(strict_types=1);
namespace OC\AppFramework\Bootstrap;
use Closure;
-use OCP\Calendar\Resource\IBackend as IResourceBackend;
-use OCP\Calendar\Room\IBackend as IRoomBackend;
-use OCP\Collaboration\Reference\IReferenceProvider;
-use OCP\TextProcessing\IProvider as ITextProcessingProvider;
-use OCP\SpeechToText\ISpeechToTextProvider;
-use OCP\Talk\ITalkBackend;
-use OCP\Translation\ITranslationProvider;
-use RuntimeException;
-use function array_shift;
use OC\Support\CrashReport\Registry;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
@@ -46,7 +37,10 @@ use OCP\AppFramework\Middleware;
use OCP\AppFramework\Services\InitialStateProvider;
use OCP\Authentication\IAlternativeLogin;
use OCP\Calendar\ICalendarProvider;
+use OCP\Calendar\Resource\IBackend as IResourceBackend;
+use OCP\Calendar\Room\IBackend as IRoomBackend;
use OCP\Capabilities\ICapability;
+use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
use OCP\EventDispatcher\IEventDispatcher;
@@ -55,11 +49,18 @@ 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\SpeechToText\ISpeechToTextProvider;
use OCP\Support\CrashReport\IReporter;
+use OCP\Talk\ITalkBackend;
+use OCP\TextProcessing\IProvider as ITextProcessingProvider;
+use OCP\Translation\ITranslationProvider;
use OCP\UserMigration\IMigrator as IUserMigrator;
use Psr\Log\LoggerInterface;
+use RuntimeException;
use Throwable;
+use function array_shift;
class RegistrationContext {
/** @var ServiceRegistration<ICapability>[] */
@@ -137,17 +138,25 @@ class RegistrationContext {
/** @var ServiceRegistration<IReferenceProvider>[] */
private array $referenceProviders = [];
+ /** @var ServiceRegistration<\OCP\TextToImage\IProvider>[] */
+ private $textToImageProviders = [];
+
+
+
+
/** @var ParameterRegistration[] */
private $sensitiveMethods = [];
/** @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;
@@ -270,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,
@@ -369,6 +385,13 @@ class RegistrationContext {
$class
);
}
+
+ public function registerSetupCheck(string $setupCheckClass): void {
+ $this->context->registerSetupCheck(
+ $this->appId,
+ $setupCheckClass
+ );
+ }
};
}
@@ -440,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);
}
@@ -521,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 {
@@ -723,6 +757,13 @@ class RegistrationContext {
}
/**
+ * @return ServiceRegistration<\OCP\TextToImage\IProvider>[]
+ */
+ public function getTextToImageProviders(): array {
+ return $this->textToImageProviders;
+ }
+
+ /**
* @return ServiceRegistration<ICustomTemplateProvider>[]
*/
public function getTemplateProviders(): array {
@@ -822,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/Bootstrap/ServiceAliasRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
index e2b115e0353..62c7169a7ee 100644
--- a/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php
@@ -46,8 +46,8 @@ class ServiceAliasRegistration extends ARegistration {
* @paslm-param string|class-string $target
*/
public function __construct(string $appId,
- string $alias,
- string $target) {
+ string $alias,
+ string $target) {
parent::__construct($appId);
$this->alias = $alias;
$this->target = $target;
diff --git a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
index b6658e55239..9d166526d94 100644
--- a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
+++ b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php
@@ -45,9 +45,9 @@ class ServiceFactoryRegistration extends ARegistration {
private $shared;
public function __construct(string $appId,
- string $alias,
- callable $target,
- bool $shared) {
+ string $alias,
+ callable $target,
+ bool $shared) {
parent::__construct($appId);
$this->name = $alias;
$this->factory = $target;
diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php
index 13b391eb287..6e946f2bfa3 100644
--- a/lib/private/AppFramework/Http/Dispatcher.php
+++ b/lib/private/AppFramework/Http/Dispatcher.php
@@ -38,6 +38,7 @@ use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\DB\ConnectionAdapter;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\ParameterOutOfRangeException;
use OCP\AppFramework\Http\Response;
use OCP\Diagnostics\IEventLogger;
use OCP\IConfig;
@@ -88,14 +89,14 @@ class Dispatcher {
* @param IEventLogger $eventLogger
*/
public function __construct(Http $protocol,
- MiddlewareDispatcher $middlewareDispatcher,
- ControllerMethodReflector $reflector,
- IRequest $request,
- IConfig $config,
- ConnectionAdapter $connection,
- LoggerInterface $logger,
- IEventLogger $eventLogger,
- ContainerInterface $appContainer) {
+ MiddlewareDispatcher $middlewareDispatcher,
+ ControllerMethodReflector $reflector,
+ IRequest $request,
+ IConfig $config,
+ ConnectionAdapter $connection,
+ LoggerInterface $logger,
+ IEventLogger $eventLogger,
+ ContainerInterface $appContainer) {
$this->protocol = $protocol;
$this->middlewareDispatcher = $middlewareDispatcher;
$this->reflector = $reflector;
@@ -197,7 +198,7 @@ class Dispatcher {
private function executeController(Controller $controller, string $methodName): Response {
$arguments = [];
- // valid types that will be casted
+ // valid types that will be cast
$types = ['int', 'integer', 'bool', 'boolean', 'float', 'double'];
foreach ($this->reflector->getParameters() as $param => $default) {
@@ -219,6 +220,7 @@ class Dispatcher {
$value = false;
} elseif ($value !== null && \in_array($type, $types, true)) {
settype($value, $type);
+ $this->ensureParameterValueSatisfiesRange($param, $value);
} elseif ($value === null && $type !== null && $this->appContainer->has($type)) {
$value = $this->appContainer->get($type);
}
@@ -250,4 +252,22 @@ class Dispatcher {
return $response;
}
+
+ /**
+ * @psalm-param mixed $value
+ * @throws ParameterOutOfRangeException
+ */
+ private function ensureParameterValueSatisfiesRange(string $param, $value): void {
+ $rangeInfo = $this->reflector->getRange($param);
+ if ($rangeInfo) {
+ if ($value < $rangeInfo['min'] || $value > $rangeInfo['max']) {
+ throw new ParameterOutOfRangeException(
+ $param,
+ $value,
+ $rangeInfo['min'],
+ $rangeInfo['max'],
+ );
+ }
+ }
+ }
}
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
index 26a76e0da27..e913c83fa8d 100644
--- a/lib/private/AppFramework/Http/Request.php
+++ b/lib/private/AppFramework/Http/Request.php
@@ -118,10 +118,10 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* @see https://www.php.net/manual/en/reserved.variables.php
*/
public function __construct(array $vars,
- IRequestId $requestId,
- IConfig $config,
- CsrfTokenManager $csrfTokenManager = null,
- string $stream = 'php://input') {
+ IRequestId $requestId,
+ IConfig $config,
+ CsrfTokenManager $csrfTokenManager = null,
+ string $stream = 'php://input') {
$this->inputStream = $stream;
$this->items['params'] = [];
$this->requestId = $requestId;
@@ -573,7 +573,14 @@ class Request implements \ArrayAccess, \Countable, IRequest {
* @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
*/
protected function isTrustedProxy($trustedProxies, $remoteAddress) {
- return IpUtils::checkIp($remoteAddress, $trustedProxies);
+ try {
+ return IpUtils::checkIp($remoteAddress, $trustedProxies);
+ } catch (\Throwable) {
+ // We can not log to our log here as the logger is using `getRemoteAddress` which uses the function, so we would have a cyclic dependency
+ // Reaching this line means `trustedProxies` is in invalid format.
+ error_log('Nextcloud trustedProxies has malformed entries');
+ return false;
+ }
}
/**
@@ -593,9 +600,11 @@ class Request implements \ArrayAccess, \Countable, IRequest {
// only have one default, so we cannot ship an insecure product out of the box
]);
- foreach ($forwardedForHeaders as $header) {
+ // Read the x-forwarded-for headers and values in reverse order as per
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address
+ foreach (array_reverse($forwardedForHeaders) as $header) {
if (isset($this->server[$header])) {
- foreach (explode(',', $this->server[$header]) as $IP) {
+ foreach (array_reverse(explode(',', $this->server[$header])) as $IP) {
$IP = trim($IP);
// remove brackets from IPv6 addresses
@@ -603,6 +612,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;
}
@@ -616,14 +629,12 @@ class Request implements \ArrayAccess, \Countable, IRequest {
/**
* Check overwrite condition
- * @param string $type
* @return bool
*/
- private function isOverwriteCondition(string $type = ''): bool {
+ private function isOverwriteCondition(): bool {
$regex = '/' . $this->config->getSystemValueString('overwritecondaddr', '') . '/';
$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
- return $regex === '//' || preg_match($regex, $remoteAddr) === 1
- || $type !== 'protocol';
+ return $regex === '//' || preg_match($regex, $remoteAddr) === 1;
}
/**
@@ -633,7 +644,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
*/
public function getServerProtocol(): string {
if ($this->config->getSystemValueString('overwriteprotocol') !== ''
- && $this->isOverwriteCondition('protocol')) {
+ && $this->isOverwriteCondition()) {
return $this->config->getSystemValueString('overwriteprotocol');
}
diff --git a/lib/private/AppFramework/Http/RequestId.php b/lib/private/AppFramework/Http/RequestId.php
index 70032873a75..a6b24c0a2ff 100644
--- a/lib/private/AppFramework/Http/RequestId.php
+++ b/lib/private/AppFramework/Http/RequestId.php
@@ -31,7 +31,7 @@ class RequestId implements IRequestId {
protected string $requestId;
public function __construct(string $uniqueId,
- ISecureRandom $secureRandom) {
+ ISecureRandom $secureRandom) {
$this->requestId = $uniqueId;
$this->secureRandom = $secureRandom;
}
diff --git a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
index 35eb0098eed..e129f70aef6 100644
--- a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
+++ b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php
@@ -40,15 +40,15 @@ use OCP\AppFramework\Middleware;
*/
class MiddlewareDispatcher {
/**
- * @var array array containing all the middlewares
+ * @var Middleware[] array containing all the middlewares
*/
- private $middlewares;
+ private array $middlewares;
/**
* @var int counter which tells us what middleware was executed once an
* exception occurs
*/
- private $middlewareCounter;
+ private int $middlewareCounter;
/**
@@ -64,14 +64,14 @@ class MiddlewareDispatcher {
* Adds a new middleware
* @param Middleware $middleWare the middleware which will be added
*/
- public function registerMiddleware(Middleware $middleWare) {
+ public function registerMiddleware(Middleware $middleWare): void {
$this->middlewares[] = $middleWare;
}
/**
* returns an array with all middleware elements
- * @return array the middlewares
+ * @return Middleware[] the middlewares
*/
public function getMiddlewares(): array {
return $this->middlewares;
@@ -86,7 +86,7 @@ class MiddlewareDispatcher {
* @param string $methodName the name of the method that will be called on
* the controller
*/
- public function beforeController(Controller $controller, string $methodName) {
+ public function beforeController(Controller $controller, string $methodName): void {
// we need to count so that we know which middlewares we have to ask in
// case there is an exception
$middlewareCount = \count($this->middlewares);
diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
index f0d6ece8a93..fef9632487e 100644
--- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
@@ -59,9 +59,9 @@ class CORSMiddleware extends Middleware {
private $throttler;
public function __construct(IRequest $request,
- ControllerMethodReflector $reflector,
- Session $session,
- IThrottler $throttler) {
+ ControllerMethodReflector $reflector,
+ Session $session,
+ IThrottler $throttler) {
$this->request = $request;
$this->reflector = $reflector;
$this->session = $session;
diff --git a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
index ae0dc1f134e..60a7cef8fa1 100644
--- a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php
@@ -44,8 +44,8 @@ class CSPMiddleware extends Middleware {
private $csrfTokenManager;
public function __construct(ContentSecurityPolicyManager $policyManager,
- ContentSecurityPolicyNonceManager $cspNonceManager,
- CsrfTokenManager $csrfTokenManager) {
+ ContentSecurityPolicyNonceManager $cspNonceManager,
+ CsrfTokenManager $csrfTokenManager) {
$this->contentSecurityPolicyManager = $policyManager;
$this->cspNonceManager = $cspNonceManager;
$this->csrfTokenManager = $csrfTokenManager;
diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
index a72a7a40016..351f47ea924 100644
--- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
@@ -55,9 +55,9 @@ class PasswordConfirmationMiddleware extends Middleware {
* @param ITimeFactory $timeFactory
*/
public function __construct(ControllerMethodReflector $reflector,
- ISession $session,
- IUserSession $userSession,
- ITimeFactory $timeFactory) {
+ ISession $session,
+ IUserSession $userSession,
+ ITimeFactory $timeFactory) {
$this->reflector = $reflector;
$this->session = $session;
$this->userSession = $userSession;
diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
index e6d35dc66f2..870efdd44fa 100644
--- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php
@@ -38,7 +38,7 @@ class SameSiteCookieMiddleware extends Middleware {
private $reflector;
public function __construct(Request $request,
- ControllerMethodReflector $reflector) {
+ ControllerMethodReflector $reflector) {
$this->request = $request;
$this->reflector = $reflector;
}
diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
index db6c7a02c77..a97876fd9ab 100644
--- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
@@ -104,18 +104,18 @@ class SecurityMiddleware extends Middleware {
private $userSession;
public function __construct(IRequest $request,
- ControllerMethodReflector $reflector,
- INavigationManager $navigationManager,
- IURLGenerator $urlGenerator,
- LoggerInterface $logger,
- string $appName,
- bool $isLoggedIn,
- bool $isAdminUser,
- bool $isSubAdmin,
- IAppManager $appManager,
- IL10N $l10n,
- AuthorizedGroupMapper $mapper,
- IUserSession $userSession
+ ControllerMethodReflector $reflector,
+ INavigationManager $navigationManager,
+ IURLGenerator $urlGenerator,
+ LoggerInterface $logger,
+ string $appName,
+ bool $isLoggedIn,
+ bool $isAdminUser,
+ bool $isSubAdmin,
+ IAppManager $appManager,
+ IL10N $l10n,
+ AuthorizedGroupMapper $mapper,
+ IUserSession $userSession
) {
$this->navigationManager = $navigationManager;
$this->request = $request;
diff --git a/lib/private/AppFramework/Middleware/SessionMiddleware.php b/lib/private/AppFramework/Middleware/SessionMiddleware.php
index 39f85915901..0acdcf8b7ef 100644
--- a/lib/private/AppFramework/Middleware/SessionMiddleware.php
+++ b/lib/private/AppFramework/Middleware/SessionMiddleware.php
@@ -44,7 +44,7 @@ class SessionMiddleware extends Middleware {
private $session;
public function __construct(ControllerMethodReflector $reflector,
- ISession $session) {
+ ISession $session) {
$this->reflector = $reflector;
$this->session = $session;
}
diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php
index 123b73d302c..3cfe8177ae7 100644
--- a/lib/private/AppFramework/OCS/BaseResponse.php
+++ b/lib/private/AppFramework/OCS/BaseResponse.php
@@ -64,10 +64,10 @@ abstract class BaseResponse extends Response {
* @param int|null $itemsPerPage
*/
public function __construct(DataResponse $dataResponse,
- $format = 'xml',
- $statusMessage = null,
- $itemsCount = null,
- $itemsPerPage = null) {
+ $format = 'xml',
+ $statusMessage = null,
+ $itemsCount = null,
+ $itemsPerPage = null) {
parent::__construct();
$this->format = $format;
diff --git a/lib/private/AppFramework/ScopedPsrLogger.php b/lib/private/AppFramework/ScopedPsrLogger.php
index 4ed91cdb6c0..1cb58da11ef 100644
--- a/lib/private/AppFramework/ScopedPsrLogger.php
+++ b/lib/private/AppFramework/ScopedPsrLogger.php
@@ -37,7 +37,7 @@ class ScopedPsrLogger implements LoggerInterface {
private $appId;
public function __construct(LoggerInterface $inner,
- string $appId) {
+ string $appId) {
$this->inner = $inner;
$this->appId = $appId;
}
diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
index b76b3c33c42..5a1ed0fd6ee 100644
--- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php
+++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php
@@ -42,6 +42,7 @@ class ControllerMethodReflector implements IControllerMethodReflector {
public $annotations = [];
private $types = [];
private $parameters = [];
+ private array $ranges = [];
/**
* @param object $object an object or classname
@@ -54,26 +55,38 @@ class ControllerMethodReflector implements IControllerMethodReflector {
if ($docs !== false) {
// extract everything prefixed by @ and first letter uppercase
preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)((?P<parameter>.*))?$/m', $docs, $matches);
- foreach ($matches['annotation'] as $key => $annontation) {
- $annontation = strtolower($annontation);
+ foreach ($matches['annotation'] as $key => $annotation) {
+ $annotation = strtolower($annotation);
$annotationValue = $matches['parameter'][$key];
if (isset($annotationValue[0]) && $annotationValue[0] === '(' && $annotationValue[\strlen($annotationValue) - 1] === ')') {
$cutString = substr($annotationValue, 1, -1);
$cutString = str_replace(' ', '', $cutString);
- $splittedArray = explode(',', $cutString);
- foreach ($splittedArray as $annotationValues) {
+ $splitArray = explode(',', $cutString);
+ foreach ($splitArray as $annotationValues) {
[$key, $value] = explode('=', $annotationValues);
- $this->annotations[$annontation][$key] = $value;
+ $this->annotations[$annotation][$key] = $value;
}
continue;
}
- $this->annotations[$annontation] = [$annotationValue];
+ $this->annotations[$annotation] = [$annotationValue];
}
// extract type parameter information
preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches);
$this->types = array_combine($matches['var'], $matches['type']);
+ preg_match_all('/@psalm-param\h+(?P<type>\w+)<(?P<rangeMin>(-?\d+|min)),\h*(?P<rangeMax>(-?\d+|max))>\h+\$(?P<var>\w+)/', $docs, $matches);
+ foreach ($matches['var'] as $index => $varName) {
+ if ($matches['type'][$index] !== 'int') {
+ // only int ranges are possible at the moment
+ // @see https://psalm.dev/docs/annotating_code/type_syntax/scalar_types
+ continue;
+ }
+ $this->ranges[$varName] = [
+ 'min' => $matches['rangeMin'][$index] === 'min' ? PHP_INT_MIN : (int)$matches['rangeMin'][$index],
+ 'max' => $matches['rangeMax'][$index] === 'max' ? PHP_INT_MAX : (int)$matches['rangeMax'][$index],
+ ];
+ }
}
foreach ($reflection->getParameters() as $param) {
@@ -106,6 +119,14 @@ class ControllerMethodReflector implements IControllerMethodReflector {
return null;
}
+ public function getRange(string $parameter): ?array {
+ if (array_key_exists($parameter, $this->ranges)) {
+ return $this->ranges[$parameter];
+ }
+
+ return null;
+ }
+
/**
* @return array the arguments of the method with key => default value
*/
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index 7aa5cb83926..83aed4381b3 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -37,8 +37,8 @@ use Pimple\Container;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
-use ReflectionParameter;
use ReflectionNamedType;
+use ReflectionParameter;
use function class_exists;
/**
@@ -105,6 +105,11 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
try {
return $this->query($resolveName);
} catch (QueryException $e2) {
+ // Pass null if typed and nullable
+ if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) {
+ return null;
+ }
+
// don't lose the error we got while trying to query by type
throw new QueryException($e->getMessage(), (int) $e->getCode(), $e);
}
diff --git a/lib/private/AppScriptSort.php b/lib/private/AppScriptSort.php
index c42d02d485d..2e36034d04f 100644
--- a/lib/private/AppScriptSort.php
+++ b/lib/private/AppScriptSort.php
@@ -46,10 +46,10 @@ class AppScriptSort {
* @param array $sortedScriptDeps
*/
private function topSortVisit(
- AppScriptDependency $app,
- array &$parents,
- array &$scriptDeps,
- array &$sortedScriptDeps): void {
+ AppScriptDependency $app,
+ array &$parents,
+ array &$scriptDeps,
+ array &$sortedScriptDeps): void {
// Detect and log circular dependencies
if (isset($parents[$app->getId()])) {
$this->logger->error('Circular dependency in app scripts at app ' . $app->getId());
diff --git a/lib/private/Archive/TAR.php b/lib/private/Archive/TAR.php
index 9dc906384e0..a6140e44eb6 100644
--- a/lib/private/Archive/TAR.php
+++ b/lib/private/Archive/TAR.php
@@ -197,7 +197,7 @@ class TAR extends Archive {
if ($pos = strpos($result, '/')) {
$result = substr($result, 0, $pos + 1);
}
- if (array_search($result, $folderContent) === false) {
+ if (!in_array($result, $folderContent)) {
$folderContent[] = $result;
}
}
@@ -269,7 +269,7 @@ class TAR extends Archive {
*/
public function fileExists(string $path): bool {
$files = $this->getFiles();
- if ((array_search($path, $files) !== false) or (array_search($path . '/', $files) !== false)) {
+ if ((in_array($path, $files)) or (in_array($path . '/', $files))) {
return true;
} else {
$folderPath = rtrim($path, '/') . '/';
diff --git a/lib/private/Authentication/Events/AppPasswordCreatedEvent.php b/lib/private/Authentication/Events/AppPasswordCreatedEvent.php
index a90abd25026..068c0a73277 100644
--- a/lib/private/Authentication/Events/AppPasswordCreatedEvent.php
+++ b/lib/private/Authentication/Events/AppPasswordCreatedEvent.php
@@ -25,16 +25,14 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Events;
-use OC\Authentication\Token\IToken;
+use OCP\Authentication\Token\IToken;
use OCP\EventDispatcher\Event;
class AppPasswordCreatedEvent extends Event {
- /** @var IToken */
- private $token;
-
- public function __construct(IToken $token) {
+ public function __construct(
+ private IToken $token,
+ ) {
parent::__construct();
- $this->token = $token;
}
public function getToken(): IToken {
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/Listeners/RemoteWipeActivityListener.php b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php
index edebb2a2641..3e8348f075a 100644
--- a/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php
+++ b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php
@@ -46,7 +46,7 @@ class RemoteWipeActivityListener implements IEventListener {
private $logger;
public function __construct(IActvityManager $activityManager,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->activityManager = $activityManager;
$this->logger = $logger;
}
diff --git a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php
index cba2b183589..fb3f771d1e4 100644
--- a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php
+++ b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php
@@ -57,9 +57,9 @@ class RemoteWipeEmailListener implements IEventListener {
private $logger;
public function __construct(IMailer $mailer,
- IUserManager $userManager,
- IL10nFactory $l10nFactory,
- LoggerInterface $logger) {
+ IUserManager $userManager,
+ IL10nFactory $l10nFactory,
+ LoggerInterface $logger) {
$this->mailer = $mailer;
$this->userManager = $userManager;
$this->l10n = $l10nFactory->get('core');
diff --git a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
index 81feab32746..37732ecf5f2 100644
--- a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
+++ b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
@@ -45,7 +45,7 @@ class RemoteWipeNotificationsListener implements IEventListener {
private $timeFactory;
public function __construct(INotificationManager $notificationManager,
- ITimeFactory $timeFactory) {
+ ITimeFactory $timeFactory) {
$this->notificationManager = $notificationManager;
$this->timeFactory = $timeFactory;
}
diff --git a/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php
index 5e657be0763..c1075c0901e 100644
--- a/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php
+++ b/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php
@@ -34,6 +34,7 @@ use OCP\Files\Storage\IStorage;
use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\User\Events\UserDeletedEvent;
+/** @template-implements IEventListener<BeforeUserDeletedEvent|UserDeletedEvent> */
class UserDeletedFilesCleanupListener implements IEventListener {
/** @var array<string,IStorage> */
private $homeStorageCache = [];
diff --git a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php
index a09a08568d5..f4f08a50add 100644
--- a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php
+++ b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php
@@ -44,7 +44,7 @@ class UserDeletedTokenCleanupListener implements IEventListener {
private $logger;
public function __construct(Manager $manager,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->manager = $manager;
$this->logger = $logger;
}
diff --git a/lib/private/Authentication/Listeners/UserDeletedWebAuthnCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedWebAuthnCleanupListener.php
index 4927c3ac7f9..26db8921016 100644
--- a/lib/private/Authentication/Listeners/UserDeletedWebAuthnCleanupListener.php
+++ b/lib/private/Authentication/Listeners/UserDeletedWebAuthnCleanupListener.php
@@ -31,6 +31,7 @@ use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\UserDeletedEvent;
+/** @template-implements IEventListener<UserDeletedEvent> */
class UserDeletedWebAuthnCleanupListener implements IEventListener {
/** @var PublicKeyCredentialMapper */
private $credentialMapper;
diff --git a/lib/private/Authentication/Login/Chain.php b/lib/private/Authentication/Login/Chain.php
index 3c3179472c4..60ecd004388 100644
--- a/lib/private/Authentication/Login/Chain.php
+++ b/lib/private/Authentication/Login/Chain.php
@@ -63,17 +63,17 @@ class Chain {
private $finishRememberedLoginCommand;
public function __construct(PreLoginHookCommand $preLoginHookCommand,
- UserDisabledCheckCommand $userDisabledCheckCommand,
- UidLoginCommand $uidLoginCommand,
- EmailLoginCommand $emailLoginCommand,
- LoggedInCheckCommand $loggedInCheckCommand,
- CompleteLoginCommand $completeLoginCommand,
- CreateSessionTokenCommand $createSessionTokenCommand,
- ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
- UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
- SetUserTimezoneCommand $setUserTimezoneCommand,
- TwoFactorCommand $twoFactorCommand,
- FinishRememberedLoginCommand $finishRememberedLoginCommand
+ UserDisabledCheckCommand $userDisabledCheckCommand,
+ UidLoginCommand $uidLoginCommand,
+ EmailLoginCommand $emailLoginCommand,
+ LoggedInCheckCommand $loggedInCheckCommand,
+ CompleteLoginCommand $completeLoginCommand,
+ CreateSessionTokenCommand $createSessionTokenCommand,
+ ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
+ UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
+ SetUserTimezoneCommand $setUserTimezoneCommand,
+ TwoFactorCommand $twoFactorCommand,
+ FinishRememberedLoginCommand $finishRememberedLoginCommand
) {
$this->preLoginHookCommand = $preLoginHookCommand;
$this->userDisabledCheckCommand = $userDisabledCheckCommand;
diff --git a/lib/private/Authentication/Login/CreateSessionTokenCommand.php b/lib/private/Authentication/Login/CreateSessionTokenCommand.php
index ba237dfbf20..41616e6dad3 100644
--- a/lib/private/Authentication/Login/CreateSessionTokenCommand.php
+++ b/lib/private/Authentication/Login/CreateSessionTokenCommand.php
@@ -39,7 +39,7 @@ class CreateSessionTokenCommand extends ALoginCommand {
private $userSession;
public function __construct(IConfig $config,
- Session $userSession) {
+ Session $userSession) {
$this->config = $config;
$this->userSession = $userSession;
}
diff --git a/lib/private/Authentication/Login/LoggedInCheckCommand.php b/lib/private/Authentication/Login/LoggedInCheckCommand.php
index dc1a4d2d883..6b241d79746 100644
--- a/lib/private/Authentication/Login/LoggedInCheckCommand.php
+++ b/lib/private/Authentication/Login/LoggedInCheckCommand.php
@@ -39,7 +39,7 @@ class LoggedInCheckCommand extends ALoginCommand {
private $dispatcher;
public function __construct(LoggerInterface $logger,
- IEventDispatcher $dispatcher) {
+ IEventDispatcher $dispatcher) {
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
diff --git a/lib/private/Authentication/Login/LoginData.php b/lib/private/Authentication/Login/LoginData.php
index 240a1dc6476..0ce11cf70fc 100644
--- a/lib/private/Authentication/Login/LoginData.php
+++ b/lib/private/Authentication/Login/LoginData.php
@@ -55,11 +55,11 @@ class LoginData {
private $rememberLogin = true;
public function __construct(IRequest $request,
- string $username,
- ?string $password,
- string $redirectUrl = null,
- string $timeZone = '',
- string $timeZoneOffset = '') {
+ string $username,
+ ?string $password,
+ string $redirectUrl = null,
+ string $timeZone = '',
+ string $timeZoneOffset = '') {
$this->request = $request;
$this->username = $username;
$this->password = $password;
diff --git a/lib/private/Authentication/Login/LoginResult.php b/lib/private/Authentication/Login/LoginResult.php
index dec012c2fc9..18820d98a47 100644
--- a/lib/private/Authentication/Login/LoginResult.php
+++ b/lib/private/Authentication/Login/LoginResult.php
@@ -25,6 +25,8 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Login;
+use OC\Core\Controller\LoginController;
+
class LoginResult {
/** @var bool */
private $success;
@@ -59,6 +61,9 @@ class LoginResult {
return $result;
}
+ /**
+ * @param LoginController::LOGIN_MSG_*|null $msg
+ */
public static function failure(LoginData $data, string $msg = null): LoginResult {
$result = new static(false, $data);
if ($msg !== null) {
diff --git a/lib/private/Authentication/Login/SetUserTimezoneCommand.php b/lib/private/Authentication/Login/SetUserTimezoneCommand.php
index f68fce1771e..881e1c451a9 100644
--- a/lib/private/Authentication/Login/SetUserTimezoneCommand.php
+++ b/lib/private/Authentication/Login/SetUserTimezoneCommand.php
@@ -36,7 +36,7 @@ class SetUserTimezoneCommand extends ALoginCommand {
private $session;
public function __construct(IConfig $config,
- ISession $session) {
+ ISession $session) {
$this->config = $config;
$this->session = $session;
}
diff --git a/lib/private/Authentication/Login/TwoFactorCommand.php b/lib/private/Authentication/Login/TwoFactorCommand.php
index 256d88ffa81..aa5a2ff96f4 100644
--- a/lib/private/Authentication/Login/TwoFactorCommand.php
+++ b/lib/private/Authentication/Login/TwoFactorCommand.php
@@ -26,12 +26,12 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Login;
-use function array_pop;
-use function count;
use OC\Authentication\TwoFactorAuth\Manager;
use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\IURLGenerator;
+use function array_pop;
+use function count;
class TwoFactorCommand extends ALoginCommand {
/** @var Manager */
@@ -44,8 +44,8 @@ class TwoFactorCommand extends ALoginCommand {
private $urlGenerator;
public function __construct(Manager $twoFactorManager,
- MandatoryTwoFactor $mandatoryTwoFactor,
- IURLGenerator $urlGenerator) {
+ MandatoryTwoFactor $mandatoryTwoFactor,
+ IURLGenerator $urlGenerator) {
$this->twoFactorManager = $twoFactorManager;
$this->mandatoryTwoFactor = $mandatoryTwoFactor;
$this->urlGenerator = $urlGenerator;
diff --git a/lib/private/Authentication/Login/UserDisabledCheckCommand.php b/lib/private/Authentication/Login/UserDisabledCheckCommand.php
index 7cf4c7235ec..8354457b56a 100644
--- a/lib/private/Authentication/Login/UserDisabledCheckCommand.php
+++ b/lib/private/Authentication/Login/UserDisabledCheckCommand.php
@@ -38,7 +38,7 @@ class UserDisabledCheckCommand extends ALoginCommand {
private $logger;
public function __construct(IUserManager $userManager,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->userManager = $userManager;
$this->logger = $logger;
}
diff --git a/lib/private/Authentication/Login/WebAuthnChain.php b/lib/private/Authentication/Login/WebAuthnChain.php
index f3ebc313a44..d0fcf691d46 100644
--- a/lib/private/Authentication/Login/WebAuthnChain.php
+++ b/lib/private/Authentication/Login/WebAuthnChain.php
@@ -57,15 +57,15 @@ class WebAuthnChain {
private $webAuthnLoginCommand;
public function __construct(UserDisabledCheckCommand $userDisabledCheckCommand,
- WebAuthnLoginCommand $webAuthnLoginCommand,
- LoggedInCheckCommand $loggedInCheckCommand,
- CompleteLoginCommand $completeLoginCommand,
- CreateSessionTokenCommand $createSessionTokenCommand,
- ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
- UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
- SetUserTimezoneCommand $setUserTimezoneCommand,
- TwoFactorCommand $twoFactorCommand,
- FinishRememberedLoginCommand $finishRememberedLoginCommand
+ WebAuthnLoginCommand $webAuthnLoginCommand,
+ LoggedInCheckCommand $loggedInCheckCommand,
+ CompleteLoginCommand $completeLoginCommand,
+ CreateSessionTokenCommand $createSessionTokenCommand,
+ ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
+ UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
+ SetUserTimezoneCommand $setUserTimezoneCommand,
+ TwoFactorCommand $twoFactorCommand,
+ FinishRememberedLoginCommand $finishRememberedLoginCommand
) {
$this->userDisabledCheckCommand = $userDisabledCheckCommand;
$this->webAuthnLoginCommand = $webAuthnLoginCommand;
diff --git a/lib/private/Authentication/LoginCredentials/Store.php b/lib/private/Authentication/LoginCredentials/Store.php
index 3a09e983ee8..2e00ac211c1 100644
--- a/lib/private/Authentication/LoginCredentials/Store.php
+++ b/lib/private/Authentication/LoginCredentials/Store.php
@@ -26,10 +26,10 @@ declare(strict_types=1);
*/
namespace OC\Authentication\LoginCredentials;
-use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\IProvider;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\LoginCredentials\ICredentials;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\ISession;
@@ -48,8 +48,8 @@ class Store implements IStore {
private $tokenProvider;
public function __construct(ISession $session,
- LoggerInterface $logger,
- IProvider $tokenProvider = null) {
+ LoggerInterface $logger,
+ IProvider $tokenProvider = null) {
$this->session = $session;
$this->logger = $logger;
$this->tokenProvider = $tokenProvider;
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index a12d3ba34d9..fcec8cecac1 100644
--- a/lib/private/Authentication/Token/IProvider.php
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -29,10 +29,11 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Token;
-use OC\Authentication\Exceptions\ExpiredTokenException;
-use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
-use OC\Authentication\Exceptions\WipeTokenException;
+use OCP\Authentication\Exceptions\ExpiredTokenException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\Authentication\Exceptions\WipeTokenException;
+use OCP\Authentication\Token\IToken as OCPIToken;
interface IProvider {
/**
@@ -45,16 +46,16 @@ interface IProvider {
* @param string $name Name will be trimmed to 120 chars when longer
* @param int $type token type
* @param int $remember whether the session token should be used for remember-me
- * @return IToken
+ * @return OCPIToken
* @throws \RuntimeException when OpenSSL reports a problem
*/
public function generateToken(string $token,
- string $uid,
- string $loginName,
- ?string $password,
- string $name,
- int $type = IToken::TEMPORARY_TOKEN,
- int $remember = IToken::DO_NOT_REMEMBER): IToken;
+ string $uid,
+ string $loginName,
+ ?string $password,
+ string $name,
+ int $type = OCPIToken::TEMPORARY_TOKEN,
+ int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken;
/**
* Get a token by token id
@@ -63,9 +64,9 @@ interface IProvider {
* @throws InvalidTokenException
* @throws ExpiredTokenException
* @throws WipeTokenException
- * @return IToken
+ * @return OCPIToken
*/
- public function getToken(string $tokenId): IToken;
+ public function getToken(string $tokenId): OCPIToken;
/**
* Get a token by token id
@@ -74,9 +75,9 @@ interface IProvider {
* @throws InvalidTokenException
* @throws ExpiredTokenException
* @throws WipeTokenException
- * @return IToken
+ * @return OCPIToken
*/
- public function getTokenById(int $tokenId): IToken;
+ public function getTokenById(int $tokenId): OCPIToken;
/**
* Duplicate an existing session token
@@ -85,9 +86,9 @@ interface IProvider {
* @param string $sessionId
* @throws InvalidTokenException
* @throws \RuntimeException when OpenSSL reports a problem
- * @return IToken The new token
+ * @return OCPIToken The new token
*/
- public function renewSessionToken(string $oldSessionId, string $sessionId): IToken;
+ public function renewSessionToken(string $oldSessionId, string $sessionId): OCPIToken;
/**
* Invalidate (delete) the given session token
@@ -117,16 +118,16 @@ interface IProvider {
/**
* Save the updated token
*
- * @param IToken $token
+ * @param OCPIToken $token
*/
- public function updateToken(IToken $token);
+ public function updateToken(OCPIToken $token);
/**
* Update token activity timestamp
*
- * @param IToken $token
+ * @param OCPIToken $token
*/
- public function updateTokenActivity(IToken $token);
+ public function updateTokenActivity(OCPIToken $token);
/**
* Get all tokens of a user
@@ -135,49 +136,49 @@ interface IProvider {
* where a high number of (session) tokens is generated
*
* @param string $uid
- * @return IToken[]
+ * @return OCPIToken[]
*/
public function getTokenByUser(string $uid): array;
/**
* Get the (unencrypted) password of the given token
*
- * @param IToken $savedToken
+ * @param OCPIToken $savedToken
* @param string $tokenId
* @throws InvalidTokenException
* @throws PasswordlessTokenException
* @return string
*/
- public function getPassword(IToken $savedToken, string $tokenId): string;
+ public function getPassword(OCPIToken $savedToken, string $tokenId): string;
/**
* Encrypt and set the password of the given token
*
- * @param IToken $token
+ * @param OCPIToken $token
* @param string $tokenId
* @param string $password
* @throws InvalidTokenException
*/
- public function setPassword(IToken $token, string $tokenId, string $password);
+ public function setPassword(OCPIToken $token, string $tokenId, string $password);
/**
* Rotate the token. Useful for for example oauth tokens
*
- * @param IToken $token
+ * @param OCPIToken $token
* @param string $oldTokenId
* @param string $newTokenId
- * @return IToken
+ * @return OCPIToken
* @throws \RuntimeException when OpenSSL reports a problem
*/
- public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken;
+ public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken;
/**
* Marks a token as having an invalid password.
*
- * @param IToken $token
+ * @param OCPIToken $token
* @param string $tokenId
*/
- public function markPasswordInvalid(IToken $token, string $tokenId);
+ public function markPasswordInvalid(OCPIToken $token, string $tokenId);
/**
* Update all the passwords of $uid if required
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/Manager.php b/lib/private/Authentication/Token/Manager.php
index 6a1c7d4c1e7..e0b0e2dd14b 100644
--- a/lib/private/Authentication/Token/Manager.php
+++ b/lib/private/Authentication/Token/Manager.php
@@ -28,11 +28,13 @@ declare(strict_types=1);
namespace OC\Authentication\Token;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
-use OC\Authentication\Exceptions\ExpiredTokenException;
-use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Exceptions\InvalidTokenException as OcInvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
-use OC\Authentication\Exceptions\WipeTokenException;
+use OCP\Authentication\Exceptions\ExpiredTokenException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\Authentication\Exceptions\WipeTokenException;
use OCP\Authentication\Token\IProvider as OCPIProvider;
+use OCP\Authentication\Token\IToken as OCPIToken;
class Manager implements IProvider, OCPIProvider {
/** @var PublicKeyTokenProvider */
@@ -52,15 +54,15 @@ class Manager implements IProvider, OCPIProvider {
* @param string $name Name will be trimmed to 120 chars when longer
* @param int $type token type
* @param int $remember whether the session token should be used for remember-me
- * @return IToken
+ * @return OCPIToken
*/
public function generateToken(string $token,
- string $uid,
- string $loginName,
- $password,
- string $name,
- int $type = IToken::TEMPORARY_TOKEN,
- int $remember = IToken::DO_NOT_REMEMBER): IToken {
+ string $uid,
+ string $loginName,
+ $password,
+ string $name,
+ int $type = OCPIToken::TEMPORARY_TOKEN,
+ int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken {
if (mb_strlen($name) > 128) {
$name = mb_substr($name, 0, 120) . '…';
}
@@ -93,10 +95,10 @@ class Manager implements IProvider, OCPIProvider {
/**
* Save the updated token
*
- * @param IToken $token
+ * @param OCPIToken $token
* @throws InvalidTokenException
*/
- public function updateToken(IToken $token) {
+ public function updateToken(OCPIToken $token) {
$provider = $this->getProvider($token);
$provider->updateToken($token);
}
@@ -105,16 +107,16 @@ class Manager implements IProvider, OCPIProvider {
* Update token activity timestamp
*
* @throws InvalidTokenException
- * @param IToken $token
+ * @param OCPIToken $token
*/
- public function updateTokenActivity(IToken $token) {
+ public function updateTokenActivity(OCPIToken $token) {
$provider = $this->getProvider($token);
$provider->updateTokenActivity($token);
}
/**
* @param string $uid
- * @return IToken[]
+ * @return OCPIToken[]
*/
public function getTokenByUser(string $uid): array {
return $this->publicKeyTokenProvider->getTokenByUser($uid);
@@ -126,9 +128,9 @@ class Manager implements IProvider, OCPIProvider {
* @param string $tokenId
* @throws InvalidTokenException
* @throws \RuntimeException when OpenSSL reports a problem
- * @return IToken
+ * @return OCPIToken
*/
- public function getToken(string $tokenId): IToken {
+ public function getToken(string $tokenId): OCPIToken {
try {
return $this->publicKeyTokenProvider->getToken($tokenId);
} catch (WipeTokenException $e) {
@@ -145,9 +147,9 @@ class Manager implements IProvider, OCPIProvider {
*
* @param int $tokenId
* @throws InvalidTokenException
- * @return IToken
+ * @return OCPIToken
*/
- public function getTokenById(int $tokenId): IToken {
+ public function getTokenById(int $tokenId): OCPIToken {
try {
return $this->publicKeyTokenProvider->getTokenById($tokenId);
} catch (ExpiredTokenException $e) {
@@ -163,9 +165,9 @@ class Manager implements IProvider, OCPIProvider {
* @param string $oldSessionId
* @param string $sessionId
* @throws InvalidTokenException
- * @return IToken
+ * @return OCPIToken
*/
- public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
+ public function renewSessionToken(string $oldSessionId, string $sessionId): OCPIToken {
try {
return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId);
} catch (ExpiredTokenException $e) {
@@ -176,18 +178,18 @@ class Manager implements IProvider, OCPIProvider {
}
/**
- * @param IToken $savedToken
+ * @param OCPIToken $savedToken
* @param string $tokenId session token
* @throws InvalidTokenException
* @throws PasswordlessTokenException
* @return string
*/
- public function getPassword(IToken $savedToken, string $tokenId): string {
+ public function getPassword(OCPIToken $savedToken, string $tokenId): string {
$provider = $this->getProvider($savedToken);
return $provider->getPassword($savedToken, $tokenId);
}
- public function setPassword(IToken $token, string $tokenId, string $password) {
+ public function setPassword(OCPIToken $token, string $tokenId, string $password) {
$provider = $this->getProvider($token);
$provider->setPassword($token, $tokenId, $password);
}
@@ -209,35 +211,37 @@ class Manager implements IProvider, OCPIProvider {
}
/**
- * @param IToken $token
+ * @param OCPIToken $token
* @param string $oldTokenId
* @param string $newTokenId
- * @return IToken
+ * @return OCPIToken
* @throws InvalidTokenException
* @throws \RuntimeException when OpenSSL reports a problem
*/
- public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
+ public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken {
if ($token instanceof PublicKeyToken) {
return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId);
}
- throw new InvalidTokenException();
+ /** @psalm-suppress DeprecatedClass We have to throw the OC version so both OC and OCP catches catch it */
+ throw new OcInvalidTokenException();
}
/**
- * @param IToken $token
+ * @param OCPIToken $token
* @return IProvider
* @throws InvalidTokenException
*/
- private function getProvider(IToken $token): IProvider {
+ private function getProvider(OCPIToken $token): IProvider {
if ($token instanceof PublicKeyToken) {
return $this->publicKeyTokenProvider;
}
- throw new InvalidTokenException();
+ /** @psalm-suppress DeprecatedClass We have to throw the OC version so both OC and OCP catches catch it */
+ throw new OcInvalidTokenException();
}
- public function markPasswordInvalid(IToken $token, string $tokenId) {
+ public function markPasswordInvalid(OCPIToken $token, string $tokenId) {
$this->getProvider($token)->markPasswordInvalid($token, $tokenId);
}
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/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 3fb11611076..490f6830a85 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -31,13 +31,14 @@ namespace OC\Authentication\Token;
use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
-use OC\Authentication\Exceptions\TokenPasswordExpiredException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
+use OC\Authentication\Exceptions\TokenPasswordExpiredException;
use OC\Authentication\Exceptions\WipeTokenException;
-use OCP\AppFramework\Db\TTransactional;
-use OCP\Cache\CappedMemoryCache;
use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\TTransactional;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\Token\IToken as OCPIToken;
+use OCP\Cache\CappedMemoryCache;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUserManager;
@@ -73,12 +74,12 @@ class PublicKeyTokenProvider implements IProvider {
private IHasher $hasher;
public function __construct(PublicKeyTokenMapper $mapper,
- ICrypto $crypto,
- IConfig $config,
- IDBConnection $db,
- LoggerInterface $logger,
- ITimeFactory $time,
- IHasher $hasher) {
+ ICrypto $crypto,
+ IConfig $config,
+ IDBConnection $db,
+ LoggerInterface $logger,
+ ITimeFactory $time,
+ IHasher $hasher) {
$this->mapper = $mapper;
$this->crypto = $crypto;
$this->config = $config;
@@ -94,12 +95,12 @@ class PublicKeyTokenProvider implements IProvider {
* {@inheritDoc}
*/
public function generateToken(string $token,
- string $uid,
- string $loginName,
- ?string $password,
- string $name,
- int $type = IToken::TEMPORARY_TOKEN,
- int $remember = IToken::DO_NOT_REMEMBER): IToken {
+ string $uid,
+ string $loginName,
+ ?string $password,
+ string $name,
+ int $type = OCPIToken::TEMPORARY_TOKEN,
+ int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken {
if (strlen($token) < self::TOKEN_MIN_LENGTH) {
$exception = new InvalidTokenException('Token is too short, minimum of ' . self::TOKEN_MIN_LENGTH . ' characters is required, ' . strlen($token) . ' characters given');
$this->logger->error('Invalid token provided when generating new token', ['exception' => $exception]);
@@ -133,7 +134,7 @@ class PublicKeyTokenProvider implements IProvider {
return $dbToken;
}
- public function getToken(string $tokenId): IToken {
+ public function getToken(string $tokenId): OCPIToken {
/**
* Token length: 72
* @see \OC\Core\Controller\ClientFlowLoginController::generateAppPassword
@@ -183,7 +184,7 @@ class PublicKeyTokenProvider implements IProvider {
throw new ExpiredTokenException($token);
}
- if ($token->getType() === IToken::WIPE_TOKEN) {
+ if ($token->getType() === OCPIToken::WIPE_TOKEN) {
throw new WipeTokenException($token);
}
@@ -195,7 +196,7 @@ class PublicKeyTokenProvider implements IProvider {
return $token;
}
- public function getTokenById(int $tokenId): IToken {
+ public function getTokenById(int $tokenId): OCPIToken {
try {
$token = $this->mapper->getTokenById($tokenId);
} catch (DoesNotExistException $ex) {
@@ -206,7 +207,7 @@ class PublicKeyTokenProvider implements IProvider {
throw new ExpiredTokenException($token);
}
- if ($token->getType() === IToken::WIPE_TOKEN) {
+ if ($token->getType() === OCPIToken::WIPE_TOKEN) {
throw new WipeTokenException($token);
}
@@ -218,7 +219,7 @@ class PublicKeyTokenProvider implements IProvider {
return $token;
}
- public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
+ public function renewSessionToken(string $oldSessionId, string $sessionId): OCPIToken {
$this->cache->clear();
return $this->atomic(function () use ($oldSessionId, $sessionId) {
@@ -239,7 +240,7 @@ class PublicKeyTokenProvider implements IProvider {
$token->getLoginName(),
$password,
$token->getName(),
- IToken::TEMPORARY_TOKEN,
+ OCPIToken::TEMPORARY_TOKEN,
$token->getRemember()
);
@@ -267,10 +268,10 @@ class PublicKeyTokenProvider implements IProvider {
$olderThan = $this->time->getTime() - $this->config->getSystemValueInt('session_lifetime', 60 * 60 * 24);
$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
- $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
+ $this->mapper->invalidateOld($olderThan, OCPIToken::DO_NOT_REMEMBER);
$rememberThreshold = $this->time->getTime() - $this->config->getSystemValueInt('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
- $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
+ $this->mapper->invalidateOld($rememberThreshold, OCPIToken::REMEMBER);
}
public function invalidateLastUsedBefore(string $uid, int $before): void {
@@ -279,7 +280,7 @@ class PublicKeyTokenProvider implements IProvider {
$this->mapper->invalidateLastUsedBefore($uid, $before);
}
- public function updateToken(IToken $token) {
+ public function updateToken(OCPIToken $token) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
@@ -288,7 +289,7 @@ class PublicKeyTokenProvider implements IProvider {
$this->mapper->update($token);
}
- public function updateTokenActivity(IToken $token) {
+ public function updateTokenActivity(OCPIToken $token) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
@@ -310,7 +311,7 @@ class PublicKeyTokenProvider implements IProvider {
return $this->mapper->getTokenByUser($uid);
}
- public function getPassword(IToken $savedToken, string $tokenId): string {
+ public function getPassword(OCPIToken $savedToken, string $tokenId): string {
if (!($savedToken instanceof PublicKeyToken)) {
throw new InvalidTokenException("Invalid token type");
}
@@ -326,7 +327,7 @@ class PublicKeyTokenProvider implements IProvider {
return $this->decryptPassword($savedToken->getPassword(), $privateKey);
}
- public function setPassword(IToken $token, string $tokenId, string $password) {
+ public function setPassword(OCPIToken $token, string $tokenId, string $password) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
@@ -353,7 +354,7 @@ class PublicKeyTokenProvider implements IProvider {
return $this->hasher->hash(sha1($password) . $password);
}
- public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
+ public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
@@ -425,12 +426,12 @@ class PublicKeyTokenProvider implements IProvider {
* @throws \RuntimeException when OpenSSL reports a problem
*/
private function newToken(string $token,
- string $uid,
- string $loginName,
- $password,
- string $name,
- int $type,
- int $remember): PublicKeyToken {
+ string $uid,
+ string $loginName,
+ $password,
+ string $name,
+ int $type,
+ int $remember): PublicKeyToken {
$dbToken = new PublicKeyToken();
$dbToken->setUid($uid);
$dbToken->setLoginName($loginName);
@@ -478,7 +479,7 @@ class PublicKeyTokenProvider implements IProvider {
return $dbToken;
}
- public function markPasswordInvalid(IToken $token, string $tokenId) {
+ public function markPasswordInvalid(OCPIToken $token, string $tokenId) {
$this->cache->clear();
if (!($token instanceof PublicKeyToken)) {
diff --git a/lib/private/Authentication/Token/RemoteWipe.php b/lib/private/Authentication/Token/RemoteWipe.php
index 5fd01cfbe87..f5267764e24 100644
--- a/lib/private/Authentication/Token/RemoteWipe.php
+++ b/lib/private/Authentication/Token/RemoteWipe.php
@@ -27,14 +27,14 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Token;
-use Psr\Log\LoggerInterface;
-use function array_filter;
use OC\Authentication\Events\RemoteWipeFinished;
use OC\Authentication\Events\RemoteWipeStarted;
-use OC\Authentication\Exceptions\InvalidTokenException;
-use OC\Authentication\Exceptions\WipeTokenException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\Authentication\Exceptions\WipeTokenException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUser;
+use Psr\Log\LoggerInterface;
+use function array_filter;
class RemoteWipe {
/** @var IProvider */
@@ -47,8 +47,8 @@ class RemoteWipe {
private $logger;
public function __construct(IProvider $tokenProvider,
- IEventDispatcher $eventDispatcher,
- LoggerInterface $logger) {
+ IEventDispatcher $eventDispatcher,
+ LoggerInterface $logger) {
$this->tokenProvider = $tokenProvider;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
diff --git a/lib/private/Authentication/TwoFactorAuth/EnforcementState.php b/lib/private/Authentication/TwoFactorAuth/EnforcementState.php
index b95128c1e0f..91f133d6ad0 100644
--- a/lib/private/Authentication/TwoFactorAuth/EnforcementState.php
+++ b/lib/private/Authentication/TwoFactorAuth/EnforcementState.php
@@ -45,8 +45,8 @@ class EnforcementState implements JsonSerializable {
* @param string[] $excludedGroups
*/
public function __construct(bool $enforced,
- array $enforcedGroups = [],
- array $excludedGroups = []) {
+ array $enforcedGroups = [],
+ array $excludedGroups = []) {
$this->enforced = $enforced;
$this->enforcedGroups = $enforcedGroups;
$this->excludedGroups = $excludedGroups;
diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php
index ff0c33445a2..3870c797f8d 100644
--- a/lib/private/Authentication/TwoFactorAuth/Manager.php
+++ b/lib/private/Authentication/TwoFactorAuth/Manager.php
@@ -29,10 +29,10 @@ namespace OC\Authentication\TwoFactorAuth;
use BadMethodCallException;
use Exception;
-use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
use OCP\Activity\IManager;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
use OCP\Authentication\TwoFactorAuth\IProvider;
use OCP\Authentication\TwoFactorAuth\IRegistry;
@@ -89,15 +89,15 @@ class Manager {
private $userIsTwoFactorAuthenticated = [];
public function __construct(ProviderLoader $providerLoader,
- IRegistry $providerRegistry,
- MandatoryTwoFactor $mandatoryTwoFactor,
- ISession $session,
- IConfig $config,
- IManager $activityManager,
- LoggerInterface $logger,
- TokenProvider $tokenProvider,
- ITimeFactory $timeFactory,
- IEventDispatcher $eventDispatcher) {
+ IRegistry $providerRegistry,
+ MandatoryTwoFactor $mandatoryTwoFactor,
+ ISession $session,
+ IConfig $config,
+ IManager $activityManager,
+ LoggerInterface $logger,
+ TokenProvider $tokenProvider,
+ ITimeFactory $timeFactory,
+ IEventDispatcher $eventDispatcher) {
$this->providerLoader = $providerLoader;
$this->providerRegistry = $providerRegistry;
$this->mandatoryTwoFactor = $mandatoryTwoFactor;
@@ -318,8 +318,8 @@ class Manager {
return false;
}
- // If we are authenticated using an app password skip all this
- if ($this->session->exists('app_password')) {
+ // If we are authenticated using an app password or AppAPI Auth, skip all this
+ if ($this->session->exists('app_password') || $this->session->get('app_api') === true) {
return false;
}
diff --git a/lib/private/Authentication/TwoFactorAuth/ProviderSet.php b/lib/private/Authentication/TwoFactorAuth/ProviderSet.php
index af270fb83c8..4d39fd82bc6 100644
--- a/lib/private/Authentication/TwoFactorAuth/ProviderSet.php
+++ b/lib/private/Authentication/TwoFactorAuth/ProviderSet.php
@@ -25,9 +25,9 @@ declare(strict_types=1);
*/
namespace OC\Authentication\TwoFactorAuth;
-use function array_filter;
use OCA\TwoFactorBackupCodes\Provider\BackupCodesProvider;
use OCP\Authentication\TwoFactorAuth\IProvider;
+use function array_filter;
/**
* Contains all two-factor provider information for the two-factor login challenge
diff --git a/lib/private/Authentication/TwoFactorAuth/Registry.php b/lib/private/Authentication/TwoFactorAuth/Registry.php
index 482c025e144..db772265583 100644
--- a/lib/private/Authentication/TwoFactorAuth/Registry.php
+++ b/lib/private/Authentication/TwoFactorAuth/Registry.php
@@ -45,7 +45,7 @@ class Registry implements IRegistry {
private $dispatcher;
public function __construct(ProviderUserAssignmentDao $assignmentDao,
- IEventDispatcher $dispatcher) {
+ IEventDispatcher $dispatcher) {
$this->assignmentDao = $assignmentDao;
$this->dispatcher = $dispatcher;
}
diff --git a/lib/private/Authentication/WebAuthn/Manager.php b/lib/private/Authentication/WebAuthn/Manager.php
index 744a3fa354a..5a97a573b99 100644
--- a/lib/private/Authentication/WebAuthn/Manager.php
+++ b/lib/private/Authentication/WebAuthn/Manager.php
@@ -91,7 +91,7 @@ class Manager {
$user->getUID(), //Name
$user->getUID(), //ID
$user->getDisplayName() //Display name
-// 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon
+ // 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon
);
$challenge = random_bytes(32);
diff --git a/lib/private/Avatar/GuestAvatar.php b/lib/private/Avatar/GuestAvatar.php
index 26614cf6cfa..106e159d192 100644
--- a/lib/private/Avatar/GuestAvatar.php
+++ b/lib/private/Avatar/GuestAvatar.php
@@ -26,8 +26,8 @@ declare(strict_types=1);
*/
namespace OC\Avatar;
-use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\InMemoryFile;
+use OCP\Files\SimpleFS\ISimpleFile;
use Psr\Log\LoggerInterface;
/**
diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php
index 2b42e2ff1ee..93d5cf74a20 100644
--- a/lib/private/BackgroundJob/JobList.php
+++ b/lib/private/BackgroundJob/JobList.php
@@ -88,6 +88,7 @@ class JobList implements IJobList {
$query->update('jobs')
->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
->set('last_checked', $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT))
+ ->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('class', $query->createNamedParameter($class)))
->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson))));
}
@@ -391,6 +392,7 @@ class JobList implements IJobList {
$query = $this->connection->getQueryBuilder();
$query->update('jobs')
->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
+ ->set('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
@@ -413,7 +415,7 @@ class JobList implements IJobList {
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('jobs')
- ->where($query->expr()->neq('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
+ ->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 6 * 3600, IQueryBuilder::PARAM_INT)))
->setMaxResults(1);
if ($className !== null) {
diff --git a/lib/private/BinaryFinder.php b/lib/private/BinaryFinder.php
index a7ef55237db..17427e92619 100644
--- a/lib/private/BinaryFinder.php
+++ b/lib/private/BinaryFinder.php
@@ -22,9 +22,9 @@ declare(strict_types = 1);
namespace OC;
+use OCP\IBinaryFinder;
use OCP\ICache;
use OCP\ICacheFactory;
-use OCP\IBinaryFinder;
use Symfony\Component\Process\ExecutableFinder;
/**
diff --git a/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php b/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php
new file mode 100644
index 00000000000..7bfe36e9d41
--- /dev/null
+++ b/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php
@@ -0,0 +1,164 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2024 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\Blurhash\Listener;
+
+use GdImage;
+use kornrunner\Blurhash\Blurhash;
+use OC\Files\Node\File;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\GenericFileException;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\FilesMetadata\AMetadataEvent;
+use OCP\FilesMetadata\Event\MetadataBackgroundEvent;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use OCP\IPreview;
+use OCP\Lock\LockedException;
+
+/**
+ * Generate a Blurhash string as metadata when image file is uploaded/edited.
+ *
+ * @template-implements IEventListener<AMetadataEvent>
+ */
+class GenerateBlurhashMetadata implements IEventListener {
+ private const RESIZE_BOXSIZE = 300;
+
+ private const COMPONENTS_X = 4;
+ private const COMPONENTS_Y = 3;
+
+ public function __construct(
+ private IPreview $preview,
+ ) {
+ }
+
+ /**
+ * @throws NotPermittedException
+ * @throws GenericFileException
+ * @throws LockedException
+ */
+ public function handle(Event $event): void {
+ if (!($event instanceof MetadataLiveEvent)
+ && !($event instanceof MetadataBackgroundEvent)) {
+ return;
+ }
+
+ $file = $event->getNode();
+ if (!($file instanceof File)) {
+ return;
+ }
+
+ // too heavy to run on the live thread, request a rerun as a background job
+ if ($event instanceof MetadataLiveEvent) {
+ $event->requestBackgroundJob();
+ return;
+ }
+
+ $image = false;
+ try {
+ // using preview image to generate the blurhash
+ $preview = $this->preview->getPreview($file, 256, 256);
+ $image = imagecreatefromstring($preview->getContent());
+ } catch (NotFoundException $e) {
+ // https://github.com/nextcloud/server/blob/9d70fd3e64b60a316a03fb2b237891380c310c58/lib/private/legacy/OC_Image.php#L668
+ // The preview system can fail on huge picture, in that case we use our own image resizer.
+ if (str_starts_with($file->getMimetype(), 'image/')) {
+ $image = $this->resizedImageFromFile($file);
+ }
+ }
+
+ if ($image === false) {
+ return;
+ }
+
+ $metadata = $event->getMetadata();
+ $metadata->setString('blurhash', $this->generateBlurHash($image));
+ }
+
+ /**
+ * @param File $file
+ *
+ * @return GdImage|false
+ * @throws GenericFileException
+ * @throws NotPermittedException
+ * @throws LockedException
+ */
+ private function resizedImageFromFile(File $file): GdImage|false {
+ $image = imagecreatefromstring($file->getContent());
+ if ($image === false) {
+ return false;
+ }
+
+ $currX = imagesx($image);
+ $currY = imagesy($image);
+
+ if ($currX > $currY) {
+ $newX = self::RESIZE_BOXSIZE;
+ $newY = intval($currY * $newX / $currX);
+ } else {
+ $newY = self::RESIZE_BOXSIZE;
+ $newX = intval($currX * $newY / $currY);
+ }
+
+ $newImage = imagescale($image, $newX, $newY);
+ return ($newImage !== false) ? $newImage : $image;
+ }
+
+ /**
+ * @param GdImage $image
+ *
+ * @return string
+ */
+ public function generateBlurHash(GdImage $image): string {
+ $width = imagesx($image);
+ $height = imagesy($image);
+
+ $pixels = [];
+ for ($y = 0; $y < $height; ++$y) {
+ $row = [];
+ for ($x = 0; $x < $width; ++$x) {
+ $index = imagecolorat($image, $x, $y);
+ $colors = imagecolorsforindex($image, $index);
+ $row[] = [$colors['red'], $colors['green'], $colors['blue']];
+ }
+
+ $pixels[] = $row;
+ }
+
+ return Blurhash::encode($pixels, self::COMPONENTS_X, self::COMPONENTS_Y);
+ }
+
+ /**
+ * @param IEventDispatcher $eventDispatcher
+ *
+ * @return void
+ */
+ public static function loadListeners(IEventDispatcher $eventDispatcher): void {
+ $eventDispatcher->addServiceListener(MetadataLiveEvent::class, self::class);
+ $eventDispatcher->addServiceListener(MetadataBackgroundEvent::class, self::class);
+ }
+}
diff --git a/lib/private/CapabilitiesManager.php b/lib/private/CapabilitiesManager.php
index 7885a98869d..6b34b50cb98 100644
--- a/lib/private/CapabilitiesManager.php
+++ b/lib/private/CapabilitiesManager.php
@@ -30,8 +30,8 @@ namespace OC;
use OCP\AppFramework\QueryException;
use OCP\Capabilities\ICapability;
-use OCP\Capabilities\IPublicCapability;
use OCP\Capabilities\IInitialStateExcludedCapability;
+use OCP\Capabilities\IPublicCapability;
use Psr\Log\LoggerInterface;
class CapabilitiesManager {
diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php
index 1c98b904e76..91e665db783 100644
--- a/lib/private/Collaboration/Collaborators/GroupPlugin.php
+++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php
@@ -49,11 +49,16 @@ class GroupPlugin implements ISearchPlugin {
private IConfig $config,
private IGroupManager $groupManager,
private IUserSession $userSession,
+ private mixed $shareWithGroupOnlyExcludeGroupsList = [],
) {
$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';
+
+ if ($this->shareWithGroupOnly) {
+ $this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
+ }
}
public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
@@ -81,6 +86,9 @@ class GroupPlugin implements ISearchPlugin {
return $group->getGID();
}, $userGroups);
$groupIds = array_intersect($groupIds, $userGroups);
+
+ // ShareWithGroupOnly filtering
+ $groupIds = array_diff($groupIds, $this->shareWithGroupOnlyExcludeGroupsList);
}
$lowerSearch = strtolower($search);
diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php
index cbdd84efbb3..44f67f5aeba 100644
--- a/lib/private/Collaboration/Collaborators/MailPlugin.php
+++ b/lib/private/Collaboration/Collaborators/MailPlugin.php
@@ -37,8 +37,8 @@ use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
-use OCP\Share\IShare;
use OCP\Mail\IMailer;
+use OCP\Share\IShare;
class MailPlugin implements ISearchPlugin {
protected bool $shareWithGroupOnly;
@@ -61,6 +61,7 @@ class MailPlugin implements ISearchPlugin {
private KnownUserService $knownUserService,
private IUserSession $userSession,
private IMailer $mailer,
+ private mixed $shareWithGroupOnlyExcludeGroupsList = [],
) {
$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';
@@ -68,6 +69,10 @@ class MailPlugin implements ISearchPlugin {
$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
$this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
$this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
+
+ if ($this->shareWithGroupOnly) {
+ $this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
+ }
}
/**
@@ -127,6 +132,10 @@ class MailPlugin implements ISearchPlugin {
* Check if the user may share with the user associated with the e-mail of the just found contact
*/
$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
+
+ // ShareWithGroupOnly filtering
+ $userGroups = array_diff($userGroups, $this->shareWithGroupOnlyExcludeGroupsList);
+
$found = false;
foreach ($userGroups as $userGroup) {
if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php
index 1bd6762d2e0..005b0d05812 100644
--- a/lib/private/Collaboration/Collaborators/UserPlugin.php
+++ b/lib/private/Collaboration/Collaborators/UserPlugin.php
@@ -67,6 +67,7 @@ class UserPlugin implements ISearchPlugin {
private IUserSession $userSession,
private KnownUserService $knownUserService,
private IUserStatusManager $userStatusManager,
+ private mixed $shareWithGroupOnlyExcludeGroupsList = [],
) {
$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';
@@ -76,6 +77,10 @@ class UserPlugin implements ISearchPlugin {
$this->shareeEnumerationFullMatchUserId = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes';
$this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
$this->shareeEnumerationFullMatchIgnoreSecondDisplayName = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
+
+ if ($this->shareWithGroupOnly) {
+ $this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
+ }
}
public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
@@ -85,6 +90,10 @@ class UserPlugin implements ISearchPlugin {
$currentUserId = $this->userSession->getUser()->getUID();
$currentUserGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
+
+ // ShareWithGroupOnly filtering
+ $currentUserGroups = array_diff($currentUserGroups, $this->shareWithGroupOnlyExcludeGroupsList);
+
if ($this->shareWithGroupOnly || $this->shareeEnumerationInGroupOnly) {
// Search in all the groups this user is part of
foreach ($currentUserGroups as $userGroupId) {
diff --git a/lib/private/Command/AsyncBus.php b/lib/private/Command/AsyncBus.php
index ec6fbc91f68..c65e6cad78e 100644
--- a/lib/private/Command/AsyncBus.php
+++ b/lib/private/Command/AsyncBus.php
@@ -82,7 +82,7 @@ abstract class AsyncBus implements IBus {
private function canRunAsync($command) {
$traits = $this->getTraits($command);
foreach ($traits as $trait) {
- if (array_search($trait, $this->syncTraits) !== false) {
+ if (in_array($trait, $this->syncTraits)) {
return false;
}
}
diff --git a/lib/private/Command/ClosureJob.php b/lib/private/Command/ClosureJob.php
index 7216bcc762a..f7b0ee1a3d3 100644
--- a/lib/private/Command/ClosureJob.php
+++ b/lib/private/Command/ClosureJob.php
@@ -22,8 +22,8 @@
*/
namespace OC\Command;
-use OC\BackgroundJob\QueuedJob;
use Laravel\SerializableClosure\SerializableClosure as LaravelClosure;
+use OC\BackgroundJob\QueuedJob;
class ClosureJob extends QueuedJob {
protected function run($argument) {
diff --git a/lib/private/Command/CronBus.php b/lib/private/Command/CronBus.php
index 8749ad0bff5..42ff458a95c 100644
--- a/lib/private/Command/CronBus.php
+++ b/lib/private/Command/CronBus.php
@@ -25,8 +25,8 @@
*/
namespace OC\Command;
-use OCP\Command\ICommand;
use Laravel\SerializableClosure\SerializableClosure;
+use OCP\Command\ICommand;
class CronBus extends AsyncBus {
/**
diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php
index 35e88c74438..183821e37b1 100644
--- a/lib/private/Comments/Comment.php
+++ b/lib/private/Comments/Comment.php
@@ -42,6 +42,7 @@ class Comment implements IComment {
'objectType' => '',
'objectId' => '',
'referenceId' => null,
+ 'metaData' => null,
'creationDT' => null,
'latestChildDT' => null,
'reactions' => null,
@@ -403,6 +404,34 @@ class Comment implements IComment {
/**
* @inheritDoc
*/
+ public function getMetaData(): ?array {
+ if ($this->data['metaData'] === null) {
+ return null;
+ }
+
+ try {
+ $metaData = json_decode($this->data['metaData'], true, flags: JSON_THROW_ON_ERROR);
+ } catch (\JsonException $e) {
+ return null;
+ }
+ return is_array($metaData) ? $metaData : null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setMetaData(?array $metaData): IComment {
+ if ($metaData === null) {
+ $this->data['metaData'] = null;
+ } else {
+ $this->data['metaData'] = json_encode($metaData, JSON_THROW_ON_ERROR);
+ }
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
public function getReactions(): array {
return $this->data['reactions'] ?? [];
}
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php
index 725febef85d..85b56f9f25c 100644
--- a/lib/private/Comments/Manager.php
+++ b/lib/private/Comments/Manager.php
@@ -29,7 +29,6 @@
namespace OC\Comments;
use Doctrine\DBAL\Exception\DriverException;
-use Doctrine\DBAL\Exception\InvalidFieldNameException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\CommentsEvent;
use OCP\Comments\IComment;
@@ -40,8 +39,8 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IEmojiHelper;
-use OCP\IUser;
use OCP\IInitialStateService;
+use OCP\IUser;
use OCP\PreConditionNotMetException;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -66,11 +65,11 @@ class Manager implements ICommentsManager {
protected array $displayNameResolvers = [];
public function __construct(IDBConnection $dbConn,
- LoggerInterface $logger,
- IConfig $config,
- ITimeFactory $timeFactory,
- IEmojiHelper $emojiHelper,
- IInitialStateService $initialStateService) {
+ LoggerInterface $logger,
+ IConfig $config,
+ ITimeFactory $timeFactory,
+ IEmojiHelper $emojiHelper,
+ IInitialStateService $initialStateService) {
$this->dbConn = $dbConn;
$this->logger = $logger;
$this->config = $config;
@@ -97,7 +96,8 @@ class Manager implements ICommentsManager {
$data['expire_date'] = new \DateTime($data['expire_date']);
}
$data['children_count'] = (int)$data['children_count'];
- $data['reference_id'] = $data['reference_id'] ?? null;
+ $data['reference_id'] = $data['reference_id'];
+ $data['meta_data'] = json_decode($data['meta_data'], true);
if ($this->supportReactions()) {
if ($data['reactions'] !== null) {
$list = json_decode($data['reactions'], true);
@@ -536,8 +536,8 @@ class Manager implements ICommentsManager {
* @param int $id the comment to look for
*/
protected function getLastKnownComment(string $objectType,
- string $objectId,
- int $id): ?IComment {
+ string $objectId,
+ int $id): ?IComment {
$query = $this->dbConn->getQueryBuilder();
$query->select('*')
->from('comments')
@@ -1150,22 +1150,6 @@ class Manager implements ICommentsManager {
* @return bool
*/
protected function insert(IComment $comment): bool {
- try {
- $result = $this->insertQuery($comment, true);
- } catch (InvalidFieldNameException $e) {
- // The reference id field was only added in Nextcloud 19.
- // In order to not cause too long waiting times on the update,
- // it was decided to only add it lazy, as it is also not a critical
- // feature, but only helps to have a better experience while commenting.
- // So in case the reference_id field is missing,
- // we simply save the comment without that field.
- $result = $this->insertQuery($comment, false);
- }
-
- return $result;
- }
-
- protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool {
$qb = $this->dbConn->getQueryBuilder();
$values = [
@@ -1181,12 +1165,10 @@ class Manager implements ICommentsManager {
'object_type' => $qb->createNamedParameter($comment->getObjectType()),
'object_id' => $qb->createNamedParameter($comment->getObjectId()),
'expire_date' => $qb->createNamedParameter($comment->getExpireDate(), 'datetime'),
+ 'reference_id' => $qb->createNamedParameter($comment->getReferenceId()),
+ 'meta_data' => $qb->createNamedParameter(json_encode($comment->getMetaData())),
];
- if ($tryWritingReferenceId) {
- $values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId());
- }
-
$affectedRows = $qb->insert('comments')
->values($values)
->execute();
@@ -1289,12 +1271,7 @@ class Manager implements ICommentsManager {
$this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId()));
$this->uncache($comment->getId());
- try {
- $result = $this->updateQuery($comment, true);
- } catch (InvalidFieldNameException $e) {
- // See function insert() for explanation
- $result = $this->updateQuery($comment, false);
- }
+ $result = $this->updateQuery($comment);
if ($comment->getVerb() === 'reaction_deleted') {
$this->deleteReaction($comment);
@@ -1305,7 +1282,7 @@ class Manager implements ICommentsManager {
return $result;
}
- protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool {
+ protected function updateQuery(IComment $comment): bool {
$qb = $this->dbConn->getQueryBuilder();
$qb
->update('comments')
@@ -1320,14 +1297,11 @@ class Manager implements ICommentsManager {
->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime'))
->set('object_type', $qb->createNamedParameter($comment->getObjectType()))
->set('object_id', $qb->createNamedParameter($comment->getObjectId()))
- ->set('expire_date', $qb->createNamedParameter($comment->getExpireDate(), 'datetime'));
-
- if ($tryWritingReferenceId) {
- $qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()));
- }
-
- $affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())))
- ->execute();
+ ->set('expire_date', $qb->createNamedParameter($comment->getExpireDate(), 'datetime'))
+ ->set('reference_id', $qb->createNamedParameter($comment->getReferenceId()))
+ ->set('meta_data', $qb->createNamedParameter(json_encode($comment->getMetaData())))
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId())));
+ $affectedRows = $qb->executeStatement();
if ($affectedRows === 0) {
throw new NotFoundException('Comment to update does ceased to exist');
diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php
index a0306c9798c..900b2c57f41 100644
--- a/lib/private/Console/Application.php
+++ b/lib/private/Console/Application.php
@@ -55,10 +55,10 @@ class Application {
private MemoryInfo $memoryInfo;
public function __construct(IConfig $config,
- IEventDispatcher $dispatcher,
- IRequest $request,
- LoggerInterface $logger,
- MemoryInfo $memoryInfo) {
+ IEventDispatcher $dispatcher,
+ IRequest $request,
+ LoggerInterface $logger,
+ MemoryInfo $memoryInfo) {
$defaults = \OC::$server->getThemingDefaults();
$this->config = $config;
$this->application = new SymfonyApplication($defaults->getName(), \OC_Util::getVersionString());
diff --git a/lib/private/Console/TimestampFormatter.php b/lib/private/Console/TimestampFormatter.php
index 8d74c28e94f..afb1f67c37f 100644
--- a/lib/private/Console/TimestampFormatter.php
+++ b/lib/private/Console/TimestampFormatter.php
@@ -27,17 +27,17 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyleInterface;
class TimestampFormatter implements OutputFormatterInterface {
- /** @var IConfig */
+ /** @var ?IConfig */
protected $config;
/** @var OutputFormatterInterface */
protected $formatter;
/**
- * @param IConfig $config
+ * @param ?IConfig $config
* @param OutputFormatterInterface $formatter
*/
- public function __construct(IConfig $config, OutputFormatterInterface $formatter) {
+ public function __construct(?IConfig $config, OutputFormatterInterface $formatter) {
$this->config = $config;
$this->formatter = $formatter;
}
@@ -104,11 +104,16 @@ class TimestampFormatter implements OutputFormatterInterface {
return $this->formatter->format($message);
}
- $timeZone = $this->config->getSystemValue('logtimezone', 'UTC');
- $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null;
+ if ($this->config instanceof IConfig) {
+ $timeZone = $this->config->getSystemValue('logtimezone', 'UTC');
+ $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null;
- $time = new \DateTime('now', $timeZone);
- $timestampInfo = $time->format($this->config->getSystemValue('logdateformat', \DateTimeInterface::ATOM));
+ $time = new \DateTime('now', $timeZone);
+ $timestampInfo = $time->format($this->config->getSystemValue('logdateformat', \DateTimeInterface::ATOM));
+ } else {
+ $time = new \DateTime('now');
+ $timestampInfo = $time->format(\DateTimeInterface::ATOM);
+ }
return $timestampInfo . ' ' . $this->formatter->format($message);
}
diff --git a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php
index 7ba5db4bb33..67354a5fb2d 100644
--- a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php
+++ b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php
@@ -33,6 +33,7 @@ use OC\Contacts\ContactsMenu\Providers\EMailProvider;
use OC\Contacts\ContactsMenu\Providers\LocalTimeProvider;
use OC\Contacts\ContactsMenu\Providers\ProfileProvider;
use OCP\AppFramework\QueryException;
+use OCP\Contacts\ContactsMenu\IBulkProvider;
use OCP\Contacts\ContactsMenu\IProvider;
use OCP\IServerContainer;
use OCP\IUser;
@@ -47,18 +48,26 @@ class ActionProviderStore {
}
/**
- * @return IProvider[]
+ * @return list<IProvider|IBulkProvider>
* @throws Exception
*/
public function getProviders(IUser $user): array {
$appClasses = $this->getAppProviderClasses($user);
$providerClasses = $this->getServerProviderClasses();
$allClasses = array_merge($providerClasses, $appClasses);
+ /** @var list<IProvider|IBulkProvider> $providers */
$providers = [];
foreach ($allClasses as $class) {
try {
- $providers[] = $this->serverContainer->get($class);
+ $provider = $this->serverContainer->get($class);
+ if ($provider instanceof IProvider || $provider instanceof IBulkProvider) {
+ $providers[] = $provider;
+ } else {
+ $this->logger->warning('Ignoring invalid contacts menu provider', [
+ 'class' => $class,
+ ]);
+ }
} catch (QueryException $ex) {
$this->logger->error(
'Could not load contacts menu action provider ' . $class,
diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php
index c692b486ae4..1b3ef55cf20 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) {
@@ -108,6 +177,9 @@ class ContactsStore implements IContactsStore {
* 3. if the `shareapi_only_share_with_group_members` config option is
* enabled it will filter all users which doesn't have a common group
* with the current user.
+ * If enabled, the 'shareapi_only_share_with_group_members_exclude_group_list'
+ * config option may specify some groups excluded from the principle of
+ * belonging to the same group.
*
* @param Entry[] $entries
* @return Entry[] the filtered contacts
@@ -141,6 +213,13 @@ class ContactsStore implements IContactsStore {
}
}
+ // ownGroupsOnly : some groups may be excluded
+ if ($ownGroupsOnly) {
+ $excludeGroupsFromOwnGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
+ $excludeGroupsFromOwnGroupsList = json_decode($excludeGroupsFromOwnGroups, true) ?? [];
+ $selfGroups = array_diff($selfGroups, $excludeGroupsFromOwnGroupsList);
+ }
+
$selfUID = $self->getUID();
return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $allowEnumerationFullMatch, $filter) {
@@ -265,12 +344,15 @@ class ContactsStore implements IContactsStore {
private function contactArrayToEntry(array $contact): Entry {
$entry = new Entry();
- if (isset($contact['UID'])) {
+ if (!empty($contact['UID'])) {
$uid = $contact['UID'];
$entry->setId($uid);
+ $entry->setProperty('isUser', false);
+ // overloaded usage so leaving as-is for now
if (isset($contact['isLocalSystemBook'])) {
$avatar = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 64]);
- } elseif (isset($contact['FN'])) {
+ $entry->setProperty('isUser', true);
+ } elseif (!empty($contact['FN'])) {
$avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => $contact['FN'], 'size' => 64]);
} else {
$avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => $uid, 'size' => 64]);
@@ -278,23 +360,23 @@ class ContactsStore implements IContactsStore {
$entry->setAvatar($avatar);
}
- if (isset($contact['FN'])) {
+ if (!empty($contact['FN'])) {
$entry->setFullName($contact['FN']);
}
$avatarPrefix = "VALUE=uri:";
- if (isset($contact['PHOTO']) && str_starts_with($contact['PHOTO'], $avatarPrefix)) {
+ if (!empty($contact['PHOTO']) && str_starts_with($contact['PHOTO'], $avatarPrefix)) {
$entry->setAvatar(substr($contact['PHOTO'], strlen($avatarPrefix)));
}
- if (isset($contact['EMAIL'])) {
+ if (!empty($contact['EMAIL'])) {
foreach ($contact['EMAIL'] as $email) {
$entry->addEMailAddress($email);
}
}
// Provide profile parameters for core/src/OC/contactsmenu/contact.handlebars template
- if (isset($contact['UID']) && isset($contact['FN'])) {
+ if (!empty($contact['UID']) && !empty($contact['FN'])) {
$targetUserId = $contact['UID'];
$targetUser = $this->userManager->get($targetUserId);
if (!empty($targetUser)) {
diff --git a/lib/private/Contacts/ContactsMenu/Entry.php b/lib/private/Contacts/ContactsMenu/Entry.php
index f1cb4f9c52f..954f46e1296 100644
--- a/lib/private/Contacts/ContactsMenu/Entry.php
+++ b/lib/private/Contacts/ContactsMenu/Entry.php
@@ -29,8 +29,11 @@ namespace OC\Contacts\ContactsMenu;
use OCP\Contacts\ContactsMenu\IAction;
use OCP\Contacts\ContactsMenu\IEntry;
+use function array_merge;
class Entry implements IEntry {
+ public const PROPERTY_STATUS_MESSAGE_TIMESTAMP = 'statusMessageTimestamp';
+
/** @var string|int|null */
private $id = null;
@@ -50,6 +53,11 @@ class Entry implements IEntry {
private array $properties = [];
+ private ?string $status = null;
+ private ?string $statusMessage = null;
+ private ?int $statusMessageTimestamp = null;
+ private ?string $statusIcon = null;
+
public function setId(string $id): void {
$this->id = $id;
}
@@ -102,6 +110,16 @@ class Entry implements IEntry {
$this->sortActions();
}
+ public function setStatus(string $status,
+ string $statusMessage = null,
+ int $statusMessageTimestamp = null,
+ string $icon = null): void {
+ $this->status = $status;
+ $this->statusMessage = $statusMessage;
+ $this->statusMessageTimestamp = $statusMessageTimestamp;
+ $this->statusIcon = $icon;
+ }
+
/**
* @return IAction[]
*/
@@ -127,11 +145,15 @@ class Entry implements IEntry {
});
}
+ public function setProperty(string $propertyName, mixed $value) {
+ $this->properties[$propertyName] = $value;
+ }
+
/**
- * @param array $contact key-value array containing additional properties
+ * @param array $properties key-value array containing additional properties
*/
- public function setProperties(array $contact): void {
- $this->properties = $contact;
+ public function setProperties(array $properties): void {
+ $this->properties = array_merge($this->properties, $properties);
}
public function getProperty(string $key): mixed {
@@ -142,7 +164,7 @@ class Entry implements IEntry {
}
/**
- * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null}
+ * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null, status: string|null, statusMessage: null|string, statusMessageTimestamp: null|int, statusIcon: null|string, isUser: bool, uid: mixed}
*/
public function jsonSerialize(): array {
$topAction = !empty($this->actions) ? $this->actions[0]->jsonSerialize() : null;
@@ -160,6 +182,20 @@ class Entry implements IEntry {
'emailAddresses' => $this->getEMailAddresses(),
'profileTitle' => $this->profileTitle,
'profileUrl' => $this->profileUrl,
+ 'status' => $this->status,
+ 'statusMessage' => $this->statusMessage,
+ 'statusMessageTimestamp' => $this->statusMessageTimestamp,
+ 'statusIcon' => $this->statusIcon,
+ 'isUser' => $this->getProperty('isUser') === true,
+ 'uid' => $this->getProperty('UID'),
];
}
+
+ public function getStatusMessage(): ?string {
+ return $this->statusMessage;
+ }
+
+ public function getStatusMessageTimestamp(): ?int {
+ return $this->statusMessageTimestamp;
+ }
}
diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php
index 490cf602283..5cf9a07c8e3 100644
--- a/lib/private/Contacts/ContactsMenu/Manager.php
+++ b/lib/private/Contacts/ContactsMenu/Manager.php
@@ -28,7 +28,9 @@ namespace OC\Contacts\ContactsMenu;
use Exception;
use OCP\App\IAppManager;
use OCP\Constants;
+use OCP\Contacts\ContactsMenu\IBulkProvider;
use OCP\Contacts\ContactsMenu\IEntry;
+use OCP\Contacts\ContactsMenu\IProvider;
use OCP\IConfig;
use OCP\IUser;
@@ -80,8 +82,19 @@ class Manager {
* @return IEntry[]
*/
private function sortEntries(array $entries): array {
- usort($entries, function (IEntry $entryA, IEntry $entryB) {
- return strcasecmp($entryA->getFullName(), $entryB->getFullName());
+ usort($entries, function (Entry $entryA, Entry $entryB) {
+ $aStatusTimestamp = $entryA->getProperty(Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP);
+ $bStatusTimestamp = $entryB->getProperty(Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP);
+ if (!$aStatusTimestamp && !$bStatusTimestamp) {
+ return strcasecmp($entryA->getFullName(), $entryB->getFullName());
+ }
+ if ($aStatusTimestamp === null) {
+ return 1;
+ }
+ if ($bStatusTimestamp === null) {
+ return -1;
+ }
+ return $bStatusTimestamp - $aStatusTimestamp;
});
return $entries;
}
@@ -92,9 +105,14 @@ class Manager {
*/
private function processEntries(array $entries, IUser $user): void {
$providers = $this->actionProviderStore->getProviders($user);
- foreach ($entries as $entry) {
- foreach ($providers as $provider) {
- $provider->process($entry);
+
+ foreach ($providers as $provider) {
+ if ($provider instanceof IBulkProvider && !($provider instanceof IProvider)) {
+ $provider->process($entries);
+ } elseif ($provider instanceof IProvider && !($provider instanceof IBulkProvider)) {
+ foreach ($entries as $entry) {
+ $provider->process($entry);
+ }
}
}
}
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index df35e0b5e0d..ed322bc90f0 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -38,24 +38,26 @@ namespace OC\DB;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Configuration;
+use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Statement;
+use OC\DB\QueryBuilder\QueryBuilder;
+use OC\SystemConfig;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Diagnostics\IEventLogger;
use OCP\IRequestId;
use OCP\PreConditionNotMetException;
use OCP\Profiler\IProfiler;
-use OC\DB\QueryBuilder\QueryBuilder;
-use OC\SystemConfig;
use Psr\Log\LoggerInterface;
-class Connection extends \Doctrine\DBAL\Connection {
+class Connection extends PrimaryReadReplicaConnection {
/** @var string */
protected $tablePrefix;
@@ -77,6 +79,11 @@ class Connection extends \Doctrine\DBAL\Connection {
/** @var DbDataCollector|null */
protected $dbDataCollector = null;
+ private array $lastConnectionCheck = [];
+
+ protected ?float $transactionActiveSince = null;
+
+ protected $tableDirtyWrites = [];
/**
* Initializes a new instance of the Connection class.
@@ -119,13 +126,16 @@ class Connection extends \Doctrine\DBAL\Connection {
/**
* @throws Exception
*/
- public function connect() {
+ public function connect($connectionName = null) {
try {
if ($this->_conn) {
+ $this->reconnectIfNeeded();
/** @psalm-suppress InternalMethod */
return parent::connect();
}
+ $this->lastConnectionCheck[$this->getConnectionName()] = time();
+
// Only trigger the event logger for the initial connect call
$eventLogger = \OC::$server->get(IEventLogger::class);
$eventLogger->start('connect:db', 'db connection opened');
@@ -254,6 +264,33 @@ class Connection extends \Doctrine\DBAL\Connection {
* @throws \Doctrine\DBAL\Exception
*/
public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result {
+ $tables = $this->getQueriedTables($sql);
+ if ($this->isTransactionActive()) {
+ // Transacted queries go to the primary. The consistency of the primary guarantees that we can not run
+ // into a dirty read.
+ } elseif (count(array_intersect($this->tableDirtyWrites, $tables)) === 0) {
+ // No tables read that could have been written already in the same request and no transaction active
+ // so we can switch back to the replica for reading as long as no writes happen that switch back to the primary
+ // We cannot log here as this would log too early in the server boot process
+ $this->ensureConnectedToReplica();
+ } else {
+ // Read to a table that has been written to previously
+ // While this might not necessarily mean that we did a read after write it is an indication for a code path to check
+ $this->logger->log(
+ (int) ($this->systemConfig->getValue('loglevel_dirty_database_queries', null) ?? 0),
+ 'dirty table reads: ' . $sql,
+ [
+ 'tables' => $this->tableDirtyWrites,
+ 'reads' => $tables,
+ 'exception' => new \Exception(),
+ ],
+ );
+ // To prevent a dirty read on a replica that is slightly out of sync, we
+ // switch back to the primary. This is detrimental for performance but
+ // safer for consistency.
+ $this->ensureConnectedToPrimary();
+ }
+
$sql = $this->replaceTablePrefix($sql);
$sql = $this->adapter->fixupStatement($sql);
$this->queriesExecuted++;
@@ -262,6 +299,16 @@ class Connection extends \Doctrine\DBAL\Connection {
}
/**
+ * Helper function to get the list of tables affected by a given query
+ * used to track dirty tables that received a write with the current request
+ */
+ private function getQueriedTables(string $sql): array {
+ $re = '/(\*PREFIX\*\w+)/mi';
+ preg_match_all($re, $sql, $matches);
+ return array_map([$this, 'replaceTablePrefix'], $matches[0] ?? []);
+ }
+
+ /**
* @throws Exception
*/
public function executeUpdate(string $sql, array $params = [], array $types = []): int {
@@ -287,6 +334,8 @@ class Connection extends \Doctrine\DBAL\Connection {
* @throws \Doctrine\DBAL\Exception
*/
public function executeStatement($sql, array $params = [], array $types = []): int {
+ $tables = $this->getQueriedTables($sql);
+ $this->tableDirtyWrites = array_unique(array_merge($this->tableDirtyWrites, $tables));
$sql = $this->replaceTablePrefix($sql);
$sql = $this->adapter->fixupStatement($sql);
$this->queriesExecuted++;
@@ -302,6 +351,11 @@ class Connection extends \Doctrine\DBAL\Connection {
$prefix .= \OC::$server->get(IRequestId::class)->getId() . "\t";
}
+ // FIXME: Improve to log the actual target db host
+ $isPrimary = $this->connections['primary'] === $this->_conn;
+ $prefix .= ' ' . ($isPrimary === true ? 'primary' : 'replica') . ' ';
+ $prefix .= ' ' . $this->getTransactionNestingLevel() . ' ';
+
file_put_contents(
$this->systemConfig->getValue('query_log_file', ''),
$prefix . $sql . "\n",
@@ -603,4 +657,57 @@ class Connection extends \Doctrine\DBAL\Connection {
return new Migrator($this, $config, $dispatcher);
}
}
+
+ public function beginTransaction() {
+ if (!$this->inTransaction()) {
+ $this->transactionActiveSince = microtime(true);
+ }
+ return parent::beginTransaction();
+ }
+
+ public function commit() {
+ $result = parent::commit();
+ if ($this->getTransactionNestingLevel() === 0) {
+ $timeTook = microtime(true) - $this->transactionActiveSince;
+ $this->transactionActiveSince = null;
+ if ($timeTook > 1) {
+ $this->logger->warning('Transaction took longer than 1s: ' . $timeTook, ['exception' => new \Exception('Long running transaction')]);
+ }
+ }
+ return $result;
+ }
+
+ public function rollBack() {
+ $result = parent::rollBack();
+ if ($this->getTransactionNestingLevel() === 0) {
+ $timeTook = microtime(true) - $this->transactionActiveSince;
+ $this->transactionActiveSince = null;
+ if ($timeTook > 1) {
+ $this->logger->warning('Transaction rollback took longer than 1s: ' . $timeTook, ['exception' => new \Exception('Long running transaction rollback')]);
+ }
+ }
+ return $result;
+ }
+
+ private function reconnectIfNeeded(): void {
+ if (
+ !isset($this->lastConnectionCheck[$this->getConnectionName()]) ||
+ $this->lastConnectionCheck[$this->getConnectionName()] + 30 >= time() ||
+ $this->isTransactionActive()
+ ) {
+ return;
+ }
+
+ try {
+ $this->_conn->query($this->getDriver()->getDatabasePlatform()->getDummySelectSQL());
+ $this->lastConnectionCheck[$this->getConnectionName()] = time();
+ } catch (ConnectionLost|\Exception $e) {
+ $this->logger->warning('Exception during connectivity check, closing and reconnecting', ['exception' => $e]);
+ $this->close();
+ }
+ }
+
+ private function getConnectionName(): string {
+ return $this->isConnectedToPrimary() ? 'primary' : 'replica';
+ }
}
diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php
index 4b286ff5442..e868f18ec34 100644
--- a/lib/private/DB/ConnectionFactory.php
+++ b/lib/private/DB/ConnectionFactory.php
@@ -32,7 +32,6 @@ use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Event\Listeners\OracleSessionInit;
-use Doctrine\DBAL\Event\Listeners\SQLSessionInit;
use OC\SystemConfig;
/**
@@ -127,11 +126,8 @@ class ConnectionFactory {
$normalizedType = $this->normalizeType($type);
$eventManager = new EventManager();
$eventManager->addEventSubscriber(new SetTransactionIsolationLevel());
+ $additionalConnectionParams = array_merge($this->createConnectionParams(), $additionalConnectionParams);
switch ($normalizedType) {
- case 'mysql':
- $eventManager->addEventSubscriber(
- new SQLSessionInit("SET SESSION AUTOCOMMIT=1"));
- break;
case 'oci':
$eventManager->addEventSubscriber(new OracleSessionInit);
// the driverOptions are unused in dbal and need to be mapped to the parameters
@@ -159,7 +155,7 @@ class ConnectionFactory {
}
/** @var Connection $connection */
$connection = DriverManager::getConnection(
- array_merge($this->getDefaultConnectionParams($type), $additionalConnectionParams),
+ $additionalConnectionParams,
new Configuration(),
$eventManager
);
@@ -195,10 +191,10 @@ class ConnectionFactory {
public function createConnectionParams(string $configPrefix = '') {
$type = $this->config->getValue('dbtype', 'sqlite');
- $connectionParams = [
+ $connectionParams = array_merge($this->getDefaultConnectionParams($type), [
'user' => $this->config->getValue($configPrefix . 'dbuser', $this->config->getValue('dbuser', '')),
'password' => $this->config->getValue($configPrefix . 'dbpassword', $this->config->getValue('dbpassword', '')),
- ];
+ ]);
$name = $this->config->getValue($configPrefix . 'dbname', $this->config->getValue('dbname', self::DEFAULT_DBNAME));
if ($this->normalizeType($type) === 'sqlite3') {
@@ -237,7 +233,11 @@ class ConnectionFactory {
$connectionParams['persistent'] = true;
}
- return $connectionParams;
+ $replica = $this->config->getValue('dbreplica', []) ?: [$connectionParams];
+ return array_merge($connectionParams, [
+ 'primary' => $connectionParams,
+ 'replica' => $replica,
+ ]);
}
/**
diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php
index 29df1c1f78d..60f9b65cd5f 100644
--- a/lib/private/DB/MigrationService.php
+++ b/lib/private/DB/MigrationService.php
@@ -390,6 +390,7 @@ class MigrationService {
*/
public function migrate(string $to = 'latest', bool $schemaOnly = false): void {
if ($schemaOnly) {
+ $this->output->debug('Migrating schema only');
$this->migrateSchemaOnly($to);
return;
}
@@ -421,6 +422,7 @@ class MigrationService {
$toSchema = null;
foreach ($toBeExecuted as $version) {
+ $this->output->debug('- Reading ' . $version);
$instance = $this->createInstance($version);
$toSchema = $instance->changeSchema($this->output, function () use ($toSchema): ISchemaWrapper {
@@ -429,16 +431,20 @@ class MigrationService {
}
if ($toSchema instanceof SchemaWrapper) {
+ $this->output->debug('- Checking target database schema');
$targetSchema = $toSchema->getWrappedSchema();
$this->ensureUniqueNamesConstraints($targetSchema);
if ($this->checkOracle) {
$beforeSchema = $this->connection->createSchema();
$this->ensureOracleConstraints($beforeSchema, $targetSchema, strlen($this->connection->getPrefix()));
}
+
+ $this->output->debug('- Migrate database schema');
$this->connection->migrateToSchema($targetSchema);
$toSchema->performDropTableCalls();
}
+ $this->output->debug('- Mark migrations as executed');
foreach ($toBeExecuted as $version) {
$this->markAsExecuted($version);
}
diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php
index 1d960e72dc5..7cf95b04000 100644
--- a/lib/private/DB/Migrator.php
+++ b/lib/private/DB/Migrator.php
@@ -35,9 +35,9 @@ use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaDiff;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\Type;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use function preg_match;
-use OCP\EventDispatcher\IEventDispatcher;
class Migrator {
/** @var Connection */
@@ -52,8 +52,8 @@ class Migrator {
private $noEmit = false;
public function __construct(Connection $connection,
- IConfig $config,
- ?IEventDispatcher $dispatcher = null) {
+ IConfig $config,
+ ?IEventDispatcher $dispatcher = null) {
$this->connection = $connection;
$this->config = $config;
$this->dispatcher = $dispatcher;
diff --git a/lib/private/DB/MissingColumnInformation.php b/lib/private/DB/MissingColumnInformation.php
index f651546b4b3..919f8923a26 100644
--- a/lib/private/DB/MissingColumnInformation.php
+++ b/lib/private/DB/MissingColumnInformation.php
@@ -26,7 +26,7 @@ declare(strict_types=1);
namespace OC\DB;
class MissingColumnInformation {
- private $listOfMissingColumns = [];
+ private array $listOfMissingColumns = [];
public function addHintForMissingColumn(string $tableName, string $columnName): void {
$this->listOfMissingColumns[] = [
diff --git a/lib/private/DB/MissingIndexInformation.php b/lib/private/DB/MissingIndexInformation.php
index 74498668349..4fc3a52d3a4 100644
--- a/lib/private/DB/MissingIndexInformation.php
+++ b/lib/private/DB/MissingIndexInformation.php
@@ -27,16 +27,16 @@ declare(strict_types=1);
namespace OC\DB;
class MissingIndexInformation {
- private $listOfMissingIndexes = [];
+ private array $listOfMissingIndices = [];
- public function addHintForMissingSubject(string $tableName, string $indexName) {
- $this->listOfMissingIndexes[] = [
+ public function addHintForMissingIndex(string $tableName, string $indexName): void {
+ $this->listOfMissingIndices[] = [
'tableName' => $tableName,
'indexName' => $indexName
];
}
- public function getListOfMissingIndexes(): array {
- return $this->listOfMissingIndexes;
+ public function getListOfMissingIndices(): array {
+ return $this->listOfMissingIndices;
}
}
diff --git a/lib/private/DB/MissingPrimaryKeyInformation.php b/lib/private/DB/MissingPrimaryKeyInformation.php
index f28c8cfb352..42e5584291c 100644
--- a/lib/private/DB/MissingPrimaryKeyInformation.php
+++ b/lib/private/DB/MissingPrimaryKeyInformation.php
@@ -26,9 +26,9 @@ declare(strict_types=1);
namespace OC\DB;
class MissingPrimaryKeyInformation {
- private $listOfMissingPrimaryKeys = [];
+ private array $listOfMissingPrimaryKeys = [];
- public function addHintForMissingSubject(string $tableName) {
+ public function addHintForMissingPrimaryKey(string $tableName): void {
$this->listOfMissingPrimaryKeys[] = [
'tableName' => $tableName,
];
diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php
index ae4f19f5d18..ad45f77e5ea 100644
--- a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php
+++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php
@@ -117,8 +117,8 @@ class ExpressionBuilder implements IExpressionBuilder {
* @return string
*/
public function comparison($x, string $operator, $y, $type = null): string {
- $x = $this->helper->quoteColumnName($x);
- $y = $this->helper->quoteColumnName($y);
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->comparison($x, $operator, $y);
}
@@ -140,8 +140,8 @@ class ExpressionBuilder implements IExpressionBuilder {
* @return string
*/
public function eq($x, $y, $type = null): string {
- $x = $this->helper->quoteColumnName($x);
- $y = $this->helper->quoteColumnName($y);
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->eq($x, $y);
}
@@ -162,8 +162,8 @@ class ExpressionBuilder implements IExpressionBuilder {
* @return string
*/
public function neq($x, $y, $type = null): string {
- $x = $this->helper->quoteColumnName($x);
- $y = $this->helper->quoteColumnName($y);
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->neq($x, $y);
}
@@ -184,8 +184,8 @@ class ExpressionBuilder implements IExpressionBuilder {
* @return string
*/
public function lt($x, $y, $type = null): string {
- $x = $this->helper->quoteColumnName($x);
- $y = $this->helper->quoteColumnName($y);
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->lt($x, $y);
}
@@ -206,8 +206,8 @@ class ExpressionBuilder implements IExpressionBuilder {
* @return string
*/
public function lte($x, $y, $type = null): string {
- $x = $this->helper->quoteColumnName($x);
- $y = $this->helper->quoteColumnName($y);
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->lte($x, $y);
}
@@ -228,8 +228,8 @@ class ExpressionBuilder implements IExpressionBuilder {
* @return string
*/
public function gt($x, $y, $type = null): string {
- $x = $this->helper->quoteColumnName($x);
- $y = $this->helper->quoteColumnName($y);
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->gt($x, $y);
}
@@ -250,8 +250,8 @@ class ExpressionBuilder implements IExpressionBuilder {
* @return string
*/
public function gte($x, $y, $type = null): string {
- $x = $this->helper->quoteColumnName($x);
- $y = $this->helper->quoteColumnName($y);
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
return $this->expressionBuilder->gte($x, $y);
}
@@ -435,4 +435,13 @@ class ExpressionBuilder implements IExpressionBuilder {
$this->helper->quoteColumnName($column)
);
}
+
+ /**
+ * @param mixed $column
+ * @param mixed|null $type
+ * @return array|IQueryFunction|string
+ */
+ protected function prepareColumn($column, $type) {
+ return $this->helper->quoteColumnNames($column);
+ }
}
diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php
index caeb8009885..8184e369317 100644
--- a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php
+++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php
@@ -39,80 +39,9 @@ class OCIExpressionBuilder extends ExpressionBuilder {
protected function prepareColumn($column, $type) {
if ($type === IQueryBuilder::PARAM_STR && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) {
$column = $this->castColumn($column, $type);
- } else {
- $column = $this->helper->quoteColumnNames($column);
}
- return $column;
- }
-
- /**
- * @inheritdoc
- */
- public function comparison($x, string $operator, $y, $type = null): string {
- $x = $this->prepareColumn($x, $type);
- $y = $this->prepareColumn($y, $type);
-
- return $this->expressionBuilder->comparison($x, $operator, $y);
- }
-
- /**
- * @inheritdoc
- */
- public function eq($x, $y, $type = null): string {
- $x = $this->prepareColumn($x, $type);
- $y = $this->prepareColumn($y, $type);
-
- return $this->expressionBuilder->eq($x, $y);
- }
-
- /**
- * @inheritdoc
- */
- public function neq($x, $y, $type = null): string {
- $x = $this->prepareColumn($x, $type);
- $y = $this->prepareColumn($y, $type);
-
- return $this->expressionBuilder->neq($x, $y);
- }
-
- /**
- * @inheritdoc
- */
- public function lt($x, $y, $type = null): string {
- $x = $this->prepareColumn($x, $type);
- $y = $this->prepareColumn($y, $type);
-
- return $this->expressionBuilder->lt($x, $y);
- }
-
- /**
- * @inheritdoc
- */
- public function lte($x, $y, $type = null): string {
- $x = $this->prepareColumn($x, $type);
- $y = $this->prepareColumn($y, $type);
-
- return $this->expressionBuilder->lte($x, $y);
- }
-
- /**
- * @inheritdoc
- */
- public function gt($x, $y, $type = null): string {
- $x = $this->prepareColumn($x, $type);
- $y = $this->prepareColumn($y, $type);
-
- return $this->expressionBuilder->gt($x, $y);
- }
-
- /**
- * @inheritdoc
- */
- public function gte($x, $y, $type = null): string {
- $x = $this->prepareColumn($x, $type);
- $y = $this->prepareColumn($y, $type);
- return $this->expressionBuilder->gte($x, $y);
+ return parent::prepareColumn($column, $type);
}
/**
diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php
index 289aa09b003..4e2797761d6 100644
--- a/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php
+++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php
@@ -23,6 +23,12 @@
*/
namespace OC\DB\QueryBuilder\ExpressionBuilder;
+use OC\DB\QueryBuilder\QueryFunction;
+use OCP\DB\QueryBuilder\ILiteral;
+use OCP\DB\QueryBuilder\IParameter;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\DB\QueryBuilder\IQueryFunction;
+
class SqliteExpressionBuilder extends ExpressionBuilder {
/**
* @inheritdoc
@@ -34,4 +40,33 @@ class SqliteExpressionBuilder extends ExpressionBuilder {
public function iLike($x, $y, $type = null): string {
return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y), $type);
}
+
+ /**
+ * @param mixed $column
+ * @param mixed|null $type
+ * @return array|IQueryFunction|string
+ */
+ protected function prepareColumn($column, $type) {
+ if ($type === IQueryBuilder::PARAM_DATE && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) {
+ return $this->castColumn($column, $type);
+ }
+
+ return parent::prepareColumn($column, $type);
+ }
+
+ /**
+ * Returns a IQueryFunction that casts the column to the given type
+ *
+ * @param string $column
+ * @param mixed $type One of IQueryBuilder::PARAM_*
+ * @return IQueryFunction
+ */
+ public function castColumn($column, $type): IQueryFunction {
+ if ($type === IQueryBuilder::PARAM_DATE) {
+ $column = $this->helper->quoteColumnName($column);
+ return new QueryFunction('DATETIME(' . $column . ')');
+ }
+
+ return parent::castColumn($column, $type);
+ }
}
diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php
index 30dc02b0c16..c2818911ccf 100644
--- a/lib/private/DB/QueryBuilder/QueryBuilder.php
+++ b/lib/private/DB/QueryBuilder/QueryBuilder.php
@@ -866,7 +866,7 @@ class QueryBuilder implements IQueryBuilder {
public function where(...$predicates) {
if ($this->getQueryPart('where') !== null && $this->systemConfig->getValue('debug', false)) {
// Only logging a warning, not throwing for now.
- $e = new QueryException('Using where() on non-empty WHERE part, please verify it is intentional to not call whereAnd() or whereOr() instead. Otherwise consider creating a new query builder object or call resetQueryPart(\'where\') first.');
+ $e = new QueryException('Using where() on non-empty WHERE part, please verify it is intentional to not call andWhere() or orWhere() instead. Otherwise consider creating a new query builder object or call resetQueryPart(\'where\') first.');
$this->logger->warning($e->getMessage(), ['exception' => $e]);
}
diff --git a/lib/private/DB/SetTransactionIsolationLevel.php b/lib/private/DB/SetTransactionIsolationLevel.php
index b067edde441..9d9323664c8 100644
--- a/lib/private/DB/SetTransactionIsolationLevel.php
+++ b/lib/private/DB/SetTransactionIsolationLevel.php
@@ -26,8 +26,10 @@ declare(strict_types=1);
namespace OC\DB;
use Doctrine\Common\EventSubscriber;
+use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
+use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\TransactionIsolationLevel;
class SetTransactionIsolationLevel implements EventSubscriber {
@@ -36,7 +38,13 @@ class SetTransactionIsolationLevel implements EventSubscriber {
* @return void
*/
public function postConnect(ConnectionEventArgs $args) {
- $args->getConnection()->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED);
+ $connection = $args->getConnection();
+ if ($connection instanceof PrimaryReadReplicaConnection && $connection->isConnectedToPrimary()) {
+ $connection->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED);
+ if ($connection->getDatabasePlatform() instanceof MySQLPlatform) {
+ $connection->executeStatement('SET SESSION AUTOCOMMIT=1');
+ }
+ }
}
public function getSubscribedEvents() {
diff --git a/lib/private/Dashboard/Manager.php b/lib/private/Dashboard/Manager.php
index afe28872e69..25a2df5d9da 100644
--- a/lib/private/Dashboard/Manager.php
+++ b/lib/private/Dashboard/Manager.php
@@ -33,8 +33,8 @@ use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
-use Throwable;
use Psr\Log\LoggerInterface;
+use Throwable;
class Manager implements IManager {
/** @var array */
@@ -115,7 +115,7 @@ class Manager implements IManager {
$endTime = microtime(true);
$duration = $endTime - $startTime;
if ($duration > 1) {
- \OC::$server->get(LoggerInterface::class)->error(
+ \OC::$server->get(LoggerInterface::class)->info(
'Dashboard widget {widget} took {duration} seconds to load.',
[
'widget' => $widget->getId(),
diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php
index 2dd2abe5408..d1be1f50330 100644
--- a/lib/private/DirectEditing/Manager.php
+++ b/lib/private/DirectEditing/Manager.php
@@ -25,8 +25,9 @@
*/
namespace OC\DirectEditing;
-use Doctrine\DBAL\FetchMode;
+use \OCP\DirectEditing\IManager;
use \OCP\Files\Folder;
+use Doctrine\DBAL\FetchMode;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
@@ -34,7 +35,6 @@ use OCP\Constants;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DirectEditing\ACreateFromTemplate;
use OCP\DirectEditing\IEditor;
-use \OCP\DirectEditing\IManager;
use OCP\DirectEditing\IToken;
use OCP\Encryption\IManager as EncryptionManager;
use OCP\Files\File;
diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php
index e58b3656593..a6bc72ef18f 100644
--- a/lib/private/Encryption/EncryptionWrapper.php
+++ b/lib/private/Encryption/EncryptionWrapper.php
@@ -53,8 +53,8 @@ class EncryptionWrapper {
* EncryptionWrapper constructor.
*/
public function __construct(ArrayCache $arrayCache,
- Manager $manager,
- LoggerInterface $logger
+ Manager $manager,
+ LoggerInterface $logger
) {
$this->arrayCache = $arrayCache;
$this->manager = $manager;
diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php
index daab097ce7c..f2b1de23234 100644
--- a/lib/private/Encryption/File.php
+++ b/lib/private/Encryption/File.php
@@ -27,9 +27,9 @@
*/
namespace OC\Encryption;
-use OCP\Cache\CappedMemoryCache;
use OCA\Files_External\Service\GlobalStoragesService;
use OCP\App\IAppManager;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Share\IManager;
@@ -47,8 +47,8 @@ class File implements \OCP\Encryption\IFile {
private ?IAppManager $appManager = null;
public function __construct(Util $util,
- IRootFolder $rootFolder,
- IManager $shareManager) {
+ IRootFolder $rootFolder,
+ IManager $shareManager) {
$this->util = $util;
$this->cache = new CappedMemoryCache();
$this->rootFolder = $rootFolder;
diff --git a/lib/private/Encryption/HookManager.php b/lib/private/Encryption/HookManager.php
index 5081bcccf94..afcb7ce3763 100644
--- a/lib/private/Encryption/HookManager.php
+++ b/lib/private/Encryption/HookManager.php
@@ -24,8 +24,8 @@
namespace OC\Encryption;
use OC\Files\Filesystem;
-use OC\Files\View;
use OC\Files\SetupManager;
+use OC\Files\View;
use Psr\Log\LoggerInterface;
class HookManager {
diff --git a/lib/private/Encryption/Update.php b/lib/private/Encryption/Update.php
index 2e390177baf..1d9ec8510d0 100644
--- a/lib/private/Encryption/Update.php
+++ b/lib/private/Encryption/Update.php
@@ -62,14 +62,14 @@ class Update {
* @param string $uid
*/
public function __construct(
- View $view,
- Util $util,
- Mount\Manager $mountManager,
- Manager $encryptionManager,
- File $file,
- LoggerInterface $logger,
- $uid
- ) {
+ View $view,
+ Util $util,
+ Mount\Manager $mountManager,
+ Manager $encryptionManager,
+ File $file,
+ LoggerInterface $logger,
+ $uid
+ ) {
$this->view = $view;
$this->util = $util;
$this->mountManager = $mountManager;
diff --git a/lib/private/EventDispatcher/EventDispatcher.php b/lib/private/EventDispatcher/EventDispatcher.php
index 88c6b2cf32c..14c13d516c0 100644
--- a/lib/private/EventDispatcher/EventDispatcher.php
+++ b/lib/private/EventDispatcher/EventDispatcher.php
@@ -27,17 +27,17 @@ declare(strict_types=1);
*/
namespace OC\EventDispatcher;
-use OC\Log;
-use Psr\Log\LoggerInterface;
-use function get_class;
use OC\Broadcast\Events\BroadcastEvent;
+use OC\Log;
use OCP\Broadcast\Events\IBroadcastEvent;
use OCP\EventDispatcher\ABroadcastedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IContainer;
use OCP\IServerContainer;
+use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyDispatcher;
+use function get_class;
class EventDispatcher implements IEventDispatcher {
/** @var SymfonyDispatcher */
@@ -50,8 +50,8 @@ class EventDispatcher implements IEventDispatcher {
private $logger;
public function __construct(SymfonyDispatcher $dispatcher,
- IServerContainer $container,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ LoggerInterface $logger) {
$this->dispatcher = $dispatcher;
$this->container = $container;
$this->logger = $logger;
@@ -64,19 +64,19 @@ class EventDispatcher implements IEventDispatcher {
}
public function addListener(string $eventName,
- callable $listener,
- int $priority = 0): void {
+ callable $listener,
+ int $priority = 0): void {
$this->dispatcher->addListener($eventName, $listener, $priority);
}
public function removeListener(string $eventName,
- callable $listener): void {
+ callable $listener): void {
$this->dispatcher->removeListener($eventName, $listener);
}
public function addServiceListener(string $eventName,
- string $className,
- int $priority = 0): void {
+ string $className,
+ int $priority = 0): void {
$listener = new ServiceEventListener(
$this->container,
$className,
@@ -90,7 +90,7 @@ class EventDispatcher implements IEventDispatcher {
* @deprecated
*/
public function dispatch(string $eventName,
- Event $event): void {
+ Event $event): void {
$this->dispatcher->dispatch($event, $eventName);
if ($event instanceof ABroadcastedEvent && !$event->isPropagationStopped()) {
diff --git a/lib/private/EventDispatcher/ServiceEventListener.php b/lib/private/EventDispatcher/ServiceEventListener.php
index 21cdf7f8cc2..a7bbbcd82aa 100644
--- a/lib/private/EventDispatcher/ServiceEventListener.php
+++ b/lib/private/EventDispatcher/ServiceEventListener.php
@@ -54,8 +54,8 @@ final class ServiceEventListener {
private $service;
public function __construct(IServerContainer $container,
- string $class,
- LoggerInterface $logger) {
+ string $class,
+ LoggerInterface $logger) {
$this->container = $container;
$this->class = $class;
$this->logger = $logger;
diff --git a/lib/private/Federation/CloudFederationShare.php b/lib/private/Federation/CloudFederationShare.php
index 0f79ba521ea..4b741b28bee 100644
--- a/lib/private/Federation/CloudFederationShare.php
+++ b/lib/private/Federation/CloudFederationShare.php
@@ -57,16 +57,16 @@ class CloudFederationShare implements ICloudFederationShare {
* @param string $sharedSecret
*/
public function __construct($shareWith = '',
- $name = '',
- $description = '',
- $providerId = '',
- $owner = '',
- $ownerDisplayName = '',
- $sharedBy = '',
- $sharedByDisplayName = '',
- $shareType = '',
- $resourceType = '',
- $sharedSecret = ''
+ $name = '',
+ $description = '',
+ $providerId = '',
+ $owner = '',
+ $ownerDisplayName = '',
+ $sharedBy = '',
+ $sharedByDisplayName = '',
+ $shareType = '',
+ $resourceType = '',
+ $sharedSecret = ''
) {
$this->setShareWith($shareWith);
$this->setResourceName($name);
diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php
index 237fcb42e03..1c632c3062f 100644
--- a/lib/private/Files/AppData/AppData.php
+++ b/lib/private/Files/AppData/AppData.php
@@ -26,9 +26,9 @@ declare(strict_types=1);
*/
namespace OC\Files\AppData;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\SimpleFS\SimpleFolder;
use OC\SystemConfig;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\Folder;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@@ -53,8 +53,8 @@ class AppData implements IAppData {
* @param string $appId
*/
public function __construct(IRootFolder $rootFolder,
- SystemConfig $systemConfig,
- string $appId) {
+ SystemConfig $systemConfig,
+ string $appId) {
$this->rootFolder = $rootFolder;
$this->config = $systemConfig;
$this->appId = $appId;
diff --git a/lib/private/Files/AppData/Factory.php b/lib/private/Files/AppData/Factory.php
index 03f8fdedcbd..a16c3df327d 100644
--- a/lib/private/Files/AppData/Factory.php
+++ b/lib/private/Files/AppData/Factory.php
@@ -39,7 +39,7 @@ class Factory implements IAppDataFactory {
private array $folders = [];
public function __construct(IRootFolder $rootFolder,
- SystemConfig $systemConfig) {
+ SystemConfig $systemConfig) {
$this->rootFolder = $rootFolder;
$this->config = $systemConfig;
}
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index 67d01bb6999..052b3c75ce8 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -47,9 +47,9 @@ use OC\Files\Storage\Wrapper\Encryption;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\CacheEntryInsertedEvent;
+use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Cache\CacheEntryUpdatedEvent;
use OCP\Files\Cache\CacheInsertEvent;
-use OCP\Files\Cache\CacheEntryRemovedEvent;
use OCP\Files\Cache\CacheUpdateEvent;
use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
@@ -59,6 +59,7 @@ use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchQuery;
use OCP\Files\Storage\IStorage;
+use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\IDBConnection;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -132,7 +133,8 @@ class Cache implements ICache {
return new CacheQueryBuilder(
$this->connection,
\OC::$server->getSystemConfig(),
- \OC::$server->get(LoggerInterface::class)
+ \OC::$server->get(LoggerInterface::class),
+ \OC::$server->get(IFilesMetadataManager::class),
);
}
@@ -154,6 +156,7 @@ class Cache implements ICache {
public function get($file) {
$query = $this->getQueryBuilder();
$query->selectFileCache();
+ $metadataQuery = $query->selectMetadata();
if (is_string($file) || $file == '') {
// normalize file
@@ -175,6 +178,7 @@ class Cache implements ICache {
} elseif (!$data) {
return $data;
} else {
+ $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? [];
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}
}
@@ -239,11 +243,14 @@ class Cache implements ICache {
->whereParent($fileId)
->orderBy('name', 'ASC');
+ $metadataQuery = $query->selectMetadata();
+
$result = $query->execute();
$files = $result->fetchAll();
$result->closeCursor();
- return array_map(function (array $data) {
+ return array_map(function (array $data) use ($metadataQuery) {
+ $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? [];
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}, $files);
}
@@ -447,7 +454,7 @@ class Cache implements ICache {
$params = [];
$extensionParams = [];
foreach ($data as $name => $value) {
- if (array_search($name, $fields) !== false) {
+ if (in_array($name, $fields)) {
if ($name === 'path') {
$params['path_hash'] = md5($value);
} elseif ($name === 'mimetype') {
@@ -467,7 +474,7 @@ class Cache implements ICache {
}
$params[$name] = $value;
}
- if (array_search($name, $extensionFields) !== false) {
+ if (in_array($name, $extensionFields)) {
$extensionParams[$name] = $value;
}
}
@@ -599,9 +606,12 @@ class Cache implements ICache {
}
/** @var ICacheEntry[] $childFolders */
- $childFolders = array_filter($children, function ($child) {
- return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
- });
+ $childFolders = [];
+ foreach ($children as $child) {
+ if ($child->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
+ $childFolders[] = $child;
+ }
+ }
foreach ($childFolders as $folder) {
$parentIds[] = $folder->getId();
$queue[] = $folder->getId();
diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php
index 34d2177b84e..365d28fc8c5 100644
--- a/lib/private/Files/Cache/CacheQueryBuilder.php
+++ b/lib/private/Files/Cache/CacheQueryBuilder.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -28,6 +29,8 @@ namespace OC\Files\Cache;
use OC\DB\QueryBuilder\QueryBuilder;
use OC\SystemConfig;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;
@@ -35,9 +38,14 @@ use Psr\Log\LoggerInterface;
* Query builder with commonly used helpers for filecache queries
*/
class CacheQueryBuilder extends QueryBuilder {
- private $alias = null;
-
- public function __construct(IDBConnection $connection, SystemConfig $systemConfig, LoggerInterface $logger) {
+ private ?string $alias = null;
+
+ public function __construct(
+ IDBConnection $connection,
+ SystemConfig $systemConfig,
+ LoggerInterface $logger,
+ private IFilesMetadataManager $filesMetadataManager,
+ ) {
parent::__construct($connection, $systemConfig, $logger);
}
@@ -63,7 +71,7 @@ class CacheQueryBuilder extends QueryBuilder {
public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
$name = $alias ?: 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
- 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
+ 'storage_mtime', 'encrypted', 'etag', "$name.permissions", 'checksum', 'unencrypted_size')
->from('filecache', $name);
if ($joinExtendedCache) {
@@ -126,4 +134,15 @@ class CacheQueryBuilder extends QueryBuilder {
return $this;
}
+
+ /**
+ * join metadata to current query builder and returns an helper
+ *
+ * @return IMetadataQuery|null NULL if no metadata have never been generated
+ */
+ public function selectMetadata(): ?IMetadataQuery {
+ $metadataQuery = $this->filesMetadataManager->getMetadataQuery($this, $this->alias, 'fileid');
+ $metadataQuery?->retrieveMetadata();
+ return $metadataQuery;
+ }
}
diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php
index 15c089a0f11..d8c5e66e129 100644
--- a/lib/private/Files/Cache/QuerySearchHelper.php
+++ b/lib/private/Files/Cache/QuerySearchHelper.php
@@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tobias Kaminsky <tobias@kaminsky.me>
@@ -37,52 +38,47 @@ use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchQuery;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUser;
use Psr\Log\LoggerInterface;
class QuerySearchHelper {
- /** @var IMimeTypeLoader */
- private $mimetypeLoader;
- /** @var IDBConnection */
- private $connection;
- /** @var SystemConfig */
- private $systemConfig;
- private LoggerInterface $logger;
- /** @var SearchBuilder */
- private $searchBuilder;
- /** @var QueryOptimizer */
- private $queryOptimizer;
- private IGroupManager $groupManager;
-
public function __construct(
- IMimeTypeLoader $mimetypeLoader,
- IDBConnection $connection,
- SystemConfig $systemConfig,
- LoggerInterface $logger,
- SearchBuilder $searchBuilder,
- QueryOptimizer $queryOptimizer,
- IGroupManager $groupManager,
+ private IMimeTypeLoader $mimetypeLoader,
+ private IDBConnection $connection,
+ private SystemConfig $systemConfig,
+ private LoggerInterface $logger,
+ private SearchBuilder $searchBuilder,
+ private QueryOptimizer $queryOptimizer,
+ private IGroupManager $groupManager,
+ private IFilesMetadataManager $filesMetadataManager,
) {
- $this->mimetypeLoader = $mimetypeLoader;
- $this->connection = $connection;
- $this->systemConfig = $systemConfig;
- $this->logger = $logger;
- $this->searchBuilder = $searchBuilder;
- $this->queryOptimizer = $queryOptimizer;
- $this->groupManager = $groupManager;
}
protected function getQueryBuilder() {
return new CacheQueryBuilder(
$this->connection,
$this->systemConfig,
- $this->logger
+ $this->logger,
+ $this->filesMetadataManager,
);
}
- protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void {
+ /**
+ * @param CacheQueryBuilder $query
+ * @param ISearchQuery $searchQuery
+ * @param array $caches
+ * @param IMetadataQuery|null $metadataQuery
+ */
+ protected function applySearchConstraints(
+ CacheQueryBuilder $query,
+ ISearchQuery $searchQuery,
+ array $caches,
+ ?IMetadataQuery $metadataQuery = null
+ ): void {
$storageFilters = array_values(array_map(function (ICache $cache) {
return $cache->getQueryFilterForStorage();
}, $caches));
@@ -90,12 +86,12 @@ class QuerySearchHelper {
$filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]);
$this->queryOptimizer->processOperator($filter);
- $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter);
+ $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter, $metadataQuery);
if ($searchExpr) {
$query->andWhere($searchExpr);
}
- $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
+ $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder(), $metadataQuery);
if ($searchQuery->getLimit()) {
$query->setMaxResults($searchQuery->getLimit());
@@ -144,6 +140,11 @@ class QuerySearchHelper {
));
}
+
+ protected function equipQueryForShares(CacheQueryBuilder $query): void {
+ $query->join('file', 'share', 's', $query->expr()->eq('file.fileid', 's.file_source'));
+ }
+
/**
* Perform a file system search in multiple caches
*
@@ -175,19 +176,31 @@ class QuerySearchHelper {
$query = $builder->selectFileCache('file', false);
$requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());
+
if (in_array('systemtag', $requestedFields)) {
$this->equipQueryForSystemTags($query, $this->requireUser($searchQuery));
}
if (in_array('tagname', $requestedFields) || in_array('favorite', $requestedFields)) {
$this->equipQueryForDavTags($query, $this->requireUser($searchQuery));
}
+ if (in_array('owner', $requestedFields) || in_array('share_with', $requestedFields) || in_array('share_type', $requestedFields)) {
+ $this->equipQueryForShares($query);
+ }
- $this->applySearchConstraints($query, $searchQuery, $caches);
+ $metadataQuery = $query->selectMetadata();
+
+ $this->applySearchConstraints($query, $searchQuery, $caches, $metadataQuery);
$result = $query->execute();
$files = $result->fetchAll();
- $rawEntries = array_map(function (array $data) {
+ $rawEntries = array_map(function (array $data) use ($metadataQuery) {
+ // migrate to null safe ...
+ if ($metadataQuery === null) {
+ $data['metadata'] = [];
+ } else {
+ $data['metadata'] = $metadataQuery->extractMetadata($data)->asArray();
+ }
return Cache::cacheEntryFromData($data, $this->mimetypeLoader);
}, $files);
diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php
index 074e88e7639..0c82e21e30d 100644
--- a/lib/private/Files/Cache/Scanner.php
+++ b/lib/private/Files/Cache/Scanner.php
@@ -37,14 +37,14 @@ namespace OC\Files\Cache;
use Doctrine\DBAL\Exception;
use OC\Files\Storage\Wrapper\Encryption;
+use OC\Files\Storage\Wrapper\Jail;
+use OC\Hooks\BasicEmitter;
use OCP\Files\Cache\IScanner;
use OCP\Files\ForbiddenException;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IReliableEtagStorage;
use OCP\IDBConnection;
use OCP\Lock\ILockingProvider;
-use OC\Files\Storage\Wrapper\Jail;
-use OC\Hooks\BasicEmitter;
use Psr\Log\LoggerInterface;
/**
@@ -203,7 +203,9 @@ class Scanner extends BasicEmitter implements IScanner {
$fileId = $cacheData['fileid'];
$data['fileid'] = $fileId;
// only reuse data if the file hasn't explicitly changed
- if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
+ $mtimeUnchanged = isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime'];
+ // if the folder is marked as unscanned, never reuse etags
+ if ($mtimeUnchanged && $cacheData['size'] !== -1) {
$data['mtime'] = $cacheData['mtime'];
if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
$data['size'] = $cacheData['size'];
@@ -220,6 +222,11 @@ class Scanner extends BasicEmitter implements IScanner {
// Only update metadata that has changed
$newData = array_diff_assoc($data, $cacheData->getData());
+
+ // make it known to the caller that etag has been changed and needs propagation
+ if (isset($newData['etag'])) {
+ $data['etag_changed'] = true;
+ }
} else {
// we only updated unencrypted_size if it's already set
unset($data['unencrypted_size']);
@@ -388,16 +395,20 @@ class Scanner extends BasicEmitter implements IScanner {
* @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|float $oldSize) {
+ protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
if ($reuse === -1) {
$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
}
$this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]);
$size = 0;
- $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
+ $childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size, $etagChanged);
foreach ($childQueue as $child => [$childId, $childSize]) {
- $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock, $childSize);
+ // "etag changed" propagates up, but not down, so we pass `false` to the children even if we already know that the etag of the current folder changed
+ $childEtagChanged = false;
+ $childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock, $childSize, $childEtagChanged);
+ $etagChanged |= $childEtagChanged;
+
if ($childSize === -1) {
$size = -1;
} elseif ($size !== -1) {
@@ -410,8 +421,17 @@ class Scanner extends BasicEmitter implements IScanner {
if ($this->storage->instanceOfStorage(Encryption::class)) {
$this->cache->calculateFolderSize($path);
} else {
- if ($this->cacheActive && $oldSize !== $size) {
- $this->cache->update($folderId, ['size' => $size]);
+ if ($this->cacheActive) {
+ $updatedData = [];
+ if ($oldSize !== $size) {
+ $updatedData['size'] = $size;
+ }
+ if ($etagChanged) {
+ $updatedData['etag'] = uniqid();
+ }
+ if ($updatedData) {
+ $this->cache->update($folderId, $updatedData);
+ }
}
}
$this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]);
@@ -421,7 +441,7 @@ class Scanner extends BasicEmitter implements IScanner {
/**
* @param bool|IScanner::SCAN_RECURSIVE_INCOMPLETE $recursive
*/
- private function handleChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float &$size): array {
+ private function handleChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float &$size, bool &$etagChanged): 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));
@@ -469,6 +489,10 @@ class Scanner extends BasicEmitter implements IScanner {
} elseif ($size !== -1) {
$size += $data['size'];
}
+
+ if (isset($data['etag_changed']) && $data['etag_changed']) {
+ $etagChanged = true;
+ }
}
} catch (Exception $ex) {
// might happen if inserting duplicate while a scanning
diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php
index b9a70bbd39b..38161ec9cc6 100644
--- a/lib/private/Files/Cache/SearchBuilder.php
+++ b/lib/private/Files/Cache/SearchBuilder.php
@@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tobias Kaminsky <tobias@kaminsky.me>
@@ -32,6 +33,7 @@ use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchOrder;
+use OCP\FilesMetadata\IMetadataQuery;
/**
* Tools for transforming search queries into database queries
@@ -45,6 +47,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte',
ISearchComparison::COMPARE_LESS_THAN => 'lt',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte',
+ ISearchComparison::COMPARE_DEFINED => 'isNotNull',
];
protected static $searchOperatorNegativeMap = [
@@ -55,6 +58,7 @@ class SearchBuilder {
ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt',
ISearchComparison::COMPARE_LESS_THAN => 'gte',
ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'gt',
+ ISearchComparison::COMPARE_DEFINED => 'isNull',
];
public const TAG_FAVORITE = '_$!<Favorite>!$_';
@@ -76,7 +80,7 @@ class SearchBuilder {
return array_reduce($operator->getArguments(), function (array $fields, ISearchOperator $operator) {
return array_unique(array_merge($fields, $this->extractRequestedFields($operator)));
}, []);
- } elseif ($operator instanceof ISearchComparison) {
+ } elseif ($operator instanceof ISearchComparison && !$operator->getExtra()) {
return [$operator->getField()];
}
return [];
@@ -86,13 +90,21 @@ class SearchBuilder {
* @param IQueryBuilder $builder
* @param ISearchOperator[] $operators
*/
- public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
- return array_filter(array_map(function ($operator) use ($builder) {
- return $this->searchOperatorToDBExpr($builder, $operator);
+ public function searchOperatorArrayToDBExprArray(
+ IQueryBuilder $builder,
+ array $operators,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
+ return array_filter(array_map(function ($operator) use ($builder, $metadataQuery) {
+ return $this->searchOperatorToDBExpr($builder, $operator, $metadataQuery);
}, $operators));
}
- public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
+ public function searchOperatorToDBExpr(
+ IQueryBuilder $builder,
+ ISearchOperator $operator,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
$expr = $builder->expr();
if ($operator instanceof ISearchBinaryOperator) {
@@ -104,29 +116,37 @@ class SearchBuilder {
case ISearchBinaryOperator::OPERATOR_NOT:
$negativeOperator = $operator->getArguments()[0];
if ($negativeOperator instanceof ISearchComparison) {
- return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap);
+ return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap, $metadataQuery);
} else {
throw new \InvalidArgumentException('Binary operators inside "not" is not supported');
}
// no break
case ISearchBinaryOperator::OPERATOR_AND:
- return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
+ return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery));
case ISearchBinaryOperator::OPERATOR_OR:
- return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments()));
+ return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery));
default:
throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType());
}
} elseif ($operator instanceof ISearchComparison) {
- return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap);
+ return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap, $metadataQuery);
} else {
throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator));
}
}
- private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) {
- $this->validateComparison($comparison);
+ private function searchComparisonToDBExpr(
+ IQueryBuilder $builder,
+ ISearchComparison $comparison,
+ array $operatorMap,
+ ?IMetadataQuery $metadataQuery = null
+ ) {
+ if ($comparison->getExtra()) {
+ [$field, $value, $type] = $this->getExtraOperatorField($comparison, $metadataQuery);
+ } else {
+ [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison);
+ }
- [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison);
if (isset($operatorMap[$type])) {
$queryOperator = $operatorMap[$type];
return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value));
@@ -136,9 +156,12 @@ class SearchBuilder {
}
private function getOperatorFieldAndValue(ISearchComparison $operator) {
+ $this->validateComparison($operator);
+
$field = $operator->getField();
$value = $operator->getValue();
$type = $operator->getType();
+
if ($field === 'mimetype') {
$value = (string)$value;
if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) {
@@ -171,6 +194,8 @@ class SearchBuilder {
} elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL && $operator->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true)) {
$field = 'path_hash';
$value = md5((string)$value);
+ } elseif ($field === 'owner') {
+ $field = 'uid_owner';
}
return [$field, $value, $type];
}
@@ -187,6 +212,9 @@ class SearchBuilder {
'favorite' => 'boolean',
'fileid' => 'integer',
'storage' => 'integer',
+ 'share_with' => 'string',
+ 'share_type' => 'integer',
+ 'owner' => 'string',
];
$comparisons = [
'mimetype' => ['eq', 'like'],
@@ -199,6 +227,9 @@ class SearchBuilder {
'favorite' => ['eq'],
'fileid' => ['eq'],
'storage' => ['eq'],
+ 'share_with' => ['eq'],
+ 'share_type' => ['eq'],
+ 'owner' => ['eq'],
];
if (!isset($types[$operator->getField()])) {
@@ -213,6 +244,24 @@ class SearchBuilder {
}
}
+
+ private function getExtraOperatorField(ISearchComparison $operator, IMetadataQuery $metadataQuery): array {
+ $field = $operator->getField();
+ $value = $operator->getValue();
+ $type = $operator->getType();
+
+ switch($operator->getExtra()) {
+ case IMetadataQuery::EXTRA:
+ $metadataQuery->joinIndex($field); // join index table if not joined yet
+ $field = $metadataQuery->getMetadataValueField($field);
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid extra type: ' . $operator->getExtra());
+ }
+
+ return [$field, $value, $type];
+ }
+
private function getParameterForValue(IQueryBuilder $builder, $value) {
if ($value instanceof \DateTime) {
$value = $value->getTimestamp();
@@ -228,24 +277,32 @@ class SearchBuilder {
/**
* @param IQueryBuilder $query
* @param ISearchOrder[] $orders
+ * @param IMetadataQuery|null $metadataQuery
*/
- public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) {
+ public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders, ?IMetadataQuery $metadataQuery = null): void {
foreach ($orders as $order) {
$field = $order->getField();
- if ($field === 'fileid') {
- $field = 'file.fileid';
- }
+ switch ($order->getExtra()) {
+ case IMetadataQuery::EXTRA:
+ $metadataQuery->joinIndex($field); // join index table if not joined yet
+ $field = $metadataQuery->getMetadataValueField($order->getField());
+ break;
- // Mysql really likes to pick an index for sorting if it can't fully satisfy the where
- // filter with an index, since search queries pretty much never are fully filtered by index
- // mysql often picks an index for sorting instead of the much more useful index for filtering.
- //
- // By changing the order by to an expression, mysql isn't smart enough to see that it could still
- // use the index, so it instead picks an index for the filtering
- if ($field === 'mtime') {
- $field = $query->func()->add($field, $query->createNamedParameter(0));
- }
+ default:
+ if ($field === 'fileid') {
+ $field = 'file.fileid';
+ }
+ // Mysql really likes to pick an index for sorting if it can't fully satisfy the where
+ // filter with an index, since search queries pretty much never are fully filtered by index
+ // mysql often picks an index for sorting instead of the much more useful index for filtering.
+ //
+ // By changing the order by to an expression, mysql isn't smart enough to see that it could still
+ // use the index, so it instead picks an index for the filtering
+ if ($field === 'mtime') {
+ $field = $query->func()->add($field, $query->createNamedParameter(0));
+ }
+ }
$query->addOrderBy($field, $order->getDirection());
}
}
diff --git a/lib/private/Files/Cache/Updater.php b/lib/private/Files/Cache/Updater.php
index 457dd207e9d..a6f2f3375a4 100644
--- a/lib/private/Files/Cache/Updater.php
+++ b/lib/private/Files/Cache/Updater.php
@@ -119,7 +119,7 @@ class Updater implements IUpdater {
* @param string $path
* @param int $time
*/
- public function update($path, $time = null) {
+ public function update($path, $time = null, ?int $sizeDifference = null) {
if (!$this->enabled or Scanner::isPartialFile($path)) {
return;
}
@@ -128,20 +128,22 @@ class Updater implements IUpdater {
}
$data = $this->scanner->scan($path, Scanner::SCAN_SHALLOW, -1, false);
- if (
- isset($data['oldSize']) && isset($data['size']) &&
- !$data['encrypted'] // encryption is a pita and touches the cache itself
- ) {
+
+ if (isset($data['oldSize']) && isset($data['size'])) {
$sizeDifference = $data['size'] - $data['oldSize'];
- } else {
- // scanner didn't provide size info, fallback to full size calculation
- $sizeDifference = 0;
- if ($this->cache instanceof Cache) {
- $this->cache->correctFolderSize($path, $data);
- }
+ }
+
+ // encryption is a pita and touches the cache itself
+ if (isset($data['encrypted']) && !!$data['encrypted']) {
+ $sizeDifference = null;
+ }
+
+ // scanner didn't provide size info, fallback to full size calculation
+ if ($this->cache instanceof Cache && $sizeDifference === null) {
+ $this->cache->correctFolderSize($path, $data);
}
$this->correctParentStorageMtime($path);
- $this->propagator->propagateChange($path, $time, $sizeDifference);
+ $this->propagator->propagateChange($path, $time, $sizeDifference ?? 0);
}
/**
diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php
index acc76f263dc..61ea5b2f848 100644
--- a/lib/private/Files/Cache/Watcher.php
+++ b/lib/private/Files/Cache/Watcher.php
@@ -129,7 +129,7 @@ class Watcher implements IWatcher {
* @return bool
*/
public function needsUpdate($path, $cachedData) {
- if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and array_search($path, $this->checkedPaths) === false)) {
+ if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and !in_array($path, $this->checkedPaths))) {
$this->checkedPaths[] = $path;
return $this->storage->hasUpdated($path, $cachedData['storage_mtime']);
}
diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php
index d8cf3eb61d7..73c9a017019 100644
--- a/lib/private/Files/Cache/Wrapper/CacheJail.php
+++ b/lib/private/Files/Cache/Wrapper/CacheJail.php
@@ -52,8 +52,6 @@ class CacheJail extends CacheWrapper {
public function __construct($cache, $root) {
parent::__construct($cache);
$this->root = $root;
- $this->connection = \OC::$server->getDatabaseConnection();
- $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
if ($cache instanceof CacheJail) {
$this->unjailedRoot = $cache->getSourcePath($root);
diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php
index 43c9fae63ec..7c97135a565 100644
--- a/lib/private/Files/Config/CachedMountInfo.php
+++ b/lib/private/Files/Config/CachedMountInfo.php
@@ -35,6 +35,7 @@ class CachedMountInfo implements ICachedMountInfo {
protected ?int $mountId;
protected string $rootInternalPath;
protected string $mountProvider;
+ protected string $key;
/**
* CachedMountInfo constructor.
@@ -65,6 +66,7 @@ class CachedMountInfo implements ICachedMountInfo {
throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters");
}
$this->mountProvider = $mountProvider;
+ $this->key = $rootId . '::' . $mountPoint;
}
/**
@@ -132,4 +134,8 @@ class CachedMountInfo implements ICachedMountInfo {
public function getMountProvider(): string {
return $this->mountProvider;
}
+
+ public function getKey(): string {
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php
index 78055a2cdb8..7e4acb2e129 100644
--- a/lib/private/Files/Config/LazyStorageMountInfo.php
+++ b/lib/private/Files/Config/LazyStorageMountInfo.php
@@ -39,6 +39,7 @@ class LazyStorageMountInfo extends CachedMountInfo {
$this->rootId = 0;
$this->storageId = 0;
$this->mountPoint = '';
+ $this->key = '';
}
/**
@@ -87,4 +88,11 @@ class LazyStorageMountInfo extends CachedMountInfo {
public function getMountProvider(): string {
return $this->mount->getMountProvider();
}
+
+ public function getKey(): string {
+ if (!$this->key) {
+ $this->key = $this->getRootId() . '::' . $this->getMountPoint();
+ }
+ return $this->key;
+ }
}
diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php
index ae6481e45bb..d251199fd43 100644
--- a/lib/private/Files/Config/MountProviderCollection.php
+++ b/lib/private/Files/Config/MountProviderCollection.php
@@ -238,6 +238,11 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
$mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
return array_merge($mounts, $providerMounts);
}, []);
+
+ if (count($mounts) === 0) {
+ throw new \Exception("No root mounts provided by any provider");
+ }
+
return $mounts;
}
diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php
index 8a6b818d413..2fb7a7d83f4 100644
--- a/lib/private/Files/Config/UserMountCache.php
+++ b/lib/private/Files/Config/UserMountCache.php
@@ -29,13 +29,11 @@
namespace OC\Files\Config;
use OCP\Cache\CappedMemoryCache;
-use OCA\Files_Sharing\SharedMount;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Diagnostics\IEventLogger;
use OCP\Files\Config\ICachedMountFileInfo;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
-use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IUser;
@@ -78,41 +76,27 @@ class UserMountCache implements IUserMountCache {
public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) {
$this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user');
- // filter out non-proper storages coming from unit tests
- $mounts = array_filter($mounts, function (IMountPoint $mount) {
- return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache());
- });
- /** @var ICachedMountInfo[] $newMounts */
- $newMounts = array_map(function (IMountPoint $mount) use ($user) {
+ /** @var array<string, ICachedMountInfo> $newMounts */
+ $newMounts = [];
+ foreach ($mounts as $mount) {
// filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet)
- if ($mount->getStorageRootId() === -1) {
- return null;
- } else {
- return new LazyStorageMountInfo($user, $mount);
+ if ($mount->getStorageRootId() !== -1) {
+ $mountInfo = new LazyStorageMountInfo($user, $mount);
+ $newMounts[$mountInfo->getKey()] = $mountInfo;
}
- }, $mounts);
- $newMounts = array_values(array_filter($newMounts));
- $newMountKeys = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId() . '::' . $mount->getMountPoint();
- }, $newMounts);
- $newMounts = array_combine($newMountKeys, $newMounts);
+ }
$cachedMounts = $this->getMountsForUser($user);
if (is_array($mountProviderClasses)) {
$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
// for existing mounts that didn't have a mount provider set
// we still want the ones that map to new mounts
- $mountKey = $mountInfo->getRootId() . '::' . $mountInfo->getMountPoint();
- if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountKey])) {
+ if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
return true;
}
return in_array($mountInfo->getMountProvider(), $mountProviderClasses);
});
}
- $cachedRootKeys = array_map(function (ICachedMountInfo $mount) {
- return $mount->getRootId() . '::' . $mount->getMountPoint();
- }, $cachedMounts);
- $cachedMounts = array_combine($cachedRootKeys, $cachedMounts);
$addedMounts = [];
$removedMounts = [];
@@ -131,46 +115,44 @@ class UserMountCache implements IUserMountCache {
$changedMounts = $this->findChangedMounts($newMounts, $cachedMounts);
- $this->connection->beginTransaction();
- try {
- foreach ($addedMounts as $mount) {
- $this->addToCache($mount);
- /** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$user->getUID()][] = $mount;
- }
- foreach ($removedMounts as $mount) {
- $this->removeFromCache($mount);
- $index = array_search($mount, $this->mountsForUsers[$user->getUID()]);
- unset($this->mountsForUsers[$user->getUID()][$index]);
- }
- foreach ($changedMounts as $mount) {
- $this->updateCachedMount($mount);
+ if ($addedMounts || $removedMounts || $changedMounts) {
+ $this->connection->beginTransaction();
+ $userUID = $user->getUID();
+ try {
+ foreach ($addedMounts as $mount) {
+ $this->addToCache($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ foreach ($removedMounts as $mount) {
+ $this->removeFromCache($mount);
+ unset($this->mountsForUsers[$userUID][$mount->getKey()]);
+ }
+ foreach ($changedMounts as $mount) {
+ $this->updateCachedMount($mount);
+ /** @psalm-suppress InvalidArgument */
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ $this->connection->commit();
+ } catch (\Throwable $e) {
+ $this->connection->rollBack();
+ throw $e;
}
- $this->connection->commit();
- } catch (\Throwable $e) {
- $this->connection->rollBack();
- throw $e;
}
$this->eventLogger->end('fs:setup:user:register');
}
/**
- * @param ICachedMountInfo[] $newMounts
- * @param ICachedMountInfo[] $cachedMounts
+ * @param array<string, ICachedMountInfo> $newMounts
+ * @param array<string, ICachedMountInfo> $cachedMounts
* @return ICachedMountInfo[]
*/
private function findChangedMounts(array $newMounts, array $cachedMounts) {
- $new = [];
- foreach ($newMounts as $mount) {
- $new[$mount->getRootId() . '::' . $mount->getMountPoint()] = $mount;
- }
$changed = [];
- foreach ($cachedMounts as $cachedMount) {
- $key = $cachedMount->getRootId() . '::' . $cachedMount->getMountPoint();
- if (isset($new[$key])) {
- $newMount = $new[$key];
+ foreach ($cachedMounts as $key => $cachedMount) {
+ if (isset($newMounts[$key])) {
+ $newMount = $newMounts[$key];
if (
- $newMount->getMountPoint() !== $cachedMount->getMountPoint() ||
$newMount->getStorageId() !== $cachedMount->getStorageId() ||
$newMount->getMountId() !== $cachedMount->getMountId() ||
$newMount->getMountProvider() !== $cachedMount->getMountProvider()
@@ -247,20 +229,28 @@ class UserMountCache implements IUserMountCache {
* @return ICachedMountInfo[]
*/
public function getMountsForUser(IUser $user) {
- if (!isset($this->mountsForUsers[$user->getUID()])) {
+ $userUID = $user->getUID();
+ if (!isset($this->mountsForUsers[$userUID])) {
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID())));
+ ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($userUID)));
$result = $query->execute();
$rows = $result->fetchAll();
$result->closeCursor();
- $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
+ $this->mountsForUsers[$userUID] = [];
+ /** @var array<string, ICachedMountInfo> $mounts */
+ foreach ($rows as $row) {
+ $mount = $this->dbRowToMountInfo($row);
+ if ($mount !== null) {
+ $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ }
+ }
}
- return $this->mountsForUsers[$user->getUID()];
+ return $this->mountsForUsers[$userUID];
}
/**
@@ -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 7800074460b..5ba2f27b78b 100644
--- a/lib/private/Files/FileInfo.php
+++ b/lib/private/Files/FileInfo.php
@@ -6,6 +6,7 @@
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Piotr M <mrow4a@yahoo.com>
* @author Robin Appelman <robin@icewind.nl>
@@ -32,9 +33,10 @@
*/
namespace OC\Files;
-use OCA\Files_Sharing\ISharedStorage;
+use OC\Files\Mount\HomeMountPoint;
+use OCA\Files_Sharing\External\Mount;
+use OCA\Files_Sharing\ISharedMountPoint;
use OCP\Files\Cache\ICacheEntry;
-use OCP\Files\IHomeStorage;
use OCP\Files\Mount\IMountPoint;
use OCP\IUser;
@@ -121,21 +123,14 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
- if ($offset === 'type') {
- return $this->getType();
- } elseif ($offset === 'etag') {
- return $this->getEtag();
- } elseif ($offset === 'size') {
- return $this->getSize();
- } elseif ($offset === 'mtime') {
- return $this->getMTime();
- } elseif ($offset === 'permissions') {
- return $this->getPermissions();
- } elseif (isset($this->data[$offset])) {
- return $this->data[$offset];
- } else {
- return null;
- }
+ return match ($offset) {
+ 'type' => $this->getType(),
+ 'etag' => $this->getEtag(),
+ 'size' => $this->getSize(),
+ 'mtime' => $this->getMTime(),
+ 'permissions' => $this->getPermissions(),
+ default => $this->data[$offset] ?? null,
+ };
}
/**
@@ -311,13 +306,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return bool
*/
public function isShared() {
- $storage = $this->getStorage();
- return $storage->instanceOfStorage(ISharedStorage::class);
+ return $this->mount instanceof ISharedMountPoint;
}
public function isMounted() {
- $storage = $this->getStorage();
- return !($storage->instanceOfStorage(IHomeStorage::class) || $storage->instanceOfStorage(ISharedStorage::class));
+ $isHome = $this->mount instanceof HomeMountPoint;
+ return !$isHome && !$this->isShared();
}
/**
@@ -416,4 +410,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
public function getParentId(): int {
return $this->data['parent'] ?? -1;
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->data['metadata'] ?? [];
+ }
}
diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php
index 5f7c0c403db..9f0d89052be 100644
--- a/lib/private/Files/Filesystem.php
+++ b/lib/private/Files/Filesystem.php
@@ -37,9 +37,9 @@
*/
namespace OC\Files;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Mount\MountPoint;
use OC\User\NoUserException;
+use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\Node\FilesystemTornDownEvent;
use OCP\Files\Mount\IMountManager;
diff --git a/lib/private/Files/Mount/HomeMountPoint.php b/lib/private/Files/Mount/HomeMountPoint.php
new file mode 100644
index 00000000000..0bec12af5c2
--- /dev/null
+++ b/lib/private/Files/Mount/HomeMountPoint.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Files\Mount;
+
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IUser;
+
+class HomeMountPoint extends MountPoint {
+ private IUser $user;
+
+ public function __construct(
+ IUser $user,
+ $storage,
+ string $mountpoint,
+ array $arguments = null,
+ IStorageFactory $loader = null,
+ array $mountOptions = null,
+ int $mountId = null,
+ string $mountProvider = null
+ ) {
+ parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, $mountProvider);
+ $this->user = $user;
+ }
+
+ public function getUser(): IUser {
+ return $this->user;
+ }
+}
diff --git a/lib/private/Files/Mount/LocalHomeMountProvider.php b/lib/private/Files/Mount/LocalHomeMountProvider.php
index 25a67fc1574..964b607d152 100644
--- a/lib/private/Files/Mount/LocalHomeMountProvider.php
+++ b/lib/private/Files/Mount/LocalHomeMountProvider.php
@@ -38,6 +38,6 @@ class LocalHomeMountProvider implements IHomeMountProvider {
*/
public function getHomeMountForUser(IUser $user, IStorageFactory $loader) {
$arguments = ['user' => $user];
- return new MountPoint('\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
+ return new HomeMountPoint($user, '\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
}
}
diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php
index 805cce658a6..2b2de1fbff1 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
*
@@ -29,10 +30,11 @@ declare(strict_types=1);
namespace OC\Files\Mount;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Filesystem;
use OC\Files\SetupManager;
use OC\Files\SetupManagerFactory;
+use OCP\Cache\CappedMemoryCache;
+use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Mount\IMountPoint;
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/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php
index 889a39fbd9e..3593a95c311 100644
--- a/lib/private/Files/Mount/ObjectHomeMountProvider.php
+++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php
@@ -65,7 +65,7 @@ class ObjectHomeMountProvider implements IHomeMountProvider {
return null;
}
- return new MountPoint('\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
+ return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
}
/**
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php
index ccd10da9d0c..c7462572fed 100644
--- a/lib/private/Files/Node/Folder.php
+++ b/lib/private/Files/Node/Folder.php
@@ -177,7 +177,7 @@ class Folder extends Node implements \OCP\Files\Folder {
* @throws \OCP\Files\NotPermittedException
*/
public function newFile($path, $content = null) {
- if (empty($path)) {
+ if ($path === '') {
throw new NotPermittedException('Could not create as provided path is empty');
}
if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php
index a8e76d95c22..f61eedee66e 100644
--- a/lib/private/Files/Node/HookConnector.php
+++ b/lib/private/Files/Node/HookConnector.php
@@ -133,7 +133,7 @@ class HookConnector {
$this->root->emit('\OC\Files', 'preDelete', [$node]);
$this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node));
- $event = new BeforeNodeDeletedEvent($node);
+ $event = new BeforeNodeDeletedEvent($node, $arguments['run']);
$this->dispatcher->dispatchTyped($event);
}
@@ -171,7 +171,7 @@ class HookConnector {
$this->root->emit('\OC\Files', 'preRename', [$source, $target]);
$this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target]));
- $event = new BeforeNodeRenamedEvent($source, $target);
+ $event = new BeforeNodeRenamedEvent($source, $target, $arguments['run']);
$this->dispatcher->dispatchTyped($event);
}
diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php
index f13cdc0c4f9..e30cfea693e 100644
--- a/lib/private/Files/Node/LazyFolder.php
+++ b/lib/private/Files/Node/LazyFolder.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -28,8 +29,8 @@ namespace OC\Files\Node;
use OC\Files\Filesystem;
use OC\Files\Utils\PathHelper;
-use OCP\Files\Folder;
use OCP\Constants;
+use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotPermittedException;
@@ -574,4 +575,12 @@ class LazyFolder implements Folder {
}
return $this->__call(__FUNCTION__, func_get_args());
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->data['metadata'] ?? $this->__call(__FUNCTION__, func_get_args());
+ }
}
diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php
index 503b0af8921..917ab80f366 100644
--- a/lib/private/Files/Node/LazyUserFolder.php
+++ b/lib/private/Files/Node/LazyUserFolder.php
@@ -23,13 +23,13 @@ declare(strict_types=1);
namespace OC\Files\Node;
-use OCP\Files\FileInfo;
use OCP\Constants;
+use OCP\Files\File;
+use OCP\Files\FileInfo;
+use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
use OCP\Files\NotFoundException;
-use OCP\Files\Folder;
-use OCP\Files\File;
use OCP\IUser;
use Psr\Log\LoggerInterface;
diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php
index 385d45f1e3e..acd91c56d3f 100644
--- a/lib/private/Files/Node/Node.php
+++ b/lib/private/Files/Node/Node.php
@@ -7,6 +7,7 @@
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
@@ -43,7 +44,7 @@ use OCP\Files\NotPermittedException;
use OCP\Lock\LockedException;
use OCP\PreConditionNotMetException;
-// FIXME: this class really should be abstract
+// FIXME: this class really should be abstract (+1)
class Node implements INode {
/**
* @var \OC\Files\View $view
@@ -131,7 +132,14 @@ class Node implements INode {
if (method_exists($this->root, 'emit')) {
$this->root->emit('\OC\Files', $hook, $args);
}
- $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args));
+
+ if (in_array($hook, ['preWrite', 'postWrite', 'preCreate', 'postCreate', 'preTouch', 'postTouch', 'preDelete', 'postDelete'], true)) {
+ $event = new GenericEvent($args[0]);
+ } else {
+ $event = new GenericEvent($args);
+ }
+
+ $dispatcher->dispatch('\OCP\Files::' . $hook, $event);
}
}
@@ -490,4 +498,12 @@ class Node implements INode {
public function getParentId(): int {
return $this->fileInfo->getParentId();
}
+
+ /**
+ * @inheritDoc
+ * @return array<string, int|string|bool|float|string[]|int[]>
+ */
+ public function getMetadata(): array {
+ return $this->fileInfo->getMetadata();
+ }
}
diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php
index 1195b644083..ee344f9be8b 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -32,7 +32,6 @@
namespace OC\Files\Node;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\FileInfo;
use OC\Files\Mount\Manager;
use OC\Files\Mount\MountPoint;
@@ -40,6 +39,7 @@ use OC\Files\Utils\PathHelper;
use OC\Files\View;
use OC\Hooks\PublicEmitter;
use OC\User\NoUserException;
+use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Config\IUserMountCache;
diff --git a/lib/private/Files/ObjectStore/ObjectStoreScanner.php b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
index d827662ae0b..8a9b844c47f 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|float $oldSize) {
+ protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
return 0;
}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 4dceee9a58b..eb8aaffe1e0 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -68,6 +68,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
private $logger;
+ private bool $handleCopiesAsOwned;
+
/** @var bool */
protected $validateWrites = true;
@@ -88,6 +90,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
if (isset($params['validateWrites'])) {
$this->validateWrites = (bool)$params['validateWrites'];
}
+ $this->handleCopiesAsOwned = (bool)($params['handleCopiesAsOwned'] ?? false);
$this->logger = \OC::$server->getLogger();
}
@@ -651,6 +654,10 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
try {
$this->objectStore->copyObject($sourceUrn, $targetUrn);
+ if ($this->handleCopiesAsOwned) {
+ // Copied the file thus we gain all permissions as we are the owner now ! warning while this aligns with local storage it should not be used and instead fix local storage !
+ $cache->update($targetId, ['permissions' => \OCP\Constants::PERMISSION_ALL]);
+ }
} catch (\Exception $e) {
$cache->remove($to);
diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
index 044c3cdc900..a1edfa1eb99 100644
--- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php
+++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
@@ -71,6 +71,11 @@ trait S3ConnectionTrait {
/** @var int */
private $putSizeLimit;
+ /** @var int */
+ private $copySizeLimit;
+
+ private bool $useMultipartCopy = true;
+
protected $test;
protected function parseParams($params) {
@@ -87,6 +92,8 @@ trait S3ConnectionTrait {
$this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
$this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
$this->putSizeLimit = $params['putSizeLimit'] ?? 104857600;
+ $this->copySizeLimit = $params['copySizeLimit'] ?? 5242880000;
+ $this->useMultipartCopy = (bool)($params['useMultipartCopy'] ?? true);
$params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region'];
$params['hostname'] = empty($params['hostname']) ? 's3.' . $params['region'] . '.amazonaws.com' : $params['hostname'];
if (!isset($params['port']) || $params['port'] === '') {
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index e9c52f11936..2ef9614ac85 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -191,15 +191,29 @@ trait S3ObjectTrait {
}
public function copyObject($from, $to, array $options = []) {
- $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)
- ], $options));
- $copy->copy();
+ $sourceMetadata = $this->getConnection()->headObject([
+ 'Bucket' => $this->getBucket(),
+ 'Key' => $from,
+ ] + $this->getSSECParameters());
+
+ $size = (int)($sourceMetadata->get('Size') ?? $sourceMetadata->get('ContentLength'));
+
+ if ($this->useMultipartCopy && $size > $this->copySizeLimit) {
+ $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();
+ } else {
+ $this->getConnection()->copy($this->getBucket(), $from, $this->getBucket(), $to, 'private', array_merge([
+ 'params' => $this->getSSECParameters() + $this->getSSECParameters(true)
+ ], $options));
+ }
}
}
diff --git a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
index 0caa9b12a02..664402f1238 100644
--- a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
+++ b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ * @author Robin Appelman <robin@icewind.nl>
+ *
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
@@ -48,7 +51,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep {
}
public function processOperator(ISearchOperator &$operator) {
- if (!$this->useHashEq && $operator instanceof ISearchComparison && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) {
+ if (!$this->useHashEq && $operator instanceof ISearchComparison && !$operator->getExtra() && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) {
$operator->setQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, false);
}
@@ -69,7 +72,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep {
private function operatorPairIsPathPrefix(ISearchOperator $like, ISearchOperator $equal): bool {
return (
$like instanceof ISearchComparison && $equal instanceof ISearchComparison &&
- $like->getField() === 'path' && $equal->getField() === 'path' &&
+ !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path' &&
$like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL
&& $like->getValue() === SearchComparison::escapeLikeParameter($equal->getValue()) . '/%'
);
diff --git a/lib/private/Files/Search/SearchComparison.php b/lib/private/Files/Search/SearchComparison.php
index 122a1f730b4..d94b3e9dfab 100644
--- a/lib/private/Files/Search/SearchComparison.php
+++ b/lib/private/Files/Search/SearchComparison.php
@@ -1,7 +1,10 @@
<?php
+
+declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -25,48 +28,45 @@ namespace OC\Files\Search;
use OCP\Files\Search\ISearchComparison;
class SearchComparison implements ISearchComparison {
- /** @var string */
- private $type;
- /** @var string */
- private $field;
- /** @var string|integer|\DateTime */
- private $value;
- private $hints = [];
+ private array $hints = [];
- /**
- * SearchComparison constructor.
- *
- * @param string $type
- * @param string $field
- * @param \DateTime|int|string $value
- */
- public function __construct($type, $field, $value) {
- $this->type = $type;
- $this->field = $field;
- $this->value = $value;
+ public function __construct(
+ private string $type,
+ private string $field,
+ private \DateTime|int|string|bool $value,
+ private string $extra = ''
+ ) {
}
/**
* @return string
*/
- public function getType() {
+ public function getType(): string {
return $this->type;
}
/**
* @return string
*/
- public function getField() {
+ public function getField(): string {
return $this->field;
}
/**
- * @return \DateTime|int|string
+ * @return \DateTime|int|string|bool
*/
- public function getValue() {
+ public function getValue(): string|int|bool|\DateTime {
return $this->value;
}
+ /**
+ * @return string
+ * @since 28.0.0
+ */
+ public function getExtra(): string {
+ return $this->extra;
+ }
+
public function getQueryHint(string $name, $default) {
return $this->hints[$name] ?? $default;
}
diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php
index 1395a87ac72..de514262bf5 100644
--- a/lib/private/Files/Search/SearchOrder.php
+++ b/lib/private/Files/Search/SearchOrder.php
@@ -2,6 +2,7 @@
/**
* @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
*
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
@@ -26,34 +27,33 @@ use OCP\Files\FileInfo;
use OCP\Files\Search\ISearchOrder;
class SearchOrder implements ISearchOrder {
- /** @var string */
- private $direction;
- /** @var string */
- private $field;
+ public function __construct(
+ private string $direction,
+ private string $field,
+ private string $extra = ''
+ ) {
+ }
/**
- * SearchOrder constructor.
- *
- * @param string $direction
- * @param string $field
+ * @return string
*/
- public function __construct($direction, $field) {
- $this->direction = $direction;
- $this->field = $field;
+ public function getDirection(): string {
+ return $this->direction;
}
/**
* @return string
*/
- public function getDirection() {
- return $this->direction;
+ public function getField(): string {
+ return $this->field;
}
/**
* @return string
+ * @since 28.0.0
*/
- public function getField() {
- return $this->field;
+ public function getExtra(): string {
+ return $this->extra;
}
public function sortFileInfo(FileInfo $a, FileInfo $b): int {
diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php
index cae0fd2f232..511e80bd7d9 100644
--- a/lib/private/Files/SetupManager.php
+++ b/lib/private/Files/SetupManager.php
@@ -24,8 +24,8 @@ declare(strict_types=1);
namespace OC\Files;
use OC\Files\Config\MountProviderCollection;
+use OC\Files\Mount\HomeMountPoint;
use OC\Files\Mount\MountPoint;
-use OC\Files\ObjectStore\HomeObjectStoreStorage;
use OC\Files\Storage\Common;
use OC\Files\Storage\Home;
use OC\Files\Storage\Storage;
@@ -39,7 +39,10 @@ use OC\Share20\ShareDisableChecker;
use OC_App;
use OC_Hook;
use OC_Util;
-use OCA\Files_Sharing\ISharedStorage;
+use OCA\Files_External\Config\ConfigAdapter;
+use OCA\Files_Sharing\External\Mount;
+use OCA\Files_Sharing\ISharedMountPoint;
+use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
@@ -117,7 +120,7 @@ class SetupManager {
$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if ($storage->instanceOfStorage(Common::class)) {
+ if ($mount->getOptions() && $storage->instanceOfStorage(Common::class)) {
$storage->setMountOptions($mount->getOptions());
}
return $storage;
@@ -130,7 +133,7 @@ class SetupManager {
'sharing_mask',
function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
- $isShared = $storage->instanceOfStorage(ISharedStorage::class);
+ $isShared = $mount instanceof ISharedMountPoint;
if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
return new PermissionsMask([
'storage' => $storage,
@@ -142,35 +145,30 @@ class SetupManager {
);
// install storage availability wrapper, before most other wrappers
- Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) {
- if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
+ Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
+ $externalMount = $mount instanceof ConfigAdapter || $mount instanceof Mount;
+ if ($externalMount && !$storage->isLocal()) {
return new Availability(['storage' => $storage]);
}
return $storage;
});
Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
+ if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
return new Encoding(['storage' => $storage]);
}
return $storage;
});
$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
- Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) use ($quotaIncludeExternal) {
+ Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
// set up quota for home storages, even for other users
// which can happen when using sharing
-
- /**
- * @var Storage $storage
- */
- if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) {
- if (is_object($storage->getUser())) {
- $user = $storage->getUser();
- return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
- return OC_Util::getUserQuota($user);
- }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
- }
+ if ($mount instanceof HomeMountPoint) {
+ $user = $mount->getUser();
+ return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
+ return OC_Util::getUserQuota($user);
+ }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
}
return $storage;
@@ -337,12 +335,13 @@ class SetupManager {
if ($this->rootSetup) {
return;
}
+
+ $this->setupBuiltinWrappers();
+
$this->rootSetup = true;
$this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
- $this->setupBuiltinWrappers();
-
$rootMounts = $this->mountProviderCollection->getRootMounts();
foreach ($rootMounts as $rootMountProvider) {
$this->mountManager->addMount($rootMountProvider);
diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php
index 4d24aa138c1..2c1f23f8e44 100644
--- a/lib/private/Files/SimpleFS/SimpleFolder.php
+++ b/lib/private/Files/SimpleFS/SimpleFolder.php
@@ -28,8 +28,8 @@ use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
-use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
class SimpleFolder implements ISimpleFolder {
/** @var Folder */
diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php
index 2d2bb52635b..e5bbbb560da 100644
--- a/lib/private/Files/Storage/DAV.php
+++ b/lib/private/Files/Storage/DAV.php
@@ -55,11 +55,11 @@ use OCP\ICertificateManager;
use OCP\IConfig;
use OCP\Util;
use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LoggerInterface;
use Sabre\DAV\Client;
use Sabre\DAV\Xml\Property\ResourceType;
use Sabre\HTTP\ClientException;
use Sabre\HTTP\ClientHttpException;
-use Psr\Log\LoggerInterface;
use Sabre\HTTP\RequestInterface;
/**
@@ -921,7 +921,7 @@ class DAV extends Common {
}
foreach ($responses as $file => $response) {
- $file = urldecode($file);
+ $file = rawurldecode($file);
$file = substr($file, strlen($this->root));
$file = $this->cleanPath($file);
$this->statCache->set($file, $response);
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index eeb9e11b24e..0fca853da59 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -74,6 +74,8 @@ class Local extends \OC\Files\Storage\Common {
protected bool $unlinkOnTruncate;
+ protected bool $caseInsensitive = false;
+
public function __construct($arguments) {
if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
throw new \InvalidArgumentException('No data directory set for local storage');
@@ -93,6 +95,7 @@ class Local extends \OC\Files\Storage\Common {
$this->config = \OC::$server->get(IConfig::class);
$this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
$this->defUMask = $this->config->getSystemValue('localstorage.umask', 0022);
+ $this->caseInsensitive = $this->config->getSystemValueBool('localstorage.case_insensitive', false);
// support Write-Once-Read-Many file systems
$this->unlinkOnTruncate = $this->config->getSystemValueBool('localstorage.unlink_on_truncate', false);
@@ -162,6 +165,9 @@ class Local extends \OC\Files\Storage\Common {
}
public function is_dir($path) {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
if (str_ends_with($path, '/')) {
$path = substr($path, 0, -1);
}
@@ -169,6 +175,9 @@ class Local extends \OC\Files\Storage\Common {
}
public function is_file($path) {
+ if ($this->caseInsensitive && !$this->file_exists($path)) {
+ return false;
+ }
return is_file($this->getSourcePath($path));
}
@@ -271,7 +280,13 @@ class Local extends \OC\Files\Storage\Common {
}
public function file_exists($path) {
- return file_exists($this->getSourcePath($path));
+ if ($this->caseInsensitive) {
+ $fullPath = $this->getSourcePath($path);
+ $content = scandir(dirname($fullPath), SCANDIR_SORT_NONE);
+ return is_array($content) && array_search(basename($fullPath), $content) !== false;
+ } else {
+ return file_exists($this->getSourcePath($path));
+ }
}
public function filemtime($path) {
@@ -372,6 +387,11 @@ class Local extends \OC\Files\Storage\Common {
}
if (@rename($this->getSourcePath($source), $this->getSourcePath($target))) {
+ if ($this->caseInsensitive) {
+ if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) {
+ return false;
+ }
+ }
return true;
}
@@ -388,6 +408,11 @@ class Local extends \OC\Files\Storage\Common {
}
$result = copy($this->getSourcePath($source), $this->getSourcePath($target));
umask($oldMask);
+ if ($this->caseInsensitive) {
+ if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) {
+ return false;
+ }
+ }
return $result;
}
}
diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php
index 6633cbf41e3..1bdb0e39f14 100644
--- a/lib/private/Files/Storage/Wrapper/Encoding.php
+++ b/lib/private/Files/Storage/Wrapper/Encoding.php
@@ -28,8 +28,8 @@
*/
namespace OC\Files\Storage\Wrapper;
-use OCP\Cache\CappedMemoryCache;
use OC\Files\Filesystem;
+use OCP\Cache\CappedMemoryCache;
use OCP\Files\Storage\IStorage;
use OCP\ICache;
diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php
index 1921ac27848..592acd418ec 100644
--- a/lib/private/Files/Storage/Wrapper/Jail.php
+++ b/lib/private/Files/Storage/Wrapper/Jail.php
@@ -396,10 +396,7 @@ class Jail extends Wrapper {
* @return \OC\Files\Cache\Cache
*/
public function getCache($path = '', $storage = null) {
- if (!$storage) {
- $storage = $this->getWrapperStorage();
- }
- $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage);
+ $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path));
return new CacheJail($sourceCache, $this->rootPath);
}
diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php
index bcf0a10740b..c57991f35a9 100644
--- a/lib/private/Files/Stream/Encryption.php
+++ b/lib/private/Files/Stream/Encryption.php
@@ -153,18 +153,18 @@ class Encryption extends Wrapper {
* @throws \BadMethodCallException
*/
public static function wrap($source, $internalPath, $fullPath, array $header,
- $uid,
- \OCP\Encryption\IEncryptionModule $encryptionModule,
- \OC\Files\Storage\Storage $storage,
- \OC\Files\Storage\Wrapper\Encryption $encStorage,
- \OC\Encryption\Util $util,
- \OC\Encryption\File $file,
- $mode,
- $size,
- $unencryptedSize,
- $headerSize,
- $signed,
- $wrapper = Encryption::class) {
+ $uid,
+ \OCP\Encryption\IEncryptionModule $encryptionModule,
+ \OC\Files\Storage\Storage $storage,
+ \OC\Files\Storage\Wrapper\Encryption $encStorage,
+ \OC\Encryption\Util $util,
+ \OC\Encryption\File $file,
+ $mode,
+ $size,
+ $unencryptedSize,
+ $headerSize,
+ $signed,
+ $wrapper = Encryption::class) {
$context = stream_context_create([
'ocencryption' => [
'source' => $source,
diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php
index 878680caa4e..9d9f6416208 100644
--- a/lib/private/Files/Template/TemplateManager.php
+++ b/lib/private/Files/Template/TemplateManager.php
@@ -31,8 +31,8 @@ use OC\AppFramework\Bootstrap\Coordinator;
use OC\Files\Cache\Scanner;
use OC\Files\Filesystem;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Files\Folder;
use OCP\Files\File;
+use OCP\Files\Folder;
use OCP\Files\GenericFileException;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
@@ -275,6 +275,11 @@ class TemplateManager implements ITemplateManager {
$isDefaultTemplates = $skeletonTemplatePath === $defaultTemplateDirectory;
$userLang = $this->l10nFactory->getUserLanguage($this->userManager->get($this->userId));
+ if ($skeletonTemplatePath === '') {
+ $this->setTemplatePath('');
+ return '';
+ }
+
try {
$l10n = $this->l10nFactory->get('lib', $userLang);
$userFolder = $this->rootFolder->getUserFolder($this->userId);
diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php
index 9a61aa93b95..71b8cb986d7 100644
--- a/lib/private/Files/Type/Detection.php
+++ b/lib/private/Files/Type/Detection.php
@@ -75,9 +75,9 @@ class Detection implements IMimeTypeDetector {
private $defaultConfigDir;
public function __construct(IURLGenerator $urlGenerator,
- LoggerInterface $logger,
- string $customConfigDir,
- string $defaultConfigDir) {
+ LoggerInterface $logger,
+ string $customConfigDir,
+ string $defaultConfigDir) {
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->customConfigDir = $customConfigDir;
@@ -96,8 +96,8 @@ class Detection implements IMimeTypeDetector {
* @param string|null $secureMimeType
*/
public function registerType(string $extension,
- string $mimetype,
- ?string $secureMimeType = null): void {
+ string $mimetype,
+ ?string $secureMimeType = null): void {
$this->mimetypes[$extension] = [$mimetype, $secureMimeType];
$this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype;
}
diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php
index b482c89ffd5..6348427cb3b 100644
--- a/lib/private/Files/Utils/Scanner.php
+++ b/lib/private/Files/Utils/Scanner.php
@@ -41,11 +41,11 @@ use OCA\Files_Sharing\SharedStorage;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\BeforeFileScannedEvent;
use OCP\Files\Events\BeforeFolderScannedEvent;
-use OCP\Files\Events\NodeAddedToCache;
use OCP\Files\Events\FileCacheUpdated;
-use OCP\Files\Events\NodeRemovedFromCache;
use OCP\Files\Events\FileScannedEvent;
use OCP\Files\Events\FolderScannedEvent;
+use OCP\Files\Events\NodeAddedToCache;
+use OCP\Files\Events\NodeRemovedFromCache;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\Files\StorageNotAvailableException;
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 86651ab3e1a..df8990790bb 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -49,10 +49,10 @@ namespace OC\Files;
use Icewind\Streams\CallbackWrapper;
use OC\Files\Mount\MoveableMount;
use OC\Files\Storage\Storage;
-use OC\User\LazyUser;
use OC\Share\Share;
-use OC\User\User;
+use OC\User\LazyUser;
use OC\User\Manager as UserManager;
+use OC\User\User;
use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
use OCP\Files\Cache\ICacheEntry;
@@ -287,12 +287,12 @@ class View {
$this->updaterEnabled = true;
}
- protected function writeUpdate(Storage $storage, string $internalPath, ?int $time = null): void {
+ protected function writeUpdate(Storage $storage, string $internalPath, ?int $time = null, ?int $sizeDifference = null): void {
if ($this->updaterEnabled) {
if (is_null($time)) {
$time = time();
}
- $storage->getUpdater()->update($internalPath, $time);
+ $storage->getUpdater()->update($internalPath, $time, $sizeDifference);
}
}
@@ -1173,7 +1173,9 @@ class View {
$this->removeUpdate($storage, $internalPath);
}
if ($result !== false && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') {
- $this->writeUpdate($storage, $internalPath);
+ $isCreateOperation = $operation === 'mkdir' || ($operation === 'file_put_contents' && in_array('create', $hooks, true));
+ $sizeDifference = $operation === 'mkdir' ? 0 : $result;
+ $this->writeUpdate($storage, $internalPath, null, $isCreateOperation ? $sizeDifference : null);
}
if ($result !== false && in_array('touch', $hooks)) {
$this->writeUpdate($storage, $internalPath, $extraParam);
@@ -1525,7 +1527,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..9bfa8ae49d6
--- /dev/null
+++ b/lib/private/FilesMetadata/FilesMetadataManager.php
@@ -0,0 +1,348 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata;
+
+use JsonException;
+use OC\FilesMetadata\Job\UpdateSingleMetadata;
+use OC\FilesMetadata\Listener\MetadataDelete;
+use OC\FilesMetadata\Listener\MetadataUpdate;
+use OC\FilesMetadata\Model\FilesMetadata;
+use OC\FilesMetadata\Service\IndexRequestService;
+use OC\FilesMetadata\Service\MetadataRequestService;
+use OCP\BackgroundJob\IJobList;
+use OCP\DB\Exception;
+use OCP\DB\Exception as DBException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Cache\CacheEntryRemovedEvent;
+use OCP\Files\Events\Node\NodeWrittenEvent;
+use OCP\Files\InvalidPathException;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\FilesMetadata\Event\MetadataBackgroundEvent;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use OCP\FilesMetadata\Event\MetadataNamedEvent;
+use OCP\FilesMetadata\Exceptions\FilesMetadataException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\FilesMetadata\IMetadataQuery;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @inheritDoc
+ * @since 28.0.0
+ */
+class FilesMetadataManager implements IFilesMetadataManager {
+ public const CONFIG_KEY = 'files_metadata';
+ public const MIGRATION_DONE = 'files_metadata_installed';
+ private const JSON_MAXSIZE = 100000;
+
+ private ?IFilesMetadata $all = null;
+
+ public function __construct(
+ private IEventDispatcher $eventDispatcher,
+ private IJobList $jobList,
+ private IConfig $config,
+ private LoggerInterface $logger,
+ private MetadataRequestService $metadataRequestService,
+ private IndexRequestService $indexRequestService,
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param Node $node related node
+ * @param int $process type of process
+ *
+ * @return IFilesMetadata
+ * @throws FilesMetadataException if metadata are invalid
+ * @throws InvalidPathException if path to file is not valid
+ * @throws NotFoundException if file cannot be found
+ * @see self::PROCESS_BACKGROUND
+ * @see self::PROCESS_LIVE
+ * @since 28.0.0
+ */
+ public function refreshMetadata(
+ Node $node,
+ int $process = self::PROCESS_LIVE,
+ string $namedEvent = ''
+ ): IFilesMetadata {
+ try {
+ $metadata = $this->metadataRequestService->getMetadataFromFileId($node->getId());
+ } catch (FilesMetadataNotFoundException) {
+ $metadata = new FilesMetadata($node->getId());
+ }
+
+ // if $process is LIVE, we enforce LIVE
+ // if $process is NAMED, we go NAMED
+ // else BACKGROUND
+ if ((self::PROCESS_LIVE & $process) !== 0) {
+ $event = new MetadataLiveEvent($node, $metadata);
+ } elseif ((self::PROCESS_NAMED & $process) !== 0) {
+ $event = new MetadataNamedEvent($node, $metadata, $namedEvent);
+ } else {
+ $event = new MetadataBackgroundEvent($node, $metadata);
+ }
+
+ $this->eventDispatcher->dispatchTyped($event);
+ $this->saveMetadata($event->getMetadata());
+
+ // if requested, we add a new job for next cron to refresh metadata out of main thread
+ // if $process was set to LIVE+BACKGROUND, we run background process directly
+ if ($event instanceof MetadataLiveEvent && $event->isRunAsBackgroundJobRequested()) {
+ if ((self::PROCESS_BACKGROUND & $process) !== 0) {
+ return $this->refreshMetadata($node, self::PROCESS_BACKGROUND);
+ }
+
+ $this->jobList->add(UpdateSingleMetadata::class, [$node->getOwner()->getUID(), $node->getId()]);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @param int $fileId file id
+ * @param boolean $generate Generate if metadata does not exists
+ *
+ * @inheritDoc
+ * @return IFilesMetadata
+ * @throws FilesMetadataNotFoundException if not found
+ * @since 28.0.0
+ */
+ public function getMetadata(int $fileId, bool $generate = false): IFilesMetadata {
+ try {
+ return $this->metadataRequestService->getMetadataFromFileId($fileId);
+ } catch (FilesMetadataNotFoundException $ex) {
+ if ($generate) {
+ return new FilesMetadata($fileId);
+ }
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * returns metadata of multiple file ids
+ *
+ * @param int[] $fileIds file ids
+ *
+ * @return array File ID is the array key, files without metadata are not returned in the array
+ * @psalm-return array<int, IFilesMetadata>
+ * @since 28.0.0
+ */
+ public function getMetadataForFiles(array $fileIds): array {
+ return $this->metadataRequestService->getMetadataFromFileIds($fileIds);
+ }
+
+ /**
+ * @param IFilesMetadata $filesMetadata metadata
+ *
+ * @inheritDoc
+ * @throws FilesMetadataException if metadata seems malformed
+ * @since 28.0.0
+ */
+ public function saveMetadata(IFilesMetadata $filesMetadata): void {
+ if ($filesMetadata->getFileId() === 0 || !$filesMetadata->updated()) {
+ return;
+ }
+
+ $json = json_encode($filesMetadata->jsonSerialize());
+ if (strlen($json) > self::JSON_MAXSIZE) {
+ $this->logger->debug('huge metadata content detected: ' . $json);
+ throw new FilesMetadataException('json cannot exceed ' . self::JSON_MAXSIZE . ' characters long; fileId: ' . $filesMetadata->getFileId() . '; size: ' . strlen($json));
+ }
+
+ try {
+ if ($filesMetadata->getSyncToken() === '') {
+ $this->metadataRequestService->store($filesMetadata);
+ } else {
+ $this->metadataRequestService->updateMetadata($filesMetadata);
+ }
+ } catch (DBException $e) {
+ // most of the logged exception are the result of race condition
+ // between 2 simultaneous process trying to create/update metadata
+ $this->logger->warning('issue while saveMetadata', ['exception' => $e, 'metadata' => $filesMetadata]);
+
+ return;
+ }
+
+ // update indexes
+ foreach ($filesMetadata->getIndexes() as $index) {
+ try {
+ $this->indexRequestService->updateIndex($filesMetadata, $index);
+ } catch (DBException $e) {
+ $this->logger->warning('issue while updateIndex', ['exception' => $e]);
+ }
+ }
+
+ // update metadata types list
+ $current = $this->getKnownMetadata();
+ $current->import($filesMetadata->jsonSerialize(true));
+ $this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current));
+ }
+
+ /**
+ * @param int $fileId file id
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ */
+ public function deleteMetadata(int $fileId): void {
+ try {
+ $this->metadataRequestService->dropMetadata($fileId);
+ } catch (Exception $e) {
+ $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]);
+ }
+
+ try {
+ $this->indexRequestService->dropIndex($fileId);
+ } catch (Exception $e) {
+ $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]);
+ }
+ }
+
+ /**
+ * @param IQueryBuilder $qb
+ * @param string $fileTableAlias alias of the table that contains data about files
+ * @param string $fileIdField alias of the field that contains file ids
+ *
+ * @inheritDoc
+ * @return IMetadataQuery|null
+ * @see IMetadataQuery
+ * @since 28.0.0
+ */
+ public function getMetadataQuery(
+ IQueryBuilder $qb,
+ string $fileTableAlias,
+ string $fileIdField
+ ): ?IMetadataQuery {
+ if (!$this->metadataInitiated()) {
+ return null;
+ }
+
+ return new MetadataQuery($qb, $this->getKnownMetadata(), $fileTableAlias, $fileIdField);
+ }
+
+ /**
+ * @inheritDoc
+ * @return IFilesMetadata
+ * @since 28.0.0
+ */
+ public function getKnownMetadata(): IFilesMetadata {
+ if (null !== $this->all) {
+ return $this->all;
+ }
+ $this->all = new FilesMetadata();
+
+ try {
+ $data = json_decode($this->config->getAppValue('core', self::CONFIG_KEY, '[]'), true, 127, JSON_THROW_ON_ERROR);
+ $this->all->import($data);
+ } catch (JsonException) {
+ $this->logger->warning('issue while reading stored list of metadata. Advised to run ./occ files:scan --all --generate-metadata');
+ }
+
+ return $this->all;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param string $type metadata type
+ * @param bool $indexed TRUE if metadata can be search
+ * @param int $editPermission remote edit permission via Webdav PROPPATCH
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ * @see IMetadataValueWrapper::TYPE_INT
+ * @see IMetadataValueWrapper::TYPE_FLOAT
+ * @see IMetadataValueWrapper::TYPE_BOOL
+ * @see IMetadataValueWrapper::TYPE_ARRAY
+ * @see IMetadataValueWrapper::TYPE_STRING_LIST
+ * @see IMetadataValueWrapper::TYPE_INT_LIST
+ * @see IMetadataValueWrapper::TYPE_STRING
+ * @see IMetadataValueWrapper::EDIT_FORBIDDEN
+ * @see IMetadataValueWrapper::EDIT_REQ_OWNERSHIP
+ * @see IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION
+ * @see IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION
+ */
+ public function initMetadata(
+ string $key,
+ string $type,
+ bool $indexed = false,
+ int $editPermission = IMetadataValueWrapper::EDIT_FORBIDDEN
+ ): void {
+ $current = $this->getKnownMetadata();
+ try {
+ if ($current->getType($key) === $type
+ && $indexed === $current->isIndex($key)
+ && $editPermission === $current->getEditPermission($key)) {
+ return; // if key exists, with same type and indexed, we do nothing.
+ }
+ } catch (FilesMetadataNotFoundException) {
+ // if value does not exist, we keep on the writing of course
+ }
+
+ $current->import([$key => ['type' => $type, 'indexed' => $indexed, 'editPermission' => $editPermission]]);
+ $this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current));
+ $this->all = $current;
+ }
+
+ /**
+ * load listeners
+ *
+ * @param IEventDispatcher $eventDispatcher
+ */
+ public static function loadListeners(IEventDispatcher $eventDispatcher): void {
+ $eventDispatcher->addServiceListener(NodeWrittenEvent::class, MetadataUpdate::class);
+ $eventDispatcher->addServiceListener(CacheEntryRemovedEvent::class, MetadataDelete::class);
+ }
+
+ /**
+ * Will confirm that tables were created and store an app value to cache the result.
+ * Can be removed in 29 as this is to avoid strange situation when Nextcloud files were
+ * replaced but the upgrade was not triggered yet.
+ *
+ * @return bool
+ */
+ private function metadataInitiated(): bool {
+ if ($this->config->getAppValue('core', self::MIGRATION_DONE, '0') === '1') {
+ return true;
+ }
+
+ $dbConnection = \OCP\Server::get(IDBConnection::class);
+ if ($dbConnection->tableExists(MetadataRequestService::TABLE_METADATA)) {
+ $this->config->setAppValue('core', self::MIGRATION_DONE, '1');
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php b/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php
new file mode 100644
index 00000000000..d18c8aa3680
--- /dev/null
+++ b/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata\Job;
+
+use OC\FilesMetadata\FilesMetadataManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\Files\IRootFolder;
+use OCP\FilesMetadata\Event\MetadataLiveEvent;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Simple background job, created when requested by an app during the
+ * dispatch of MetadataLiveEvent.
+ * This background job will re-run the event to refresh metadata on a non-live thread.
+ *
+ * @see MetadataLiveEvent::requestBackgroundJob()
+ * @since 28.0.0
+ */
+class UpdateSingleMetadata extends QueuedJob {
+ public function __construct(
+ ITimeFactory $time,
+ private IRootFolder $rootFolder,
+ private FilesMetadataManager $filesMetadataManager,
+ private LoggerInterface $logger
+ ) {
+ parent::__construct($time);
+ }
+
+ protected function run($argument) {
+ [$userId, $fileId] = $argument;
+
+ try {
+ $node = $this->rootFolder->getUserFolder($userId)->getById($fileId);
+ if (count($node) > 0) {
+ $file = array_shift($node);
+ $this->filesMetadataManager->refreshMetadata($file, IFilesMetadataManager::PROCESS_BACKGROUND);
+ }
+ } catch (\Exception $e) {
+ $this->logger->warning('issue while running UpdateSingleMetadata', ['exception' => $e, 'userId' => $userId, 'fileId' => $fileId]);
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/Listener/MetadataDelete.php b/lib/private/FilesMetadata/Listener/MetadataDelete.php
new file mode 100644
index 00000000000..d950c2cea5f
--- /dev/null
+++ b/lib/private/FilesMetadata/Listener/MetadataDelete.php
@@ -0,0 +1,61 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata\Listener;
+
+use Exception;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\Cache\CacheEntryRemovedEvent;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Handle file deletion event and remove stored metadata related to the deleted file
+ *
+ * @template-implements IEventListener<CacheEntryRemovedEvent>
+ */
+class MetadataDelete implements IEventListener {
+ public function __construct(
+ private IFilesMetadataManager $filesMetadataManager,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof CacheEntryRemovedEvent)) {
+ return;
+ }
+
+ try {
+ $nodeId = $event->getFileId();
+ if ($nodeId > 0) {
+ $this->filesMetadataManager->deleteMetadata($nodeId);
+ }
+ } catch (Exception $e) {
+ $this->logger->warning('issue while running MetadataDelete', ['exception' => $e]);
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/Listener/MetadataUpdate.php b/lib/private/FilesMetadata/Listener/MetadataUpdate.php
new file mode 100644
index 00000000000..9848f079882
--- /dev/null
+++ b/lib/private/FilesMetadata/Listener/MetadataUpdate.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata\Listener;
+
+use Exception;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Files\Events\Node\NodeCreatedEvent;
+use OCP\Files\Events\Node\NodeWrittenEvent;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Handle file creation/modification events and initiate a new event related to the created/edited file.
+ * The generated new event is broadcast in order to obtain file related metadata from other apps.
+ * metadata will be stored in database.
+ *
+ * @template-implements IEventListener<NodeCreatedEvent|NodeWrittenEvent>
+ */
+class MetadataUpdate implements IEventListener {
+ public function __construct(
+ private IFilesMetadataManager $filesMetadataManager,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ /**
+ * @param Event $event
+ */
+ public function handle(Event $event): void {
+ if (!($event instanceof NodeWrittenEvent)) {
+ return;
+ }
+
+ try {
+ $this->filesMetadataManager->refreshMetadata($event->getNode());
+ } catch (Exception $e) {
+ $this->logger->warning('issue while running MetadataUpdate', ['exception' => $e]);
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/MetadataQuery.php b/lib/private/FilesMetadata/MetadataQuery.php
new file mode 100644
index 00000000000..aa079c678d7
--- /dev/null
+++ b/lib/private/FilesMetadata/MetadataQuery.php
@@ -0,0 +1,167 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata;
+
+use OC\FilesMetadata\Model\FilesMetadata;
+use OC\FilesMetadata\Service\IndexRequestService;
+use OC\FilesMetadata\Service\MetadataRequestService;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\IMetadataQuery;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+
+/**
+ * @inheritDoc
+ * @since 28.0.0
+ */
+class MetadataQuery implements IMetadataQuery {
+ private array $knownJoinedIndex = [];
+ public function __construct(
+ private IQueryBuilder $queryBuilder,
+ private IFilesMetadata $knownMetadata,
+ private string $fileTableAlias = 'fc',
+ private string $fileIdField = 'fileid',
+ private string $alias = 'meta',
+ private string $aliasIndexPrefix = 'meta_index'
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ * @see self::extractMetadata()
+ * @since 28.0.0
+ */
+ public function retrieveMetadata(): void {
+ $this->queryBuilder->selectAlias($this->alias . '.json', 'meta_json');
+ $this->queryBuilder->selectAlias($this->alias . '.sync_token', 'meta_sync_token');
+ $this->queryBuilder->leftJoin(
+ $this->fileTableAlias, MetadataRequestService::TABLE_METADATA, $this->alias,
+ $this->queryBuilder->expr()->eq($this->fileTableAlias . '.' . $this->fileIdField, $this->alias . '.file_id')
+ );
+ }
+
+ /**
+ * @param array $row result row
+ *
+ * @inheritDoc
+ * @return IFilesMetadata metadata
+ * @see self::retrieveMetadata()
+ * @since 28.0.0
+ */
+ public function extractMetadata(array $row): IFilesMetadata {
+ $fileId = (array_key_exists($this->fileIdField, $row)) ? $row[$this->fileIdField] : 0;
+ $metadata = new FilesMetadata((int)$fileId);
+ try {
+ $metadata->importFromDatabase($row, $this->alias . '_');
+ } catch (FilesMetadataNotFoundException) {
+ // can be ignored as files' metadata are optional and might not exist in database
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @param string $metadataKey metadata key
+ * @param bool $enforce limit the request only to existing metadata
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ */
+ public function joinIndex(string $metadataKey, bool $enforce = false): string {
+ if (array_key_exists($metadataKey, $this->knownJoinedIndex)) {
+ return $this->knownJoinedIndex[$metadataKey];
+ }
+
+ $aliasIndex = $this->aliasIndexPrefix . '_' . count($this->knownJoinedIndex);
+ $this->knownJoinedIndex[$metadataKey] = $aliasIndex;
+
+ $expr = $this->queryBuilder->expr();
+ $andX = $expr->andX($expr->eq($aliasIndex . '.file_id', $this->fileTableAlias . '.' . $this->fileIdField));
+ $andX->add($expr->eq($this->getMetadataKeyField($metadataKey), $this->queryBuilder->createNamedParameter($metadataKey)));
+
+ if ($enforce) {
+ $this->queryBuilder->innerJoin(
+ $this->fileTableAlias,
+ IndexRequestService::TABLE_METADATA_INDEX,
+ $aliasIndex,
+ $andX
+ );
+ } else {
+ $this->queryBuilder->leftJoin(
+ $this->fileTableAlias,
+ IndexRequestService::TABLE_METADATA_INDEX,
+ $aliasIndex,
+ $andX
+ );
+ }
+
+ return $aliasIndex;
+ }
+
+ /**
+ * @throws FilesMetadataNotFoundException
+ */
+ private function joinedTableAlias(string $metadataKey): string {
+ if (!array_key_exists($metadataKey, $this->knownJoinedIndex)) {
+ throw new FilesMetadataNotFoundException('table related to ' . $metadataKey . ' not initiated, you need to use leftJoin() first.');
+ }
+
+ return $this->knownJoinedIndex[$metadataKey];
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $metadataKey metadata key
+ *
+ * @return string table field
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function getMetadataKeyField(string $metadataKey): string {
+ return $this->joinedTableAlias($metadataKey) . '.meta_key';
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $metadataKey metadata key
+ *
+ * @return string table field
+ * @throws FilesMetadataNotFoundException if metadataKey is not known
+ * @throws FilesMetadataTypeException is metadataKey is not set as indexed
+ * @since 28.0.0
+ */
+ public function getMetadataValueField(string $metadataKey): string {
+ return match ($this->knownMetadata->getType($metadataKey)) {
+ IMetadataValueWrapper::TYPE_STRING => $this->joinedTableAlias($metadataKey) . '.meta_value_string',
+ IMetadataValueWrapper::TYPE_INT, IMetadataValueWrapper::TYPE_BOOL => $this->joinedTableAlias($metadataKey) . '.meta_value_int',
+ default => throw new FilesMetadataTypeException('metadata is not set as indexed'),
+ };
+ }
+}
diff --git a/lib/private/FilesMetadata/Model/FilesMetadata.php b/lib/private/FilesMetadata/Model/FilesMetadata.php
new file mode 100644
index 00000000000..629b537dabe
--- /dev/null
+++ b/lib/private/FilesMetadata/Model/FilesMetadata.php
@@ -0,0 +1,621 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata\Model;
+
+use JsonException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataKeyFormatException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+
+/**
+ * Model that represent metadata linked to a specific file.
+ *
+ * @inheritDoc
+ * @since 28.0.0
+ */
+class FilesMetadata implements IFilesMetadata {
+ /** @var array<string, MetadataValueWrapper> */
+ private array $metadata = [];
+ private bool $updated = false;
+ private int $lastUpdate = 0;
+ private string $syncToken = '';
+
+ public function __construct(
+ private int $fileId = 0
+ ) {
+ }
+
+ /**
+ * @inheritDoc
+ * @return int related file id
+ * @since 28.0.0
+ */
+ public function getFileId(): int {
+ return $this->fileId;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int timestamp
+ * @since 28.0.0
+ */
+ public function lastUpdateTimestamp(): int {
+ return $this->lastUpdate;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string token
+ * @since 28.0.0
+ */
+ public function getSyncToken(): string {
+ return $this->syncToken;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string[] list of keys
+ * @since 28.0.0
+ */
+ public function getKeys(): array {
+ return array_keys($this->metadata);
+ }
+
+ /**
+ * @param string $needle metadata key to search
+ *
+ * @inheritDoc
+ * @return bool TRUE if key exist
+ * @since 28.0.0
+ */
+ public function hasKey(string $needle): bool {
+ return (in_array($needle, $this->getKeys()));
+ }
+
+ /**
+ * @inheritDoc
+ * @return string[] list of indexes
+ * @since 28.0.0
+ */
+ public function getIndexes(): array {
+ $indexes = [];
+ foreach ($this->getKeys() as $key) {
+ if ($this->metadata[$key]->isIndexed()) {
+ $indexes[] = $key;
+ }
+ }
+
+ return $indexes;
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return bool TRUE if key exists and is set as indexed
+ * @since 28.0.0
+ */
+ public function isIndex(string $key): bool {
+ return $this->metadata[$key]?->isIndexed() ?? false;
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return int edit permission
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function getEditPermission(string $key): int {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getEditPermission();
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param int $permission edit permission
+ *
+ * @inheritDoc
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function setEditPermission(string $key, int $permission): void {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ $this->metadata[$key]->setEditPermission($permission);
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return string metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getString(string $key): string {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueString();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return int metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getInt(string $key): int {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueInt();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return float metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getFloat(string $key): float {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueFloat();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return bool metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getBool(string $key): bool {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueBool();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return array metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getArray(string $key): array {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueArray();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return string[] metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getStringList(string $key): array {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueStringList();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return int[] metadata value
+ * @throws FilesMetadataNotFoundException
+ * @throws FilesMetadataTypeException
+ * @since 28.0.0
+ */
+ public function getIntList(string $key): array {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getValueIntList();
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return string value type
+ * @throws FilesMetadataNotFoundException
+ * @see IMetadataValueWrapper::TYPE_STRING
+ * @see IMetadataValueWrapper::TYPE_INT
+ * @see IMetadataValueWrapper::TYPE_FLOAT
+ * @see IMetadataValueWrapper::TYPE_BOOL
+ * @see IMetadataValueWrapper::TYPE_ARRAY
+ * @see IMetadataValueWrapper::TYPE_STRING_LIST
+ * @see IMetadataValueWrapper::TYPE_INT_LIST
+ * @since 28.0.0
+ */
+ public function getType(string $key): string {
+ if (!array_key_exists($key, $this->metadata)) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ return $this->metadata[$key]->getType();
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param string $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setString(string $key, string $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getString($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value and index have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING);
+ $this->updated = true;
+ $this->metadata[$key] = $meta->setValueString($value)->setIndexed($index);
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param int $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setInt(string $key, int $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getInt($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_INT);
+ $this->metadata[$key] = $meta->setValueInt($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param float $value metadata value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setFloat(string $key, float $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getFloat($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_FLOAT);
+ $this->metadata[$key] = $meta->setValueFloat($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+
+ /**
+ * @param string $key metadata key
+ * @param bool $value metadata value
+ * @param bool $index set TRUE if value must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setBool(string $key, bool $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getBool($key) === $value && $index === $this->isIndex($key)) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_BOOL);
+ $this->metadata[$key] = $meta->setValueBool($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+
+ /**
+ * @param string $key metadata key
+ * @param array $value metadata value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setArray(string $key, array $value): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getArray($key) === $value) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_ARRAY);
+ $this->metadata[$key] = $meta->setValueArray($value);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param string[] $value metadata value
+ * @param bool $index set TRUE if each values from the list must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setStringList(string $key, array $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getStringList($key) === $value) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST);
+ $this->metadata[$key] = $meta->setValueStringList($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ * @param int[] $value metadata value
+ * @param bool $index set TRUE if each values from the list must be indexed
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataKeyFormatException
+ * @since 28.0.0
+ */
+ public function setIntList(string $key, array $value, bool $index = false): IFilesMetadata {
+ $this->confirmKeyFormat($key);
+ try {
+ if ($this->getIntList($key) === $value) {
+ return $this; // we ignore if value have not changed
+ }
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) {
+ // if value does not exist, or type has changed, we keep on the writing
+ }
+
+ $valueWrapper = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST);
+ $this->metadata[$key] = $valueWrapper->setValueIntList($value)->setIndexed($index);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $key metadata key
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function unset(string $key): IFilesMetadata {
+ if (!array_key_exists($key, $this->metadata)) {
+ return $this;
+ }
+
+ unset($this->metadata[$key]);
+ $this->updated = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $keyPrefix metadata key prefix
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function removeStartsWith(string $keyPrefix): IFilesMetadata {
+ if ($keyPrefix === '') {
+ return $this;
+ }
+
+ foreach ($this->getKeys() as $key) {
+ if (str_starts_with($key, $keyPrefix)) {
+ $this->unset($key);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return void
+ * @throws FilesMetadataKeyFormatException
+ */
+ private function confirmKeyFormat(string $key): void {
+ $acceptedChars = ['-', '_'];
+ if (ctype_alnum(str_replace($acceptedChars, '', $key))) {
+ return;
+ }
+
+ throw new FilesMetadataKeyFormatException('key can only contains alphanumerical characters, and dash (-, _)');
+ }
+
+ /**
+ * @inheritDoc
+ * @return bool TRUE if metadata have been modified
+ * @since 28.0.0
+ */
+ public function updated(): bool {
+ return $this->updated;
+ }
+
+ public function jsonSerialize(bool $emptyValues = false): array {
+ $data = [];
+ foreach ($this->metadata as $metaKey => $metaValueWrapper) {
+ $data[$metaKey] = $metaValueWrapper->jsonSerialize($emptyValues);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return array<string, string|int|bool|float|string[]|int[]>
+ */
+ public function asArray(): array {
+ $data = [];
+ foreach ($this->metadata as $metaKey => $metaValueWrapper) {
+ try {
+ $data[$metaKey] = $metaValueWrapper->getValueAny();
+ } catch (FilesMetadataNotFoundException $e) {
+ // ignore exception
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @inheritDoc
+ * @return IFilesMetadata
+ * @since 28.0.0
+ */
+ public function import(array $data): IFilesMetadata {
+ foreach ($data as $k => $v) {
+ $valueWrapper = new MetadataValueWrapper();
+ $this->metadata[$k] = $valueWrapper->import($v);
+ }
+ $this->updated = false;
+
+ return $this;
+ }
+
+ /**
+ * import data from database to configure this model
+ *
+ * @param array $data
+ * @param string $prefix
+ *
+ * @return IFilesMetadata
+ * @throws FilesMetadataNotFoundException
+ * @since 28.0.0
+ */
+ public function importFromDatabase(array $data, string $prefix = ''): IFilesMetadata {
+ try {
+ $this->syncToken = $data[$prefix . 'sync_token'] ?? '';
+
+ return $this->import(
+ json_decode(
+ $data[$prefix . 'json'] ?? '[]',
+ true,
+ 512,
+ JSON_THROW_ON_ERROR
+ )
+ );
+ } catch (JsonException) {
+ throw new FilesMetadataNotFoundException();
+ }
+ }
+}
diff --git a/lib/private/FilesMetadata/Model/MetadataValueWrapper.php b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php
new file mode 100644
index 00000000000..90f1554180d
--- /dev/null
+++ b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php
@@ -0,0 +1,421 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata\Model;
+
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+
+/**
+ * @inheritDoc
+ * @see IFilesMetadata
+ * @since 28.0.0
+ */
+class MetadataValueWrapper implements IMetadataValueWrapper {
+ private string $type;
+ /** @var string|int|float|bool|array|string[]|int[] */
+ private mixed $value = null;
+ private bool $indexed = false;
+ private int $editPermission = self::EDIT_FORBIDDEN;
+
+ /**
+ * @param string $type value type
+ *
+ * @inheritDoc
+ * @see self::TYPE_INT
+ * @see self::TYPE_FLOAT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @since 28.0.0
+ */
+ public function __construct(string $type = '') {
+ $this->type = $type;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string value type
+ * @see self::TYPE_INT
+ * @see self::TYPE_FLOAT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @since 28.0.0
+ */
+ public function getType(): string {
+ return $this->type;
+ }
+
+ /**
+ * @param string $type value type
+ *
+ * @inheritDoc
+ * @return bool
+ * @see self::TYPE_INT
+ * @see self::TYPE_FLOAT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @since 28.0.0
+ */
+ public function isType(string $type): bool {
+ return (strtolower($type) === strtolower($this->type));
+ }
+
+ /**
+ * @param string $type value type
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if type cannot be confirmed
+ * @see self::TYPE_INT
+ * @see self::TYPE_BOOL
+ * @see self::TYPE_ARRAY
+ * @see self::TYPE_STRING_LIST
+ * @see self::TYPE_INT_LIST
+ * @see self::TYPE_STRING
+ * @see self::TYPE_FLOAT
+ * @since 28.0.0
+ */
+ public function assertType(string $type): self {
+ if (!$this->isType($type)) {
+ throw new FilesMetadataTypeException('type is \'' . $this->getType() . '\', expecting \'' . $type . '\'');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $value string to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string
+ * @since 28.0.0
+ */
+ public function setValueString(string $value): self {
+ $this->assertType(self::TYPE_STRING);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param int $value int to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int
+ * @since 28.0.0
+ */
+ public function setValueInt(int $value): self {
+ $this->assertType(self::TYPE_INT);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param float $value float to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a float
+ * @since 28.0.0
+ */
+ public function setValueFloat(float $value): self {
+ $this->assertType(self::TYPE_FLOAT);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param bool $value bool to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a bool
+ * @since 28.0.0
+ */
+ public function setValueBool(bool $value): self {
+ $this->assertType(self::TYPE_BOOL);
+ $this->value = $value;
+
+
+ return $this;
+ }
+
+ /**
+ * @param array $value array to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an array
+ * @since 28.0.0
+ */
+ public function setValueArray(array $value): self {
+ $this->assertType(self::TYPE_ARRAY);
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $value string list to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string list
+ * @since 28.0.0
+ */
+ public function setValueStringList(array $value): self {
+ $this->assertType(self::TYPE_STRING_LIST);
+ // TODO confirm value is an array or string ?
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param int[] $value int list to be set as value
+ *
+ * @inheritDoc
+ * @return self
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int list
+ * @since 28.0.0
+ */
+ public function setValueIntList(array $value): self {
+ $this->assertType(self::TYPE_INT_LIST);
+ // TODO confirm value is an array of int ?
+ $this->value = $value;
+
+ return $this;
+ }
+
+
+ /**
+ * @inheritDoc
+ * @return string set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueString(): string {
+ $this->assertType(self::TYPE_STRING);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (string)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueInt(): int {
+ $this->assertType(self::TYPE_INT);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (int)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return float set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a float
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueFloat(): float {
+ $this->assertType(self::TYPE_FLOAT);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (float)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return bool set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a bool
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueBool(): bool {
+ $this->assertType(self::TYPE_BOOL);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (bool)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return array set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store an array
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueArray(): array {
+ $this->assertType(self::TYPE_ARRAY);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (array)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string[] set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store a string list
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueStringList(): array {
+ $this->assertType(self::TYPE_STRING_LIST);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (array)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int[] set value
+ * @throws FilesMetadataTypeException if wrapper was not set to store an int list
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueIntList(): array {
+ $this->assertType(self::TYPE_INT_LIST);
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return (array)$this->value;
+ }
+
+ /**
+ * @inheritDoc
+ * @return string|int|float|bool|array|string[]|int[] set value
+ * @throws FilesMetadataNotFoundException if value is not set
+ * @since 28.0.0
+ */
+ public function getValueAny(): mixed {
+ if (null === $this->value) {
+ throw new FilesMetadataNotFoundException('value is not set');
+ }
+
+ return $this->value;
+ }
+
+ /**
+ * @param bool $indexed TRUE to set the stored value as an indexed value
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function setIndexed(bool $indexed): self {
+ $this->indexed = $indexed;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ * @return bool TRUE if value is an indexed value
+ * @since 28.0.0
+ */
+ public function isIndexed(): bool {
+ return $this->indexed;
+ }
+
+ /**
+ * @param int $permission edit permission
+ *
+ * @inheritDoc
+ * @return self
+ * @since 28.0.0
+ */
+ public function setEditPermission(int $permission): self {
+ $this->editPermission = $permission;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ * @return int edit permission
+ * @since 28.0.0
+ */
+ public function getEditPermission(): int {
+ return $this->editPermission;
+ }
+
+ /**
+ * @param array $data serialized version of the object
+ *
+ * @inheritDoc
+ * @return self
+ * @see jsonSerialize
+ * @since 28.0.0
+ */
+ public function import(array $data): self {
+ $this->value = $data['value'] ?? null;
+ $this->type = $data['type'] ?? '';
+ $this->setIndexed($data['indexed'] ?? false);
+ $this->setEditPermission($data['editPermission'] ?? self::EDIT_FORBIDDEN);
+ return $this;
+ }
+
+ public function jsonSerialize(bool $emptyValues = false): array {
+ return [
+ 'value' => ($emptyValues) ? null : $this->value,
+ 'type' => $this->getType(),
+ 'indexed' => $this->isIndexed(),
+ 'editPermission' => $this->getEditPermission()
+ ];
+ }
+}
diff --git a/lib/private/FilesMetadata/Service/IndexRequestService.php b/lib/private/FilesMetadata/Service/IndexRequestService.php
new file mode 100644
index 00000000000..2a23e2c9a67
--- /dev/null
+++ b/lib/private/FilesMetadata/Service/IndexRequestService.php
@@ -0,0 +1,195 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata\Service;
+
+use OCP\DB\Exception as DbException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\FilesMetadata\Model\IMetadataValueWrapper;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
+
+/**
+ * manage sql request to the metadata_index table
+ */
+class IndexRequestService {
+ public const TABLE_METADATA_INDEX = 'files_metadata_index';
+
+ public function __construct(
+ private IDBConnection $dbConnection,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ /**
+ * update the index for a specific metadata key
+ *
+ * @param IFilesMetadata $filesMetadata metadata
+ * @param string $key metadata key to update
+ *
+ * @throws DbException
+ */
+ public function updateIndex(IFilesMetadata $filesMetadata, string $key): void {
+ $fileId = $filesMetadata->getFileId();
+ try {
+ $metadataType = $filesMetadata->getType($key);
+ } catch (FilesMetadataNotFoundException $e) {
+ return;
+ }
+
+ /**
+ * might look harsh, but a lot simpler than comparing current indexed data, as we can expect
+ * conflict with a change of types.
+ * We assume that each time one random metadata were modified we can drop all index for this
+ * key and recreate them.
+ * To make it slightly cleaner, we'll use transaction
+ */
+ $this->dbConnection->beginTransaction();
+ try {
+ $this->dropIndex($fileId, $key);
+ match ($metadataType) {
+ IMetadataValueWrapper::TYPE_STRING => $this->insertIndexString($fileId, $key, $filesMetadata->getString($key)),
+ IMetadataValueWrapper::TYPE_INT => $this->insertIndexInt($fileId, $key, $filesMetadata->getInt($key)),
+ IMetadataValueWrapper::TYPE_BOOL => $this->insertIndexBool($fileId, $key, $filesMetadata->getBool($key)),
+ IMetadataValueWrapper::TYPE_STRING_LIST => $this->insertIndexStringList($fileId, $key, $filesMetadata->getStringList($key)),
+ IMetadataValueWrapper::TYPE_INT_LIST => $this->insertIndexIntList($fileId, $key, $filesMetadata->getIntList($key))
+ };
+ } catch (FilesMetadataNotFoundException|FilesMetadataTypeException|DbException $e) {
+ $this->dbConnection->rollBack();
+ $this->logger->warning('issue while updateIndex', ['exception' => $e, 'fileId' => $fileId, 'key' => $key]);
+ }
+
+ $this->dbConnection->commit();
+ }
+
+ /**
+ * insert a new entry in the metadata_index table for a string value
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param string $value metadata value
+ *
+ * @throws DbException
+ */
+ private function insertIndexString(int $fileId, string $key, string $value): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA_INDEX)
+ ->setValue('meta_key', $qb->createNamedParameter($key))
+ ->setValue('meta_value_string', $qb->createNamedParameter($value))
+ ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
+ $qb->executeStatement();
+ }
+
+ /**
+ * insert a new entry in the metadata_index table for an int value
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param int $value metadata value
+ *
+ * @throws DbException
+ */
+ public function insertIndexInt(int $fileId, string $key, int $value): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA_INDEX)
+ ->setValue('meta_key', $qb->createNamedParameter($key))
+ ->setValue('meta_value_int', $qb->createNamedParameter($value, IQueryBuilder::PARAM_INT))
+ ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
+ $qb->executeStatement();
+ }
+
+ /**
+ * insert a new entry in the metadata_index table for a bool value
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param bool $value metadata value
+ *
+ * @throws DbException
+ */
+ public function insertIndexBool(int $fileId, string $key, bool $value): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA_INDEX)
+ ->setValue('meta_key', $qb->createNamedParameter($key))
+ ->setValue('meta_value_int', $qb->createNamedParameter(($value) ? '1' : '0', IQueryBuilder::PARAM_INT))
+ ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
+ $qb->executeStatement();
+ }
+
+ /**
+ * insert entries in the metadata_index table for list of string
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param string[] $values metadata values
+ *
+ * @throws DbException
+ */
+ public function insertIndexStringList(int $fileId, string $key, array $values): void {
+ foreach ($values as $value) {
+ $this->insertIndexString($fileId, $key, $value);
+ }
+ }
+
+ /**
+ * insert entries in the metadata_index table for list of int
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ * @param int[] $values metadata values
+ *
+ * @throws DbException
+ */
+ public function insertIndexIntList(int $fileId, string $key, array $values): void {
+ foreach ($values as $value) {
+ $this->insertIndexInt($fileId, $key, $value);
+ }
+ }
+
+ /**
+ * drop indexes related to a file id
+ * if a key is specified, only drop entries related to it
+ *
+ * @param int $fileId file id
+ * @param string $key metadata key
+ *
+ * @throws DbException
+ */
+ public function dropIndex(int $fileId, string $key = ''): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $expr = $qb->expr();
+ $qb->delete(self::TABLE_METADATA_INDEX)
+ ->where($expr->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
+
+ if ($key !== '') {
+ $qb->andWhere($expr->eq('meta_key', $qb->createNamedParameter($key)));
+ }
+
+ $qb->executeStatement();
+ }
+}
diff --git a/lib/private/FilesMetadata/Service/MetadataRequestService.php b/lib/private/FilesMetadata/Service/MetadataRequestService.php
new file mode 100644
index 00000000000..cdce624d75c
--- /dev/null
+++ b/lib/private/FilesMetadata/Service/MetadataRequestService.php
@@ -0,0 +1,194 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright 2023 Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\FilesMetadata\Service;
+
+use OC\FilesMetadata\Model\FilesMetadata;
+use OCP\DB\Exception;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
+use OCP\FilesMetadata\Model\IFilesMetadata;
+use OCP\IDBConnection;
+use Psr\Log\LoggerInterface;
+
+/**
+ * manage sql request to the metadata table
+ */
+class MetadataRequestService {
+ public const TABLE_METADATA = 'files_metadata';
+
+ public function __construct(
+ private IDBConnection $dbConnection,
+ private LoggerInterface $logger
+ ) {
+ }
+
+ /**
+ * store metadata into database
+ *
+ * @param IFilesMetadata $filesMetadata
+ *
+ * @throws Exception
+ */
+ public function store(IFilesMetadata $filesMetadata): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->insert(self::TABLE_METADATA)
+ ->setValue('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT))
+ ->setValue('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize())))
+ ->setValue('sync_token', $qb->createNamedParameter($this->generateSyncToken()))
+ ->setValue('last_update', (string) $qb->createFunction('NOW()'));
+ $qb->executeStatement();
+ }
+
+ /**
+ * returns metadata for a file id
+ *
+ * @param int $fileId file id
+ *
+ * @return IFilesMetadata
+ * @throws FilesMetadataNotFoundException if no metadata are found in database
+ */
+ public function getMetadataFromFileId(int $fileId): IFilesMetadata {
+ try {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('json', 'sync_token')->from(self::TABLE_METADATA);
+ $qb->where(
+ $qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))
+ );
+ $result = $qb->executeQuery();
+ $data = $result->fetch();
+ $result->closeCursor();
+ } catch (Exception $e) {
+ $this->logger->warning(
+ 'exception while getMetadataFromDatabase()', ['exception' => $e, 'fileId' => $fileId]
+ );
+ throw new FilesMetadataNotFoundException();
+ }
+
+ if ($data === false) {
+ throw new FilesMetadataNotFoundException();
+ }
+
+ $metadata = new FilesMetadata($fileId);
+ $metadata->importFromDatabase($data);
+
+ return $metadata;
+ }
+
+ /**
+ * returns metadata for multiple file ids
+ *
+ * If
+ *
+ * @param array $fileIds file ids
+ *
+ * @return array File ID is the array key, files without metadata are not returned in the array
+ * @psalm-return array<int, IFilesMetadata>
+ */
+ public function getMetadataFromFileIds(array $fileIds): array {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->select('file_id', 'json', 'sync_token')->from(self::TABLE_METADATA);
+ $qb->where(
+ $qb->expr()->in('file_id', $qb->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY))
+ );
+
+ $list = [];
+ $result = $qb->executeQuery();
+ while ($data = $result->fetch()) {
+ $fileId = (int) $data['file_id'];
+ $metadata = new FilesMetadata($fileId);
+ try {
+ $metadata->importFromDatabase($data);
+ } catch (FilesMetadataNotFoundException) {
+ continue;
+ }
+ $list[$fileId] = $metadata;
+ }
+ $result->closeCursor();
+
+ return $list;
+ }
+
+ /**
+ * drop metadata related to a file id
+ *
+ * @param int $fileId file id
+ *
+ * @return void
+ * @throws Exception
+ */
+ public function dropMetadata(int $fileId): void {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->delete(self::TABLE_METADATA)
+ ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
+ $qb->executeStatement();
+ }
+
+ /**
+ * update metadata in the database
+ *
+ * @param IFilesMetadata $filesMetadata metadata
+ *
+ * @return int number of affected rows
+ * @throws Exception
+ */
+ public function updateMetadata(IFilesMetadata $filesMetadata): int {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $expr = $qb->expr();
+
+ $qb->update(self::TABLE_METADATA)
+ ->set('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize())))
+ ->set('sync_token', $qb->createNamedParameter($this->generateSyncToken()))
+ ->set('last_update', $qb->createFunction('NOW()'))
+ ->where(
+ $expr->andX(
+ $expr->eq('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT)),
+ $expr->eq('sync_token', $qb->createNamedParameter($filesMetadata->getSyncToken()))
+ )
+ );
+
+ return $qb->executeStatement();
+ }
+
+ /**
+ * generate a random token
+ * @return string
+ */
+ private function generateSyncToken(): string {
+ $chars = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
+
+ $str = '';
+ $max = strlen($chars);
+ for ($i = 0; $i < 7; $i++) {
+ try {
+ $str .= $chars[random_int(0, $max - 2)];
+ } catch (\Exception $e) {
+ $this->logger->warning('exception during generateSyncToken', ['exception' => $e]);
+ }
+ }
+
+ return $str;
+ }
+}
diff --git a/lib/private/Group/Database.php b/lib/private/Group/Database.php
index 55792ce1dff..13837eef552 100644
--- a/lib/private/Group/Database.php
+++ b/lib/private/Group/Database.php
@@ -30,6 +30,7 @@
namespace OC\Group;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OC\User\LazyUser;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Group\Backend\ABackend;
use OCP\Group\Backend\IAddToGroupBackend;
@@ -40,13 +41,12 @@ use OCP\Group\Backend\ICreateGroupBackend;
use OCP\Group\Backend\IDeleteGroupBackend;
use OCP\Group\Backend\IGetDisplayNameBackend;
use OCP\Group\Backend\IGroupDetailsBackend;
+use OCP\Group\Backend\INamedBackend;
use OCP\Group\Backend\IRemoveFromGroupBackend;
use OCP\Group\Backend\ISearchableGroupBackend;
use OCP\Group\Backend\ISetDisplayNameBackend;
-use OCP\Group\Backend\INamedBackend;
use OCP\IDBConnection;
use OCP\IUserManager;
-use OC\User\LazyUser;
/**
* Class for group management in a SQL Database (e.g. MySQL, SQLite)
diff --git a/lib/private/Group/DisplayNameCache.php b/lib/private/Group/DisplayNameCache.php
index 4eb8211be6e..28f9d817b0d 100644
--- a/lib/private/Group/DisplayNameCache.php
+++ b/lib/private/Group/DisplayNameCache.php
@@ -38,6 +38,7 @@ use OCP\IGroupManager;
* Class that cache the relation Group ID -> Display name
*
* This saves fetching the group from the backend for "just" the display name
+ * @template-implements IEventListener<GroupChangedEvent|GroupDeletedEvent>
*/
class DisplayNameCache implements IEventListener {
private CappedMemoryCache $cache;
diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php
index 441ee64604d..d8d1a73762d 100644
--- a/lib/private/Group/Group.php
+++ b/lib/private/Group/Group.php
@@ -35,13 +35,6 @@ namespace OC\Group;
use OC\Hooks\PublicEmitter;
use OC\User\LazyUser;
use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Group\Events\BeforeGroupDeletedEvent;
-use OCP\Group\Events\BeforeUserAddedEvent;
-use OCP\Group\Events\BeforeUserRemovedEvent;
-use OCP\Group\Events\GroupDeletedEvent;
-use OCP\Group\Events\UserAddedEvent;
-use OCP\Group\Events\UserRemovedEvent;
-use OCP\GroupInterface;
use OCP\Group\Backend\ICountDisabledInGroup;
use OCP\Group\Backend\IGetDisplayNameBackend;
use OCP\Group\Backend\IHideFromCollaborationBackend;
@@ -49,7 +42,14 @@ use OCP\Group\Backend\INamedBackend;
use OCP\Group\Backend\ISearchableGroupBackend;
use OCP\Group\Backend\ISetDisplayNameBackend;
use OCP\Group\Events\BeforeGroupChangedEvent;
+use OCP\Group\Events\BeforeGroupDeletedEvent;
+use OCP\Group\Events\BeforeUserAddedEvent;
+use OCP\Group\Events\BeforeUserRemovedEvent;
use OCP\Group\Events\GroupChangedEvent;
+use OCP\Group\Events\GroupDeletedEvent;
+use OCP\Group\Events\UserAddedEvent;
+use OCP\Group\Events\UserRemovedEvent;
+use OCP\GroupInterface;
use OCP\IGroup;
use OCP\IUser;
use OCP\IUserManager;
@@ -85,11 +85,11 @@ class Group implements IGroup {
$this->displayName = $displayName;
}
- public function getGID() {
+ public function getGID(): string {
return $this->gid;
}
- public function getDisplayName() {
+ public function getDisplayName(): string {
if (is_null($this->displayName)) {
foreach ($this->backends as $backend) {
if ($backend instanceof IGetDisplayNameBackend) {
@@ -126,7 +126,7 @@ class Group implements IGroup {
*
* @return \OC\User\User[]
*/
- public function getUsers() {
+ public function getUsers(): array {
if ($this->usersLoaded) {
return $this->users;
}
@@ -153,7 +153,7 @@ class Group implements IGroup {
* @param IUser $user
* @return bool
*/
- public function inGroup(IUser $user) {
+ public function inGroup(IUser $user): bool {
if (isset($this->users[$user->getUID()])) {
return true;
}
@@ -171,7 +171,7 @@ class Group implements IGroup {
*
* @param IUser $user
*/
- public function addUser(IUser $user) {
+ public function addUser(IUser $user): void {
if ($this->inGroup($user)) {
return;
}
@@ -200,10 +200,8 @@ class Group implements IGroup {
/**
* remove a user from the group
- *
- * @param \OC\User\User $user
*/
- public function removeUser($user) {
+ public function removeUser(IUser $user): void {
$result = false;
$this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($this, $user));
if ($this->emitter) {
@@ -262,7 +260,7 @@ class Group implements IGroup {
* @param string $search
* @return int|bool
*/
- public function count($search = '') {
+ public function count($search = ''): int|bool {
$users = false;
foreach ($this->backends as $backend) {
if ($backend->implementsActions(\OC\Group\Backend::COUNT_USERS)) {
@@ -282,7 +280,7 @@ class Group implements IGroup {
*
* @return int|bool
*/
- public function countDisabled() {
+ public function countDisabled(): int|bool {
$users = false;
foreach ($this->backends as $backend) {
if ($backend instanceof ICountDisabledInGroup) {
@@ -306,7 +304,7 @@ class Group implements IGroup {
* @return IUser[]
* @deprecated 27.0.0 Use searchUsers instead (same implementation)
*/
- public function searchDisplayName($search, $limit = null, $offset = null) {
+ public function searchDisplayName(string $search, int $limit = null, int $offset = null): array {
return $this->searchUsers($search, $limit, $offset);
}
@@ -315,7 +313,7 @@ class Group implements IGroup {
*
* @return string[]
*/
- public function getBackendNames() {
+ public function getBackendNames(): array {
$backends = [];
foreach ($this->backends as $backend) {
if ($backend instanceof INamedBackend) {
@@ -329,11 +327,11 @@ class Group implements IGroup {
}
/**
- * delete the group
+ * Delete the group
*
* @return bool
*/
- public function delete() {
+ public function delete(): bool {
// Prevent users from deleting group admin
if ($this->getGID() === 'admin') {
return false;
@@ -378,7 +376,7 @@ class Group implements IGroup {
* @return bool
* @since 14.0.0
*/
- public function canRemoveUser() {
+ public function canRemoveUser(): bool {
foreach ($this->backends as $backend) {
if ($backend->implementsActions(GroupInterface::REMOVE_FROM_GOUP)) {
return true;
@@ -391,7 +389,7 @@ class Group implements IGroup {
* @return bool
* @since 14.0.0
*/
- public function canAddUser() {
+ public function canAddUser(): bool {
foreach ($this->backends as $backend) {
if ($backend->implementsActions(GroupInterface::ADD_TO_GROUP)) {
return true;
diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php
index 47475121ea0..eb9daebb222 100644
--- a/lib/private/Group/Manager.php
+++ b/lib/private/Group/Manager.php
@@ -89,9 +89,9 @@ class Manager extends PublicEmitter implements IGroupManager {
private DisplayNameCache $displayNameCache;
public function __construct(\OC\User\Manager $userManager,
- IEventDispatcher $dispatcher,
- LoggerInterface $logger,
- ICacheFactory $cacheFactory) {
+ IEventDispatcher $dispatcher,
+ LoggerInterface $logger,
+ ICacheFactory $cacheFactory) {
$this->userManager = $userManager;
$this->dispatcher = $dispatcher;
$this->logger = $logger;
@@ -371,7 +371,7 @@ class Manager extends PublicEmitter implements IGroupManager {
* @return bool if in group
*/
public function isInGroup($userId, $group) {
- return array_search($group, $this->getUserIdGroupIds($userId)) !== false;
+ return in_array($group, $this->getUserIdGroupIds($userId));
}
/**
diff --git a/lib/private/Group/MetaData.php b/lib/private/Group/MetaData.php
index a58d7e78bfc..973db134728 100644
--- a/lib/private/Group/MetaData.php
+++ b/lib/private/Group/MetaData.php
@@ -57,11 +57,11 @@ class MetaData {
* @param bool $isAdmin whether the current users is an admin
*/
public function __construct(
- string $user,
- bool $isAdmin,
- IGroupManager $groupManager,
- IUserSession $userSession
- ) {
+ string $user,
+ bool $isAdmin,
+ IGroupManager $groupManager,
+ IUserSession $userSession
+ ) {
$this->user = $user;
$this->isAdmin = $isAdmin;
$this->groupManager = $groupManager;
diff --git a/lib/private/Hooks/EmitterTrait.php b/lib/private/Hooks/EmitterTrait.php
index da4e3da2bd6..fe9cba893de 100644
--- a/lib/private/Hooks/EmitterTrait.php
+++ b/lib/private/Hooks/EmitterTrait.php
@@ -43,7 +43,7 @@ trait EmitterTrait {
if (!isset($this->listeners[$eventName])) {
$this->listeners[$eventName] = [];
}
- if (array_search($callback, $this->listeners[$eventName], true) === false) {
+ if (!in_array($callback, $this->listeners[$eventName], true)) {
$this->listeners[$eventName][] = $callback;
}
}
diff --git a/lib/private/Http/CookieHelper.php b/lib/private/Http/CookieHelper.php
index 720a1e9185d..eedb6e05c39 100644
--- a/lib/private/Http/CookieHelper.php
+++ b/lib/private/Http/CookieHelper.php
@@ -33,13 +33,13 @@ class CookieHelper {
public const SAMESITE_STRICT = 2;
public static function setCookie(string $name,
- string $value = '',
- int $maxAge = 0,
- string $path = '',
- string $domain = '',
- bool $secure = false,
- bool $httponly = false,
- int $samesite = self::SAMESITE_NONE) {
+ string $value = '',
+ int $maxAge = 0,
+ string $path = '',
+ string $domain = '',
+ bool $secure = false,
+ bool $httponly = false,
+ int $samesite = self::SAMESITE_NONE) {
$header = sprintf(
'Set-Cookie: %s=%s',
$name,
diff --git a/lib/private/Http/WellKnown/RequestManager.php b/lib/private/Http/WellKnown/RequestManager.php
index b83ff2ada50..783b04c0f5d 100644
--- a/lib/private/Http/WellKnown/RequestManager.php
+++ b/lib/private/Http/WellKnown/RequestManager.php
@@ -49,8 +49,8 @@ class RequestManager {
private $logger;
public function __construct(Coordinator $coordinator,
- IServerContainer $container,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ LoggerInterface $logger) {
$this->coordinator = $coordinator;
$this->container = $container;
$this->logger = $logger;
diff --git a/lib/private/Installer.php b/lib/private/Installer.php
index dc81135b644..6ab497b9dea 100644
--- a/lib/private/Installer.php
+++ b/lib/private/Installer.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
@@ -53,6 +56,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;
@@ -60,37 +64,17 @@ use Psr\Log\LoggerInterface;
* This class provides the functionality needed to install, update and remove apps
*/
class Installer {
- /** @var AppFetcher */
- private $appFetcher;
- /** @var IClientService */
- private $clientService;
- /** @var ITempManager */
- private $tempManager;
- /** @var LoggerInterface */
- private $logger;
- /** @var IConfig */
- private $config;
- /** @var array - for caching the result of app fetcher */
- private $apps = null;
- /** @var bool|null - for caching the result of the ready status */
- private $isInstanceReadyForUpdates = null;
- /** @var bool */
- private $isCLI;
+ private ?bool $isInstanceReadyForUpdates = null;
+ private ?array $apps = null;
public function __construct(
- AppFetcher $appFetcher,
- IClientService $clientService,
- ITempManager $tempManager,
- LoggerInterface $logger,
- IConfig $config,
- bool $isCLI
+ private AppFetcher $appFetcher,
+ private IClientService $clientService,
+ private ITempManager $tempManager,
+ private LoggerInterface $logger,
+ private IConfig $config,
+ private bool $isCLI,
) {
- $this->appFetcher = $appFetcher;
- $this->clientService = $clientService;
- $this->tempManager = $tempManager;
- $this->logger = $logger;
- $this->config = $config;
- $this->isCLI = $isCLI;
}
/**
@@ -113,7 +97,7 @@ class Installer {
throw new \Exception('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
}
- $l = \OC::$server->getL10N('core');
+ $l = \OCP\Util::getL10N('core');
$info = \OCP\Server::get(IAppManager::class)->getAppInfo($basedir . '/appinfo/info.xml', true, $l->getLanguageCode());
if (!is_array($info)) {
@@ -150,7 +134,7 @@ class Installer {
}
//install the database
- $ms = new MigrationService($info['id'], \OC::$server->get(Connection::class));
+ $ms = new MigrationService($info['id'], \OCP\Server::get(Connection::class));
$ms->migrate('latest', !$previousVersion);
if ($previousVersion) {
@@ -164,16 +148,17 @@ class Installer {
OC_App::executeRepairSteps($appId, $info['repair-steps']['install']);
+ $config = \OCP\Server::get(IConfig::class);
//set the installed version
- \OC::$server->getConfig()->setAppValue($info['id'], 'installed_version', \OCP\Server::get(IAppManager::class)->getAppVersion($info['id'], false));
- \OC::$server->getConfig()->setAppValue($info['id'], 'enabled', 'no');
+ $config->setAppValue($info['id'], 'installed_version', \OCP\Server::get(IAppManager::class)->getAppVersion($info['id'], false));
+ $config->setAppValue($info['id'], 'enabled', 'no');
//set remote/public handlers
foreach ($info['remote'] as $name => $path) {
- \OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
+ $config->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
}
foreach ($info['public'] as $name => $path) {
- \OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
+ $config->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
}
OC_App::setAppTypes($info['id']);
@@ -184,11 +169,9 @@ class Installer {
/**
* Updates the specified app from the appstore
*
- * @param string $appId
- * @param bool [$allowUnstable] Allow unstable releases
- * @return bool
+ * @param bool $allowUnstable Allow unstable releases
*/
- public function updateAppstoreApp($appId, $allowUnstable = false) {
+ public function updateAppstoreApp(string $appId, bool $allowUnstable = false): bool {
if ($this->isUpdateAvailable($appId, $allowUnstable)) {
try {
$this->downloadApp($appId, $allowUnstable);
@@ -224,7 +207,7 @@ class Installer {
*
* @throws \Exception If the installation was not successful
*/
- public function downloadApp($appId, $allowUnstable = false) {
+ public function downloadApp(string $appId, bool $allowUnstable = false): void {
$appId = strtolower($appId);
$apps = $this->appFetcher->get($allowUnstable);
@@ -400,10 +383,10 @@ class Installer {
* @param bool $allowUnstable
* @return string|false false or the version number of the update
*/
- public function isUpdateAvailable($appId, $allowUnstable = false) {
+ public function isUpdateAvailable($appId, $allowUnstable = false): string|false {
if ($this->isInstanceReadyForUpdates === null) {
$installPath = OC_App::getInstallPath();
- if ($installPath === false || $installPath === null) {
+ if ($installPath === null) {
$this->isInstanceReadyForUpdates = false;
} else {
$this->isInstanceReadyForUpdates = true;
@@ -443,12 +426,10 @@ class Installer {
/**
* Check if app has been installed from git
- * @param string $name name of the application to remove
- * @return boolean
*
* The function will check if the path contains a .git folder
*/
- private function isInstalledFromGit($appId) {
+ private function isInstalledFromGit(string $appId): bool {
$app = \OC_App::findAppInDirectories($appId);
if ($app === false) {
return false;
@@ -459,12 +440,10 @@ class Installer {
/**
* Check if app is already downloaded
- * @param string $name name of the application to remove
- * @return boolean
*
* The function will check if the app is already downloaded in the apps repository
*/
- public function isDownloaded($name) {
+ public function isDownloaded(string $name): bool {
foreach (\OC::$APPSROOTS as $dir) {
$dirToTest = $dir['path'];
$dirToTest .= '/';
@@ -481,9 +460,6 @@ class Installer {
/**
* Removes an app
- * @param string $appId ID of the application to remove
- * @return boolean
- *
*
* This function works as follows
* -# call uninstall repair steps
@@ -492,9 +468,9 @@ class Installer {
* The function will not delete preferences, tables and the configuration,
* this has to be done by the function oc_app_uninstall().
*/
- public function removeApp($appId) {
+ public function removeApp(string $appId): bool {
if ($this->isDownloaded($appId)) {
- if (\OC::$server->getAppManager()->isShipped($appId)) {
+ if (\OCP\Server::get(IAppManager::class)->isShipped($appId)) {
return false;
}
$appDir = OC_App::getInstallPath() . '/' . $appId;
@@ -510,10 +486,9 @@ class Installer {
/**
* Installs the app within the bundle and marks the bundle as installed
*
- * @param Bundle $bundle
* @throws \Exception If app could not get installed
*/
- public function installAppBundle(Bundle $bundle) {
+ public function installAppBundle(Bundle $bundle): void {
$appIds = $bundle->getAppIdentifiers();
foreach ($appIds as $appId) {
if (!$this->isDownloaded($appId)) {
@@ -536,9 +511,12 @@ 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) {
- $appManager = \OC::$server->getAppManager();
- $config = \OC::$server->getConfig();
+ public static function installShippedApps(bool $softErrors = false, ?IOutput $output = null): array {
+ if ($output instanceof IOutput) {
+ $output->debug('Installing shipped apps');
+ }
+ $appManager = \OCP\Server::get(IAppManager::class);
+ $config = \OCP\Server::get(IConfig::class);
$errors = [];
foreach (\OC::$APPSROOTS as $app_dir) {
if ($dir = opendir($app_dir['path'])) {
@@ -551,7 +529,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 +538,7 @@ class Installer {
throw $e;
}
} else {
- Installer::installShippedApp($filename);
+ Installer::installShippedApp($filename, $output);
}
$config->setAppValue($filename, 'enabled', 'yes');
}
@@ -577,17 +555,21 @@ class Installer {
/**
* install an app already placed in the app folder
- * @param string $app id of the app to install
- * @return integer
*/
- public static function installShippedApp($app) {
+ public static function installShippedApp(string $app, ?IOutput $output = null): string|false {
+ if ($output instanceof IOutput) {
+ $output->debug('Installing ' . $app);
+ }
//install the database
$appPath = OC_App::getAppPath($app);
\OC_App::registerAutoloading($app, $appPath);
- $config = \OC::$server->getConfig();
+ $config = \OCP\Server::get(IConfig::class);
- $ms = new MigrationService($app, \OC::$server->get(Connection::class));
+ $ms = new MigrationService($app, \OCP\Server::get(Connection::class));
+ if ($output instanceof IOutput) {
+ $ms->setOutput($output);
+ }
$previousVersion = $config->getAppValue($app, 'installed_version', false);
$ms->migrate('latest', !$previousVersion);
@@ -598,6 +580,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']);
@@ -620,10 +605,7 @@ class Installer {
return $info['id'];
}
- /**
- * @param string $script
- */
- private static function includeAppScript($script) {
+ private static function includeAppScript(string $script): void {
if (file_exists($script)) {
include $script;
}
diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php
index a2ff62e4070..a5dec637bdb 100644
--- a/lib/private/IntegrityCheck/Checker.php
+++ b/lib/private/IntegrityCheck/Checker.php
@@ -83,12 +83,12 @@ class Checker {
* @param IMimeTypeDetector $mimeTypeDetector
*/
public function __construct(EnvironmentHelper $environmentHelper,
- FileAccessHelper $fileAccessHelper,
- AppLocator $appLocator,
- ?IConfig $config,
- ICacheFactory $cacheFactory,
- ?IAppManager $appManager,
- IMimeTypeDetector $mimeTypeDetector) {
+ FileAccessHelper $fileAccessHelper,
+ AppLocator $appLocator,
+ ?IConfig $config,
+ ICacheFactory $cacheFactory,
+ ?IAppManager $appManager,
+ IMimeTypeDetector $mimeTypeDetector) {
$this->environmentHelper = $environmentHelper;
$this->fileAccessHelper = $fileAccessHelper;
$this->appLocator = $appLocator;
@@ -161,7 +161,7 @@ class Checker {
* @return array Array of hashes.
*/
private function generateHashes(\RecursiveIteratorIterator $iterator,
- string $path): array {
+ string $path): array {
$hashes = [];
$baseDirectoryLength = \strlen($path);
@@ -223,8 +223,8 @@ class Checker {
* @return array
*/
private function createSignatureData(array $hashes,
- X509 $certificate,
- RSA $privateKey): array {
+ X509 $certificate,
+ RSA $privateKey): array {
ksort($hashes);
$privateKey->setSignatureMode(RSA::SIGNATURE_PSS);
@@ -249,8 +249,8 @@ class Checker {
* @throws \Exception
*/
public function writeAppSignature($path,
- X509 $certificate,
- RSA $privateKey) {
+ X509 $certificate,
+ RSA $privateKey) {
$appInfoDir = $path . '/appinfo';
try {
$this->fileAccessHelper->assertDirectoryExists($appInfoDir);
@@ -279,8 +279,8 @@ class Checker {
* @throws \Exception
*/
public function writeCoreSignature(X509 $certificate,
- RSA $rsa,
- $path) {
+ RSA $rsa,
+ $path) {
$coreDir = $path . '/core';
try {
$this->fileAccessHelper->assertDirectoryExists($coreDir);
diff --git a/lib/private/Lock/MemcacheLockingProvider.php b/lib/private/Lock/MemcacheLockingProvider.php
index 8ad25576084..b9c3e995460 100644
--- a/lib/private/Lock/MemcacheLockingProvider.php
+++ b/lib/private/Lock/MemcacheLockingProvider.php
@@ -27,21 +27,42 @@ declare(strict_types=1);
*/
namespace OC\Lock;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IMemcache;
use OCP\IMemcacheTTL;
use OCP\Lock\LockedException;
class MemcacheLockingProvider extends AbstractLockingProvider {
+ /** @var array<string, array{time: int, ttl: int}> */
+ private array $oldTTLs = [];
+
public function __construct(
private IMemcache $memcache,
+ private ITimeFactory $timeFactory,
int $ttl = 3600,
) {
parent::__construct($ttl);
}
- private function setTTL(string $path): void {
+ private function setTTL(string $path, int $ttl = null, ?int $compare = null): void {
+ if (is_null($ttl)) {
+ $ttl = $this->ttl;
+ }
+ if ($this->memcache instanceof IMemcacheTTL) {
+ if ($compare !== null) {
+ $this->memcache->compareSetTTL($path, $compare, $ttl);
+ } else {
+ $this->memcache->setTTL($path, $ttl);
+ }
+ }
+ }
+
+ private function getTTL(string $path): int {
if ($this->memcache instanceof IMemcacheTTL) {
- $this->memcache->setTTL($path, $this->ttl);
+ $ttl = $this->memcache->getTTL($path);
+ return $ttl === false ? -1 : $ttl;
+ } else {
+ return -1;
}
}
@@ -58,14 +79,22 @@ class MemcacheLockingProvider extends AbstractLockingProvider {
public function acquireLock(string $path, int $type, ?string $readablePath = null): void {
if ($type === self::LOCK_SHARED) {
+ // save the old TTL to for `restoreTTL`
+ $this->oldTTLs[$path] = [
+ "ttl" => $this->getTTL($path),
+ "time" => $this->timeFactory->getTime()
+ ];
if (!$this->memcache->inc($path)) {
throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath);
}
} else {
+ // when getting exclusive locks, we know there are no old TTLs to restore
$this->memcache->add($path, 0);
+ // ttl is updated automatically when the `set` succeeds
if (!$this->memcache->cas($path, 0, 'exclusive')) {
throw new LockedException($path, null, $this->getExistingLockForException($path), $readablePath);
}
+ unset($this->oldTTLs[$path]);
}
$this->setTTL($path);
$this->markAcquire($path, $type);
@@ -88,6 +117,12 @@ class MemcacheLockingProvider extends AbstractLockingProvider {
$newValue = $this->memcache->dec($path);
}
+ if ($newValue > 0) {
+ $this->restoreTTL($path);
+ } else {
+ unset($this->oldTTLs[$path]);
+ }
+
// if we somehow release more locks then exists, reset the lock
if ($newValue < 0) {
$this->memcache->cad($path, $newValue);
@@ -106,13 +141,52 @@ class MemcacheLockingProvider extends AbstractLockingProvider {
} elseif ($targetType === self::LOCK_EXCLUSIVE) {
// we can only change a shared lock to an exclusive if there's only a single owner of the shared lock
if (!$this->memcache->cas($path, 1, 'exclusive')) {
+ $this->restoreTTL($path);
throw new LockedException($path, null, $this->getExistingLockForException($path));
}
+ unset($this->oldTTLs[$path]);
}
$this->setTTL($path);
$this->markChange($path, $targetType);
}
+ /**
+ * With shared locks, each time the lock is acquired, the ttl for the path is reset.
+ *
+ * Due to this "ttl extension" when a shared lock isn't freed correctly for any reason
+ * the lock won't expire until no shared locks are required for the path for 1h.
+ * This can lead to a client repeatedly trying to upload a file, and failing forever
+ * because the lock never gets the opportunity to expire.
+ *
+ * To help the lock expire in this case, we lower the TTL back to what it was before we
+ * took the shared lock *only* if nobody else got a shared lock after we did.
+ *
+ * This doesn't handle all cases where multiple requests are acquiring shared locks
+ * but it should handle some of the more common ones and not hurt things further
+ */
+ private function restoreTTL(string $path): void {
+ if (isset($this->oldTTLs[$path])) {
+ $saved = $this->oldTTLs[$path];
+ $elapsed = $this->timeFactory->getTime() - $saved['time'];
+
+ // old value to compare to when setting ttl in case someone else changes the lock in the middle of this function
+ $value = $this->memcache->get($path);
+
+ $currentTtl = $this->getTTL($path);
+
+ // what the old ttl would be given the time elapsed since we acquired the lock
+ // note that if this gets negative the key will be expired directly when we set the ttl
+ $remainingOldTtl = $saved['ttl'] - $elapsed;
+ // what the currently ttl would be if nobody else acquired a lock since we did (+1 to cover rounding errors)
+ $expectedTtl = $this->ttl - $elapsed + 1;
+
+ // check if another request has acquired a lock (and didn't release it yet)
+ if ($currentTtl <= $expectedTtl) {
+ $this->setTTL($path, $remainingOldTtl, $value);
+ }
+ }
+ }
+
private function getExistingLockForException(string $path): string {
$existing = $this->memcache->get($path);
if (!$existing) {
diff --git a/lib/private/Log.php b/lib/private/Log.php
index d6750491d92..9975696ff06 100644
--- a/lib/private/Log.php
+++ b/lib/private/Log.php
@@ -38,6 +38,8 @@ namespace OC;
use Exception;
use Nextcloud\LogNormalizer\Normalizer;
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\Log\ExceptionSerializer;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger;
use OCP\IUserSession;
@@ -46,8 +48,6 @@ use OCP\Log\IDataLogger;
use OCP\Log\IFileBased;
use OCP\Log\IWriter;
use OCP\Support\CrashReport\IRegistry;
-use OC\AppFramework\Bootstrap\Coordinator;
-use OC\Log\ExceptionSerializer;
use Throwable;
use function array_merge;
use function strtr;
@@ -344,7 +344,7 @@ class Log implements ILogger, IDataLogger {
unset($data['app']);
unset($data['level']);
$data = array_merge($serializer->serializeException($exception), $data);
- $data = $this->interpolateMessage($data, $context['message'] ?? '--', 'CustomMessage');
+ $data = $this->interpolateMessage($data, isset($context['message']) && $context['message'] !== '' ? $context['message'] : ('Exception thrown: ' . get_class($exception)), 'CustomMessage');
array_walk($context, [$this->normalizer, 'format']);
diff --git a/lib/private/Log/Rotate.php b/lib/private/Log/Rotate.php
index 4c0e258b2f9..efe548b7783 100644
--- a/lib/private/Log/Rotate.php
+++ b/lib/private/Log/Rotate.php
@@ -43,7 +43,7 @@ class Rotate extends \OCP\BackgroundJob\Job {
if ($this->shouldRotateBySize()) {
$rotatedFile = $this->rotate();
$msg = 'Log file "'.$this->filePath.'" was over '.$this->maxSize.' bytes, moved to "'.$rotatedFile.'"';
- \OC::$server->getLogger()->warning($msg, ['app' => Rotate::class]);
+ \OC::$server->getLogger()->info($msg, ['app' => Rotate::class]);
}
}
}
diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php
index 16d6ae32f72..ab8fcea4e6a 100644
--- a/lib/private/Memcache/Factory.php
+++ b/lib/private/Memcache/Factory.php
@@ -32,10 +32,10 @@
namespace OC\Memcache;
use OCP\Cache\CappedMemoryCache;
-use OCP\Profiler\IProfiler;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IMemcache;
+use OCP\Profiler\IProfiler;
use Psr\Log\LoggerInterface;
class Factory implements ICacheFactory {
diff --git a/lib/private/Memcache/LoggerWrapperCache.php b/lib/private/Memcache/LoggerWrapperCache.php
index 55c0e76db79..55d5104dedb 100644
--- a/lib/private/Memcache/LoggerWrapperCache.php
+++ b/lib/private/Memcache/LoggerWrapperCache.php
@@ -167,10 +167,18 @@ class LoggerWrapperCache extends Cache implements IMemcacheTTL {
}
/** @inheritDoc */
- public function setTTL($key, $ttl) {
+ public function setTTL(string $key, int $ttl) {
$this->wrappedCache->setTTL($key, $ttl);
}
+ public function getTTL(string $key): int|false {
+ return $this->wrappedCache->getTTL($key);
+ }
+
+ public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
+ return $this->wrappedCache->compareSetTTL($key, $value, $ttl);
+ }
+
public static function isAvailable(): bool {
return true;
}
diff --git a/lib/private/Memcache/ProfilerWrapperCache.php b/lib/private/Memcache/ProfilerWrapperCache.php
index 6e76989dddd..a5cb667114c 100644
--- a/lib/private/Memcache/ProfilerWrapperCache.php
+++ b/lib/private/Memcache/ProfilerWrapperCache.php
@@ -183,10 +183,18 @@ class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL
}
/** @inheritDoc */
- public function setTTL($key, $ttl) {
+ public function setTTL(string $key, int $ttl) {
$this->wrappedCache->setTTL($key, $ttl);
}
+ public function getTTL(string $key): int|false {
+ return $this->wrappedCache->getTTL($key);
+ }
+
+ public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
+ return $this->wrappedCache->compareSetTTL($key, $value, $ttl);
+ }
+
public function offsetExists($offset): bool {
return $this->hasKey($offset);
}
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php
index bde25a3385a..6634f325005 100644
--- a/lib/private/Memcache/Redis.php
+++ b/lib/private/Memcache/Redis.php
@@ -46,6 +46,10 @@ class Redis extends Cache implements IMemcacheTTL {
'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end',
'cf0e94b2e9ffc7e04395cf88f7583fc309985910',
],
+ 'caSetTtl' => [
+ 'if redis.call("get", KEYS[1]) == ARGV[1] then redis.call("expire", KEYS[1], ARGV[2]) return 1 else return 0 end',
+ 'fa4acbc946d23ef41d7d3910880b60e6e4972d72',
+ ],
];
/**
@@ -181,6 +185,17 @@ class Redis extends Cache implements IMemcacheTTL {
$this->getCache()->expire($this->getPrefix() . $key, $ttl);
}
+ public function getTTL(string $key): int|false {
+ $ttl = $this->getCache()->ttl($this->getPrefix() . $key);
+ return $ttl > 0 ? (int)$ttl : false;
+ }
+
+ public function compareSetTTL(string $key, mixed $value, int $ttl): bool {
+ $value = self::encodeValue($value);
+
+ return $this->evalLua('caSetTtl', [$key], [$value, $ttl]) > 0;
+ }
+
public static function isAvailable(): bool {
return \OC::$server->getGetRedisFactory()->isAvailable();
}
diff --git a/lib/private/Metadata/FileEventListener.php b/lib/private/Metadata/FileEventListener.php
deleted file mode 100644
index 162e85ff3aa..00000000000
--- a/lib/private/Metadata/FileEventListener.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @license AGPL-3.0-or-later
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-namespace OC\Metadata;
-
-use OC\Files\Filesystem;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventListener;
-use OCP\Files\Events\Node\NodeDeletedEvent;
-use OCP\Files\Events\Node\NodeWrittenEvent;
-use OCP\Files\Events\NodeRemovedFromCache;
-use OCP\Files\File;
-use OCP\Files\Node;
-use OCP\Files\NotFoundException;
-use OCP\Files\FileInfo;
-use Psr\Log\LoggerInterface;
-
-/**
- * @template-implements IEventListener<NodeRemovedFromCache>
- * @template-implements IEventListener<NodeDeletedEvent>
- * @template-implements IEventListener<NodeWrittenEvent>
- */
-class FileEventListener implements IEventListener {
- private IMetadataManager $manager;
- private LoggerInterface $logger;
-
- public function __construct(IMetadataManager $manager, LoggerInterface $logger) {
- $this->manager = $manager;
- $this->logger = $logger;
- }
-
- private function shouldExtractMetadata(Node $node): bool {
- try {
- if ($node->getMimetype() === 'httpd/unix-directory') {
- return false;
- }
- } catch (NotFoundException $e) {
- return false;
- }
- if ($node->getSize(false) <= 0) {
- return false;
- }
-
- $path = $node->getPath();
- return $this->isCorrectPath($path);
- }
-
- private function isCorrectPath(string $path): bool {
- // TODO make this more dynamic, we have the same issue in other places
- return !str_starts_with($path, 'appdata_') && !str_starts_with($path, 'files_versions/') && !str_starts_with($path, 'files_trashbin/');
- }
-
- public function handle(Event $event): void {
- if ($event instanceof NodeRemovedFromCache) {
- if (!$this->isCorrectPath($event->getPath())) {
- // Don't listen to paths for which we don't extract metadata
- return;
- }
- $view = Filesystem::getView();
- if (!$view) {
- // Should not happen since a scan in the user folder should setup
- // the file system.
- $e = new \Exception(); // don't trigger, just get backtrace
- $this->logger->error('Detecting deletion of a file with possible metadata but file system setup is not setup', [
- 'exception' => $e,
- 'app' => 'metadata'
- ]);
- return;
- }
- $info = $view->getFileInfo($event->getPath());
- if ($info && $info->getType() === FileInfo::TYPE_FILE) {
- $this->manager->clearMetadata($info->getId());
- }
- }
-
- if ($event instanceof NodeDeletedEvent) {
- $node = $event->getNode();
- if ($this->shouldExtractMetadata($node)) {
- /** @var File $node */
- $this->manager->clearMetadata($event->getNode()->getId());
- }
- }
-
- if ($event instanceof NodeWrittenEvent) {
- $node = $event->getNode();
- if ($this->shouldExtractMetadata($node)) {
- /** @var File $node */
- $this->manager->generateMetadata($event->getNode(), false);
- }
- }
- }
-}
diff --git a/lib/private/Metadata/FileMetadata.php b/lib/private/Metadata/FileMetadata.php
deleted file mode 100644
index a9808a86998..00000000000
--- a/lib/private/Metadata/FileMetadata.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-namespace OC\Metadata;
-
-use OCP\AppFramework\Db\Entity;
-use OCP\DB\Types;
-
-/**
- * @method string getGroupName()
- * @method void setGroupName(string $groupName)
- * @method string getValue()
- * @method void setValue(string $value)
- * @see \OC\Core\Migrations\Version240000Date20220404230027
- */
-class FileMetadata extends Entity {
- protected ?string $groupName = null;
- protected ?string $value = null;
-
- public function __construct() {
- $this->addType('groupName', 'string');
- $this->addType('value', Types::STRING);
- }
-
- public function getDecodedValue(): array {
- return json_decode($this->getValue(), true) ?? [];
- }
-
- public function setArrayAsValue(array $value): void {
- $this->setValue(json_encode($value, JSON_THROW_ON_ERROR));
- }
-}
diff --git a/lib/private/Metadata/FileMetadataMapper.php b/lib/private/Metadata/FileMetadataMapper.php
deleted file mode 100644
index 003ab13126e..00000000000
--- a/lib/private/Metadata/FileMetadataMapper.php
+++ /dev/null
@@ -1,177 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @copyright Copyright 2022 Louis Chmn <louis@chmn.me>
- * @license AGPL-3.0-or-later
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-namespace OC\Metadata;
-
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\AppFramework\Db\MultipleObjectsReturnedException;
-use OCP\AppFramework\Db\QBMapper;
-use OCP\AppFramework\Db\Entity;
-use OCP\DB\Exception;
-use OCP\DB\QueryBuilder\IQueryBuilder;
-use OCP\IDBConnection;
-
-/**
- * @template-extends QBMapper<FileMetadata>
- */
-class FileMetadataMapper extends QBMapper {
- public function __construct(IDBConnection $db) {
- parent::__construct($db, 'file_metadata', FileMetadata::class);
- }
-
- /**
- * @return FileMetadata[]
- * @throws Exception
- */
- public function findForFile(int $fileId): array {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
-
- return $this->findEntities($qb);
- }
-
- /**
- * @throws DoesNotExistException
- * @throws MultipleObjectsReturnedException
- * @throws Exception
- */
- public function findForGroupForFile(int $fileId, string $groupName): FileMetadata {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, IQueryBuilder::PARAM_STR)));
-
- return $this->findEntity($qb);
- }
-
- /**
- * @return array<int, FileMetadata>
- * @throws Exception
- */
- public function findForGroupForFiles(array $fileIds, string $groupName): array {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->getTableName())
- ->where($qb->expr()->in('id', $qb->createParameter('fileIds')))
- ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, IQueryBuilder::PARAM_STR)));
-
- $metadata = [];
- foreach (array_chunk($fileIds, 1000) as $fileIdsChunk) {
- $qb->setParameter('fileIds', $fileIdsChunk, IQueryBuilder::PARAM_INT_ARRAY);
- /** @var FileMetadata[] $rawEntities */
- $rawEntities = $this->findEntities($qb);
- foreach ($rawEntities as $entity) {
- $metadata[$entity->getId()] = $entity;
- }
- }
-
- foreach ($fileIds as $id) {
- if (isset($metadata[$id])) {
- continue;
- }
- $empty = new FileMetadata();
- $empty->setValue('');
- $empty->setGroupName($groupName);
- $empty->setId($id);
- $metadata[$id] = $empty;
- }
- return $metadata;
- }
-
- public function clear(int $fileId): void {
- $qb = $this->db->getQueryBuilder();
- $qb->delete($this->getTableName())
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
-
- $qb->executeStatement();
- }
-
- /**
- * Updates an entry in the db from an entity
- *
- * @param FileMetadata $entity the entity that should be created
- * @return FileMetadata the saved entity with the set id
- * @throws Exception
- * @throws \InvalidArgumentException if entity has no id
- */
- public function update(Entity $entity): FileMetadata {
- if (!($entity instanceof FileMetadata)) {
- throw new \Exception("Entity should be a FileMetadata entity");
- }
-
- // entity needs an id
- $id = $entity->getId();
- if ($id === null) {
- throw new \InvalidArgumentException('Entity which should be updated has no id');
- }
-
- // entity needs an group_name
- $groupName = $entity->getGroupName();
- if ($groupName === null) {
- throw new \InvalidArgumentException('Entity which should be updated has no group_name');
- }
-
- $idType = $this->getParameterTypeForProperty($entity, 'id');
- $groupNameType = $this->getParameterTypeForProperty($entity, 'groupName');
- $value = $entity->getValue();
- $valueType = $this->getParameterTypeForProperty($entity, 'value');
-
- $qb = $this->db->getQueryBuilder();
-
- $qb->update($this->tableName)
- ->set('value', $qb->createNamedParameter($value, $valueType))
- ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, $idType)))
- ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, $groupNameType)))
- ->executeStatement();
-
- return $entity;
- }
-
- /**
- * Override the insertOrUpdate as we could be in a transaction in which case we can not afford on error.
- *
- * @param FileMetadata $entity the entity that should be created/updated
- * @return FileMetadata the saved entity with the (new) id
- * @throws Exception
- * @throws \InvalidArgumentException if entity has no id
- */
- public function insertOrUpdate(Entity $entity): FileMetadata {
- try {
- $existingEntity = $this->findForGroupForFile($entity->getId(), $entity->getGroupName());
- } catch (\Throwable) {
- $existingEntity = null;
- }
-
- if ($existingEntity !== null) {
- if ($entity->getValue() !== $existingEntity->getValue()) {
- return $this->update($entity);
- } else {
- return $existingEntity;
- }
- } else {
- return parent::insertOrUpdate($entity);
- }
- }
-}
diff --git a/lib/private/Metadata/IMetadataManager.php b/lib/private/Metadata/IMetadataManager.php
deleted file mode 100644
index fa0bcc22801..00000000000
--- a/lib/private/Metadata/IMetadataManager.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace OC\Metadata;
-
-use OCP\Files\File;
-
-/**
- * Interface to manage additional metadata for files
- */
-interface IMetadataManager {
- /**
- * @param class-string<IMetadataProvider> $className
- */
- public function registerProvider(string $className): void;
-
- /**
- * Generate the metadata for one file
- */
- public function generateMetadata(File $file, bool $checkExisting = false): void;
-
- /**
- * Clear the metadata for one file
- */
- public function clearMetadata(int $fileId): void;
-
- /** @return array<int, FileMetadata> */
- public function fetchMetadataFor(string $group, array $fileIds): array;
-
- /**
- * Get the capabilities as an array of mimetype regex to the type provided
- */
- public function getCapabilities(): array;
-}
diff --git a/lib/private/Metadata/IMetadataProvider.php b/lib/private/Metadata/IMetadataProvider.php
deleted file mode 100644
index 7cbe102a538..00000000000
--- a/lib/private/Metadata/IMetadataProvider.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-namespace OC\Metadata;
-
-use OCP\Files\File;
-
-/**
- * Interface for the metadata providers. If you want an application to provide
- * some metadata, you can use this to store them.
- */
-interface IMetadataProvider {
- /**
- * The list of groups that this metadata provider is able to provide.
- *
- * @return string[]
- */
- public static function groupsProvided(): array;
-
- /**
- * Check if the metadata provider is available. A metadata provider might be
- * unavailable due to a php extension not being installed.
- */
- public static function isAvailable(): bool;
-
- /**
- * Get the mimetypes supported as a regex.
- */
- public static function getMimetypesSupported(): string;
-
- /**
- * Execute the extraction on the specified file. The metadata should be
- * grouped by metadata
- *
- * Each group should be json serializable and the string representation
- * shouldn't be longer than 4000 characters.
- *
- * @param File $file The file to extract the metadata from
- * @param array<string, FileMetadata> An array containing all the metadata fetched.
- */
- public function execute(File $file): array;
-}
diff --git a/lib/private/Metadata/MetadataManager.php b/lib/private/Metadata/MetadataManager.php
deleted file mode 100644
index 6d96ff1ab68..00000000000
--- a/lib/private/Metadata/MetadataManager.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @license AGPL-3.0-or-later
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-namespace OC\Metadata;
-
-use OC\Metadata\Provider\ExifProvider;
-use OCP\Files\File;
-
-class MetadataManager implements IMetadataManager {
- /** @var array<string, IMetadataProvider> */
- private array $providers;
- private array $providerClasses;
- private FileMetadataMapper $fileMetadataMapper;
-
- public function __construct(
- FileMetadataMapper $fileMetadataMapper
- ) {
- $this->providers = [];
- $this->providerClasses = [];
- $this->fileMetadataMapper = $fileMetadataMapper;
-
- // TODO move to another place, where?
- $this->registerProvider(ExifProvider::class);
- }
-
- /**
- * @param class-string<IMetadataProvider> $className
- */
- public function registerProvider(string $className):void {
- if (in_array($className, $this->providerClasses)) {
- return;
- }
-
- if (call_user_func([$className, 'isAvailable'])) {
- $this->providers[call_user_func([$className, 'getMimetypesSupported'])] = \OC::$server->get($className);
- }
- }
-
- public function generateMetadata(File $file, bool $checkExisting = false): void {
- $existingMetadataGroups = [];
-
- if ($checkExisting) {
- $existingMetadata = $this->fileMetadataMapper->findForFile($file->getId());
- foreach ($existingMetadata as $metadata) {
- $existingMetadataGroups[] = $metadata->getGroupName();
- }
- }
-
- foreach ($this->providers as $supportedMimetype => $provider) {
- if (preg_match($supportedMimetype, $file->getMimeType())) {
- if (count(array_diff($provider::groupsProvided(), $existingMetadataGroups)) > 0) {
- $metaDataGroup = $provider->execute($file);
- foreach ($metaDataGroup as $group => $metadata) {
- $this->fileMetadataMapper->insertOrUpdate($metadata);
- }
- }
- }
- }
- }
-
- public function clearMetadata(int $fileId): void {
- $this->fileMetadataMapper->clear($fileId);
- }
-
- /**
- * @return array<int, FileMetadata>
- */
- public function fetchMetadataFor(string $group, array $fileIds): array {
- return $this->fileMetadataMapper->findForGroupForFiles($fileIds, $group);
- }
-
- public function getCapabilities(): array {
- $capabilities = [];
- foreach ($this->providers as $supportedMimetype => $provider) {
- foreach ($provider::groupsProvided() as $group) {
- if (isset($capabilities[$group])) {
- $capabilities[$group][] = $supportedMimetype;
- }
- $capabilities[$group] = [$supportedMimetype];
- }
- }
- return $capabilities;
- }
-}
diff --git a/lib/private/Metadata/Provider/ExifProvider.php b/lib/private/Metadata/Provider/ExifProvider.php
deleted file mode 100644
index b1598abbbc8..00000000000
--- a/lib/private/Metadata/Provider/ExifProvider.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-
-declare(strict_types=1);
-/**
- * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu>
- * @copyright Copyright 2022 Louis Chmn <louis@chmn.me>
- * @license AGPL-3.0-or-later
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-namespace OC\Metadata\Provider;
-
-use OC\Metadata\FileMetadata;
-use OC\Metadata\IMetadataProvider;
-use OCP\Files\File;
-use Psr\Log\LoggerInterface;
-
-class ExifProvider implements IMetadataProvider {
- private LoggerInterface $logger;
-
- public function __construct(
- LoggerInterface $logger
- ) {
- $this->logger = $logger;
- }
-
- public static function groupsProvided(): array {
- return ['size', 'gps'];
- }
-
- public static function isAvailable(): bool {
- return extension_loaded('exif');
- }
-
- /** @return array{'gps'?: FileMetadata, 'size'?: FileMetadata} */
- public function execute(File $file): array {
- $exifData = [];
- $fileDescriptor = $file->fopen('rb');
-
- if ($fileDescriptor === false) {
- return [];
- }
-
- $data = null;
- try {
- // Needed to make reading exif data reliable.
- // This is to trigger this condition: https://github.com/php/php-src/blob/d64aa6f646a7b5e58359dc79479860164239580a/main/streams/streams.c#L710
- // But I don't understand why 1 as a special meaning.
- // Revert right after reading the exif data.
- $oldBufferSize = stream_set_chunk_size($fileDescriptor, 1);
- $data = @exif_read_data($fileDescriptor, 'ANY_TAG', true);
- stream_set_chunk_size($fileDescriptor, $oldBufferSize);
- } catch (\Exception $ex) {
- $this->logger->info("Couldn't extract metadata for ".$file->getId(), ['exception' => $ex]);
- }
-
- $size = new FileMetadata();
- $size->setGroupName('size');
- $size->setId($file->getId());
- $size->setArrayAsValue([]);
-
- if (!$data) {
- $sizeResult = getimagesizefromstring($file->getContent());
- if ($sizeResult !== false) {
- $size->setArrayAsValue([
- 'width' => $sizeResult[0],
- 'height' => $sizeResult[1],
- ]);
-
- $exifData['size'] = $size;
- }
- } elseif (array_key_exists('COMPUTED', $data)) {
- if (array_key_exists('Width', $data['COMPUTED']) && array_key_exists('Height', $data['COMPUTED'])) {
- $size->setArrayAsValue([
- 'width' => $data['COMPUTED']['Width'],
- 'height' => $data['COMPUTED']['Height'],
- ]);
-
- $exifData['size'] = $size;
- }
- }
-
- if ($data && array_key_exists('GPS', $data)
- && array_key_exists('GPSLatitude', $data['GPS']) && array_key_exists('GPSLatitudeRef', $data['GPS'])
- && array_key_exists('GPSLongitude', $data['GPS']) && array_key_exists('GPSLongitudeRef', $data['GPS'])
- ) {
- $gps = new FileMetadata();
- $gps->setGroupName('gps');
- $gps->setId($file->getId());
- $gps->setArrayAsValue([
- 'latitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLatitude'], $data['GPS']['GPSLatitudeRef']),
- 'longitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLongitude'], $data['GPS']['GPSLongitudeRef']),
- ]);
-
- $exifData['gps'] = $gps;
- }
-
- return $exifData;
- }
-
- public static function getMimetypesSupported(): string {
- return '/image\/(png|jpeg|heif|webp|tiff)/';
- }
-
- /**
- * @param array|string $coordinates
- */
- private static function gpsDegreesToDecimal($coordinates, ?string $hemisphere): float {
- if (is_string($coordinates)) {
- $coordinates = array_map("trim", explode(",", $coordinates));
- }
-
- if (count($coordinates) !== 3) {
- throw new \Exception('Invalid coordinate format: ' . json_encode($coordinates));
- }
-
- [$degrees, $minutes, $seconds] = array_map(function (string $rawDegree) {
- $parts = explode('/', $rawDegree);
-
- if ($parts[1] === '0') {
- return 0;
- }
-
- return floatval($parts[0]) / floatval($parts[1] ?? 1);
- }, $coordinates);
-
- $sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1;
- return $sign * ($degrees + $minutes / 60 + $seconds / 3600);
- }
-}
diff --git a/lib/private/Migration/BackgroundRepair.php b/lib/private/Migration/BackgroundRepair.php
index 579ba494e58..dd1b15c7492 100644
--- a/lib/private/Migration/BackgroundRepair.php
+++ b/lib/private/Migration/BackgroundRepair.php
@@ -26,13 +26,13 @@
*/
namespace OC\Migration;
+use OC\NeedsUpdateException;
+use OC\Repair;
+use OC_App;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\TimedJob;
use OCP\EventDispatcher\IEventDispatcher;
-use OC\NeedsUpdateException;
-use OC\Repair;
-use OC_App;
use Psr\Log\LoggerInterface;
/**
@@ -41,15 +41,13 @@ use Psr\Log\LoggerInterface;
* @package OC\Migration
*/
class BackgroundRepair extends TimedJob {
- private IJobList $jobList;
- private LoggerInterface $logger;
- private IEventDispatcher $dispatcher;
-
- public function __construct(IEventDispatcher $dispatcher, ITimeFactory $time, LoggerInterface $logger, IJobList $jobList) {
+ public function __construct(
+ private IEventDispatcher $dispatcher,
+ ITimeFactory $time,
+ private LoggerInterface $logger,
+ private IJobList $jobList,
+ ) {
parent::__construct($time);
- $this->dispatcher = $dispatcher;
- $this->logger = $logger;
- $this->jobList = $jobList;
$this->setInterval(15 * 60);
}
@@ -58,7 +56,7 @@ class BackgroundRepair extends TimedJob {
* @throws \Exception
* @throws \OC\NeedsUpdateException
*/
- protected function run($argument) {
+ protected function run($argument): void {
if (!isset($argument['app']) || !isset($argument['step'])) {
// remove the job - we can never execute it
$this->jobList->remove($this, $this->argument);
@@ -101,7 +99,7 @@ class BackgroundRepair extends TimedJob {
* @param $app
* @throws NeedsUpdateException
*/
- protected function loadApp($app) {
+ protected function loadApp($app): void {
OC_App::loadApp($app);
}
}
diff --git a/lib/private/Migration/ConsoleOutput.php b/lib/private/Migration/ConsoleOutput.php
index 9e3396f2a75..841e3d302fc 100644
--- a/lib/private/Migration/ConsoleOutput.php
+++ b/lib/private/Migration/ConsoleOutput.php
@@ -34,34 +34,35 @@ use Symfony\Component\Console\Output\OutputInterface;
* @package OC\Migration
*/
class ConsoleOutput implements IOutput {
- /** @var OutputInterface */
- private $output;
+ private ?ProgressBar $progressBar = null;
- /** @var ProgressBar */
- private $progressBar;
+ public function __construct(
+ private OutputInterface $output,
+ ) {
+ }
- public function __construct(OutputInterface $output) {
- $this->output = $output;
+ public function debug(string $message): void {
+ $this->output->writeln($message, OutputInterface::VERBOSITY_VERBOSE);
}
/**
* @param string $message
*/
- public function info($message) {
+ public function info($message): void {
$this->output->writeln("<info>$message</info>");
}
/**
* @param string $message
*/
- public function warning($message) {
+ public function warning($message): void {
$this->output->writeln("<comment>$message</comment>");
}
/**
* @param int $max
*/
- public function startProgress($max = 0) {
+ public function startProgress($max = 0): void {
if (!is_null($this->progressBar)) {
$this->progressBar->finish();
}
@@ -73,7 +74,7 @@ class ConsoleOutput implements IOutput {
* @param int $step
* @param string $description
*/
- public function advance($step = 1, $description = '') {
+ public function advance($step = 1, $description = ''): void {
if (is_null($this->progressBar)) {
$this->progressBar = new ProgressBar($this->output);
$this->progressBar->start();
@@ -84,7 +85,7 @@ class ConsoleOutput implements IOutput {
}
}
- public function finishProgress() {
+ public function finishProgress(): void {
if (is_null($this->progressBar)) {
return;
}
diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php
index f97bcb767f8..f1b06d008bb 100644
--- a/lib/private/Migration/SimpleOutput.php
+++ b/lib/private/Migration/SimpleOutput.php
@@ -33,19 +33,21 @@ use Psr\Log\LoggerInterface;
* @package OC\Migration
*/
class SimpleOutput implements IOutput {
- private LoggerInterface $logger;
- private $appName;
+ public function __construct(
+ private LoggerInterface $logger,
+ private $appName,
+ ) {
+ }
- public function __construct(LoggerInterface $logger, $appName) {
- $this->logger = $logger;
- $this->appName = $appName;
+ public function debug(string $message): void {
+ $this->logger->debug($message, ['app' => $this->appName]);
}
/**
* @param string $message
* @since 9.1.0
*/
- public function info($message) {
+ public function info($message): void {
$this->logger->info($message, ['app' => $this->appName]);
}
@@ -53,7 +55,7 @@ class SimpleOutput implements IOutput {
* @param string $message
* @since 9.1.0
*/
- public function warning($message) {
+ public function warning($message): void {
$this->logger->warning($message, ['app' => $this->appName]);
}
@@ -61,7 +63,7 @@ class SimpleOutput implements IOutput {
* @param int $max
* @since 9.1.0
*/
- public function startProgress($max = 0) {
+ public function startProgress($max = 0): void {
}
/**
@@ -69,12 +71,12 @@ class SimpleOutput implements IOutput {
* @param string $description
* @since 9.1.0
*/
- public function advance($step = 1, $description = '') {
+ public function advance($step = 1, $description = ''): void {
}
/**
* @since 9.1.0
*/
- public function finishProgress() {
+ public function finishProgress(): void {
}
}
diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php
index a651cde379d..17573d9db86 100644
--- a/lib/private/NavigationManager.php
+++ b/lib/private/NavigationManager.php
@@ -65,19 +65,25 @@ class NavigationManager implements INavigationManager {
private $groupManager;
/** @var IConfig */
private $config;
+ /** The default app for the current user (cached for the `add` function) */
+ private ?string $defaultApp;
+ /** User defined app order (cached for the `add` function) */
+ private array $customAppOrder;
public function __construct(IAppManager $appManager,
- IURLGenerator $urlGenerator,
- IFactory $l10nFac,
- IUserSession $userSession,
- IGroupManager $groupManager,
- IConfig $config) {
+ IURLGenerator $urlGenerator,
+ IFactory $l10nFac,
+ IUserSession $userSession,
+ IGroupManager $groupManager,
+ IConfig $config) {
$this->appManager = $appManager;
$this->urlGenerator = $urlGenerator;
$this->l10nFac = $l10nFac;
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->config = $config;
+
+ $this->defaultApp = null;
}
/**
@@ -89,7 +95,10 @@ class NavigationManager implements INavigationManager {
return;
}
+ $id = $entry['id'];
+
$entry['active'] = false;
+ $entry['unread'] = $this->unreadCounters[$id] ?? 0;
if (!isset($entry['icon'])) {
$entry['icon'] = '';
}
@@ -100,8 +109,17 @@ class NavigationManager implements INavigationManager {
$entry['type'] = 'link';
}
- $id = $entry['id'];
- $entry['unread'] = $this->unreadCounters[$id] ?? 0;
+ if ($entry['type'] === 'link') {
+ // app might not be set when using closures, in this case try to fallback to ID
+ if (!isset($entry['app']) && $this->appManager->isEnabledForUser($id)) {
+ $entry['app'] = $id;
+ }
+
+ // This is the default app that will always be shown first
+ $entry['default'] = ($entry['app'] ?? false) === $this->defaultApp;
+ // Set order from user defined app order
+ $entry['order'] = $this->customAppOrder[$id]['order'] ?? $entry['order'] ?? 100;
+ }
$this->entries[$id] = $entry;
}
@@ -123,26 +141,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) {
@@ -200,7 +236,25 @@ class NavigationManager implements INavigationManager {
]);
}
+ if ($this->appManager === 'null') {
+ return;
+ }
+
+ $this->defaultApp = $this->appManager->getDefaultAppForUser($this->userSession->getUser(), false);
+
if ($this->userSession->isLoggedIn()) {
+ // Profile
+ $this->add([
+ 'type' => 'settings',
+ 'id' => 'profile',
+ 'order' => 1,
+ 'href' => $this->urlGenerator->linkToRoute(
+ 'core.ProfilePage.index',
+ ['targetUserId' => $this->userSession->getUser()->getUID()],
+ ),
+ 'name' => $l->t('View profile'),
+ ]);
+
// Accessibility settings
if ($this->appManager->isEnabledForUser('theming', $this->userSession->getUser())) {
$this->add([
@@ -212,6 +266,7 @@ class NavigationManager implements INavigationManager {
'icon' => $this->urlGenerator->imagePath('theming', 'accessibility-dark.svg'),
]);
}
+
if ($this->isAdmin()) {
// App management
$this->add([
@@ -280,20 +335,15 @@ class NavigationManager implements INavigationManager {
}
}
- if ($this->appManager === 'null') {
- return;
- }
-
if ($this->userSession->isLoggedIn()) {
$user = $this->userSession->getUser();
$apps = $this->appManager->getEnabledAppsForUser($user);
- $customOrders = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
+ $this->customAppOrder = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
} else {
$apps = $this->appManager->getInstalledApps();
- $customOrders = [];
+ $this->customAppOrder = [];
}
-
foreach ($apps as $app) {
if (!$this->userSession->isLoggedIn() && !$this->appManager->isEnabledForUser($app, $this->userSession->getUser())) {
continue;
@@ -319,7 +369,7 @@ class NavigationManager implements INavigationManager {
}
$l = $this->l10nFac->get($app);
$id = $nav['id'] ?? $app . ($key === 0 ? '' : $key);
- $order = $customOrders[$app][$key] ?? $nav['order'] ?? 100;
+ $order = $nav['order'] ?? 100;
$type = $nav['type'];
$route = !empty($nav['route']) ? $this->urlGenerator->linkToRoute($nav['route']) : '';
$icon = $nav['icon'] ?? 'app.svg';
@@ -335,14 +385,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/Notification/Action.php b/lib/private/Notification/Action.php
index ff9cf9e38f5..9590d28af4a 100644
--- a/lib/private/Notification/Action.php
+++ b/lib/private/Notification/Action.php
@@ -27,23 +27,17 @@ namespace OC\Notification;
use OCP\Notification\IAction;
class Action implements IAction {
- /** @var string */
- protected $label;
+ protected string $label;
- /** @var string */
- protected $labelParsed;
+ protected string $labelParsed;
- /** @var string */
- protected $link;
+ protected string $link;
- /** @var string */
- protected $requestType;
+ protected string $requestType;
- /** @var string */
- protected $icon;
+ protected string $icon;
- /** @var bool */
- protected $primary;
+ protected bool $primary;
public function __construct() {
$this->label = '';
diff --git a/lib/private/Notification/Manager.php b/lib/private/Notification/Manager.php
index 3d77f643d93..c712d2754e2 100644
--- a/lib/private/Notification/Manager.php
+++ b/lib/private/Notification/Manager.php
@@ -43,48 +43,35 @@ use Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface;
class Manager implements IManager {
- /** @var IValidator */
- protected $validator;
- /** @var IUserManager */
- private $userManager;
/** @var ICache */
- protected $cache;
- /** @var IRegistry */
- protected $subscription;
- /** @var LoggerInterface */
- protected $logger;
- /** @var Coordinator */
- private $coordinator;
+ protected ICache $cache;
/** @var IApp[] */
- protected $apps;
+ protected array $apps;
/** @var string[] */
- protected $appClasses;
+ protected array $appClasses;
/** @var INotifier[] */
- protected $notifiers;
+ protected array $notifiers;
/** @var string[] */
- protected $notifierClasses;
+ protected array $notifierClasses;
/** @var bool */
- protected $preparingPushNotification;
+ protected bool $preparingPushNotification;
/** @var bool */
- protected $deferPushing;
+ protected bool $deferPushing;
/** @var bool */
- private $parsedRegistrationContext;
-
- public function __construct(IValidator $validator,
- IUserManager $userManager,
- ICacheFactory $cacheFactory,
- IRegistry $subscription,
- LoggerInterface $logger,
- Coordinator $coordinator) {
- $this->validator = $validator;
- $this->userManager = $userManager;
+ private bool $parsedRegistrationContext;
+
+ public function __construct(
+ protected IValidator $validator,
+ private IUserManager $userManager,
+ ICacheFactory $cacheFactory,
+ protected IRegistry $subscription,
+ protected LoggerInterface $logger,
+ private Coordinator $coordinator,
+ ) {
$this->cache = $cacheFactory->createDistributed('notifications');
- $this->subscription = $subscription;
- $this->logger = $logger;
- $this->coordinator = $coordinator;
$this->apps = [];
$this->notifiers = [];
@@ -111,7 +98,7 @@ class Manager implements IManager {
* @deprecated 17.0.0 use registerNotifierService instead.
* @since 8.2.0 - Parameter $info was added in 9.0.0
*/
- public function registerNotifier(\Closure $service, \Closure $info) {
+ public function registerNotifier(\Closure $service, \Closure $info): void {
$infoData = $info();
$exception = new \InvalidArgumentException(
'Notifier ' . $infoData['name'] . ' (id: ' . $infoData['id'] . ') is not considered because it is using the old way to register.'
diff --git a/lib/private/Notification/Notification.php b/lib/private/Notification/Notification.php
index 2291c4ae34f..ed2a84b0de2 100644
--- a/lib/private/Notification/Notification.php
+++ b/lib/private/Notification/Notification.php
@@ -32,94 +32,33 @@ use OCP\RichObjectStrings\InvalidObjectExeption;
use OCP\RichObjectStrings\IValidator;
class Notification implements INotification {
- /** @var IValidator */
- protected $richValidator;
-
- /** @var string */
- protected $app;
-
- /** @var string */
- protected $user;
-
- /** @var \DateTime */
- protected $dateTime;
-
- /** @var string */
- protected $objectType;
-
- /** @var string */
- protected $objectId;
-
- /** @var string */
- protected $subject;
-
- /** @var array */
- protected $subjectParameters;
-
- /** @var string */
- protected $subjectParsed;
-
- /** @var string */
- protected $subjectRich;
-
- /** @var array */
- protected $subjectRichParameters;
-
- /** @var string */
- protected $message;
-
- /** @var array */
- protected $messageParameters;
-
- /** @var string */
- protected $messageParsed;
-
- /** @var string */
- protected $messageRich;
-
- /** @var array */
- protected $messageRichParameters;
-
- /** @var string */
- protected $link;
-
- /** @var string */
- protected $icon;
-
- /** @var array */
- protected $actions;
-
- /** @var array */
- protected $actionsParsed;
-
- /** @var bool */
- protected $hasPrimaryAction;
-
- /** @var bool */
- protected $hasPrimaryParsedAction;
-
- public function __construct(IValidator $richValidator) {
- $this->richValidator = $richValidator;
- $this->app = '';
- $this->user = '';
+ protected string $app = '';
+ protected string $user = '';
+ protected \DateTime $dateTime;
+ protected string $objectType = '';
+ protected string $objectId = '';
+ protected string $subject = '';
+ protected array $subjectParameters = [];
+ protected string $subjectParsed = '';
+ protected string $subjectRich = '';
+ protected array $subjectRichParameters = [];
+ protected string $message = '';
+ protected array $messageParameters = [];
+ protected string $messageParsed = '';
+ protected string $messageRich = '';
+ protected array $messageRichParameters = [];
+ protected string $link = '';
+ protected string $icon = '';
+ protected array $actions = [];
+ protected array $actionsParsed = [];
+ protected bool $hasPrimaryAction = false;
+ protected bool $hasPrimaryParsedAction = false;
+
+ public function __construct(
+ protected IValidator $richValidator,
+ ) {
$this->dateTime = new \DateTime();
$this->dateTime->setTimestamp(0);
- $this->objectType = '';
- $this->objectId = '';
- $this->subject = '';
- $this->subjectParameters = [];
- $this->subjectParsed = '';
- $this->subjectRich = '';
- $this->subjectRichParameters = [];
- $this->message = '';
- $this->messageParameters = [];
- $this->messageParsed = '';
- $this->messageRich = '';
- $this->messageRichParameters = [];
- $this->link = '';
- $this->icon = '';
- $this->actions = [];
- $this->actionsParsed = [];
}
/**
diff --git a/lib/private/OCS/DiscoveryService.php b/lib/private/OCS/DiscoveryService.php
index 8f98ff7d5ae..f53d39465e8 100644
--- a/lib/private/OCS/DiscoveryService.php
+++ b/lib/private/OCS/DiscoveryService.php
@@ -47,7 +47,7 @@ class DiscoveryService implements IDiscoveryService {
* @param IClientService $clientService
*/
public function __construct(ICacheFactory $cacheFactory,
- IClientService $clientService
+ IClientService $clientService
) {
$this->cache = $cacheFactory->createDistributed('ocs-discovery');
$this->client = $clientService->newClient();
diff --git a/lib/private/OCS/Provider.php b/lib/private/OCS/Provider.php
index 5e7a86a1341..83219c018f8 100644
--- a/lib/private/OCS/Provider.php
+++ b/lib/private/OCS/Provider.php
@@ -34,8 +34,8 @@ class Provider extends \OCP\AppFramework\Controller {
* @param \OCP\App\IAppManager $appManager
*/
public function __construct($appName,
- \OCP\IRequest $request,
- \OCP\App\IAppManager $appManager) {
+ \OCP\IRequest $request,
+ \OCP\App\IAppManager $appManager) {
parent::__construct($appName, $request);
$this->appManager = $appManager;
}
diff --git a/lib/private/Preview/BackgroundCleanupJob.php b/lib/private/Preview/BackgroundCleanupJob.php
index 4eba96d1a82..4376cc06eca 100644
--- a/lib/private/Preview/BackgroundCleanupJob.php
+++ b/lib/private/Preview/BackgroundCleanupJob.php
@@ -48,10 +48,10 @@ class BackgroundCleanupJob extends TimedJob {
private $mimeTypeLoader;
public function __construct(ITimeFactory $timeFactory,
- IDBConnection $connection,
- Root $previewFolder,
- IMimeTypeLoader $mimeTypeLoader,
- bool $isCLI) {
+ IDBConnection $connection,
+ Root $previewFolder,
+ IMimeTypeLoader $mimeTypeLoader,
+ bool $isCLI) {
parent::__construct($timeFactory);
// Run at most once an hour
$this->setInterval(3600);
diff --git a/lib/private/Preview/EMF.php b/lib/private/Preview/EMF.php
new file mode 100644
index 00000000000..2b5f40e66af
--- /dev/null
+++ b/lib/private/Preview/EMF.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Preview;
+
+class EMF extends Office {
+ public function getMimeType(): string {
+ return '/image\/emf/';
+ }
+}
diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php
index 4a1270fa4a6..695d4a3357f 100644
--- a/lib/private/Preview/Generator.php
+++ b/lib/private/Preview/Generator.php
@@ -221,7 +221,7 @@ class Generator {
*
* @param int $semId
* @param int $concurrency
- * @return false|resource the semaphore on success or false on failure
+ * @return false|\SysvSemaphore the semaphore on success or false on failure
*/
public static function guardWithSemaphore(int $semId, int $concurrency) {
if (!extension_loaded('sysvsem')) {
@@ -240,11 +240,11 @@ class Generator {
/**
* Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
*
- * @param resource|bool $semId the semaphore identifier returned by guardWithSemaphore
+ * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
* @return bool
*/
- public static function unguardWithSemaphore($semId): bool {
- if (!is_resource($semId) || !extension_loaded('sysvsem')) {
+ public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
+ if ($semId === false || !($semId instanceof \SysvSemaphore)) {
return false;
}
return sem_release($semId);
@@ -257,9 +257,15 @@ class Generator {
*/
public static function getHardwareConcurrency(): int {
static $width;
+
if (!isset($width)) {
- if (is_file("/proc/cpuinfo")) {
- $width = substr_count(file_get_contents("/proc/cpuinfo"), "processor");
+ if (function_exists('ini_get')) {
+ $openBasedir = ini_get('open_basedir');
+ if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
+ $width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
+ } else {
+ $width = 0;
+ }
} else {
$width = 0;
}
diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php
index 7184f7e9e76..faf84696e17 100644
--- a/lib/private/Preview/Imaginary.php
+++ b/lib/private/Preview/Imaginary.php
@@ -23,13 +23,13 @@
namespace OC\Preview;
+use OC\StreamImage;
use OCP\Files\File;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IImage;
-use OCP\Image;
-use OC\StreamImage;
+use OCP\Image;
use Psr\Log\LoggerInterface;
class Imaginary extends ProviderV2 {
@@ -78,6 +78,9 @@ class Imaginary extends ProviderV2 {
// Object store
$stream = $file->fopen('r');
+ if (!$stream || !is_resource($stream) || feof($stream)) {
+ return null;
+ }
$httpClient = $this->service->newClient();
@@ -165,7 +168,7 @@ class Imaginary extends ProviderV2 {
'timeout' => 120,
'connect_timeout' => 3,
]);
- } catch (\Exception $e) {
+ } catch (\Throwable $e) {
$this->logger->info('Imaginary preview generation failed: ' . $e->getMessage(), [
'exception' => $e,
]);
diff --git a/lib/private/Preview/Office.php b/lib/private/Preview/Office.php
index 3ba7c5a21a0..68499a6fea6 100644
--- a/lib/private/Preview/Office.php
+++ b/lib/private/Preview/Office.php
@@ -31,7 +31,8 @@ namespace OC\Preview;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\IImage;
-use Psr\Log\LoggerInterface;
+use OCP\ITempManager;
+use OCP\Server;
abstract class Office extends ProviderV2 {
/**
@@ -49,51 +50,60 @@ abstract class Office extends ProviderV2 {
return null;
}
- $absPath = $this->getLocalFile($file);
-
- $tmpDir = \OC::$server->getTempManager()->getTempBaseDir();
+ $tempManager = Server::get(ITempManager::class);
- $defaultParameters = ' -env:UserInstallation=file://' . escapeshellarg($tmpDir . '/owncloud-' . \OC_Util::getInstanceId() . '/') . ' --headless --nologo --nofirststartwizard --invisible --norestore --convert-to png --outdir ';
- $clParameters = \OC::$server->getConfig()->getSystemValue('preview_office_cl_parameters', $defaultParameters);
+ // The file to generate the preview for.
+ $absPath = $this->getLocalFile($file);
- $cmd = $this->options['officeBinary'] . $clParameters . escapeshellarg($tmpDir) . ' ' . escapeshellarg($absPath);
+ // The destination for the LibreOffice user profile.
+ // LibreOffice can rune once per user profile and therefore instance id and file id are included.
+ $profile = $tempManager->getTemporaryFolder(
+ 'nextcloud-office-profile-' . \OC_Util::getInstanceId() . '-' . $file->getId()
+ );
- exec($cmd, $output, $returnCode);
+ // The destination for the LibreOffice convert result.
+ $outdir = $tempManager->getTemporaryFolder(
+ 'nextcloud-office-preview-' . \OC_Util::getInstanceId() . '-' . $file->getId()
+ );
- if ($returnCode !== 0) {
+ if ($profile === false || $outdir === false) {
$this->cleanTmpFiles();
return null;
}
- //create imagick object from png
- $pngPreview = null;
- try {
- [$dirname, , , $filename] = array_values(pathinfo($absPath));
- $pngPreview = $tmpDir . '/' . $filename . '.png';
+ $parameters = [
+ $this->options['officeBinary'],
+ '-env:UserInstallation=file://' . escapeshellarg($profile),
+ '--headless',
+ '--nologo',
+ '--nofirststartwizard',
+ '--invisible',
+ '--norestore',
+ '--convert-to png',
+ '--outdir ' . escapeshellarg($outdir),
+ escapeshellarg($absPath),
+ ];
- $png = new \Imagick($pngPreview . '[0]');
- $png->setImageFormat('jpg');
- } catch (\Exception $e) {
+ $cmd = implode(' ', $parameters);
+ exec($cmd, $output, $returnCode);
+
+ if ($returnCode !== 0) {
$this->cleanTmpFiles();
- unlink($pngPreview);
- \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [
- 'exception' => $e,
- 'app' => 'core',
- ]);
return null;
}
+ $preview = $outdir . pathinfo($absPath, PATHINFO_FILENAME) . '.png';
+
$image = new \OCP\Image();
- $image->loadFromData((string) $png);
+ $image->loadFromFile($preview);
$this->cleanTmpFiles();
- unlink($pngPreview);
if ($image->valid()) {
$image->scaleDownToFit($maxX, $maxY);
-
return $image;
}
+
return null;
}
}
diff --git a/lib/private/Preview/Watcher.php b/lib/private/Preview/Watcher.php
index 7f4593f9fe3..ad6fed56020 100644
--- a/lib/private/Preview/Watcher.php
+++ b/lib/private/Preview/Watcher.php
@@ -61,6 +61,9 @@ class Watcher {
}
try {
+ if (is_null($node->getId())) {
+ return;
+ }
$folder = $this->appData->getFolder((string)$node->getId());
$folder->delete();
} catch (NotFoundException $e) {
diff --git a/lib/private/Preview/WatcherConnector.php b/lib/private/Preview/WatcherConnector.php
index ffbdf825211..b11a6ab86da 100644
--- a/lib/private/Preview/WatcherConnector.php
+++ b/lib/private/Preview/WatcherConnector.php
@@ -43,7 +43,7 @@ class WatcherConnector {
* @param SystemConfig $config
*/
public function __construct(IRootFolder $root,
- SystemConfig $config) {
+ SystemConfig $config) {
$this->root = $root;
$this->config = $config;
}
diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php
index 3af6848686e..aedcbbce335 100644
--- a/lib/private/PreviewManager.php
+++ b/lib/private/PreviewManager.php
@@ -366,7 +366,7 @@ class PreviewManager implements IPreview {
$this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
$this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes());
- // SVG, Office and Bitmap require imagick
+ // SVG and Bitmap require imagick
if ($this->imagickSupport->hasExtension()) {
$imagickProviders = [
'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class],
@@ -391,27 +391,10 @@ class PreviewManager implements IPreview {
$this->registerCoreProvider($class, $provider['mimetype']);
}
}
-
- if ($this->imagickSupport->supportsFormat('PDF')) {
- // Office requires openoffice or libreoffice
- $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', null);
- if (!is_string($officeBinary)) {
- $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
- }
- if (!is_string($officeBinary)) {
- $officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
- }
-
- if (is_string($officeBinary)) {
- $this->registerCoreProvider(Preview\MSOfficeDoc::class, '/application\/msword/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\MSOffice2003::class, '/application\/vnd.ms-.*/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\MSOffice2007::class, '/application\/vnd.openxmlformats-officedocument.*/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/', ["officeBinary" => $officeBinary]);
- $this->registerCoreProvider(Preview\StarOffice::class, '/application\/vnd.sun.xml.*/', ["officeBinary" => $officeBinary]);
- }
- }
}
+ $this->registerCoreProvidersOffice();
+
// Video requires avconv or ffmpeg
if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) {
$movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null);
@@ -429,6 +412,43 @@ class PreviewManager implements IPreview {
}
}
+ private function registerCoreProvidersOffice(): void {
+ $officeProviders = [
+ ['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class],
+ ['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class],
+ ['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class],
+ ['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class],
+ ['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class],
+ ['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class],
+ ];
+
+ $findBinary = true;
+ $officeBinary = false;
+
+ foreach ($officeProviders as $provider) {
+ $class = $provider['class'];
+ if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
+ continue;
+ }
+
+ if ($findBinary) {
+ // Office requires openoffice or libreoffice
+ $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false);
+ if ($officeBinary === false) {
+ $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
+ }
+ if ($officeBinary === false) {
+ $officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
+ }
+ $findBinary = false;
+ }
+
+ if ($officeBinary) {
+ $this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]);
+ }
+ }
+ }
+
private function registerBootstrapProviders(): void {
$context = $this->bootstrapCoordinator->getRegistrationContext();
diff --git a/lib/private/Profile/Actions/FediverseAction.php b/lib/private/Profile/Actions/FediverseAction.php
index 30682ba6b2a..4c73f785dd0 100644
--- a/lib/private/Profile/Actions/FediverseAction.php
+++ b/lib/private/Profile/Actions/FediverseAction.php
@@ -26,12 +26,12 @@ declare(strict_types=1);
namespace OC\Profile\Actions;
-use function Safe\substr;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
+use function substr;
class FediverseAction implements ILinkAction {
private string $value = '';
diff --git a/lib/private/Profile/Actions/TwitterAction.php b/lib/private/Profile/Actions/TwitterAction.php
index 178bd04c786..f7f57d4c6d1 100644
--- a/lib/private/Profile/Actions/TwitterAction.php
+++ b/lib/private/Profile/Actions/TwitterAction.php
@@ -26,12 +26,12 @@ declare(strict_types=1);
namespace OC\Profile\Actions;
-use function Safe\substr;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
+use function substr;
class TwitterAction implements ILinkAction {
private string $value = '';
diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php
index 8fa65271205..c8fb780bbe8 100644
--- a/lib/private/Profile/ProfileManager.php
+++ b/lib/private/Profile/ProfileManager.php
@@ -26,30 +26,31 @@ declare(strict_types=1);
namespace OC\Profile;
-use function Safe\array_flip;
-use function Safe\usort;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Core\Db\ProfileConfig;
use OC\Core\Db\ProfileConfigMapper;
use OC\KnownUser\KnownUserService;
use OC\Profile\Actions\EmailAction;
+use OC\Profile\Actions\FediverseAction;
use OC\Profile\Actions\PhoneAction;
use OC\Profile\Actions\TwitterAction;
-use OC\Profile\Actions\FediverseAction;
use OC\Profile\Actions\WebsiteAction;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\PropertyDoesNotExistException;
use OCP\App\IAppManager;
use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\Cache\CappedMemoryCache;
use OCP\IConfig;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
-use OCP\Cache\CappedMemoryCache;
+use OCP\Profile\IProfileManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use function array_flip;
+use function usort;
-class ProfileManager {
+class ProfileManager implements IProfileManager {
/** @var ILinkAction[] */
private array $actions = [];
@@ -101,7 +102,7 @@ class ProfileManager {
/**
* 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) {
@@ -109,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,
@@ -193,18 +194,18 @@ 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
- if ($visibility === ProfileConfig::VISIBILITY_SHOW_USERS_ONLY) {
+ if ($visibility === self::VISIBILITY_SHOW_USERS_ONLY) {
if (empty($scope)) {
return $visitingUser !== null;
}
@@ -218,10 +219,10 @@ class ProfileManager {
};
}
- if ($visibility === ProfileConfig::VISIBILITY_SHOW) {
+ if ($visibility === self::VISIBILITY_SHOW) {
if (empty($scope)) {
return true;
- };
+ }
return match ($scope) {
IAccountManager::SCOPE_PRIVATE => $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID()),
@@ -238,8 +239,9 @@ class ProfileManager {
/**
* 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
@@ -257,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;
}
}
@@ -284,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);
}
),
)
@@ -316,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/Profiler/Profiler.php b/lib/private/Profiler/Profiler.php
index 40050b7bf43..d6749c55e3c 100644
--- a/lib/private/Profiler/Profiler.php
+++ b/lib/private/Profiler/Profiler.php
@@ -27,11 +27,11 @@ declare(strict_types = 1);
namespace OC\Profiler;
use OC\AppFramework\Http\Request;
+use OC\SystemConfig;
use OCP\AppFramework\Http\Response;
use OCP\DataCollector\IDataCollector;
-use OCP\Profiler\IProfiler;
use OCP\Profiler\IProfile;
-use OC\SystemConfig;
+use OCP\Profiler\IProfiler;
class Profiler implements IProfiler {
/** @var array<string, IDataCollector> */
@@ -95,7 +95,7 @@ class Profiler implements IProfiler {
* @return array[]
*/
public function find(?string $url, ?int $limit, ?string $method, ?int $start, ?int $end,
- string $statusCode = null): array {
+ string $statusCode = null): array {
if ($this->storage) {
return $this->storage->find($url, $limit, $method, $start, $end, $statusCode);
} else {
diff --git a/lib/private/Repair.php b/lib/private/Repair.php
index 5c68c106993..21caed3e39f 100644
--- a/lib/private/Repair.php
+++ b/lib/private/Repair.php
@@ -34,19 +34,14 @@
*/
namespace OC;
-use OC\Repair\AddRemoveOldTasksBackgroundJob;
-use OC\Repair\CleanUpAbandonedApps;
-use OCP\AppFramework\QueryException;
-use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\Collaboration\Resources\IManager;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\Migration\IOutput;
-use OCP\Migration\IRepairStep;
use OC\DB\Connection;
use OC\DB\ConnectionAdapter;
use OC\Repair\AddBruteForceCleanupJob;
use OC\Repair\AddCleanupUpdaterBackupsJob;
+use OC\Repair\AddMetadataGenerationJob;
+use OC\Repair\AddRemoveOldTasksBackgroundJob;
use OC\Repair\CleanTags;
+use OC\Repair\CleanUpAbandonedApps;
use OC\Repair\ClearFrontendCaches;
use OC\Repair\ClearGeneratedAvatarCache;
use OC\Repair\Collation;
@@ -85,6 +80,12 @@ use OC\Repair\RepairDavShares;
use OC\Repair\RepairInvalidShares;
use OC\Repair\RepairMimeTypes;
use OC\Template\JSCombiner;
+use OCP\AppFramework\QueryException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Collaboration\Resources\IManager;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
use Psr\Log\LoggerInterface;
use Throwable;
@@ -211,6 +212,7 @@ class Repair implements IOutput {
\OCP\Server::get(CleanUpAbandonedApps::class),
\OCP\Server::get(AddMissingSecretJob::class),
\OCP\Server::get(AddRemoveOldTasksBackgroundJob::class),
+ \OCP\Server::get(AddMetadataGenerationJob::class),
];
}
@@ -246,6 +248,9 @@ class Repair implements IOutput {
return $steps;
}
+ public function debug(string $message): void {
+ }
+
/**
* @param string $message
*/
diff --git a/lib/private/Repair/AddMetadataGenerationJob.php b/lib/private/Repair/AddMetadataGenerationJob.php
new file mode 100644
index 00000000000..72e5df03bbd
--- /dev/null
+++ b/lib/private/Repair/AddMetadataGenerationJob.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (c) 2023 Louis Chmn <louis@chmn.me>
+ *
+ * @author Louis Chmn <louis@chmn.me>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Repair;
+
+use OC\Core\BackgroundJobs\GenerateMetadataJob;
+use OCP\BackgroundJob\IJobList;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class AddMetadataGenerationJob implements IRepairStep {
+ public function __construct(
+ private IJobList $jobList,
+ ) {
+ }
+
+ public function getName() {
+ return 'Queue a job to generate metadata';
+ }
+
+ public function run(IOutput $output) {
+ $this->jobList->add(GenerateMetadataJob::class);
+ }
+}
diff --git a/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php b/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php
index 94ae39f2183..00badbb726d 100644
--- a/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php
+++ b/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php
@@ -25,7 +25,8 @@ declare(strict_types=1);
*/
namespace OC\Repair;
-use OC\TextProcessing\RemoveOldTasksBackgroundJob;
+use OC\TextProcessing\RemoveOldTasksBackgroundJob as RemoveOldTextProcessingTasksBackgroundJob;
+use OC\TextToImage\RemoveOldTasksBackgroundJob as RemoveOldTextToImageTasksBackgroundJob;
use OCP\BackgroundJob\IJobList;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
@@ -38,10 +39,11 @@ class AddRemoveOldTasksBackgroundJob implements IRepairStep {
}
public function getName(): string {
- return 'Add language model tasks cleanup job';
+ return 'Add AI tasks cleanup job';
}
public function run(IOutput $output) {
- $this->jobList->add(RemoveOldTasksBackgroundJob::class);
+ $this->jobList->add(RemoveOldTextProcessingTasksBackgroundJob::class);
+ $this->jobList->add(RemoveOldTextToImageTasksBackgroundJob::class);
}
}
diff --git a/lib/private/Repair/ClearFrontendCaches.php b/lib/private/Repair/ClearFrontendCaches.php
index bf94e5bfbff..3661560c5f6 100644
--- a/lib/private/Repair/ClearFrontendCaches.php
+++ b/lib/private/Repair/ClearFrontendCaches.php
@@ -37,7 +37,7 @@ class ClearFrontendCaches implements IRepairStep {
protected $jsCombiner;
public function __construct(ICacheFactory $cacheFactory,
- JSCombiner $JSCombiner) {
+ JSCombiner $JSCombiner) {
$this->cacheFactory = $cacheFactory;
$this->jsCombiner = $JSCombiner;
}
diff --git a/lib/private/Repair/ClearGeneratedAvatarCache.php b/lib/private/Repair/ClearGeneratedAvatarCache.php
index fb3b42847dc..88b2b07ead5 100644
--- a/lib/private/Repair/ClearGeneratedAvatarCache.php
+++ b/lib/private/Repair/ClearGeneratedAvatarCache.php
@@ -25,8 +25,8 @@
namespace OC\Repair;
use OC\Avatar\AvatarManager;
-use OCP\IConfig;
use OCP\BackgroundJob\IJobList;
+use OCP\IConfig;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
diff --git a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php
index e8513e7a933..5caa74638e5 100644
--- a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php
+++ b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php
@@ -20,9 +20,9 @@
*/
namespace OC\Repair;
-use OCP\BackgroundJob\QueuedJob;
-use OCP\AppFramework\Utility\ITimeFactory;
use OC\Avatar\AvatarManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
class ClearGeneratedAvatarCacheJob extends QueuedJob {
protected AvatarManager $avatarManager;
diff --git a/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php b/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php
index d5ae1d7ab63..185ff3be1be 100644
--- a/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php
+++ b/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php
@@ -37,7 +37,7 @@ class ResetGeneratedAvatarFlag implements IRepairStep {
private $connection;
public function __construct(IConfig $config,
- IDBConnection $connection) {
+ IDBConnection $connection) {
$this->config = $config;
$this->connection = $connection;
}
diff --git a/lib/private/Repair/NC20/EncryptionLegacyCipher.php b/lib/private/Repair/NC20/EncryptionLegacyCipher.php
index a7d008e87be..42a8778662b 100644
--- a/lib/private/Repair/NC20/EncryptionLegacyCipher.php
+++ b/lib/private/Repair/NC20/EncryptionLegacyCipher.php
@@ -38,7 +38,7 @@ class EncryptionLegacyCipher implements IRepairStep {
private $manager;
public function __construct(IConfig $config,
- IManager $manager) {
+ IManager $manager) {
$this->config = $config;
$this->manager = $manager;
}
diff --git a/lib/private/Repair/NC20/EncryptionMigration.php b/lib/private/Repair/NC20/EncryptionMigration.php
index 239a62c2718..dea51b1b57e 100644
--- a/lib/private/Repair/NC20/EncryptionMigration.php
+++ b/lib/private/Repair/NC20/EncryptionMigration.php
@@ -38,7 +38,7 @@ class EncryptionMigration implements IRepairStep {
private $manager;
public function __construct(IConfig $config,
- IManager $manager) {
+ IManager $manager) {
$this->config = $config;
$this->manager = $manager;
}
diff --git a/lib/private/Repair/NC21/ValidatePhoneNumber.php b/lib/private/Repair/NC21/ValidatePhoneNumber.php
index b3534dbeae8..51120c9d139 100644
--- a/lib/private/Repair/NC21/ValidatePhoneNumber.php
+++ b/lib/private/Repair/NC21/ValidatePhoneNumber.php
@@ -42,8 +42,8 @@ class ValidatePhoneNumber implements IRepairStep {
private $accountManager;
public function __construct(IUserManager $userManager,
- IAccountManager $accountManager,
- IConfig $config) {
+ IAccountManager $accountManager,
+ IConfig $config) {
$this->config = $config;
$this->userManager = $userManager;
$this->accountManager = $accountManager;
diff --git a/lib/private/Repair/Owncloud/CleanPreviews.php b/lib/private/Repair/Owncloud/CleanPreviews.php
index 853a94c8adc..2020ae8bfc1 100644
--- a/lib/private/Repair/Owncloud/CleanPreviews.php
+++ b/lib/private/Repair/Owncloud/CleanPreviews.php
@@ -47,8 +47,8 @@ class CleanPreviews implements IRepairStep {
* @param IConfig $config
*/
public function __construct(IJobList $jobList,
- IUserManager $userManager,
- IConfig $config) {
+ IUserManager $userManager,
+ IConfig $config) {
$this->jobList = $jobList;
$this->userManager = $userManager;
$this->config = $config;
diff --git a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php
index 7f4bbc35c17..4ba9ad083e3 100644
--- a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php
+++ b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php
@@ -51,10 +51,10 @@ class CleanPreviewsBackgroundJob extends QueuedJob {
* CleanPreviewsBackgroundJob constructor.
*/
public function __construct(IRootFolder $rootFolder,
- LoggerInterface $logger,
- IJobList $jobList,
- ITimeFactory $timeFactory,
- IUserManager $userManager) {
+ LoggerInterface $logger,
+ IJobList $jobList,
+ ITimeFactory $timeFactory,
+ IUserManager $userManager) {
$this->rootFolder = $rootFolder;
$this->logger = $logger;
$this->jobList = $jobList;
diff --git a/lib/private/Repair/Owncloud/MigrateOauthTables.php b/lib/private/Repair/Owncloud/MigrateOauthTables.php
index 5bf0816d8de..ae2b46e1949 100644
--- a/lib/private/Repair/Owncloud/MigrateOauthTables.php
+++ b/lib/private/Repair/Owncloud/MigrateOauthTables.php
@@ -20,11 +20,11 @@
*/
namespace OC\Repair\Owncloud;
-use OCP\Migration\IOutput;
-use OCP\Migration\IRepairStep;
use OC\DB\Connection;
use OC\DB\SchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
class MigrateOauthTables implements IRepairStep {
/** @var Connection */
diff --git a/lib/private/Repair/Owncloud/MoveAvatars.php b/lib/private/Repair/Owncloud/MoveAvatars.php
index 44ba9b7643b..1ec08710b3a 100644
--- a/lib/private/Repair/Owncloud/MoveAvatars.php
+++ b/lib/private/Repair/Owncloud/MoveAvatars.php
@@ -41,7 +41,7 @@ class MoveAvatars implements IRepairStep {
* @param IConfig $config
*/
public function __construct(IJobList $jobList,
- IConfig $config) {
+ IConfig $config) {
$this->jobList = $jobList;
$this->config = $config;
}
diff --git a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php
index e08a0b55a9a..ae8e8bb0743 100644
--- a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php
+++ b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php
@@ -40,7 +40,7 @@ class UpdateLanguageCodes implements IRepairStep {
* @param IConfig $config
*/
public function __construct(IDBConnection $connection,
- IConfig $config) {
+ IConfig $config) {
$this->connection = $connection;
$this->config = $config;
}
diff --git a/lib/private/Repair/RemoveLinkShares.php b/lib/private/Repair/RemoveLinkShares.php
index b45a1d83a56..3e47e3233a2 100644
--- a/lib/private/Repair/RemoveLinkShares.php
+++ b/lib/private/Repair/RemoveLinkShares.php
@@ -54,10 +54,10 @@ class RemoveLinkShares implements IRepairStep {
private $timeFactory;
public function __construct(IDBConnection $connection,
- IConfig $config,
- IGroupManager $groupManager,
- IManager $notificationManager,
- ITimeFactory $timeFactory) {
+ IConfig $config,
+ IGroupManager $groupManager,
+ IManager $notificationManager,
+ ITimeFactory $timeFactory) {
$this->connection = $connection;
$this->config = $config;
$this->groupManager = $groupManager;
diff --git a/lib/private/Repair/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php
index ee5a84ad65c..4d15dda45dd 100644
--- a/lib/private/Repair/RepairMimeTypes.php
+++ b/lib/private/Repair/RepairMimeTypes.php
@@ -49,7 +49,7 @@ class RepairMimeTypes implements IRepairStep {
protected $folderMimeTypeId;
public function __construct(IConfig $config,
- IDBConnection $connection) {
+ IDBConnection $connection) {
$this->config = $config;
$this->connection = $connection;
}
@@ -229,6 +229,22 @@ class RepairMimeTypes implements IRepairStep {
return $this->updateMimetypes($updatedMimetypes);
}
+ private function introduceEnhancedMetafileFormatType() {
+ $updatedMimetypes = [
+ 'emf' => 'image/emf',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceEmlAndMsgFormatType() {
+ $updatedMimetypes = [
+ 'eml' => 'message/rfc822',
+ 'msg' => 'application/vnd.ms-outlook',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
/**
* Fix mime types
@@ -286,5 +302,13 @@ class RepairMimeTypes implements IRepairStep {
if (version_compare($ocVersionFromBeforeUpdate, '26.0.0.1', '<') && $this->introduceAsciidocType()) {
$out->info('Fixed AsciiDoc mime types');
}
+
+ if (version_compare($ocVersionFromBeforeUpdate, '28.0.0.5', '<') && $this->introduceEnhancedMetafileFormatType()) {
+ $out->info('Fixed Enhanced Metafile Format mime types');
+ }
+
+ if (version_compare($ocVersionFromBeforeUpdate, '29.0.0.2', '<') && $this->introduceEmlAndMsgFormatType()) {
+ $out->info('Fixed eml and msg mime type');
+ }
}
}
diff --git a/lib/private/RichObjectStrings/Validator.php b/lib/private/RichObjectStrings/Validator.php
index 4585cbfc814..d7329c945e9 100644
--- a/lib/private/RichObjectStrings/Validator.php
+++ b/lib/private/RichObjectStrings/Validator.php
@@ -95,7 +95,7 @@ class Validator implements IValidator {
$missingKeys = array_diff($requiredParameters, array_keys($parameter));
if (!empty($missingKeys)) {
- throw new InvalidObjectExeption('Object is invalid');
+ throw new InvalidObjectExeption('Object is invalid, missing keys:'.json_encode($missingKeys));
}
}
diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php
index 5ce6c7c5c8f..65bbf602be0 100644
--- a/lib/private/Route/Router.php
+++ b/lib/private/Route/Router.php
@@ -230,9 +230,9 @@ class Router implements IRouter {
* @return \OC\Route\Route
*/
public function create($name,
- $pattern,
- array $defaults = [],
- array $requirements = []) {
+ $pattern,
+ array $defaults = [],
+ array $requirements = []) {
$route = new Route($pattern, $defaults, $requirements);
$this->collection->add($name, $route);
return $route;
@@ -354,8 +354,8 @@ class Router implements IRouter {
* @return string
*/
public function generate($name,
- $parameters = [],
- $absolute = false) {
+ $parameters = [],
+ $absolute = false) {
$referenceType = UrlGenerator::ABSOLUTE_URL;
if ($absolute === false) {
$referenceType = UrlGenerator::ABSOLUTE_PATH;
diff --git a/lib/private/Search/Filter/BooleanFilter.php b/lib/private/Search/Filter/BooleanFilter.php
new file mode 100644
index 00000000000..a64bf17f31c
--- /dev/null
+++ b/lib/private/Search/Filter/BooleanFilter.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class BooleanFilter implements IFilter {
+ private bool $value;
+
+ public function __construct(string $value) {
+ $this->value = match ($value) {
+ 'true', 'yes', 'y', '1' => true,
+ 'false', 'no', 'n', '0', '' => false,
+ default => throw new InvalidArgumentException('Invalid boolean value '. $value),
+ };
+ }
+
+ public function get(): bool {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/DateTimeFilter.php b/lib/private/Search/Filter/DateTimeFilter.php
new file mode 100644
index 00000000000..79abf9ad542
--- /dev/null
+++ b/lib/private/Search/Filter/DateTimeFilter.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use DateTimeImmutable;
+use OCP\Search\IFilter;
+
+class DateTimeFilter implements IFilter {
+ private DateTimeImmutable $value;
+
+ public function __construct(string $value) {
+ if (filter_var($value, FILTER_VALIDATE_INT)) {
+ $value = '@'.$value;
+ }
+
+ $this->value = new DateTimeImmutable($value);
+ }
+
+ public function get(): DateTimeImmutable {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/FloatFilter.php b/lib/private/Search/Filter/FloatFilter.php
new file mode 100644
index 00000000000..3db19ded59b
--- /dev/null
+++ b/lib/private/Search/Filter/FloatFilter.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class FloatFilter implements IFilter {
+ private float $value;
+
+ public function __construct(string $value) {
+ $this->value = filter_var($value, FILTER_VALIDATE_FLOAT);
+ if ($this->value === false) {
+ throw new InvalidArgumentException('Invalid float value '. $value);
+ }
+ }
+
+ public function get(): float {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/GroupFilter.php b/lib/private/Search/Filter/GroupFilter.php
new file mode 100644
index 00000000000..f0b34a360ca
--- /dev/null
+++ b/lib/private/Search/Filter/GroupFilter.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\IGroup;
+use OCP\IGroupManager;
+use OCP\Search\IFilter;
+
+class GroupFilter implements IFilter {
+ private IGroup $group;
+
+ public function __construct(
+ string $value,
+ IGroupManager $groupManager,
+ ) {
+ $group = $groupManager->get($value);
+ if ($group === null) {
+ throw new InvalidArgumentException('Group '.$value.' not found');
+ }
+ $this->group = $group;
+ }
+
+ public function get(): IGroup {
+ return $this->group;
+ }
+}
diff --git a/lib/private/Search/Filter/IntegerFilter.php b/lib/private/Search/Filter/IntegerFilter.php
new file mode 100644
index 00000000000..b5b907b220e
--- /dev/null
+++ b/lib/private/Search/Filter/IntegerFilter.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class IntegerFilter implements IFilter {
+ private int $value;
+
+ public function __construct(string $value) {
+ $this->value = filter_var($value, FILTER_VALIDATE_INT);
+ if ($this->value === false) {
+ throw new InvalidArgumentException('Invalid integer value '. $value);
+ }
+ }
+
+ public function get(): int {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/StringFilter.php b/lib/private/Search/Filter/StringFilter.php
new file mode 100644
index 00000000000..8f754d12051
--- /dev/null
+++ b/lib/private/Search/Filter/StringFilter.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class StringFilter implements IFilter {
+ public function __construct(
+ private string $value,
+ ) {
+ if ($value === '') {
+ throw new InvalidArgumentException('String filter can’t be empty');
+ }
+ }
+
+ public function get(): string {
+ return $this->value;
+ }
+}
diff --git a/lib/private/Search/Filter/StringsFilter.php b/lib/private/Search/Filter/StringsFilter.php
new file mode 100644
index 00000000000..7a8d88768e8
--- /dev/null
+++ b/lib/private/Search/Filter/StringsFilter.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\Search\IFilter;
+
+class StringsFilter implements IFilter {
+ /**
+ * @var string[]
+ */
+ private array $values;
+
+ public function __construct(string ...$values) {
+ $this->values = array_unique(array_filter($values));
+ if (empty($this->values)) {
+ throw new InvalidArgumentException('Strings filter can’t be empty');
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ public function get(): array {
+ return $this->values;
+ }
+}
diff --git a/lib/private/Search/Filter/UserFilter.php b/lib/private/Search/Filter/UserFilter.php
new file mode 100644
index 00000000000..963d5e123ac
--- /dev/null
+++ b/lib/private/Search/Filter/UserFilter.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Search\Filter;
+
+use InvalidArgumentException;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Search\IFilter;
+
+class UserFilter implements IFilter {
+ private IUser $user;
+
+ public function __construct(
+ string $value,
+ IUserManager $userManager,
+ ) {
+ $user = $userManager->get($value);
+ if ($user === null) {
+ throw new InvalidArgumentException('User '.$value.' not found');
+ }
+ $this->user = $user;
+ }
+
+ public function get(): IUser {
+ return $this->user;
+ }
+}
diff --git a/lib/private/Search/FilterCollection.php b/lib/private/Search/FilterCollection.php
new file mode 100644
index 00000000000..15d6695dcac
--- /dev/null
+++ b/lib/private/Search/FilterCollection.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Search;
+
+use Generator;
+use OCP\Search\IFilter;
+use OCP\Search\IFilterCollection;
+
+/**
+ * Interface for search filters
+ *
+ * @since 28.0.0
+ */
+class FilterCollection implements IFilterCollection {
+ /**
+ * @var IFilter[]
+ */
+ private array $filters;
+
+ public function __construct(IFilter ...$filters) {
+ $this->filters = $filters;
+ }
+
+ public function has(string $name): bool {
+ return isset($this->filters[$name]);
+ }
+
+ public function get(string $name): ?IFilter {
+ return $this->filters[$name] ?? null;
+ }
+
+ public function getIterator(): Generator {
+ foreach ($this->filters as $k => $v) {
+ yield $k => $v;
+ }
+ }
+}
diff --git a/lib/private/Search/FilterFactory.php b/lib/private/Search/FilterFactory.php
new file mode 100644
index 00000000000..3f4388f405c
--- /dev/null
+++ b/lib/private/Search/FilterFactory.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Search;
+
+use OCP\IGroupManager;
+use OCP\IUserManager;
+use OCP\Search\FilterDefinition;
+use OCP\Search\IFilter;
+use RuntimeException;
+
+final class FilterFactory {
+ private const PERSON_TYPE_SEPARATOR = '/';
+
+ public static function get(string $type, string|array $filter): IFilter {
+ return match ($type) {
+ FilterDefinition::TYPE_BOOL => new Filter\BooleanFilter($filter),
+ FilterDefinition::TYPE_DATETIME => new Filter\DateTimeFilter($filter),
+ FilterDefinition::TYPE_FLOAT => new Filter\FloatFilter($filter),
+ FilterDefinition::TYPE_INT => new Filter\IntegerFilter($filter),
+ FilterDefinition::TYPE_NC_GROUP => new Filter\GroupFilter($filter, \OC::$server->get(IGroupManager::class)),
+ FilterDefinition::TYPE_NC_USER => new Filter\UserFilter($filter, \OC::$server->get(IUserManager::class)),
+ FilterDefinition::TYPE_PERSON => self::getPerson($filter),
+ FilterDefinition::TYPE_STRING => new Filter\StringFilter($filter),
+ FilterDefinition::TYPE_STRINGS => new Filter\StringsFilter(... (array) $filter),
+ default => throw new RuntimeException('Invalid filter type '. $type),
+ };
+ }
+
+ private static function getPerson(string $person): IFilter {
+ $parts = explode(self::PERSON_TYPE_SEPARATOR, $person, 2);
+
+ return match (count($parts)) {
+ 1 => self::get(FilterDefinition::TYPE_NC_USER, $person),
+ 2 => self::get(... $parts),
+ };
+ }
+}
diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php
index 4ec73ec54e9..03e84a079fe 100644
--- a/lib/private/Search/SearchComposer.php
+++ b/lib/private/Search/SearchComposer.php
@@ -28,14 +28,20 @@ declare(strict_types=1);
namespace OC\Search;
use InvalidArgumentException;
-use OCP\AppFramework\QueryException;
-use OCP\IServerContainer;
+use OC\AppFramework\Bootstrap\Coordinator;
+use OCP\IURLGenerator;
use OCP\IUser;
+use OCP\Search\FilterDefinition;
+use OCP\Search\IFilter;
+use OCP\Search\IFilteringProvider;
+use OCP\Search\IInAppSearch;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
-use OC\AppFramework\Bootstrap\Coordinator;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use RuntimeException;
use function array_map;
/**
@@ -58,31 +64,40 @@ use function array_map;
* @see IProvider::search() for the arguments of the individual search requests
*/
class SearchComposer {
- /** @var IProvider[] */
- private $providers = [];
-
- /** @var Coordinator */
- private $bootstrapCoordinator;
+ /**
+ * @var array<string, array{appId: string, provider: IProvider}>
+ */
+ private array $providers = [];
- /** @var IServerContainer */
- private $container;
+ private array $commonFilters;
+ private array $customFilters = [];
- private LoggerInterface $logger;
+ private array $handlers = [];
- public function __construct(Coordinator $bootstrapCoordinator,
- IServerContainer $container,
- LoggerInterface $logger) {
- $this->container = $container;
- $this->logger = $logger;
- $this->bootstrapCoordinator = $bootstrapCoordinator;
+ public function __construct(
+ private Coordinator $bootstrapCoordinator,
+ private ContainerInterface $container,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger
+ ) {
+ $this->commonFilters = [
+ IFilter::BUILTIN_TERM => new FilterDefinition(IFilter::BUILTIN_TERM, FilterDefinition::TYPE_STRING),
+ IFilter::BUILTIN_SINCE => new FilterDefinition(IFilter::BUILTIN_SINCE, FilterDefinition::TYPE_DATETIME),
+ IFilter::BUILTIN_UNTIL => new FilterDefinition(IFilter::BUILTIN_UNTIL, FilterDefinition::TYPE_DATETIME),
+ IFilter::BUILTIN_TITLE_ONLY => new FilterDefinition(IFilter::BUILTIN_TITLE_ONLY, FilterDefinition::TYPE_BOOL, false),
+ IFilter::BUILTIN_PERSON => new FilterDefinition(IFilter::BUILTIN_PERSON, FilterDefinition::TYPE_PERSON),
+ IFilter::BUILTIN_PLACES => new FilterDefinition(IFilter::BUILTIN_PLACES, FilterDefinition::TYPE_STRINGS, false),
+ IFilter::BUILTIN_PROVIDER => new FilterDefinition(IFilter::BUILTIN_PROVIDER, FilterDefinition::TYPE_STRING, false),
+ ];
}
/**
* Load all providers dynamically that were registered through `registerProvider`
*
+ * If $targetProviderId is provided, only this provider is loaded
* If a provider can't be loaded we log it but the operation continues nevertheless
*/
- private function loadLazyProviders(): void {
+ private function loadLazyProviders(?string $targetProviderId = null): void {
$context = $this->bootstrapCoordinator->getRegistrationContext();
if ($context === null) {
// Too early, nothing registered yet
@@ -93,9 +108,20 @@ class SearchComposer {
foreach ($registrations as $registration) {
try {
/** @var IProvider $provider */
- $provider = $this->container->query($registration->getService());
- $this->providers[$provider->getId()] = $provider;
- } catch (QueryException $e) {
+ $provider = $this->container->get($registration->getService());
+ $providerId = $provider->getId();
+ if ($targetProviderId !== null && $targetProviderId !== $providerId) {
+ continue;
+ }
+ $this->providers[$providerId] = [
+ 'appId' => $registration->getAppId(),
+ 'provider' => $provider,
+ ];
+ $this->handlers[$providerId] = [$providerId];
+ if ($targetProviderId !== null) {
+ break;
+ }
+ } catch (ContainerExceptionInterface $e) {
// Log an continue. We can be fault tolerant here.
$this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [
'exception' => $e,
@@ -103,6 +129,43 @@ class SearchComposer {
]);
}
}
+
+ $this->loadFilters();
+ }
+
+ private function loadFilters(): void {
+ foreach ($this->providers as $providerId => $providerData) {
+ $appId = $providerData['appId'];
+ $provider = $providerData['provider'];
+ if (!$provider instanceof IFilteringProvider) {
+ continue;
+ }
+
+ foreach ($provider->getCustomFilters() as $filter) {
+ $this->registerCustomFilter($filter, $providerId);
+ }
+ foreach ($provider->getAlternateIds() as $alternateId) {
+ $this->handlers[$alternateId][] = $providerId;
+ }
+ foreach ($provider->getSupportedFilters() as $filterName) {
+ if ($this->getFilterDefinition($filterName, $providerId) === null) {
+ throw new InvalidArgumentException('Invalid filter '. $filterName);
+ }
+ }
+ }
+ }
+
+ private function registerCustomFilter(FilterDefinition $filter, string $providerId): void {
+ $name = $filter->name();
+ if (isset($this->commonFilters[$name])) {
+ throw new InvalidArgumentException('Filter name is already used');
+ }
+
+ if (isset($this->customFilters[$providerId])) {
+ $this->customFilters[$providerId][$name] = $filter;
+ } else {
+ $this->customFilters[$providerId] = [$name => $filter];
+ }
}
/**
@@ -117,26 +180,146 @@ class SearchComposer {
public function getProviders(string $route, array $routeParameters): array {
$this->loadLazyProviders();
- $providers = array_values(
- array_map(function (IProvider $provider) use ($route, $routeParameters) {
+ $providers = array_map(
+ function (array $providerData) use ($route, $routeParameters) {
+ $appId = $providerData['appId'];
+ $provider = $providerData['provider'];
+ $order = $provider->getOrder($route, $routeParameters);
+ if ($order === null) {
+ return;
+ }
+ $triggers = [$provider->getId()];
+ if ($provider instanceof IFilteringProvider) {
+ $triggers += $provider->getAlternateIds();
+ $filters = $provider->getSupportedFilters();
+ } else {
+ $filters = [IFilter::BUILTIN_TERM];
+ }
+
return [
'id' => $provider->getId(),
+ 'appId' => $appId,
'name' => $provider->getName(),
- 'order' => $provider->getOrder($route, $routeParameters),
+ 'icon' => $this->fetchIcon($appId, $provider->getId()),
+ 'order' => $order,
+ 'triggers' => $triggers,
+ 'filters' => $this->getFiltersType($filters, $provider->getId()),
+ 'inAppSearch' => $provider instanceof IInAppSearch,
];
- }, $this->providers)
+ },
+ $this->providers,
);
+ $providers = array_filter($providers);
+ // Sort providers by order and strip associative keys
usort($providers, function ($provider1, $provider2) {
return $provider1['order'] <=> $provider2['order'];
});
- /**
- * Return an array with the IDs, but strip the associative keys
- */
return $providers;
}
+ private function fetchIcon(string $appId, string $providerId): string {
+ $icons = [
+ [$providerId, $providerId.'.svg'],
+ [$providerId, 'app.svg'],
+ [$appId, $providerId.'.svg'],
+ [$appId, $appId.'.svg'],
+ [$appId, 'app.svg'],
+ ['core', 'places/default-app-icon.svg'],
+ ];
+ if ($appId === 'settings' && $providerId === 'users') {
+ // Conflict:
+ // the file /apps/settings/users.svg is already used in black version by top right user menu
+ // Override icon name here
+ $icons = [['settings', 'users-white.svg']];
+ }
+ foreach ($icons as $i => $icon) {
+ try {
+ return $this->urlGenerator->imagePath(... $icon);
+ } catch (RuntimeException $e) {
+ // Ignore error
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * @param $filters string[]
+ * @return array<string, string>
+ */
+ private function getFiltersType(array $filters, string $providerId): array {
+ $filterList = [];
+ foreach ($filters as $filter) {
+ $filterList[$filter] = $this->getFilterDefinition($filter, $providerId)->type();
+ }
+
+ return $filterList;
+ }
+
+ private function getFilterDefinition(string $name, string $providerId): ?FilterDefinition {
+ if (isset($this->commonFilters[$name])) {
+ return $this->commonFilters[$name];
+ }
+ if (isset($this->customFilters[$providerId][$name])) {
+ return $this->customFilters[$providerId][$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param array<string, string> $parameters
+ */
+ public function buildFilterList(string $providerId, array $parameters): FilterCollection {
+ $this->loadLazyProviders($providerId);
+
+ $list = [];
+ foreach ($parameters as $name => $value) {
+ $filter = $this->buildFilter($name, $value, $providerId);
+ if ($filter === null) {
+ continue;
+ }
+ $list[$name] = $filter;
+ }
+
+ return new FilterCollection(... $list);
+ }
+
+ private function buildFilter(string $name, string $value, string $providerId): ?IFilter {
+ $filterDefinition = $this->getFilterDefinition($name, $providerId);
+ if ($filterDefinition === null) {
+ $this->logger->debug('Unable to find {name} definition', [
+ 'name' => $name,
+ 'value' => $value,
+ ]);
+
+ return null;
+ }
+
+ if (!$this->filterSupportedByProvider($filterDefinition, $providerId)) {
+ // FIXME Use dedicated exception and handle it
+ throw new UnsupportedFilter($name, $providerId);
+ }
+
+ return FilterFactory::get($filterDefinition->type(), $value);
+ }
+
+ private function filterSupportedByProvider(FilterDefinition $filterDefinition, string $providerId): bool {
+ // Non exclusive filters can be ommited by apps
+ if (!$filterDefinition->exclusive()) {
+ return true;
+ }
+
+ $provider = $this->providers[$providerId]['provider'];
+ $supportedFilters = $provider instanceof IFilteringProvider
+ ? $provider->getSupportedFilters()
+ : [IFilter::BUILTIN_TERM];
+
+ return in_array($filterDefinition->name(), $supportedFilters, true);
+ }
+
/**
* Query an individual search provider for results
*
@@ -147,15 +330,18 @@ class SearchComposer {
* @return SearchResult
* @throws InvalidArgumentException when the $providerId does not correspond to a registered provider
*/
- public function search(IUser $user,
- string $providerId,
- ISearchQuery $query): SearchResult {
- $this->loadLazyProviders();
+ public function search(
+ IUser $user,
+ string $providerId,
+ ISearchQuery $query,
+ ): SearchResult {
+ $this->loadLazyProviders($providerId);
- $provider = $this->providers[$providerId] ?? null;
+ $provider = $this->providers[$providerId]['provider'] ?? null;
if ($provider === null) {
throw new InvalidArgumentException("Provider $providerId is unknown");
}
+
return $provider->search($user, $query);
}
}
diff --git a/lib/private/Search/SearchQuery.php b/lib/private/Search/SearchQuery.php
index c89446d5970..e4295c4ab76 100644
--- a/lib/private/Search/SearchQuery.php
+++ b/lib/private/Search/SearchQuery.php
@@ -27,89 +27,57 @@ declare(strict_types=1);
*/
namespace OC\Search;
+use OCP\Search\IFilter;
+use OCP\Search\IFilterCollection;
use OCP\Search\ISearchQuery;
class SearchQuery implements ISearchQuery {
public const LIMIT_DEFAULT = 5;
- /** @var string */
- private $term;
-
- /** @var int */
- private $sortOrder;
-
- /** @var int */
- private $limit;
-
- /** @var int|string|null */
- private $cursor;
-
- /** @var string */
- private $route;
-
- /** @var array */
- private $routeParameters;
-
/**
- * @param string $term
- * @param int $sortOrder
- * @param int $limit
- * @param int|string|null $cursor
- * @param string $route
- * @param array $routeParameters
+ * @param string[] $params Request query
+ * @param string[] $routeParameters
*/
- public function __construct(string $term,
- int $sortOrder = ISearchQuery::SORT_DATE_DESC,
- int $limit = self::LIMIT_DEFAULT,
- $cursor = null,
- string $route = '',
- array $routeParameters = []) {
- $this->term = $term;
- $this->sortOrder = $sortOrder;
- $this->limit = $limit;
- $this->cursor = $cursor;
- $this->route = $route;
- $this->routeParameters = $routeParameters;
+ public function __construct(
+ private IFilterCollection $filters,
+ private int $sortOrder = ISearchQuery::SORT_DATE_DESC,
+ private int $limit = self::LIMIT_DEFAULT,
+ private int|string|null $cursor = null,
+ private string $route = '',
+ private array $routeParameters = [],
+ ) {
}
- /**
- * @inheritDoc
- */
public function getTerm(): string {
- return $this->term;
+ return $this->getFilter('term')?->get() ?? '';
+ }
+
+ public function getFilter(string $name): ?IFilter {
+ return $this->filters->has($name)
+ ? $this->filters->get($name)
+ : null;
+ }
+
+ public function getFilters(): IFilterCollection {
+ return $this->filters;
}
- /**
- * @inheritDoc
- */
public function getSortOrder(): int {
return $this->sortOrder;
}
- /**
- * @inheritDoc
- */
public function getLimit(): int {
return $this->limit;
}
- /**
- * @inheritDoc
- */
- public function getCursor() {
+ public function getCursor(): int|string|null {
return $this->cursor;
}
- /**
- * @inheritDoc
- */
public function getRoute(): string {
return $this->route;
}
- /**
- * @inheritDoc
- */
public function getRouteParameters(): array {
return $this->routeParameters;
}
diff --git a/lib/private/Search/UnsupportedFilter.php b/lib/private/Search/UnsupportedFilter.php
new file mode 100644
index 00000000000..84b6163d2fa
--- /dev/null
+++ b/lib/private/Search/UnsupportedFilter.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Search;
+
+use Exception;
+
+final class UnsupportedFilter extends Exception {
+ public function __construct(string $filerName, $providerId) {
+ parent::__construct('Provider '.$providerId.' doesn’t support filter '.$filerName.'.');
+ }
+}
diff --git a/lib/private/Security/Bruteforce/Capabilities.php b/lib/private/Security/Bruteforce/Capabilities.php
index b50eea0b7af..add2bb8d8b5 100644
--- a/lib/private/Security/Bruteforce/Capabilities.php
+++ b/lib/private/Security/Bruteforce/Capabilities.php
@@ -29,8 +29,8 @@ declare(strict_types=1);
*/
namespace OC\Security\Bruteforce;
-use OCP\Capabilities\IPublicCapability;
use OCP\Capabilities\IInitialStateExcludedCapability;
+use OCP\Capabilities\IPublicCapability;
use OCP\IRequest;
use OCP\Security\Bruteforce\IThrottler;
diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php
index 5316071f25c..7e5f1daa28c 100644
--- a/lib/private/Security/Bruteforce/Throttler.php
+++ b/lib/private/Security/Bruteforce/Throttler.php
@@ -72,8 +72,8 @@ class Throttler implements IThrottler {
* {@inheritDoc}
*/
public function registerAttempt(string $action,
- string $ip,
- array $metadata = []): void {
+ string $ip,
+ array $metadata = []): void {
// No need to log if the bruteforce protection is disabled
if (!$this->config->getSystemValueBool('auth.bruteforce.protection.enabled', true)) {
return;
diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php
index eca3e2b6b29..ee525af4c2a 100644
--- a/lib/private/Security/CSP/ContentSecurityPolicy.php
+++ b/lib/private/Security/CSP/ContentSecurityPolicy.php
@@ -191,4 +191,12 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
public function setStrictDynamicAllowed(bool $strictDynamicAllowed): void {
$this->strictDynamicAllowed = $strictDynamicAllowed;
}
+
+ public function isStrictDynamicAllowedOnScripts(): bool {
+ return $this->strictDynamicAllowedOnScripts;
+ }
+
+ public function setStrictDynamicAllowedOnScripts(bool $strictDynamicAllowedOnScripts): void {
+ $this->strictDynamicAllowedOnScripts = $strictDynamicAllowedOnScripts;
+ }
}
diff --git a/lib/private/Security/Normalizer/IpAddress.php b/lib/private/Security/Normalizer/IpAddress.php
index 9aade6c3591..f8e55370da7 100644
--- a/lib/private/Security/Normalizer/IpAddress.php
+++ b/lib/private/Security/Normalizer/IpAddress.php
@@ -38,7 +38,7 @@ namespace OC\Security\Normalizer;
*/
class IpAddress {
/**
- * @param string $ip IP to normalized
+ * @param string $ip IP to normalize
*/
public function __construct(
private string $ip,
@@ -46,24 +46,9 @@ class IpAddress {
}
/**
- * Return the given subnet for an IPv4 address and mask bits
+ * Return the given subnet for an IPv6 address (64 first bits)
*/
- private function getIPv4Subnet(string $ip, int $maskBits = 32): string {
- $binary = \inet_pton($ip);
- for ($i = 32; $i > $maskBits; $i -= 8) {
- $j = \intdiv($i, 8) - 1;
- $k = \min(8, $i - $maskBits);
- $mask = (0xff - ((2 ** $k) - 1));
- $int = \unpack('C', $binary[$j]);
- $binary[$j] = \pack('C', $int[1] & $mask);
- }
- return \inet_ntop($binary).'/'.$maskBits;
- }
-
- /**
- * Return the given subnet for an IPv6 address and mask bits
- */
- private function getIPv6Subnet(string $ip, int $maskBits = 48): string {
+ private function getIPv6Subnet(string $ip): string {
if ($ip[0] === '[' && $ip[-1] === ']') { // If IP is with brackets, for example [::1]
$ip = substr($ip, 1, strlen($ip) - 2);
}
@@ -71,15 +56,11 @@ class IpAddress {
if ($pos !== false) {
$ip = substr($ip, 0, $pos - 1);
}
+
$binary = \inet_pton($ip);
- for ($i = 128; $i > $maskBits; $i -= 8) {
- $j = \intdiv($i, 8) - 1;
- $k = \min(8, $i - $maskBits);
- $mask = (0xff - ((2 ** $k) - 1));
- $int = \unpack('C', $binary[$j]);
- $binary[$j] = \pack('C', $int[1] & $mask);
- }
- return \inet_ntop($binary).'/'.$maskBits;
+ $mask = inet_pton('FFFF:FFFF:FFFF:FFFF::');
+
+ return inet_ntop($binary & $mask).'/64';
}
/**
@@ -93,24 +74,13 @@ class IpAddress {
if (!$binary) {
return null;
}
- for ($i = 0; $i <= 9; $i++) {
- if (unpack('C', $binary[$i])[1] !== 0) {
- return null;
- }
- }
- for ($i = 10; $i <= 11; $i++) {
- if (unpack('C', $binary[$i])[1] !== 255) {
- return null;
- }
- }
-
- $binary4 = '';
- for ($i = 12; $i < 16; $i++) {
- $binary4 .= $binary[$i];
+ $mask = inet_pton('::FFFF:FFFF');
+ if (($binary & ~$mask) !== inet_pton('::FFFF:0.0.0.0')) {
+ return null;
}
- return inet_ntop($binary4);
+ return inet_ntop(substr($binary, -4));
}
@@ -118,25 +88,16 @@ class IpAddress {
* Gets either the /32 (IPv4) or the /64 (IPv6) subnet of an IP address
*/
public function getSubnet(): string {
- if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->ip)) {
- return $this->getIPv4Subnet(
- $this->ip,
- 32
- );
+ if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return $this->ip.'/32';
}
$ipv4 = $this->getEmbeddedIpv4($this->ip);
if ($ipv4 !== null) {
- return $this->getIPv4Subnet(
- $ipv4,
- 32
- );
+ return $ipv4.'/32';
}
- return $this->getIPv6Subnet(
- $this->ip,
- 64
- );
+ return $this->getIPv6Subnet($this->ip);
}
/**
diff --git a/lib/private/Security/RemoteHostValidator.php b/lib/private/Security/RemoteHostValidator.php
index 385b38cff98..9cc69594c32 100644
--- a/lib/private/Security/RemoteHostValidator.php
+++ b/lib/private/Security/RemoteHostValidator.php
@@ -52,6 +52,10 @@ final class RemoteHostValidator implements IRemoteHostValidator {
}
$host = idn_to_utf8(strtolower(urldecode($host)));
+ if ($host === false) {
+ return false;
+ }
+
// Remove brackets from IPv6 addresses
if (str_starts_with($host, '[') && str_ends_with($host, ']')) {
$host = substr($host, 1, -1);
diff --git a/lib/private/Security/VerificationToken/CleanUpJob.php b/lib/private/Security/VerificationToken/CleanUpJob.php
index 1f4af046451..9c1b27d344d 100644
--- a/lib/private/Security/VerificationToken/CleanUpJob.php
+++ b/lib/private/Security/VerificationToken/CleanUpJob.php
@@ -27,10 +27,10 @@ declare(strict_types=1);
namespace OC\Security\VerificationToken;
use OCP\AppFramework\Utility\ITimeFactory;
-use OCP\IConfig;
-use OCP\IUserManager;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\Job;
+use OCP\IConfig;
+use OCP\IUserManager;
use OCP\Security\VerificationToken\InvalidTokenException;
use OCP\Security\VerificationToken\IVerificationToken;
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 949a7ccfd3f..d026ad4286d 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -68,6 +68,7 @@ use OC\Authentication\Listeners\UserLoggedInListener;
use OC\Authentication\LoginCredentials\Store;
use OC\Authentication\Token\IProvider;
use OC\Avatar\AvatarManager;
+use OC\Blurhash\Listener\GenerateBlurhashMetadata;
use OC\Collaboration\Collaborators\GroupPlugin;
use OC\Collaboration\Collaborators\MailPlugin;
use OC\Collaboration\Collaborators\RemoteGroupPlugin;
@@ -102,6 +103,7 @@ use OC\Files\Storage\StorageFactory;
use OC\Files\Template\TemplateManager;
use OC\Files\Type\Loader;
use OC\Files\View;
+use OC\FilesMetadata\FilesMetadataManager;
use OC\FullTextSearch\FullTextSearchManager;
use OC\Http\Client\ClientService;
use OC\Http\Client\NegativeDnsCache;
@@ -109,8 +111,8 @@ use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\AppLocator;
use OC\IntegrityCheck\Helpers\EnvironmentHelper;
use OC\IntegrityCheck\Helpers\FileAccessHelper;
-use OC\LDAP\NullLDAPProviderFactory;
use OC\KnownUser\KnownUserService;
+use OC\LDAP\NullLDAPProviderFactory;
use OC\Lock\DBLockingProvider;
use OC\Lock\MemcacheLockingProvider;
use OC\Lock\NoopLockingProvider;
@@ -120,9 +122,6 @@ use OC\Log\PsrLoggerAdapter;
use OC\Mail\Mailer;
use OC\Memcache\ArrayCache;
use OC\Memcache\Factory;
-use OC\Metadata\Capabilities as MetadataCapabilities;
-use OC\Metadata\IMetadataManager;
-use OC\Metadata\MetadataManager;
use OC\Notification\Manager;
use OC\OCM\Model\OCMProvider;
use OC\OCM\OCMDiscoveryService;
@@ -130,6 +129,8 @@ use OC\OCS\DiscoveryService;
use OC\Preview\GeneratorHelper;
use OC\Preview\IMagickSupport;
use OC\Preview\MimeIconProvider;
+use OC\Profile\ProfileManager;
+use OC\Profiler\Profiler;
use OC\Remote\Api\ApiFactory;
use OC\Remote\InstanceFactory;
use OC\RichObjectStrings\Validator;
@@ -149,6 +150,7 @@ 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;
@@ -158,15 +160,21 @@ use OC\Tagging\TagMapper;
use OC\Talk\Broker;
use OC\Template\JSCombiner;
use OC\Translation\TranslationManager;
+use OC\User\AvailabilityCoordinator;
use OC\User\DisplayNameCache;
use OC\User\Listeners\BeforeUserDeletedListener;
use OC\User\Listeners\UserChangedListener;
use OC\User\Session;
+use OCA\Files_External\Service\BackendService;
+use OCA\Files_External\Service\GlobalStoragesService;
+use OCA\Files_External\Service\UserGlobalStoragesService;
+use OCA\Files_External\Service\UserStoragesService;
use OCA\Theming\ImageManager;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\Authentication\Token\IProvider as OCPIProvider;
use OCP\BackgroundJob\IJobList;
@@ -194,16 +202,17 @@ use OCP\Files\Lock\ILockManager;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Storage\IStorageFactory;
use OCP\Files\Template\ITemplateManager;
+use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\FullTextSearch\IFullTextSearchManager;
use OCP\GlobalScale\IConfig;
use OCP\Group\ISubAdmin;
use OCP\Http\Client\IClientService;
use OCP\IAppConfig;
use OCP\IAvatarManager;
+use OCP\IBinaryFinder;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\ICertificateManager;
-use OCP\IBinaryFinder;
use OCP\IDateTimeFormatter;
use OCP\IDateTimeZone;
use OCP\IDBConnection;
@@ -234,6 +243,9 @@ use OCP\Log\ILogFactory;
use OCP\Mail\IMailer;
use OCP\OCM\IOCMDiscoveryService;
use OCP\OCM\IOCMProvider;
+use OCP\Preview\IMimeIconProvider;
+use OCP\Profile\IProfileManager;
+use OCP\Profiler\IProfiler;
use OCP\Remote\Api\IApiFactory;
use OCP\Remote\IInstanceFactory;
use OCP\RichObjectStrings\IValidator;
@@ -247,6 +259,7 @@ 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;
@@ -262,16 +275,10 @@ use OCP\User\Events\UserChangedEvent;
use OCP\User\Events\UserLoggedInEvent;
use OCP\User\Events\UserLoggedInWithCookieEvent;
use OCP\User\Events\UserLoggedOutEvent;
+use OCP\User\IAvailabilityCoordinator;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
-use OCA\Files_External\Service\UserStoragesService;
-use OCA\Files_External\Service\UserGlobalStoragesService;
-use OCA\Files_External\Service\GlobalStoragesService;
-use OCA\Files_External\Service\BackendService;
-use OCP\Profiler\IProfiler;
-use OC\Profiler\Profiler;
-use OCP\Preview\IMimeIconProvider;
/**
* Class Server
@@ -837,8 +844,7 @@ class Server extends ServerContainer implements IServerContainer {
if (!$factory->isValidType($type)) {
throw new \OC\DatabaseException('Invalid database type');
}
- $connectionParams = $factory->createConnectionParams();
- $connection = $factory->getConnection($type, $connectionParams);
+ $connection = $factory->getConnection($type, []);
return $connection;
});
/** @deprecated 19.0.0 */
@@ -1074,7 +1080,8 @@ class Server extends ServerContainer implements IServerContainer {
$memcacheFactory = $c->get(ICacheFactory::class);
$memcache = $memcacheFactory->createLocking('lock');
if (!($memcache instanceof \OC\Memcache\NullCache)) {
- return new MemcacheLockingProvider($memcache, $ttl);
+ $timeFactory = $c->get(ITimeFactory::class);
+ return new MemcacheLockingProvider($memcache, $timeFactory, $ttl);
}
return new DBLockingProvider(
$c->get(IDBConnection::class),
@@ -1130,9 +1137,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 */
@@ -1397,6 +1401,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);
@@ -1408,8 +1413,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);
@@ -1424,12 +1427,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();
}
@@ -1470,6 +1481,9 @@ 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);
+ GenerateBlurhashMetadata::loadListeners($eventDispatcher);
}
/**
diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php
index 76a214584a6..22d2aba0405 100644
--- a/lib/private/Session/CryptoSessionData.php
+++ b/lib/private/Session/CryptoSessionData.php
@@ -32,6 +32,7 @@ namespace OC\Session;
use OCP\ISession;
use OCP\Security\ICrypto;
use OCP\Session\Exceptions\SessionNotAvailableException;
+use function json_decode;
use function OCP\Log\logger;
/**
@@ -58,8 +59,8 @@ class CryptoSessionData implements \ArrayAccess, ISession {
* @param string $passphrase
*/
public function __construct(ISession $session,
- ICrypto $crypto,
- string $passphrase) {
+ ICrypto $crypto,
+ string $passphrase) {
$this->crypto = $crypto;
$this->session = $session;
$this->passphrase = $passphrase;
@@ -80,19 +81,24 @@ class CryptoSessionData implements \ArrayAccess, ISession {
protected function initializeSession() {
$encryptedSessionData = $this->session->get(self::encryptedSessionName) ?: '';
- try {
- $this->sessionValues = json_decode(
- $this->crypto->decrypt($encryptedSessionData, $this->passphrase),
- true,
- 512,
- JSON_THROW_ON_ERROR,
- );
- } catch (\Exception $e) {
- logger('core')->critical('Could not decrypt or decode encrypted session data', [
- 'exception' => $e,
- ]);
+ if ($encryptedSessionData === '') {
+ // Nothing to decrypt
$this->sessionValues = [];
- $this->regenerateId(true, false);
+ } else {
+ try {
+ $this->sessionValues = json_decode(
+ $this->crypto->decrypt($encryptedSessionData, $this->passphrase),
+ true,
+ 512,
+ JSON_THROW_ON_ERROR,
+ );
+ } catch (\Exception $e) {
+ logger('core')->critical('Could not decrypt or decode encrypted session data', [
+ 'exception' => $e,
+ ]);
+ $this->sessionValues = [];
+ $this->regenerateId(true, false);
+ }
}
}
diff --git a/lib/private/Session/CryptoWrapper.php b/lib/private/Session/CryptoWrapper.php
index e98aac3b8bf..5004ebf82cf 100644
--- a/lib/private/Session/CryptoWrapper.php
+++ b/lib/private/Session/CryptoWrapper.php
@@ -68,9 +68,9 @@ class CryptoWrapper {
* @param IRequest $request
*/
public function __construct(IConfig $config,
- ICrypto $crypto,
- ISecureRandom $random,
- IRequest $request) {
+ ICrypto $crypto,
+ ISecureRandom $random,
+ IRequest $request) {
$this->crypto = $crypto;
$this->config = $config;
$this->random = $random;
diff --git a/lib/private/Session/Internal.php b/lib/private/Session/Internal.php
index e8e2a4f2d8e..5fb9b05c5f4 100644
--- a/lib/private/Session/Internal.php
+++ b/lib/private/Session/Internal.php
@@ -33,8 +33,8 @@ declare(strict_types=1);
*/
namespace OC\Session;
-use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IProvider;
+use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Session\Exceptions\SessionNotAvailableException;
/**
diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php
index 2d44ac7d3df..839d3e5ce38 100644
--- a/lib/private/Settings/Manager.php
+++ b/lib/private/Settings/Manager.php
@@ -12,6 +12,7 @@
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author sualko <klaus@jsxc.org>
* @author Carl Schwan <carl@carlschwan.eu>
+ * @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
@@ -90,17 +91,14 @@ class Manager implements IManager {
$this->subAdmin = $subAdmin;
}
- /** @var array */
+ /** @var array<self::SETTINGS_*, list<class-string<IIconSection>>> */
protected $sectionClasses = [];
- /** @var array */
+ /** @var array<self::SETTINGS_*, array<string, IIconSection>> */
protected $sections = [];
/**
- * @param string $type 'admin' or 'personal'
- * @param string $section Class must implement OCP\Settings\IIconSection
- *
- * @return void
+ * @inheritdoc
*/
public function registerSection(string $type, string $section) {
if (!isset($this->sectionClasses[$type])) {
@@ -111,7 +109,7 @@ class Manager implements IManager {
}
/**
- * @param string $type 'admin' or 'personal'
+ * @psalm-param self::SETTINGS_* $type
*
* @return IIconSection[]
*/
@@ -149,6 +147,9 @@ class Manager implements IManager {
return $this->sections[$type];
}
+ /**
+ * @inheritdoc
+ */
public function getSection(string $type, string $sectionId): ?IIconSection {
if (isset($this->sections[$type]) && isset($this->sections[$type][$sectionId])) {
return $this->sections[$type][$sectionId];
@@ -163,27 +164,23 @@ class Manager implements IManager {
], true);
}
- /** @var array */
+ /** @var array<class-string<ISettings>, self::SETTINGS_*> */
protected $settingClasses = [];
- /** @var array */
+ /** @var array<self::SETTINGS_*, array<string, list<ISettings>>> */
protected $settings = [];
/**
- * @psam-param 'admin'|'personal' $type The type of the setting.
- * @param string $setting Class must implement OCP\Settings\ISettings
- * @param bool $allowedDelegation
- *
- * @return void
+ * @inheritdoc
*/
public function registerSetting(string $type, string $setting) {
$this->settingClasses[$setting] = $type;
}
/**
- * @param string $type 'admin' or 'personal'
+ * @psalm-param self::SETTINGS_* $type The type of the setting.
* @param string $section
- * @param Closure $filter optional filter to apply on all loaded ISettings
+ * @param ?Closure $filter optional filter to apply on all loaded ISettings
*
* @return ISettings[]
*/
@@ -258,7 +255,7 @@ class Manager implements IManager {
/**
* @inheritdoc
*/
- public function getAdminSettings($section, bool $subAdminOnly = false): array {
+ public function getAdminSettings(string $section, bool $subAdminOnly = false): array {
if ($subAdminOnly) {
$subAdminSettingsFilter = function (ISettings $settings) {
return $settings instanceof ISubAdminSettings;
@@ -329,7 +326,7 @@ class Manager implements IManager {
/**
* @inheritdoc
*/
- public function getPersonalSettings($section): array {
+ public function getPersonalSettings(string $section): array {
$settings = [];
$appSettings = $this->getSettings('personal', $section);
@@ -344,6 +341,9 @@ class Manager implements IManager {
return $settings;
}
+ /**
+ * @inheritdoc
+ */
public function getAllowedAdminSettings(string $section, IUser $user): array {
$isAdmin = $this->groupManager->isAdmin($user->getUID());
if ($isAdmin) {
@@ -375,6 +375,9 @@ class Manager implements IManager {
return $settings;
}
+ /**
+ * @inheritdoc
+ */
public function getAllAllowedAdminSettings(IUser $user): array {
$this->getSettings('admin', ''); // Make sure all the settings are loaded
$settings = [];
diff --git a/lib/private/Setup.php b/lib/private/Setup.php
index f167d19adeb..650dca97067 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
@@ -53,51 +56,41 @@ use Exception;
use InvalidArgumentException;
use OC\Authentication\Token\PublicKeyTokenProvider;
use OC\Authentication\Token\TokenCleanupJob;
-use OC\TextProcessing\RemoveOldTasksBackgroundJob;
use OC\Log\Rotate;
use OC\Preview\BackgroundCleanupJob;
+use OC\TextProcessing\RemoveOldTasksBackgroundJob;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
use OCP\Defaults;
+use OCP\IConfig;
use OCP\IGroup;
+use OCP\IGroupManager;
use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use OCP\L10N\IFactory as IL10NFactory;
+use OCP\Migration\IOutput;
use OCP\Security\ISecureRandom;
+use OCP\Server;
use Psr\Log\LoggerInterface;
class Setup {
- /** @var SystemConfig */
- protected $config;
- /** @var IniGetWrapper */
- protected $iniWrapper;
- /** @var IL10N */
- protected $l10n;
- /** @var Defaults */
- protected $defaults;
- /** @var LoggerInterface */
- protected $logger;
- /** @var ISecureRandom */
- protected $random;
- /** @var Installer */
- protected $installer;
+ protected IL10N $l10n;
public function __construct(
- SystemConfig $config,
- IniGetWrapper $iniWrapper,
- IL10N $l10n,
- Defaults $defaults,
- LoggerInterface $logger,
- ISecureRandom $random,
- Installer $installer
+ protected SystemConfig $config,
+ protected IniGetWrapper $iniWrapper,
+ IL10NFactory $l10nFactory,
+ protected Defaults $defaults,
+ protected LoggerInterface $logger,
+ protected ISecureRandom $random,
+ protected Installer $installer
) {
- $this->config = $config;
- $this->iniWrapper = $iniWrapper;
- $this->l10n = $l10n;
- $this->defaults = $defaults;
- $this->logger = $logger;
- $this->random = $random;
- $this->installer = $installer;
+ $this->l10n = $l10nFactory->get('lib');
}
- protected static $dbSetupClasses = [
+ protected static array $dbSetupClasses = [
'mysql' => \OC\Setup\MySQL::class,
'pgsql' => \OC\Setup\PostgreSQL::class,
'oci' => \OC\Setup\OCI::class,
@@ -107,30 +100,22 @@ class Setup {
/**
* Wrapper around the "class_exists" PHP function to be able to mock it
- *
- * @param string $name
- * @return bool
*/
- protected function class_exists($name) {
+ protected function class_exists(string $name): bool {
return class_exists($name);
}
/**
* Wrapper around the "is_callable" PHP function to be able to mock it
- *
- * @param string $name
- * @return bool
*/
- protected function is_callable($name) {
+ protected function is_callable(string $name): bool {
return is_callable($name);
}
/**
* Wrapper around \PDO::getAvailableDrivers
- *
- * @return array
*/
- protected function getAvailableDbDriversForPdo() {
+ protected function getAvailableDbDriversForPdo(): array {
if (class_exists(\PDO::class)) {
return \PDO::getAvailableDrivers();
}
@@ -140,11 +125,10 @@ class Setup {
/**
* Get the available and supported databases of this instance
*
- * @param bool $allowAllDatabases
* @return array
* @throws Exception
*/
- public function getSupportedDatabases($allowAllDatabases = false) {
+ public function getSupportedDatabases(bool $allowAllDatabases = false): array {
$availableDatabases = [
'sqlite' => [
'type' => 'pdo',
@@ -206,7 +190,7 @@ class Setup {
* @return array of system info, including an "errors" value
* in case of errors/warnings
*/
- public function getSystemInfo($allowAllDatabases = false) {
+ public function getSystemInfo(bool $allowAllDatabases = false): array {
$databases = $this->getSupportedDatabases($allowAllDatabases);
$dataDir = $this->config->getValue('datadirectory', \OC::$SERVERROOT . '/data');
@@ -226,7 +210,7 @@ class Setup {
try {
$util = new \OC_Util();
- $htAccessWorking = $util->isHtaccessWorking(\OC::$server->getConfig());
+ $htAccessWorking = $util->isHtaccessWorking(Server::get(IConfig::class));
} catch (\OCP\HintException $e) {
$errors[] = [
'error' => $e->getMessage(),
@@ -272,10 +256,9 @@ class Setup {
}
/**
- * @param $options
- * @return array
+ * @return array<string|array> errors
*/
- public function install($options) {
+ public function install(array $options, ?IOutput $output = null): array {
$l = $this->l10n;
$error = [];
@@ -313,7 +296,7 @@ class Setup {
return $error;
}
- $request = \OC::$server->getRequest();
+ $request = Server::get(IRequest::class);
//no errors, good
if (isset($options['trusted_domains'])
@@ -349,6 +332,7 @@ class Setup {
$this->config->setValues($newConfigValues);
+ $this->outputDebug($output, 'Configuring database');
$dbSetup->initialize($options);
try {
$dbSetup->setupDatabase($username);
@@ -367,9 +351,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,78 +365,87 @@ class Setup {
return $error;
}
+ $this->outputDebug($output, 'Create admin user');
//create the user and group
$user = null;
try {
- $user = \OC::$server->getUserManager()->createUser($username, $password);
+ $user = Server::get(IUserManager::class)->createUser($username, $password);
if (!$user) {
$error[] = "User <$username> could not be created.";
+ return $error;
}
} catch (Exception $exception) {
$error[] = $exception->getMessage();
+ return $error;
}
- if (empty($error)) {
- $config = \OC::$server->getConfig();
- $config->setAppValue('core', 'installedat', (string)microtime(true));
- $config->setAppValue('core', 'lastupdatedat', (string)microtime(true));
-
- $vendorData = $this->getVendorData();
- $config->setAppValue('core', 'vendor', $vendorData['vendor']);
- if ($vendorData['channel'] !== 'stable') {
- $config->setSystemValue('updater.release.channel', $vendorData['channel']);
- }
+ $config = Server::get(IConfig::class);
+ $config->setAppValue('core', 'installedat', (string)microtime(true));
+ $config->setAppValue('core', 'lastupdatedat', (string)microtime(true));
- $group = \OC::$server->getGroupManager()->createGroup('admin');
- if ($group instanceof IGroup) {
- $group->addUser($user);
- }
+ $vendorData = $this->getVendorData();
+ $config->setAppValue('core', 'vendor', $vendorData['vendor']);
+ if ($vendorData['channel'] !== 'stable') {
+ $config->setSystemValue('updater.release.channel', $vendorData['channel']);
+ }
- // Install shipped apps and specified app bundles
- Installer::installShippedApps();
+ $group = Server::get(IGroupManager::class)->createGroup('admin');
+ if ($group instanceof IGroup) {
+ $group->addUser($user);
+ }
- // create empty file in data dir, so we can later find
- // out that this is indeed an ownCloud data directory
- file_put_contents($config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', '');
+ // Install shipped apps and specified app bundles
+ $this->outputDebug($output, 'Install default apps');
+ Installer::installShippedApps(false, $output);
- // Update .htaccess files
- self::updateHtaccess();
- self::protectDataDirectory();
+ // 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', '');
- self::installBackgroundJobs();
+ // Update .htaccess files
+ self::updateHtaccess();
+ self::protectDataDirectory();
- //and we are done
- $config->setSystemValue('installed', true);
- if (self::shouldRemoveCanInstallFile()) {
- unlink(\OC::$configDir.'/CAN_INSTALL');
- }
+ $this->outputDebug($output, 'Install background jobs');
+ self::installBackgroundJobs();
- $bootstrapCoordinator = \OCP\Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
- $bootstrapCoordinator->runInitialRegistration();
+ //and we are done
+ $config->setSystemValue('installed', true);
+ if (self::shouldRemoveCanInstallFile()) {
+ unlink(\OC::$configDir.'/CAN_INSTALL');
+ }
- // Create a session token for the newly created user
- // The token provider requires a working db, so it's not injected on setup
- /* @var $userSession User\Session */
- $userSession = \OC::$server->getUserSession();
- $provider = \OCP\Server::get(PublicKeyTokenProvider::class);
- $userSession->setTokenProvider($provider);
- $userSession->login($username, $password);
- $userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password);
+ $bootstrapCoordinator = \OCP\Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
+ $bootstrapCoordinator->runInitialRegistration();
+
+ // Create a session token for the newly created user
+ // The token provider requires a working db, so it's not injected on setup
+ /** @var \OC\User\Session $userSession */
+ $userSession = Server::get(IUserSession::class);
+ $provider = Server::get(PublicKeyTokenProvider::class);
+ $userSession->setTokenProvider($provider);
+ $userSession->login($username, $password);
+ $user = $userSession->getUser();
+ if (!$user) {
+ $error[] = "No user found in session.";
+ return $error;
+ }
+ $userSession->createSessionToken($request, $user->getUID(), $username, $password);
- $session = $userSession->getSession();
- $session->set('last-password-confirm', \OCP\Server::get(ITimeFactory::class)->getTime());
+ $session = $userSession->getSession();
+ $session->set('last-password-confirm', Server::get(ITimeFactory::class)->getTime());
- // Set email for admin
- if (!empty($options['adminemail'])) {
- $user->setSystemEMailAddress($options['adminemail']);
- }
+ // Set email for admin
+ if (!empty($options['adminemail'])) {
+ $user->setSystemEMailAddress($options['adminemail']);
}
return $error;
}
- public static function installBackgroundJobs() {
- $jobList = \OC::$server->getJobList();
+ public static function installBackgroundJobs(): void {
+ $jobList = Server::get(IJobList::class);
$jobList->add(TokenCleanupJob::class);
$jobList->add(Rotate::class);
$jobList->add(BackgroundCleanupJob::class);
@@ -460,15 +455,13 @@ class Setup {
/**
* @return string Absolute path to htaccess
*/
- private function pathToHtaccess() {
+ private function pathToHtaccess(): string {
return \OC::$SERVERROOT . '/.htaccess';
}
/**
* Find webroot from config
*
- * @param SystemConfig $config
- * @return string
* @throws InvalidArgumentException when invalid value for overwrite.cli.url
*/
private static function findWebRoot(SystemConfig $config): string {
@@ -495,8 +488,8 @@ class Setup {
* @return bool True when success, False otherwise
* @throws \OCP\AppFramework\QueryException
*/
- public static function updateHtaccess() {
- $config = \OC::$server->getSystemConfig();
+ public static function updateHtaccess(): bool {
+ $config = Server::get(SystemConfig::class);
try {
$webRoot = self::findWebRoot($config);
@@ -504,15 +497,11 @@ class Setup {
return false;
}
- $setupHelper = new \OC\Setup(
- $config,
- \OC::$server->get(IniGetWrapper::class),
- \OC::$server->getL10N('lib'),
- \OCP\Server::get(Defaults::class),
- \OC::$server->get(LoggerInterface::class),
- \OC::$server->getSecureRandom(),
- \OCP\Server::get(Installer::class)
- );
+ $setupHelper = Server::get(\OC\Setup::class);
+
+ if (!is_writable($setupHelper->pathToHtaccess())) {
+ return false;
+ }
$htaccessContent = file_get_contents($setupHelper->pathToHtaccess());
$content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n";
@@ -531,7 +520,7 @@ class Setup {
$content .= "\n Options -MultiViews";
$content .= "\n RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]";
$content .= "\n RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]";
- $content .= "\n RewriteCond %{REQUEST_FILENAME} !\\.(css|js|mjs|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav|wasm|tflite)$";
+ $content .= "\n RewriteCond %{REQUEST_FILENAME} !\\.(css|js|mjs|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav|flac|wasm|tflite)$";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/core/ajax/update\\.php";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/core/img/(favicon\\.ico|manifest\\.json)$";
$content .= "\n RewriteCond %{REQUEST_FILENAME} !/(cron|public|remote|status)\\.php";
@@ -551,15 +540,19 @@ class Setup {
$content .= "\n</IfModule>";
}
- if ($content !== '') {
- //suppress errors in case we don't have permissions for it
- return (bool)@file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent . $content . "\n");
+ // 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!");
+ }
}
-
- return false;
+ //suppress errors in case we don't have permissions for it
+ return (bool)@file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent . $content . "\n");
}
- public static function protectDataDirectory() {
+ public static function protectDataDirectory(): void {
//Require all denied
$now = date('Y-m-d H:i:s');
$content = "# Generated by Nextcloud on $now\n";
@@ -587,7 +580,7 @@ class Setup {
$content .= " IndexIgnore *\n";
$content .= "</IfModule>";
- $baseDir = \OC::$server->getConfig()->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
+ $baseDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
file_put_contents($baseDir . '/.htaccess', $content);
file_put_contents($baseDir . '/index.html', '');
}
@@ -603,17 +596,17 @@ class Setup {
];
}
- /**
- * @return bool
- */
- public function shouldRemoveCanInstallFile() {
+ public function shouldRemoveCanInstallFile(): bool {
return \OC_Util::getChannel() !== 'git' && is_file(\OC::$configDir.'/CAN_INSTALL');
}
- /**
- * @return bool
- */
- public function canInstallFileExists() {
+ public function canInstallFileExists(): bool {
return is_file(\OC::$configDir.'/CAN_INSTALL');
}
+
+ protected function outputDebug(?IOutput $output, string $message): void {
+ if ($output instanceof IOutput) {
+ $output->debug($message);
+ }
+ }
}
diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php
index 79f23de8ef8..82c00cb271a 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;
@@ -139,10 +140,12 @@ abstract class AbstractDatabase {
}
$connectionParams['host'] = $host;
}
-
$connectionParams = array_merge($connectionParams, $configOverwrite);
+ $connectionParams = array_merge($connectionParams, ['primary' => $connectionParams, 'replica' => [$connectionParams]]);
$cf = new ConnectionFactory($this->config);
- return $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams);
+ $connection = $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams);
+ $connection->ensureConnectedToPrimary();
+ return $connection;
}
/**
@@ -150,11 +153,11 @@ abstract class AbstractDatabase {
*/
abstract public function setupDatabase($username);
- public function runMigrations() {
+ public function runMigrations(?IOutput $output = null) {
if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) {
return;
}
- $ms = new MigrationService('core', \OC::$server->get(Connection::class));
+ $ms = new MigrationService('core', \OC::$server->get(Connection::class), $output);
$ms->migrate('latest', true);
}
}
diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php
index 50f566728a9..0d672f324eb 100644
--- a/lib/private/Setup/MySQL.php
+++ b/lib/private/Setup/MySQL.php
@@ -29,10 +29,10 @@
*/
namespace OC\Setup;
+use Doctrine\DBAL\Platforms\MySQL80Platform;
use OC\DB\ConnectionAdapter;
use OC\DB\MySqlTools;
use OCP\IDBConnection;
-use Doctrine\DBAL\Platforms\MySQL80Platform;
use OCP\Security\ISecureRandom;
class MySQL extends AbstractDatabase {
diff --git a/lib/private/SetupCheck/SetupCheckManager.php b/lib/private/SetupCheck/SetupCheckManager.php
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/Share/Share.php b/lib/private/Share/Share.php
index 8d14f293e5a..3aa01b3cf29 100644
--- a/lib/private/Share/Share.php
+++ b/lib/private/Share/Share.php
@@ -243,7 +243,7 @@ class Share extends Constants {
* * defacto $parameters and $format is always the default and therefore is removed in the subsequent call
*/
public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
- $parameters = null, $includeCollections = false) {
+ $parameters = null, $includeCollections = false) {
return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE,
null, -1, $includeCollections);
}
@@ -349,8 +349,8 @@ class Share extends Constants {
* * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call
*/
public static function getItems($itemType, ?string $item = null, ?int $shareType = null, $shareWith = null,
- $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
- $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
+ $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
+ $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
return [];
}
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 4606101b7e6..3af74789602 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -548,6 +548,11 @@ class Manager implements IManager {
$this->groupManager->getUserGroupIds($sharedBy),
$this->groupManager->getUserGroupIds($sharedWith)
);
+
+ // optional excluded groups
+ $excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
+ $groups = array_diff($groups, $excludedGroups);
+
if (empty($groups)) {
$message_t = $this->l->t('Sharing is only allowed with group members');
throw new \Exception($message_t);
@@ -608,7 +613,10 @@ class Manager implements IManager {
if ($this->shareWithGroupMembersOnly()) {
$sharedBy = $this->userManager->get($share->getSharedBy());
$sharedWith = $this->groupManager->get($share->getSharedWith());
- if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
+
+ // optional excluded groups
+ $excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
+ if (is_null($sharedWith) || in_array($share->getSharedWith(), $excludedGroups) || !$sharedWith->inGroup($sharedBy)) {
throw new \Exception('Sharing is only allowed within your own groups');
}
}
@@ -880,12 +888,12 @@ class Manager implements IManager {
* @param \DateTime|null $expiration
*/
protected function sendMailNotification(IL10N $l,
- $filename,
- $link,
- $initiator,
- $shareWith,
- \DateTime $expiration = null,
- $note = '') {
+ $filename,
+ $link,
+ $initiator,
+ $shareWith,
+ \DateTime $expiration = null,
+ $note = '') {
$initiatorUser = $this->userManager->get($initiator);
$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
@@ -1573,14 +1581,8 @@ class Manager implements IManager {
* @return bool
*/
public function checkPassword(IShare $share, $password) {
- $passwordProtected = $share->getShareType() !== IShare::TYPE_LINK
- || $share->getShareType() !== IShare::TYPE_EMAIL
- || $share->getShareType() !== IShare::TYPE_CIRCLE;
- if (!$passwordProtected) {
- //TODO maybe exception?
- return false;
- }
+ // if there is no password on the share object / passsword is null, there is nothing to check
if ($password === null || $share->getPassword() === null) {
return false;
}
@@ -1945,6 +1947,21 @@ class Manager implements IManager {
}
/**
+ * If shareWithGroupMembersOnly is enabled, return an optional
+ * list of groups that must be excluded from the principle of
+ * belonging to the same group.
+ *
+ * @return array
+ */
+ public function shareWithGroupMembersOnlyExcludeGroupsList() {
+ if (!$this->shareWithGroupMembersOnly()) {
+ return [];
+ }
+ $excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
+ return json_decode($excludeGroups, true) ?? [];
+ }
+
+ /**
* Check if users can share with groups
*
* @return bool
diff --git a/lib/private/Share20/PublicShareTemplateFactory.php b/lib/private/Share20/PublicShareTemplateFactory.php
index 222f327496a..0e9642c306e 100644
--- a/lib/private/Share20/PublicShareTemplateFactory.php
+++ b/lib/private/Share20/PublicShareTemplateFactory.php
@@ -27,9 +27,9 @@ use Exception;
use OC\AppFramework\Bootstrap\Coordinator;
use OCA\Files_Sharing\DefaultPublicShareTemplateProvider;
use OCP\Server;
-use OCP\Share\IShare;
use OCP\Share\IPublicShareTemplateFactory;
use OCP\Share\IPublicShareTemplateProvider;
+use OCP\Share\IShare;
class PublicShareTemplateFactory implements IPublicShareTemplateFactory {
public function __construct(
diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php
index 0a50fa0ccfb..c80d332e9db 100644
--- a/lib/private/Share20/Share.php
+++ b/lib/private/Share20/Share.php
@@ -29,8 +29,8 @@
*/
namespace OC\Share20;
-use OCP\Files\File;
use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
diff --git a/lib/private/SpeechToText/SpeechToTextManager.php b/lib/private/SpeechToText/SpeechToTextManager.php
index bdd04ad3651..520700cf198 100644
--- a/lib/private/SpeechToText/SpeechToTextManager.php
+++ b/lib/private/SpeechToText/SpeechToTextManager.php
@@ -39,6 +39,7 @@ use OCP\IServerContainer;
use OCP\PreConditionNotMetException;
use OCP\SpeechToText\ISpeechToTextManager;
use OCP\SpeechToText\ISpeechToTextProvider;
+use OCP\SpeechToText\ISpeechToTextProviderWithId;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
@@ -117,8 +118,13 @@ class SpeechToTextManager implements ISpeechToTextManager {
$json = $this->config->getAppValue('core', 'ai.stt_provider', '');
if ($json !== '') {
- $className = json_decode($json, true);
- $provider = current(array_filter($providers, fn ($provider) => $provider::class === $className));
+ $classNameOrId = json_decode($json, true);
+ $provider = current(array_filter($providers, function ($provider) use ($classNameOrId) {
+ if ($provider instanceof ISpeechToTextProviderWithId) {
+ return $provider->getId() === $classNameOrId;
+ }
+ return $provider::class === $classNameOrId;
+ }));
if ($provider !== false) {
$providers = [$provider];
}
diff --git a/lib/private/StreamImage.php b/lib/private/StreamImage.php
index 33078310d27..e2f854bc233 100644
--- a/lib/private/StreamImage.php
+++ b/lib/private/StreamImage.php
@@ -23,8 +23,8 @@
namespace OC;
-use OCP\IStreamImage;
use OCP\IImage;
+use OCP\IStreamImage;
/**
* Only useful when dealing with transferring streamed previews from an external
diff --git a/lib/private/SubAdmin.php b/lib/private/SubAdmin.php
index 54f14b8ab88..9f079d30e04 100644
--- a/lib/private/SubAdmin.php
+++ b/lib/private/SubAdmin.php
@@ -60,9 +60,9 @@ class SubAdmin extends PublicEmitter implements ISubAdmin {
* @param IDBConnection $dbConn
*/
public function __construct(IUserManager $userManager,
- IGroupManager $groupManager,
- IDBConnection $dbConn,
- IEventDispatcher $eventDispatcher) {
+ IGroupManager $groupManager,
+ IDBConnection $dbConn,
+ IEventDispatcher $eventDispatcher) {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->dbConn = $dbConn;
diff --git a/lib/private/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php
index eba76ca103e..008cded5dd3 100644
--- a/lib/private/Support/Subscription/Registry.php
+++ b/lib/private/Support/Subscription/Registry.php
@@ -62,10 +62,10 @@ class Registry implements IRegistry {
private $logger;
public function __construct(IConfig $config,
- IServerContainer $container,
- IUserManager $userManager,
- IGroupManager $groupManager,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ IUserManager $userManager,
+ IGroupManager $groupManager,
+ LoggerInterface $logger) {
$this->config = $config;
$this->container = $container;
$this->userManager = $userManager;
diff --git a/lib/private/SystemConfig.php b/lib/private/SystemConfig.php
index c104f001809..f63663fbfe3 100644
--- a/lib/private/SystemConfig.php
+++ b/lib/private/SystemConfig.php
@@ -55,6 +55,7 @@ class SystemConfig {
'secret' => true,
'updater.secret' => true,
'trusted_proxies' => true,
+ 'preview_imaginary_url' => true,
'proxyuserpwd' => true,
'sentry.dsn' => true,
'sentry.public-dsn' => true,
@@ -123,11 +124,9 @@ class SystemConfig {
],
];
- /** @var Config */
- private $config;
-
- public function __construct(Config $config) {
- $this->config = $config;
+ public function __construct(
+ private Config $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/TagManager.php b/lib/private/TagManager.php
index 82c4dd2188d..552113f89dc 100644
--- a/lib/private/TagManager.php
+++ b/lib/private/TagManager.php
@@ -27,6 +27,7 @@
namespace OC;
use OC\Tagging\TagMapper;
+use OCP\Db\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
@@ -35,7 +36,6 @@ use OCP\ITagManager;
use OCP\ITags;
use OCP\IUserSession;
use OCP\User\Events\UserDeletedEvent;
-use OCP\Db\Exception as DBException;
use Psr\Log\LoggerInterface;
/**
diff --git a/lib/private/Tagging/TagMapper.php b/lib/private/Tagging/TagMapper.php
index 1ee9c33acf7..ab227de5f7f 100644
--- a/lib/private/Tagging/TagMapper.php
+++ b/lib/private/Tagging/TagMapper.php
@@ -27,8 +27,8 @@ namespace OC\Tagging;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
-use OCP\IDBConnection;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
/**
* Mapper for Tag entity
diff --git a/lib/private/Talk/Broker.php b/lib/private/Talk/Broker.php
index 12e6c5fb34b..451e7822790 100644
--- a/lib/private/Talk/Broker.php
+++ b/lib/private/Talk/Broker.php
@@ -48,8 +48,8 @@ class Broker implements IBroker {
private ?ITalkBackend $backend = null;
public function __construct(Coordinator $coordinator,
- IServerContainer $container,
- LoggerInterface $logger) {
+ IServerContainer $container,
+ LoggerInterface $logger) {
$this->coordinator = $coordinator;
$this->container = $container;
$this->logger = $logger;
@@ -94,8 +94,8 @@ class Broker implements IBroker {
}
public function createConversation(string $name,
- array $moderators,
- IConversationOptions $options = null): IConversation {
+ array $moderators,
+ IConversationOptions $options = null): IConversation {
if (!$this->hasBackend()) {
throw new NoBackendException("The Talk broker has no registered backend");
}
diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php
index b87829360d5..fad69a76ae0 100644
--- a/lib/private/Template/JSCombiner.php
+++ b/lib/private/Template/JSCombiner.php
@@ -55,10 +55,10 @@ class JSCombiner {
private $cacheFactory;
public function __construct(IAppData $appData,
- IURLGenerator $urlGenerator,
- ICacheFactory $cacheFactory,
- SystemConfig $config,
- LoggerInterface $logger) {
+ IURLGenerator $urlGenerator,
+ ICacheFactory $cacheFactory,
+ SystemConfig $config,
+ LoggerInterface $logger) {
$this->appData = $appData;
$this->urlGenerator = $urlGenerator;
$this->cacheFactory = $cacheFactory;
diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php
index 7b6d0a6a346..8cba93f1f4e 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -45,9 +45,9 @@ use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IInitialStateService;
use OCP\IL10N;
+use OCP\ILogger;
use OCP\ISession;
use OCP\IURLGenerator;
-use OCP\ILogger;
use OCP\IUser;
use OCP\User\Backend\IPasswordConfirmationBackend;
use OCP\Util;
@@ -69,16 +69,16 @@ class JSConfigHelper {
private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true];
public function __construct(IL10N $l,
- Defaults $defaults,
- IAppManager $appManager,
- ISession $session,
- ?IUser $currentUser,
- IConfig $config,
- IGroupManager $groupManager,
- IniGetWrapper $iniWrapper,
- IURLGenerator $urlGenerator,
- CapabilitiesManager $capabilitiesManager,
- IInitialStateService $initialStateService) {
+ Defaults $defaults,
+ IAppManager $appManager,
+ ISession $session,
+ ?IUser $currentUser,
+ IConfig $config,
+ IGroupManager $groupManager,
+ IniGetWrapper $iniWrapper,
+ IURLGenerator $urlGenerator,
+ CapabilitiesManager $capabilitiesManager,
+ IInitialStateService $initialStateService) {
$this->l = $l;
$this->defaults = $defaults;
$this->appManager = $appManager;
@@ -179,7 +179,8 @@ class JSConfigHelper {
'sharing.maxAutocompleteResults' => max(0, $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT)),
'sharing.minSearchStringLength' => $this->config->getSystemValueInt('sharing.minSearchStringLength', 0),
'version' => implode('.', Util::getVersion()),
- 'versionstring' => \OC_Util::getVersionString()
+ 'versionstring' => \OC_Util::getVersionString(),
+ 'enable_non-accessible_features' => $this->config->getSystemValueBool('enable_non-accessible_features', true),
];
$array = [
diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php
index b283f0b610f..68a83fa4b73 100644
--- a/lib/private/Template/JSResourceLocator.php
+++ b/lib/private/Template/JSResourceLocator.php
@@ -60,8 +60,13 @@ class JSResourceLocator extends ResourceLocator {
$found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'core/'.$script);
$found += $this->appendScriptIfExist($this->serverroot, $script);
$found += $this->appendScriptIfExist($this->serverroot, $theme_dir.$script);
- $found += $this->appendScriptIfExist($this->serverroot, 'apps/'.$script);
- $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'apps/'.$script);
+
+ foreach (\OC::$APPSROOTS as $appRoot) {
+ $dirName = basename($appRoot['path']);
+ $rootPath = dirname($appRoot['path']);
+ $found += $this->appendScriptIfExist($rootPath, $dirName.'/'.$script);
+ $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.$dirName.'/'.$script);
+ }
if ($found) {
return;
diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php
index e2504363257..96d0ae3e517 100644
--- a/lib/private/TemplateLayout.php
+++ b/lib/private/TemplateLayout.php
@@ -47,11 +47,13 @@ use OC\Search\SearchQuery;
use OC\Template\CSSResourceLocator;
use OC\Template\JSConfigHelper;
use OC\Template\JSResourceLocator;
+use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\INavigationManager;
+use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Support\Subscription\IRegistry;
use OCP\Util;
@@ -106,11 +108,15 @@ class TemplateLayout extends \OC_Template {
$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
$this->initialState->provideInitialState('core', 'apps', $this->navigationManager->getAll());
- $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
- $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
- $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
- Util::addScript('core', 'unified-search', 'core');
+ if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
+ $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
+ $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
+ $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
+ Util::addScript('core', 'legacy-unified-search', 'core');
+ } else {
+ Util::addScript('core', 'unified-search', 'core');
+ }
// Set body data-theme
$this->assign('enabledThemes', []);
if (\OC::$server->getAppManager()->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
@@ -199,7 +205,21 @@ class TemplateLayout extends \OC_Template {
if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) {
$showSimpleSignup = false;
}
+
+ $defaultSignUpLink = 'https://nextcloud.com/signup/';
+ $signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink);
+ if ($signUpLink !== $defaultSignUpLink) {
+ $showSimpleSignup = true;
+ }
+
+ $appManager = \OCP\Server::get(IAppManager::class);
+ if ($appManager->isEnabledForUser('registration')) {
+ $urlGenerator = \OCP\Server::get(IURLGenerator::class);
+ $signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/');
+ }
+
$this->assign('showSimpleSignUpLink', $showSimpleSignup);
+ $this->assign('signUpLink', $signUpLink);
} else {
parent::__construct('core', 'layout.base');
}
diff --git a/lib/private/TextProcessing/Db/Task.php b/lib/private/TextProcessing/Db/Task.php
index 9c6f16d11ae..5f362d429f3 100644
--- a/lib/private/TextProcessing/Db/Task.php
+++ b/lib/private/TextProcessing/Db/Task.php
@@ -45,6 +45,8 @@ use OCP\TextProcessing\Task as OCPTask;
* @method string getAppId()
* @method setIdentifier(string $identifier)
* @method string getIdentifier()
+ * @method setCompletionExpectedAt(null|\DateTime $completionExpectedAt)
+ * @method null|\DateTime getCompletionExpectedAt()
*/
class Task extends Entity {
protected $lastUpdated;
@@ -55,16 +57,17 @@ class Task extends Entity {
protected $userId;
protected $appId;
protected $identifier;
+ protected $completionExpectedAt;
/**
* @var string[]
*/
- public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier'];
+ public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier', 'completion_expected_at'];
/**
* @var string[]
*/
- public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier'];
+ public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier', 'completionExpectedAt'];
public function __construct() {
@@ -78,6 +81,7 @@ class Task extends Entity {
$this->addType('userId', 'string');
$this->addType('appId', 'string');
$this->addType('identifier', 'string');
+ $this->addType('completionExpectedAt', 'datetime');
}
public function toRow(): array {
@@ -98,6 +102,7 @@ class Task extends Entity {
'userId' => $task->getUserId(),
'appId' => $task->getAppId(),
'identifier' => $task->getIdentifier(),
+ 'completionExpectedAt' => $task->getCompletionExpectedAt(),
]);
return $task;
}
@@ -107,6 +112,7 @@ class Task extends Entity {
$task->setId($this->getId());
$task->setStatus($this->getStatus());
$task->setOutput($this->getOutput());
+ $task->setCompletionExpectedAt($this->getCompletionExpectedAt());
return $task;
}
}
diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php
index b9cb06c298e..34f0b4e7964 100644
--- a/lib/private/TextProcessing/Manager.php
+++ b/lib/private/TextProcessing/Manager.php
@@ -27,19 +27,22 @@ namespace OC\TextProcessing;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\TextProcessing\Db\Task as DbTask;
-use OCP\IConfig;
-use OCP\TextProcessing\Task;
-use OCP\TextProcessing\Task as OCPTask;
use OC\TextProcessing\Db\TaskMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\BackgroundJob\IJobList;
use OCP\Common\Exception\NotFoundException;
use OCP\DB\Exception;
+use OCP\IConfig;
use OCP\IServerContainer;
+use OCP\PreConditionNotMetException;
+use OCP\TextProcessing\Exception\TaskFailureException;
use OCP\TextProcessing\IManager;
use OCP\TextProcessing\IProvider;
-use OCP\PreConditionNotMetException;
+use OCP\TextProcessing\IProviderWithExpectedRuntime;
+use OCP\TextProcessing\IProviderWithId;
+use OCP\TextProcessing\Task;
+use OCP\TextProcessing\Task as OCPTask;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Throwable;
@@ -114,26 +117,16 @@ class Manager implements IManager {
if (!$this->canHandleTask($task)) {
throw new PreConditionNotMetException('No text processing provider is installed that can handle this task');
}
- $providers = $this->getProviders();
- $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', '');
- if ($json !== '') {
- $preferences = json_decode($json, true);
- if (isset($preferences[$task->getType()])) {
- // If a preference for this task type is set, move the preferred provider to the start
- $provider = current(array_filter($providers, 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 +138,37 @@ class Manager implements IManager {
$task->setStatus(OCPTask::STATUS_SUCCESSFUL);
$this->taskMapper->update(DbTask::fromPublicTask($task));
return $output;
- } catch (\RuntimeException $e) {
- $this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]);
- $task->setStatus(OCPTask::STATUS_FAILED);
- $this->taskMapper->update(DbTask::fromPublicTask($task));
- throw $e;
} catch (\Throwable $e) {
$this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]);
$task->setStatus(OCPTask::STATUS_FAILED);
$this->taskMapper->update(DbTask::fromPublicTask($task));
- throw new RuntimeException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e);
+ throw new TaskFailureException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e);
}
}
- throw new RuntimeException('Could not run task');
+ $task->setStatus(OCPTask::STATUS_FAILED);
+ $this->taskMapper->update(DbTask::fromPublicTask($task));
+ throw new TaskFailureException('Could not run task');
}
/**
* @inheritDoc
- * @throws Exception
*/
public function scheduleTask(OCPTask $task): void {
if (!$this->canHandleTask($task)) {
throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task');
}
$task->setStatus(OCPTask::STATUS_SCHEDULED);
+ $providers = $this->getPreferredProviders($task);
+ if (count($providers) === 0) {
+ throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task');
+ }
+ [$provider,] = $providers;
+ if ($provider instanceof IProviderWithExpectedRuntime) {
+ $completionExpectedAt = new \DateTime('now');
+ $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S'));
+ $task->setCompletionExpectedAt($completionExpectedAt);
+ }
$taskEntity = DbTask::fromPublicTask($task);
$this->taskMapper->insert($taskEntity);
$task->setId($taskEntity->getId());
@@ -181,6 +180,25 @@ class Manager implements IManager {
/**
* @inheritDoc
*/
+ public function runOrScheduleTask(OCPTask $task): bool {
+ if (!$this->canHandleTask($task)) {
+ throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task');
+ }
+ [$provider,] = $this->getPreferredProviders($task);
+ $maxExecutionTime = (int) ini_get('max_execution_time');
+ // Offload the task to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time
+ // or if the provider doesn't provide a getExpectedRuntime() method
+ if (!$provider instanceof IProviderWithExpectedRuntime || $provider->getExpectedRuntime() > $maxExecutionTime * 0.8) {
+ $this->scheduleTask($task);
+ return false;
+ }
+ $this->runTask($task);
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ */
public function deleteTask(Task $task): void {
$taskEntity = DbTask::fromPublicTask($task);
$this->taskMapper->delete($taskEntity);
@@ -253,4 +271,31 @@ class Manager implements IManager {
throw new RuntimeException('Failure while trying to find tasks by appId and identifier: ' . $e->getMessage(), 0, $e);
}
}
+
+ /**
+ * @param OCPTask $task
+ * @return IProvider[]
+ */
+ public function getPreferredProviders(OCPTask $task): array {
+ $providers = $this->getProviders();
+ $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', '');
+ if ($json !== '') {
+ $preferences = json_decode($json, true);
+ if (isset($preferences[$task->getType()])) {
+ // If a preference for this task type is set, move the preferred provider to the start
+ $provider = current(array_values(array_filter($providers, function ($provider) use ($preferences, $task) {
+ if ($provider instanceof IProviderWithId) {
+ return $provider->getId() === $preferences[$task->getType()];
+ }
+ $provider::class === $preferences[$task->getType()];
+ })));
+ if ($provider !== false) {
+ $providers = array_filter($providers, fn ($p) => $p !== $provider);
+ array_unshift($providers, $provider);
+ }
+ }
+ }
+ $providers = array_values(array_filter($providers, fn (IProvider $provider) => $task->canUseProvider($provider)));
+ return $providers;
+ }
}
diff --git a/lib/private/TextToImage/Db/Task.php b/lib/private/TextToImage/Db/Task.php
new file mode 100644
index 00000000000..96dd6e4e165
--- /dev/null
+++ b/lib/private/TextToImage/Db/Task.php
@@ -0,0 +1,117 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\TextToImage\Db;
+
+use DateTime;
+use OCP\AppFramework\Db\Entity;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\TextToImage\Task as OCPTask;
+
+/**
+ * @method setLastUpdated(DateTime $lastUpdated)
+ * @method DateTime getLastUpdated()
+ * @method setInput(string $type)
+ * @method string getInput()
+ * @method setResultPath(string $resultPath)
+ * @method string getResultPath()
+ * @method setStatus(int $type)
+ * @method int getStatus()
+ * @method setUserId(?string $userId)
+ * @method string|null getUserId()
+ * @method setAppId(string $type)
+ * @method string getAppId()
+ * @method setIdentifier(string $identifier)
+ * @method string|null getIdentifier()
+ * @method setNumberOfImages(int $numberOfImages)
+ * @method int getNumberOfImages()
+ * @method setCompletionExpectedAt(DateTime $at)
+ * @method DateTime getCompletionExpectedAt()
+ */
+class Task extends Entity {
+ protected $lastUpdated;
+ protected $type;
+ protected $input;
+ protected $status;
+ protected $userId;
+ protected $appId;
+ protected $identifier;
+ protected $numberOfImages;
+ protected $completionExpectedAt;
+
+ /**
+ * @var string[]
+ */
+ public static array $columns = ['id', 'last_updated', 'input', 'status', 'user_id', 'app_id', 'identifier', 'number_of_images', 'completion_expected_at'];
+
+ /**
+ * @var string[]
+ */
+ public static array $fields = ['id', 'lastUpdated', 'input', 'status', 'userId', 'appId', 'identifier', 'numberOfImages', 'completionExpectedAt'];
+
+
+ public function __construct() {
+ // add types in constructor
+ $this->addType('id', 'integer');
+ $this->addType('lastUpdated', 'datetime');
+ $this->addType('input', 'string');
+ $this->addType('status', 'integer');
+ $this->addType('userId', 'string');
+ $this->addType('appId', 'string');
+ $this->addType('identifier', 'string');
+ $this->addType('numberOfImages', 'integer');
+ $this->addType('completionExpectedAt', 'datetime');
+ }
+
+ public function toRow(): array {
+ return array_combine(self::$columns, array_map(function ($field) {
+ return $this->{'get'.ucfirst($field)}();
+ }, self::$fields));
+ }
+
+ public static function fromPublicTask(OCPTask $task): Task {
+ /** @var Task $dbTask */
+ $dbTask = Task::fromParams([
+ 'id' => $task->getId(),
+ 'lastUpdated' => \OCP\Server::get(ITimeFactory::class)->getDateTime(),
+ 'status' => $task->getStatus(),
+ 'numberOfImages' => $task->getNumberOfImages(),
+ 'input' => $task->getInput(),
+ 'userId' => $task->getUserId(),
+ 'appId' => $task->getAppId(),
+ 'identifier' => $task->getIdentifier(),
+ 'completionExpectedAt' => $task->getCompletionExpectedAt(),
+ ]);
+ return $dbTask;
+ }
+
+ public function toPublicTask(): OCPTask {
+ $task = new OCPTask($this->getInput(), $this->getAppId(), $this->getNumberOfImages(), $this->getuserId(), $this->getIdentifier());
+ $task->setId($this->getId());
+ $task->setStatus($this->getStatus());
+ $task->setCompletionExpectedAt($this->getCompletionExpectedAt());
+ return $task;
+ }
+}
diff --git a/lib/private/TextToImage/Db/TaskMapper.php b/lib/private/TextToImage/Db/TaskMapper.php
new file mode 100644
index 00000000000..68fdd8f40de
--- /dev/null
+++ b/lib/private/TextToImage/Db/TaskMapper.php
@@ -0,0 +1,127 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\TextToImage\Db;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\Entity;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\DB\Exception;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * @extends QBMapper<Task>
+ */
+class TaskMapper extends QBMapper {
+ public function __construct(
+ IDBConnection $db,
+ private ITimeFactory $timeFactory,
+ ) {
+ parent::__construct($db, 'text2image_tasks', Task::class);
+ }
+
+ /**
+ * @param int $id
+ * @return Task
+ * @throws Exception
+ * @throws DoesNotExistException
+ * @throws MultipleObjectsReturnedException
+ */
+ public function find(int $id): Task {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @param int $id
+ * @param string|null $userId
+ * @return Task
+ * @throws DoesNotExistException
+ * @throws Exception
+ * @throws MultipleObjectsReturnedException
+ */
+ public function findByIdAndUser(int $id, ?string $userId): Task {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
+ if ($userId === null) {
+ $qb->andWhere($qb->expr()->isNull('user_id'));
+ } else {
+ $qb->andWhere($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
+ }
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @param string $userId
+ * @param string $appId
+ * @param string|null $identifier
+ * @return array
+ * @throws Exception
+ */
+ public function findUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
+ ->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId)));
+ if ($identifier !== null) {
+ $qb->andWhere($qb->expr()->eq('identifier', $qb->createPositionalParameter($identifier)));
+ }
+ return $this->findEntities($qb);
+ }
+
+ /**
+ * @param int $timeout
+ * @return Task[] the deleted tasks
+ * @throws Exception
+ */
+ public function deleteOlderThan(int $timeout): array {
+ $datetime = $this->timeFactory->getDateTime();
+ $datetime->sub(new \DateInterval('PT'.$timeout.'S'));
+ $qb = $this->db->getQueryBuilder();
+ $qb->select('*')
+ ->from($this->tableName)
+ ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE)));
+ $deletedTasks = $this->findEntities($qb);
+ $qb = $this->db->getQueryBuilder();
+ $qb->delete($this->tableName)
+ ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE)));
+ $qb->executeStatement();
+ return $deletedTasks;
+ }
+
+ public function update(Entity $entity): Entity {
+ $entity->setLastUpdated($this->timeFactory->getDateTime());
+ return parent::update($entity);
+ }
+}
diff --git a/lib/private/TextToImage/Manager.php b/lib/private/TextToImage/Manager.php
new file mode 100644
index 00000000000..b549f386b6a
--- /dev/null
+++ b/lib/private/TextToImage/Manager.php
@@ -0,0 +1,341 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\TextToImage;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\TextToImage\Db\Task as DbTask;
+use OC\TextToImage\Db\TaskMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\BackgroundJob\IJobList;
+use OCP\DB\Exception;
+use OCP\Files\AppData\IAppDataFactory;
+use OCP\Files\IAppData;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\IConfig;
+use OCP\IServerContainer;
+use OCP\PreConditionNotMetException;
+use OCP\TextToImage\Exception\TaskFailureException;
+use OCP\TextToImage\Exception\TaskNotFoundException;
+use OCP\TextToImage\IManager;
+use OCP\TextToImage\IProvider;
+use OCP\TextToImage\IProviderWithUserId;
+use OCP\TextToImage\Task;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use Throwable;
+
+class Manager implements IManager {
+ /** @var ?list<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');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ 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 {
+ /** @var IProvider $provider */
+ $provider = $this->serverContainer->get($class);
+ $this->providers[] = $provider;
+ } 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');
+ if ($provider instanceof IProviderWithUserId) {
+ $provider->setUserId($task->getUserId());
+ }
+ $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 list<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/Translation/TranslationManager.php b/lib/private/Translation/TranslationManager.php
index 48a0e2cdebd..306275121ea 100644
--- a/lib/private/Translation/TranslationManager.php
+++ b/lib/private/Translation/TranslationManager.php
@@ -30,11 +30,14 @@ use InvalidArgumentException;
use OC\AppFramework\Bootstrap\Coordinator;
use OCP\IConfig;
use OCP\IServerContainer;
+use OCP\IUserSession;
use OCP\PreConditionNotMetException;
use OCP\Translation\CouldNotTranslateException;
use OCP\Translation\IDetectLanguageProvider;
use OCP\Translation\ITranslationManager;
use OCP\Translation\ITranslationProvider;
+use OCP\Translation\ITranslationProviderWithId;
+use OCP\Translation\ITranslationProviderWithUserId;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
@@ -50,6 +53,7 @@ class TranslationManager implements ITranslationManager {
private Coordinator $coordinator,
private LoggerInterface $logger,
private IConfig $config,
+ private IUserSession $userSession,
) {
}
@@ -73,19 +77,26 @@ class TranslationManager implements ITranslationManager {
$precedence = json_decode($json, true);
$newProviders = [];
foreach ($precedence as $className) {
- $provider = current(array_filter($providers, fn ($provider) => $provider::class === $className));
+ $provider = current(array_filter($providers, function ($provider) use ($className) {
+ return $provider instanceof ITranslationProviderWithId ? $provider->getId() === $className : $provider::class === $className;
+ }));
if ($provider !== false) {
$newProviders[] = $provider;
}
}
// Add all providers that haven't been added so far
- $newProviders += array_udiff($providers, $newProviders, fn ($a, $b) => $a::class > $b::class ? 1 : ($a::class < $b::class ? -1 : 0));
+ $newProviders += array_udiff($providers, $newProviders, function ($a, $b) {
+ return ($a instanceof ITranslationProviderWithId ? $a->getId() : $a::class) <=> ($b instanceof ITranslationProviderWithId ? $b->getId() : $b::class);
+ });
$providers = $newProviders;
}
if ($fromLanguage === null) {
foreach ($providers as $provider) {
if ($provider instanceof IDetectLanguageProvider) {
+ if ($provider instanceof ITranslationProviderWithUserId) {
+ $provider->setUserId($this->userSession->getUser()?->getUID());
+ }
$fromLanguage = $provider->detectLanguage($text);
}
@@ -105,6 +116,9 @@ class TranslationManager implements ITranslationManager {
foreach ($providers as $provider) {
try {
+ if ($provider instanceof ITranslationProviderWithUserId) {
+ $provider->setUserId($this->userSession->getUser()?->getUID());
+ }
return $provider->translate($fromLanguage, $toLanguage, $text);
} catch (RuntimeException $e) {
$this->logger->warning("Failed to translate from {$fromLanguage} to {$toLanguage} using provider {$provider->getName()}", ['exception' => $e]);
diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php
index 57bafc3e18d..3d384de5842 100644
--- a/lib/private/URLGenerator.php
+++ b/lib/private/URLGenerator.php
@@ -70,10 +70,10 @@ class URLGenerator implements IURLGenerator {
private ?IAppManager $appManager = null;
public function __construct(IConfig $config,
- IUserSession $userSession,
- ICacheFactory $cacheFactory,
- IRequest $request,
- Router $router
+ IUserSession $userSession,
+ ICacheFactory $cacheFactory,
+ IRequest $request,
+ Router $router
) {
$this->config = $config;
$this->userSession = $userSession;
@@ -116,16 +116,25 @@ class URLGenerator implements IURLGenerator {
}
public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string {
+ // Returns `/subfolder/index.php/ocsapp/…` with `'htaccess.IgnoreFrontController' => false` in config.php
+ // And `/subfolder/ocsapp/…` with `'htaccess.IgnoreFrontController' => true` in config.php
$route = $this->router->generate('ocs.'.$routeName, $arguments, false);
- $indexPhpPos = strpos($route, '/index.php/');
- if ($indexPhpPos !== false) {
- $route = substr($route, $indexPhpPos + 10);
+ // Cut off `/subfolder`
+ if (\OC::$WEBROOT !== '' && str_starts_with($route, \OC::$WEBROOT)) {
+ $route = substr($route, \strlen(\OC::$WEBROOT));
}
+ if (str_starts_with($route, '/index.php/')) {
+ $route = substr($route, 10);
+ }
+
+ // Remove `ocsapp/` bit
$route = substr($route, 7);
+ // Prefix with ocs/v2.php endpoint
$route = '/ocs/v2.php' . $route;
+ // Turn into an absolute URL
return $this->getAbsoluteURL($route);
}
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 5a14bb17507..018e4797232 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -40,13 +40,6 @@ declare(strict_types=1);
*/
namespace OC;
-use OCP\App\IAppManager;
-use OCP\EventDispatcher\Event;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\HintException;
-use OCP\IConfig;
-use OCP\ILogger;
-use OCP\Util;
use OC\App\AppManager;
use OC\DB\Connection;
use OC\DB\MigrationService;
@@ -61,6 +54,13 @@ use OC\Repair\Events\RepairStartEvent;
use OC\Repair\Events\RepairStepEvent;
use OC\Repair\Events\RepairWarningEvent;
use OC_App;
+use OCP\App\IAppManager;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\HintException;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\Util;
use Psr\Log\LoggerInterface;
/**
@@ -94,9 +94,9 @@ class Updater extends BasicEmitter {
];
public function __construct(IConfig $config,
- Checker $checker,
- ?LoggerInterface $log,
- Installer $installer) {
+ Checker $checker,
+ ?LoggerInterface $log,
+ Installer $installer) {
$this->log = $log;
$this->config = $config;
$this->checker = $checker;
diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php
index 97f770b6998..e37024ec2c2 100644
--- a/lib/private/Updater/VersionCheck.php
+++ b/lib/private/Updater/VersionCheck.php
@@ -127,7 +127,9 @@ class VersionCheck {
*/
protected function getUrlContent($url) {
$client = $this->clientService->newClient();
- $response = $client->get($url);
+ $response = $client->get($url, [
+ 'timeout' => 5,
+ ]);
return $response->getBody();
}
diff --git a/lib/private/User/AvailabilityCoordinator.php b/lib/private/User/AvailabilityCoordinator.php
new file mode 100644
index 00000000000..c32c3005c32
--- /dev/null
+++ b/lib/private/User/AvailabilityCoordinator.php
@@ -0,0 +1,139 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Richard Steinmetz <richard@steinmetz.cloud>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\User;
+
+use JsonException;
+use OCA\DAV\AppInfo\Application;
+use OCA\DAV\CalDAV\TimezoneService;
+use OCA\DAV\Service\AbsenceService;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\User\IAvailabilityCoordinator;
+use OCP\User\IOutOfOfficeData;
+use Psr\Log\LoggerInterface;
+
+class AvailabilityCoordinator implements IAvailabilityCoordinator {
+ private ICache $cache;
+
+ public function __construct(
+ ICacheFactory $cacheFactory,
+ private IConfig $config,
+ private AbsenceService $absenceService,
+ private LoggerInterface $logger,
+ private TimezoneService $timezoneService,
+ ) {
+ $this->cache = $cacheFactory->createLocal('OutOfOfficeData');
+ }
+
+ public function isEnabled(): bool {
+ return $this->config->getAppValue(Application::APP_ID, 'hide_absence_settings', 'no') === 'no';
+ }
+
+ private function getCachedOutOfOfficeData(IUser $user): ?OutOfOfficeData {
+ $cachedString = $this->cache->get($user->getUID());
+ if ($cachedString === null) {
+ return null;
+ }
+
+ try {
+ $cachedData = json_decode($cachedString, true, 10, JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ $this->logger->error('Failed to deserialize cached out-of-office data: ' . $e->getMessage(), [
+ 'exception' => $e,
+ 'json' => $cachedString,
+ ]);
+ return null;
+ }
+
+ return new OutOfOfficeData(
+ $cachedData['id'],
+ $user,
+ $cachedData['startDate'],
+ $cachedData['endDate'],
+ $cachedData['shortMessage'],
+ $cachedData['message'],
+ );
+ }
+
+ private function setCachedOutOfOfficeData(IOutOfOfficeData $data): void {
+ try {
+ $cachedString = json_encode([
+ 'id' => $data->getId(),
+ 'startDate' => $data->getStartDate(),
+ 'endDate' => $data->getEndDate(),
+ 'shortMessage' => $data->getShortMessage(),
+ 'message' => $data->getMessage(),
+ ], JSON_THROW_ON_ERROR);
+ } catch (JsonException $e) {
+ $this->logger->error('Failed to serialize out-of-office data: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ return;
+ }
+
+ $this->cache->set($data->getUser()->getUID(), $cachedString, 300);
+ }
+
+ public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData {
+ $timezone = $this->getCachedTimezone($user->getUID());
+ if ($timezone === null) {
+ $timezone = $this->timezoneService->getUserTimezone($user->getUID()) ?? $this->timezoneService->getDefaultTimezone();
+ $this->setCachedTimezone($user->getUID(), $timezone);
+ }
+
+ $data = $this->getCachedOutOfOfficeData($user);
+ if ($data === null) {
+ $absenceData = $this->absenceService->getAbsence($user->getUID());
+ if ($absenceData === null) {
+ return null;
+ }
+ $data = $absenceData->toOutOufOfficeData($user, $timezone);
+ }
+
+ $this->setCachedOutOfOfficeData($data);
+ return $data;
+ }
+
+ private function getCachedTimezone(string $userId): ?string {
+ return $this->cache->get($userId . '_timezone') ?? null;
+ }
+
+ private function setCachedTimezone(string $userId, string $timezone): void {
+ $this->cache->set($userId . '_timezone', $timezone, 3600);
+ }
+
+ public function clearCache(string $userId): void {
+ $this->cache->set($userId, null, 300);
+ $this->cache->set($userId . '_timezone', null, 3600);
+ }
+
+ public function isInEffect(IOutOfOfficeData $data): bool {
+ return $this->absenceService->isInEffect($data);
+ }
+}
diff --git a/lib/private/User/DisplayNameCache.php b/lib/private/User/DisplayNameCache.php
index 6ee74cc9f6c..a3bc69646a1 100644
--- a/lib/private/User/DisplayNameCache.php
+++ b/lib/private/User/DisplayNameCache.php
@@ -37,6 +37,7 @@ use OCP\User\Events\UserDeletedEvent;
* This saves fetching the user from a user backend and later on fetching
* their preferences. It's generally not an issue if this data is slightly
* outdated.
+ * @template-implements IEventListener<UserChangedEvent|UserDeletedEvent>
*/
class DisplayNameCache implements IEventListener {
private array $cache = [];
diff --git a/lib/private/User/Listeners/BeforeUserDeletedListener.php b/lib/private/User/Listeners/BeforeUserDeletedListener.php
index ec1f80c5413..8978c341a13 100644
--- a/lib/private/User/Listeners/BeforeUserDeletedListener.php
+++ b/lib/private/User/Listeners/BeforeUserDeletedListener.php
@@ -25,9 +25,9 @@ namespace OC\User\Listeners;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
-use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\Files\NotFoundException;
use OCP\IAvatarManager;
+use OCP\User\Events\BeforeUserDeletedEvent;
use Psr\Log\LoggerInterface;
/**
diff --git a/lib/private/User/Listeners/UserChangedListener.php b/lib/private/User/Listeners/UserChangedListener.php
index a561db2423d..0fa5ceeb0ed 100644
--- a/lib/private/User/Listeners/UserChangedListener.php
+++ b/lib/private/User/Listeners/UserChangedListener.php
@@ -25,9 +25,9 @@ namespace OC\User\Listeners;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
-use OCP\User\Events\UserChangedEvent;
use OCP\Files\NotFoundException;
use OCP\IAvatarManager;
+use OCP\User\Events\UserChangedEvent;
/**
* @template-implements IEventListener<UserChangedEvent>
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php
index 8ec8ef0c4be..5013c9bed7a 100644
--- a/lib/private/User/Manager.php
+++ b/lib/private/User/Manager.php
@@ -48,11 +48,11 @@ use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Server;
use OCP\Support\Subscription\IAssertion;
-use OCP\User\Backend\IGetRealUIDBackend;
-use OCP\User\Backend\ISearchKnownUsersBackend;
use OCP\User\Backend\ICheckPasswordBackend;
use OCP\User\Backend\ICountUsersBackend;
+use OCP\User\Backend\IGetRealUIDBackend;
use OCP\User\Backend\IProvideEnabledStateBackend;
+use OCP\User\Backend\ISearchKnownUsersBackend;
use OCP\User\Events\BeforeUserCreatedEvent;
use OCP\User\Events\UserCreatedEvent;
use OCP\UserInterface;
@@ -97,8 +97,8 @@ class Manager extends PublicEmitter implements IUserManager {
private DisplayNameCache $displayNameCache;
public function __construct(IConfig $config,
- ICacheFactory $cacheFactory,
- IEventDispatcher $eventDispatcher) {
+ ICacheFactory $cacheFactory,
+ IEventDispatcher $eventDispatcher) {
$this->config = $config;
$this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map'));
$cachedUsers = &$this->cachedUsers;
diff --git a/lib/private/User/OutOfOfficeData.php b/lib/private/User/OutOfOfficeData.php
new file mode 100644
index 00000000000..72e42afab6a
--- /dev/null
+++ b/lib/private/User/OutOfOfficeData.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\User;
+
+use OCP\IUser;
+use OCP\User\IOutOfOfficeData;
+
+class OutOfOfficeData implements IOutOfOfficeData {
+ public function __construct(private string $id,
+ private IUser $user,
+ private int $startDate,
+ private int $endDate,
+ private string $shortMessage,
+ private string $message) {
+ }
+
+ public function getId(): string {
+ return $this->id;
+ }
+
+ public function getUser(): IUser {
+ return $this->user;
+ }
+
+ public function getStartDate(): int {
+ return $this->startDate;
+ }
+
+ public function getEndDate(): int {
+ return $this->endDate;
+ }
+
+ public function getShortMessage(): string {
+ return $this->shortMessage;
+ }
+
+ public function getMessage(): string {
+ return $this->message;
+ }
+
+ public function jsonSerialize(): array {
+ return [
+ 'id' => $this->getId(),
+ 'userId' => $this->getUser()->getUID(),
+ 'startDate' => $this->getStartDate(),
+ 'endDate' => $this->getEndDate(),
+ 'shortMessage' => $this->getShortMessage(),
+ 'message' => $this->getMessage(),
+ ];
+ }
+}
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index d6971d1486b..a411326c93f 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -39,8 +39,6 @@
namespace OC\User;
use OC;
-use OC\Authentication\Exceptions\ExpiredTokenException;
-use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
use OC\Authentication\Token\IProvider;
@@ -51,6 +49,8 @@ use OC_User;
use OC_Util;
use OCA\DAV\Connector\Sabre\Auth;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\Exceptions\ExpiredTokenException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\EventDispatcher\GenericEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\NotPermittedException;
@@ -120,14 +120,14 @@ class Session implements IUserSession, Emitter {
private $dispatcher;
public function __construct(Manager $manager,
- ISession $session,
- ITimeFactory $timeFactory,
- ?IProvider $tokenProvider,
- IConfig $config,
- ISecureRandom $random,
- ILockdownManager $lockdownManager,
- LoggerInterface $logger,
- IEventDispatcher $dispatcher
+ ISession $session,
+ ITimeFactory $timeFactory,
+ ?IProvider $tokenProvider,
+ IConfig $config,
+ ISecureRandom $random,
+ ILockdownManager $lockdownManager,
+ LoggerInterface $logger,
+ IEventDispatcher $dispatcher
) {
$this->manager = $manager;
$this->session = $session;
@@ -425,9 +425,9 @@ class Session implements IUserSession, Emitter {
* @return boolean
*/
public function logClientIn($user,
- $password,
- IRequest $request,
- IThrottler $throttler) {
+ $password,
+ IRequest $request,
+ IThrottler $throttler) {
$remoteAddress = $request->getRemoteAddress();
$currentDelay = $throttler->sleepDelayOrThrowOnMax($remoteAddress, 'login');
@@ -456,8 +456,18 @@ class Session implements IUserSession, Emitter {
$this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password);
return false;
}
- $users = $this->manager->getByEmail($user);
- if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) {
+
+ if ($isTokenPassword) {
+ $dbToken = $this->tokenProvider->getToken($password);
+ $userFromToken = $this->manager->get($dbToken->getUID());
+ $isValidEmailLogin = $userFromToken->getEMailAddress() === $user
+ && $this->validateTokenLoginName($userFromToken->getEMailAddress(), $dbToken);
+ } else {
+ $users = $this->manager->getByEmail($user);
+ $isValidEmailLogin = (\count($users) === 1 && $this->login($users[0]->getUID(), $password));
+ }
+
+ if (!$isValidEmailLogin) {
$this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password);
return false;
}
@@ -576,7 +586,7 @@ class Session implements IUserSession, Emitter {
* @return boolean if the login was successful
*/
public function tryBasicAuthLogin(IRequest $request,
- IThrottler $throttler) {
+ IThrottler $throttler) {
if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
try {
if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
@@ -783,7 +793,7 @@ class Session implements IUserSession, Emitter {
try {
$dbToken = $this->tokenProvider->getToken($token);
} catch (InvalidTokenException $ex) {
- $this->logger->warning('Session token is invalid because it does not exist', [
+ $this->logger->debug('Session token is invalid because it does not exist', [
'app' => 'core',
'user' => $user,
'exception' => $ex,
@@ -791,18 +801,7 @@ class Session implements IUserSession, Emitter {
return false;
}
- // Check if login names match
- if (!is_null($user) && $dbToken->getLoginName() !== $user) {
- // TODO: this makes it impossible to use different login names on browser and client
- // e.g. login by e-mail 'user@example.com' on browser for generating the token will not
- // allow to use the client token with the login name 'user'.
- $this->logger->error('App token login name does not match', [
- 'tokenLoginName' => $dbToken->getLoginName(),
- 'sessionLoginName' => $user,
- 'app' => 'core',
- 'user' => $dbToken->getUID(),
- ]);
-
+ if (!is_null($user) && !$this->validateTokenLoginName($user, $dbToken)) {
return false;
}
@@ -823,6 +822,27 @@ class Session implements IUserSession, Emitter {
}
/**
+ * Check if login names match
+ */
+ private function validateTokenLoginName(?string $loginName, IToken $token): bool {
+ if ($token->getLoginName() !== $loginName) {
+ // TODO: this makes it impossible to use different login names on browser and client
+ // e.g. login by e-mail 'user@example.com' on browser for generating the token will not
+ // allow to use the client token with the login name 'user'.
+ $this->logger->error('App token login name does not match', [
+ 'tokenLoginName' => $token->getLoginName(),
+ 'sessionLoginName' => $loginName,
+ 'app' => 'core',
+ 'user' => $token->getUID(),
+ ]);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Tries to login the user with auth token header
*
* @param IRequest $request
@@ -833,13 +853,16 @@ class Session implements IUserSession, Emitter {
$authHeader = $request->getHeader('Authorization');
if (str_starts_with($authHeader, 'Bearer ')) {
$token = substr($authHeader, 7);
- } else {
- // No auth header, let's try session id
+ } elseif ($request->getCookie($this->config->getSystemValueString('instanceid')) !== null) {
+ // No auth header, let's try session id, but only if this is an existing
+ // session and the request has a session cookie
try {
$token = $this->session->getId();
} catch (SessionNotAvailableException $ex) {
return false;
}
+ } else {
+ return false;
}
if (!$this->loginWithToken($token)) {
@@ -916,9 +939,10 @@ class Session implements IUserSession, Emitter {
]);
return false;
} catch (InvalidTokenException $ex) {
- $this->logger->error('Renewing session token failed', [
+ $this->logger->error('Renewing session token failed: ' . $ex->getMessage(), [
'app' => 'core',
'user' => $uid,
+ 'exception' => $ex,
]);
return false;
}
diff --git a/lib/private/User/User.php b/lib/private/User/User.php
index 69ef82f3e85..580c590e6eb 100644
--- a/lib/private/User/User.php
+++ b/lib/private/User/User.php
@@ -49,17 +49,17 @@ use OCP\IImage;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserBackend;
+use OCP\User\Backend\IGetHomeBackend;
+use OCP\User\Backend\IProvideAvatarBackend;
+use OCP\User\Backend\IProvideEnabledStateBackend;
+use OCP\User\Backend\ISetDisplayNameBackend;
+use OCP\User\Backend\ISetPasswordBackend;
use OCP\User\Events\BeforePasswordUpdatedEvent;
use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\User\Events\PasswordUpdatedEvent;
use OCP\User\Events\UserChangedEvent;
use OCP\User\Events\UserDeletedEvent;
use OCP\User\GetQuotaEvent;
-use OCP\User\Backend\ISetDisplayNameBackend;
-use OCP\User\Backend\ISetPasswordBackend;
-use OCP\User\Backend\IProvideAvatarBackend;
-use OCP\User\Backend\IProvideEnabledStateBackend;
-use OCP\User\Backend\IGetHomeBackend;
use OCP\UserInterface;
use function json_decode;
use function json_encode;
@@ -576,7 +576,7 @@ class User implements IUser {
public function getAvatarImage($size) {
// delay the initialization
if (is_null($this->avatarManager)) {
- $this->avatarManager = \OC::$server->getAvatarManager();
+ $this->avatarManager = \OC::$server->get(IAvatarManager::class);
}
$avatar = $this->avatarManager->getAvatar($this->uid);
diff --git a/lib/private/UserStatus/ISettableProvider.php b/lib/private/UserStatus/ISettableProvider.php
index 88a107d1f86..957d3274f1d 100644
--- a/lib/private/UserStatus/ISettableProvider.php
+++ b/lib/private/UserStatus/ISettableProvider.php
@@ -39,8 +39,9 @@ interface ISettableProvider extends IProvider {
* @param string $messageId The new message id.
* @param string $status The new status.
* @param bool $createBackup If true, this will store the old status so that it is possible to revert it later (e.g. after a call).
+ * @param string|null $customMessage
*/
- public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup): void;
+ public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup, ?string $customMessage = null): void;
/**
* Revert an automatically set user status. For example after leaving a call,
diff --git a/lib/private/UserStatus/Manager.php b/lib/private/UserStatus/Manager.php
index 89a1bb455c7..a5594158c1e 100644
--- a/lib/private/UserStatus/Manager.php
+++ b/lib/private/UserStatus/Manager.php
@@ -51,7 +51,7 @@ class Manager implements IManager {
* @param LoggerInterface $logger
*/
public function __construct(IServerContainer $container,
- LoggerInterface $logger) {
+ LoggerInterface $logger) {
$this->container = $container;
$this->logger = $logger;
}
@@ -104,13 +104,13 @@ class Manager implements IManager {
$this->provider = $provider;
}
- public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false): void {
+ public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false, ?string $customMessage = null): void {
$this->setupProvider();
if (!$this->provider || !($this->provider instanceof ISettableProvider)) {
return;
}
- $this->provider->setUserStatus($userId, $messageId, $status, $createBackup);
+ $this->provider->setUserStatus($userId, $messageId, $status, $createBackup, $customMessage);
}
public function revertUserStatus(string $userId, string $messageId, string $status): void {
diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php
index 23e0b099e91..edd844bf89f 100644
--- a/lib/private/legacy/OC_App.php
+++ b/lib/private/legacy/OC_App.php
@@ -51,18 +51,19 @@ declare(strict_types=1);
*
*/
-use OCP\App\Events\AppUpdateEvent;
-use OCP\App\IAppManager;
-use OCP\App\ManagerEvent;
-use OCP\Authentication\IAlternativeLogin;
-use OCP\EventDispatcher\IEventDispatcher;
-use OC\AppFramework\Bootstrap\Coordinator;
use OC\App\DependencyAnalyzer;
use OC\App\Platform;
+use OC\AppFramework\Bootstrap\Coordinator;
use OC\DB\MigrationService;
use OC\Installer;
use OC\Repair;
use OC\Repair\Events\RepairErrorEvent;
+use OCP\App\Events\AppUpdateEvent;
+use OCP\App\IAppManager;
+use OCP\App\ManagerEvent;
+use OCP\Authentication\IAlternativeLogin;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IAppConfig;
use Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface;
@@ -251,7 +252,7 @@ class OC_App {
* This function set an app as enabled in appconfig.
*/
public function enable(string $appId,
- array $groups = []) {
+ array $groups = []) {
// Check if app is already downloaded
/** @var Installer $installer */
$installer = \OCP\Server::get(Installer::class);
@@ -281,10 +282,8 @@ class OC_App {
/**
* Get the path where to install apps
- *
- * @return string|false
*/
- public static function getInstallPath() {
+ public static function getInstallPath(): string|null {
foreach (OC::$APPSROOTS as $dir) {
if (isset($dir['writable']) && $dir['writable'] === true) {
return $dir['path'];
@@ -564,7 +563,7 @@ class OC_App {
$supportedApps = $this->getSupportedApps();
foreach ($installedApps as $app) {
- if (array_search($app, $blacklist) === false) {
+ if (!in_array($app, $blacklist)) {
$info = $appManager->getAppInfo($app, false, $langCode);
if (!is_array($info)) {
\OCP\Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
@@ -730,8 +729,9 @@ class OC_App {
static $versions;
if (!$versions) {
- $appConfig = \OC::$server->getAppConfig();
- $versions = $appConfig->getValues(false, 'installed_version');
+ /** @var IAppConfig $appConfig */
+ $appConfig = \OCP\Server::get(IAppConfig::class);
+ $versions = $appConfig->searchValues('installed_version');
}
return $versions;
}
diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php
index ac0a2bbd0e9..a2f47639e65 100644
--- a/lib/private/legacy/OC_Files.php
+++ b/lib/private/legacy/OC_Files.php
@@ -43,10 +43,10 @@
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Files\View;
use OC\Streamer;
-use OCP\Lock\ILockingProvider;
-use OCP\Files\Events\BeforeZipCreatedEvent;
-use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Events\BeforeDirectFileDownloadEvent;
+use OCP\Files\Events\BeforeZipCreatedEvent;
+use OCP\Lock\ILockingProvider;
/**
* Class for file server access
diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php
index cf39d045ae9..37fbf7f5f8f 100644
--- a/lib/private/legacy/OC_Helper.php
+++ b/lib/private/legacy/OC_Helper.php
@@ -46,8 +46,8 @@
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Files\Filesystem;
use OCP\Files\Mount\IMountPoint;
-use OCP\ICacheFactory;
use OCP\IBinaryFinder;
+use OCP\ICacheFactory;
use OCP\IUser;
use OCP\Util;
use Psr\Log\LoggerInterface;
diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php
index 5751b813f2c..d2dc2a2389f 100644
--- a/lib/private/legacy/OC_User.php
+++ b/lib/private/legacy/OC_User.php
@@ -138,7 +138,7 @@ class OC_User {
$class = $config['class'];
$arguments = $config['arguments'];
if (class_exists($class)) {
- if (array_search($i, self::$_setupedBackends) === false) {
+ if (!in_array($i, self::$_setupedBackends)) {
// make a reflection object
$reflectionObj = new ReflectionClass($class);
diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php
index f82ddcc78ee..c008ccd63ba 100644
--- a/lib/private/legacy/OC_Util.php
+++ b/lib/private/legacy/OC_Util.php
@@ -513,15 +513,7 @@ class OC_Util {
}
$webServerRestart = false;
- $setup = new \OC\Setup(
- $config,
- \OC::$server->get(IniGetWrapper::class),
- \OC::$server->getL10N('lib'),
- \OC::$server->get(\OCP\Defaults::class),
- \OC::$server->get(LoggerInterface::class),
- \OC::$server->getSecureRandom(),
- \OC::$server->get(\OC\Installer::class)
- );
+ $setup = \OCP\Server::get(\OC\Setup::class);
$urlGenerator = \OC::$server->getURLGenerator();
diff --git a/lib/public/App/IAppManager.php b/lib/public/App/IAppManager.php
index 2497544dcbe..d15cfdbea96 100644
--- a/lib/public/App/IAppManager.php
+++ b/lib/public/App/IAppManager.php
@@ -45,8 +45,8 @@ interface IAppManager {
/**
* Returns the app information from "appinfo/info.xml".
*
- * @param string $appId
- * @return mixed
+ * @param string|null $lang
+ * @return array|null
* @since 14.0.0
*/
public function getAppInfo(string $appId, bool $path = false, $lang = null);
@@ -65,7 +65,7 @@ interface IAppManager {
* Check if an app is enabled for user
*
* @param string $appId
- * @param \OCP\IUser $user (optional) if not defined, the currently loggedin user will be used
+ * @param \OCP\IUser|null $user (optional) if not defined, the currently loggedin user will be used
* @return bool
* @since 8.0.0
*/
@@ -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/ApiController.php b/lib/public/AppFramework/ApiController.php
index 66c278e62d8..9505af0b2e2 100644
--- a/lib/public/AppFramework/ApiController.php
+++ b/lib/public/AppFramework/ApiController.php
@@ -52,10 +52,10 @@ abstract class ApiController extends Controller {
* @since 7.0.0
*/
public function __construct($appName,
- IRequest $request,
- $corsMethods = 'PUT, POST, GET, DELETE, PATCH',
- $corsAllowedHeaders = 'Authorization, Content-Type, Accept',
- $corsMaxAge = 1728000) {
+ IRequest $request,
+ $corsMethods = 'PUT, POST, GET, DELETE, PATCH',
+ $corsAllowedHeaders = 'Authorization, Content-Type, Accept',
+ $corsMaxAge = 1728000) {
parent::__construct($appName, $request);
$this->corsMethods = $corsMethods;
$this->corsAllowedHeaders = $corsAllowedHeaders;
diff --git a/lib/public/AppFramework/AuthPublicShareController.php b/lib/public/AppFramework/AuthPublicShareController.php
index 78dd45551ed..847f1823db8 100644
--- a/lib/public/AppFramework/AuthPublicShareController.php
+++ b/lib/public/AppFramework/AuthPublicShareController.php
@@ -57,9 +57,9 @@ abstract class AuthPublicShareController extends PublicShareController {
* @since 14.0.0
*/
public function __construct(string $appName,
- IRequest $request,
- ISession $session,
- IURLGenerator $urlGenerator) {
+ IRequest $request,
+ ISession $session,
+ IURLGenerator $urlGenerator) {
parent::__construct($appName, $request, $session);
$this->urlGenerator = $urlGenerator;
diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
index c34cec38eb1..b39c8591a7e 100644
--- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
+++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
@@ -37,10 +37,11 @@ use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Template\ICustomTemplateProvider;
use OCP\IContainer;
-use OCP\TextProcessing\IProvider as ITextProcessingProvider;
use OCP\Notification\INotifier;
use OCP\Preview\IProviderV2;
use OCP\SpeechToText\ISpeechToTextProvider;
+use OCP\TextProcessing\IProvider as ITextProcessingProvider;
+use OCP\TextToImage\IProvider as ITextToImageProvider;
use OCP\Translation\ITranslationProvider;
/**
@@ -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/Controller.php b/lib/public/AppFramework/Controller.php
index e8500d5ae1a..12e1c31626b 100644
--- a/lib/public/AppFramework/Controller.php
+++ b/lib/public/AppFramework/Controller.php
@@ -65,7 +65,7 @@ abstract class Controller {
* @since 6.0.0 - parameter $appName was added in 7.0.0 - parameter $app was removed in 7.0.0
*/
public function __construct($appName,
- IRequest $request) {
+ IRequest $request) {
$this->appName = $appName;
$this->request = $request;
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/ParameterOutOfRangeException.php b/lib/public/AppFramework/Http/ParameterOutOfRangeException.php
new file mode 100644
index 00000000000..22518d8eddf
--- /dev/null
+++ b/lib/public/AppFramework/Http/ParameterOutOfRangeException.php
@@ -0,0 +1,79 @@
+<?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;
+
+/**
+ * @since 29.0.0
+ */
+class ParameterOutOfRangeException extends \OutOfRangeException {
+ /**
+ * @since 29.0.0
+ */
+ public function __construct(
+ protected string $parameterName,
+ protected int $actualValue,
+ protected int $minValue,
+ protected int $maxValue,
+ ) {
+ parent::__construct(
+ sprintf(
+ 'Parameter %s must be between %d and %d',
+ $this->parameterName,
+ $this->minValue,
+ $this->maxValue,
+ )
+ );
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getParameterName(): string {
+ return $this->parameterName;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getActualValue(): int {
+ return $this->actualValue;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getMinValue(): int {
+ return $this->minValue;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getMaxValue(): int {
+ return $this->maxValue;
+ }
+}
diff --git a/lib/public/AppFramework/Http/TooManyRequestsResponse.php b/lib/public/AppFramework/Http/TooManyRequestsResponse.php
index 043ae0161e9..688fb6cc385 100644
--- a/lib/public/AppFramework/Http/TooManyRequestsResponse.php
+++ b/lib/public/AppFramework/Http/TooManyRequestsResponse.php
@@ -26,8 +26,8 @@ declare(strict_types=1);
*/
namespace OCP\AppFramework\Http;
-use OCP\Template;
use OCP\AppFramework\Http;
+use OCP\Template;
/**
* A generic 429 response showing an 404 error page as well to the end-user
diff --git a/lib/public/AppFramework/OCSController.php b/lib/public/AppFramework/OCSController.php
index 51c71ad4e64..68c88915b1c 100644
--- a/lib/public/AppFramework/OCSController.php
+++ b/lib/public/AppFramework/OCSController.php
@@ -59,10 +59,10 @@ abstract class OCSController extends ApiController {
* @since 8.1.0
*/
public function __construct($appName,
- IRequest $request,
- $corsMethods = 'PUT, POST, GET, DELETE, PATCH',
- $corsAllowedHeaders = 'Authorization, Content-Type, Accept, OCS-APIRequest',
- $corsMaxAge = 1728000) {
+ IRequest $request,
+ $corsMethods = 'PUT, POST, GET, DELETE, PATCH',
+ $corsAllowedHeaders = 'Authorization, Content-Type, Accept, OCS-APIRequest',
+ $corsMaxAge = 1728000) {
parent::__construct($appName, $request, $corsMethods,
$corsAllowedHeaders, $corsMaxAge);
$this->registerResponder('json', function ($data) {
diff --git a/lib/public/AppFramework/PublicShareController.php b/lib/public/AppFramework/PublicShareController.php
index 52acbe841b4..cbcb9343198 100644
--- a/lib/public/AppFramework/PublicShareController.php
+++ b/lib/public/AppFramework/PublicShareController.php
@@ -53,8 +53,8 @@ abstract class PublicShareController extends Controller {
* @since 14.0.0
*/
public function __construct(string $appName,
- IRequest $request,
- ISession $session) {
+ IRequest $request,
+ ISession $session) {
parent::__construct($appName, $request);
$this->session = $session;
@@ -86,7 +86,7 @@ abstract class PublicShareController extends Controller {
*
* @since 14.0.0
*/
- abstract protected function getPasswordHash(): string;
+ abstract protected function getPasswordHash(): ?string;
/**
* Is the provided token a valid token
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 eeca986423b..0b00326ca1a 100644
--- a/lib/public/BackgroundJob/IJobList.php
+++ b/lib/public/BackgroundJob/IJobList.php
@@ -160,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/BackgroundJob/Job.php b/lib/public/BackgroundJob/Job.php
index a574e90e1a0..2842486b74d 100644
--- a/lib/public/BackgroundJob/Job.php
+++ b/lib/public/BackgroundJob/Job.php
@@ -76,16 +76,17 @@ abstract class Job implements IJob, IParallelAwareJob {
$logger = $this->logger ?? \OCP\Server::get(LoggerInterface::class);
try {
+ $jobDetails = get_class($this) . ' (id: ' . $this->getId() . ', arguments: ' . json_encode($this->getArgument()) . ')';
$jobStartTime = $this->time->getTime();
- $logger->debug('Run ' . get_class($this) . ' job with ID ' . $this->getId(), ['app' => 'cron']);
+ $logger->debug('Starting job ' . $jobDetails, ['app' => 'cron']);
$this->run($this->argument);
$timeTaken = $this->time->getTime() - $jobStartTime;
- $logger->debug('Finished ' . get_class($this) . ' job with ID ' . $this->getId() . ' in ' . $timeTaken . ' seconds', ['app' => 'cron']);
+ $logger->debug('Finished job ' . $jobDetails . ' in ' . $timeTaken . ' seconds', ['app' => 'cron']);
$jobList->setExecutionTime($this, $timeTaken);
} catch (\Throwable $e) {
if ($logger) {
- $logger->error('Error while running background job (class: ' . get_class($this) . ', arguments: ' . print_r($this->argument, true) . ')', [
+ $logger->error('Error while running background job ' . $jobDetails, [
'app' => 'core',
'exception' => $e,
]);
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/Comments/IComment.php b/lib/public/Comments/IComment.php
index eb696fa5f06..0a0f1b1b251 100644
--- a/lib/public/Comments/IComment.php
+++ b/lib/public/Comments/IComment.php
@@ -280,6 +280,25 @@ interface IComment {
public function setReferenceId(?string $referenceId): IComment;
/**
+ * Returns the metadata of the comment
+ *
+ * @return array|null
+ * @since 29.0.0
+ */
+ public function getMetaData(): ?array;
+
+ /**
+ * Sets (overwrites) the metadata of the comment
+ * Data as a json encoded array
+ *
+ * @param array|null $metaData
+ * @return IComment
+ * @throws \JsonException When the metadata can not be converted to a json encoded string
+ * @since 29.0.0
+ */
+ public function setMetaData(?array $metaData): IComment;
+
+ /**
* Returns the reactions array if exists
*
* The keys is the emoji of reaction and the value is the total.
diff --git a/lib/public/Comments/ICommentsManager.php b/lib/public/Comments/ICommentsManager.php
index 8d7ffd164b3..152c8deeaf3 100644
--- a/lib/public/Comments/ICommentsManager.php
+++ b/lib/public/Comments/ICommentsManager.php
@@ -114,11 +114,11 @@ interface ICommentsManager {
* @since 9.0.0
*/
public function getForObject(
- $objectType,
- $objectId,
- $limit = 0,
- $offset = 0,
- \DateTime $notOlderThan = null
+ $objectType,
+ $objectId,
+ $limit = 0,
+ $offset = 0,
+ \DateTime $notOlderThan = null
);
/**
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/Dashboard/Model/WidgetItem.php b/lib/public/Dashboard/Model/WidgetItem.php
index 1c54d2bd426..22235053b5d 100644
--- a/lib/public/Dashboard/Model/WidgetItem.php
+++ b/lib/public/Dashboard/Model/WidgetItem.php
@@ -70,11 +70,11 @@ final class WidgetItem implements JsonSerializable {
* @since 22.0.0
*/
public function __construct(string $title = '',
- string $subtitle = '',
- string $link = '',
- string $iconUrl = '',
- string $sinceId = '',
- string $overlayIconUrl = '') {
+ string $subtitle = '',
+ string $link = '',
+ string $iconUrl = '',
+ string $sinceId = '',
+ string $overlayIconUrl = '') {
$this->title = $title;
$this->subtitle = $subtitle;
$this->iconUrl = $iconUrl;
diff --git a/lib/public/Exceptions/AppConfigException.php b/lib/public/Exceptions/AppConfigException.php
new file mode 100644
index 00000000000..73c91d9f018
--- /dev/null
+++ b/lib/public/Exceptions/AppConfigException.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\Exceptions;
+
+use Exception;
+
+/**
+ * @since 29.0.0
+ */
+class AppConfigException extends Exception {
+}
diff --git a/lib/public/Exceptions/AppConfigIncorrectTypeException.php b/lib/public/Exceptions/AppConfigIncorrectTypeException.php
new file mode 100644
index 00000000000..1284e4b193e
--- /dev/null
+++ b/lib/public/Exceptions/AppConfigIncorrectTypeException.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\Exceptions;
+
+/**
+ * @since 29.0.0
+ */
+class AppConfigIncorrectTypeException extends AppConfigException {
+}
diff --git a/lib/public/Exceptions/AppConfigTypeConflictException.php b/lib/public/Exceptions/AppConfigTypeConflictException.php
new file mode 100644
index 00000000000..599fed0cb3b
--- /dev/null
+++ b/lib/public/Exceptions/AppConfigTypeConflictException.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\Exceptions;
+
+/**
+ * @since 29.0.0
+ */
+class AppConfigTypeConflictException extends AppConfigException {
+}
diff --git a/lib/public/Exceptions/AppConfigUnknownKeyException.php b/lib/public/Exceptions/AppConfigUnknownKeyException.php
new file mode 100644
index 00000000000..e2b9d7fd3dc
--- /dev/null
+++ b/lib/public/Exceptions/AppConfigUnknownKeyException.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\Exceptions;
+
+/**
+ * @since 29.0.0
+ */
+class AppConfigUnknownKeyException extends AppConfigException {
+}
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/Cache/ICacheEntry.php b/lib/public/Files/Cache/ICacheEntry.php
index e1e8129394c..3a069ca69e0 100644
--- a/lib/public/Files/Cache/ICacheEntry.php
+++ b/lib/public/Files/Cache/ICacheEntry.php
@@ -123,8 +123,8 @@ interface ICacheEntry extends ArrayAccess {
public function getEtag();
/**
- * Get the permissions for the file stored as bitwise combination of \OCP\PERMISSION_READ, \OCP\PERMISSION_CREATE
- * \OCP\PERMISSION_UPDATE, \OCP\PERMISSION_DELETE and \OCP\PERMISSION_SHARE
+ * Get the permissions for the file stored as bitwise combination of \OCP\Constants::PERMISSION_READ, \OCP\Constants::PERMISSION_CREATE
+ * \OCP\Constants::PERMISSION_UPDATE, \OCP\Constants::PERMISSION_DELETE and \OCP\Constants::PERMISSION_SHARE
*
* @return int
* @since 9.0.0
diff --git a/lib/public/Files/Cache/IUpdater.php b/lib/public/Files/Cache/IUpdater.php
index 5a776d4be7e..625bc91c5a7 100644
--- a/lib/public/Files/Cache/IUpdater.php
+++ b/lib/public/Files/Cache/IUpdater.php
@@ -53,7 +53,7 @@ interface IUpdater {
* @param int $time
* @since 9.0.0
*/
- public function update($path, $time = null);
+ public function update($path, $time = null, ?int $sizeDifference = null);
/**
* Remove $path from the cache and update the size, etag and mtime of the parent folders
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/Events/FileCacheUpdated.php b/lib/public/Files/Events/FileCacheUpdated.php
index 18d6318f2f4..2669c51088e 100644
--- a/lib/public/Files/Events/FileCacheUpdated.php
+++ b/lib/public/Files/Events/FileCacheUpdated.php
@@ -44,7 +44,7 @@ class FileCacheUpdated extends Event {
* @since 18.0.0
*/
public function __construct(IStorage $storage,
- string $path) {
+ string $path) {
parent::__construct();
$this->storage = $storage;
$this->path = $path;
diff --git a/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php b/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php
index 316a30fadd8..dd29a39a279 100644
--- a/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php
+++ b/lib/public/Files/Events/Node/BeforeNodeDeletedEvent.php
@@ -25,8 +25,31 @@ declare(strict_types=1);
*/
namespace OCP\Files\Events\Node;
+use Exception;
+use OCP\Files\Node;
+
/**
* @since 20.0.0
*/
class BeforeNodeDeletedEvent extends AbstractNodeEvent {
+ /**
+ * @since 20.0.0
+ */
+ public function __construct(Node $node, private bool &$run) {
+ parent::__construct($node);
+ }
+
+ /**
+ * @since 28.0.0
+ * @return never
+ */
+ public function abortOperation(\Throwable $ex = null) {
+ $this->stopPropagation();
+ $this->run = false;
+ if ($ex !== null) {
+ throw $ex;
+ } else {
+ throw new Exception('Operation aborted');
+ }
+ }
}
diff --git a/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php b/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php
index efbef03e383..c6876666713 100644
--- a/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php
+++ b/lib/public/Files/Events/Node/BeforeNodeRenamedEvent.php
@@ -25,8 +25,31 @@ declare(strict_types=1);
*/
namespace OCP\Files\Events\Node;
+use Exception;
+use OCP\Files\Node;
+
/**
* @since 20.0.0
*/
class BeforeNodeRenamedEvent extends AbstractNodesEvent {
+ /**
+ * @since 20.0.0
+ */
+ public function __construct(Node $source, Node $target, private bool &$run) {
+ parent::__construct($source, $target);
+ }
+
+ /**
+ * @since 28.0.0
+ * @return never
+ */
+ public function abortOperation(\Throwable $ex = null) {
+ $this->stopPropagation();
+ $this->run = false;
+ if ($ex !== null) {
+ throw $ex;
+ } else {
+ throw new Exception('Operation aborted');
+ }
+ }
}
diff --git a/lib/public/Files/Events/NodeAddedToCache.php b/lib/public/Files/Events/NodeAddedToCache.php
index 6986b4b5989..3a1947e7a16 100644
--- a/lib/public/Files/Events/NodeAddedToCache.php
+++ b/lib/public/Files/Events/NodeAddedToCache.php
@@ -44,7 +44,7 @@ class NodeAddedToCache extends Event {
* @since 18.0.0
*/
public function __construct(IStorage $storage,
- string $path) {
+ string $path) {
parent::__construct();
$this->storage = $storage;
$this->path = $path;
diff --git a/lib/public/Files/Events/NodeRemovedFromCache.php b/lib/public/Files/Events/NodeRemovedFromCache.php
index 9f67cb71371..83c4bd16531 100644
--- a/lib/public/Files/Events/NodeRemovedFromCache.php
+++ b/lib/public/Files/Events/NodeRemovedFromCache.php
@@ -44,7 +44,7 @@ class NodeRemovedFromCache extends Event {
* @since 18.0.0
*/
public function __construct(IStorage $storage,
- string $path) {
+ string $path) {
parent::__construct();
$this->storage = $storage;
$this->path = $path;
diff --git a/lib/public/Files/FileInfo.php b/lib/public/Files/FileInfo.php
index da35f7f9028..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>
@@ -308,4 +309,12 @@ interface FileInfo {
* @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/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/Node.php b/lib/public/Files/Node.php
index b49b4a0f83d..ecf1427fa1a 100644
--- a/lib/public/Files/Node.php
+++ b/lib/public/Files/Node.php
@@ -30,8 +30,8 @@
namespace OCP\Files;
-use OCP\Lock\LockedException;
use OCP\Files\Storage\IStorage;
+use OCP\Lock\LockedException;
/**
* Interface Node
diff --git a/lib/public/Files/Search/ISearchComparison.php b/lib/public/Files/Search/ISearchComparison.php
index 8ebaeced304..ba02f39996f 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
@@ -34,6 +35,7 @@ interface ISearchComparison extends ISearchOperator {
public const COMPARE_LESS_THAN_EQUAL = 'lte';
public const COMPARE_LIKE = 'like';
public const COMPARE_LIKE_CASE_SENSITIVE = 'clike';
+ public const COMPARE_DEFINED = 'is-defined';
public const HINT_PATH_EQ_HASH = 'path_eq_hash'; // transform `path = "$path"` into `path_hash = md5("$path")`, on by default
@@ -43,7 +45,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 +55,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/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..55feefc4f12
--- /dev/null
+++ b/lib/public/FilesMetadata/IFilesMetadataManager.php
@@ -0,0 +1,169 @@
+<?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 of 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;
+
+ /**
+ * returns metadata of multiple file ids
+ *
+ * @param int[] $fileIds file ids
+ *
+ * @return array File ID is the array key, files without metadata are not returned in the array
+ * @psalm-return array<int, IFilesMetadata>
+ * @since 28.0.0
+ */
+ public function getMetadataForFiles(array $fileIds): array;
+
+ /**
+ * 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/Http/WellKnown/JrdResponse.php b/lib/public/Http/WellKnown/JrdResponse.php
index 7a25f8fe0c3..077b58dc1d9 100644
--- a/lib/public/Http/WellKnown/JrdResponse.php
+++ b/lib/public/Http/WellKnown/JrdResponse.php
@@ -127,10 +127,10 @@ final class JrdResponse implements IResponse {
* @since 21.0.0
*/
public function addLink(string $rel,
- ?string $type,
- ?string $href,
- ?array $titles = [],
- ?array $properties = []): self {
+ ?string $type,
+ ?string $href,
+ ?array $titles = [],
+ ?array $properties = []): self {
$this->links[] = array_filter([
'rel' => $rel,
'type' => $type,
diff --git a/lib/public/IAppConfig.php b/lib/public/IAppConfig.php
index cf387a8a44c..9bdeb14b295 100644
--- a/lib/public/IAppConfig.php
+++ b/lib/public/IAppConfig.php
@@ -1,9 +1,12 @@
<?php
+
+declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Joas Schilling <coding@schilljs.com>
+ * @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
@@ -26,28 +29,486 @@
*/
namespace OCP;
+use OCP\Exceptions\AppConfigUnknownKeyException;
+
/**
* This class provides an easy way for apps to store config values in the
* database.
+ *
+ * **Note:** since 29.0.0, it supports **lazy loading**
+ *
+ * ### What is lazy loading ?
+ * In order to avoid loading useless config values into memory for each request,
+ * only non-lazy values are now loaded.
+ *
+ * Once a value that is lazy is requested, all lazy values will be loaded.
+ *
+ * Similarly, some methods from this class are marked with a warning about ignoring
+ * lazy loading. Use them wisely and only on parts of the code that are called
+ * during specific requests or actions to avoid loading the lazy values all the time.
+ *
* @since 7.0.0
+ * @since 29.0.0 - Supporting types and lazy loading
*/
interface IAppConfig {
+ /** @since 29.0.0 */
+ public const VALUE_SENSITIVE = 1;
+ /** @since 29.0.0 */
+ public const VALUE_MIXED = 2;
+ /** @since 29.0.0 */
+ public const VALUE_STRING = 4;
+ /** @since 29.0.0 */
+ public const VALUE_INT = 8;
+ /** @since 29.0.0 */
+ public const VALUE_FLOAT = 16;
+ /** @since 29.0.0 */
+ public const VALUE_BOOL = 32;
+ /** @since 29.0.0 */
+ public const VALUE_ARRAY = 64;
+
/**
- * check if a key is set in the appconfig
- * @param string $app
- * @param string $key
- * @return bool
+ * Get list of all apps that have at least one config value stored in database
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @return string[] list of app ids
* @since 7.0.0
*/
- public function hasKey($app, $key);
+ public function getApps(): array;
+
+ /**
+ * Returns all keys stored in database, related to an app.
+ * Please note that the values are not returned.
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $app id of the app
+ *
+ * @return string[] list of stored config keys
+ * @since 29.0.0
+ */
+ public function getKeys(string $app): array;
+
+ /**
+ * Check if a key exists in the list of stored config values.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool TRUE if key exists
+ * @since 29.0.0 Added the $lazy argument
+ * @since 7.0.0
+ */
+ public function hasKey(string $app, string $key, ?bool $lazy = false): bool;
+
+ /**
+ * best way to see if a value is set as sensitive (not displayed in report)
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool|null $lazy search within lazy loaded config
+ *
+ * @return bool TRUE if value is sensitive
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @since 29.0.0
+ */
+ public function isSensitive(string $app, string $key, ?bool $lazy = false): bool;
+
+ /**
+ * Returns if the config key stored in database is lazy loaded
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @return bool TRUE if config is lazy loaded
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @see IAppConfig for details about lazy loading
+ * @since 29.0.0
+ */
+ public function isLazy(string $app, string $key): bool;
+
+ /**
+ * List all config values from an app with config key starting with $key.
+ * Returns an array with config key as key, stored value as value.
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $app id of the app
+ * @param string $key config keys prefix to search, can be empty.
+ * @param bool $filtered filter sensitive config values
+ *
+ * @return array<string, string> [configKey => configValue]
+ * @since 29.0.0
+ */
+ public function getAllValues(string $app, string $key = '', bool $filtered = false): array;
+
+ /**
+ * List all apps storing a specific config key and its stored value.
+ * Returns an array with appId as key, stored value as value.
+ *
+ * @param string $key config key
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return array<string, string> [appId => configValue]
+ * @since 29.0.0
+ */
+ public function searchValues(string $key, bool $lazy = false): array;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return string stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see getValueInt()
+ * @see getValueFloat()
+ * @see getValueBool()
+ * @see getValueArray()
+ */
+ public function getValueString(string $app, string $key, string $default = '', bool $lazy = false): string;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return int stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueFloat()
+ * @see getValueBool()
+ * @see getValueArray()
+ */
+ public function getValueInt(string $app, string $key, int $default = 0, bool $lazy = false): int;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param float $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return float stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueInt()
+ * @see getValueBool()
+ * @see getValueArray()
+ */
+ public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueInt()
+ * @see getValueFloat()
+ * @see getValueArray()
+ */
+ public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param array $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return array stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see getValueString()
+ * @see getValueInt()
+ * @see getValueFloat()
+ * @see getValueBool()
+ */
+ public function getValueArray(string $app, string $key, array $default = [], bool $lazy = false): array;
+
+ /**
+ * returns the type of config value
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @return int
+ * @throws AppConfigUnknownKeyException
+ * @since 29.0.0
+ * @see VALUE_STRING
+ * @see VALUE_INT
+ * @see VALUE_FLOAT
+ * @see VALUE_BOOL
+ * @see VALUE_ARRAY
+ */
+ public function getValueType(string $app, string $key): int;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see setValueInt()
+ * @see setValueFloat()
+ * @see setValueBool()
+ * @see setValueArray()
+ */
+ public function setValueString(string $app, string $key, string $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * When handling huge value around and/or above 2,147,483,647, a debug log will be generated
+ * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers.
+ *
+ * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()}
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param int $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueFloat()
+ * @see setValueBool()
+ * @see setValueArray()
+ */
+ public function setValueInt(string $app, string $key, int $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * Store a config key and its value in database.
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param float $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueInt()
+ * @see setValueBool()
+ * @see setValueArray()
+ */
+ public function setValueFloat(string $app, string $key, float $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $value config value
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueInt()
+ * @see setValueFloat()
+ * @see setValueArray()
+ */
+ public function setValueBool(string $app, string $key, bool $value, bool $lazy = false): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param array $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see IAppConfig for explanation about lazy loading
+ * @see setValueString()
+ * @see setValueInt()
+ * @see setValueFloat()
+ * @see setValueBool()
+ */
+ public function setValueArray(string $app, string $key, array $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * switch sensitive status of a config value
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
+ *
+ * @return bool TRUE if database update were necessary
+ * @since 29.0.0
+ */
+ public function updateSensitive(string $app, string $key, bool $sensitive): bool;
+
+ /**
+ * switch lazy loading status of a config value
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
+ *
+ * @return bool TRUE if database update was necessary
+ * @since 29.0.0
+ */
+ public function updateLazy(string $app, string $key, bool $lazy): bool;
+
+ /**
+ * returns an array contains details about a config value
+ *
+ * ```
+ * [
+ * "app" => "myapp",
+ * "key" => "mykey",
+ * "value" => "its_value",
+ * "lazy" => false,
+ * "type" => 4,
+ * "typeString" => "string",
+ * 'sensitive' => true
+ * ]
+ * ```
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ *
+ * @return array
+ * @throws AppConfigUnknownKeyException if config key is not known in database
+ * @since 29.0.0
+ */
+ public function getDetails(string $app, string $key): array;
+
+ /**
+ * Convert string like 'string', 'integer', 'float', 'bool' or 'array' to
+ * to bitflag {@see VALUE_STRING}, {@see VALUE_INT}, {@see VALUE_FLOAT},
+ * {@see VALUE_BOOL} and {@see VALUE_ARRAY}
+ *
+ * @param string $type
+ *
+ * @return int
+ * @since 29.0.0
+ */
+ public function convertTypeToInt(string $type): int;
+
+ /**
+ * Convert bitflag {@see VALUE_STRING}, {@see VALUE_INT}, {@see VALUE_FLOAT},
+ * {@see VALUE_BOOL} and {@see VALUE_ARRAY} to human-readable string
+ *
+ * @param int $type
+ *
+ * @return string
+ * @since 29.0.0
+ */
+ public function convertTypeToString(int $type): string;
+
+ /**
+ * Delete single config key from database.
+ *
+ * @param string $app id of the app
+ * @param string $key config key
+ * @since 29.0.0
+ */
+ public function deleteKey(string $app, string $key): void;
+
+ /**
+ * delete all config keys linked to an app
+ *
+ * @param string $app id of the app
+ * @since 29.0.0
+ */
+ public function deleteApp(string $app): void;
+
+ /**
+ * Clear the cache.
+ *
+ * The cache will be rebuilt only the next time a config value is requested.
+ *
+ * @param bool $reload set to TRUE to refill cache instantly after clearing it
+ * @since 29.0.0
+ */
+ public function clearCache(bool $reload = false): void;
/**
* get multiply values, either the app or key can be used as wildcard by setting it to false
*
* @param string|false $key
* @param string|false $app
+ *
* @return array|false
* @since 7.0.0
+ * @deprecated 29.0.0 Use {@see getAllValues()} or {@see searchValues()}
*/
public function getValues($app, $key);
@@ -55,18 +516,10 @@ interface IAppConfig {
* get all values of the app or and filters out sensitive data
*
* @param string $app
+ *
* @return array
* @since 12.0.0
+ * @deprecated 29.0.0 Use {@see getAllValues()} or {@see searchValues()}
*/
public function getFilteredValues($app);
-
- /**
- * Get all apps using the config
- * @return string[] an array of app ids
- *
- * This function returns a list of all apps that have at least one
- * entry in the appconfig table.
- * @since 7.0.0
- */
- public function getApps();
}
diff --git a/lib/public/IConfig.php b/lib/public/IConfig.php
index 0e7a7523218..706e4776221 100644
--- a/lib/public/IConfig.php
+++ b/lib/public/IConfig.php
@@ -126,6 +126,7 @@ interface IConfig {
* @param string $appName the appName that we stored the value under
* @return string[] the keys stored for the app
* @since 8.0.0
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function getAppKeys($appName);
@@ -137,6 +138,7 @@ interface IConfig {
* @param string $value the value that should be stored
* @return void
* @since 6.0.0
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function setAppValue($appName, $key, $value);
@@ -146,8 +148,10 @@ interface IConfig {
* @param string $appName the appName that we stored the value under
* @param string $key the key of the value, under which it was saved
* @param string $default the default value to be returned if the value isn't set
+ *
* @return string the saved value
* @since 6.0.0 - parameter $default was added in 7.0.0
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function getAppValue($appName, $key, $default = '');
@@ -157,6 +161,7 @@ interface IConfig {
* @param string $appName the appName that we stored the value under
* @param string $key the key of the value, under which it was saved
* @since 8.0.0
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function deleteAppValue($appName, $key);
@@ -165,6 +170,7 @@ interface IConfig {
*
* @param string $appName the appName the configs are stored under
* @since 8.0.0
+ * @deprecated 29.0.0 Use {@see IAppConfig} directly
*/
public function deleteAppValues($appName);
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/IMemcacheTTL.php b/lib/public/IMemcacheTTL.php
index 8424e5b2bfa..250e727308b 100644
--- a/lib/public/IMemcacheTTL.php
+++ b/lib/public/IMemcacheTTL.php
@@ -35,5 +35,22 @@ interface IMemcacheTTL extends IMemcache {
* @param int $ttl time to live in seconds
* @since 8.2.2
*/
- public function setTTL($key, $ttl);
+ public function setTTL(string $key, int $ttl);
+
+ /**
+ * Get the ttl for an existing value, in seconds till expiry
+ *
+ * @return int|false
+ * @since 27
+ */
+ public function getTTL(string $key): int|false;
+ /**
+ * Set the ttl for an existing value if the value matches
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param int $ttl time to live in seconds
+ * @since 27
+ */
+ public function compareSetTTL(string $key, $value, int $ttl): 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/Log/Audit/CriticalActionPerformedEvent.php b/lib/public/Log/Audit/CriticalActionPerformedEvent.php
index 79c67e5b8bd..45786a5c980 100644
--- a/lib/public/Log/Audit/CriticalActionPerformedEvent.php
+++ b/lib/public/Log/Audit/CriticalActionPerformedEvent.php
@@ -49,8 +49,8 @@ class CriticalActionPerformedEvent extends Event {
* @since 22.0.0
*/
public function __construct(string $logMessage,
- array $parameters = [],
- bool $obfuscateParameters = false) {
+ array $parameters = [],
+ bool $obfuscateParameters = false) {
parent::__construct();
$this->logMessage = $logMessage;
$this->parameters = $parameters;
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/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/Search/SearchResult.php b/lib/public/Search/SearchResult.php
index 9ac8b28fb8b..892c60777b3 100644
--- a/lib/public/Search/SearchResult.php
+++ b/lib/public/Search/SearchResult.php
@@ -54,9 +54,9 @@ final class SearchResult implements JsonSerializable {
* @since 20.0.0
*/
private function __construct(string $name,
- bool $isPaginated,
- array $entries,
- $cursor = null) {
+ bool $isPaginated,
+ array $entries,
+ $cursor = null) {
$this->name = $name;
$this->isPaginated = $isPaginated;
$this->entries = $entries;
@@ -87,8 +87,8 @@ final class SearchResult implements JsonSerializable {
* @since 20.0.0
*/
public static function paginated(string $name,
- array $entries,
- $cursor): self {
+ array $entries,
+ $cursor): self {
return new self(
$name,
true,
diff --git a/lib/public/Search/SearchResultEntry.php b/lib/public/Search/SearchResultEntry.php
index 86a3f08cfe6..f4ff1ec2a5c 100644
--- a/lib/public/Search/SearchResultEntry.php
+++ b/lib/public/Search/SearchResultEntry.php
@@ -98,11 +98,11 @@ class SearchResultEntry implements JsonSerializable {
* @since 20.0.0
*/
public function __construct(string $thumbnailUrl,
- string $title,
- string $subline,
- string $resourceUrl,
- string $icon = '',
- bool $rounded = false) {
+ string $title,
+ string $subline,
+ string $resourceUrl,
+ string $icon = '',
+ bool $rounded = false) {
$this->thumbnailUrl = $thumbnailUrl;
$this->title = $title;
$this->subline = $subline;
diff --git a/lib/public/Security/ISecureRandom.php b/lib/public/Security/ISecureRandom.php
index 3634ebf99f7..48013b2cca7 100644
--- a/lib/public/Security/ISecureRandom.php
+++ b/lib/public/Security/ISecureRandom.php
@@ -64,5 +64,5 @@ interface ISecureRandom {
* @since 8.0.0
*/
public function generate(int $length,
- string $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'): string;
+ string $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'): string;
}
diff --git a/lib/public/Security/RateLimiting/ILimiter.php b/lib/public/Security/RateLimiting/ILimiter.php
index 275746b0b49..cfc7387664d 100644
--- a/lib/public/Security/RateLimiting/ILimiter.php
+++ b/lib/public/Security/RateLimiting/ILimiter.php
@@ -50,9 +50,9 @@ interface ILimiter {
*
*/
public function registerAnonRequest(string $identifier,
- int $anonLimit,
- int $anonPeriod,
- string $ip): void;
+ int $anonLimit,
+ int $anonPeriod,
+ string $ip): void;
/**
* Registers attempt for an authenticated request
@@ -66,7 +66,7 @@ interface ILimiter {
*
*/
public function registerUserRequest(string $identifier,
- int $userLimit,
- int $userPeriod,
- IUser $user): void;
+ int $userLimit,
+ int $userPeriod,
+ IUser $user): void;
}
diff --git a/lib/public/Settings/IManager.php b/lib/public/Settings/IManager.php
index 10de596dbea..49bac831dc2 100644
--- a/lib/public/Settings/IManager.php
+++ b/lib/public/Settings/IManager.php
@@ -6,6 +6,7 @@
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
+ * @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
@@ -34,34 +35,48 @@ use OCP\IUser;
interface IManager {
/**
* @since 9.1.0
+ * @depreacted 29.0.0 Use {@see self::SETTINGS_ADMIN} instead
*/
public const KEY_ADMIN_SETTINGS = 'admin';
/**
* @since 9.1.0
+ * @depreacted 29.0.0 Use {@see self::SETTINGS_ADMIN} instead
*/
public const KEY_ADMIN_SECTION = 'admin-section';
/**
* @since 13.0.0
+ * @depreacted 29.0.0 Use {@see self::SETTINGS_PERSONAL} instead
*/
public const KEY_PERSONAL_SETTINGS = 'personal';
/**
* @since 13.0.0
+ * @depreacted 29.0.0 Use {@see self::SETTINGS_PERSONAL} instead
*/
public const KEY_PERSONAL_SECTION = 'personal-section';
/**
- * @param string $type 'admin-section' or 'personal-section'
- * @param string $section Class must implement OCP\Settings\ISection
+ * @since 29.0.0
+ */
+ public const SETTINGS_ADMIN = 'admin';
+
+ /**
+ * @since 29.0.0
+ */
+ public const SETTINGS_PERSONAL = 'personal';
+
+ /**
+ * @psalm-param self::SETTINGS_* $type
+ * @param class-string<IIconSection> $section
* @since 14.0.0
*/
public function registerSection(string $type, string $section);
/**
- * @param string $type 'admin' or 'personal'
- * @param string $setting Class must implement OCP\Settings\ISettings
+ * @psalm-param self::SETTINGS_* $type
+ * @param class-string<ISettings> $setting
* @since 14.0.0
*/
public function registerSetting(string $type, string $setting);
@@ -69,7 +84,7 @@ interface IManager {
/**
* returns a list of the admin sections
*
- * @return array<int, array<int, IIconSection>> array from IConSection[] where key is the priority
+ * @return array<int, list<IIconSection>> list of sections with priority as key
* @since 9.1.0
*/
public function getAdminSections(): array;
@@ -77,7 +92,7 @@ interface IManager {
/**
* returns a list of the personal sections
*
- * @return array array of ISection[] where key is the priority
+ * @return array<int, list<IIconSection>> list of sections with priority as key
* @since 13.0.0
*/
public function getPersonalSections(): array;
@@ -87,10 +102,10 @@ interface IManager {
*
* @param string $section the section id for which to load the settings
* @param bool $subAdminOnly only return settings sub admins are supposed to see (since 17.0.0)
- * @return array<int, array<int, ISettings>> array of ISettings[] where key is the priority
+ * @return array<int, list<ISettings>> list of settings with priority as key
* @since 9.1.0
*/
- public function getAdminSettings($section, bool $subAdminOnly = false): array;
+ public function getAdminSettings(string $section, bool $subAdminOnly = false): array;
/**
* Returns a list of admin settings that the given user can use for the give section
@@ -103,7 +118,7 @@ interface IManager {
/**
* Returns a list of admin settings that the given user can use.
*
- * @return array<int, list<ISettings>> The array of admin settings there admin delegation is allowed.
+ * @return list<ISettings> The array of admin settings there admin delegation is allowed.
* @since 23.0.0
*/
public function getAllAllowedAdminSettings(IUser $user): array;
@@ -112,13 +127,14 @@ interface IManager {
* returns a list of the personal settings
*
* @param string $section the section id for which to load the settings
- * @return array array of ISettings[] where key is the priority
+ * @return array<int, list<ISettings>> list of settings with priority as key
* @since 13.0.0
*/
- public function getPersonalSettings($section): array;
+ public function getPersonalSettings(string $section): array;
/**
* Get a specific section by type and id
+ * @psalm-param self::SETTINGS_* $type
* @since 25.0.0
*/
public function getSection(string $type, string $sectionId): ?IIconSection;
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..e5183fa0a6d
--- /dev/null
+++ b/lib/public/SetupCheck/SetupResult.php
@@ -0,0 +1,189 @@
+<?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;
+
+use OCP\RichObjectStrings\IValidator;
+
+/**
+ * @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
+ * @throws \OCP\RichObjectStrings\InvalidObjectExeption
+ * @since 28.0.0
+ * @since 28.0.2 Optional parameter ?array $descriptionParameters
+ * @since 28.0.2 throws \OCP\RichObjectStrings\InvalidObjectExeption
+ */
+ private function __construct(
+ private string $severity,
+ private ?string $description = null,
+ private ?array $descriptionParameters = null,
+ private ?string $linkToDoc = null,
+ ) {
+ if ($description !== null && $descriptionParameters !== null) {
+ \OCP\Server::get(IValidator::class)->validate($description, $descriptionParameters);
+ }
+ }
+
+ /**
+ * @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
+ * @throws \OCP\RichObjectStrings\InvalidObjectExeption
+ * @since 28.0.0
+ * @since 28.0.2 Optional parameter ?array $descriptionParameters
+ * @since 28.0.2 throws \OCP\RichObjectStrings\InvalidObjectExeption
+ */
+ public static function success(?string $description = null, ?string $linkToDoc = null, ?array $descriptionParameters = null): self {
+ return new self(self::SUCCESS, $description, $descriptionParameters, $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
+ * @throws \OCP\RichObjectStrings\InvalidObjectExeption
+ * @since 28.0.0
+ * @since 28.0.2 Optional parameter ?array $descriptionParameters
+ * @since 28.0.2 throws \OCP\RichObjectStrings\InvalidObjectExeption
+ */
+ public static function info(?string $description = null, ?string $linkToDoc = null, ?array $descriptionParameters = null): self {
+ return new self(self::INFO, $description, $descriptionParameters, $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
+ * @throws \OCP\RichObjectStrings\InvalidObjectExeption
+ * @since 28.0.0
+ * @since 28.0.2 Optional parameter ?array $descriptionParameters
+ * @since 28.0.2 throws \OCP\RichObjectStrings\InvalidObjectExeption
+ */
+ public static function warning(?string $description = null, ?string $linkToDoc = null, ?array $descriptionParameters = null): self {
+ return new self(self::WARNING, $description, $descriptionParameters, $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
+ * @throws \OCP\RichObjectStrings\InvalidObjectExeption
+ * @since 28.0.0
+ * @since 28.0.2 Optional parameter ?array $descriptionParameters
+ * @since 28.0.2 throws \OCP\RichObjectStrings\InvalidObjectExeption
+ */
+ public static function error(?string $description = null, ?string $linkToDoc = null, ?array $descriptionParameters = null): self {
+ return new self(self::ERROR, $description, $descriptionParameters, $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 description parameters for the setup check result
+ *
+ * If this returns null, description must not be treated as rich text
+ *
+ * @since 28.0.2
+ */
+ public function getDescriptionParameters(): ?array {
+ return $this->descriptionParameters;
+ }
+
+ /**
+ * @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,
+ 'descriptionParameters' => $this->descriptionParameters,
+ 'linkToDoc' => $this->linkToDoc,
+ ];
+ }
+}
diff --git a/lib/public/Share.php b/lib/public/Share.php
index 9499cdb14b6..225f252c2a9 100644
--- a/lib/public/Share.php
+++ b/lib/public/Share.php
@@ -116,7 +116,7 @@ class Share extends \OC\Share\Constants {
* * defacto $parameters and $format is always the default and therefore is removed in the subsequent call
*/
public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
- $parameters = null, $includeCollections = false) {
+ $parameters = null, $includeCollections = false) {
return \OC\Share\Share::getItemShared($itemType, $itemSource, self::FORMAT_NONE, null, $includeCollections);
}
diff --git a/lib/public/Share/Events/VerifyMountPointEvent.php b/lib/public/Share/Events/VerifyMountPointEvent.php
index 650f4ad2245..af71314930b 100644
--- a/lib/public/Share/Events/VerifyMountPointEvent.php
+++ b/lib/public/Share/Events/VerifyMountPointEvent.php
@@ -44,8 +44,8 @@ class VerifyMountPointEvent extends Event {
* @since 19.0.0
*/
public function __construct(IShare $share,
- View $view,
- string $parent) {
+ View $view,
+ string $parent) {
parent::__construct();
$this->share = $share;
diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php
index 9ac224ed7ef..07517dd7eb5 100644
--- a/lib/public/Share/IManager.php
+++ b/lib/public/Share/IManager.php
@@ -416,6 +416,15 @@ interface IManager {
public function shareWithGroupMembersOnly();
/**
+ * If shareWithGroupMembersOnly is enabled, return an optional
+ * list of groups that must be excluded from the principle of
+ * belonging to the same group.
+ * @return array
+ * @since 27.0.0
+ */
+ public function shareWithGroupMembersOnlyExcludeGroupsList();
+
+ /**
* Check if users can share with groups
* @return bool
* @since 9.0.1
diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php
index 40548c6c73d..74d404101cd 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();
@@ -474,7 +474,7 @@ interface IShare {
* If this share is obtained via a shareprovider the password is
* hashed.
*
- * @return string
+ * @return string|null
* @since 9.0.0
*/
public function getPassword();
diff --git a/lib/public/Share/IShareProvider.php b/lib/public/Share/IShareProvider.php
index b6e0c4ba38b..2a37508d449 100644
--- a/lib/public/Share/IShareProvider.php
+++ b/lib/public/Share/IShareProvider.php
@@ -70,7 +70,7 @@ interface IShareProvider {
* @return IShare The share object
* @since 17.0.0
*/
-// public function acceptShare(IShare $share, string $recipient): IShare;
+ // public function acceptShare(IShare $share, string $recipient): IShare;
/**
* Delete a share
diff --git a/lib/public/SpeechToText/ISpeechToTextProviderWithId.php b/lib/public/SpeechToText/ISpeechToTextProviderWithId.php
new file mode 100644
index 00000000000..0fb337f4602
--- /dev/null
+++ b/lib/public/SpeechToText/ISpeechToTextProviderWithId.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace OCP\SpeechToText;
+
+/**
+ * @since 28.0.0
+ */
+interface ISpeechToTextProviderWithId extends ISpeechToTextProvider {
+
+ /**
+ * @since 28.0.0
+ */
+ public function getId(): string;
+}
diff --git a/lib/public/Talk/IBroker.php b/lib/public/Talk/IBroker.php
index d3b6e1429e6..f2b512ea4a8 100644
--- a/lib/public/Talk/IBroker.php
+++ b/lib/public/Talk/IBroker.php
@@ -68,8 +68,8 @@ interface IBroker {
* @since 24.0.0
*/
public function createConversation(string $name,
- array $moderators,
- IConversationOptions $options = null): IConversation;
+ array $moderators,
+ IConversationOptions $options = null): IConversation;
/**
* Delete a conversation by id
diff --git a/lib/public/Talk/ITalkBackend.php b/lib/public/Talk/ITalkBackend.php
index 3ee995576a4..a2f4d962019 100644
--- a/lib/public/Talk/ITalkBackend.php
+++ b/lib/public/Talk/ITalkBackend.php
@@ -46,8 +46,8 @@ interface ITalkBackend {
* @since 24.0.0
*/
public function createConversation(string $name,
- array $moderators,
- IConversationOptions $options): IConversation;
+ array $moderators,
+ IConversationOptions $options): IConversation;
/**
* Delete a conversation by id
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..2f517a4ebe2 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;
/**
@@ -48,7 +50,7 @@ interface IManager {
public function getProviders(): array;
/**
- * @return class-string<ITaskType>[]
+ * @return string[]
* @since 27.1.0
*/
public function getAvailableTaskTypes(): array;
@@ -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/IProvider.php b/lib/public/TextProcessing/IProvider.php
index 6132e60b493..fc57add1835 100644
--- a/lib/public/TextProcessing/IProvider.php
+++ b/lib/public/TextProcessing/IProvider.php
@@ -31,7 +31,7 @@ use RuntimeException;
/**
* This is the interface that is implemented by apps that
* implement a text processing provider
- * @template T of ITaskType
+ * @psalm-template-covariant T of ITaskType
* @since 27.1.0
*/
interface IProvider {
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/IProviderWithId.php b/lib/public/TextProcessing/IProviderWithId.php
new file mode 100644
index 00000000000..1bd02278d1c
--- /dev/null
+++ b/lib/public/TextProcessing/IProviderWithId.php
@@ -0,0 +1,39 @@
+<?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;
+
+/**
+ * @since 28.0.0
+ * @extends IProvider<T>
+ * @template T of ITaskType
+ */
+interface IProviderWithId extends IProvider {
+ /**
+ * The id of this provider
+ * @since 28.0.0
+ */
+ public function getId(): string;
+}
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..c62b7b2fff8 100644
--- a/lib/public/TextProcessing/Task.php
+++ b/lib/public/TextProcessing/Task.php
@@ -28,13 +28,12 @@ namespace OCP\TextProcessing;
/**
* This is a text processing task
* @since 27.1.0
- * @psalm-template T of ITaskType
- * @psalm-template S as class-string<T>
- * @psalm-template P as IProvider<T>
+ * @psalm-template-covariant T of ITaskType
*/
final class Task implements \JsonSerializable {
protected ?int $id = null;
protected ?string $output = null;
+ private ?\DateTime $completionExpectedAt = null;
/**
* @since 27.1.0
@@ -73,7 +72,7 @@ final class Task implements \JsonSerializable {
protected int $status = self::STATUS_UNKNOWN;
/**
- * @psalm-param S $type
+ * @psalm-param class-string<T> $type
* @param string $type
* @param string $input
* @param string $appId
@@ -91,13 +90,16 @@ final class Task implements \JsonSerializable {
}
/**
- * @psalm-param P $provider
+ * @psalm-param IProvider<T> $provider
* @param IProvider $provider
* @return string
* @since 27.1.0
*/
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());
@@ -105,7 +107,7 @@ final class Task implements \JsonSerializable {
}
/**
- * @psalm-param P $provider
+ * @psalm-param IProvider<T> $provider
* @param IProvider $provider
* @return bool
* @since 27.1.0
@@ -115,7 +117,7 @@ final class Task implements \JsonSerializable {
}
/**
- * @psalm-return S
+ * @psalm-return class-string<T>
* @since 27.1.0
*/
final public function getType(): string {
@@ -203,7 +205,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: class-string<T>, 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 +218,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..f2092476e78
--- /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 list<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/IProviderWithUserId.php b/lib/public/TextToImage/IProviderWithUserId.php
new file mode 100644
index 00000000000..8afb0e56fbb
--- /dev/null
+++ b/lib/public/TextToImage/IProviderWithUserId.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCP\TextToImage;
+
+/**
+ * @since 29.0.0
+ */
+interface IProviderWithUserId extends IProvider {
+ /**
+ * @since 29.0.0
+ */
+ public function setUserId(?string $userId): void;
+}
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/Translation/ITranslationProviderWithId.php b/lib/public/Translation/ITranslationProviderWithId.php
new file mode 100644
index 00000000000..fa08ef7cb17
--- /dev/null
+++ b/lib/public/Translation/ITranslationProviderWithId.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2024 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\Translation;
+
+/**
+ * @since 29.0.0
+ */
+interface ITranslationProviderWithId extends ITranslationProvider {
+ /**
+ * @since 29.0.0
+ */
+ public function getId(): string;
+}
diff --git a/lib/public/Translation/ITranslationProviderWithUserId.php b/lib/public/Translation/ITranslationProviderWithUserId.php
new file mode 100644
index 00000000000..9a573a8150e
--- /dev/null
+++ b/lib/public/Translation/ITranslationProviderWithUserId.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2024 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\Translation;
+
+/**
+ * @since 29.0.0
+ */
+interface ITranslationProviderWithUserId extends ITranslationProvider {
+ /**
+ * @param string|null $userId The userId of the user requesting the current task
+ * @since 29.0.0
+ */
+ public function setUserId(?string $userId);
+}
diff --git a/lib/public/User/Backend/IProvideEnabledStateBackend.php b/lib/public/User/Backend/IProvideEnabledStateBackend.php
index d03beacd7b8..f12d99fd1a6 100644
--- a/lib/public/User/Backend/IProvideEnabledStateBackend.php
+++ b/lib/public/User/Backend/IProvideEnabledStateBackend.php
@@ -52,5 +52,5 @@ interface IProvideEnabledStateBackend {
*
* @return string[]
*/
- public function getDisabledUserList(int $offset = 0, ?int $limit = null): array;
+ public function getDisabledUserList(?int $limit = null, int $offset = 0): array;
}
diff --git a/lib/public/User/Events/BeforePasswordUpdatedEvent.php b/lib/public/User/Events/BeforePasswordUpdatedEvent.php
index 11eb5ad9dd0..ee228ae01e7 100644
--- a/lib/public/User/Events/BeforePasswordUpdatedEvent.php
+++ b/lib/public/User/Events/BeforePasswordUpdatedEvent.php
@@ -51,8 +51,8 @@ class BeforePasswordUpdatedEvent extends Event {
* @since 18.0.0
*/
public function __construct(IUser $user,
- string $password,
- string $recoveryPassword = null) {
+ string $password,
+ string $recoveryPassword = null) {
parent::__construct();
$this->user = $user;
$this->password = $password;
diff --git a/lib/public/User/Events/BeforeUserCreatedEvent.php b/lib/public/User/Events/BeforeUserCreatedEvent.php
index 67e9177b34d..ee33239a12c 100644
--- a/lib/public/User/Events/BeforeUserCreatedEvent.php
+++ b/lib/public/User/Events/BeforeUserCreatedEvent.php
@@ -44,7 +44,7 @@ class BeforeUserCreatedEvent extends Event {
* @since 18.0.0
*/
public function __construct(string $uid,
- string $password) {
+ string $password) {
parent::__construct();
$this->uid = $uid;
$this->password = $password;
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/OutOfOfficeEndedEvent.php b/lib/public/User/Events/OutOfOfficeEndedEvent.php
new file mode 100644
index 00000000000..43a6bf77e28
--- /dev/null
+++ b/lib/public/User/Events/OutOfOfficeEndedEvent.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
+ *
+ * @author Richard Steinmetz <richard@steinmetz.cloud>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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 ended
+ *
+ * @since 28.0.0
+ */
+class OutOfOfficeEndedEvent 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/Events/OutOfOfficeStartedEvent.php b/lib/public/User/Events/OutOfOfficeStartedEvent.php
new file mode 100644
index 00000000000..f7816c968dd
--- /dev/null
+++ b/lib/public/User/Events/OutOfOfficeStartedEvent.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
+ *
+ * @author Richard Steinmetz <richard@steinmetz.cloud>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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 started
+ *
+ * @since 28.0.0
+ */
+class OutOfOfficeStartedEvent 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/PasswordUpdatedEvent.php b/lib/public/User/Events/PasswordUpdatedEvent.php
index 41d510553b5..782d6d270ea 100644
--- a/lib/public/User/Events/PasswordUpdatedEvent.php
+++ b/lib/public/User/Events/PasswordUpdatedEvent.php
@@ -51,8 +51,8 @@ class PasswordUpdatedEvent extends Event {
* @since 18.0.0
*/
public function __construct(IUser $user,
- string $password,
- string $recoveryPassword = null) {
+ string $password,
+ string $recoveryPassword = null) {
parent::__construct();
$this->user = $user;
$this->password = $password;
diff --git a/lib/public/User/Events/UserChangedEvent.php b/lib/public/User/Events/UserChangedEvent.php
index f48dd3914e6..870b0326920 100644
--- a/lib/public/User/Events/UserChangedEvent.php
+++ b/lib/public/User/Events/UserChangedEvent.php
@@ -43,9 +43,9 @@ class UserChangedEvent extends Event {
* @since 18.0.0
*/
public function __construct(IUser $user,
- string $feature,
- $value,
- $oldValue = null) {
+ string $feature,
+ $value,
+ $oldValue = null) {
parent::__construct();
$this->user = $user;
$this->feature = $feature;
diff --git a/lib/public/User/Events/UserCreatedEvent.php b/lib/public/User/Events/UserCreatedEvent.php
index 7d343bfd5b8..b0a734be0cb 100644
--- a/lib/public/User/Events/UserCreatedEvent.php
+++ b/lib/public/User/Events/UserCreatedEvent.php
@@ -45,7 +45,7 @@ class UserCreatedEvent extends Event {
* @since 18.0.0
*/
public function __construct(IUser $user,
- string $password) {
+ string $password) {
parent::__construct();
$this->user = $user;
$this->password = $password;
diff --git a/lib/public/User/Events/UserLiveStatusEvent.php b/lib/public/User/Events/UserLiveStatusEvent.php
index d04c3b61e24..8b6207d685d 100644
--- a/lib/public/User/Events/UserLiveStatusEvent.php
+++ b/lib/public/User/Events/UserLiveStatusEvent.php
@@ -60,8 +60,8 @@ class UserLiveStatusEvent extends Event {
* @since 20.0.0
*/
public function __construct(IUser $user,
- string $status,
- int $timestamp) {
+ string $status,
+ int $timestamp) {
parent::__construct();
$this->user = $user;
$this->status = $status;
diff --git a/lib/public/User/IAvailabilityCoordinator.php b/lib/public/User/IAvailabilityCoordinator.php
new file mode 100644
index 00000000000..3a79e39b7b7
--- /dev/null
+++ b/lib/public/User/IAvailabilityCoordinator.php
@@ -0,0 +1,67 @@
+<?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;
+
+ /**
+ * Reset the absence cache to null
+ *
+ * @since 28.0.0
+ */
+ public function clearCache(string $userId): void;
+
+ /**
+ * Is the absence in effect at this moment
+ *
+ * @param IOutOfOfficeData $data
+ * @return bool
+ * @since 28.0.0
+ */
+ public function isInEffect(IOutOfOfficeData $data): bool;
+}
diff --git a/lib/public/User/IOutOfOfficeData.php b/lib/public/User/IOutOfOfficeData.php
new file mode 100644
index 00000000000..31281104382
--- /dev/null
+++ b/lib/public/User/IOutOfOfficeData.php
@@ -0,0 +1,94 @@
+<?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 JsonSerializable;
+use OCP\IUser;
+
+/**
+ * DTO to hold out-of-office information of a user
+ *
+ * @psalm-type OutOfOfficeData = array{
+ * id: string,
+ * userId: string,
+ * startDate: int,
+ * endDate: int,
+ * shortMessage: string,
+ * message: string,
+ * }
+ *
+ * @since 28.0.0
+ */
+interface IOutOfOfficeData extends JsonSerializable {
+ /**
+ * 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;
+
+ /**
+ * @return OutOfOfficeData
+ *
+ * @since 28.0.0
+ */
+ public function jsonSerialize(): array;
+}
diff --git a/lib/public/UserStatus/IManager.php b/lib/public/UserStatus/IManager.php
index 9cc8eaad8ee..a85c1894c65 100644
--- a/lib/public/UserStatus/IManager.php
+++ b/lib/public/UserStatus/IManager.php
@@ -52,9 +52,11 @@ interface IManager {
* @param string $messageId The id of the predefined message.
* @param string $status The status to assign
* @param bool $createBackup If true, this will store the old status so that it is possible to revert it later (e.g. after a call).
+ * @param string|null $customMessage
* @since 23.0.0
+ * @since 28.0.0 Optional parameter $customMessage was added
*/
- public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false): void;
+ public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false, ?string $customMessage = null): void;
/**
* Revert an automatically set user status. For example after leaving a call,
diff --git a/lib/public/UserStatus/IUserStatus.php b/lib/public/UserStatus/IUserStatus.php
index 74c54cc9da2..f167f9a82ee 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,30 @@ interface IUserStatus {
public const MESSAGE_AVAILABILITY = 'availability';
/**
+ * @var string
+ * @since 28.0.1
+ */
+ public const MESSAGE_OUT_OF_OFFICE = 'out-of-office';
+
+ /**
+ * @var string
+ * @since 28.0.0
+ */
+ public const MESSAGE_VACATION = 'vacationing';
+
+ /**
+ * @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 cabb84c0cf6..6322ab56a88 100644
--- a/lib/public/Util.php
+++ b/lib/public/Util.php
@@ -46,9 +46,9 @@
namespace OCP;
+use bantu\IniGetWrapper\IniGetWrapper;
use OC\AppScriptDependency;
use OC\AppScriptSort;
-use bantu\IniGetWrapper\IniGetWrapper;
use OCP\Share\IManager;
use Psr\Container\ContainerExceptionInterface;
@@ -161,6 +161,12 @@ class Util {
$path = "js/$file";
}
+ // We need to handle the translation BEFORE the init script
+ // is loaded, as the init script might use translations
+ if ($application !== 'core' && !str_contains($file, 'l10n')) {
+ self::addTranslations($application, null, true);
+ }
+
self::$scriptsInit[] = $path;
}
@@ -233,9 +239,10 @@ class Util {
* Add a translation JS file
* @param string $application application id
* @param string $languageCode language code, defaults to the current locale
+ * @param bool $init whether the translations should be loaded early or not
* @since 8.0.0
*/
- public static function addTranslations($application, $languageCode = null) {
+ public static function addTranslations($application, $languageCode = null, $init = false) {
if (is_null($languageCode)) {
$languageCode = \OC::$server->getL10NFactory()->findLanguage($application);
}
@@ -244,7 +251,12 @@ class Util {
} else {
$path = "l10n/$languageCode";
}
- self::$scripts[$application][] = $path;
+
+ if ($init) {
+ self::$scriptsInit[] = $path;
+ } else {
+ self::$scripts[$application][] = $path;
+ }
}
/**