diff options
author | Marcel Klehr <mklehr@gmx.net> | 2023-12-19 12:29:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-19 12:29:03 +0100 |
commit | 2e0141165aef63fa494ec8f7668ccdb379e8d55e (patch) | |
tree | e3b15303961a8e30e7bc939b6216aa8212af6f2c /lib | |
parent | ab736429ce1bf126bd8b1bef1db4cac9a31e139e (diff) | |
parent | d8381acf861202bed821e92f3cf8647db87a0efe (diff) | |
download | nextcloud-server-2e0141165aef63fa494ec8f7668ccdb379e8d55e.tar.gz nextcloud-server-2e0141165aef63fa494ec8f7668ccdb379e8d55e.zip |
Merge branch 'master' into enh/text-processing-provider-with-id
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Diffstat (limited to 'lib')
500 files changed, 10296 insertions, 2616 deletions
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 d57b1925d8e..6cbb31b7fbf 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', @@ -169,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', @@ -210,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', @@ -284,6 +288,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', @@ -575,6 +591,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', @@ -664,15 +685,27 @@ 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\\IProviderWithId' => $baseDir . '/lib/public/TextProcessing/IProviderWithId.php', + 'OCP\\TextProcessing\\IProviderWithExpectedRuntime' => $baseDir . '/lib/public/TextProcessing/IProviderWithExpectedRuntime.php', + 'OCP\\TextProcessing\\IProviderWithUserId' => $baseDir . '/lib/public/TextProcessing/IProviderWithUserId.php', 'OCP\\TextProcessing\\ITaskType' => $baseDir . '/lib/public/TextProcessing/ITaskType.php', 'OCP\\TextProcessing\\SummaryTaskType' => $baseDir . '/lib/public/TextProcessing/SummaryTaskType.php', 'OCP\\TextProcessing\\Task' => $baseDir . '/lib/public/TextProcessing/Task.php', 'OCP\\TextProcessing\\TopicsTaskType' => $baseDir . '/lib/public/TextProcessing/TopicsTaskType.php', + 'OCP\\TextToImage\\Events\\AbstractTextToImageEvent' => $baseDir . '/lib/public/TextToImage/Events/AbstractTextToImageEvent.php', + 'OCP\\TextToImage\\Events\\TaskFailedEvent' => $baseDir . '/lib/public/TextToImage/Events/TaskFailedEvent.php', + 'OCP\\TextToImage\\Events\\TaskSuccessfulEvent' => $baseDir . '/lib/public/TextToImage/Events/TaskSuccessfulEvent.php', + 'OCP\\TextToImage\\Exception\\TaskFailureException' => $baseDir . '/lib/public/TextToImage/Exception/TaskFailureException.php', + 'OCP\\TextToImage\\Exception\\TaskNotFoundException' => $baseDir . '/lib/public/TextToImage/Exception/TaskNotFoundException.php', + 'OCP\\TextToImage\\Exception\\TextToImageException' => $baseDir . '/lib/public/TextToImage/Exception/TextToImageException.php', + 'OCP\\TextToImage\\IManager' => $baseDir . '/lib/public/TextToImage/IManager.php', + 'OCP\\TextToImage\\IProvider' => $baseDir . '/lib/public/TextToImage/IProvider.php', + 'OCP\\TextToImage\\Task' => $baseDir . '/lib/public/TextToImage/Task.php', 'OCP\\Translation\\CouldNotTranslateException' => $baseDir . '/lib/public/Translation/CouldNotTranslateException.php', 'OCP\\Translation\\IDetectLanguageProvider' => $baseDir . '/lib/public/Translation/IDetectLanguageProvider.php', 'OCP\\Translation\\ITranslationManager' => $baseDir . '/lib/public/Translation/ITranslationManager.php', @@ -709,6 +742,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', @@ -720,6 +758,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', @@ -967,6 +1007,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', @@ -1014,6 +1055,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', @@ -1049,6 +1091,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', @@ -1103,6 +1146,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', @@ -1184,6 +1228,10 @@ 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\\Version29000Date20231213104850' => $baseDir . '/core/Migrations/Version29000Date20231213104850.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', @@ -1268,6 +1316,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', @@ -1301,6 +1358,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', @@ -1460,14 +1518,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', @@ -1496,6 +1546,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', @@ -1559,6 +1610,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', @@ -1607,6 +1659,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', @@ -1614,6 +1676,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', @@ -1720,6 +1783,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', @@ -1729,6 +1797,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', @@ -1738,6 +1807,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 768a7a956e0..3fb9f20d72d 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', @@ -202,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', @@ -243,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', @@ -317,6 +321,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', @@ -608,6 +624,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', @@ -697,15 +718,27 @@ 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\\IProviderWithId' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithId.php', + 'OCP\\TextProcessing\\IProviderWithExpectedRuntime' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithExpectedRuntime.php', + 'OCP\\TextProcessing\\IProviderWithUserId' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithUserId.php', 'OCP\\TextProcessing\\ITaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/ITaskType.php', 'OCP\\TextProcessing\\SummaryTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/SummaryTaskType.php', 'OCP\\TextProcessing\\Task' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Task.php', 'OCP\\TextProcessing\\TopicsTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/TopicsTaskType.php', + 'OCP\\TextToImage\\Events\\AbstractTextToImageEvent' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Events/AbstractTextToImageEvent.php', + 'OCP\\TextToImage\\Events\\TaskFailedEvent' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Events/TaskFailedEvent.php', + 'OCP\\TextToImage\\Events\\TaskSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Events/TaskSuccessfulEvent.php', + 'OCP\\TextToImage\\Exception\\TaskFailureException' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Exception/TaskFailureException.php', + 'OCP\\TextToImage\\Exception\\TaskNotFoundException' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Exception/TaskNotFoundException.php', + 'OCP\\TextToImage\\Exception\\TextToImageException' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Exception/TextToImageException.php', + 'OCP\\TextToImage\\IManager' => __DIR__ . '/../../..' . '/lib/public/TextToImage/IManager.php', + 'OCP\\TextToImage\\IProvider' => __DIR__ . '/../../..' . '/lib/public/TextToImage/IProvider.php', + 'OCP\\TextToImage\\Task' => __DIR__ . '/../../..' . '/lib/public/TextToImage/Task.php', 'OCP\\Translation\\CouldNotTranslateException' => __DIR__ . '/../../..' . '/lib/public/Translation/CouldNotTranslateException.php', 'OCP\\Translation\\IDetectLanguageProvider' => __DIR__ . '/../../..' . '/lib/public/Translation/IDetectLanguageProvider.php', 'OCP\\Translation\\ITranslationManager' => __DIR__ . '/../../..' . '/lib/public/Translation/ITranslationManager.php', @@ -742,6 +775,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', @@ -753,6 +791,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', @@ -1000,6 +1040,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', @@ -1047,6 +1088,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', @@ -1082,6 +1124,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', @@ -1136,6 +1179,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', @@ -1217,6 +1261,10 @@ 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\\Version29000Date20231213104850' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20231213104850.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', @@ -1301,6 +1349,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', @@ -1334,6 +1391,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', @@ -1493,14 +1551,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', @@ -1529,6 +1579,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', @@ -1592,6 +1643,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', @@ -1640,6 +1692,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', @@ -1647,6 +1709,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', @@ -1753,6 +1816,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', @@ -1762,6 +1830,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', @@ -1771,6 +1840,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 8e7dfd09696..ef0519ab7ab 100644 --- a/lib/l10n/ar.js +++ b/lib/l10n/ar.js @@ -270,13 +270,11 @@ 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 مطلوبة.", - "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك.", - "404" : "404" + "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك." }, "nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"); diff --git a/lib/l10n/ar.json b/lib/l10n/ar.json index abffc79003c..f08c7d59fb9 100644 --- a/lib/l10n/ar.json +++ b/lib/l10n/ar.json @@ -268,13 +268,11 @@ "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 مطلوبة.", - "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك.", - "404" : "404" + "Please upgrade your database version." : "رجاءً، قم بترقية إصدار قاعدة بياناتك." },"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;" }
\ No newline at end of file diff --git a/lib/l10n/bg.js b/lib/l10n/bg.js index 9603dd7adc0..fa4775849ad 100644 --- a/lib/l10n/bg.js +++ b/lib/l10n/bg.js @@ -260,13 +260,11 @@ 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", - "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни.", - "404" : "404" + "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/bg.json b/lib/l10n/bg.json index ef36ae9d24c..2cceb6a3614 100644 --- a/lib/l10n/bg.json +++ b/lib/l10n/bg.json @@ -258,13 +258,11 @@ "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", - "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни.", - "404" : "404" + "Please upgrade your database version." : "Моля, надстройте версията на вашата база данни." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/ca.js b/lib/l10n/ca.js index 6b2fc3a193e..74d84e04d48 100644 --- a/lib/l10n/ca.js +++ b/lib/l10n/ca.js @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Actualitzeu la versió de la base de dades.", - "404" : "404" + "Please upgrade your database version." : "Actualitzeu la versió de la base de dades." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/ca.json b/lib/l10n/ca.json index 54c1f2bac8b..12f2368d028 100644 --- a/lib/l10n/ca.json +++ b/lib/l10n/ca.json @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Actualitzeu la versió de la base de dades.", - "404" : "404" + "Please upgrade your database version." : "Actualitzeu la versió de la base de dades." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/cs.js b/lib/l10n/cs.js index 70f14a7bbff..de7ac919319 100644 --- a/lib/l10n/cs.js +++ b/lib/l10n/cs.js @@ -136,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á", @@ -240,7 +240,7 @@ OC.L10N.register( "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pro nápravu nastavte v souboru php.ini parametr <code>mbstring.func_overload</code> na <code>0</code>.", "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je patrně nastaveno tak, aby odstraňovalo bloky komentářů. Toto bude mít za následek znepřístupnění mnoha důležitých aplikací.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Toto je pravděpodobně způsobeno aplikacemi pro urychlení načítání jako jsou Zend OPcache nebo eAccelerator.", - "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, ale stále se tváří jako chybějící?", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, přesto jsou uváděny jako chybějící?", "Please ask your server administrator to restart the web server." : "Požádejte správce serveru, který využíváte o restart webového serveru.", "The required %s config variable is not configured in the config.php file." : "Požadovaná proměnná nastavení %s není v souboru s nastaveními config.php nastavena.", "Please ask your server administrator to check the Nextcloud configuration." : "Požádejte správce serveru, který využíváte, aby zkontroloval nastavení serveru.", @@ -270,13 +270,11 @@ 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ší.", - "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze.", - "404" : "404" + "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze." }, "nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;"); diff --git a/lib/l10n/cs.json b/lib/l10n/cs.json index 975de1cb0e6..ad244acd656 100644 --- a/lib/l10n/cs.json +++ b/lib/l10n/cs.json @@ -134,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á", @@ -238,7 +238,7 @@ "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pro nápravu nastavte v souboru php.ini parametr <code>mbstring.func_overload</code> na <code>0</code>.", "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP je patrně nastaveno tak, aby odstraňovalo bloky komentářů. Toto bude mít za následek znepřístupnění mnoha důležitých aplikací.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Toto je pravděpodobně způsobeno aplikacemi pro urychlení načítání jako jsou Zend OPcache nebo eAccelerator.", - "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, ale stále se tváří jako chybějící?", + "PHP modules have been installed, but they are still listed as missing?" : "PHP moduly jsou nainstalovány, přesto jsou uváděny jako chybějící?", "Please ask your server administrator to restart the web server." : "Požádejte správce serveru, který využíváte o restart webového serveru.", "The required %s config variable is not configured in the config.php file." : "Požadovaná proměnná nastavení %s není v souboru s nastaveními config.php nastavena.", "Please ask your server administrator to check the Nextcloud configuration." : "Požádejte správce serveru, který využíváte, aby zkontroloval nastavení serveru.", @@ -268,13 +268,11 @@ "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ší.", - "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze.", - "404" : "404" + "Please upgrade your database version." : "Aktualizujte verzi vámi využívané databáze." },"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;" }
\ No newline at end of file diff --git a/lib/l10n/da.js b/lib/l10n/da.js index 0988ca55bae..26becdb373b 100644 --- a/lib/l10n/da.js +++ b/lib/l10n/da.js @@ -76,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", @@ -269,13 +269,11 @@ 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.", - "Please upgrade your database version." : "Opgradér venligst din databaseversion.", - "404" : "404" + "Please upgrade your database version." : "Opgradér venligst din databaseversion." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/da.json b/lib/l10n/da.json index 699a5a37d25..d58800be580 100644 --- a/lib/l10n/da.json +++ b/lib/l10n/da.json @@ -74,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", @@ -267,13 +267,11 @@ "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.", - "Please upgrade your database version." : "Opgradér venligst din databaseversion.", - "404" : "404" + "Please upgrade your database version." : "Opgradér venligst din databaseversion." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/de.js b/lib/l10n/de.js index 4cb820c4ef1..d9eeb02812d 100644 --- a/lib/l10n/de.js +++ b/lib/l10n/de.js @@ -5,6 +5,7 @@ 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.", "The page could not be found on the server." : "Die Seite konnte auf dem Server nicht gefunden werden.", @@ -95,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", @@ -105,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", @@ -154,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", @@ -259,14 +261,20 @@ 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", - "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion", - "404" : "404" + "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion" }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/de.json b/lib/l10n/de.json index e548559d76f..8cd0a9ef87d 100644 --- a/lib/l10n/de.json +++ b/lib/l10n/de.json @@ -3,6 +3,7 @@ "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.", "The page could not be found on the server." : "Die Seite konnte auf dem Server nicht gefunden werden.", @@ -93,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", @@ -103,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", @@ -152,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", @@ -257,14 +259,20 @@ "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", - "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion", - "404" : "404" + "Please upgrade your database version." : "Bitte aktualisiere deine Datenbankversion" },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/de_DE.js b/lib/l10n/de_DE.js index fdc840d0b19..8371cf8ecd3 100644 --- a/lib/l10n/de_DE.js +++ b/lib/l10n/de_DE.js @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion.", - "404" : "404" + "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/de_DE.json b/lib/l10n/de_DE.json index 1887e243dc3..d217e7fe881 100644 --- a/lib/l10n/de_DE.json +++ b/lib/l10n/de_DE.json @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion.", - "404" : "404" + "Please upgrade your database version." : "Bitte aktualisieren Sie Ihre Datenbankversion." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file 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 c2fb2209747..b8264f5cf9b 100644 --- a/lib/l10n/en_GB.js +++ b/lib/l10n/en_GB.js @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Please upgrade your database version.", - "404" : "404" + "Please upgrade your database version." : "Please upgrade your database version." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/en_GB.json b/lib/l10n/en_GB.json index a52ee86c2d8..9a1e921efd4 100644 --- a/lib/l10n/en_GB.json +++ b/lib/l10n/en_GB.json @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Please upgrade your database version.", - "404" : "404" + "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/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 bac38858d13..4db9814b89f 100644 --- a/lib/l10n/es.js +++ b/lib/l10n/es.js @@ -270,13 +270,11 @@ 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.", "PostgreSQL >= 9 required." : "PostgreSQL >= 9 requerido.", - "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos.", - "404" : "404" + "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos." }, "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"); diff --git a/lib/l10n/es.json b/lib/l10n/es.json index 1aaf483f009..66d7b7128fb 100644 --- a/lib/l10n/es.json +++ b/lib/l10n/es.json @@ -268,13 +268,11 @@ "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.", "PostgreSQL >= 9 required." : "PostgreSQL >= 9 requerido.", - "Please upgrade your database version." : "Por favor, actualiza la versión de tu base de datos.", - "404" : "404" + "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;" }
\ No newline at end of file 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 7696a74a271..0a8cc39fe7b 100644 --- a/lib/l10n/es_EC.js +++ b/lib/l10n/es_EC.js @@ -261,13 +261,11 @@ 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.", - "Please upgrade your database version." : "Actualiza la versión de tu base de datos.", - "404" : "404" + "Please upgrade your database version." : "Actualiza la versión de tu base de datos." }, "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"); diff --git a/lib/l10n/es_EC.json b/lib/l10n/es_EC.json index 04c1a0ba25a..21bc698d220 100644 --- a/lib/l10n/es_EC.json +++ b/lib/l10n/es_EC.json @@ -259,13 +259,11 @@ "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.", - "Please upgrade your database version." : "Actualiza la versión de tu base de datos.", - "404" : "404" + "Please upgrade your database version." : "Actualiza la versión de tu base de datos." },"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" }
\ No newline at end of file diff --git a/lib/l10n/es_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 c9f9aceb660..71293e24543 100644 --- a/lib/l10n/eu.js +++ b/lib/l10n/eu.js @@ -270,13 +270,11 @@ 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", - "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa.", - "404" : "404" + "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/eu.json b/lib/l10n/eu.json index d07e4622dee..993f78d836c 100644 --- a/lib/l10n/eu.json +++ b/lib/l10n/eu.json @@ -268,13 +268,11 @@ "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", - "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa.", - "404" : "404" + "Please upgrade your database version." : "Mesedez eguneratu zure datu-basearen bertsioa." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/fa.js b/lib/l10n/fa.js index 83132586486..f45a33ffcf8 100644 --- a/lib/l10n/fa.js +++ b/lib/l10n/fa.js @@ -56,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" : "فردا", @@ -269,13 +269,11 @@ 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.", - "Please upgrade your database version." : "Please upgrade your database version.", - "404" : "۴۰۴" + "Please upgrade your database version." : "Please upgrade your database version." }, "nplurals=2; plural=(n > 1);"); diff --git a/lib/l10n/fa.json b/lib/l10n/fa.json index e5bae83fdcf..cf6b800b850 100644 --- a/lib/l10n/fa.json +++ b/lib/l10n/fa.json @@ -54,7 +54,7 @@ "Invalid image" : "عکس نامعتبر", "Avatar image is not square" : "تصویر آواتار مربع نیست", "Files" : "پوشهها", - "View profile" : "مشاهده پروفایل", + "View profile" : "مشاهدهٔ نمایه", "Local time: %s" : "Local time: %s", "today" : "امروز", "tomorrow" : "فردا", @@ -267,13 +267,11 @@ "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.", - "Please upgrade your database version." : "Please upgrade your database version.", - "404" : "۴۰۴" + "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/fi.js b/lib/l10n/fi.js index 3325fbc9321..b904c582b3c 100644 --- a/lib/l10n/fi.js +++ b/lib/l10n/fi.js @@ -225,13 +225,11 @@ 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.", - "Please upgrade your database version." : "Päivitä tietokannan versio.", - "404" : "404" + "Please upgrade your database version." : "Päivitä tietokannan versio." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/fi.json b/lib/l10n/fi.json index 13b52751e44..701aed759bf 100644 --- a/lib/l10n/fi.json +++ b/lib/l10n/fi.json @@ -223,13 +223,11 @@ "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.", - "Please upgrade your database version." : "Päivitä tietokannan versio.", - "404" : "404" + "Please upgrade your database version." : "Päivitä tietokannan versio." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/fr.js b/lib/l10n/fr.js index afffb71ac6b..604a60ac333 100644 --- a/lib/l10n/fr.js +++ b/lib/l10n/fr.js @@ -3,7 +3,7 @@ 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", @@ -53,7 +53,7 @@ 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", + "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", @@ -84,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", @@ -130,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.", @@ -148,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", @@ -157,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", @@ -212,8 +212,8 @@ OC.L10N.register( "Username must not consist of dots only" : "Le nom d'utilisateur ne doit pas être composé uniquement de points", "Username is invalid because files already exist for this user" : "Ce nom d'utilisateur n'est pas valide car des fichiers existent déjà pour cet utilisateur", "User disabled" : "Utilisateur désactivé", - "Login canceled by app" : "L'authentification a été annulé par l'application", - "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application \"%1$s\" ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s", + "Login canceled by app" : "L'authentification a été annulée par l'application", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application « %1$s » ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s", "a safe home for all your data" : "un lieu sûr pour toutes vos données", "File is currently busy, please try again later" : "Le fichier est actuellement utilisé, veuillez réessayer plus tard", "Cannot download file" : "Impossible de télécharger le fichier", @@ -221,10 +221,10 @@ OC.L10N.register( "Authentication error" : "Erreur d'authentification", "Token expired. Please reload page." : "La session a expiré. Veuillez recharger la page.", "No database drivers (sqlite, mysql, or postgresql) installed." : "Aucun pilote de base de données n’est installé (sqlite, mysql ou postgresql).", - "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire \"config\".", - "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\". Voir %s", - "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true. Voir %s", - "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire \"apps\".", + "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire « config ».", + "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire « config ». Voir %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option « config_is_read_only » sur true. Voir %s", + "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire « apps ».", "This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire des applications ou en désactivant le magasin d'applications dans le fichier de configuration.", "Cannot create \"data\" directory." : "Impossible de créer le dossier \"data\".", "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s", @@ -234,7 +234,7 @@ OC.L10N.register( "Please install one of these locales on your system and restart your web server." : "Veuillez installer l'un de ces paramètres régionaux sur votre système et redémarrer votre serveur web.", "PHP module %s not installed." : "Le module PHP %s n’est pas installé.", "Please ask your server administrator to install the module." : "Veuillez demander à votre administrateur d’installer le module.", - "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP \"%s\" n'est pas \"%s\".", + "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP « %s » n'est pas « %s ».", "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajuster ce paramètre dans php.ini fera fonctionner Nextcould à nouveau", "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> est défini à <code>%s</code> alors que la valeur <code>0</code> est attendue.", "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pour corriger ce problème définissez <code>mbstring.func_overload</code> à <code>0</code> dans votre php.ini.", @@ -255,7 +255,7 @@ 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", @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données.", - "404" : "404" + "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données." }, "nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"); diff --git a/lib/l10n/fr.json b/lib/l10n/fr.json index 7b9975d6f1d..9fc505c49a0 100644 --- a/lib/l10n/fr.json +++ b/lib/l10n/fr.json @@ -1,7 +1,7 @@ { "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", @@ -51,7 +51,7 @@ "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", + "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", @@ -82,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", @@ -128,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.", @@ -146,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", @@ -155,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", @@ -210,8 +210,8 @@ "Username must not consist of dots only" : "Le nom d'utilisateur ne doit pas être composé uniquement de points", "Username is invalid because files already exist for this user" : "Ce nom d'utilisateur n'est pas valide car des fichiers existent déjà pour cet utilisateur", "User disabled" : "Utilisateur désactivé", - "Login canceled by app" : "L'authentification a été annulé par l'application", - "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application \"%1$s\" ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s", + "Login canceled by app" : "L'authentification a été annulée par l'application", + "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "L'application « %1$s » ne peut pas être installée à cause des dépendances suivantes non satisfaites : %2$s", "a safe home for all your data" : "un lieu sûr pour toutes vos données", "File is currently busy, please try again later" : "Le fichier est actuellement utilisé, veuillez réessayer plus tard", "Cannot download file" : "Impossible de télécharger le fichier", @@ -219,10 +219,10 @@ "Authentication error" : "Erreur d'authentification", "Token expired. Please reload page." : "La session a expiré. Veuillez recharger la page.", "No database drivers (sqlite, mysql, or postgresql) installed." : "Aucun pilote de base de données n’est installé (sqlite, mysql ou postgresql).", - "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire \"config\".", - "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire \"config\". Voir %s", - "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option \"config_is_read_only\" sur true. Voir %s", - "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire \"apps\".", + "Cannot write into \"config\" directory." : "Impossible d’écrire dans le répertoire « config ».", + "This can usually be fixed by giving the web server write access to the config directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire « config ». Voir %s", + "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Ou, si vous préférez conserver le fichier config.php en lecture seule, définissez l'option « config_is_read_only » sur true. Voir %s", + "Cannot write into \"apps\" directory." : "Impossible d'écrire dans le répertoire « apps ».", "This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file." : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire des applications ou en désactivant le magasin d'applications dans le fichier de configuration.", "Cannot create \"data\" directory." : "Impossible de créer le dossier \"data\".", "This can usually be fixed by giving the web server write access to the root directory. See %s" : "Ce problème est généralement résolu en donnant au serveur web un accès en écriture au répertoire racine. Voir %s", @@ -232,7 +232,7 @@ "Please install one of these locales on your system and restart your web server." : "Veuillez installer l'un de ces paramètres régionaux sur votre système et redémarrer votre serveur web.", "PHP module %s not installed." : "Le module PHP %s n’est pas installé.", "Please ask your server administrator to install the module." : "Veuillez demander à votre administrateur d’installer le module.", - "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP \"%s\" n'est pas \"%s\".", + "PHP setting \"%s\" is not set to \"%s\"." : "Le paramètre PHP « %s » n'est pas « %s ».", "Adjusting this setting in php.ini will make Nextcloud run again" : "Ajuster ce paramètre dans php.ini fera fonctionner Nextcould à nouveau", "<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>." : "<code>mbstring.func_overload</code> est défini à <code>%s</code> alors que la valeur <code>0</code> est attendue.", "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini." : "Pour corriger ce problème définissez <code>mbstring.func_overload</code> à <code>0</code> dans votre php.ini.", @@ -253,7 +253,7 @@ "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", @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données.", - "404" : "404" + "Please upgrade your database version." : "Veuillez mettre à jour votre gestionnaire de base de données." },"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" }
\ No newline at end of file diff --git a/lib/l10n/gl.js b/lib/l10n/gl.js index f78b2264bc4..5cff04260bb 100644 --- a/lib/l10n/gl.js +++ b/lib/l10n/gl.js @@ -24,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", @@ -62,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", @@ -214,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", @@ -242,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.", @@ -270,13 +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.", - "Please upgrade your database version." : "Anove a versión da súa base de datos", - "404" : "404" + "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 c23fc6ecab4..8f052ed1510 100644 --- a/lib/l10n/gl.json +++ b/lib/l10n/gl.json @@ -22,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", @@ -60,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", @@ -212,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", @@ -240,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.", @@ -268,13 +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.", - "Please upgrade your database version." : "Anove a versión da súa base de datos", - "404" : "404" + "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 81908533395..f1b679d1577 100644 --- a/lib/l10n/hu.js +++ b/lib/l10n/hu.js @@ -263,20 +263,18 @@ 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.", - "Please upgrade your database version." : "Frissítse az adatbázis verzióját.", - "404" : "404" + "Please upgrade your database version." : "Frissítse az adatbázis verzióját." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/hu.json b/lib/l10n/hu.json index 6221e5c9a02..8aad272c59f 100644 --- a/lib/l10n/hu.json +++ b/lib/l10n/hu.json @@ -261,20 +261,18 @@ "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.", - "Please upgrade your database version." : "Frissítse az adatbázis verzióját.", - "404" : "404" + "Please upgrade your database version." : "Frissítse az adatbázis verzióját." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/id.js b/lib/l10n/id.js index 22c945924d7..7dc88e95615 100644 --- a/lib/l10n/id.js +++ b/lib/l10n/id.js @@ -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..56595375abb 100644 --- a/lib/l10n/id.json +++ b/lib/l10n/id.json @@ -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 53082d65cf9..c9ef9c3cef2 100644 --- a/lib/l10n/it.js +++ b/lib/l10n/it.js @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Aggiorna la versione del tuo database.", - "404" : "404" + "Please upgrade your database version." : "Aggiorna la versione del tuo database." }, "nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"); diff --git a/lib/l10n/it.json b/lib/l10n/it.json index 104c01a1439..c30ee15f522 100644 --- a/lib/l10n/it.json +++ b/lib/l10n/it.json @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Aggiorna la versione del tuo database.", - "404" : "404" + "Please upgrade your database version." : "Aggiorna la versione del tuo database." },"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" }
\ No newline at end of file diff --git a/lib/l10n/ja.js b/lib/l10n/ja.js index 18cc0e5633c..9c38f82f070 100644 --- a/lib/l10n/ja.js +++ b/lib/l10n/ja.js @@ -261,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." : "テキストの見出しの候補を生成", @@ -270,13 +270,11 @@ 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以上が必要です", - "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。", - "404" : "404" + "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。" }, "nplurals=1; plural=0;"); diff --git a/lib/l10n/ja.json b/lib/l10n/ja.json index 2d62cdecb16..6388c6ecdbe 100644 --- a/lib/l10n/ja.json +++ b/lib/l10n/ja.json @@ -259,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." : "テキストの見出しの候補を生成", @@ -268,13 +268,11 @@ "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以上が必要です", - "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。", - "404" : "404" + "Please upgrade your database version." : "新しいバージョンのデータベースにアップグレードしてください。" },"pluralForm" :"nplurals=1; plural=0;" }
\ 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..d0bd676a31d 100644 --- a/lib/l10n/ko.js +++ b/lib/l10n/ko.js @@ -81,6 +81,7 @@ OC.L10N.register( "Address" : "주소", "Profile picture" : "프로필 사진", "About" : "정보", + "Display name" : "표시 이름", "Headline" : "표제", "Organisation" : "조직", "Role" : "직책", @@ -194,7 +195,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/ko.json b/lib/l10n/ko.json index 0fb317916e3..3a571d45863 100644 --- a/lib/l10n/ko.json +++ b/lib/l10n/ko.json @@ -79,6 +79,7 @@ "Address" : "주소", "Profile picture" : "프로필 사진", "About" : "정보", + "Display name" : "표시 이름", "Headline" : "표제", "Organisation" : "조직", "Role" : "직책", @@ -192,7 +193,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=1; plural=0;" diff --git a/lib/l10n/lt_LT.js b/lib/l10n/lt_LT.js index 0bb3bf18e90..24240599936 100644 --- a/lib/l10n/lt_LT.js +++ b/lib/l10n/lt_LT.js @@ -191,7 +191,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..9fcddb78fdb 100644 --- a/lib/l10n/lt_LT.json +++ b/lib/l10n/lt_LT.json @@ -189,7 +189,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 c33558ceb0c..5ed4ea6e4a5 100644 --- a/lib/l10n/lv.js +++ b/lib/l10n/lv.js @@ -131,7 +131,7 @@ OC.L10N.register( "Storage connection error. %s" : "Datu savienojuma kļūda. %s", "Storage is temporarily not available" : "Glabātuve īslaicīgi nav pieejama", "Storage connection timeout. %s" : "Datu savienojuma taimauts. %s", - "Full name" : "Pilns vārds", - "404" : "404" + "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 b05c244729c..10912106676 100644 --- a/lib/l10n/lv.json +++ b/lib/l10n/lv.json @@ -129,7 +129,7 @@ "Storage connection error. %s" : "Datu savienojuma kļūda. %s", "Storage is temporarily not available" : "Glabātuve īslaicīgi nav pieejama", "Storage connection timeout. %s" : "Datu savienojuma taimauts. %s", - "Full name" : "Pilns vārds", - "404" : "404" + "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 3c92f5e619e..c948ee8d4de 100644 --- a/lib/l10n/mk.js +++ b/lib/l10n/mk.js @@ -64,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 години"], @@ -261,13 +261,11 @@ 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 ", - "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци", - "404" : "404" + "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци" }, "nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"); diff --git a/lib/l10n/mk.json b/lib/l10n/mk.json index 3599550f3ed..19a77b4749e 100644 --- a/lib/l10n/mk.json +++ b/lib/l10n/mk.json @@ -62,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 години"], @@ -259,13 +259,11 @@ "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 ", - "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци", - "404" : "404" + "Please upgrade your database version." : "Ве молиме надградете ја верзијата на базата со податоци" },"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;" }
\ No newline at end of file diff --git a/lib/l10n/nb.js b/lib/l10n/nb.js index ea0856ccd8a..e7386d3abd5 100644 --- a/lib/l10n/nb.js +++ b/lib/l10n/nb.js @@ -194,10 +194,9 @@ 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.", - "404" : "404" + "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." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/nb.json b/lib/l10n/nb.json index 627d3df471d..115e4c2730d 100644 --- a/lib/l10n/nb.json +++ b/lib/l10n/nb.json @@ -192,10 +192,9 @@ "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.", - "404" : "404" + "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);" }
\ No newline at end of file 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 62fbf06d75d..7dc10139dd0 100644 --- a/lib/l10n/pl.js +++ b/lib/l10n/pl.js @@ -5,6 +5,7 @@ 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", "The page could not be found on the server." : "Nie znaleziono strony na serwerze.", @@ -112,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", @@ -153,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", @@ -258,14 +261,20 @@ 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", - "Please upgrade your database version." : "Zaktualizuj wersję bazy danych.", - "404" : "404" + "Please upgrade your database version." : "Zaktualizuj wersję bazy danych." }, "nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);"); diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json index f09679895af..ed456f68784 100644 --- a/lib/l10n/pl.json +++ b/lib/l10n/pl.json @@ -3,6 +3,7 @@ "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", "The page could not be found on the server." : "Nie znaleziono strony na serwerze.", @@ -110,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", @@ -151,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", @@ -256,14 +259,20 @@ "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", - "Please upgrade your database version." : "Zaktualizuj wersję bazy danych.", - "404" : "404" + "Please upgrade your database version." : "Zaktualizuj wersję bazy danych." },"pluralForm" :"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);" }
\ No newline at end of file diff --git a/lib/l10n/pt_BR.js b/lib/l10n/pt_BR.js index 114a6e86471..cc2e191a85d 100644 --- a/lib/l10n/pt_BR.js +++ b/lib/l10n/pt_BR.js @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados.", - "404" : "404" + "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados." }, "nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"); diff --git a/lib/l10n/pt_BR.json b/lib/l10n/pt_BR.json index 98571e5a453..0589fed24a1 100644 --- a/lib/l10n/pt_BR.json +++ b/lib/l10n/pt_BR.json @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados.", - "404" : "404" + "Please upgrade your database version." : "Por favor, atualize a versão do seu banco de dados." },"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" }
\ No newline at end of file diff --git a/lib/l10n/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 c7d74a623af..911774d5abb 100644 --- a/lib/l10n/ru.js +++ b/lib/l10n/ru.js @@ -95,7 +95,7 @@ OC.L10N.register( "Help" : "Помощь", "Appearance and accessibility" : "Внешний вид и доступность", "Apps" : "Приложения", - "Personal settings" : "Параметры пользователя", + "Personal settings" : "Личные настройки", "Administration settings" : "Параметры сервера", "Settings" : "Настройки", "Log out" : "Выйти", @@ -270,13 +270,11 @@ 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 или более новый.", - "Please upgrade your database version." : "Обновите базу данных.", - "404" : "404" + "Please upgrade your database version." : "Обновите базу данных." }, "nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"); diff --git a/lib/l10n/ru.json b/lib/l10n/ru.json index 0ac07611e47..96676b22331 100644 --- a/lib/l10n/ru.json +++ b/lib/l10n/ru.json @@ -93,7 +93,7 @@ "Help" : "Помощь", "Appearance and accessibility" : "Внешний вид и доступность", "Apps" : "Приложения", - "Personal settings" : "Параметры пользователя", + "Personal settings" : "Личные настройки", "Administration settings" : "Параметры сервера", "Settings" : "Настройки", "Log out" : "Выйти", @@ -268,13 +268,11 @@ "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 или более новый.", - "Please upgrade your database version." : "Обновите базу данных.", - "404" : "404" + "Please upgrade your database version." : "Обновите базу данных." },"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" }
\ No newline at end of file diff --git a/lib/l10n/sc.js b/lib/l10n/sc.js index 68f638c9bd8..6ac2f72fab9 100644 --- a/lib/l10n/sc.js +++ b/lib/l10n/sc.js @@ -216,7 +216,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..e1df080a887 100644 --- a/lib/l10n/sc.json +++ b/lib/l10n/sc.json @@ -214,7 +214,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 8b1de695f91..e5c440a33c7 100644 --- a/lib/l10n/sk.js +++ b/lib/l10n/sk.js @@ -259,13 +259,11 @@ 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.", - "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy.", - "404" : "404" + "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy." }, "nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);"); diff --git a/lib/l10n/sk.json b/lib/l10n/sk.json index 35ed8357bec..d16b77d6949 100644 --- a/lib/l10n/sk.json +++ b/lib/l10n/sk.json @@ -257,13 +257,11 @@ "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.", - "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy.", - "404" : "404" + "Please upgrade your database version." : "Prosím, aktualizujte verziu svojej databázy." },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);" }
\ No newline at end of file diff --git a/lib/l10n/sl.js b/lib/l10n/sl.js index e212e29c003..68da66e2ccf 100644 --- a/lib/l10n/sl.js +++ b/lib/l10n/sl.js @@ -254,10 +254,9 @@ 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.", - "404" : "404" + "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." }, "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"); diff --git a/lib/l10n/sl.json b/lib/l10n/sl.json index c6deda5413c..944f3f8c5f8 100644 --- a/lib/l10n/sl.json +++ b/lib/l10n/sl.json @@ -252,10 +252,9 @@ "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.", - "404" : "404" + "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);" }
\ No newline at end of file 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 5739978c2e1..412c105f73b 100644 --- a/lib/l10n/sr.js +++ b/lib/l10n/sr.js @@ -261,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" : "Резимирај", @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података.", - "404" : "404" + "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података." }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"); diff --git a/lib/l10n/sr.json b/lib/l10n/sr.json index 9549cecb2e3..0f11702d9c8 100644 --- a/lib/l10n/sr.json +++ b/lib/l10n/sr.json @@ -259,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" : "Резимирај", @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података.", - "404" : "404" + "Please upgrade your database version." : "Молимо вас да ажурирате верзију базе података." },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }
\ No newline at end of file diff --git a/lib/l10n/sv.js b/lib/l10n/sv.js index f0f7081dd6a..a4cd1a0dbe8 100644 --- a/lib/l10n/sv.js +++ b/lib/l10n/sv.js @@ -268,13 +268,11 @@ 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.", - "Please upgrade your database version." : "Uppgradera din databasversion.", - "404" : "404" + "Please upgrade your database version." : "Uppgradera din databasversion." }, "nplurals=2; plural=(n != 1);"); diff --git a/lib/l10n/sv.json b/lib/l10n/sv.json index 11fd22b6b64..a1f0a007e63 100644 --- a/lib/l10n/sv.json +++ b/lib/l10n/sv.json @@ -266,13 +266,11 @@ "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.", - "Please upgrade your database version." : "Uppgradera din databasversion.", - "404" : "404" + "Please upgrade your database version." : "Uppgradera din databasversion." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/lib/l10n/th.js b/lib/l10n/th.js index 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 d5fe9f7b3fe..8c2d550d78b 100644 --- a/lib/l10n/tr.js +++ b/lib/l10n/tr.js @@ -63,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"], @@ -78,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ı", @@ -147,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ı", @@ -251,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", @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin.", - "404" : "404" + "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin." }, "nplurals=2; plural=(n > 1);"); diff --git a/lib/l10n/tr.json b/lib/l10n/tr.json index 4c5ec5c1f08..7d744fd1417 100644 --- a/lib/l10n/tr.json +++ b/lib/l10n/tr.json @@ -61,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"], @@ -76,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ı", @@ -145,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ı", @@ -249,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", @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin.", - "404" : "404" + "Please upgrade your database version." : "Lütfen veri tabanı sürümünüzü yükseltin." },"pluralForm" :"nplurals=2; plural=(n > 1);" }
\ No newline at end of file diff --git a/lib/l10n/uk.js b/lib/l10n/uk.js index d5277a526a8..10020525224 100644 --- a/lib/l10n/uk.js +++ b/lib/l10n/uk.js @@ -102,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", @@ -270,13 +270,11 @@ 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.", - "Please upgrade your database version." : "Оновіть версію бази даних.", - "404" : "404" + "Please upgrade your database version." : "Оновіть версію бази даних." }, "nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"); diff --git a/lib/l10n/uk.json b/lib/l10n/uk.json index e87a4ff6da6..490b8882499 100644 --- a/lib/l10n/uk.json +++ b/lib/l10n/uk.json @@ -100,7 +100,7 @@ "Users" : "Користувачі", "Email" : "Електронна пошта", "Mail %s" : "Пошта %s", - "Fediverse" : "Федіверс", + "Fediverse" : "Fediverse", "View %s on the fediverse" : "Переглянути %s у Fediverse", "Phone" : "Телефон", "Call %s" : "Телефонуйте %s", @@ -268,13 +268,11 @@ "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.", - "Please upgrade your database version." : "Оновіть версію бази даних.", - "404" : "404" + "Please upgrade your database version." : "Оновіть версію бази даних." },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);" }
\ No newline at end of file diff --git a/lib/l10n/vi.js b/lib/l10n/vi.js index 7ea1f68dca5..070ab15c3cc 100644 --- a/lib/l10n/vi.js +++ b/lib/l10n/vi.js @@ -23,6 +23,7 @@ OC.L10N.register( "Unknown filetype" : "Không biết kiểu tập tin", "Invalid image" : "Hình ảnh không hợp lệ", "Files" : "Tệp", + "View profile" : "Xem hồ sơ", "today" : "hôm nay", "yesterday" : "hôm qua", "last month" : "tháng trước", @@ -36,6 +37,7 @@ OC.L10N.register( "Templates" : "Mẫu", "__language_name__" : "Tiếng Việt", "Help" : "Giúp đỡ", + "Appearance and accessibility" : "Ngoại hình và khả năng tiếp cận", "Apps" : "Ứng dụng", "Settings" : "Thiết lập", "Log out" : "Đăng xuất", @@ -47,6 +49,9 @@ OC.L10N.register( "Address" : "Địa chỉ", "Profile picture" : "Ảnh đại diện", "About" : "Giới thiệu", + "Headline" : "Tiêu đề", + "Organisation" : "Tổ chức", + "Role" : "Vai trò", "Unknown user" : "Người dùng không tồn tại", "Additional settings" : "Cài đặt bổ sung", "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Hãy xóa thiết lập open_basedir tại tập tin cấu hình php.ini hoặc chuyển sang dùng PHP 64-bit.", diff --git a/lib/l10n/vi.json b/lib/l10n/vi.json index 31603bf9c63..c36f661b169 100644 --- a/lib/l10n/vi.json +++ b/lib/l10n/vi.json @@ -21,6 +21,7 @@ "Unknown filetype" : "Không biết kiểu tập tin", "Invalid image" : "Hình ảnh không hợp lệ", "Files" : "Tệp", + "View profile" : "Xem hồ sơ", "today" : "hôm nay", "yesterday" : "hôm qua", "last month" : "tháng trước", @@ -34,6 +35,7 @@ "Templates" : "Mẫu", "__language_name__" : "Tiếng Việt", "Help" : "Giúp đỡ", + "Appearance and accessibility" : "Ngoại hình và khả năng tiếp cận", "Apps" : "Ứng dụng", "Settings" : "Thiết lập", "Log out" : "Đăng xuất", @@ -45,6 +47,9 @@ "Address" : "Địa chỉ", "Profile picture" : "Ảnh đại diện", "About" : "Giới thiệu", + "Headline" : "Tiêu đề", + "Organisation" : "Tổ chức", + "Role" : "Vai trò", "Unknown user" : "Người dùng không tồn tại", "Additional settings" : "Cài đặt bổ sung", "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Hãy xóa thiết lập open_basedir tại tập tin cấu hình php.ini hoặc chuyển sang dùng PHP 64-bit.", diff --git a/lib/l10n/zh_CN.js b/lib/l10n/zh_CN.js index a74a6937ecf..82929d9ffde 100644 --- a/lib/l10n/zh_CN.js +++ b/lib/l10n/zh_CN.js @@ -5,6 +5,7 @@ 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 进行修改之前请先阅读相关文档。", "The page could not be found on the server." : "无法在服务器上找到此页面", @@ -154,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" : "星期一", @@ -203,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" : "用户名不能仅由点组成", @@ -257,14 +261,20 @@ 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。", - "Please upgrade your database version." : "请升级您的数据库版本。", - "404" : "404" + "Please upgrade your database version." : "请升级您的数据库版本。" }, "nplurals=1; plural=0;"); diff --git a/lib/l10n/zh_CN.json b/lib/l10n/zh_CN.json index 38813c8120d..87e5c9b4021 100644 --- a/lib/l10n/zh_CN.json +++ b/lib/l10n/zh_CN.json @@ -3,6 +3,7 @@ "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 进行修改之前请先阅读相关文档。", "The page could not be found on the server." : "无法在服务器上找到此页面", @@ -152,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" : "星期一", @@ -201,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" : "用户名不能仅由点组成", @@ -255,14 +259,20 @@ "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。", - "Please upgrade your database version." : "请升级您的数据库版本。", - "404" : "404" + "Please upgrade your database version." : "请升级您的数据库版本。" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/lib/l10n/zh_HK.js b/lib/l10n/zh_HK.js index d5334cbde22..21e2e4453c7 100644 --- a/lib/l10n/zh_HK.js +++ b/lib/l10n/zh_HK.js @@ -270,13 +270,11 @@ 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", - "Please upgrade your database version." : "請升級您數據庫的版本。", - "404" : "404" + "Please upgrade your database version." : "請升級您數據庫的版本。" }, "nplurals=1; plural=0;"); diff --git a/lib/l10n/zh_HK.json b/lib/l10n/zh_HK.json index 4ee6495971b..11bbc622b69 100644 --- a/lib/l10n/zh_HK.json +++ b/lib/l10n/zh_HK.json @@ -268,13 +268,11 @@ "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", - "Please upgrade your database version." : "請升級您數據庫的版本。", - "404" : "404" + "Please upgrade your database version." : "請升級您數據庫的版本。" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/lib/l10n/zh_TW.js b/lib/l10n/zh_TW.js index 0b1a2c20455..6b98fd06acf 100644 --- a/lib/l10n/zh_TW.js +++ b/lib/l10n/zh_TW.js @@ -270,13 +270,11 @@ 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。", - "Please upgrade your database version." : "請升級您的資料庫版本。", - "404" : "404" + "Please upgrade your database version." : "請升級您的資料庫版本。" }, "nplurals=1; plural=0;"); diff --git a/lib/l10n/zh_TW.json b/lib/l10n/zh_TW.json index 6b4047be509..fa4e390d81f 100644 --- a/lib/l10n/zh_TW.json +++ b/lib/l10n/zh_TW.json @@ -268,13 +268,11 @@ "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。", - "Please upgrade your database version." : "請升級您的資料庫版本。", - "404" : "404" + "Please upgrade your database version." : "請升級您的資料庫版本。" },"pluralForm" :"nplurals=1; plural=0;" }
\ No newline at end of file diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 3e33e783635..97156a027e6 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -38,15 +38,15 @@ namespace OC\Accounts; use Exception; use InvalidArgumentException; use OC\Profile\TProfileHelper; -use OCP\Accounts\UserUpdatedEvent; -use OCP\Cache\CappedMemoryCache; use OCA\Settings\BackgroundJobs\VerifyUserData; use OCP\Accounts\IAccount; use OCP\Accounts\IAccountManager; use OCP\Accounts\IAccountProperty; use OCP\Accounts\IAccountPropertyCollection; use OCP\Accounts\PropertyDoesNotExistException; +use OCP\Accounts\UserUpdatedEvent; use OCP\BackgroundJob\IJobList; +use OCP\Cache\CappedMemoryCache; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php index a7d24510d53..14069260c6c 100644 --- a/lib/private/Activity/Manager.php +++ b/lib/private/Activity/Manager.php @@ -70,11 +70,11 @@ class Manager implements IManager { protected $l10n; public function __construct( - IRequest $request, - IUserSession $session, - IConfig $config, - IValidator $validator, - IL10N $l10n + IRequest $request, + IUserSession $session, + IConfig $config, + IValidator $validator, + IL10N $l10n ) { $this->request = $request; $this->session = $session; diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index 2a0e8f53b14..92178d64635 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -32,6 +32,7 @@ */ namespace OC; +use Doctrine\DBAL\Platforms\OraclePlatform; use OCP\Cache\CappedMemoryCache; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; @@ -490,12 +491,15 @@ class AllConfig implements IConfig { $this->fixDIInit(); $qb = $this->connection->getQueryBuilder(); + $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform) + ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) + : 'configvalue'; $result = $qb->select('userid') ->from('preferences') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR))) ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR))) ->andWhere($qb->expr()->eq( - $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR), + $configValueColumn, $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR)) )->orderBy('userid') ->executeQuery(); @@ -524,13 +528,18 @@ class AllConfig implements IConfig { // Email address is always stored lowercase in the database return $this->getUsersForUserValue($appName, $key, strtolower($value)); } + $qb = $this->connection->getQueryBuilder(); + $configValueColumn = ($this->connection->getDatabasePlatform() instanceof OraclePlatform) + ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR) + : 'configvalue'; + $result = $qb->select('userid') ->from('preferences') ->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR))) ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR))) ->andWhere($qb->expr()->eq( - $qb->func()->lower($qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)), + $qb->func()->lower($configValueColumn), $qb->createNamedParameter(strtolower($value), IQueryBuilder::PARAM_STR)) )->orderBy('userid') ->executeQuery(); diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index ab7b470bb8d..ad5fdc5afed 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -48,10 +48,10 @@ use OCP\App\Events\AppDisableEvent; use OCP\App\Events\AppEnableEvent; use OCP\App\IAppManager; use OCP\App\ManagerEvent; -use OCP\EventDispatcher\IEventDispatcher; use OCP\Collaboration\AutoComplete\IManager as IAutoCompleteManager; use OCP\Collaboration\Collaborators\ISearch as ICollaboratorSearch; use OCP\Diagnostics\IEventLogger; +use OCP\EventDispatcher\IEventDispatcher; use OCP\ICacheFactory; use OCP\IConfig; use OCP\IGroup; @@ -105,12 +105,12 @@ class AppManager implements IAppManager { private array $loadedApps = []; public function __construct(IUserSession $userSession, - IConfig $config, - AppConfig $appConfig, - IGroupManager $groupManager, - ICacheFactory $memCacheFactory, - IEventDispatcher $dispatcher, - LoggerInterface $logger) { + IConfig $config, + AppConfig $appConfig, + IGroupManager $groupManager, + ICacheFactory $memCacheFactory, + IEventDispatcher $dispatcher, + LoggerInterface $logger) { $this->userSession = $userSession; $this->config = $config; $this->appConfig = $appConfig; @@ -838,9 +838,12 @@ class AppManager implements IAppManager { /* Fallback on user defined apporder */ $customOrders = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR); if (!empty($customOrders)) { - $customOrders = array_map('min', $customOrders); - asort($customOrders); - $defaultApps = array_keys($customOrders); + // filter only entries with app key (when added using closures or NavigationManager::add the app is not guranteed to be set) + $customOrders = array_filter($customOrders, fn ($entry) => isset($entry['app'])); + // sort apps by order + usort($customOrders, fn ($a, $b) => $a['order'] - $b['order']); + // set default apps to sorted apps + $defaultApps = array_map(fn ($entry) => $entry['app'], $customOrders); } } } diff --git a/lib/private/App/AppStore/Fetcher/AppFetcher.php b/lib/private/App/AppStore/Fetcher/AppFetcher.php index 47bdece372d..f9fbd05855b 100644 --- a/lib/private/App/AppStore/Fetcher/AppFetcher.php +++ b/lib/private/App/AppStore/Fetcher/AppFetcher.php @@ -49,12 +49,12 @@ class AppFetcher extends Fetcher { private $ignoreMaxVersion; public function __construct(Factory $appDataFactory, - IClientService $clientService, - ITimeFactory $timeFactory, - IConfig $config, - CompareVersion $compareVersion, - LoggerInterface $logger, - IRegistry $registry) { + IClientService $clientService, + ITimeFactory $timeFactory, + IConfig $config, + CompareVersion $compareVersion, + LoggerInterface $logger, + IRegistry $registry) { parent::__construct( $appDataFactory, $clientService, diff --git a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php index afe051e6281..d1bbe4f7b04 100644 --- a/lib/private/App/AppStore/Fetcher/CategoryFetcher.php +++ b/lib/private/App/AppStore/Fetcher/CategoryFetcher.php @@ -35,11 +35,11 @@ use Psr\Log\LoggerInterface; class CategoryFetcher extends Fetcher { public function __construct(Factory $appDataFactory, - IClientService $clientService, - ITimeFactory $timeFactory, - IConfig $config, - LoggerInterface $logger, - IRegistry $registry) { + IClientService $clientService, + ITimeFactory $timeFactory, + IConfig $config, + LoggerInterface $logger, + IRegistry $registry) { parent::__construct( $appDataFactory, $clientService, diff --git a/lib/private/App/AppStore/Fetcher/Fetcher.php b/lib/private/App/AppStore/Fetcher/Fetcher.php index 095b026cb44..3e76ab2d5da 100644 --- a/lib/private/App/AppStore/Fetcher/Fetcher.php +++ b/lib/private/App/AppStore/Fetcher/Fetcher.php @@ -68,11 +68,11 @@ abstract class Fetcher { protected $channel = null; public function __construct(Factory $appDataFactory, - IClientService $clientService, - ITimeFactory $timeFactory, - IConfig $config, - LoggerInterface $logger, - IRegistry $registry) { + IClientService $clientService, + ITimeFactory $timeFactory, + IConfig $config, + LoggerInterface $logger, + IRegistry $registry) { $this->appData = $appDataFactory->get('appstore'); $this->clientService = $clientService; $this->timeFactory = $timeFactory; diff --git a/lib/private/App/Platform.php b/lib/private/App/Platform.php index 1cab740bebb..daff247d1bd 100644 --- a/lib/private/App/Platform.php +++ b/lib/private/App/Platform.php @@ -25,8 +25,8 @@ */ namespace OC\App; -use OCP\IConfig; use OCP\IBinaryFinder; +use OCP\IConfig; /** * Class Platform diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index ffd77da888e..b18c95a2f0d 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -34,16 +34,16 @@ namespace OC\AppFramework; use OC\AppFramework\DependencyInjection\DIContainer; use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Http\Request; -use OCP\App\IAppManager; -use OCP\Profiler\IProfiler; use OC\Profiler\RoutingDataCollector; -use OCP\AppFramework\QueryException; +use OCP\App\IAppManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\ICallbackResponse; use OCP\AppFramework\Http\IOutput; +use OCP\AppFramework\QueryException; use OCP\Diagnostics\IEventLogger; use OCP\HintException; use OCP\IRequest; +use OCP\Profiler\IProfiler; /** * Entry point for every request in your app. You can consider this as your @@ -257,7 +257,7 @@ class App { * @param DIContainer $container an instance of a pimple container. */ public static function part(string $controllerName, string $methodName, array $urlParams, - DIContainer $container) { + DIContainer $container) { $container['urlParams'] = $urlParams; $controller = $container[$controllerName]; diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index f41b734a25b..8526a3dc1a1 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -30,20 +30,20 @@ declare(strict_types=1); namespace OC\AppFramework\Bootstrap; -use OCP\Diagnostics\IEventLogger; -use function class_exists; -use function class_implements; -use function in_array; -use OC_App; use OC\Support\CrashReport\Registry; +use OC_App; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\QueryException; use OCP\Dashboard\IManager; +use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; use OCP\IServerContainer; use Psr\Log\LoggerInterface; use Throwable; +use function class_exists; +use function class_implements; +use function in_array; class Coordinator { /** @var IServerContainer */ diff --git a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php index 2ad410be26f..12801e62763 100644 --- a/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php +++ b/lib/private/AppFramework/Bootstrap/EventListenerRegistration.php @@ -37,9 +37,9 @@ class EventListenerRegistration extends ServiceRegistration { private $priority; public function __construct(string $appId, - string $event, - string $service, - int $priority) { + string $event, + string $service, + int $priority) { parent::__construct($appId, $service); $this->event = $event; $this->priority = $priority; diff --git a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php index b501a757abd..958f24cb600 100644 --- a/lib/private/AppFramework/Bootstrap/ParameterRegistration.php +++ b/lib/private/AppFramework/Bootstrap/ParameterRegistration.php @@ -36,8 +36,8 @@ final class ParameterRegistration extends ARegistration { private $value; public function __construct(string $appId, - string $name, - $value) { + string $name, + $value) { parent::__construct($appId); $this->name = $name; $this->value = $value; diff --git a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php index 36c5cae7db3..e4d75f75bc8 100644 --- a/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php +++ b/lib/private/AppFramework/Bootstrap/PreviewProviderRegistration.php @@ -34,8 +34,8 @@ class PreviewProviderRegistration extends ServiceRegistration { private $mimeTypeRegex; public function __construct(string $appId, - string $service, - string $mimeTypeRegex) { + string $service, + string $mimeTypeRegex) { parent::__construct($appId, $service); $this->mimeTypeRegex = $mimeTypeRegex; } diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index d4588527006..120ee7ea9fa 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -30,15 +30,6 @@ declare(strict_types=1); namespace OC\AppFramework\Bootstrap; use Closure; -use OCP\Calendar\Resource\IBackend as IResourceBackend; -use OCP\Calendar\Room\IBackend as IRoomBackend; -use OCP\Collaboration\Reference\IReferenceProvider; -use OCP\TextProcessing\IProvider as ITextProcessingProvider; -use OCP\SpeechToText\ISpeechToTextProvider; -use OCP\Talk\ITalkBackend; -use OCP\Translation\ITranslationProvider; -use RuntimeException; -use function array_shift; use OC\Support\CrashReport\Registry; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IRegistrationContext; @@ -46,7 +37,10 @@ use OCP\AppFramework\Middleware; use OCP\AppFramework\Services\InitialStateProvider; use OCP\Authentication\IAlternativeLogin; use OCP\Calendar\ICalendarProvider; +use OCP\Calendar\Resource\IBackend as IResourceBackend; +use OCP\Calendar\Room\IBackend as IRoomBackend; use OCP\Capabilities\ICapability; +use OCP\Collaboration\Reference\IReferenceProvider; use OCP\Dashboard\IManager; use OCP\Dashboard\IWidget; use OCP\EventDispatcher\IEventDispatcher; @@ -57,10 +51,16 @@ use OCP\Profile\ILinkAction; use OCP\Search\IProvider; use OCP\SetupCheck\ISetupCheck; use OCP\Share\IPublicShareTemplateProvider; +use OCP\SpeechToText\ISpeechToTextProvider; use OCP\Support\CrashReport\IReporter; +use OCP\Talk\ITalkBackend; +use OCP\TextProcessing\IProvider as ITextProcessingProvider; +use OCP\Translation\ITranslationProvider; use OCP\UserMigration\IMigrator as IUserMigrator; use Psr\Log\LoggerInterface; +use RuntimeException; use Throwable; +use function array_shift; class RegistrationContext { /** @var ServiceRegistration<ICapability>[] */ @@ -138,6 +138,12 @@ class RegistrationContext { /** @var ServiceRegistration<IReferenceProvider>[] */ private array $referenceProviders = []; + /** @var ServiceRegistration<\OCP\TextToImage\IProvider>[] */ + private $textToImageProviders = []; + + + + /** @var ParameterRegistration[] */ private $sensitiveMethods = []; @@ -273,6 +279,13 @@ class RegistrationContext { ); } + public function registerTextToImageProvider(string $providerClass): void { + $this->context->registerTextToImageProvider( + $this->appId, + $providerClass + ); + } + public function registerTemplateProvider(string $providerClass): void { $this->context->registerTemplateProvider( $this->appId, @@ -450,6 +463,10 @@ class RegistrationContext { $this->textProcessingProviders[] = new ServiceRegistration($appId, $class); } + public function registerTextToImageProvider(string $appId, string $class): void { + $this->textToImageProviders[] = new ServiceRegistration($appId, $class); + } + public function registerTemplateProvider(string $appId, string $class): void { $this->templateProviders[] = new ServiceRegistration($appId, $class); } @@ -740,6 +757,13 @@ class RegistrationContext { } /** + * @return ServiceRegistration<\OCP\TextToImage\IProvider>[] + */ + public function getTextToImageProviders(): array { + return $this->textToImageProviders; + } + + /** * @return ServiceRegistration<ICustomTemplateProvider>[] */ public function getTemplateProviders(): array { diff --git a/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php index e2b115e0353..62c7169a7ee 100644 --- a/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php +++ b/lib/private/AppFramework/Bootstrap/ServiceAliasRegistration.php @@ -46,8 +46,8 @@ class ServiceAliasRegistration extends ARegistration { * @paslm-param string|class-string $target */ public function __construct(string $appId, - string $alias, - string $target) { + string $alias, + string $target) { parent::__construct($appId); $this->alias = $alias; $this->target = $target; diff --git a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php index b6658e55239..9d166526d94 100644 --- a/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php +++ b/lib/private/AppFramework/Bootstrap/ServiceFactoryRegistration.php @@ -45,9 +45,9 @@ class ServiceFactoryRegistration extends ARegistration { private $shared; public function __construct(string $appId, - string $alias, - callable $target, - bool $shared) { + string $alias, + callable $target, + bool $shared) { parent::__construct($appId); $this->name = $alias; $this->factory = $target; diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php index 13b391eb287..6e946f2bfa3 100644 --- a/lib/private/AppFramework/Http/Dispatcher.php +++ b/lib/private/AppFramework/Http/Dispatcher.php @@ -38,6 +38,7 @@ use OC\AppFramework\Utility\ControllerMethodReflector; use OC\DB\ConnectionAdapter; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\ParameterOutOfRangeException; use OCP\AppFramework\Http\Response; use OCP\Diagnostics\IEventLogger; use OCP\IConfig; @@ -88,14 +89,14 @@ class Dispatcher { * @param IEventLogger $eventLogger */ public function __construct(Http $protocol, - MiddlewareDispatcher $middlewareDispatcher, - ControllerMethodReflector $reflector, - IRequest $request, - IConfig $config, - ConnectionAdapter $connection, - LoggerInterface $logger, - IEventLogger $eventLogger, - ContainerInterface $appContainer) { + MiddlewareDispatcher $middlewareDispatcher, + ControllerMethodReflector $reflector, + IRequest $request, + IConfig $config, + ConnectionAdapter $connection, + LoggerInterface $logger, + IEventLogger $eventLogger, + ContainerInterface $appContainer) { $this->protocol = $protocol; $this->middlewareDispatcher = $middlewareDispatcher; $this->reflector = $reflector; @@ -197,7 +198,7 @@ class Dispatcher { private function executeController(Controller $controller, string $methodName): Response { $arguments = []; - // valid types that will be casted + // valid types that will be cast $types = ['int', 'integer', 'bool', 'boolean', 'float', 'double']; foreach ($this->reflector->getParameters() as $param => $default) { @@ -219,6 +220,7 @@ class Dispatcher { $value = false; } elseif ($value !== null && \in_array($type, $types, true)) { settype($value, $type); + $this->ensureParameterValueSatisfiesRange($param, $value); } elseif ($value === null && $type !== null && $this->appContainer->has($type)) { $value = $this->appContainer->get($type); } @@ -250,4 +252,22 @@ class Dispatcher { return $response; } + + /** + * @psalm-param mixed $value + * @throws ParameterOutOfRangeException + */ + private function ensureParameterValueSatisfiesRange(string $param, $value): void { + $rangeInfo = $this->reflector->getRange($param); + if ($rangeInfo) { + if ($value < $rangeInfo['min'] || $value > $rangeInfo['max']) { + throw new ParameterOutOfRangeException( + $param, + $value, + $rangeInfo['min'], + $rangeInfo['max'], + ); + } + } + } } diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 26a76e0da27..b09737a6fc6 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -118,10 +118,10 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @see https://www.php.net/manual/en/reserved.variables.php */ public function __construct(array $vars, - IRequestId $requestId, - IConfig $config, - CsrfTokenManager $csrfTokenManager = null, - string $stream = 'php://input') { + IRequestId $requestId, + IConfig $config, + CsrfTokenManager $csrfTokenManager = null, + string $stream = 'php://input') { $this->inputStream = $stream; $this->items['params'] = []; $this->requestId = $requestId; @@ -593,9 +593,11 @@ class Request implements \ArrayAccess, \Countable, IRequest { // only have one default, so we cannot ship an insecure product out of the box ]); - foreach ($forwardedForHeaders as $header) { + // Read the x-forwarded-for headers and values in reverse order as per + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address + foreach (array_reverse($forwardedForHeaders) as $header) { if (isset($this->server[$header])) { - foreach (explode(',', $this->server[$header]) as $IP) { + foreach (array_reverse(explode(',', $this->server[$header])) as $IP) { $IP = trim($IP); // remove brackets from IPv6 addresses @@ -603,6 +605,10 @@ class Request implements \ArrayAccess, \Countable, IRequest { $IP = substr($IP, 1, -1); } + if ($this->isTrustedProxy($trustedProxies, $IP)) { + continue; + } + if (filter_var($IP, FILTER_VALIDATE_IP) !== false) { return $IP; } diff --git a/lib/private/AppFramework/Http/RequestId.php b/lib/private/AppFramework/Http/RequestId.php index 70032873a75..a6b24c0a2ff 100644 --- a/lib/private/AppFramework/Http/RequestId.php +++ b/lib/private/AppFramework/Http/RequestId.php @@ -31,7 +31,7 @@ class RequestId implements IRequestId { protected string $requestId; public function __construct(string $uniqueId, - ISecureRandom $secureRandom) { + ISecureRandom $secureRandom) { $this->requestId = $uniqueId; $this->secureRandom = $secureRandom; } diff --git a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php index 35eb0098eed..e129f70aef6 100644 --- a/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php +++ b/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php @@ -40,15 +40,15 @@ use OCP\AppFramework\Middleware; */ class MiddlewareDispatcher { /** - * @var array array containing all the middlewares + * @var Middleware[] array containing all the middlewares */ - private $middlewares; + private array $middlewares; /** * @var int counter which tells us what middleware was executed once an * exception occurs */ - private $middlewareCounter; + private int $middlewareCounter; /** @@ -64,14 +64,14 @@ class MiddlewareDispatcher { * Adds a new middleware * @param Middleware $middleWare the middleware which will be added */ - public function registerMiddleware(Middleware $middleWare) { + public function registerMiddleware(Middleware $middleWare): void { $this->middlewares[] = $middleWare; } /** * returns an array with all middleware elements - * @return array the middlewares + * @return Middleware[] the middlewares */ public function getMiddlewares(): array { return $this->middlewares; @@ -86,7 +86,7 @@ class MiddlewareDispatcher { * @param string $methodName the name of the method that will be called on * the controller */ - public function beforeController(Controller $controller, string $methodName) { + public function beforeController(Controller $controller, string $methodName): void { // we need to count so that we know which middlewares we have to ask in // case there is an exception $middlewareCount = \count($this->middlewares); diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php index f0d6ece8a93..fef9632487e 100644 --- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -59,9 +59,9 @@ class CORSMiddleware extends Middleware { private $throttler; public function __construct(IRequest $request, - ControllerMethodReflector $reflector, - Session $session, - IThrottler $throttler) { + ControllerMethodReflector $reflector, + Session $session, + IThrottler $throttler) { $this->request = $request; $this->reflector = $reflector; $this->session = $session; diff --git a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php index ae0dc1f134e..60a7cef8fa1 100644 --- a/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CSPMiddleware.php @@ -44,8 +44,8 @@ class CSPMiddleware extends Middleware { private $csrfTokenManager; public function __construct(ContentSecurityPolicyManager $policyManager, - ContentSecurityPolicyNonceManager $cspNonceManager, - CsrfTokenManager $csrfTokenManager) { + ContentSecurityPolicyNonceManager $cspNonceManager, + CsrfTokenManager $csrfTokenManager) { $this->contentSecurityPolicyManager = $policyManager; $this->cspNonceManager = $cspNonceManager; $this->csrfTokenManager = $csrfTokenManager; diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php index a72a7a40016..351f47ea924 100644 --- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -55,9 +55,9 @@ class PasswordConfirmationMiddleware extends Middleware { * @param ITimeFactory $timeFactory */ public function __construct(ControllerMethodReflector $reflector, - ISession $session, - IUserSession $userSession, - ITimeFactory $timeFactory) { + ISession $session, + IUserSession $userSession, + ITimeFactory $timeFactory) { $this->reflector = $reflector; $this->session = $session; $this->userSession = $userSession; diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php index e6d35dc66f2..870efdd44fa 100644 --- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php @@ -38,7 +38,7 @@ class SameSiteCookieMiddleware extends Middleware { private $reflector; public function __construct(Request $request, - ControllerMethodReflector $reflector) { + ControllerMethodReflector $reflector) { $this->request = $request; $this->reflector = $reflector; } diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index db6c7a02c77..a97876fd9ab 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -104,18 +104,18 @@ class SecurityMiddleware extends Middleware { private $userSession; public function __construct(IRequest $request, - ControllerMethodReflector $reflector, - INavigationManager $navigationManager, - IURLGenerator $urlGenerator, - LoggerInterface $logger, - string $appName, - bool $isLoggedIn, - bool $isAdminUser, - bool $isSubAdmin, - IAppManager $appManager, - IL10N $l10n, - AuthorizedGroupMapper $mapper, - IUserSession $userSession + ControllerMethodReflector $reflector, + INavigationManager $navigationManager, + IURLGenerator $urlGenerator, + LoggerInterface $logger, + string $appName, + bool $isLoggedIn, + bool $isAdminUser, + bool $isSubAdmin, + IAppManager $appManager, + IL10N $l10n, + AuthorizedGroupMapper $mapper, + IUserSession $userSession ) { $this->navigationManager = $navigationManager; $this->request = $request; diff --git a/lib/private/AppFramework/Middleware/SessionMiddleware.php b/lib/private/AppFramework/Middleware/SessionMiddleware.php index 39f85915901..0acdcf8b7ef 100644 --- a/lib/private/AppFramework/Middleware/SessionMiddleware.php +++ b/lib/private/AppFramework/Middleware/SessionMiddleware.php @@ -44,7 +44,7 @@ class SessionMiddleware extends Middleware { private $session; public function __construct(ControllerMethodReflector $reflector, - ISession $session) { + ISession $session) { $this->reflector = $reflector; $this->session = $session; } diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php index 123b73d302c..3cfe8177ae7 100644 --- a/lib/private/AppFramework/OCS/BaseResponse.php +++ b/lib/private/AppFramework/OCS/BaseResponse.php @@ -64,10 +64,10 @@ abstract class BaseResponse extends Response { * @param int|null $itemsPerPage */ public function __construct(DataResponse $dataResponse, - $format = 'xml', - $statusMessage = null, - $itemsCount = null, - $itemsPerPage = null) { + $format = 'xml', + $statusMessage = null, + $itemsCount = null, + $itemsPerPage = null) { parent::__construct(); $this->format = $format; diff --git a/lib/private/AppFramework/ScopedPsrLogger.php b/lib/private/AppFramework/ScopedPsrLogger.php index 4ed91cdb6c0..1cb58da11ef 100644 --- a/lib/private/AppFramework/ScopedPsrLogger.php +++ b/lib/private/AppFramework/ScopedPsrLogger.php @@ -37,7 +37,7 @@ class ScopedPsrLogger implements LoggerInterface { private $appId; public function __construct(LoggerInterface $inner, - string $appId) { + string $appId) { $this->inner = $inner; $this->appId = $appId; } diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php index b76b3c33c42..5a1ed0fd6ee 100644 --- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php +++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php @@ -42,6 +42,7 @@ class ControllerMethodReflector implements IControllerMethodReflector { public $annotations = []; private $types = []; private $parameters = []; + private array $ranges = []; /** * @param object $object an object or classname @@ -54,26 +55,38 @@ class ControllerMethodReflector implements IControllerMethodReflector { if ($docs !== false) { // extract everything prefixed by @ and first letter uppercase preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)((?P<parameter>.*))?$/m', $docs, $matches); - foreach ($matches['annotation'] as $key => $annontation) { - $annontation = strtolower($annontation); + foreach ($matches['annotation'] as $key => $annotation) { + $annotation = strtolower($annotation); $annotationValue = $matches['parameter'][$key]; if (isset($annotationValue[0]) && $annotationValue[0] === '(' && $annotationValue[\strlen($annotationValue) - 1] === ')') { $cutString = substr($annotationValue, 1, -1); $cutString = str_replace(' ', '', $cutString); - $splittedArray = explode(',', $cutString); - foreach ($splittedArray as $annotationValues) { + $splitArray = explode(',', $cutString); + foreach ($splitArray as $annotationValues) { [$key, $value] = explode('=', $annotationValues); - $this->annotations[$annontation][$key] = $value; + $this->annotations[$annotation][$key] = $value; } continue; } - $this->annotations[$annontation] = [$annotationValue]; + $this->annotations[$annotation] = [$annotationValue]; } // extract type parameter information preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches); $this->types = array_combine($matches['var'], $matches['type']); + preg_match_all('/@psalm-param\h+(?P<type>\w+)<(?P<rangeMin>(-?\d+|min)),\h*(?P<rangeMax>(-?\d+|max))>\h+\$(?P<var>\w+)/', $docs, $matches); + foreach ($matches['var'] as $index => $varName) { + if ($matches['type'][$index] !== 'int') { + // only int ranges are possible at the moment + // @see https://psalm.dev/docs/annotating_code/type_syntax/scalar_types + continue; + } + $this->ranges[$varName] = [ + 'min' => $matches['rangeMin'][$index] === 'min' ? PHP_INT_MIN : (int)$matches['rangeMin'][$index], + 'max' => $matches['rangeMax'][$index] === 'max' ? PHP_INT_MAX : (int)$matches['rangeMax'][$index], + ]; + } } foreach ($reflection->getParameters() as $param) { @@ -106,6 +119,14 @@ class ControllerMethodReflector implements IControllerMethodReflector { return null; } + public function getRange(string $parameter): ?array { + if (array_key_exists($parameter, $this->ranges)) { + return $this->ranges[$parameter]; + } + + return null; + } + /** * @return array the arguments of the method with key => default value */ diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 7aa5cb83926..83aed4381b3 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -37,8 +37,8 @@ use Pimple\Container; use Psr\Container\ContainerInterface; use ReflectionClass; use ReflectionException; -use ReflectionParameter; use ReflectionNamedType; +use ReflectionParameter; use function class_exists; /** @@ -105,6 +105,11 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { try { return $this->query($resolveName); } catch (QueryException $e2) { + // Pass null if typed and nullable + if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) { + return null; + } + // don't lose the error we got while trying to query by type throw new QueryException($e->getMessage(), (int) $e->getCode(), $e); } diff --git a/lib/private/AppScriptSort.php b/lib/private/AppScriptSort.php index c42d02d485d..2e36034d04f 100644 --- a/lib/private/AppScriptSort.php +++ b/lib/private/AppScriptSort.php @@ -46,10 +46,10 @@ class AppScriptSort { * @param array $sortedScriptDeps */ private function topSortVisit( - AppScriptDependency $app, - array &$parents, - array &$scriptDeps, - array &$sortedScriptDeps): void { + AppScriptDependency $app, + array &$parents, + array &$scriptDeps, + array &$sortedScriptDeps): void { // Detect and log circular dependencies if (isset($parents[$app->getId()])) { $this->logger->error('Circular dependency in app scripts at app ' . $app->getId()); diff --git a/lib/private/Archive/TAR.php b/lib/private/Archive/TAR.php index 9dc906384e0..a6140e44eb6 100644 --- a/lib/private/Archive/TAR.php +++ b/lib/private/Archive/TAR.php @@ -197,7 +197,7 @@ class TAR extends Archive { if ($pos = strpos($result, '/')) { $result = substr($result, 0, $pos + 1); } - if (array_search($result, $folderContent) === false) { + if (!in_array($result, $folderContent)) { $folderContent[] = $result; } } @@ -269,7 +269,7 @@ class TAR extends Archive { */ public function fileExists(string $path): bool { $files = $this->getFiles(); - if ((array_search($path, $files) !== false) or (array_search($path . '/', $files) !== false)) { + if ((in_array($path, $files)) or (in_array($path . '/', $files))) { return true; } else { $folderPath = rtrim($path, '/') . '/'; diff --git a/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php index edebb2a2641..3e8348f075a 100644 --- a/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php +++ b/lib/private/Authentication/Listeners/RemoteWipeActivityListener.php @@ -46,7 +46,7 @@ class RemoteWipeActivityListener implements IEventListener { private $logger; public function __construct(IActvityManager $activityManager, - LoggerInterface $logger) { + LoggerInterface $logger) { $this->activityManager = $activityManager; $this->logger = $logger; } diff --git a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php index cba2b183589..fb3f771d1e4 100644 --- a/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php +++ b/lib/private/Authentication/Listeners/RemoteWipeEmailListener.php @@ -57,9 +57,9 @@ class RemoteWipeEmailListener implements IEventListener { private $logger; public function __construct(IMailer $mailer, - IUserManager $userManager, - IL10nFactory $l10nFactory, - LoggerInterface $logger) { + IUserManager $userManager, + IL10nFactory $l10nFactory, + LoggerInterface $logger) { $this->mailer = $mailer; $this->userManager = $userManager; $this->l10n = $l10nFactory->get('core'); diff --git a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php index 81feab32746..37732ecf5f2 100644 --- a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php +++ b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php @@ -45,7 +45,7 @@ class RemoteWipeNotificationsListener implements IEventListener { private $timeFactory; public function __construct(INotificationManager $notificationManager, - ITimeFactory $timeFactory) { + ITimeFactory $timeFactory) { $this->notificationManager = $notificationManager; $this->timeFactory = $timeFactory; } diff --git a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php index a09a08568d5..f4f08a50add 100644 --- a/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php +++ b/lib/private/Authentication/Listeners/UserDeletedTokenCleanupListener.php @@ -44,7 +44,7 @@ class UserDeletedTokenCleanupListener implements IEventListener { private $logger; public function __construct(Manager $manager, - LoggerInterface $logger) { + LoggerInterface $logger) { $this->manager = $manager; $this->logger = $logger; } diff --git a/lib/private/Authentication/Login/Chain.php b/lib/private/Authentication/Login/Chain.php index 3c3179472c4..60ecd004388 100644 --- a/lib/private/Authentication/Login/Chain.php +++ b/lib/private/Authentication/Login/Chain.php @@ -63,17 +63,17 @@ class Chain { private $finishRememberedLoginCommand; public function __construct(PreLoginHookCommand $preLoginHookCommand, - UserDisabledCheckCommand $userDisabledCheckCommand, - UidLoginCommand $uidLoginCommand, - EmailLoginCommand $emailLoginCommand, - LoggedInCheckCommand $loggedInCheckCommand, - CompleteLoginCommand $completeLoginCommand, - CreateSessionTokenCommand $createSessionTokenCommand, - ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand, - UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand, - SetUserTimezoneCommand $setUserTimezoneCommand, - TwoFactorCommand $twoFactorCommand, - FinishRememberedLoginCommand $finishRememberedLoginCommand + UserDisabledCheckCommand $userDisabledCheckCommand, + UidLoginCommand $uidLoginCommand, + EmailLoginCommand $emailLoginCommand, + LoggedInCheckCommand $loggedInCheckCommand, + CompleteLoginCommand $completeLoginCommand, + CreateSessionTokenCommand $createSessionTokenCommand, + ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand, + UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand, + SetUserTimezoneCommand $setUserTimezoneCommand, + TwoFactorCommand $twoFactorCommand, + FinishRememberedLoginCommand $finishRememberedLoginCommand ) { $this->preLoginHookCommand = $preLoginHookCommand; $this->userDisabledCheckCommand = $userDisabledCheckCommand; diff --git a/lib/private/Authentication/Login/CreateSessionTokenCommand.php b/lib/private/Authentication/Login/CreateSessionTokenCommand.php index ba237dfbf20..41616e6dad3 100644 --- a/lib/private/Authentication/Login/CreateSessionTokenCommand.php +++ b/lib/private/Authentication/Login/CreateSessionTokenCommand.php @@ -39,7 +39,7 @@ class CreateSessionTokenCommand extends ALoginCommand { private $userSession; public function __construct(IConfig $config, - Session $userSession) { + Session $userSession) { $this->config = $config; $this->userSession = $userSession; } diff --git a/lib/private/Authentication/Login/LoggedInCheckCommand.php b/lib/private/Authentication/Login/LoggedInCheckCommand.php index dc1a4d2d883..6b241d79746 100644 --- a/lib/private/Authentication/Login/LoggedInCheckCommand.php +++ b/lib/private/Authentication/Login/LoggedInCheckCommand.php @@ -39,7 +39,7 @@ class LoggedInCheckCommand extends ALoginCommand { private $dispatcher; public function __construct(LoggerInterface $logger, - IEventDispatcher $dispatcher) { + IEventDispatcher $dispatcher) { $this->logger = $logger; $this->dispatcher = $dispatcher; } diff --git a/lib/private/Authentication/Login/LoginData.php b/lib/private/Authentication/Login/LoginData.php index 240a1dc6476..0ce11cf70fc 100644 --- a/lib/private/Authentication/Login/LoginData.php +++ b/lib/private/Authentication/Login/LoginData.php @@ -55,11 +55,11 @@ class LoginData { private $rememberLogin = true; public function __construct(IRequest $request, - string $username, - ?string $password, - string $redirectUrl = null, - string $timeZone = '', - string $timeZoneOffset = '') { + string $username, + ?string $password, + string $redirectUrl = null, + string $timeZone = '', + string $timeZoneOffset = '') { $this->request = $request; $this->username = $username; $this->password = $password; diff --git a/lib/private/Authentication/Login/LoginResult.php b/lib/private/Authentication/Login/LoginResult.php index dec012c2fc9..18820d98a47 100644 --- a/lib/private/Authentication/Login/LoginResult.php +++ b/lib/private/Authentication/Login/LoginResult.php @@ -25,6 +25,8 @@ declare(strict_types=1); */ namespace OC\Authentication\Login; +use OC\Core\Controller\LoginController; + class LoginResult { /** @var bool */ private $success; @@ -59,6 +61,9 @@ class LoginResult { return $result; } + /** + * @param LoginController::LOGIN_MSG_*|null $msg + */ public static function failure(LoginData $data, string $msg = null): LoginResult { $result = new static(false, $data); if ($msg !== null) { diff --git a/lib/private/Authentication/Login/SetUserTimezoneCommand.php b/lib/private/Authentication/Login/SetUserTimezoneCommand.php index f68fce1771e..881e1c451a9 100644 --- a/lib/private/Authentication/Login/SetUserTimezoneCommand.php +++ b/lib/private/Authentication/Login/SetUserTimezoneCommand.php @@ -36,7 +36,7 @@ class SetUserTimezoneCommand extends ALoginCommand { private $session; public function __construct(IConfig $config, - ISession $session) { + ISession $session) { $this->config = $config; $this->session = $session; } diff --git a/lib/private/Authentication/Login/TwoFactorCommand.php b/lib/private/Authentication/Login/TwoFactorCommand.php index 256d88ffa81..aa5a2ff96f4 100644 --- a/lib/private/Authentication/Login/TwoFactorCommand.php +++ b/lib/private/Authentication/Login/TwoFactorCommand.php @@ -26,12 +26,12 @@ declare(strict_types=1); */ namespace OC\Authentication\Login; -use function array_pop; -use function count; use OC\Authentication\TwoFactorAuth\Manager; use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor; use OCP\Authentication\TwoFactorAuth\IProvider; use OCP\IURLGenerator; +use function array_pop; +use function count; class TwoFactorCommand extends ALoginCommand { /** @var Manager */ @@ -44,8 +44,8 @@ class TwoFactorCommand extends ALoginCommand { private $urlGenerator; public function __construct(Manager $twoFactorManager, - MandatoryTwoFactor $mandatoryTwoFactor, - IURLGenerator $urlGenerator) { + MandatoryTwoFactor $mandatoryTwoFactor, + IURLGenerator $urlGenerator) { $this->twoFactorManager = $twoFactorManager; $this->mandatoryTwoFactor = $mandatoryTwoFactor; $this->urlGenerator = $urlGenerator; diff --git a/lib/private/Authentication/Login/UserDisabledCheckCommand.php b/lib/private/Authentication/Login/UserDisabledCheckCommand.php index 7cf4c7235ec..8354457b56a 100644 --- a/lib/private/Authentication/Login/UserDisabledCheckCommand.php +++ b/lib/private/Authentication/Login/UserDisabledCheckCommand.php @@ -38,7 +38,7 @@ class UserDisabledCheckCommand extends ALoginCommand { private $logger; public function __construct(IUserManager $userManager, - LoggerInterface $logger) { + LoggerInterface $logger) { $this->userManager = $userManager; $this->logger = $logger; } diff --git a/lib/private/Authentication/Login/WebAuthnChain.php b/lib/private/Authentication/Login/WebAuthnChain.php index f3ebc313a44..d0fcf691d46 100644 --- a/lib/private/Authentication/Login/WebAuthnChain.php +++ b/lib/private/Authentication/Login/WebAuthnChain.php @@ -57,15 +57,15 @@ class WebAuthnChain { private $webAuthnLoginCommand; public function __construct(UserDisabledCheckCommand $userDisabledCheckCommand, - WebAuthnLoginCommand $webAuthnLoginCommand, - LoggedInCheckCommand $loggedInCheckCommand, - CompleteLoginCommand $completeLoginCommand, - CreateSessionTokenCommand $createSessionTokenCommand, - ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand, - UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand, - SetUserTimezoneCommand $setUserTimezoneCommand, - TwoFactorCommand $twoFactorCommand, - FinishRememberedLoginCommand $finishRememberedLoginCommand + WebAuthnLoginCommand $webAuthnLoginCommand, + LoggedInCheckCommand $loggedInCheckCommand, + CompleteLoginCommand $completeLoginCommand, + CreateSessionTokenCommand $createSessionTokenCommand, + ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand, + UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand, + SetUserTimezoneCommand $setUserTimezoneCommand, + TwoFactorCommand $twoFactorCommand, + FinishRememberedLoginCommand $finishRememberedLoginCommand ) { $this->userDisabledCheckCommand = $userDisabledCheckCommand; $this->webAuthnLoginCommand = $webAuthnLoginCommand; diff --git a/lib/private/Authentication/LoginCredentials/Store.php b/lib/private/Authentication/LoginCredentials/Store.php index 3a09e983ee8..e1e29946446 100644 --- a/lib/private/Authentication/LoginCredentials/Store.php +++ b/lib/private/Authentication/LoginCredentials/Store.php @@ -48,8 +48,8 @@ class Store implements IStore { private $tokenProvider; public function __construct(ISession $session, - LoggerInterface $logger, - IProvider $tokenProvider = null) { + LoggerInterface $logger, + IProvider $tokenProvider = null) { $this->session = $session; $this->logger = $logger; $this->tokenProvider = $tokenProvider; diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index a12d3ba34d9..4af5e2b25c3 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -49,12 +49,12 @@ interface IProvider { * @throws \RuntimeException when OpenSSL reports a problem */ public function generateToken(string $token, - string $uid, - string $loginName, - ?string $password, - string $name, - int $type = IToken::TEMPORARY_TOKEN, - int $remember = IToken::DO_NOT_REMEMBER): IToken; + string $uid, + string $loginName, + ?string $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken; /** * Get a token by token id diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index 6a1c7d4c1e7..18ec687cac2 100644 --- a/lib/private/Authentication/Token/Manager.php +++ b/lib/private/Authentication/Token/Manager.php @@ -55,12 +55,12 @@ class Manager implements IProvider, OCPIProvider { * @return IToken */ public function generateToken(string $token, - string $uid, - string $loginName, - $password, - string $name, - int $type = IToken::TEMPORARY_TOKEN, - int $remember = IToken::DO_NOT_REMEMBER): IToken { + string $uid, + string $loginName, + $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken { if (mb_strlen($name) > 128) { $name = mb_substr($name, 0, 120) . '…'; } diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index 3fb11611076..a88194726ae 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -31,13 +31,13 @@ namespace OC\Authentication\Token; use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; -use OC\Authentication\Exceptions\TokenPasswordExpiredException; use OC\Authentication\Exceptions\PasswordlessTokenException; +use OC\Authentication\Exceptions\TokenPasswordExpiredException; use OC\Authentication\Exceptions\WipeTokenException; -use OCP\AppFramework\Db\TTransactional; -use OCP\Cache\CappedMemoryCache; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\TTransactional; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Cache\CappedMemoryCache; use OCP\IConfig; use OCP\IDBConnection; use OCP\IUserManager; @@ -73,12 +73,12 @@ class PublicKeyTokenProvider implements IProvider { private IHasher $hasher; public function __construct(PublicKeyTokenMapper $mapper, - ICrypto $crypto, - IConfig $config, - IDBConnection $db, - LoggerInterface $logger, - ITimeFactory $time, - IHasher $hasher) { + ICrypto $crypto, + IConfig $config, + IDBConnection $db, + LoggerInterface $logger, + ITimeFactory $time, + IHasher $hasher) { $this->mapper = $mapper; $this->crypto = $crypto; $this->config = $config; @@ -94,12 +94,12 @@ class PublicKeyTokenProvider implements IProvider { * {@inheritDoc} */ public function generateToken(string $token, - string $uid, - string $loginName, - ?string $password, - string $name, - int $type = IToken::TEMPORARY_TOKEN, - int $remember = IToken::DO_NOT_REMEMBER): IToken { + string $uid, + string $loginName, + ?string $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken { if (strlen($token) < self::TOKEN_MIN_LENGTH) { $exception = new InvalidTokenException('Token is too short, minimum of ' . self::TOKEN_MIN_LENGTH . ' characters is required, ' . strlen($token) . ' characters given'); $this->logger->error('Invalid token provided when generating new token', ['exception' => $exception]); @@ -425,12 +425,12 @@ class PublicKeyTokenProvider implements IProvider { * @throws \RuntimeException when OpenSSL reports a problem */ private function newToken(string $token, - string $uid, - string $loginName, - $password, - string $name, - int $type, - int $remember): PublicKeyToken { + string $uid, + string $loginName, + $password, + string $name, + int $type, + int $remember): PublicKeyToken { $dbToken = new PublicKeyToken(); $dbToken->setUid($uid); $dbToken->setLoginName($loginName); diff --git a/lib/private/Authentication/Token/RemoteWipe.php b/lib/private/Authentication/Token/RemoteWipe.php index 5fd01cfbe87..e4882f678d9 100644 --- a/lib/private/Authentication/Token/RemoteWipe.php +++ b/lib/private/Authentication/Token/RemoteWipe.php @@ -27,14 +27,14 @@ declare(strict_types=1); */ namespace OC\Authentication\Token; -use Psr\Log\LoggerInterface; -use function array_filter; use OC\Authentication\Events\RemoteWipeFinished; use OC\Authentication\Events\RemoteWipeStarted; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\WipeTokenException; use OCP\EventDispatcher\IEventDispatcher; use OCP\IUser; +use Psr\Log\LoggerInterface; +use function array_filter; class RemoteWipe { /** @var IProvider */ @@ -47,8 +47,8 @@ class RemoteWipe { private $logger; public function __construct(IProvider $tokenProvider, - IEventDispatcher $eventDispatcher, - LoggerInterface $logger) { + IEventDispatcher $eventDispatcher, + LoggerInterface $logger) { $this->tokenProvider = $tokenProvider; $this->eventDispatcher = $eventDispatcher; $this->logger = $logger; diff --git a/lib/private/Authentication/TwoFactorAuth/EnforcementState.php b/lib/private/Authentication/TwoFactorAuth/EnforcementState.php index b95128c1e0f..91f133d6ad0 100644 --- a/lib/private/Authentication/TwoFactorAuth/EnforcementState.php +++ b/lib/private/Authentication/TwoFactorAuth/EnforcementState.php @@ -45,8 +45,8 @@ class EnforcementState implements JsonSerializable { * @param string[] $excludedGroups */ public function __construct(bool $enforced, - array $enforcedGroups = [], - array $excludedGroups = []) { + array $enforcedGroups = [], + array $excludedGroups = []) { $this->enforced = $enforced; $this->enforcedGroups = $enforcedGroups; $this->excludedGroups = $excludedGroups; diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php index ff0c33445a2..b0bb73c3115 100644 --- a/lib/private/Authentication/TwoFactorAuth/Manager.php +++ b/lib/private/Authentication/TwoFactorAuth/Manager.php @@ -89,15 +89,15 @@ class Manager { private $userIsTwoFactorAuthenticated = []; public function __construct(ProviderLoader $providerLoader, - IRegistry $providerRegistry, - MandatoryTwoFactor $mandatoryTwoFactor, - ISession $session, - IConfig $config, - IManager $activityManager, - LoggerInterface $logger, - TokenProvider $tokenProvider, - ITimeFactory $timeFactory, - IEventDispatcher $eventDispatcher) { + IRegistry $providerRegistry, + MandatoryTwoFactor $mandatoryTwoFactor, + ISession $session, + IConfig $config, + IManager $activityManager, + LoggerInterface $logger, + TokenProvider $tokenProvider, + ITimeFactory $timeFactory, + IEventDispatcher $eventDispatcher) { $this->providerLoader = $providerLoader; $this->providerRegistry = $providerRegistry; $this->mandatoryTwoFactor = $mandatoryTwoFactor; diff --git a/lib/private/Authentication/TwoFactorAuth/ProviderSet.php b/lib/private/Authentication/TwoFactorAuth/ProviderSet.php index af270fb83c8..4d39fd82bc6 100644 --- a/lib/private/Authentication/TwoFactorAuth/ProviderSet.php +++ b/lib/private/Authentication/TwoFactorAuth/ProviderSet.php @@ -25,9 +25,9 @@ declare(strict_types=1); */ namespace OC\Authentication\TwoFactorAuth; -use function array_filter; use OCA\TwoFactorBackupCodes\Provider\BackupCodesProvider; use OCP\Authentication\TwoFactorAuth\IProvider; +use function array_filter; /** * Contains all two-factor provider information for the two-factor login challenge diff --git a/lib/private/Authentication/TwoFactorAuth/Registry.php b/lib/private/Authentication/TwoFactorAuth/Registry.php index 482c025e144..db772265583 100644 --- a/lib/private/Authentication/TwoFactorAuth/Registry.php +++ b/lib/private/Authentication/TwoFactorAuth/Registry.php @@ -45,7 +45,7 @@ class Registry implements IRegistry { private $dispatcher; public function __construct(ProviderUserAssignmentDao $assignmentDao, - IEventDispatcher $dispatcher) { + IEventDispatcher $dispatcher) { $this->assignmentDao = $assignmentDao; $this->dispatcher = $dispatcher; } diff --git a/lib/private/Authentication/WebAuthn/Manager.php b/lib/private/Authentication/WebAuthn/Manager.php index 744a3fa354a..5a97a573b99 100644 --- a/lib/private/Authentication/WebAuthn/Manager.php +++ b/lib/private/Authentication/WebAuthn/Manager.php @@ -91,7 +91,7 @@ class Manager { $user->getUID(), //Name $user->getUID(), //ID $user->getDisplayName() //Display name -// 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon + // 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon ); $challenge = random_bytes(32); diff --git a/lib/private/Avatar/GuestAvatar.php b/lib/private/Avatar/GuestAvatar.php index 26614cf6cfa..106e159d192 100644 --- a/lib/private/Avatar/GuestAvatar.php +++ b/lib/private/Avatar/GuestAvatar.php @@ -26,8 +26,8 @@ declare(strict_types=1); */ namespace OC\Avatar; -use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\InMemoryFile; +use OCP\Files\SimpleFS\ISimpleFile; use Psr\Log\LoggerInterface; /** diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index 2b42e2ff1ee..3ec2e0af1b3 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -88,6 +88,7 @@ class JobList implements IJobList { $query->update('jobs') ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT)) ->set('last_checked', $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT)) + ->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)) ->where($query->expr()->eq('class', $query->createNamedParameter($class))) ->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson)))); } @@ -413,7 +414,7 @@ class JobList implements IJobList { $query = $this->connection->getQueryBuilder(); $query->select('*') ->from('jobs') - ->where($query->expr()->neq('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))) + ->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 6 * 3600, IQueryBuilder::PARAM_INT))) ->setMaxResults(1); if ($className !== null) { diff --git a/lib/private/BinaryFinder.php b/lib/private/BinaryFinder.php index a7ef55237db..17427e92619 100644 --- a/lib/private/BinaryFinder.php +++ b/lib/private/BinaryFinder.php @@ -22,9 +22,9 @@ declare(strict_types = 1); namespace OC; +use OCP\IBinaryFinder; use OCP\ICache; use OCP\ICacheFactory; -use OCP\IBinaryFinder; use Symfony\Component\Process\ExecutableFinder; /** diff --git a/lib/private/CapabilitiesManager.php b/lib/private/CapabilitiesManager.php index 7885a98869d..6b34b50cb98 100644 --- a/lib/private/CapabilitiesManager.php +++ b/lib/private/CapabilitiesManager.php @@ -30,8 +30,8 @@ namespace OC; use OCP\AppFramework\QueryException; use OCP\Capabilities\ICapability; -use OCP\Capabilities\IPublicCapability; use OCP\Capabilities\IInitialStateExcludedCapability; +use OCP\Capabilities\IPublicCapability; use Psr\Log\LoggerInterface; class CapabilitiesManager { diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index cbdd84efbb3..37ebf2fb129 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -37,8 +37,8 @@ use OCP\IConfig; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; -use OCP\Share\IShare; use OCP\Mail\IMailer; +use OCP\Share\IShare; class MailPlugin implements ISearchPlugin { protected bool $shareWithGroupOnly; diff --git a/lib/private/Command/AsyncBus.php b/lib/private/Command/AsyncBus.php index ec6fbc91f68..c65e6cad78e 100644 --- a/lib/private/Command/AsyncBus.php +++ b/lib/private/Command/AsyncBus.php @@ -82,7 +82,7 @@ abstract class AsyncBus implements IBus { private function canRunAsync($command) { $traits = $this->getTraits($command); foreach ($traits as $trait) { - if (array_search($trait, $this->syncTraits) !== false) { + if (in_array($trait, $this->syncTraits)) { return false; } } diff --git a/lib/private/Command/ClosureJob.php b/lib/private/Command/ClosureJob.php index 7216bcc762a..f7b0ee1a3d3 100644 --- a/lib/private/Command/ClosureJob.php +++ b/lib/private/Command/ClosureJob.php @@ -22,8 +22,8 @@ */ namespace OC\Command; -use OC\BackgroundJob\QueuedJob; use Laravel\SerializableClosure\SerializableClosure as LaravelClosure; +use OC\BackgroundJob\QueuedJob; class ClosureJob extends QueuedJob { protected function run($argument) { diff --git a/lib/private/Command/CronBus.php b/lib/private/Command/CronBus.php index 8749ad0bff5..42ff458a95c 100644 --- a/lib/private/Command/CronBus.php +++ b/lib/private/Command/CronBus.php @@ -25,8 +25,8 @@ */ namespace OC\Command; -use OCP\Command\ICommand; use Laravel\SerializableClosure\SerializableClosure; +use OCP\Command\ICommand; class CronBus extends AsyncBus { /** diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php index 35e88c74438..183821e37b1 100644 --- a/lib/private/Comments/Comment.php +++ b/lib/private/Comments/Comment.php @@ -42,6 +42,7 @@ class Comment implements IComment { 'objectType' => '', 'objectId' => '', 'referenceId' => null, + 'metaData' => null, 'creationDT' => null, 'latestChildDT' => null, 'reactions' => null, @@ -403,6 +404,34 @@ class Comment implements IComment { /** * @inheritDoc */ + public function getMetaData(): ?array { + if ($this->data['metaData'] === null) { + return null; + } + + try { + $metaData = json_decode($this->data['metaData'], true, flags: JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + return null; + } + return is_array($metaData) ? $metaData : null; + } + + /** + * @inheritDoc + */ + public function setMetaData(?array $metaData): IComment { + if ($metaData === null) { + $this->data['metaData'] = null; + } else { + $this->data['metaData'] = json_encode($metaData, JSON_THROW_ON_ERROR); + } + return $this; + } + + /** + * @inheritDoc + */ public function getReactions(): array { return $this->data['reactions'] ?? []; } diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 725febef85d..85b56f9f25c 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -29,7 +29,6 @@ namespace OC\Comments; use Doctrine\DBAL\Exception\DriverException; -use Doctrine\DBAL\Exception\InvalidFieldNameException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\CommentsEvent; use OCP\Comments\IComment; @@ -40,8 +39,8 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; use OCP\IEmojiHelper; -use OCP\IUser; use OCP\IInitialStateService; +use OCP\IUser; use OCP\PreConditionNotMetException; use OCP\Util; use Psr\Log\LoggerInterface; @@ -66,11 +65,11 @@ class Manager implements ICommentsManager { protected array $displayNameResolvers = []; public function __construct(IDBConnection $dbConn, - LoggerInterface $logger, - IConfig $config, - ITimeFactory $timeFactory, - IEmojiHelper $emojiHelper, - IInitialStateService $initialStateService) { + LoggerInterface $logger, + IConfig $config, + ITimeFactory $timeFactory, + IEmojiHelper $emojiHelper, + IInitialStateService $initialStateService) { $this->dbConn = $dbConn; $this->logger = $logger; $this->config = $config; @@ -97,7 +96,8 @@ class Manager implements ICommentsManager { $data['expire_date'] = new \DateTime($data['expire_date']); } $data['children_count'] = (int)$data['children_count']; - $data['reference_id'] = $data['reference_id'] ?? null; + $data['reference_id'] = $data['reference_id']; + $data['meta_data'] = json_decode($data['meta_data'], true); if ($this->supportReactions()) { if ($data['reactions'] !== null) { $list = json_decode($data['reactions'], true); @@ -536,8 +536,8 @@ class Manager implements ICommentsManager { * @param int $id the comment to look for */ protected function getLastKnownComment(string $objectType, - string $objectId, - int $id): ?IComment { + string $objectId, + int $id): ?IComment { $query = $this->dbConn->getQueryBuilder(); $query->select('*') ->from('comments') @@ -1150,22 +1150,6 @@ class Manager implements ICommentsManager { * @return bool */ protected function insert(IComment $comment): bool { - try { - $result = $this->insertQuery($comment, true); - } catch (InvalidFieldNameException $e) { - // The reference id field was only added in Nextcloud 19. - // In order to not cause too long waiting times on the update, - // it was decided to only add it lazy, as it is also not a critical - // feature, but only helps to have a better experience while commenting. - // So in case the reference_id field is missing, - // we simply save the comment without that field. - $result = $this->insertQuery($comment, false); - } - - return $result; - } - - protected function insertQuery(IComment $comment, bool $tryWritingReferenceId): bool { $qb = $this->dbConn->getQueryBuilder(); $values = [ @@ -1181,12 +1165,10 @@ class Manager implements ICommentsManager { 'object_type' => $qb->createNamedParameter($comment->getObjectType()), 'object_id' => $qb->createNamedParameter($comment->getObjectId()), 'expire_date' => $qb->createNamedParameter($comment->getExpireDate(), 'datetime'), + 'reference_id' => $qb->createNamedParameter($comment->getReferenceId()), + 'meta_data' => $qb->createNamedParameter(json_encode($comment->getMetaData())), ]; - if ($tryWritingReferenceId) { - $values['reference_id'] = $qb->createNamedParameter($comment->getReferenceId()); - } - $affectedRows = $qb->insert('comments') ->values($values) ->execute(); @@ -1289,12 +1271,7 @@ class Manager implements ICommentsManager { $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId())); $this->uncache($comment->getId()); - try { - $result = $this->updateQuery($comment, true); - } catch (InvalidFieldNameException $e) { - // See function insert() for explanation - $result = $this->updateQuery($comment, false); - } + $result = $this->updateQuery($comment); if ($comment->getVerb() === 'reaction_deleted') { $this->deleteReaction($comment); @@ -1305,7 +1282,7 @@ class Manager implements ICommentsManager { return $result; } - protected function updateQuery(IComment $comment, bool $tryWritingReferenceId): bool { + protected function updateQuery(IComment $comment): bool { $qb = $this->dbConn->getQueryBuilder(); $qb ->update('comments') @@ -1320,14 +1297,11 @@ class Manager implements ICommentsManager { ->set('latest_child_timestamp', $qb->createNamedParameter($comment->getLatestChildDateTime(), 'datetime')) ->set('object_type', $qb->createNamedParameter($comment->getObjectType())) ->set('object_id', $qb->createNamedParameter($comment->getObjectId())) - ->set('expire_date', $qb->createNamedParameter($comment->getExpireDate(), 'datetime')); - - if ($tryWritingReferenceId) { - $qb->set('reference_id', $qb->createNamedParameter($comment->getReferenceId())); - } - - $affectedRows = $qb->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId()))) - ->execute(); + ->set('expire_date', $qb->createNamedParameter($comment->getExpireDate(), 'datetime')) + ->set('reference_id', $qb->createNamedParameter($comment->getReferenceId())) + ->set('meta_data', $qb->createNamedParameter(json_encode($comment->getMetaData()))) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($comment->getId()))); + $affectedRows = $qb->executeStatement(); if ($affectedRows === 0) { throw new NotFoundException('Comment to update does ceased to exist'); diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index a0306c9798c..900b2c57f41 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -55,10 +55,10 @@ class Application { private MemoryInfo $memoryInfo; public function __construct(IConfig $config, - IEventDispatcher $dispatcher, - IRequest $request, - LoggerInterface $logger, - MemoryInfo $memoryInfo) { + IEventDispatcher $dispatcher, + IRequest $request, + LoggerInterface $logger, + MemoryInfo $memoryInfo) { $defaults = \OC::$server->getThemingDefaults(); $this->config = $config; $this->application = new SymfonyApplication($defaults->getName(), \OC_Util::getVersionString()); diff --git a/lib/private/Console/TimestampFormatter.php b/lib/private/Console/TimestampFormatter.php index 8d74c28e94f..afb1f67c37f 100644 --- a/lib/private/Console/TimestampFormatter.php +++ b/lib/private/Console/TimestampFormatter.php @@ -27,17 +27,17 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Formatter\OutputFormatterStyleInterface; class TimestampFormatter implements OutputFormatterInterface { - /** @var IConfig */ + /** @var ?IConfig */ protected $config; /** @var OutputFormatterInterface */ protected $formatter; /** - * @param IConfig $config + * @param ?IConfig $config * @param OutputFormatterInterface $formatter */ - public function __construct(IConfig $config, OutputFormatterInterface $formatter) { + public function __construct(?IConfig $config, OutputFormatterInterface $formatter) { $this->config = $config; $this->formatter = $formatter; } @@ -104,11 +104,16 @@ class TimestampFormatter implements OutputFormatterInterface { return $this->formatter->format($message); } - $timeZone = $this->config->getSystemValue('logtimezone', 'UTC'); - $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null; + if ($this->config instanceof IConfig) { + $timeZone = $this->config->getSystemValue('logtimezone', 'UTC'); + $timeZone = $timeZone !== null ? new \DateTimeZone($timeZone) : null; - $time = new \DateTime('now', $timeZone); - $timestampInfo = $time->format($this->config->getSystemValue('logdateformat', \DateTimeInterface::ATOM)); + $time = new \DateTime('now', $timeZone); + $timestampInfo = $time->format($this->config->getSystemValue('logdateformat', \DateTimeInterface::ATOM)); + } else { + $time = new \DateTime('now'); + $timestampInfo = $time->format(\DateTimeInterface::ATOM); + } return $timestampInfo . ' ' . $this->formatter->format($message); } diff --git a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php index 7ba5db4bb33..67354a5fb2d 100644 --- a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php +++ b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php @@ -33,6 +33,7 @@ use OC\Contacts\ContactsMenu\Providers\EMailProvider; use OC\Contacts\ContactsMenu\Providers\LocalTimeProvider; use OC\Contacts\ContactsMenu\Providers\ProfileProvider; use OCP\AppFramework\QueryException; +use OCP\Contacts\ContactsMenu\IBulkProvider; use OCP\Contacts\ContactsMenu\IProvider; use OCP\IServerContainer; use OCP\IUser; @@ -47,18 +48,26 @@ class ActionProviderStore { } /** - * @return IProvider[] + * @return list<IProvider|IBulkProvider> * @throws Exception */ public function getProviders(IUser $user): array { $appClasses = $this->getAppProviderClasses($user); $providerClasses = $this->getServerProviderClasses(); $allClasses = array_merge($providerClasses, $appClasses); + /** @var list<IProvider|IBulkProvider> $providers */ $providers = []; foreach ($allClasses as $class) { try { - $providers[] = $this->serverContainer->get($class); + $provider = $this->serverContainer->get($class); + if ($provider instanceof IProvider || $provider instanceof IBulkProvider) { + $providers[] = $provider; + } else { + $this->logger->warning('Ignoring invalid contacts menu provider', [ + 'class' => $class, + ]); + } } catch (QueryException $ex) { $this->logger->error( 'Could not load contacts menu action provider ' . $class, diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index c692b486ae4..eeb6ae56bc1 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -33,6 +33,8 @@ namespace OC\Contacts\ContactsMenu; use OC\KnownUser\KnownUserService; use OC\Profile\ProfileManager; +use OCA\UserStatus\Db\UserStatus; +use OCA\UserStatus\Service\StatusService; use OCP\Contacts\ContactsMenu\IContactsStore; use OCP\Contacts\ContactsMenu\IEntry; use OCP\Contacts\IManager; @@ -42,10 +44,17 @@ use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; use OCP\L10N\IFactory as IL10NFactory; +use function array_column; +use function array_fill_keys; +use function array_filter; +use function array_key_exists; +use function array_merge; +use function count; class ContactsStore implements IContactsStore { public function __construct( private IManager $contactsManager, + private ?StatusService $userStatusService, private IConfig $config, private ProfileManager $profileManager, private IUserManager $userManager, @@ -70,15 +79,75 @@ class ContactsStore implements IContactsStore { if ($offset !== null) { $options['offset'] = $offset; } + // Status integration only works without pagination and filters + if ($offset === null && ($filter === null || $filter === '')) { + $recentStatuses = $this->userStatusService?->findAllRecentStatusChanges($limit, $offset) ?? []; + } else { + $recentStatuses = []; + } - $allContacts = $this->contactsManager->search( - $filter ?? '', - [ - 'FN', - 'EMAIL' - ], - $options - ); + // Search by status if there is no filter and statuses are available + if (!empty($recentStatuses)) { + $allContacts = array_filter(array_map(function (UserStatus $userStatus) use ($options) { + // UID is ambiguous with federation. We have to use the federated cloud ID to an exact match of + // A local user + $user = $this->userManager->get($userStatus->getUserId()); + if ($user === null) { + return null; + } + + $contact = $this->contactsManager->search( + $user->getCloudId(), + [ + 'CLOUD', + ], + array_merge( + $options, + [ + 'limit' => 1, + 'offset' => 0, + ], + ), + )[0] ?? null; + if ($contact !== null) { + $contact[Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP] = $userStatus->getStatusMessageTimestamp(); + } + return $contact; + }, $recentStatuses)); + if ($limit !== null && count($allContacts) < $limit) { + // More contacts were requested + $fromContacts = $this->contactsManager->search( + $filter ?? '', + [ + 'FN', + 'EMAIL' + ], + array_merge( + $options, + [ + 'limit' => $limit - count($allContacts), + ], + ), + ); + + // Create hash map of all status contacts + $existing = array_fill_keys(array_column($allContacts, 'URI'), null); + // Append the ones that are new + $allContacts = array_merge( + $allContacts, + array_filter($fromContacts, fn (array $contact): bool => !array_key_exists($contact['URI'], $existing)) + ); + } + } else { + $allContacts = $this->contactsManager->search( + $filter ?? '', + [ + 'FN', + 'EMAIL' + ], + $options + ); + } $userId = $user->getUID(); $contacts = array_filter($allContacts, function ($contact) use ($userId) { @@ -268,8 +337,10 @@ class ContactsStore implements IContactsStore { if (isset($contact['UID'])) { $uid = $contact['UID']; $entry->setId($uid); + $entry->setProperty('isUser', false); if (isset($contact['isLocalSystemBook'])) { $avatar = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 64]); + $entry->setProperty('isUser', true); } elseif (isset($contact['FN'])) { $avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => $contact['FN'], 'size' => 64]); } else { diff --git a/lib/private/Contacts/ContactsMenu/Entry.php b/lib/private/Contacts/ContactsMenu/Entry.php index f1cb4f9c52f..954f46e1296 100644 --- a/lib/private/Contacts/ContactsMenu/Entry.php +++ b/lib/private/Contacts/ContactsMenu/Entry.php @@ -29,8 +29,11 @@ namespace OC\Contacts\ContactsMenu; use OCP\Contacts\ContactsMenu\IAction; use OCP\Contacts\ContactsMenu\IEntry; +use function array_merge; class Entry implements IEntry { + public const PROPERTY_STATUS_MESSAGE_TIMESTAMP = 'statusMessageTimestamp'; + /** @var string|int|null */ private $id = null; @@ -50,6 +53,11 @@ class Entry implements IEntry { private array $properties = []; + private ?string $status = null; + private ?string $statusMessage = null; + private ?int $statusMessageTimestamp = null; + private ?string $statusIcon = null; + public function setId(string $id): void { $this->id = $id; } @@ -102,6 +110,16 @@ class Entry implements IEntry { $this->sortActions(); } + public function setStatus(string $status, + string $statusMessage = null, + int $statusMessageTimestamp = null, + string $icon = null): void { + $this->status = $status; + $this->statusMessage = $statusMessage; + $this->statusMessageTimestamp = $statusMessageTimestamp; + $this->statusIcon = $icon; + } + /** * @return IAction[] */ @@ -127,11 +145,15 @@ class Entry implements IEntry { }); } + public function setProperty(string $propertyName, mixed $value) { + $this->properties[$propertyName] = $value; + } + /** - * @param array $contact key-value array containing additional properties + * @param array $properties key-value array containing additional properties */ - public function setProperties(array $contact): void { - $this->properties = $contact; + public function setProperties(array $properties): void { + $this->properties = array_merge($this->properties, $properties); } public function getProperty(string $key): mixed { @@ -142,7 +164,7 @@ class Entry implements IEntry { } /** - * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null} + * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null, status: string|null, statusMessage: null|string, statusMessageTimestamp: null|int, statusIcon: null|string, isUser: bool, uid: mixed} */ public function jsonSerialize(): array { $topAction = !empty($this->actions) ? $this->actions[0]->jsonSerialize() : null; @@ -160,6 +182,20 @@ class Entry implements IEntry { 'emailAddresses' => $this->getEMailAddresses(), 'profileTitle' => $this->profileTitle, 'profileUrl' => $this->profileUrl, + 'status' => $this->status, + 'statusMessage' => $this->statusMessage, + 'statusMessageTimestamp' => $this->statusMessageTimestamp, + 'statusIcon' => $this->statusIcon, + 'isUser' => $this->getProperty('isUser') === true, + 'uid' => $this->getProperty('UID'), ]; } + + public function getStatusMessage(): ?string { + return $this->statusMessage; + } + + public function getStatusMessageTimestamp(): ?int { + return $this->statusMessageTimestamp; + } } diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php index 490cf602283..5cf9a07c8e3 100644 --- a/lib/private/Contacts/ContactsMenu/Manager.php +++ b/lib/private/Contacts/ContactsMenu/Manager.php @@ -28,7 +28,9 @@ namespace OC\Contacts\ContactsMenu; use Exception; use OCP\App\IAppManager; use OCP\Constants; +use OCP\Contacts\ContactsMenu\IBulkProvider; use OCP\Contacts\ContactsMenu\IEntry; +use OCP\Contacts\ContactsMenu\IProvider; use OCP\IConfig; use OCP\IUser; @@ -80,8 +82,19 @@ class Manager { * @return IEntry[] */ private function sortEntries(array $entries): array { - usort($entries, function (IEntry $entryA, IEntry $entryB) { - return strcasecmp($entryA->getFullName(), $entryB->getFullName()); + usort($entries, function (Entry $entryA, Entry $entryB) { + $aStatusTimestamp = $entryA->getProperty(Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP); + $bStatusTimestamp = $entryB->getProperty(Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP); + if (!$aStatusTimestamp && !$bStatusTimestamp) { + return strcasecmp($entryA->getFullName(), $entryB->getFullName()); + } + if ($aStatusTimestamp === null) { + return 1; + } + if ($bStatusTimestamp === null) { + return -1; + } + return $bStatusTimestamp - $aStatusTimestamp; }); return $entries; } @@ -92,9 +105,14 @@ class Manager { */ private function processEntries(array $entries, IUser $user): void { $providers = $this->actionProviderStore->getProviders($user); - foreach ($entries as $entry) { - foreach ($providers as $provider) { - $provider->process($entry); + + foreach ($providers as $provider) { + if ($provider instanceof IBulkProvider && !($provider instanceof IProvider)) { + $provider->process($entries); + } elseif ($provider instanceof IProvider && !($provider instanceof IBulkProvider)) { + foreach ($entries as $entry) { + $provider->process($entry); + } } } } diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index df35e0b5e0d..6e2724ca5ab 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -46,13 +46,13 @@ use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Statement; +use OC\DB\QueryBuilder\QueryBuilder; +use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Diagnostics\IEventLogger; use OCP\IRequestId; use OCP\PreConditionNotMetException; use OCP\Profiler\IProfiler; -use OC\DB\QueryBuilder\QueryBuilder; -use OC\SystemConfig; use Psr\Log\LoggerInterface; class Connection extends \Doctrine\DBAL\Connection { diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index 29df1c1f78d..60f9b65cd5f 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -390,6 +390,7 @@ class MigrationService { */ public function migrate(string $to = 'latest', bool $schemaOnly = false): void { if ($schemaOnly) { + $this->output->debug('Migrating schema only'); $this->migrateSchemaOnly($to); return; } @@ -421,6 +422,7 @@ class MigrationService { $toSchema = null; foreach ($toBeExecuted as $version) { + $this->output->debug('- Reading ' . $version); $instance = $this->createInstance($version); $toSchema = $instance->changeSchema($this->output, function () use ($toSchema): ISchemaWrapper { @@ -429,16 +431,20 @@ class MigrationService { } if ($toSchema instanceof SchemaWrapper) { + $this->output->debug('- Checking target database schema'); $targetSchema = $toSchema->getWrappedSchema(); $this->ensureUniqueNamesConstraints($targetSchema); if ($this->checkOracle) { $beforeSchema = $this->connection->createSchema(); $this->ensureOracleConstraints($beforeSchema, $targetSchema, strlen($this->connection->getPrefix())); } + + $this->output->debug('- Migrate database schema'); $this->connection->migrateToSchema($targetSchema); $toSchema->performDropTableCalls(); } + $this->output->debug('- Mark migrations as executed'); foreach ($toBeExecuted as $version) { $this->markAsExecuted($version); } diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php index 1d960e72dc5..7cf95b04000 100644 --- a/lib/private/DB/Migrator.php +++ b/lib/private/DB/Migrator.php @@ -35,9 +35,9 @@ use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaDiff; use Doctrine\DBAL\Types\StringType; use Doctrine\DBAL\Types\Type; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use function preg_match; -use OCP\EventDispatcher\IEventDispatcher; class Migrator { /** @var Connection */ @@ -52,8 +52,8 @@ class Migrator { private $noEmit = false; public function __construct(Connection $connection, - IConfig $config, - ?IEventDispatcher $dispatcher = null) { + IConfig $config, + ?IEventDispatcher $dispatcher = null) { $this->connection = $connection; $this->config = $config; $this->dispatcher = $dispatcher; diff --git a/lib/private/DB/MissingColumnInformation.php b/lib/private/DB/MissingColumnInformation.php index f651546b4b3..919f8923a26 100644 --- a/lib/private/DB/MissingColumnInformation.php +++ b/lib/private/DB/MissingColumnInformation.php @@ -26,7 +26,7 @@ declare(strict_types=1); namespace OC\DB; class MissingColumnInformation { - private $listOfMissingColumns = []; + private array $listOfMissingColumns = []; public function addHintForMissingColumn(string $tableName, string $columnName): void { $this->listOfMissingColumns[] = [ diff --git a/lib/private/DB/MissingIndexInformation.php b/lib/private/DB/MissingIndexInformation.php index 74498668349..4fc3a52d3a4 100644 --- a/lib/private/DB/MissingIndexInformation.php +++ b/lib/private/DB/MissingIndexInformation.php @@ -27,16 +27,16 @@ declare(strict_types=1); namespace OC\DB; class MissingIndexInformation { - private $listOfMissingIndexes = []; + private array $listOfMissingIndices = []; - public function addHintForMissingSubject(string $tableName, string $indexName) { - $this->listOfMissingIndexes[] = [ + public function addHintForMissingIndex(string $tableName, string $indexName): void { + $this->listOfMissingIndices[] = [ 'tableName' => $tableName, 'indexName' => $indexName ]; } - public function getListOfMissingIndexes(): array { - return $this->listOfMissingIndexes; + public function getListOfMissingIndices(): array { + return $this->listOfMissingIndices; } } diff --git a/lib/private/DB/MissingPrimaryKeyInformation.php b/lib/private/DB/MissingPrimaryKeyInformation.php index f28c8cfb352..42e5584291c 100644 --- a/lib/private/DB/MissingPrimaryKeyInformation.php +++ b/lib/private/DB/MissingPrimaryKeyInformation.php @@ -26,9 +26,9 @@ declare(strict_types=1); namespace OC\DB; class MissingPrimaryKeyInformation { - private $listOfMissingPrimaryKeys = []; + private array $listOfMissingPrimaryKeys = []; - public function addHintForMissingSubject(string $tableName) { + public function addHintForMissingPrimaryKey(string $tableName): void { $this->listOfMissingPrimaryKeys[] = [ 'tableName' => $tableName, ]; diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index 30dc02b0c16..c2818911ccf 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -866,7 +866,7 @@ class QueryBuilder implements IQueryBuilder { public function where(...$predicates) { if ($this->getQueryPart('where') !== null && $this->systemConfig->getValue('debug', false)) { // Only logging a warning, not throwing for now. - $e = new QueryException('Using where() on non-empty WHERE part, please verify it is intentional to not call whereAnd() or whereOr() instead. Otherwise consider creating a new query builder object or call resetQueryPart(\'where\') first.'); + $e = new QueryException('Using where() on non-empty WHERE part, please verify it is intentional to not call andWhere() or orWhere() instead. Otherwise consider creating a new query builder object or call resetQueryPart(\'where\') first.'); $this->logger->warning($e->getMessage(), ['exception' => $e]); } diff --git a/lib/private/Dashboard/Manager.php b/lib/private/Dashboard/Manager.php index afe28872e69..5a7e4f3c6dc 100644 --- a/lib/private/Dashboard/Manager.php +++ b/lib/private/Dashboard/Manager.php @@ -33,8 +33,8 @@ use OCP\Dashboard\IManager; use OCP\Dashboard\IWidget; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; -use Throwable; use Psr\Log\LoggerInterface; +use Throwable; class Manager implements IManager { /** @var array */ diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php index 2dd2abe5408..d1be1f50330 100644 --- a/lib/private/DirectEditing/Manager.php +++ b/lib/private/DirectEditing/Manager.php @@ -25,8 +25,9 @@ */ namespace OC\DirectEditing; -use Doctrine\DBAL\FetchMode; +use \OCP\DirectEditing\IManager; use \OCP\Files\Folder; +use Doctrine\DBAL\FetchMode; use OCP\AppFramework\Http\NotFoundResponse; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\TemplateResponse; @@ -34,7 +35,6 @@ use OCP\Constants; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DirectEditing\ACreateFromTemplate; use OCP\DirectEditing\IEditor; -use \OCP\DirectEditing\IManager; use OCP\DirectEditing\IToken; use OCP\Encryption\IManager as EncryptionManager; use OCP\Files\File; diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php index e58b3656593..a6bc72ef18f 100644 --- a/lib/private/Encryption/EncryptionWrapper.php +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -53,8 +53,8 @@ class EncryptionWrapper { * EncryptionWrapper constructor. */ public function __construct(ArrayCache $arrayCache, - Manager $manager, - LoggerInterface $logger + Manager $manager, + LoggerInterface $logger ) { $this->arrayCache = $arrayCache; $this->manager = $manager; diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php index daab097ce7c..f2b1de23234 100644 --- a/lib/private/Encryption/File.php +++ b/lib/private/Encryption/File.php @@ -27,9 +27,9 @@ */ namespace OC\Encryption; -use OCP\Cache\CappedMemoryCache; use OCA\Files_External\Service\GlobalStoragesService; use OCP\App\IAppManager; +use OCP\Cache\CappedMemoryCache; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Share\IManager; @@ -47,8 +47,8 @@ class File implements \OCP\Encryption\IFile { private ?IAppManager $appManager = null; public function __construct(Util $util, - IRootFolder $rootFolder, - IManager $shareManager) { + IRootFolder $rootFolder, + IManager $shareManager) { $this->util = $util; $this->cache = new CappedMemoryCache(); $this->rootFolder = $rootFolder; diff --git a/lib/private/Encryption/HookManager.php b/lib/private/Encryption/HookManager.php index 5081bcccf94..afcb7ce3763 100644 --- a/lib/private/Encryption/HookManager.php +++ b/lib/private/Encryption/HookManager.php @@ -24,8 +24,8 @@ namespace OC\Encryption; use OC\Files\Filesystem; -use OC\Files\View; use OC\Files\SetupManager; +use OC\Files\View; use Psr\Log\LoggerInterface; class HookManager { diff --git a/lib/private/Encryption/Update.php b/lib/private/Encryption/Update.php index 2e390177baf..1d9ec8510d0 100644 --- a/lib/private/Encryption/Update.php +++ b/lib/private/Encryption/Update.php @@ -62,14 +62,14 @@ class Update { * @param string $uid */ public function __construct( - View $view, - Util $util, - Mount\Manager $mountManager, - Manager $encryptionManager, - File $file, - LoggerInterface $logger, - $uid - ) { + View $view, + Util $util, + Mount\Manager $mountManager, + Manager $encryptionManager, + File $file, + LoggerInterface $logger, + $uid + ) { $this->view = $view; $this->util = $util; $this->mountManager = $mountManager; diff --git a/lib/private/EventDispatcher/EventDispatcher.php b/lib/private/EventDispatcher/EventDispatcher.php index 88c6b2cf32c..14c13d516c0 100644 --- a/lib/private/EventDispatcher/EventDispatcher.php +++ b/lib/private/EventDispatcher/EventDispatcher.php @@ -27,17 +27,17 @@ declare(strict_types=1); */ namespace OC\EventDispatcher; -use OC\Log; -use Psr\Log\LoggerInterface; -use function get_class; use OC\Broadcast\Events\BroadcastEvent; +use OC\Log; use OCP\Broadcast\Events\IBroadcastEvent; use OCP\EventDispatcher\ABroadcastedEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\IContainer; use OCP\IServerContainer; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyDispatcher; +use function get_class; class EventDispatcher implements IEventDispatcher { /** @var SymfonyDispatcher */ @@ -50,8 +50,8 @@ class EventDispatcher implements IEventDispatcher { private $logger; public function __construct(SymfonyDispatcher $dispatcher, - IServerContainer $container, - LoggerInterface $logger) { + IServerContainer $container, + LoggerInterface $logger) { $this->dispatcher = $dispatcher; $this->container = $container; $this->logger = $logger; @@ -64,19 +64,19 @@ class EventDispatcher implements IEventDispatcher { } public function addListener(string $eventName, - callable $listener, - int $priority = 0): void { + callable $listener, + int $priority = 0): void { $this->dispatcher->addListener($eventName, $listener, $priority); } public function removeListener(string $eventName, - callable $listener): void { + callable $listener): void { $this->dispatcher->removeListener($eventName, $listener); } public function addServiceListener(string $eventName, - string $className, - int $priority = 0): void { + string $className, + int $priority = 0): void { $listener = new ServiceEventListener( $this->container, $className, @@ -90,7 +90,7 @@ class EventDispatcher implements IEventDispatcher { * @deprecated */ public function dispatch(string $eventName, - Event $event): void { + Event $event): void { $this->dispatcher->dispatch($event, $eventName); if ($event instanceof ABroadcastedEvent && !$event->isPropagationStopped()) { diff --git a/lib/private/EventDispatcher/ServiceEventListener.php b/lib/private/EventDispatcher/ServiceEventListener.php index 21cdf7f8cc2..a7bbbcd82aa 100644 --- a/lib/private/EventDispatcher/ServiceEventListener.php +++ b/lib/private/EventDispatcher/ServiceEventListener.php @@ -54,8 +54,8 @@ final class ServiceEventListener { private $service; public function __construct(IServerContainer $container, - string $class, - LoggerInterface $logger) { + string $class, + LoggerInterface $logger) { $this->container = $container; $this->class = $class; $this->logger = $logger; diff --git a/lib/private/Federation/CloudFederationShare.php b/lib/private/Federation/CloudFederationShare.php index 0f79ba521ea..4b741b28bee 100644 --- a/lib/private/Federation/CloudFederationShare.php +++ b/lib/private/Federation/CloudFederationShare.php @@ -57,16 +57,16 @@ class CloudFederationShare implements ICloudFederationShare { * @param string $sharedSecret */ public function __construct($shareWith = '', - $name = '', - $description = '', - $providerId = '', - $owner = '', - $ownerDisplayName = '', - $sharedBy = '', - $sharedByDisplayName = '', - $shareType = '', - $resourceType = '', - $sharedSecret = '' + $name = '', + $description = '', + $providerId = '', + $owner = '', + $ownerDisplayName = '', + $sharedBy = '', + $sharedByDisplayName = '', + $shareType = '', + $resourceType = '', + $sharedSecret = '' ) { $this->setShareWith($shareWith); $this->setResourceName($name); diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php index 237fcb42e03..1c632c3062f 100644 --- a/lib/private/Files/AppData/AppData.php +++ b/lib/private/Files/AppData/AppData.php @@ -26,9 +26,9 @@ declare(strict_types=1); */ namespace OC\Files\AppData; -use OCP\Cache\CappedMemoryCache; use OC\Files\SimpleFS\SimpleFolder; use OC\SystemConfig; +use OCP\Cache\CappedMemoryCache; use OCP\Files\Folder; use OCP\Files\IAppData; use OCP\Files\IRootFolder; @@ -53,8 +53,8 @@ class AppData implements IAppData { * @param string $appId */ public function __construct(IRootFolder $rootFolder, - SystemConfig $systemConfig, - string $appId) { + SystemConfig $systemConfig, + string $appId) { $this->rootFolder = $rootFolder; $this->config = $systemConfig; $this->appId = $appId; diff --git a/lib/private/Files/AppData/Factory.php b/lib/private/Files/AppData/Factory.php index 03f8fdedcbd..a16c3df327d 100644 --- a/lib/private/Files/AppData/Factory.php +++ b/lib/private/Files/AppData/Factory.php @@ -39,7 +39,7 @@ class Factory implements IAppDataFactory { private array $folders = []; public function __construct(IRootFolder $rootFolder, - SystemConfig $systemConfig) { + SystemConfig $systemConfig) { $this->rootFolder = $rootFolder; $this->config = $systemConfig; } diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 67d01bb6999..052b3c75ce8 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -47,9 +47,9 @@ use OC\Files\Storage\Wrapper\Encryption; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Cache\CacheEntryInsertedEvent; +use OCP\Files\Cache\CacheEntryRemovedEvent; use OCP\Files\Cache\CacheEntryUpdatedEvent; use OCP\Files\Cache\CacheInsertEvent; -use OCP\Files\Cache\CacheEntryRemovedEvent; use OCP\Files\Cache\CacheUpdateEvent; use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; @@ -59,6 +59,7 @@ use OCP\Files\Search\ISearchComparison; use OCP\Files\Search\ISearchOperator; use OCP\Files\Search\ISearchQuery; use OCP\Files\Storage\IStorage; +use OCP\FilesMetadata\IFilesMetadataManager; use OCP\IDBConnection; use OCP\Util; use Psr\Log\LoggerInterface; @@ -132,7 +133,8 @@ class Cache implements ICache { return new CacheQueryBuilder( $this->connection, \OC::$server->getSystemConfig(), - \OC::$server->get(LoggerInterface::class) + \OC::$server->get(LoggerInterface::class), + \OC::$server->get(IFilesMetadataManager::class), ); } @@ -154,6 +156,7 @@ class Cache implements ICache { public function get($file) { $query = $this->getQueryBuilder(); $query->selectFileCache(); + $metadataQuery = $query->selectMetadata(); if (is_string($file) || $file == '') { // normalize file @@ -175,6 +178,7 @@ class Cache implements ICache { } elseif (!$data) { return $data; } else { + $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? []; return self::cacheEntryFromData($data, $this->mimetypeLoader); } } @@ -239,11 +243,14 @@ class Cache implements ICache { ->whereParent($fileId) ->orderBy('name', 'ASC'); + $metadataQuery = $query->selectMetadata(); + $result = $query->execute(); $files = $result->fetchAll(); $result->closeCursor(); - return array_map(function (array $data) { + return array_map(function (array $data) use ($metadataQuery) { + $data['metadata'] = $metadataQuery?->extractMetadata($data)->asArray() ?? []; return self::cacheEntryFromData($data, $this->mimetypeLoader); }, $files); } @@ -447,7 +454,7 @@ class Cache implements ICache { $params = []; $extensionParams = []; foreach ($data as $name => $value) { - if (array_search($name, $fields) !== false) { + if (in_array($name, $fields)) { if ($name === 'path') { $params['path_hash'] = md5($value); } elseif ($name === 'mimetype') { @@ -467,7 +474,7 @@ class Cache implements ICache { } $params[$name] = $value; } - if (array_search($name, $extensionFields) !== false) { + if (in_array($name, $extensionFields)) { $extensionParams[$name] = $value; } } @@ -599,9 +606,12 @@ class Cache implements ICache { } /** @var ICacheEntry[] $childFolders */ - $childFolders = array_filter($children, function ($child) { - return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER; - }); + $childFolders = []; + foreach ($children as $child) { + if ($child->getMimeType() == FileInfo::MIMETYPE_FOLDER) { + $childFolders[] = $child; + } + } foreach ($childFolders as $folder) { $parentIds[] = $folder->getId(); $queue[] = $folder->getId(); diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index 34d2177b84e..365d28fc8c5 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl> * + * @author Maxence Lange <maxence@artificial-owl.com> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -28,6 +29,8 @@ namespace OC\Files\Cache; use OC\DB\QueryBuilder\QueryBuilder; use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\FilesMetadata\IFilesMetadataManager; +use OCP\FilesMetadata\IMetadataQuery; use OCP\IDBConnection; use Psr\Log\LoggerInterface; @@ -35,9 +38,14 @@ use Psr\Log\LoggerInterface; * Query builder with commonly used helpers for filecache queries */ class CacheQueryBuilder extends QueryBuilder { - private $alias = null; - - public function __construct(IDBConnection $connection, SystemConfig $systemConfig, LoggerInterface $logger) { + private ?string $alias = null; + + public function __construct( + IDBConnection $connection, + SystemConfig $systemConfig, + LoggerInterface $logger, + private IFilesMetadataManager $filesMetadataManager, + ) { parent::__construct($connection, $systemConfig, $logger); } @@ -63,7 +71,7 @@ class CacheQueryBuilder extends QueryBuilder { public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) { $name = $alias ?: 'filecache'; $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime', - 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size') + 'storage_mtime', 'encrypted', 'etag', "$name.permissions", 'checksum', 'unencrypted_size') ->from('filecache', $name); if ($joinExtendedCache) { @@ -126,4 +134,15 @@ class CacheQueryBuilder extends QueryBuilder { return $this; } + + /** + * join metadata to current query builder and returns an helper + * + * @return IMetadataQuery|null NULL if no metadata have never been generated + */ + public function selectMetadata(): ?IMetadataQuery { + $metadataQuery = $this->filesMetadataManager->getMetadataQuery($this, $this->alias, 'fileid'); + $metadataQuery?->retrieveMetadata(); + return $metadataQuery; + } } diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index 15c089a0f11..d8c5e66e129 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Tobias Kaminsky <tobias@kaminsky.me> @@ -37,52 +38,47 @@ use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountPoint; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchQuery; +use OCP\FilesMetadata\IFilesMetadataManager; +use OCP\FilesMetadata\IMetadataQuery; use OCP\IDBConnection; use OCP\IGroupManager; use OCP\IUser; use Psr\Log\LoggerInterface; class QuerySearchHelper { - /** @var IMimeTypeLoader */ - private $mimetypeLoader; - /** @var IDBConnection */ - private $connection; - /** @var SystemConfig */ - private $systemConfig; - private LoggerInterface $logger; - /** @var SearchBuilder */ - private $searchBuilder; - /** @var QueryOptimizer */ - private $queryOptimizer; - private IGroupManager $groupManager; - public function __construct( - IMimeTypeLoader $mimetypeLoader, - IDBConnection $connection, - SystemConfig $systemConfig, - LoggerInterface $logger, - SearchBuilder $searchBuilder, - QueryOptimizer $queryOptimizer, - IGroupManager $groupManager, + private IMimeTypeLoader $mimetypeLoader, + private IDBConnection $connection, + private SystemConfig $systemConfig, + private LoggerInterface $logger, + private SearchBuilder $searchBuilder, + private QueryOptimizer $queryOptimizer, + private IGroupManager $groupManager, + private IFilesMetadataManager $filesMetadataManager, ) { - $this->mimetypeLoader = $mimetypeLoader; - $this->connection = $connection; - $this->systemConfig = $systemConfig; - $this->logger = $logger; - $this->searchBuilder = $searchBuilder; - $this->queryOptimizer = $queryOptimizer; - $this->groupManager = $groupManager; } protected function getQueryBuilder() { return new CacheQueryBuilder( $this->connection, $this->systemConfig, - $this->logger + $this->logger, + $this->filesMetadataManager, ); } - protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void { + /** + * @param CacheQueryBuilder $query + * @param ISearchQuery $searchQuery + * @param array $caches + * @param IMetadataQuery|null $metadataQuery + */ + protected function applySearchConstraints( + CacheQueryBuilder $query, + ISearchQuery $searchQuery, + array $caches, + ?IMetadataQuery $metadataQuery = null + ): void { $storageFilters = array_values(array_map(function (ICache $cache) { return $cache->getQueryFilterForStorage(); }, $caches)); @@ -90,12 +86,12 @@ class QuerySearchHelper { $filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]); $this->queryOptimizer->processOperator($filter); - $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter); + $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter, $metadataQuery); if ($searchExpr) { $query->andWhere($searchExpr); } - $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder()); + $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder(), $metadataQuery); if ($searchQuery->getLimit()) { $query->setMaxResults($searchQuery->getLimit()); @@ -144,6 +140,11 @@ class QuerySearchHelper { )); } + + protected function equipQueryForShares(CacheQueryBuilder $query): void { + $query->join('file', 'share', 's', $query->expr()->eq('file.fileid', 's.file_source')); + } + /** * Perform a file system search in multiple caches * @@ -175,19 +176,31 @@ class QuerySearchHelper { $query = $builder->selectFileCache('file', false); $requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation()); + if (in_array('systemtag', $requestedFields)) { $this->equipQueryForSystemTags($query, $this->requireUser($searchQuery)); } if (in_array('tagname', $requestedFields) || in_array('favorite', $requestedFields)) { $this->equipQueryForDavTags($query, $this->requireUser($searchQuery)); } + if (in_array('owner', $requestedFields) || in_array('share_with', $requestedFields) || in_array('share_type', $requestedFields)) { + $this->equipQueryForShares($query); + } - $this->applySearchConstraints($query, $searchQuery, $caches); + $metadataQuery = $query->selectMetadata(); + + $this->applySearchConstraints($query, $searchQuery, $caches, $metadataQuery); $result = $query->execute(); $files = $result->fetchAll(); - $rawEntries = array_map(function (array $data) { + $rawEntries = array_map(function (array $data) use ($metadataQuery) { + // migrate to null safe ... + if ($metadataQuery === null) { + $data['metadata'] = []; + } else { + $data['metadata'] = $metadataQuery->extractMetadata($data)->asArray(); + } return Cache::cacheEntryFromData($data, $this->mimetypeLoader); }, $files); diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php index 074e88e7639..2711fc8ad19 100644 --- a/lib/private/Files/Cache/Scanner.php +++ b/lib/private/Files/Cache/Scanner.php @@ -37,14 +37,14 @@ namespace OC\Files\Cache; use Doctrine\DBAL\Exception; use OC\Files\Storage\Wrapper\Encryption; +use OC\Files\Storage\Wrapper\Jail; +use OC\Hooks\BasicEmitter; use OCP\Files\Cache\IScanner; use OCP\Files\ForbiddenException; use OCP\Files\NotFoundException; use OCP\Files\Storage\IReliableEtagStorage; use OCP\IDBConnection; use OCP\Lock\ILockingProvider; -use OC\Files\Storage\Wrapper\Jail; -use OC\Hooks\BasicEmitter; use Psr\Log\LoggerInterface; /** diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php index b9a70bbd39b..38161ec9cc6 100644 --- a/lib/private/Files/Cache/SearchBuilder.php +++ b/lib/private/Files/Cache/SearchBuilder.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Tobias Kaminsky <tobias@kaminsky.me> @@ -32,6 +33,7 @@ use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchComparison; use OCP\Files\Search\ISearchOperator; use OCP\Files\Search\ISearchOrder; +use OCP\FilesMetadata\IMetadataQuery; /** * Tools for transforming search queries into database queries @@ -45,6 +47,7 @@ class SearchBuilder { ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'gte', ISearchComparison::COMPARE_LESS_THAN => 'lt', ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'lte', + ISearchComparison::COMPARE_DEFINED => 'isNotNull', ]; protected static $searchOperatorNegativeMap = [ @@ -55,6 +58,7 @@ class SearchBuilder { ISearchComparison::COMPARE_GREATER_THAN_EQUAL => 'lt', ISearchComparison::COMPARE_LESS_THAN => 'gte', ISearchComparison::COMPARE_LESS_THAN_EQUAL => 'gt', + ISearchComparison::COMPARE_DEFINED => 'isNull', ]; public const TAG_FAVORITE = '_$!<Favorite>!$_'; @@ -76,7 +80,7 @@ class SearchBuilder { return array_reduce($operator->getArguments(), function (array $fields, ISearchOperator $operator) { return array_unique(array_merge($fields, $this->extractRequestedFields($operator))); }, []); - } elseif ($operator instanceof ISearchComparison) { + } elseif ($operator instanceof ISearchComparison && !$operator->getExtra()) { return [$operator->getField()]; } return []; @@ -86,13 +90,21 @@ class SearchBuilder { * @param IQueryBuilder $builder * @param ISearchOperator[] $operators */ - public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) { - return array_filter(array_map(function ($operator) use ($builder) { - return $this->searchOperatorToDBExpr($builder, $operator); + public function searchOperatorArrayToDBExprArray( + IQueryBuilder $builder, + array $operators, + ?IMetadataQuery $metadataQuery = null + ) { + return array_filter(array_map(function ($operator) use ($builder, $metadataQuery) { + return $this->searchOperatorToDBExpr($builder, $operator, $metadataQuery); }, $operators)); } - public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) { + public function searchOperatorToDBExpr( + IQueryBuilder $builder, + ISearchOperator $operator, + ?IMetadataQuery $metadataQuery = null + ) { $expr = $builder->expr(); if ($operator instanceof ISearchBinaryOperator) { @@ -104,29 +116,37 @@ class SearchBuilder { case ISearchBinaryOperator::OPERATOR_NOT: $negativeOperator = $operator->getArguments()[0]; if ($negativeOperator instanceof ISearchComparison) { - return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap); + return $this->searchComparisonToDBExpr($builder, $negativeOperator, self::$searchOperatorNegativeMap, $metadataQuery); } else { throw new \InvalidArgumentException('Binary operators inside "not" is not supported'); } // no break case ISearchBinaryOperator::OPERATOR_AND: - return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments())); + return call_user_func_array([$expr, 'andX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery)); case ISearchBinaryOperator::OPERATOR_OR: - return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments())); + return call_user_func_array([$expr, 'orX'], $this->searchOperatorArrayToDBExprArray($builder, $operator->getArguments(), $metadataQuery)); default: throw new \InvalidArgumentException('Invalid operator type: ' . $operator->getType()); } } elseif ($operator instanceof ISearchComparison) { - return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap); + return $this->searchComparisonToDBExpr($builder, $operator, self::$searchOperatorMap, $metadataQuery); } else { throw new \InvalidArgumentException('Invalid operator type: ' . get_class($operator)); } } - private function searchComparisonToDBExpr(IQueryBuilder $builder, ISearchComparison $comparison, array $operatorMap) { - $this->validateComparison($comparison); + private function searchComparisonToDBExpr( + IQueryBuilder $builder, + ISearchComparison $comparison, + array $operatorMap, + ?IMetadataQuery $metadataQuery = null + ) { + if ($comparison->getExtra()) { + [$field, $value, $type] = $this->getExtraOperatorField($comparison, $metadataQuery); + } else { + [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison); + } - [$field, $value, $type] = $this->getOperatorFieldAndValue($comparison); if (isset($operatorMap[$type])) { $queryOperator = $operatorMap[$type]; return $builder->expr()->$queryOperator($field, $this->getParameterForValue($builder, $value)); @@ -136,9 +156,12 @@ class SearchBuilder { } private function getOperatorFieldAndValue(ISearchComparison $operator) { + $this->validateComparison($operator); + $field = $operator->getField(); $value = $operator->getValue(); $type = $operator->getType(); + if ($field === 'mimetype') { $value = (string)$value; if ($operator->getType() === ISearchComparison::COMPARE_EQUAL) { @@ -171,6 +194,8 @@ class SearchBuilder { } elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL && $operator->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true)) { $field = 'path_hash'; $value = md5((string)$value); + } elseif ($field === 'owner') { + $field = 'uid_owner'; } return [$field, $value, $type]; } @@ -187,6 +212,9 @@ class SearchBuilder { 'favorite' => 'boolean', 'fileid' => 'integer', 'storage' => 'integer', + 'share_with' => 'string', + 'share_type' => 'integer', + 'owner' => 'string', ]; $comparisons = [ 'mimetype' => ['eq', 'like'], @@ -199,6 +227,9 @@ class SearchBuilder { 'favorite' => ['eq'], 'fileid' => ['eq'], 'storage' => ['eq'], + 'share_with' => ['eq'], + 'share_type' => ['eq'], + 'owner' => ['eq'], ]; if (!isset($types[$operator->getField()])) { @@ -213,6 +244,24 @@ class SearchBuilder { } } + + private function getExtraOperatorField(ISearchComparison $operator, IMetadataQuery $metadataQuery): array { + $field = $operator->getField(); + $value = $operator->getValue(); + $type = $operator->getType(); + + switch($operator->getExtra()) { + case IMetadataQuery::EXTRA: + $metadataQuery->joinIndex($field); // join index table if not joined yet + $field = $metadataQuery->getMetadataValueField($field); + break; + default: + throw new \InvalidArgumentException('Invalid extra type: ' . $operator->getExtra()); + } + + return [$field, $value, $type]; + } + private function getParameterForValue(IQueryBuilder $builder, $value) { if ($value instanceof \DateTime) { $value = $value->getTimestamp(); @@ -228,24 +277,32 @@ class SearchBuilder { /** * @param IQueryBuilder $query * @param ISearchOrder[] $orders + * @param IMetadataQuery|null $metadataQuery */ - public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders) { + public function addSearchOrdersToQuery(IQueryBuilder $query, array $orders, ?IMetadataQuery $metadataQuery = null): void { foreach ($orders as $order) { $field = $order->getField(); - if ($field === 'fileid') { - $field = 'file.fileid'; - } + switch ($order->getExtra()) { + case IMetadataQuery::EXTRA: + $metadataQuery->joinIndex($field); // join index table if not joined yet + $field = $metadataQuery->getMetadataValueField($order->getField()); + break; - // Mysql really likes to pick an index for sorting if it can't fully satisfy the where - // filter with an index, since search queries pretty much never are fully filtered by index - // mysql often picks an index for sorting instead of the much more useful index for filtering. - // - // By changing the order by to an expression, mysql isn't smart enough to see that it could still - // use the index, so it instead picks an index for the filtering - if ($field === 'mtime') { - $field = $query->func()->add($field, $query->createNamedParameter(0)); - } + default: + if ($field === 'fileid') { + $field = 'file.fileid'; + } + // Mysql really likes to pick an index for sorting if it can't fully satisfy the where + // filter with an index, since search queries pretty much never are fully filtered by index + // mysql often picks an index for sorting instead of the much more useful index for filtering. + // + // By changing the order by to an expression, mysql isn't smart enough to see that it could still + // use the index, so it instead picks an index for the filtering + if ($field === 'mtime') { + $field = $query->func()->add($field, $query->createNamedParameter(0)); + } + } $query->addOrderBy($field, $order->getDirection()); } } diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php index acc76f263dc..61ea5b2f848 100644 --- a/lib/private/Files/Cache/Watcher.php +++ b/lib/private/Files/Cache/Watcher.php @@ -129,7 +129,7 @@ class Watcher implements IWatcher { * @return bool */ public function needsUpdate($path, $cachedData) { - if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and array_search($path, $this->checkedPaths) === false)) { + if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and !in_array($path, $this->checkedPaths))) { $this->checkedPaths[] = $path; return $this->storage->hasUpdated($path, $cachedData['storage_mtime']); } diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index d8cf3eb61d7..73c9a017019 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -52,8 +52,6 @@ class CacheJail extends CacheWrapper { public function __construct($cache, $root) { parent::__construct($cache); $this->root = $root; - $this->connection = \OC::$server->getDatabaseConnection(); - $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); if ($cache instanceof CacheJail) { $this->unjailedRoot = $cache->getSourcePath($root); diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php index 43c9fae63ec..7c97135a565 100644 --- a/lib/private/Files/Config/CachedMountInfo.php +++ b/lib/private/Files/Config/CachedMountInfo.php @@ -35,6 +35,7 @@ class CachedMountInfo implements ICachedMountInfo { protected ?int $mountId; protected string $rootInternalPath; protected string $mountProvider; + protected string $key; /** * CachedMountInfo constructor. @@ -65,6 +66,7 @@ class CachedMountInfo implements ICachedMountInfo { throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters"); } $this->mountProvider = $mountProvider; + $this->key = $rootId . '::' . $mountPoint; } /** @@ -132,4 +134,8 @@ class CachedMountInfo implements ICachedMountInfo { public function getMountProvider(): string { return $this->mountProvider; } + + public function getKey(): string { + return $this->key; + } } diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php index 78055a2cdb8..7e4acb2e129 100644 --- a/lib/private/Files/Config/LazyStorageMountInfo.php +++ b/lib/private/Files/Config/LazyStorageMountInfo.php @@ -39,6 +39,7 @@ class LazyStorageMountInfo extends CachedMountInfo { $this->rootId = 0; $this->storageId = 0; $this->mountPoint = ''; + $this->key = ''; } /** @@ -87,4 +88,11 @@ class LazyStorageMountInfo extends CachedMountInfo { public function getMountProvider(): string { return $this->mount->getMountProvider(); } + + public function getKey(): string { + if (!$this->key) { + $this->key = $this->getRootId() . '::' . $this->getMountPoint(); + } + return $this->key; + } } diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index ae6481e45bb..d251199fd43 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -238,6 +238,11 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { $mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) { return array_merge($mounts, $providerMounts); }, []); + + if (count($mounts) === 0) { + throw new \Exception("No root mounts provided by any provider"); + } + return $mounts; } diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index 7502d65d044..2fb7a7d83f4 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -29,13 +29,11 @@ namespace OC\Files\Config; use OCP\Cache\CappedMemoryCache; -use OCA\Files_Sharing\SharedMount; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Diagnostics\IEventLogger; use OCP\Files\Config\ICachedMountFileInfo; use OCP\Files\Config\ICachedMountInfo; use OCP\Files\Config\IUserMountCache; -use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\IDBConnection; use OCP\IUser; @@ -78,41 +76,27 @@ class UserMountCache implements IUserMountCache { public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) { $this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user'); - // filter out non-proper storages coming from unit tests - $mounts = array_filter($mounts, function (IMountPoint $mount) { - return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache()); - }); - /** @var ICachedMountInfo[] $newMounts */ - $newMounts = array_map(function (IMountPoint $mount) use ($user) { + /** @var array<string, ICachedMountInfo> $newMounts */ + $newMounts = []; + foreach ($mounts as $mount) { // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet) - if ($mount->getStorageRootId() === -1) { - return null; - } else { - return new LazyStorageMountInfo($user, $mount); + if ($mount->getStorageRootId() !== -1) { + $mountInfo = new LazyStorageMountInfo($user, $mount); + $newMounts[$mountInfo->getKey()] = $mountInfo; } - }, $mounts); - $newMounts = array_values(array_filter($newMounts)); - $newMountKeys = array_map(function (ICachedMountInfo $mount) { - return $mount->getRootId() . '::' . $mount->getMountPoint(); - }, $newMounts); - $newMounts = array_combine($newMountKeys, $newMounts); + } $cachedMounts = $this->getMountsForUser($user); if (is_array($mountProviderClasses)) { $cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) { // for existing mounts that didn't have a mount provider set // we still want the ones that map to new mounts - $mountKey = $mountInfo->getRootId() . '::' . $mountInfo->getMountPoint(); - if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountKey])) { + if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) { return true; } return in_array($mountInfo->getMountProvider(), $mountProviderClasses); }); } - $cachedRootKeys = array_map(function (ICachedMountInfo $mount) { - return $mount->getRootId() . '::' . $mount->getMountPoint(); - }, $cachedMounts); - $cachedMounts = array_combine($cachedRootKeys, $cachedMounts); $addedMounts = []; $removedMounts = []; @@ -131,46 +115,44 @@ class UserMountCache implements IUserMountCache { $changedMounts = $this->findChangedMounts($newMounts, $cachedMounts); - $this->connection->beginTransaction(); - try { - foreach ($addedMounts as $mount) { - $this->addToCache($mount); - /** @psalm-suppress InvalidArgument */ - $this->mountsForUsers[$user->getUID()][] = $mount; - } - foreach ($removedMounts as $mount) { - $this->removeFromCache($mount); - $index = array_search($mount, $this->mountsForUsers[$user->getUID()]); - unset($this->mountsForUsers[$user->getUID()][$index]); - } - foreach ($changedMounts as $mount) { - $this->updateCachedMount($mount); + if ($addedMounts || $removedMounts || $changedMounts) { + $this->connection->beginTransaction(); + $userUID = $user->getUID(); + try { + foreach ($addedMounts as $mount) { + $this->addToCache($mount); + /** @psalm-suppress InvalidArgument */ + $this->mountsForUsers[$userUID][$mount->getKey()] = $mount; + } + foreach ($removedMounts as $mount) { + $this->removeFromCache($mount); + unset($this->mountsForUsers[$userUID][$mount->getKey()]); + } + foreach ($changedMounts as $mount) { + $this->updateCachedMount($mount); + /** @psalm-suppress InvalidArgument */ + $this->mountsForUsers[$userUID][$mount->getKey()] = $mount; + } + $this->connection->commit(); + } catch (\Throwable $e) { + $this->connection->rollBack(); + throw $e; } - $this->connection->commit(); - } catch (\Throwable $e) { - $this->connection->rollBack(); - throw $e; } $this->eventLogger->end('fs:setup:user:register'); } /** - * @param ICachedMountInfo[] $newMounts - * @param ICachedMountInfo[] $cachedMounts + * @param array<string, ICachedMountInfo> $newMounts + * @param array<string, ICachedMountInfo> $cachedMounts * @return ICachedMountInfo[] */ private function findChangedMounts(array $newMounts, array $cachedMounts) { - $new = []; - foreach ($newMounts as $mount) { - $new[$mount->getRootId() . '::' . $mount->getMountPoint()] = $mount; - } $changed = []; - foreach ($cachedMounts as $cachedMount) { - $key = $cachedMount->getRootId() . '::' . $cachedMount->getMountPoint(); - if (isset($new[$key])) { - $newMount = $new[$key]; + foreach ($cachedMounts as $key => $cachedMount) { + if (isset($newMounts[$key])) { + $newMount = $newMounts[$key]; if ( - $newMount->getMountPoint() !== $cachedMount->getMountPoint() || $newMount->getStorageId() !== $cachedMount->getStorageId() || $newMount->getMountId() !== $cachedMount->getMountId() || $newMount->getMountProvider() !== $cachedMount->getMountProvider() @@ -247,20 +229,28 @@ class UserMountCache implements IUserMountCache { * @return ICachedMountInfo[] */ public function getMountsForUser(IUser $user) { - if (!isset($this->mountsForUsers[$user->getUID()])) { + $userUID = $user->getUID(); + if (!isset($this->mountsForUsers[$userUID])) { $builder = $this->connection->getQueryBuilder(); $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class') ->from('mounts', 'm') ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) - ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); + ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($userUID))); $result = $query->execute(); $rows = $result->fetchAll(); $result->closeCursor(); - $this->mountsForUsers[$user->getUID()] = array_filter(array_map([$this, 'dbRowToMountInfo'], $rows)); + $this->mountsForUsers[$userUID] = []; + /** @var array<string, ICachedMountInfo> $mounts */ + foreach ($rows as $row) { + $mount = $this->dbRowToMountInfo($row); + if ($mount !== null) { + $this->mountsForUsers[$userUID][$mount->getKey()] = $mount; + } + } } - return $this->mountsForUsers[$user->getUID()]; + return $this->mountsForUsers[$userUID]; } /** diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 7800074460b..5ba2f27b78b 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -6,6 +6,7 @@ * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Piotr M <mrow4a@yahoo.com> * @author Robin Appelman <robin@icewind.nl> @@ -32,9 +33,10 @@ */ namespace OC\Files; -use OCA\Files_Sharing\ISharedStorage; +use OC\Files\Mount\HomeMountPoint; +use OCA\Files_Sharing\External\Mount; +use OCA\Files_Sharing\ISharedMountPoint; use OCP\Files\Cache\ICacheEntry; -use OCP\Files\IHomeStorage; use OCP\Files\Mount\IMountPoint; use OCP\IUser; @@ -121,21 +123,14 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { */ #[\ReturnTypeWillChange] public function offsetGet($offset) { - if ($offset === 'type') { - return $this->getType(); - } elseif ($offset === 'etag') { - return $this->getEtag(); - } elseif ($offset === 'size') { - return $this->getSize(); - } elseif ($offset === 'mtime') { - return $this->getMTime(); - } elseif ($offset === 'permissions') { - return $this->getPermissions(); - } elseif (isset($this->data[$offset])) { - return $this->data[$offset]; - } else { - return null; - } + return match ($offset) { + 'type' => $this->getType(), + 'etag' => $this->getEtag(), + 'size' => $this->getSize(), + 'mtime' => $this->getMTime(), + 'permissions' => $this->getPermissions(), + default => $this->data[$offset] ?? null, + }; } /** @@ -311,13 +306,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { * @return bool */ public function isShared() { - $storage = $this->getStorage(); - return $storage->instanceOfStorage(ISharedStorage::class); + return $this->mount instanceof ISharedMountPoint; } public function isMounted() { - $storage = $this->getStorage(); - return !($storage->instanceOfStorage(IHomeStorage::class) || $storage->instanceOfStorage(ISharedStorage::class)); + $isHome = $this->mount instanceof HomeMountPoint; + return !$isHome && !$this->isShared(); } /** @@ -416,4 +410,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { public function getParentId(): int { return $this->data['parent'] ?? -1; } + + /** + * @inheritDoc + * @return array<string, int|string|bool|float|string[]|int[]> + */ + public function getMetadata(): array { + return $this->data['metadata'] ?? []; + } } diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 5f7c0c403db..9f0d89052be 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -37,9 +37,9 @@ */ namespace OC\Files; -use OCP\Cache\CappedMemoryCache; use OC\Files\Mount\MountPoint; use OC\User\NoUserException; +use OCP\Cache\CappedMemoryCache; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\Mount\IMountManager; diff --git a/lib/private/Files/Mount/HomeMountPoint.php b/lib/private/Files/Mount/HomeMountPoint.php new file mode 100644 index 00000000000..0bec12af5c2 --- /dev/null +++ b/lib/private/Files/Mount/HomeMountPoint.php @@ -0,0 +1,49 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Mount; + +use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; + +class HomeMountPoint extends MountPoint { + private IUser $user; + + public function __construct( + IUser $user, + $storage, + string $mountpoint, + array $arguments = null, + IStorageFactory $loader = null, + array $mountOptions = null, + int $mountId = null, + string $mountProvider = null + ) { + parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, $mountProvider); + $this->user = $user; + } + + public function getUser(): IUser { + return $this->user; + } +} diff --git a/lib/private/Files/Mount/LocalHomeMountProvider.php b/lib/private/Files/Mount/LocalHomeMountProvider.php index 25a67fc1574..964b607d152 100644 --- a/lib/private/Files/Mount/LocalHomeMountProvider.php +++ b/lib/private/Files/Mount/LocalHomeMountProvider.php @@ -38,6 +38,6 @@ class LocalHomeMountProvider implements IHomeMountProvider { */ public function getHomeMountForUser(IUser $user, IStorageFactory $loader) { $arguments = ['user' => $user]; - return new MountPoint('\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class); + return new HomeMountPoint($user, '\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class); } } diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index e623211cc7a..2b2de1fbff1 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -30,10 +30,10 @@ declare(strict_types=1); namespace OC\Files\Mount; -use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OC\Files\SetupManager; use OC\Files\SetupManagerFactory; +use OCP\Cache\CappedMemoryCache; use OCP\Files\Config\ICachedMountInfo; use OCP\Files\Mount\IMountManager; use OCP\Files\Mount\IMountPoint; @@ -101,6 +101,15 @@ class Manager implements IMountManager { return $this->pathCache[$path]; } + + + if (count($this->mounts) === 0) { + $this->setupManager->setupRoot(); + if (count($this->mounts) === 0) { + throw new \Exception("No mounts even after explicitly setting up the root mounts"); + } + } + $current = $path; while (true) { $mountPoint = $current . '/'; @@ -117,7 +126,7 @@ class Manager implements IMountManager { } } - throw new NotFoundException("No mount for path " . $path . " existing mounts: " . implode(",", array_keys($this->mounts))); + throw new NotFoundException("No mount for path " . $path . " existing mounts (" . count($this->mounts) ."): " . implode(",", array_keys($this->mounts))); } /** diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php index 889a39fbd9e..3593a95c311 100644 --- a/lib/private/Files/Mount/ObjectHomeMountProvider.php +++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php @@ -65,7 +65,7 @@ class ObjectHomeMountProvider implements IHomeMountProvider { return null; } - return new MountPoint('\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class); + return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class); } /** diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index ccd10da9d0c..c7462572fed 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -177,7 +177,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @throws \OCP\Files\NotPermittedException */ public function newFile($path, $content = null) { - if (empty($path)) { + if ($path === '') { throw new NotPermittedException('Could not create as provided path is empty'); } if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php index a8e76d95c22..f61eedee66e 100644 --- a/lib/private/Files/Node/HookConnector.php +++ b/lib/private/Files/Node/HookConnector.php @@ -133,7 +133,7 @@ class HookConnector { $this->root->emit('\OC\Files', 'preDelete', [$node]); $this->dispatcher->dispatch('\OCP\Files::preDelete', new GenericEvent($node)); - $event = new BeforeNodeDeletedEvent($node); + $event = new BeforeNodeDeletedEvent($node, $arguments['run']); $this->dispatcher->dispatchTyped($event); } @@ -171,7 +171,7 @@ class HookConnector { $this->root->emit('\OC\Files', 'preRename', [$source, $target]); $this->dispatcher->dispatch('\OCP\Files::preRename', new GenericEvent([$source, $target])); - $event = new BeforeNodeRenamedEvent($source, $target); + $event = new BeforeNodeRenamedEvent($source, $target, $arguments['run']); $this->dispatcher->dispatchTyped($event); } diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index f13cdc0c4f9..e30cfea693e 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -5,6 +5,7 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl> * + * @author Maxence Lange <maxence@artificial-owl.com> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -28,8 +29,8 @@ namespace OC\Files\Node; use OC\Files\Filesystem; use OC\Files\Utils\PathHelper; -use OCP\Files\Folder; use OCP\Constants; +use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountPoint; use OCP\Files\NotPermittedException; @@ -574,4 +575,12 @@ class LazyFolder implements Folder { } return $this->__call(__FUNCTION__, func_get_args()); } + + /** + * @inheritDoc + * @return array<string, int|string|bool|float|string[]|int[]> + */ + public function getMetadata(): array { + return $this->data['metadata'] ?? $this->__call(__FUNCTION__, func_get_args()); + } } diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php index 503b0af8921..917ab80f366 100644 --- a/lib/private/Files/Node/LazyUserFolder.php +++ b/lib/private/Files/Node/LazyUserFolder.php @@ -23,13 +23,13 @@ declare(strict_types=1); namespace OC\Files\Node; -use OCP\Files\FileInfo; use OCP\Constants; +use OCP\Files\File; +use OCP\Files\FileInfo; +use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountManager; use OCP\Files\NotFoundException; -use OCP\Files\Folder; -use OCP\Files\File; use OCP\IUser; use Psr\Log\LoggerInterface; diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 385d45f1e3e..acd91c56d3f 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -7,6 +7,7 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> * @author Julius Härtl <jus@bitgrid.net> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -43,7 +44,7 @@ use OCP\Files\NotPermittedException; use OCP\Lock\LockedException; use OCP\PreConditionNotMetException; -// FIXME: this class really should be abstract +// FIXME: this class really should be abstract (+1) class Node implements INode { /** * @var \OC\Files\View $view @@ -131,7 +132,14 @@ class Node implements INode { if (method_exists($this->root, 'emit')) { $this->root->emit('\OC\Files', $hook, $args); } - $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args)); + + if (in_array($hook, ['preWrite', 'postWrite', 'preCreate', 'postCreate', 'preTouch', 'postTouch', 'preDelete', 'postDelete'], true)) { + $event = new GenericEvent($args[0]); + } else { + $event = new GenericEvent($args); + } + + $dispatcher->dispatch('\OCP\Files::' . $hook, $event); } } @@ -490,4 +498,12 @@ class Node implements INode { public function getParentId(): int { return $this->fileInfo->getParentId(); } + + /** + * @inheritDoc + * @return array<string, int|string|bool|float|string[]|int[]> + */ + public function getMetadata(): array { + return $this->fileInfo->getMetadata(); + } } diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 1195b644083..ee344f9be8b 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -32,7 +32,6 @@ namespace OC\Files\Node; -use OCP\Cache\CappedMemoryCache; use OC\Files\FileInfo; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; @@ -40,6 +39,7 @@ use OC\Files\Utils\PathHelper; use OC\Files\View; use OC\Hooks\PublicEmitter; use OC\User\NoUserException; +use OCP\Cache\CappedMemoryCache; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Cache\ICacheEntry; use OCP\Files\Config\IUserMountCache; diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 4dceee9a58b..eb8aaffe1e0 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -68,6 +68,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil private $logger; + private bool $handleCopiesAsOwned; + /** @var bool */ protected $validateWrites = true; @@ -88,6 +90,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil if (isset($params['validateWrites'])) { $this->validateWrites = (bool)$params['validateWrites']; } + $this->handleCopiesAsOwned = (bool)($params['handleCopiesAsOwned'] ?? false); $this->logger = \OC::$server->getLogger(); } @@ -651,6 +654,10 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil try { $this->objectStore->copyObject($sourceUrn, $targetUrn); + if ($this->handleCopiesAsOwned) { + // Copied the file thus we gain all permissions as we are the owner now ! warning while this aligns with local storage it should not be used and instead fix local storage ! + $cache->update($targetId, ['permissions' => \OCP\Constants::PERMISSION_ALL]); + } } catch (\Exception $e) { $cache->remove($to); diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index e9c52f11936..217e1a1a2ff 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -191,6 +191,11 @@ trait S3ObjectTrait { } public function copyObject($from, $to, array $options = []) { + $sourceMetadata = $this->getConnection()->headObject([ + 'Bucket' => $this->getBucket(), + 'Key' => $from, + ] + $this->getSSECParameters()); + $copy = new MultipartCopy($this->getConnection(), [ "source_bucket" => $this->getBucket(), "source_key" => $from @@ -198,7 +203,8 @@ trait S3ObjectTrait { "bucket" => $this->getBucket(), "key" => $to, "acl" => "private", - "params" => $this->getSSECParameters() + $this->getSSECParameters(true) + "params" => $this->getSSECParameters() + $this->getSSECParameters(true), + "source_metadata" => $sourceMetadata ], $options)); $copy->copy(); } diff --git a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php index 0caa9b12a02..664402f1238 100644 --- a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php +++ b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php @@ -4,6 +4,9 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl> * + * @author Maxence Lange <maxence@artificial-owl.com> + * @author Robin Appelman <robin@icewind.nl> + * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify @@ -48,7 +51,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep { } public function processOperator(ISearchOperator &$operator) { - if (!$this->useHashEq && $operator instanceof ISearchComparison && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) { + if (!$this->useHashEq && $operator instanceof ISearchComparison && !$operator->getExtra() && $operator->getField() === 'path' && $operator->getType() === ISearchComparison::COMPARE_EQUAL) { $operator->setQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, false); } @@ -69,7 +72,7 @@ class PathPrefixOptimizer extends QueryOptimizerStep { private function operatorPairIsPathPrefix(ISearchOperator $like, ISearchOperator $equal): bool { return ( $like instanceof ISearchComparison && $equal instanceof ISearchComparison && - $like->getField() === 'path' && $equal->getField() === 'path' && + !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path' && $like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL && $like->getValue() === SearchComparison::escapeLikeParameter($equal->getValue()) . '/%' ); diff --git a/lib/private/Files/Search/SearchComparison.php b/lib/private/Files/Search/SearchComparison.php index 122a1f730b4..d94b3e9dfab 100644 --- a/lib/private/Files/Search/SearchComparison.php +++ b/lib/private/Files/Search/SearchComparison.php @@ -1,7 +1,10 @@ <?php + +declare(strict_types=1); /** * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> * + * @author Maxence Lange <maxence@artificial-owl.com> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -25,48 +28,45 @@ namespace OC\Files\Search; use OCP\Files\Search\ISearchComparison; class SearchComparison implements ISearchComparison { - /** @var string */ - private $type; - /** @var string */ - private $field; - /** @var string|integer|\DateTime */ - private $value; - private $hints = []; + private array $hints = []; - /** - * SearchComparison constructor. - * - * @param string $type - * @param string $field - * @param \DateTime|int|string $value - */ - public function __construct($type, $field, $value) { - $this->type = $type; - $this->field = $field; - $this->value = $value; + public function __construct( + private string $type, + private string $field, + private \DateTime|int|string|bool $value, + private string $extra = '' + ) { } /** * @return string */ - public function getType() { + public function getType(): string { return $this->type; } /** * @return string */ - public function getField() { + public function getField(): string { return $this->field; } /** - * @return \DateTime|int|string + * @return \DateTime|int|string|bool */ - public function getValue() { + public function getValue(): string|int|bool|\DateTime { return $this->value; } + /** + * @return string + * @since 28.0.0 + */ + public function getExtra(): string { + return $this->extra; + } + public function getQueryHint(string $name, $default) { return $this->hints[$name] ?? $default; } diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php index 1395a87ac72..de514262bf5 100644 --- a/lib/private/Files/Search/SearchOrder.php +++ b/lib/private/Files/Search/SearchOrder.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> * + * @author Maxence Lange <maxence@artificial-owl.com> * @author Robin Appelman <robin@icewind.nl> * * @license GNU AGPL version 3 or any later version @@ -26,34 +27,33 @@ use OCP\Files\FileInfo; use OCP\Files\Search\ISearchOrder; class SearchOrder implements ISearchOrder { - /** @var string */ - private $direction; - /** @var string */ - private $field; + public function __construct( + private string $direction, + private string $field, + private string $extra = '' + ) { + } /** - * SearchOrder constructor. - * - * @param string $direction - * @param string $field + * @return string */ - public function __construct($direction, $field) { - $this->direction = $direction; - $this->field = $field; + public function getDirection(): string { + return $this->direction; } /** * @return string */ - public function getDirection() { - return $this->direction; + public function getField(): string { + return $this->field; } /** * @return string + * @since 28.0.0 */ - public function getField() { - return $this->field; + public function getExtra(): string { + return $this->extra; } public function sortFileInfo(FileInfo $a, FileInfo $b): int { diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index cae0fd2f232..511e80bd7d9 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -24,8 +24,8 @@ declare(strict_types=1); namespace OC\Files; use OC\Files\Config\MountProviderCollection; +use OC\Files\Mount\HomeMountPoint; use OC\Files\Mount\MountPoint; -use OC\Files\ObjectStore\HomeObjectStoreStorage; use OC\Files\Storage\Common; use OC\Files\Storage\Home; use OC\Files\Storage\Storage; @@ -39,7 +39,10 @@ use OC\Share20\ShareDisableChecker; use OC_App; use OC_Hook; use OC_Util; -use OCA\Files_Sharing\ISharedStorage; +use OCA\Files_External\Config\ConfigAdapter; +use OCA\Files_Sharing\External\Mount; +use OCA\Files_Sharing\ISharedMountPoint; +use OCA\Files_Sharing\SharedMount; use OCP\Constants; use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; @@ -117,7 +120,7 @@ class SetupManager { $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) { - if ($storage->instanceOfStorage(Common::class)) { + if ($mount->getOptions() && $storage->instanceOfStorage(Common::class)) { $storage->setMountOptions($mount->getOptions()); } return $storage; @@ -130,7 +133,7 @@ class SetupManager { 'sharing_mask', function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) { $sharingEnabledForMount = $mount->getOption('enable_sharing', true); - $isShared = $storage->instanceOfStorage(ISharedStorage::class); + $isShared = $mount instanceof ISharedMountPoint; if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) { return new PermissionsMask([ 'storage' => $storage, @@ -142,35 +145,30 @@ class SetupManager { ); // install storage availability wrapper, before most other wrappers - Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) { - if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { + Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) { + $externalMount = $mount instanceof ConfigAdapter || $mount instanceof Mount; + if ($externalMount && !$storage->isLocal()) { return new Availability(['storage' => $storage]); } return $storage; }); Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) { - if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { + if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) { return new Encoding(['storage' => $storage]); } return $storage; }); $quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false); - Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) use ($quotaIncludeExternal) { + Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) { // set up quota for home storages, even for other users // which can happen when using sharing - - /** - * @var Storage $storage - */ - if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) { - if (is_object($storage->getUser())) { - $user = $storage->getUser(); - return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) { - return OC_Util::getUserQuota($user); - }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]); - } + if ($mount instanceof HomeMountPoint) { + $user = $mount->getUser(); + return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) { + return OC_Util::getUserQuota($user); + }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]); } return $storage; @@ -337,12 +335,13 @@ class SetupManager { if ($this->rootSetup) { return; } + + $this->setupBuiltinWrappers(); + $this->rootSetup = true; $this->eventLogger->start('fs:setup:root', 'Setup root filesystem'); - $this->setupBuiltinWrappers(); - $rootMounts = $this->mountProviderCollection->getRootMounts(); foreach ($rootMounts as $rootMountProvider) { $this->mountManager->addMount($rootMountProvider); diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php index 4d24aa138c1..2c1f23f8e44 100644 --- a/lib/private/Files/SimpleFS/SimpleFolder.php +++ b/lib/private/Files/SimpleFS/SimpleFolder.php @@ -28,8 +28,8 @@ use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\Node; use OCP\Files\NotFoundException; -use OCP\Files\SimpleFS\ISimpleFolder; use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; class SimpleFolder implements ISimpleFolder { /** @var Folder */ diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php index 2d2bb52635b..35add2c606b 100644 --- a/lib/private/Files/Storage/DAV.php +++ b/lib/private/Files/Storage/DAV.php @@ -55,11 +55,11 @@ use OCP\ICertificateManager; use OCP\IConfig; use OCP\Util; use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; use Sabre\DAV\Client; use Sabre\DAV\Xml\Property\ResourceType; use Sabre\HTTP\ClientException; use Sabre\HTTP\ClientHttpException; -use Psr\Log\LoggerInterface; use Sabre\HTTP\RequestInterface; /** diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index eeb9e11b24e..0fca853da59 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -74,6 +74,8 @@ class Local extends \OC\Files\Storage\Common { protected bool $unlinkOnTruncate; + protected bool $caseInsensitive = false; + public function __construct($arguments) { if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) { throw new \InvalidArgumentException('No data directory set for local storage'); @@ -93,6 +95,7 @@ class Local extends \OC\Files\Storage\Common { $this->config = \OC::$server->get(IConfig::class); $this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class); $this->defUMask = $this->config->getSystemValue('localstorage.umask', 0022); + $this->caseInsensitive = $this->config->getSystemValueBool('localstorage.case_insensitive', false); // support Write-Once-Read-Many file systems $this->unlinkOnTruncate = $this->config->getSystemValueBool('localstorage.unlink_on_truncate', false); @@ -162,6 +165,9 @@ class Local extends \OC\Files\Storage\Common { } public function is_dir($path) { + if ($this->caseInsensitive && !$this->file_exists($path)) { + return false; + } if (str_ends_with($path, '/')) { $path = substr($path, 0, -1); } @@ -169,6 +175,9 @@ class Local extends \OC\Files\Storage\Common { } public function is_file($path) { + if ($this->caseInsensitive && !$this->file_exists($path)) { + return false; + } return is_file($this->getSourcePath($path)); } @@ -271,7 +280,13 @@ class Local extends \OC\Files\Storage\Common { } public function file_exists($path) { - return file_exists($this->getSourcePath($path)); + if ($this->caseInsensitive) { + $fullPath = $this->getSourcePath($path); + $content = scandir(dirname($fullPath), SCANDIR_SORT_NONE); + return is_array($content) && array_search(basename($fullPath), $content) !== false; + } else { + return file_exists($this->getSourcePath($path)); + } } public function filemtime($path) { @@ -372,6 +387,11 @@ class Local extends \OC\Files\Storage\Common { } if (@rename($this->getSourcePath($source), $this->getSourcePath($target))) { + if ($this->caseInsensitive) { + if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) { + return false; + } + } return true; } @@ -388,6 +408,11 @@ class Local extends \OC\Files\Storage\Common { } $result = copy($this->getSourcePath($source), $this->getSourcePath($target)); umask($oldMask); + if ($this->caseInsensitive) { + if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) { + return false; + } + } return $result; } } diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php index 6633cbf41e3..1bdb0e39f14 100644 --- a/lib/private/Files/Storage/Wrapper/Encoding.php +++ b/lib/private/Files/Storage/Wrapper/Encoding.php @@ -28,8 +28,8 @@ */ namespace OC\Files\Storage\Wrapper; -use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; +use OCP\Cache\CappedMemoryCache; use OCP\Files\Storage\IStorage; use OCP\ICache; diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php index 1921ac27848..592acd418ec 100644 --- a/lib/private/Files/Storage/Wrapper/Jail.php +++ b/lib/private/Files/Storage/Wrapper/Jail.php @@ -396,10 +396,7 @@ class Jail extends Wrapper { * @return \OC\Files\Cache\Cache */ public function getCache($path = '', $storage = null) { - if (!$storage) { - $storage = $this->getWrapperStorage(); - } - $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage); + $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path)); return new CacheJail($sourceCache, $this->rootPath); } diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index bcf0a10740b..c57991f35a9 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -153,18 +153,18 @@ class Encryption extends Wrapper { * @throws \BadMethodCallException */ public static function wrap($source, $internalPath, $fullPath, array $header, - $uid, - \OCP\Encryption\IEncryptionModule $encryptionModule, - \OC\Files\Storage\Storage $storage, - \OC\Files\Storage\Wrapper\Encryption $encStorage, - \OC\Encryption\Util $util, - \OC\Encryption\File $file, - $mode, - $size, - $unencryptedSize, - $headerSize, - $signed, - $wrapper = Encryption::class) { + $uid, + \OCP\Encryption\IEncryptionModule $encryptionModule, + \OC\Files\Storage\Storage $storage, + \OC\Files\Storage\Wrapper\Encryption $encStorage, + \OC\Encryption\Util $util, + \OC\Encryption\File $file, + $mode, + $size, + $unencryptedSize, + $headerSize, + $signed, + $wrapper = Encryption::class) { $context = stream_context_create([ 'ocencryption' => [ 'source' => $source, diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php index 878680caa4e..9d9f6416208 100644 --- a/lib/private/Files/Template/TemplateManager.php +++ b/lib/private/Files/Template/TemplateManager.php @@ -31,8 +31,8 @@ use OC\AppFramework\Bootstrap\Coordinator; use OC\Files\Cache\Scanner; use OC\Files\Filesystem; use OCP\EventDispatcher\IEventDispatcher; -use OCP\Files\Folder; use OCP\Files\File; +use OCP\Files\Folder; use OCP\Files\GenericFileException; use OCP\Files\IRootFolder; use OCP\Files\Node; @@ -275,6 +275,11 @@ class TemplateManager implements ITemplateManager { $isDefaultTemplates = $skeletonTemplatePath === $defaultTemplateDirectory; $userLang = $this->l10nFactory->getUserLanguage($this->userManager->get($this->userId)); + if ($skeletonTemplatePath === '') { + $this->setTemplatePath(''); + return ''; + } + try { $l10n = $this->l10nFactory->get('lib', $userLang); $userFolder = $this->rootFolder->getUserFolder($this->userId); diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php index 9a61aa93b95..71b8cb986d7 100644 --- a/lib/private/Files/Type/Detection.php +++ b/lib/private/Files/Type/Detection.php @@ -75,9 +75,9 @@ class Detection implements IMimeTypeDetector { private $defaultConfigDir; public function __construct(IURLGenerator $urlGenerator, - LoggerInterface $logger, - string $customConfigDir, - string $defaultConfigDir) { + LoggerInterface $logger, + string $customConfigDir, + string $defaultConfigDir) { $this->urlGenerator = $urlGenerator; $this->logger = $logger; $this->customConfigDir = $customConfigDir; @@ -96,8 +96,8 @@ class Detection implements IMimeTypeDetector { * @param string|null $secureMimeType */ public function registerType(string $extension, - string $mimetype, - ?string $secureMimeType = null): void { + string $mimetype, + ?string $secureMimeType = null): void { $this->mimetypes[$extension] = [$mimetype, $secureMimeType]; $this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype; } diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php index b7f6972ee10..226e5462b34 100644 --- a/lib/private/Files/Utils/Scanner.php +++ b/lib/private/Files/Utils/Scanner.php @@ -41,11 +41,11 @@ use OCA\Files_Sharing\SharedStorage; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Events\BeforeFileScannedEvent; use OCP\Files\Events\BeforeFolderScannedEvent; -use OCP\Files\Events\NodeAddedToCache; use OCP\Files\Events\FileCacheUpdated; -use OCP\Files\Events\NodeRemovedFromCache; use OCP\Files\Events\FileScannedEvent; use OCP\Files\Events\FolderScannedEvent; +use OCP\Files\Events\NodeAddedToCache; +use OCP\Files\Events\NodeRemovedFromCache; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\Files\StorageNotAvailableException; diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 86651ab3e1a..6eefb093795 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -49,10 +49,10 @@ namespace OC\Files; use Icewind\Streams\CallbackWrapper; use OC\Files\Mount\MoveableMount; use OC\Files\Storage\Storage; -use OC\User\LazyUser; use OC\Share\Share; -use OC\User\User; +use OC\User\LazyUser; use OC\User\Manager as UserManager; +use OC\User\User; use OCA\Files_Sharing\SharedMount; use OCP\Constants; use OCP\Files\Cache\ICacheEntry; @@ -1525,7 +1525,7 @@ class View { $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ // if sharing was disabled for the user we remove the share permissions - if (\OCP\Util::isSharingDisabledForUser()) { + if ($sharingDisabled) { $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; } diff --git a/lib/private/FilesMetadata/FilesMetadataManager.php b/lib/private/FilesMetadata/FilesMetadataManager.php new file mode 100644 index 00000000000..013c85af604 --- /dev/null +++ b/lib/private/FilesMetadata/FilesMetadataManager.php @@ -0,0 +1,347 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata; + +use JsonException; +use OC\FilesMetadata\Job\UpdateSingleMetadata; +use OC\FilesMetadata\Listener\MetadataDelete; +use OC\FilesMetadata\Listener\MetadataUpdate; +use OC\FilesMetadata\Model\FilesMetadata; +use OC\FilesMetadata\Service\IndexRequestService; +use OC\FilesMetadata\Service\MetadataRequestService; +use OCP\BackgroundJob\IJobList; +use OCP\DB\Exception; +use OCP\DB\Exception as DBException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Cache\CacheEntryRemovedEvent; +use OCP\Files\Events\Node\NodeWrittenEvent; +use OCP\Files\InvalidPathException; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\FilesMetadata\Event\MetadataBackgroundEvent; +use OCP\FilesMetadata\Event\MetadataLiveEvent; +use OCP\FilesMetadata\Event\MetadataNamedEvent; +use OCP\FilesMetadata\Exceptions\FilesMetadataException; +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\IFilesMetadataManager; +use OCP\FilesMetadata\IMetadataQuery; +use OCP\FilesMetadata\Model\IFilesMetadata; +use OCP\FilesMetadata\Model\IMetadataValueWrapper; +use OCP\IConfig; +use OCP\IDBConnection; +use Psr\Log\LoggerInterface; + +/** + * @inheritDoc + * @since 28.0.0 + */ +class FilesMetadataManager implements IFilesMetadataManager { + public const CONFIG_KEY = 'files_metadata'; + public const MIGRATION_DONE = 'files_metadata_installed'; + private const JSON_MAXSIZE = 100000; + + private ?IFilesMetadata $all = null; + + public function __construct( + private IEventDispatcher $eventDispatcher, + private IJobList $jobList, + private IConfig $config, + private LoggerInterface $logger, + private MetadataRequestService $metadataRequestService, + private IndexRequestService $indexRequestService, + ) { + } + + /** + * @inheritDoc + * + * @param Node $node related node + * @param int $process type of process + * + * @return IFilesMetadata + * @throws FilesMetadataException if metadata are invalid + * @throws InvalidPathException if path to file is not valid + * @throws NotFoundException if file cannot be found + * @see self::PROCESS_BACKGROUND + * @see self::PROCESS_LIVE + * @since 28.0.0 + */ + public function refreshMetadata( + Node $node, + int $process = self::PROCESS_LIVE, + string $namedEvent = '' + ): IFilesMetadata { + try { + $metadata = $this->metadataRequestService->getMetadataFromFileId($node->getId()); + } catch (FilesMetadataNotFoundException) { + $metadata = new FilesMetadata($node->getId()); + } + + // if $process is LIVE, we enforce LIVE + // if $process is NAMED, we go NAMED + // else BACKGROUND + if ((self::PROCESS_LIVE & $process) !== 0) { + $event = new MetadataLiveEvent($node, $metadata); + } elseif ((self::PROCESS_NAMED & $process) !== 0) { + $event = new MetadataNamedEvent($node, $metadata, $namedEvent); + } else { + $event = new MetadataBackgroundEvent($node, $metadata); + } + + $this->eventDispatcher->dispatchTyped($event); + $this->saveMetadata($event->getMetadata()); + + // if requested, we add a new job for next cron to refresh metadata out of main thread + // if $process was set to LIVE+BACKGROUND, we run background process directly + if ($event instanceof MetadataLiveEvent && $event->isRunAsBackgroundJobRequested()) { + if ((self::PROCESS_BACKGROUND & $process) !== 0) { + return $this->refreshMetadata($node, self::PROCESS_BACKGROUND); + } + + $this->jobList->add(UpdateSingleMetadata::class, [$node->getOwner()->getUID(), $node->getId()]); + } + + return $metadata; + } + + /** + * @param int $fileId file id + * @param boolean $generate Generate if metadata does not exists + * + * @inheritDoc + * @return IFilesMetadata + * @throws FilesMetadataNotFoundException if not found + * @since 28.0.0 + */ + public function getMetadata(int $fileId, bool $generate = false): IFilesMetadata { + try { + return $this->metadataRequestService->getMetadataFromFileId($fileId); + } catch (FilesMetadataNotFoundException $ex) { + if ($generate) { + return new FilesMetadata($fileId); + } + + throw $ex; + } + } + + /** + * returns metadata of multiple file ids + * + * @param int[] $fileIds file ids + * + * @return array File ID is the array key, files without metadata are not returned in the array + * @psalm-return array<int, IFilesMetadata> + * @since 28.0.0 + */ + public function getMetadataForFiles(array $fileIds): array { + return $this->metadataRequestService->getMetadataFromFileIds($fileIds); + } + + /** + * @param IFilesMetadata $filesMetadata metadata + * + * @inheritDoc + * @throws FilesMetadataException if metadata seems malformed + * @since 28.0.0 + */ + public function saveMetadata(IFilesMetadata $filesMetadata): void { + if ($filesMetadata->getFileId() === 0 || !$filesMetadata->updated()) { + return; + } + + $json = json_encode($filesMetadata->jsonSerialize()); + if (strlen($json) > self::JSON_MAXSIZE) { + throw new FilesMetadataException('json cannot exceed ' . self::JSON_MAXSIZE . ' characters long'); + } + + try { + if ($filesMetadata->getSyncToken() === '') { + $this->metadataRequestService->store($filesMetadata); + } else { + $this->metadataRequestService->updateMetadata($filesMetadata); + } + } catch (DBException $e) { + // most of the logged exception are the result of race condition + // between 2 simultaneous process trying to create/update metadata + $this->logger->warning('issue while saveMetadata', ['exception' => $e, 'metadata' => $filesMetadata]); + + return; + } + + // update indexes + foreach ($filesMetadata->getIndexes() as $index) { + try { + $this->indexRequestService->updateIndex($filesMetadata, $index); + } catch (DBException $e) { + $this->logger->warning('issue while updateIndex', ['exception' => $e]); + } + } + + // update metadata types list + $current = $this->getKnownMetadata(); + $current->import($filesMetadata->jsonSerialize(true)); + $this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current)); + } + + /** + * @param int $fileId file id + * + * @inheritDoc + * @since 28.0.0 + */ + public function deleteMetadata(int $fileId): void { + try { + $this->metadataRequestService->dropMetadata($fileId); + } catch (Exception $e) { + $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]); + } + + try { + $this->indexRequestService->dropIndex($fileId); + } catch (Exception $e) { + $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]); + } + } + + /** + * @param IQueryBuilder $qb + * @param string $fileTableAlias alias of the table that contains data about files + * @param string $fileIdField alias of the field that contains file ids + * + * @inheritDoc + * @return IMetadataQuery|null + * @see IMetadataQuery + * @since 28.0.0 + */ + public function getMetadataQuery( + IQueryBuilder $qb, + string $fileTableAlias, + string $fileIdField + ): ?IMetadataQuery { + if (!$this->metadataInitiated()) { + return null; + } + + return new MetadataQuery($qb, $this->getKnownMetadata(), $fileTableAlias, $fileIdField); + } + + /** + * @inheritDoc + * @return IFilesMetadata + * @since 28.0.0 + */ + public function getKnownMetadata(): IFilesMetadata { + if (null !== $this->all) { + return $this->all; + } + $this->all = new FilesMetadata(); + + try { + $data = json_decode($this->config->getAppValue('core', self::CONFIG_KEY, '[]'), true, 127, JSON_THROW_ON_ERROR); + $this->all->import($data); + } catch (JsonException) { + $this->logger->warning('issue while reading stored list of metadata. Advised to run ./occ files:scan --all --generate-metadata'); + } + + return $this->all; + } + + /** + * @param string $key metadata key + * @param string $type metadata type + * @param bool $indexed TRUE if metadata can be search + * @param int $editPermission remote edit permission via Webdav PROPPATCH + * + * @inheritDoc + * @since 28.0.0 + * @see IMetadataValueWrapper::TYPE_INT + * @see IMetadataValueWrapper::TYPE_FLOAT + * @see IMetadataValueWrapper::TYPE_BOOL + * @see IMetadataValueWrapper::TYPE_ARRAY + * @see IMetadataValueWrapper::TYPE_STRING_LIST + * @see IMetadataValueWrapper::TYPE_INT_LIST + * @see IMetadataValueWrapper::TYPE_STRING + * @see IMetadataValueWrapper::EDIT_FORBIDDEN + * @see IMetadataValueWrapper::EDIT_REQ_OWNERSHIP + * @see IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION + * @see IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION + */ + public function initMetadata( + string $key, + string $type, + bool $indexed = false, + int $editPermission = IMetadataValueWrapper::EDIT_FORBIDDEN + ): void { + $current = $this->getKnownMetadata(); + try { + if ($current->getType($key) === $type + && $indexed === $current->isIndex($key) + && $editPermission === $current->getEditPermission($key)) { + return; // if key exists, with same type and indexed, we do nothing. + } + } catch (FilesMetadataNotFoundException) { + // if value does not exist, we keep on the writing of course + } + + $current->import([$key => ['type' => $type, 'indexed' => $indexed, 'editPermission' => $editPermission]]); + $this->config->setAppValue('core', self::CONFIG_KEY, json_encode($current)); + $this->all = $current; + } + + /** + * load listeners + * + * @param IEventDispatcher $eventDispatcher + */ + public static function loadListeners(IEventDispatcher $eventDispatcher): void { + $eventDispatcher->addServiceListener(NodeWrittenEvent::class, MetadataUpdate::class); + $eventDispatcher->addServiceListener(CacheEntryRemovedEvent::class, MetadataDelete::class); + } + + /** + * Will confirm that tables were created and store an app value to cache the result. + * Can be removed in 29 as this is to avoid strange situation when Nextcloud files were + * replaced but the upgrade was not triggered yet. + * + * @return bool + */ + private function metadataInitiated(): bool { + if ($this->config->getAppValue('core', self::MIGRATION_DONE, '0') === '1') { + return true; + } + + $dbConnection = \OCP\Server::get(IDBConnection::class); + if ($dbConnection->tableExists(MetadataRequestService::TABLE_METADATA)) { + $this->config->setAppValue('core', self::MIGRATION_DONE, '1'); + + return true; + } + + return false; + } +} diff --git a/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php b/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php new file mode 100644 index 00000000000..d18c8aa3680 --- /dev/null +++ b/lib/private/FilesMetadata/Job/UpdateSingleMetadata.php @@ -0,0 +1,67 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata\Job; + +use OC\FilesMetadata\FilesMetadataManager; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\QueuedJob; +use OCP\Files\IRootFolder; +use OCP\FilesMetadata\Event\MetadataLiveEvent; +use OCP\FilesMetadata\IFilesMetadataManager; +use Psr\Log\LoggerInterface; + +/** + * Simple background job, created when requested by an app during the + * dispatch of MetadataLiveEvent. + * This background job will re-run the event to refresh metadata on a non-live thread. + * + * @see MetadataLiveEvent::requestBackgroundJob() + * @since 28.0.0 + */ +class UpdateSingleMetadata extends QueuedJob { + public function __construct( + ITimeFactory $time, + private IRootFolder $rootFolder, + private FilesMetadataManager $filesMetadataManager, + private LoggerInterface $logger + ) { + parent::__construct($time); + } + + protected function run($argument) { + [$userId, $fileId] = $argument; + + try { + $node = $this->rootFolder->getUserFolder($userId)->getById($fileId); + if (count($node) > 0) { + $file = array_shift($node); + $this->filesMetadataManager->refreshMetadata($file, IFilesMetadataManager::PROCESS_BACKGROUND); + } + } catch (\Exception $e) { + $this->logger->warning('issue while running UpdateSingleMetadata', ['exception' => $e, 'userId' => $userId, 'fileId' => $fileId]); + } + } +} diff --git a/lib/private/FilesMetadata/Listener/MetadataDelete.php b/lib/private/FilesMetadata/Listener/MetadataDelete.php new file mode 100644 index 00000000000..d950c2cea5f --- /dev/null +++ b/lib/private/FilesMetadata/Listener/MetadataDelete.php @@ -0,0 +1,61 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata\Listener; + +use Exception; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Files\Cache\CacheEntryRemovedEvent; +use OCP\FilesMetadata\IFilesMetadataManager; +use Psr\Log\LoggerInterface; + +/** + * Handle file deletion event and remove stored metadata related to the deleted file + * + * @template-implements IEventListener<CacheEntryRemovedEvent> + */ +class MetadataDelete implements IEventListener { + public function __construct( + private IFilesMetadataManager $filesMetadataManager, + private LoggerInterface $logger + ) { + } + + public function handle(Event $event): void { + if (!($event instanceof CacheEntryRemovedEvent)) { + return; + } + + try { + $nodeId = $event->getFileId(); + if ($nodeId > 0) { + $this->filesMetadataManager->deleteMetadata($nodeId); + } + } catch (Exception $e) { + $this->logger->warning('issue while running MetadataDelete', ['exception' => $e]); + } + } +} diff --git a/lib/private/FilesMetadata/Listener/MetadataUpdate.php b/lib/private/FilesMetadata/Listener/MetadataUpdate.php new file mode 100644 index 00000000000..9848f079882 --- /dev/null +++ b/lib/private/FilesMetadata/Listener/MetadataUpdate.php @@ -0,0 +1,64 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata\Listener; + +use Exception; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Files\Events\Node\NodeCreatedEvent; +use OCP\Files\Events\Node\NodeWrittenEvent; +use OCP\FilesMetadata\IFilesMetadataManager; +use Psr\Log\LoggerInterface; + +/** + * Handle file creation/modification events and initiate a new event related to the created/edited file. + * The generated new event is broadcast in order to obtain file related metadata from other apps. + * metadata will be stored in database. + * + * @template-implements IEventListener<NodeCreatedEvent|NodeWrittenEvent> + */ +class MetadataUpdate implements IEventListener { + public function __construct( + private IFilesMetadataManager $filesMetadataManager, + private LoggerInterface $logger + ) { + } + + /** + * @param Event $event + */ + public function handle(Event $event): void { + if (!($event instanceof NodeWrittenEvent)) { + return; + } + + try { + $this->filesMetadataManager->refreshMetadata($event->getNode()); + } catch (Exception $e) { + $this->logger->warning('issue while running MetadataUpdate', ['exception' => $e]); + } + } +} diff --git a/lib/private/FilesMetadata/MetadataQuery.php b/lib/private/FilesMetadata/MetadataQuery.php new file mode 100644 index 00000000000..aa079c678d7 --- /dev/null +++ b/lib/private/FilesMetadata/MetadataQuery.php @@ -0,0 +1,167 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata; + +use OC\FilesMetadata\Model\FilesMetadata; +use OC\FilesMetadata\Service\IndexRequestService; +use OC\FilesMetadata\Service\MetadataRequestService; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; +use OCP\FilesMetadata\IMetadataQuery; +use OCP\FilesMetadata\Model\IFilesMetadata; +use OCP\FilesMetadata\Model\IMetadataValueWrapper; + +/** + * @inheritDoc + * @since 28.0.0 + */ +class MetadataQuery implements IMetadataQuery { + private array $knownJoinedIndex = []; + public function __construct( + private IQueryBuilder $queryBuilder, + private IFilesMetadata $knownMetadata, + private string $fileTableAlias = 'fc', + private string $fileIdField = 'fileid', + private string $alias = 'meta', + private string $aliasIndexPrefix = 'meta_index' + ) { + } + + /** + * @inheritDoc + * @see self::extractMetadata() + * @since 28.0.0 + */ + public function retrieveMetadata(): void { + $this->queryBuilder->selectAlias($this->alias . '.json', 'meta_json'); + $this->queryBuilder->selectAlias($this->alias . '.sync_token', 'meta_sync_token'); + $this->queryBuilder->leftJoin( + $this->fileTableAlias, MetadataRequestService::TABLE_METADATA, $this->alias, + $this->queryBuilder->expr()->eq($this->fileTableAlias . '.' . $this->fileIdField, $this->alias . '.file_id') + ); + } + + /** + * @param array $row result row + * + * @inheritDoc + * @return IFilesMetadata metadata + * @see self::retrieveMetadata() + * @since 28.0.0 + */ + public function extractMetadata(array $row): IFilesMetadata { + $fileId = (array_key_exists($this->fileIdField, $row)) ? $row[$this->fileIdField] : 0; + $metadata = new FilesMetadata((int)$fileId); + try { + $metadata->importFromDatabase($row, $this->alias . '_'); + } catch (FilesMetadataNotFoundException) { + // can be ignored as files' metadata are optional and might not exist in database + } + + return $metadata; + } + + /** + * @param string $metadataKey metadata key + * @param bool $enforce limit the request only to existing metadata + * + * @inheritDoc + * @since 28.0.0 + */ + public function joinIndex(string $metadataKey, bool $enforce = false): string { + if (array_key_exists($metadataKey, $this->knownJoinedIndex)) { + return $this->knownJoinedIndex[$metadataKey]; + } + + $aliasIndex = $this->aliasIndexPrefix . '_' . count($this->knownJoinedIndex); + $this->knownJoinedIndex[$metadataKey] = $aliasIndex; + + $expr = $this->queryBuilder->expr(); + $andX = $expr->andX($expr->eq($aliasIndex . '.file_id', $this->fileTableAlias . '.' . $this->fileIdField)); + $andX->add($expr->eq($this->getMetadataKeyField($metadataKey), $this->queryBuilder->createNamedParameter($metadataKey))); + + if ($enforce) { + $this->queryBuilder->innerJoin( + $this->fileTableAlias, + IndexRequestService::TABLE_METADATA_INDEX, + $aliasIndex, + $andX + ); + } else { + $this->queryBuilder->leftJoin( + $this->fileTableAlias, + IndexRequestService::TABLE_METADATA_INDEX, + $aliasIndex, + $andX + ); + } + + return $aliasIndex; + } + + /** + * @throws FilesMetadataNotFoundException + */ + private function joinedTableAlias(string $metadataKey): string { + if (!array_key_exists($metadataKey, $this->knownJoinedIndex)) { + throw new FilesMetadataNotFoundException('table related to ' . $metadataKey . ' not initiated, you need to use leftJoin() first.'); + } + + return $this->knownJoinedIndex[$metadataKey]; + } + + /** + * @inheritDoc + * + * @param string $metadataKey metadata key + * + * @return string table field + * @throws FilesMetadataNotFoundException + * @since 28.0.0 + */ + public function getMetadataKeyField(string $metadataKey): string { + return $this->joinedTableAlias($metadataKey) . '.meta_key'; + } + + /** + * @inheritDoc + * + * @param string $metadataKey metadata key + * + * @return string table field + * @throws FilesMetadataNotFoundException if metadataKey is not known + * @throws FilesMetadataTypeException is metadataKey is not set as indexed + * @since 28.0.0 + */ + public function getMetadataValueField(string $metadataKey): string { + return match ($this->knownMetadata->getType($metadataKey)) { + IMetadataValueWrapper::TYPE_STRING => $this->joinedTableAlias($metadataKey) . '.meta_value_string', + IMetadataValueWrapper::TYPE_INT, IMetadataValueWrapper::TYPE_BOOL => $this->joinedTableAlias($metadataKey) . '.meta_value_int', + default => throw new FilesMetadataTypeException('metadata is not set as indexed'), + }; + } +} diff --git a/lib/private/FilesMetadata/Model/FilesMetadata.php b/lib/private/FilesMetadata/Model/FilesMetadata.php new file mode 100644 index 00000000000..629b537dabe --- /dev/null +++ b/lib/private/FilesMetadata/Model/FilesMetadata.php @@ -0,0 +1,621 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata\Model; + +use JsonException; +use OCP\FilesMetadata\Exceptions\FilesMetadataKeyFormatException; +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; +use OCP\FilesMetadata\Model\IFilesMetadata; +use OCP\FilesMetadata\Model\IMetadataValueWrapper; + +/** + * Model that represent metadata linked to a specific file. + * + * @inheritDoc + * @since 28.0.0 + */ +class FilesMetadata implements IFilesMetadata { + /** @var array<string, MetadataValueWrapper> */ + private array $metadata = []; + private bool $updated = false; + private int $lastUpdate = 0; + private string $syncToken = ''; + + public function __construct( + private int $fileId = 0 + ) { + } + + /** + * @inheritDoc + * @return int related file id + * @since 28.0.0 + */ + public function getFileId(): int { + return $this->fileId; + } + + /** + * @inheritDoc + * @return int timestamp + * @since 28.0.0 + */ + public function lastUpdateTimestamp(): int { + return $this->lastUpdate; + } + + /** + * @inheritDoc + * @return string token + * @since 28.0.0 + */ + public function getSyncToken(): string { + return $this->syncToken; + } + + /** + * @inheritDoc + * @return string[] list of keys + * @since 28.0.0 + */ + public function getKeys(): array { + return array_keys($this->metadata); + } + + /** + * @param string $needle metadata key to search + * + * @inheritDoc + * @return bool TRUE if key exist + * @since 28.0.0 + */ + public function hasKey(string $needle): bool { + return (in_array($needle, $this->getKeys())); + } + + /** + * @inheritDoc + * @return string[] list of indexes + * @since 28.0.0 + */ + public function getIndexes(): array { + $indexes = []; + foreach ($this->getKeys() as $key) { + if ($this->metadata[$key]->isIndexed()) { + $indexes[] = $key; + } + } + + return $indexes; + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return bool TRUE if key exists and is set as indexed + * @since 28.0.0 + */ + public function isIndex(string $key): bool { + return $this->metadata[$key]?->isIndexed() ?? false; + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return int edit permission + * @throws FilesMetadataNotFoundException + * @since 28.0.0 + */ + public function getEditPermission(string $key): int { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getEditPermission(); + } + + /** + * @param string $key metadata key + * @param int $permission edit permission + * + * @inheritDoc + * @throws FilesMetadataNotFoundException + * @since 28.0.0 + */ + public function setEditPermission(string $key, int $permission): void { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + $this->metadata[$key]->setEditPermission($permission); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return string metadata value + * @throws FilesMetadataNotFoundException + * @throws FilesMetadataTypeException + * @since 28.0.0 + */ + public function getString(string $key): string { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getValueString(); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return int metadata value + * @throws FilesMetadataNotFoundException + * @throws FilesMetadataTypeException + * @since 28.0.0 + */ + public function getInt(string $key): int { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getValueInt(); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return float metadata value + * @throws FilesMetadataNotFoundException + * @throws FilesMetadataTypeException + * @since 28.0.0 + */ + public function getFloat(string $key): float { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getValueFloat(); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return bool metadata value + * @throws FilesMetadataNotFoundException + * @throws FilesMetadataTypeException + * @since 28.0.0 + */ + public function getBool(string $key): bool { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getValueBool(); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return array metadata value + * @throws FilesMetadataNotFoundException + * @throws FilesMetadataTypeException + * @since 28.0.0 + */ + public function getArray(string $key): array { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getValueArray(); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return string[] metadata value + * @throws FilesMetadataNotFoundException + * @throws FilesMetadataTypeException + * @since 28.0.0 + */ + public function getStringList(string $key): array { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getValueStringList(); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return int[] metadata value + * @throws FilesMetadataNotFoundException + * @throws FilesMetadataTypeException + * @since 28.0.0 + */ + public function getIntList(string $key): array { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getValueIntList(); + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return string value type + * @throws FilesMetadataNotFoundException + * @see IMetadataValueWrapper::TYPE_STRING + * @see IMetadataValueWrapper::TYPE_INT + * @see IMetadataValueWrapper::TYPE_FLOAT + * @see IMetadataValueWrapper::TYPE_BOOL + * @see IMetadataValueWrapper::TYPE_ARRAY + * @see IMetadataValueWrapper::TYPE_STRING_LIST + * @see IMetadataValueWrapper::TYPE_INT_LIST + * @since 28.0.0 + */ + public function getType(string $key): string { + if (!array_key_exists($key, $this->metadata)) { + throw new FilesMetadataNotFoundException(); + } + + return $this->metadata[$key]->getType(); + } + + /** + * @param string $key metadata key + * @param string $value metadata value + * @param bool $index set TRUE if value must be indexed + * + * @inheritDoc + * @return self + * @throws FilesMetadataKeyFormatException + * @since 28.0.0 + */ + public function setString(string $key, string $value, bool $index = false): IFilesMetadata { + $this->confirmKeyFormat($key); + try { + if ($this->getString($key) === $value && $index === $this->isIndex($key)) { + return $this; // we ignore if value and index have not changed + } + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { + // if value does not exist, or type has changed, we keep on the writing + } + + $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING); + $this->updated = true; + $this->metadata[$key] = $meta->setValueString($value)->setIndexed($index); + + return $this; + } + + /** + * @param string $key metadata key + * @param int $value metadata value + * @param bool $index set TRUE if value must be indexed + * + * @inheritDoc + * @return self + * @throws FilesMetadataKeyFormatException + * @since 28.0.0 + */ + public function setInt(string $key, int $value, bool $index = false): IFilesMetadata { + $this->confirmKeyFormat($key); + try { + if ($this->getInt($key) === $value && $index === $this->isIndex($key)) { + return $this; // we ignore if value have not changed + } + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { + // if value does not exist, or type has changed, we keep on the writing + } + + $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_INT); + $this->metadata[$key] = $meta->setValueInt($value)->setIndexed($index); + $this->updated = true; + + return $this; + } + + /** + * @param string $key metadata key + * @param float $value metadata value + * + * @inheritDoc + * @return self + * @throws FilesMetadataKeyFormatException + * @since 28.0.0 + */ + public function setFloat(string $key, float $value, bool $index = false): IFilesMetadata { + $this->confirmKeyFormat($key); + try { + if ($this->getFloat($key) === $value && $index === $this->isIndex($key)) { + return $this; // we ignore if value have not changed + } + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { + // if value does not exist, or type has changed, we keep on the writing + } + + $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_FLOAT); + $this->metadata[$key] = $meta->setValueFloat($value)->setIndexed($index); + $this->updated = true; + + return $this; + } + + + /** + * @param string $key metadata key + * @param bool $value metadata value + * @param bool $index set TRUE if value must be indexed + * + * @inheritDoc + * @return self + * @throws FilesMetadataKeyFormatException + * @since 28.0.0 + */ + public function setBool(string $key, bool $value, bool $index = false): IFilesMetadata { + $this->confirmKeyFormat($key); + try { + if ($this->getBool($key) === $value && $index === $this->isIndex($key)) { + return $this; // we ignore if value have not changed + } + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { + // if value does not exist, or type has changed, we keep on the writing + } + + $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_BOOL); + $this->metadata[$key] = $meta->setValueBool($value)->setIndexed($index); + $this->updated = true; + + return $this; + } + + + /** + * @param string $key metadata key + * @param array $value metadata value + * + * @inheritDoc + * @return self + * @throws FilesMetadataKeyFormatException + * @since 28.0.0 + */ + public function setArray(string $key, array $value): IFilesMetadata { + $this->confirmKeyFormat($key); + try { + if ($this->getArray($key) === $value) { + return $this; // we ignore if value have not changed + } + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { + // if value does not exist, or type has changed, we keep on the writing + } + + $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_ARRAY); + $this->metadata[$key] = $meta->setValueArray($value); + $this->updated = true; + + return $this; + } + + /** + * @param string $key metadata key + * @param string[] $value metadata value + * @param bool $index set TRUE if each values from the list must be indexed + * + * @inheritDoc + * @return self + * @throws FilesMetadataKeyFormatException + * @since 28.0.0 + */ + public function setStringList(string $key, array $value, bool $index = false): IFilesMetadata { + $this->confirmKeyFormat($key); + try { + if ($this->getStringList($key) === $value) { + return $this; // we ignore if value have not changed + } + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { + // if value does not exist, or type has changed, we keep on the writing + } + + $meta = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST); + $this->metadata[$key] = $meta->setValueStringList($value)->setIndexed($index); + $this->updated = true; + + return $this; + } + + /** + * @param string $key metadata key + * @param int[] $value metadata value + * @param bool $index set TRUE if each values from the list must be indexed + * + * @inheritDoc + * @return self + * @throws FilesMetadataKeyFormatException + * @since 28.0.0 + */ + public function setIntList(string $key, array $value, bool $index = false): IFilesMetadata { + $this->confirmKeyFormat($key); + try { + if ($this->getIntList($key) === $value) { + return $this; // we ignore if value have not changed + } + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException $e) { + // if value does not exist, or type has changed, we keep on the writing + } + + $valueWrapper = new MetadataValueWrapper(IMetadataValueWrapper::TYPE_STRING_LIST); + $this->metadata[$key] = $valueWrapper->setValueIntList($value)->setIndexed($index); + $this->updated = true; + + return $this; + } + + /** + * @param string $key metadata key + * + * @inheritDoc + * @return self + * @since 28.0.0 + */ + public function unset(string $key): IFilesMetadata { + if (!array_key_exists($key, $this->metadata)) { + return $this; + } + + unset($this->metadata[$key]); + $this->updated = true; + + return $this; + } + + /** + * @param string $keyPrefix metadata key prefix + * + * @inheritDoc + * @return self + * @since 28.0.0 + */ + public function removeStartsWith(string $keyPrefix): IFilesMetadata { + if ($keyPrefix === '') { + return $this; + } + + foreach ($this->getKeys() as $key) { + if (str_starts_with($key, $keyPrefix)) { + $this->unset($key); + } + } + + return $this; + } + + /** + * @param string $key + * + * @return void + * @throws FilesMetadataKeyFormatException + */ + private function confirmKeyFormat(string $key): void { + $acceptedChars = ['-', '_']; + if (ctype_alnum(str_replace($acceptedChars, '', $key))) { + return; + } + + throw new FilesMetadataKeyFormatException('key can only contains alphanumerical characters, and dash (-, _)'); + } + + /** + * @inheritDoc + * @return bool TRUE if metadata have been modified + * @since 28.0.0 + */ + public function updated(): bool { + return $this->updated; + } + + public function jsonSerialize(bool $emptyValues = false): array { + $data = []; + foreach ($this->metadata as $metaKey => $metaValueWrapper) { + $data[$metaKey] = $metaValueWrapper->jsonSerialize($emptyValues); + } + + return $data; + } + + /** + * @return array<string, string|int|bool|float|string[]|int[]> + */ + public function asArray(): array { + $data = []; + foreach ($this->metadata as $metaKey => $metaValueWrapper) { + try { + $data[$metaKey] = $metaValueWrapper->getValueAny(); + } catch (FilesMetadataNotFoundException $e) { + // ignore exception + } + } + + return $data; + } + + /** + * @param array $data + * + * @inheritDoc + * @return IFilesMetadata + * @since 28.0.0 + */ + public function import(array $data): IFilesMetadata { + foreach ($data as $k => $v) { + $valueWrapper = new MetadataValueWrapper(); + $this->metadata[$k] = $valueWrapper->import($v); + } + $this->updated = false; + + return $this; + } + + /** + * import data from database to configure this model + * + * @param array $data + * @param string $prefix + * + * @return IFilesMetadata + * @throws FilesMetadataNotFoundException + * @since 28.0.0 + */ + public function importFromDatabase(array $data, string $prefix = ''): IFilesMetadata { + try { + $this->syncToken = $data[$prefix . 'sync_token'] ?? ''; + + return $this->import( + json_decode( + $data[$prefix . 'json'] ?? '[]', + true, + 512, + JSON_THROW_ON_ERROR + ) + ); + } catch (JsonException) { + throw new FilesMetadataNotFoundException(); + } + } +} diff --git a/lib/private/FilesMetadata/Model/MetadataValueWrapper.php b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php new file mode 100644 index 00000000000..90f1554180d --- /dev/null +++ b/lib/private/FilesMetadata/Model/MetadataValueWrapper.php @@ -0,0 +1,421 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata\Model; + +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; +use OCP\FilesMetadata\Model\IMetadataValueWrapper; + +/** + * @inheritDoc + * @see IFilesMetadata + * @since 28.0.0 + */ +class MetadataValueWrapper implements IMetadataValueWrapper { + private string $type; + /** @var string|int|float|bool|array|string[]|int[] */ + private mixed $value = null; + private bool $indexed = false; + private int $editPermission = self::EDIT_FORBIDDEN; + + /** + * @param string $type value type + * + * @inheritDoc + * @see self::TYPE_INT + * @see self::TYPE_FLOAT + * @see self::TYPE_BOOL + * @see self::TYPE_ARRAY + * @see self::TYPE_STRING_LIST + * @see self::TYPE_INT_LIST + * @see self::TYPE_STRING + * @since 28.0.0 + */ + public function __construct(string $type = '') { + $this->type = $type; + } + + /** + * @inheritDoc + * @return string value type + * @see self::TYPE_INT + * @see self::TYPE_FLOAT + * @see self::TYPE_BOOL + * @see self::TYPE_ARRAY + * @see self::TYPE_STRING_LIST + * @see self::TYPE_INT_LIST + * @see self::TYPE_STRING + * @since 28.0.0 + */ + public function getType(): string { + return $this->type; + } + + /** + * @param string $type value type + * + * @inheritDoc + * @return bool + * @see self::TYPE_INT + * @see self::TYPE_FLOAT + * @see self::TYPE_BOOL + * @see self::TYPE_ARRAY + * @see self::TYPE_STRING_LIST + * @see self::TYPE_INT_LIST + * @see self::TYPE_STRING + * @since 28.0.0 + */ + public function isType(string $type): bool { + return (strtolower($type) === strtolower($this->type)); + } + + /** + * @param string $type value type + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if type cannot be confirmed + * @see self::TYPE_INT + * @see self::TYPE_BOOL + * @see self::TYPE_ARRAY + * @see self::TYPE_STRING_LIST + * @see self::TYPE_INT_LIST + * @see self::TYPE_STRING + * @see self::TYPE_FLOAT + * @since 28.0.0 + */ + public function assertType(string $type): self { + if (!$this->isType($type)) { + throw new FilesMetadataTypeException('type is \'' . $this->getType() . '\', expecting \'' . $type . '\''); + } + + return $this; + } + + /** + * @param string $value string to be set as value + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if wrapper was not set to store a string + * @since 28.0.0 + */ + public function setValueString(string $value): self { + $this->assertType(self::TYPE_STRING); + $this->value = $value; + + return $this; + } + + /** + * @param int $value int to be set as value + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if wrapper was not set to store an int + * @since 28.0.0 + */ + public function setValueInt(int $value): self { + $this->assertType(self::TYPE_INT); + $this->value = $value; + + return $this; + } + + /** + * @param float $value float to be set as value + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if wrapper was not set to store a float + * @since 28.0.0 + */ + public function setValueFloat(float $value): self { + $this->assertType(self::TYPE_FLOAT); + $this->value = $value; + + return $this; + } + + /** + * @param bool $value bool to be set as value + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if wrapper was not set to store a bool + * @since 28.0.0 + */ + public function setValueBool(bool $value): self { + $this->assertType(self::TYPE_BOOL); + $this->value = $value; + + + return $this; + } + + /** + * @param array $value array to be set as value + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if wrapper was not set to store an array + * @since 28.0.0 + */ + public function setValueArray(array $value): self { + $this->assertType(self::TYPE_ARRAY); + $this->value = $value; + + return $this; + } + + /** + * @param string[] $value string list to be set as value + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if wrapper was not set to store a string list + * @since 28.0.0 + */ + public function setValueStringList(array $value): self { + $this->assertType(self::TYPE_STRING_LIST); + // TODO confirm value is an array or string ? + $this->value = $value; + + return $this; + } + + /** + * @param int[] $value int list to be set as value + * + * @inheritDoc + * @return self + * @throws FilesMetadataTypeException if wrapper was not set to store an int list + * @since 28.0.0 + */ + public function setValueIntList(array $value): self { + $this->assertType(self::TYPE_INT_LIST); + // TODO confirm value is an array of int ? + $this->value = $value; + + return $this; + } + + + /** + * @inheritDoc + * @return string set value + * @throws FilesMetadataTypeException if wrapper was not set to store a string + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueString(): string { + $this->assertType(self::TYPE_STRING); + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return (string)$this->value; + } + + /** + * @inheritDoc + * @return int set value + * @throws FilesMetadataTypeException if wrapper was not set to store an int + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueInt(): int { + $this->assertType(self::TYPE_INT); + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return (int)$this->value; + } + + /** + * @inheritDoc + * @return float set value + * @throws FilesMetadataTypeException if wrapper was not set to store a float + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueFloat(): float { + $this->assertType(self::TYPE_FLOAT); + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return (float)$this->value; + } + + /** + * @inheritDoc + * @return bool set value + * @throws FilesMetadataTypeException if wrapper was not set to store a bool + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueBool(): bool { + $this->assertType(self::TYPE_BOOL); + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return (bool)$this->value; + } + + /** + * @inheritDoc + * @return array set value + * @throws FilesMetadataTypeException if wrapper was not set to store an array + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueArray(): array { + $this->assertType(self::TYPE_ARRAY); + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return (array)$this->value; + } + + /** + * @inheritDoc + * @return string[] set value + * @throws FilesMetadataTypeException if wrapper was not set to store a string list + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueStringList(): array { + $this->assertType(self::TYPE_STRING_LIST); + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return (array)$this->value; + } + + /** + * @inheritDoc + * @return int[] set value + * @throws FilesMetadataTypeException if wrapper was not set to store an int list + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueIntList(): array { + $this->assertType(self::TYPE_INT_LIST); + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return (array)$this->value; + } + + /** + * @inheritDoc + * @return string|int|float|bool|array|string[]|int[] set value + * @throws FilesMetadataNotFoundException if value is not set + * @since 28.0.0 + */ + public function getValueAny(): mixed { + if (null === $this->value) { + throw new FilesMetadataNotFoundException('value is not set'); + } + + return $this->value; + } + + /** + * @param bool $indexed TRUE to set the stored value as an indexed value + * + * @inheritDoc + * @return self + * @since 28.0.0 + */ + public function setIndexed(bool $indexed): self { + $this->indexed = $indexed; + + return $this; + } + + /** + * @inheritDoc + * @return bool TRUE if value is an indexed value + * @since 28.0.0 + */ + public function isIndexed(): bool { + return $this->indexed; + } + + /** + * @param int $permission edit permission + * + * @inheritDoc + * @return self + * @since 28.0.0 + */ + public function setEditPermission(int $permission): self { + $this->editPermission = $permission; + + return $this; + } + + /** + * @inheritDoc + * @return int edit permission + * @since 28.0.0 + */ + public function getEditPermission(): int { + return $this->editPermission; + } + + /** + * @param array $data serialized version of the object + * + * @inheritDoc + * @return self + * @see jsonSerialize + * @since 28.0.0 + */ + public function import(array $data): self { + $this->value = $data['value'] ?? null; + $this->type = $data['type'] ?? ''; + $this->setIndexed($data['indexed'] ?? false); + $this->setEditPermission($data['editPermission'] ?? self::EDIT_FORBIDDEN); + return $this; + } + + public function jsonSerialize(bool $emptyValues = false): array { + return [ + 'value' => ($emptyValues) ? null : $this->value, + 'type' => $this->getType(), + 'indexed' => $this->isIndexed(), + 'editPermission' => $this->getEditPermission() + ]; + } +} diff --git a/lib/private/FilesMetadata/Service/IndexRequestService.php b/lib/private/FilesMetadata/Service/IndexRequestService.php new file mode 100644 index 00000000000..2a23e2c9a67 --- /dev/null +++ b/lib/private/FilesMetadata/Service/IndexRequestService.php @@ -0,0 +1,195 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata\Service; + +use OCP\DB\Exception as DbException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\Exceptions\FilesMetadataTypeException; +use OCP\FilesMetadata\Model\IFilesMetadata; +use OCP\FilesMetadata\Model\IMetadataValueWrapper; +use OCP\IDBConnection; +use Psr\Log\LoggerInterface; + +/** + * manage sql request to the metadata_index table + */ +class IndexRequestService { + public const TABLE_METADATA_INDEX = 'files_metadata_index'; + + public function __construct( + private IDBConnection $dbConnection, + private LoggerInterface $logger + ) { + } + + /** + * update the index for a specific metadata key + * + * @param IFilesMetadata $filesMetadata metadata + * @param string $key metadata key to update + * + * @throws DbException + */ + public function updateIndex(IFilesMetadata $filesMetadata, string $key): void { + $fileId = $filesMetadata->getFileId(); + try { + $metadataType = $filesMetadata->getType($key); + } catch (FilesMetadataNotFoundException $e) { + return; + } + + /** + * might look harsh, but a lot simpler than comparing current indexed data, as we can expect + * conflict with a change of types. + * We assume that each time one random metadata were modified we can drop all index for this + * key and recreate them. + * To make it slightly cleaner, we'll use transaction + */ + $this->dbConnection->beginTransaction(); + try { + $this->dropIndex($fileId, $key); + match ($metadataType) { + IMetadataValueWrapper::TYPE_STRING => $this->insertIndexString($fileId, $key, $filesMetadata->getString($key)), + IMetadataValueWrapper::TYPE_INT => $this->insertIndexInt($fileId, $key, $filesMetadata->getInt($key)), + IMetadataValueWrapper::TYPE_BOOL => $this->insertIndexBool($fileId, $key, $filesMetadata->getBool($key)), + IMetadataValueWrapper::TYPE_STRING_LIST => $this->insertIndexStringList($fileId, $key, $filesMetadata->getStringList($key)), + IMetadataValueWrapper::TYPE_INT_LIST => $this->insertIndexIntList($fileId, $key, $filesMetadata->getIntList($key)) + }; + } catch (FilesMetadataNotFoundException|FilesMetadataTypeException|DbException $e) { + $this->dbConnection->rollBack(); + $this->logger->warning('issue while updateIndex', ['exception' => $e, 'fileId' => $fileId, 'key' => $key]); + } + + $this->dbConnection->commit(); + } + + /** + * insert a new entry in the metadata_index table for a string value + * + * @param int $fileId file id + * @param string $key metadata key + * @param string $value metadata value + * + * @throws DbException + */ + private function insertIndexString(int $fileId, string $key, string $value): void { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_METADATA_INDEX) + ->setValue('meta_key', $qb->createNamedParameter($key)) + ->setValue('meta_value_string', $qb->createNamedParameter($value)) + ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); + $qb->executeStatement(); + } + + /** + * insert a new entry in the metadata_index table for an int value + * + * @param int $fileId file id + * @param string $key metadata key + * @param int $value metadata value + * + * @throws DbException + */ + public function insertIndexInt(int $fileId, string $key, int $value): void { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_METADATA_INDEX) + ->setValue('meta_key', $qb->createNamedParameter($key)) + ->setValue('meta_value_int', $qb->createNamedParameter($value, IQueryBuilder::PARAM_INT)) + ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); + $qb->executeStatement(); + } + + /** + * insert a new entry in the metadata_index table for a bool value + * + * @param int $fileId file id + * @param string $key metadata key + * @param bool $value metadata value + * + * @throws DbException + */ + public function insertIndexBool(int $fileId, string $key, bool $value): void { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_METADATA_INDEX) + ->setValue('meta_key', $qb->createNamedParameter($key)) + ->setValue('meta_value_int', $qb->createNamedParameter(($value) ? '1' : '0', IQueryBuilder::PARAM_INT)) + ->setValue('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)); + $qb->executeStatement(); + } + + /** + * insert entries in the metadata_index table for list of string + * + * @param int $fileId file id + * @param string $key metadata key + * @param string[] $values metadata values + * + * @throws DbException + */ + public function insertIndexStringList(int $fileId, string $key, array $values): void { + foreach ($values as $value) { + $this->insertIndexString($fileId, $key, $value); + } + } + + /** + * insert entries in the metadata_index table for list of int + * + * @param int $fileId file id + * @param string $key metadata key + * @param int[] $values metadata values + * + * @throws DbException + */ + public function insertIndexIntList(int $fileId, string $key, array $values): void { + foreach ($values as $value) { + $this->insertIndexInt($fileId, $key, $value); + } + } + + /** + * drop indexes related to a file id + * if a key is specified, only drop entries related to it + * + * @param int $fileId file id + * @param string $key metadata key + * + * @throws DbException + */ + public function dropIndex(int $fileId, string $key = ''): void { + $qb = $this->dbConnection->getQueryBuilder(); + $expr = $qb->expr(); + $qb->delete(self::TABLE_METADATA_INDEX) + ->where($expr->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + + if ($key !== '') { + $qb->andWhere($expr->eq('meta_key', $qb->createNamedParameter($key))); + } + + $qb->executeStatement(); + } +} diff --git a/lib/private/FilesMetadata/Service/MetadataRequestService.php b/lib/private/FilesMetadata/Service/MetadataRequestService.php new file mode 100644 index 00000000000..cdce624d75c --- /dev/null +++ b/lib/private/FilesMetadata/Service/MetadataRequestService.php @@ -0,0 +1,194 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\FilesMetadata\Service; + +use OC\FilesMetadata\Model\FilesMetadata; +use OCP\DB\Exception; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException; +use OCP\FilesMetadata\Model\IFilesMetadata; +use OCP\IDBConnection; +use Psr\Log\LoggerInterface; + +/** + * manage sql request to the metadata table + */ +class MetadataRequestService { + public const TABLE_METADATA = 'files_metadata'; + + public function __construct( + private IDBConnection $dbConnection, + private LoggerInterface $logger + ) { + } + + /** + * store metadata into database + * + * @param IFilesMetadata $filesMetadata + * + * @throws Exception + */ + public function store(IFilesMetadata $filesMetadata): void { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_METADATA) + ->setValue('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT)) + ->setValue('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize()))) + ->setValue('sync_token', $qb->createNamedParameter($this->generateSyncToken())) + ->setValue('last_update', (string) $qb->createFunction('NOW()')); + $qb->executeStatement(); + } + + /** + * returns metadata for a file id + * + * @param int $fileId file id + * + * @return IFilesMetadata + * @throws FilesMetadataNotFoundException if no metadata are found in database + */ + public function getMetadataFromFileId(int $fileId): IFilesMetadata { + try { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('json', 'sync_token')->from(self::TABLE_METADATA); + $qb->where( + $qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)) + ); + $result = $qb->executeQuery(); + $data = $result->fetch(); + $result->closeCursor(); + } catch (Exception $e) { + $this->logger->warning( + 'exception while getMetadataFromDatabase()', ['exception' => $e, 'fileId' => $fileId] + ); + throw new FilesMetadataNotFoundException(); + } + + if ($data === false) { + throw new FilesMetadataNotFoundException(); + } + + $metadata = new FilesMetadata($fileId); + $metadata->importFromDatabase($data); + + return $metadata; + } + + /** + * returns metadata for multiple file ids + * + * If + * + * @param array $fileIds file ids + * + * @return array File ID is the array key, files without metadata are not returned in the array + * @psalm-return array<int, IFilesMetadata> + */ + public function getMetadataFromFileIds(array $fileIds): array { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('file_id', 'json', 'sync_token')->from(self::TABLE_METADATA); + $qb->where( + $qb->expr()->in('file_id', $qb->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY)) + ); + + $list = []; + $result = $qb->executeQuery(); + while ($data = $result->fetch()) { + $fileId = (int) $data['file_id']; + $metadata = new FilesMetadata($fileId); + try { + $metadata->importFromDatabase($data); + } catch (FilesMetadataNotFoundException) { + continue; + } + $list[$fileId] = $metadata; + } + $result->closeCursor(); + + return $list; + } + + /** + * drop metadata related to a file id + * + * @param int $fileId file id + * + * @return void + * @throws Exception + */ + public function dropMetadata(int $fileId): void { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_METADATA) + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + $qb->executeStatement(); + } + + /** + * update metadata in the database + * + * @param IFilesMetadata $filesMetadata metadata + * + * @return int number of affected rows + * @throws Exception + */ + public function updateMetadata(IFilesMetadata $filesMetadata): int { + $qb = $this->dbConnection->getQueryBuilder(); + $expr = $qb->expr(); + + $qb->update(self::TABLE_METADATA) + ->set('json', $qb->createNamedParameter(json_encode($filesMetadata->jsonSerialize()))) + ->set('sync_token', $qb->createNamedParameter($this->generateSyncToken())) + ->set('last_update', $qb->createFunction('NOW()')) + ->where( + $expr->andX( + $expr->eq('file_id', $qb->createNamedParameter($filesMetadata->getFileId(), IQueryBuilder::PARAM_INT)), + $expr->eq('sync_token', $qb->createNamedParameter($filesMetadata->getSyncToken())) + ) + ); + + return $qb->executeStatement(); + } + + /** + * generate a random token + * @return string + */ + private function generateSyncToken(): string { + $chars = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890'; + + $str = ''; + $max = strlen($chars); + for ($i = 0; $i < 7; $i++) { + try { + $str .= $chars[random_int(0, $max - 2)]; + } catch (\Exception $e) { + $this->logger->warning('exception during generateSyncToken', ['exception' => $e]); + } + } + + return $str; + } +} diff --git a/lib/private/Group/Database.php b/lib/private/Group/Database.php index 55792ce1dff..13837eef552 100644 --- a/lib/private/Group/Database.php +++ b/lib/private/Group/Database.php @@ -30,6 +30,7 @@ namespace OC\Group; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OC\User\LazyUser; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Group\Backend\ABackend; use OCP\Group\Backend\IAddToGroupBackend; @@ -40,13 +41,12 @@ use OCP\Group\Backend\ICreateGroupBackend; use OCP\Group\Backend\IDeleteGroupBackend; use OCP\Group\Backend\IGetDisplayNameBackend; use OCP\Group\Backend\IGroupDetailsBackend; +use OCP\Group\Backend\INamedBackend; use OCP\Group\Backend\IRemoveFromGroupBackend; use OCP\Group\Backend\ISearchableGroupBackend; use OCP\Group\Backend\ISetDisplayNameBackend; -use OCP\Group\Backend\INamedBackend; use OCP\IDBConnection; use OCP\IUserManager; -use OC\User\LazyUser; /** * Class for group management in a SQL Database (e.g. MySQL, SQLite) diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php index 441ee64604d..d8d1a73762d 100644 --- a/lib/private/Group/Group.php +++ b/lib/private/Group/Group.php @@ -35,13 +35,6 @@ namespace OC\Group; use OC\Hooks\PublicEmitter; use OC\User\LazyUser; use OCP\EventDispatcher\IEventDispatcher; -use OCP\Group\Events\BeforeGroupDeletedEvent; -use OCP\Group\Events\BeforeUserAddedEvent; -use OCP\Group\Events\BeforeUserRemovedEvent; -use OCP\Group\Events\GroupDeletedEvent; -use OCP\Group\Events\UserAddedEvent; -use OCP\Group\Events\UserRemovedEvent; -use OCP\GroupInterface; use OCP\Group\Backend\ICountDisabledInGroup; use OCP\Group\Backend\IGetDisplayNameBackend; use OCP\Group\Backend\IHideFromCollaborationBackend; @@ -49,7 +42,14 @@ use OCP\Group\Backend\INamedBackend; use OCP\Group\Backend\ISearchableGroupBackend; use OCP\Group\Backend\ISetDisplayNameBackend; use OCP\Group\Events\BeforeGroupChangedEvent; +use OCP\Group\Events\BeforeGroupDeletedEvent; +use OCP\Group\Events\BeforeUserAddedEvent; +use OCP\Group\Events\BeforeUserRemovedEvent; use OCP\Group\Events\GroupChangedEvent; +use OCP\Group\Events\GroupDeletedEvent; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; +use OCP\GroupInterface; use OCP\IGroup; use OCP\IUser; use OCP\IUserManager; @@ -85,11 +85,11 @@ class Group implements IGroup { $this->displayName = $displayName; } - public function getGID() { + public function getGID(): string { return $this->gid; } - public function getDisplayName() { + public function getDisplayName(): string { if (is_null($this->displayName)) { foreach ($this->backends as $backend) { if ($backend instanceof IGetDisplayNameBackend) { @@ -126,7 +126,7 @@ class Group implements IGroup { * * @return \OC\User\User[] */ - public function getUsers() { + public function getUsers(): array { if ($this->usersLoaded) { return $this->users; } @@ -153,7 +153,7 @@ class Group implements IGroup { * @param IUser $user * @return bool */ - public function inGroup(IUser $user) { + public function inGroup(IUser $user): bool { if (isset($this->users[$user->getUID()])) { return true; } @@ -171,7 +171,7 @@ class Group implements IGroup { * * @param IUser $user */ - public function addUser(IUser $user) { + public function addUser(IUser $user): void { if ($this->inGroup($user)) { return; } @@ -200,10 +200,8 @@ class Group implements IGroup { /** * remove a user from the group - * - * @param \OC\User\User $user */ - public function removeUser($user) { + public function removeUser(IUser $user): void { $result = false; $this->dispatcher->dispatchTyped(new BeforeUserRemovedEvent($this, $user)); if ($this->emitter) { @@ -262,7 +260,7 @@ class Group implements IGroup { * @param string $search * @return int|bool */ - public function count($search = '') { + public function count($search = ''): int|bool { $users = false; foreach ($this->backends as $backend) { if ($backend->implementsActions(\OC\Group\Backend::COUNT_USERS)) { @@ -282,7 +280,7 @@ class Group implements IGroup { * * @return int|bool */ - public function countDisabled() { + public function countDisabled(): int|bool { $users = false; foreach ($this->backends as $backend) { if ($backend instanceof ICountDisabledInGroup) { @@ -306,7 +304,7 @@ class Group implements IGroup { * @return IUser[] * @deprecated 27.0.0 Use searchUsers instead (same implementation) */ - public function searchDisplayName($search, $limit = null, $offset = null) { + public function searchDisplayName(string $search, int $limit = null, int $offset = null): array { return $this->searchUsers($search, $limit, $offset); } @@ -315,7 +313,7 @@ class Group implements IGroup { * * @return string[] */ - public function getBackendNames() { + public function getBackendNames(): array { $backends = []; foreach ($this->backends as $backend) { if ($backend instanceof INamedBackend) { @@ -329,11 +327,11 @@ class Group implements IGroup { } /** - * delete the group + * Delete the group * * @return bool */ - public function delete() { + public function delete(): bool { // Prevent users from deleting group admin if ($this->getGID() === 'admin') { return false; @@ -378,7 +376,7 @@ class Group implements IGroup { * @return bool * @since 14.0.0 */ - public function canRemoveUser() { + public function canRemoveUser(): bool { foreach ($this->backends as $backend) { if ($backend->implementsActions(GroupInterface::REMOVE_FROM_GOUP)) { return true; @@ -391,7 +389,7 @@ class Group implements IGroup { * @return bool * @since 14.0.0 */ - public function canAddUser() { + public function canAddUser(): bool { foreach ($this->backends as $backend) { if ($backend->implementsActions(GroupInterface::ADD_TO_GROUP)) { return true; diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php index 47475121ea0..eb9daebb222 100644 --- a/lib/private/Group/Manager.php +++ b/lib/private/Group/Manager.php @@ -89,9 +89,9 @@ class Manager extends PublicEmitter implements IGroupManager { private DisplayNameCache $displayNameCache; public function __construct(\OC\User\Manager $userManager, - IEventDispatcher $dispatcher, - LoggerInterface $logger, - ICacheFactory $cacheFactory) { + IEventDispatcher $dispatcher, + LoggerInterface $logger, + ICacheFactory $cacheFactory) { $this->userManager = $userManager; $this->dispatcher = $dispatcher; $this->logger = $logger; @@ -371,7 +371,7 @@ class Manager extends PublicEmitter implements IGroupManager { * @return bool if in group */ public function isInGroup($userId, $group) { - return array_search($group, $this->getUserIdGroupIds($userId)) !== false; + return in_array($group, $this->getUserIdGroupIds($userId)); } /** diff --git a/lib/private/Group/MetaData.php b/lib/private/Group/MetaData.php index a58d7e78bfc..973db134728 100644 --- a/lib/private/Group/MetaData.php +++ b/lib/private/Group/MetaData.php @@ -57,11 +57,11 @@ class MetaData { * @param bool $isAdmin whether the current users is an admin */ public function __construct( - string $user, - bool $isAdmin, - IGroupManager $groupManager, - IUserSession $userSession - ) { + string $user, + bool $isAdmin, + IGroupManager $groupManager, + IUserSession $userSession + ) { $this->user = $user; $this->isAdmin = $isAdmin; $this->groupManager = $groupManager; diff --git a/lib/private/Hooks/EmitterTrait.php b/lib/private/Hooks/EmitterTrait.php index da4e3da2bd6..fe9cba893de 100644 --- a/lib/private/Hooks/EmitterTrait.php +++ b/lib/private/Hooks/EmitterTrait.php @@ -43,7 +43,7 @@ trait EmitterTrait { if (!isset($this->listeners[$eventName])) { $this->listeners[$eventName] = []; } - if (array_search($callback, $this->listeners[$eventName], true) === false) { + if (!in_array($callback, $this->listeners[$eventName], true)) { $this->listeners[$eventName][] = $callback; } } diff --git a/lib/private/Http/CookieHelper.php b/lib/private/Http/CookieHelper.php index 720a1e9185d..eedb6e05c39 100644 --- a/lib/private/Http/CookieHelper.php +++ b/lib/private/Http/CookieHelper.php @@ -33,13 +33,13 @@ class CookieHelper { public const SAMESITE_STRICT = 2; public static function setCookie(string $name, - string $value = '', - int $maxAge = 0, - string $path = '', - string $domain = '', - bool $secure = false, - bool $httponly = false, - int $samesite = self::SAMESITE_NONE) { + string $value = '', + int $maxAge = 0, + string $path = '', + string $domain = '', + bool $secure = false, + bool $httponly = false, + int $samesite = self::SAMESITE_NONE) { $header = sprintf( 'Set-Cookie: %s=%s', $name, diff --git a/lib/private/Http/WellKnown/RequestManager.php b/lib/private/Http/WellKnown/RequestManager.php index b83ff2ada50..783b04c0f5d 100644 --- a/lib/private/Http/WellKnown/RequestManager.php +++ b/lib/private/Http/WellKnown/RequestManager.php @@ -49,8 +49,8 @@ class RequestManager { private $logger; public function __construct(Coordinator $coordinator, - IServerContainer $container, - LoggerInterface $logger) { + IServerContainer $container, + LoggerInterface $logger) { $this->coordinator = $coordinator; $this->container = $container; $this->logger = $logger; diff --git a/lib/private/Installer.php b/lib/private/Installer.php index dc81135b644..dd4f1f790e3 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -53,6 +53,7 @@ use OCP\HintException; use OCP\Http\Client\IClientService; use OCP\IConfig; use OCP\ITempManager; +use OCP\Migration\IOutput; use phpseclib\File\X509; use Psr\Log\LoggerInterface; @@ -536,7 +537,10 @@ class Installer { * working ownCloud at the end instead of an aborted update. * @return array Array of error messages (appid => Exception) */ - public static function installShippedApps($softErrors = false) { + public static function installShippedApps($softErrors = false, ?IOutput $output = null) { + if ($output instanceof IOutput) { + $output->debug('Installing shipped apps'); + } $appManager = \OC::$server->getAppManager(); $config = \OC::$server->getConfig(); $errors = []; @@ -551,7 +555,7 @@ class Installer { && $config->getAppValue($filename, 'enabled') !== 'no') { if ($softErrors) { try { - Installer::installShippedApp($filename); + Installer::installShippedApp($filename, $output); } catch (HintException $e) { if ($e->getPrevious() instanceof TableExistsException) { $errors[$filename] = $e; @@ -560,7 +564,7 @@ class Installer { throw $e; } } else { - Installer::installShippedApp($filename); + Installer::installShippedApp($filename, $output); } $config->setAppValue($filename, 'enabled', 'yes'); } @@ -578,9 +582,12 @@ class Installer { /** * install an app already placed in the app folder * @param string $app id of the app to install - * @return integer + * @return string */ - public static function installShippedApp($app) { + public static function installShippedApp($app, ?IOutput $output = null) { + if ($output instanceof IOutput) { + $output->debug('Installing ' . $app); + } //install the database $appPath = OC_App::getAppPath($app); \OC_App::registerAutoloading($app, $appPath); @@ -588,6 +595,9 @@ class Installer { $config = \OC::$server->getConfig(); $ms = new MigrationService($app, \OC::$server->get(Connection::class)); + if ($output instanceof IOutput) { + $ms->setOutput($output); + } $previousVersion = $config->getAppValue($app, 'installed_version', false); $ms->migrate('latest', !$previousVersion); @@ -598,6 +608,9 @@ class Installer { if (is_null($info)) { return false; } + if ($output instanceof IOutput) { + $output->debug('Registering tasks of ' . $app); + } \OC_App::setupBackgroundJobs($info['background-jobs']); OC_App::executeRepairSteps($app, $info['repair-steps']['install']); diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index a2ff62e4070..a5dec637bdb 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -83,12 +83,12 @@ class Checker { * @param IMimeTypeDetector $mimeTypeDetector */ public function __construct(EnvironmentHelper $environmentHelper, - FileAccessHelper $fileAccessHelper, - AppLocator $appLocator, - ?IConfig $config, - ICacheFactory $cacheFactory, - ?IAppManager $appManager, - IMimeTypeDetector $mimeTypeDetector) { + FileAccessHelper $fileAccessHelper, + AppLocator $appLocator, + ?IConfig $config, + ICacheFactory $cacheFactory, + ?IAppManager $appManager, + IMimeTypeDetector $mimeTypeDetector) { $this->environmentHelper = $environmentHelper; $this->fileAccessHelper = $fileAccessHelper; $this->appLocator = $appLocator; @@ -161,7 +161,7 @@ class Checker { * @return array Array of hashes. */ private function generateHashes(\RecursiveIteratorIterator $iterator, - string $path): array { + string $path): array { $hashes = []; $baseDirectoryLength = \strlen($path); @@ -223,8 +223,8 @@ class Checker { * @return array */ private function createSignatureData(array $hashes, - X509 $certificate, - RSA $privateKey): array { + X509 $certificate, + RSA $privateKey): array { ksort($hashes); $privateKey->setSignatureMode(RSA::SIGNATURE_PSS); @@ -249,8 +249,8 @@ class Checker { * @throws \Exception */ public function writeAppSignature($path, - X509 $certificate, - RSA $privateKey) { + X509 $certificate, + RSA $privateKey) { $appInfoDir = $path . '/appinfo'; try { $this->fileAccessHelper->assertDirectoryExists($appInfoDir); @@ -279,8 +279,8 @@ class Checker { * @throws \Exception */ public function writeCoreSignature(X509 $certificate, - RSA $rsa, - $path) { + RSA $rsa, + $path) { $coreDir = $path . '/core'; try { $this->fileAccessHelper->assertDirectoryExists($coreDir); diff --git a/lib/private/Log.php b/lib/private/Log.php index d6750491d92..9975696ff06 100644 --- a/lib/private/Log.php +++ b/lib/private/Log.php @@ -38,6 +38,8 @@ namespace OC; use Exception; use Nextcloud\LogNormalizer\Normalizer; +use OC\AppFramework\Bootstrap\Coordinator; +use OC\Log\ExceptionSerializer; use OCP\EventDispatcher\IEventDispatcher; use OCP\ILogger; use OCP\IUserSession; @@ -46,8 +48,6 @@ use OCP\Log\IDataLogger; use OCP\Log\IFileBased; use OCP\Log\IWriter; use OCP\Support\CrashReport\IRegistry; -use OC\AppFramework\Bootstrap\Coordinator; -use OC\Log\ExceptionSerializer; use Throwable; use function array_merge; use function strtr; @@ -344,7 +344,7 @@ class Log implements ILogger, IDataLogger { unset($data['app']); unset($data['level']); $data = array_merge($serializer->serializeException($exception), $data); - $data = $this->interpolateMessage($data, $context['message'] ?? '--', 'CustomMessage'); + $data = $this->interpolateMessage($data, isset($context['message']) && $context['message'] !== '' ? $context['message'] : ('Exception thrown: ' . get_class($exception)), 'CustomMessage'); array_walk($context, [$this->normalizer, 'format']); diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php index 16d6ae32f72..ab8fcea4e6a 100644 --- a/lib/private/Memcache/Factory.php +++ b/lib/private/Memcache/Factory.php @@ -32,10 +32,10 @@ namespace OC\Memcache; use OCP\Cache\CappedMemoryCache; -use OCP\Profiler\IProfiler; use OCP\ICache; use OCP\ICacheFactory; use OCP\IMemcache; +use OCP\Profiler\IProfiler; use Psr\Log\LoggerInterface; class Factory implements ICacheFactory { diff --git a/lib/private/Metadata/Capabilities.php b/lib/private/Metadata/Capabilities.php deleted file mode 100644 index 2fa0006f581..00000000000 --- a/lib/private/Metadata/Capabilities.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu> - * @license AGPL-3.0-or-later - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Metadata; - -use OCP\Capabilities\IPublicCapability; -use OCP\IConfig; - -class Capabilities implements IPublicCapability { - private IMetadataManager $manager; - private IConfig $config; - - public function __construct(IMetadataManager $manager, IConfig $config) { - $this->manager = $manager; - $this->config = $config; - } - - public function getCapabilities() { - if ($this->config->getSystemValueBool('enable_file_metadata', true)) { - return ['metadataAvailable' => $this->manager->getCapabilities()]; - } - - return []; - } -} diff --git a/lib/private/Metadata/FileEventListener.php b/lib/private/Metadata/FileEventListener.php deleted file mode 100644 index 162e85ff3aa..00000000000 --- a/lib/private/Metadata/FileEventListener.php +++ /dev/null @@ -1,110 +0,0 @@ -<?php - -declare(strict_types=1); -/** - * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu> - * @license AGPL-3.0-or-later - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Metadata; - -use OC\Files\Filesystem; -use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\IEventListener; -use OCP\Files\Events\Node\NodeDeletedEvent; -use OCP\Files\Events\Node\NodeWrittenEvent; -use OCP\Files\Events\NodeRemovedFromCache; -use OCP\Files\File; -use OCP\Files\Node; -use OCP\Files\NotFoundException; -use OCP\Files\FileInfo; -use Psr\Log\LoggerInterface; - -/** - * @template-implements IEventListener<NodeRemovedFromCache> - * @template-implements IEventListener<NodeDeletedEvent> - * @template-implements IEventListener<NodeWrittenEvent> - */ -class FileEventListener implements IEventListener { - private IMetadataManager $manager; - private LoggerInterface $logger; - - public function __construct(IMetadataManager $manager, LoggerInterface $logger) { - $this->manager = $manager; - $this->logger = $logger; - } - - private function shouldExtractMetadata(Node $node): bool { - try { - if ($node->getMimetype() === 'httpd/unix-directory') { - return false; - } - } catch (NotFoundException $e) { - return false; - } - if ($node->getSize(false) <= 0) { - return false; - } - - $path = $node->getPath(); - return $this->isCorrectPath($path); - } - - private function isCorrectPath(string $path): bool { - // TODO make this more dynamic, we have the same issue in other places - return !str_starts_with($path, 'appdata_') && !str_starts_with($path, 'files_versions/') && !str_starts_with($path, 'files_trashbin/'); - } - - public function handle(Event $event): void { - if ($event instanceof NodeRemovedFromCache) { - if (!$this->isCorrectPath($event->getPath())) { - // Don't listen to paths for which we don't extract metadata - return; - } - $view = Filesystem::getView(); - if (!$view) { - // Should not happen since a scan in the user folder should setup - // the file system. - $e = new \Exception(); // don't trigger, just get backtrace - $this->logger->error('Detecting deletion of a file with possible metadata but file system setup is not setup', [ - 'exception' => $e, - 'app' => 'metadata' - ]); - return; - } - $info = $view->getFileInfo($event->getPath()); - if ($info && $info->getType() === FileInfo::TYPE_FILE) { - $this->manager->clearMetadata($info->getId()); - } - } - - if ($event instanceof NodeDeletedEvent) { - $node = $event->getNode(); - if ($this->shouldExtractMetadata($node)) { - /** @var File $node */ - $this->manager->clearMetadata($event->getNode()->getId()); - } - } - - if ($event instanceof NodeWrittenEvent) { - $node = $event->getNode(); - if ($this->shouldExtractMetadata($node)) { - /** @var File $node */ - $this->manager->generateMetadata($event->getNode(), false); - } - } - } -} diff --git a/lib/private/Metadata/FileMetadata.php b/lib/private/Metadata/FileMetadata.php deleted file mode 100644 index a9808a86998..00000000000 --- a/lib/private/Metadata/FileMetadata.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -declare(strict_types=1); -/** - * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Metadata; - -use OCP\AppFramework\Db\Entity; -use OCP\DB\Types; - -/** - * @method string getGroupName() - * @method void setGroupName(string $groupName) - * @method string getValue() - * @method void setValue(string $value) - * @see \OC\Core\Migrations\Version240000Date20220404230027 - */ -class FileMetadata extends Entity { - protected ?string $groupName = null; - protected ?string $value = null; - - public function __construct() { - $this->addType('groupName', 'string'); - $this->addType('value', Types::STRING); - } - - public function getDecodedValue(): array { - return json_decode($this->getValue(), true) ?? []; - } - - public function setArrayAsValue(array $value): void { - $this->setValue(json_encode($value, JSON_THROW_ON_ERROR)); - } -} diff --git a/lib/private/Metadata/FileMetadataMapper.php b/lib/private/Metadata/FileMetadataMapper.php deleted file mode 100644 index 003ab13126e..00000000000 --- a/lib/private/Metadata/FileMetadataMapper.php +++ /dev/null @@ -1,177 +0,0 @@ -<?php - -declare(strict_types=1); -/** - * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu> - * @copyright Copyright 2022 Louis Chmn <louis@chmn.me> - * @license AGPL-3.0-or-later - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Metadata; - -use OCP\AppFramework\Db\DoesNotExistException; -use OCP\AppFramework\Db\MultipleObjectsReturnedException; -use OCP\AppFramework\Db\QBMapper; -use OCP\AppFramework\Db\Entity; -use OCP\DB\Exception; -use OCP\DB\QueryBuilder\IQueryBuilder; -use OCP\IDBConnection; - -/** - * @template-extends QBMapper<FileMetadata> - */ -class FileMetadataMapper extends QBMapper { - public function __construct(IDBConnection $db) { - parent::__construct($db, 'file_metadata', FileMetadata::class); - } - - /** - * @return FileMetadata[] - * @throws Exception - */ - public function findForFile(int $fileId): array { - $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from($this->getTableName()) - ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); - - return $this->findEntities($qb); - } - - /** - * @throws DoesNotExistException - * @throws MultipleObjectsReturnedException - * @throws Exception - */ - public function findForGroupForFile(int $fileId, string $groupName): FileMetadata { - $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from($this->getTableName()) - ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))) - ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, IQueryBuilder::PARAM_STR))); - - return $this->findEntity($qb); - } - - /** - * @return array<int, FileMetadata> - * @throws Exception - */ - public function findForGroupForFiles(array $fileIds, string $groupName): array { - $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from($this->getTableName()) - ->where($qb->expr()->in('id', $qb->createParameter('fileIds'))) - ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, IQueryBuilder::PARAM_STR))); - - $metadata = []; - foreach (array_chunk($fileIds, 1000) as $fileIdsChunk) { - $qb->setParameter('fileIds', $fileIdsChunk, IQueryBuilder::PARAM_INT_ARRAY); - /** @var FileMetadata[] $rawEntities */ - $rawEntities = $this->findEntities($qb); - foreach ($rawEntities as $entity) { - $metadata[$entity->getId()] = $entity; - } - } - - foreach ($fileIds as $id) { - if (isset($metadata[$id])) { - continue; - } - $empty = new FileMetadata(); - $empty->setValue(''); - $empty->setGroupName($groupName); - $empty->setId($id); - $metadata[$id] = $empty; - } - return $metadata; - } - - public function clear(int $fileId): void { - $qb = $this->db->getQueryBuilder(); - $qb->delete($this->getTableName()) - ->where($qb->expr()->eq('id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); - - $qb->executeStatement(); - } - - /** - * Updates an entry in the db from an entity - * - * @param FileMetadata $entity the entity that should be created - * @return FileMetadata the saved entity with the set id - * @throws Exception - * @throws \InvalidArgumentException if entity has no id - */ - public function update(Entity $entity): FileMetadata { - if (!($entity instanceof FileMetadata)) { - throw new \Exception("Entity should be a FileMetadata entity"); - } - - // entity needs an id - $id = $entity->getId(); - if ($id === null) { - throw new \InvalidArgumentException('Entity which should be updated has no id'); - } - - // entity needs an group_name - $groupName = $entity->getGroupName(); - if ($groupName === null) { - throw new \InvalidArgumentException('Entity which should be updated has no group_name'); - } - - $idType = $this->getParameterTypeForProperty($entity, 'id'); - $groupNameType = $this->getParameterTypeForProperty($entity, 'groupName'); - $value = $entity->getValue(); - $valueType = $this->getParameterTypeForProperty($entity, 'value'); - - $qb = $this->db->getQueryBuilder(); - - $qb->update($this->tableName) - ->set('value', $qb->createNamedParameter($value, $valueType)) - ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, $idType))) - ->andWhere($qb->expr()->eq('group_name', $qb->createNamedParameter($groupName, $groupNameType))) - ->executeStatement(); - - return $entity; - } - - /** - * Override the insertOrUpdate as we could be in a transaction in which case we can not afford on error. - * - * @param FileMetadata $entity the entity that should be created/updated - * @return FileMetadata the saved entity with the (new) id - * @throws Exception - * @throws \InvalidArgumentException if entity has no id - */ - public function insertOrUpdate(Entity $entity): FileMetadata { - try { - $existingEntity = $this->findForGroupForFile($entity->getId(), $entity->getGroupName()); - } catch (\Throwable) { - $existingEntity = null; - } - - if ($existingEntity !== null) { - if ($entity->getValue() !== $existingEntity->getValue()) { - return $this->update($entity); - } else { - return $existingEntity; - } - } else { - return parent::insertOrUpdate($entity); - } - } -} diff --git a/lib/private/Metadata/IMetadataManager.php b/lib/private/Metadata/IMetadataManager.php deleted file mode 100644 index fa0bcc22801..00000000000 --- a/lib/private/Metadata/IMetadataManager.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace OC\Metadata; - -use OCP\Files\File; - -/** - * Interface to manage additional metadata for files - */ -interface IMetadataManager { - /** - * @param class-string<IMetadataProvider> $className - */ - public function registerProvider(string $className): void; - - /** - * Generate the metadata for one file - */ - public function generateMetadata(File $file, bool $checkExisting = false): void; - - /** - * Clear the metadata for one file - */ - public function clearMetadata(int $fileId): void; - - /** @return array<int, FileMetadata> */ - public function fetchMetadataFor(string $group, array $fileIds): array; - - /** - * Get the capabilities as an array of mimetype regex to the type provided - */ - public function getCapabilities(): array; -} diff --git a/lib/private/Metadata/IMetadataProvider.php b/lib/private/Metadata/IMetadataProvider.php deleted file mode 100644 index 7cbe102a538..00000000000 --- a/lib/private/Metadata/IMetadataProvider.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php - -namespace OC\Metadata; - -use OCP\Files\File; - -/** - * Interface for the metadata providers. If you want an application to provide - * some metadata, you can use this to store them. - */ -interface IMetadataProvider { - /** - * The list of groups that this metadata provider is able to provide. - * - * @return string[] - */ - public static function groupsProvided(): array; - - /** - * Check if the metadata provider is available. A metadata provider might be - * unavailable due to a php extension not being installed. - */ - public static function isAvailable(): bool; - - /** - * Get the mimetypes supported as a regex. - */ - public static function getMimetypesSupported(): string; - - /** - * Execute the extraction on the specified file. The metadata should be - * grouped by metadata - * - * Each group should be json serializable and the string representation - * shouldn't be longer than 4000 characters. - * - * @param File $file The file to extract the metadata from - * @param array<string, FileMetadata> An array containing all the metadata fetched. - */ - public function execute(File $file): array; -} diff --git a/lib/private/Metadata/MetadataManager.php b/lib/private/Metadata/MetadataManager.php deleted file mode 100644 index 6d96ff1ab68..00000000000 --- a/lib/private/Metadata/MetadataManager.php +++ /dev/null @@ -1,100 +0,0 @@ -<?php -/** - * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu> - * @license AGPL-3.0-or-later - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Metadata; - -use OC\Metadata\Provider\ExifProvider; -use OCP\Files\File; - -class MetadataManager implements IMetadataManager { - /** @var array<string, IMetadataProvider> */ - private array $providers; - private array $providerClasses; - private FileMetadataMapper $fileMetadataMapper; - - public function __construct( - FileMetadataMapper $fileMetadataMapper - ) { - $this->providers = []; - $this->providerClasses = []; - $this->fileMetadataMapper = $fileMetadataMapper; - - // TODO move to another place, where? - $this->registerProvider(ExifProvider::class); - } - - /** - * @param class-string<IMetadataProvider> $className - */ - public function registerProvider(string $className):void { - if (in_array($className, $this->providerClasses)) { - return; - } - - if (call_user_func([$className, 'isAvailable'])) { - $this->providers[call_user_func([$className, 'getMimetypesSupported'])] = \OC::$server->get($className); - } - } - - public function generateMetadata(File $file, bool $checkExisting = false): void { - $existingMetadataGroups = []; - - if ($checkExisting) { - $existingMetadata = $this->fileMetadataMapper->findForFile($file->getId()); - foreach ($existingMetadata as $metadata) { - $existingMetadataGroups[] = $metadata->getGroupName(); - } - } - - foreach ($this->providers as $supportedMimetype => $provider) { - if (preg_match($supportedMimetype, $file->getMimeType())) { - if (count(array_diff($provider::groupsProvided(), $existingMetadataGroups)) > 0) { - $metaDataGroup = $provider->execute($file); - foreach ($metaDataGroup as $group => $metadata) { - $this->fileMetadataMapper->insertOrUpdate($metadata); - } - } - } - } - } - - public function clearMetadata(int $fileId): void { - $this->fileMetadataMapper->clear($fileId); - } - - /** - * @return array<int, FileMetadata> - */ - public function fetchMetadataFor(string $group, array $fileIds): array { - return $this->fileMetadataMapper->findForGroupForFiles($fileIds, $group); - } - - public function getCapabilities(): array { - $capabilities = []; - foreach ($this->providers as $supportedMimetype => $provider) { - foreach ($provider::groupsProvided() as $group) { - if (isset($capabilities[$group])) { - $capabilities[$group][] = $supportedMimetype; - } - $capabilities[$group] = [$supportedMimetype]; - } - } - return $capabilities; - } -} diff --git a/lib/private/Metadata/Provider/ExifProvider.php b/lib/private/Metadata/Provider/ExifProvider.php deleted file mode 100644 index b1598abbbc8..00000000000 --- a/lib/private/Metadata/Provider/ExifProvider.php +++ /dev/null @@ -1,142 +0,0 @@ -<?php - -declare(strict_types=1); -/** - * @copyright Copyright 2022 Carl Schwan <carl@carlschwan.eu> - * @copyright Copyright 2022 Louis Chmn <louis@chmn.me> - * @license AGPL-3.0-or-later - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Metadata\Provider; - -use OC\Metadata\FileMetadata; -use OC\Metadata\IMetadataProvider; -use OCP\Files\File; -use Psr\Log\LoggerInterface; - -class ExifProvider implements IMetadataProvider { - private LoggerInterface $logger; - - public function __construct( - LoggerInterface $logger - ) { - $this->logger = $logger; - } - - public static function groupsProvided(): array { - return ['size', 'gps']; - } - - public static function isAvailable(): bool { - return extension_loaded('exif'); - } - - /** @return array{'gps'?: FileMetadata, 'size'?: FileMetadata} */ - public function execute(File $file): array { - $exifData = []; - $fileDescriptor = $file->fopen('rb'); - - if ($fileDescriptor === false) { - return []; - } - - $data = null; - try { - // Needed to make reading exif data reliable. - // This is to trigger this condition: https://github.com/php/php-src/blob/d64aa6f646a7b5e58359dc79479860164239580a/main/streams/streams.c#L710 - // But I don't understand why 1 as a special meaning. - // Revert right after reading the exif data. - $oldBufferSize = stream_set_chunk_size($fileDescriptor, 1); - $data = @exif_read_data($fileDescriptor, 'ANY_TAG', true); - stream_set_chunk_size($fileDescriptor, $oldBufferSize); - } catch (\Exception $ex) { - $this->logger->info("Couldn't extract metadata for ".$file->getId(), ['exception' => $ex]); - } - - $size = new FileMetadata(); - $size->setGroupName('size'); - $size->setId($file->getId()); - $size->setArrayAsValue([]); - - if (!$data) { - $sizeResult = getimagesizefromstring($file->getContent()); - if ($sizeResult !== false) { - $size->setArrayAsValue([ - 'width' => $sizeResult[0], - 'height' => $sizeResult[1], - ]); - - $exifData['size'] = $size; - } - } elseif (array_key_exists('COMPUTED', $data)) { - if (array_key_exists('Width', $data['COMPUTED']) && array_key_exists('Height', $data['COMPUTED'])) { - $size->setArrayAsValue([ - 'width' => $data['COMPUTED']['Width'], - 'height' => $data['COMPUTED']['Height'], - ]); - - $exifData['size'] = $size; - } - } - - if ($data && array_key_exists('GPS', $data) - && array_key_exists('GPSLatitude', $data['GPS']) && array_key_exists('GPSLatitudeRef', $data['GPS']) - && array_key_exists('GPSLongitude', $data['GPS']) && array_key_exists('GPSLongitudeRef', $data['GPS']) - ) { - $gps = new FileMetadata(); - $gps->setGroupName('gps'); - $gps->setId($file->getId()); - $gps->setArrayAsValue([ - 'latitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLatitude'], $data['GPS']['GPSLatitudeRef']), - 'longitude' => $this->gpsDegreesToDecimal($data['GPS']['GPSLongitude'], $data['GPS']['GPSLongitudeRef']), - ]); - - $exifData['gps'] = $gps; - } - - return $exifData; - } - - public static function getMimetypesSupported(): string { - return '/image\/(png|jpeg|heif|webp|tiff)/'; - } - - /** - * @param array|string $coordinates - */ - private static function gpsDegreesToDecimal($coordinates, ?string $hemisphere): float { - if (is_string($coordinates)) { - $coordinates = array_map("trim", explode(",", $coordinates)); - } - - if (count($coordinates) !== 3) { - throw new \Exception('Invalid coordinate format: ' . json_encode($coordinates)); - } - - [$degrees, $minutes, $seconds] = array_map(function (string $rawDegree) { - $parts = explode('/', $rawDegree); - - if ($parts[1] === '0') { - return 0; - } - - return floatval($parts[0]) / floatval($parts[1] ?? 1); - }, $coordinates); - - $sign = ($hemisphere === 'W' || $hemisphere === 'S') ? -1 : 1; - return $sign * ($degrees + $minutes / 60 + $seconds / 3600); - } -} diff --git a/lib/private/Migration/BackgroundRepair.php b/lib/private/Migration/BackgroundRepair.php index 579ba494e58..dd1b15c7492 100644 --- a/lib/private/Migration/BackgroundRepair.php +++ b/lib/private/Migration/BackgroundRepair.php @@ -26,13 +26,13 @@ */ namespace OC\Migration; +use OC\NeedsUpdateException; +use OC\Repair; +use OC_App; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\BackgroundJob\TimedJob; use OCP\EventDispatcher\IEventDispatcher; -use OC\NeedsUpdateException; -use OC\Repair; -use OC_App; use Psr\Log\LoggerInterface; /** @@ -41,15 +41,13 @@ use Psr\Log\LoggerInterface; * @package OC\Migration */ class BackgroundRepair extends TimedJob { - private IJobList $jobList; - private LoggerInterface $logger; - private IEventDispatcher $dispatcher; - - public function __construct(IEventDispatcher $dispatcher, ITimeFactory $time, LoggerInterface $logger, IJobList $jobList) { + public function __construct( + private IEventDispatcher $dispatcher, + ITimeFactory $time, + private LoggerInterface $logger, + private IJobList $jobList, + ) { parent::__construct($time); - $this->dispatcher = $dispatcher; - $this->logger = $logger; - $this->jobList = $jobList; $this->setInterval(15 * 60); } @@ -58,7 +56,7 @@ class BackgroundRepair extends TimedJob { * @throws \Exception * @throws \OC\NeedsUpdateException */ - protected function run($argument) { + protected function run($argument): void { if (!isset($argument['app']) || !isset($argument['step'])) { // remove the job - we can never execute it $this->jobList->remove($this, $this->argument); @@ -101,7 +99,7 @@ class BackgroundRepair extends TimedJob { * @param $app * @throws NeedsUpdateException */ - protected function loadApp($app) { + protected function loadApp($app): void { OC_App::loadApp($app); } } diff --git a/lib/private/Migration/ConsoleOutput.php b/lib/private/Migration/ConsoleOutput.php index 9e3396f2a75..841e3d302fc 100644 --- a/lib/private/Migration/ConsoleOutput.php +++ b/lib/private/Migration/ConsoleOutput.php @@ -34,34 +34,35 @@ use Symfony\Component\Console\Output\OutputInterface; * @package OC\Migration */ class ConsoleOutput implements IOutput { - /** @var OutputInterface */ - private $output; + private ?ProgressBar $progressBar = null; - /** @var ProgressBar */ - private $progressBar; + public function __construct( + private OutputInterface $output, + ) { + } - public function __construct(OutputInterface $output) { - $this->output = $output; + public function debug(string $message): void { + $this->output->writeln($message, OutputInterface::VERBOSITY_VERBOSE); } /** * @param string $message */ - public function info($message) { + public function info($message): void { $this->output->writeln("<info>$message</info>"); } /** * @param string $message */ - public function warning($message) { + public function warning($message): void { $this->output->writeln("<comment>$message</comment>"); } /** * @param int $max */ - public function startProgress($max = 0) { + public function startProgress($max = 0): void { if (!is_null($this->progressBar)) { $this->progressBar->finish(); } @@ -73,7 +74,7 @@ class ConsoleOutput implements IOutput { * @param int $step * @param string $description */ - public function advance($step = 1, $description = '') { + public function advance($step = 1, $description = ''): void { if (is_null($this->progressBar)) { $this->progressBar = new ProgressBar($this->output); $this->progressBar->start(); @@ -84,7 +85,7 @@ class ConsoleOutput implements IOutput { } } - public function finishProgress() { + public function finishProgress(): void { if (is_null($this->progressBar)) { return; } diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php index f97bcb767f8..f1b06d008bb 100644 --- a/lib/private/Migration/SimpleOutput.php +++ b/lib/private/Migration/SimpleOutput.php @@ -33,19 +33,21 @@ use Psr\Log\LoggerInterface; * @package OC\Migration */ class SimpleOutput implements IOutput { - private LoggerInterface $logger; - private $appName; + public function __construct( + private LoggerInterface $logger, + private $appName, + ) { + } - public function __construct(LoggerInterface $logger, $appName) { - $this->logger = $logger; - $this->appName = $appName; + public function debug(string $message): void { + $this->logger->debug($message, ['app' => $this->appName]); } /** * @param string $message * @since 9.1.0 */ - public function info($message) { + public function info($message): void { $this->logger->info($message, ['app' => $this->appName]); } @@ -53,7 +55,7 @@ class SimpleOutput implements IOutput { * @param string $message * @since 9.1.0 */ - public function warning($message) { + public function warning($message): void { $this->logger->warning($message, ['app' => $this->appName]); } @@ -61,7 +63,7 @@ class SimpleOutput implements IOutput { * @param int $max * @since 9.1.0 */ - public function startProgress($max = 0) { + public function startProgress($max = 0): void { } /** @@ -69,12 +71,12 @@ class SimpleOutput implements IOutput { * @param string $description * @since 9.1.0 */ - public function advance($step = 1, $description = '') { + public function advance($step = 1, $description = ''): void { } /** * @since 9.1.0 */ - public function finishProgress() { + public function finishProgress(): void { } } diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php index d34ba5fed98..17573d9db86 100644 --- a/lib/private/NavigationManager.php +++ b/lib/private/NavigationManager.php @@ -65,19 +65,25 @@ class NavigationManager implements INavigationManager { private $groupManager; /** @var IConfig */ private $config; + /** The default app for the current user (cached for the `add` function) */ + private ?string $defaultApp; + /** User defined app order (cached for the `add` function) */ + private array $customAppOrder; public function __construct(IAppManager $appManager, - IURLGenerator $urlGenerator, - IFactory $l10nFac, - IUserSession $userSession, - IGroupManager $groupManager, - IConfig $config) { + IURLGenerator $urlGenerator, + IFactory $l10nFac, + IUserSession $userSession, + IGroupManager $groupManager, + IConfig $config) { $this->appManager = $appManager; $this->urlGenerator = $urlGenerator; $this->l10nFac = $l10nFac; $this->userSession = $userSession; $this->groupManager = $groupManager; $this->config = $config; + + $this->defaultApp = null; } /** @@ -89,7 +95,10 @@ class NavigationManager implements INavigationManager { return; } + $id = $entry['id']; + $entry['active'] = false; + $entry['unread'] = $this->unreadCounters[$id] ?? 0; if (!isset($entry['icon'])) { $entry['icon'] = ''; } @@ -100,8 +109,17 @@ class NavigationManager implements INavigationManager { $entry['type'] = 'link'; } - $id = $entry['id']; - $entry['unread'] = $this->unreadCounters[$id] ?? 0; + if ($entry['type'] === 'link') { + // app might not be set when using closures, in this case try to fallback to ID + if (!isset($entry['app']) && $this->appManager->isEnabledForUser($id)) { + $entry['app'] = $id; + } + + // This is the default app that will always be shown first + $entry['default'] = ($entry['app'] ?? false) === $this->defaultApp; + // Set order from user defined app order + $entry['order'] = $this->customAppOrder[$id]['order'] ?? $entry['order'] ?? 100; + } $this->entries[$id] = $entry; } @@ -218,7 +236,25 @@ class NavigationManager implements INavigationManager { ]); } + if ($this->appManager === 'null') { + return; + } + + $this->defaultApp = $this->appManager->getDefaultAppForUser($this->userSession->getUser(), false); + if ($this->userSession->isLoggedIn()) { + // Profile + $this->add([ + 'type' => 'settings', + 'id' => 'profile', + 'order' => 1, + 'href' => $this->urlGenerator->linkToRoute( + 'core.ProfilePage.index', + ['targetUserId' => $this->userSession->getUser()->getUID()], + ), + 'name' => $l->t('View profile'), + ]); + // Accessibility settings if ($this->appManager->isEnabledForUser('theming', $this->userSession->getUser())) { $this->add([ @@ -230,6 +266,7 @@ class NavigationManager implements INavigationManager { 'icon' => $this->urlGenerator->imagePath('theming', 'accessibility-dark.svg'), ]); } + if ($this->isAdmin()) { // App management $this->add([ @@ -298,22 +335,15 @@ class NavigationManager implements INavigationManager { } } - if ($this->appManager === 'null') { - return; - } - if ($this->userSession->isLoggedIn()) { $user = $this->userSession->getUser(); $apps = $this->appManager->getEnabledAppsForUser($user); - $customOrders = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR); + $this->customAppOrder = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR); } else { $apps = $this->appManager->getInstalledApps(); - $customOrders = []; + $this->customAppOrder = []; } - // The default app of the current user without fallbacks - $defaultApp = $this->appManager->getDefaultAppForUser($this->userSession->getUser(), false); - foreach ($apps as $app) { if (!$this->userSession->isLoggedIn() && !$this->appManager->isEnabledForUser($app, $this->userSession->getUser())) { continue; @@ -339,7 +369,7 @@ class NavigationManager implements INavigationManager { } $l = $this->l10nFac->get($app); $id = $nav['id'] ?? $app . ($key === 0 ? '' : $key); - $order = $customOrders[$app][$key] ?? $nav['order'] ?? 100; + $order = $nav['order'] ?? 100; $type = $nav['type']; $route = !empty($nav['route']) ? $this->urlGenerator->linkToRoute($nav['route']) : ''; $icon = $nav['icon'] ?? 'app.svg'; @@ -369,12 +399,8 @@ class NavigationManager implements INavigationManager { // Localized name of the navigation entry 'name' => $l->t($nav['name']), ], $type === 'link' ? [ - // This is the default app that will always be shown first - 'default' => $defaultApp === $id, // App that registered this navigation entry (not necessarly the same as the id) 'app' => $app, - // The key used to identify this entry in the navigations entries - 'key' => $key, ] : [] )); } diff --git a/lib/private/Net/HostnameClassifier.php b/lib/private/Net/HostnameClassifier.php index 626aa47083e..42dae790152 100644 --- a/lib/private/Net/HostnameClassifier.php +++ b/lib/private/Net/HostnameClassifier.php @@ -52,10 +52,6 @@ class HostnameClassifier { * Check host identifier for local hostname * * IP addresses are not considered local. Use the IpAddressClassifier for those. - * - * @param string $hostname - * - * @return bool */ public function isLocalHostname(string $hostname): bool { // Disallow local network top-level domains from RFC 6762 diff --git a/lib/private/Net/IpAddressClassifier.php b/lib/private/Net/IpAddressClassifier.php index d4698864ec8..b012ca8e956 100644 --- a/lib/private/Net/IpAddressClassifier.php +++ b/lib/private/Net/IpAddressClassifier.php @@ -46,10 +46,6 @@ class IpAddressClassifier { * Check host identifier for local IPv4 and IPv6 address ranges * * Hostnames are not considered local. Use the HostnameClassifier for those. - * - * @param string $ip - * - * @return bool */ public function isLocalAddress(string $ip): bool { $parsedIp = Factory::parseAddressString( diff --git a/lib/private/Notification/Manager.php b/lib/private/Notification/Manager.php index 3d77f643d93..e81b6c4fa35 100644 --- a/lib/private/Notification/Manager.php +++ b/lib/private/Notification/Manager.php @@ -74,11 +74,11 @@ class Manager implements IManager { private $parsedRegistrationContext; public function __construct(IValidator $validator, - IUserManager $userManager, - ICacheFactory $cacheFactory, - IRegistry $subscription, - LoggerInterface $logger, - Coordinator $coordinator) { + IUserManager $userManager, + ICacheFactory $cacheFactory, + IRegistry $subscription, + LoggerInterface $logger, + Coordinator $coordinator) { $this->validator = $validator; $this->userManager = $userManager; $this->cache = $cacheFactory->createDistributed('notifications'); diff --git a/lib/private/OCS/DiscoveryService.php b/lib/private/OCS/DiscoveryService.php index 8f98ff7d5ae..f53d39465e8 100644 --- a/lib/private/OCS/DiscoveryService.php +++ b/lib/private/OCS/DiscoveryService.php @@ -47,7 +47,7 @@ class DiscoveryService implements IDiscoveryService { * @param IClientService $clientService */ public function __construct(ICacheFactory $cacheFactory, - IClientService $clientService + IClientService $clientService ) { $this->cache = $cacheFactory->createDistributed('ocs-discovery'); $this->client = $clientService->newClient(); diff --git a/lib/private/OCS/Provider.php b/lib/private/OCS/Provider.php index 5e7a86a1341..83219c018f8 100644 --- a/lib/private/OCS/Provider.php +++ b/lib/private/OCS/Provider.php @@ -34,8 +34,8 @@ class Provider extends \OCP\AppFramework\Controller { * @param \OCP\App\IAppManager $appManager */ public function __construct($appName, - \OCP\IRequest $request, - \OCP\App\IAppManager $appManager) { + \OCP\IRequest $request, + \OCP\App\IAppManager $appManager) { parent::__construct($appName, $request); $this->appManager = $appManager; } diff --git a/lib/private/Preview/BackgroundCleanupJob.php b/lib/private/Preview/BackgroundCleanupJob.php index 4eba96d1a82..4376cc06eca 100644 --- a/lib/private/Preview/BackgroundCleanupJob.php +++ b/lib/private/Preview/BackgroundCleanupJob.php @@ -48,10 +48,10 @@ class BackgroundCleanupJob extends TimedJob { private $mimeTypeLoader; public function __construct(ITimeFactory $timeFactory, - IDBConnection $connection, - Root $previewFolder, - IMimeTypeLoader $mimeTypeLoader, - bool $isCLI) { + IDBConnection $connection, + Root $previewFolder, + IMimeTypeLoader $mimeTypeLoader, + bool $isCLI) { parent::__construct($timeFactory); // Run at most once an hour $this->setInterval(3600); diff --git a/lib/private/Preview/EMF.php b/lib/private/Preview/EMF.php new file mode 100644 index 00000000000..2b5f40e66af --- /dev/null +++ b/lib/private/Preview/EMF.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Daniel Kesselberg <mail@danielkesselberg.de> + * + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Preview; + +class EMF extends Office { + public function getMimeType(): string { + return '/image\/emf/'; + } +} diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php index 4a1270fa4a6..695d4a3357f 100644 --- a/lib/private/Preview/Generator.php +++ b/lib/private/Preview/Generator.php @@ -221,7 +221,7 @@ class Generator { * * @param int $semId * @param int $concurrency - * @return false|resource the semaphore on success or false on failure + * @return false|\SysvSemaphore the semaphore on success or false on failure */ public static function guardWithSemaphore(int $semId, int $concurrency) { if (!extension_loaded('sysvsem')) { @@ -240,11 +240,11 @@ class Generator { /** * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}. * - * @param resource|bool $semId the semaphore identifier returned by guardWithSemaphore + * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore * @return bool */ - public static function unguardWithSemaphore($semId): bool { - if (!is_resource($semId) || !extension_loaded('sysvsem')) { + public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool { + if ($semId === false || !($semId instanceof \SysvSemaphore)) { return false; } return sem_release($semId); @@ -257,9 +257,15 @@ class Generator { */ public static function getHardwareConcurrency(): int { static $width; + if (!isset($width)) { - if (is_file("/proc/cpuinfo")) { - $width = substr_count(file_get_contents("/proc/cpuinfo"), "processor"); + if (function_exists('ini_get')) { + $openBasedir = ini_get('open_basedir'); + if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) { + $width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0; + } else { + $width = 0; + } } else { $width = 0; } diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php index 7184f7e9e76..faf84696e17 100644 --- a/lib/private/Preview/Imaginary.php +++ b/lib/private/Preview/Imaginary.php @@ -23,13 +23,13 @@ namespace OC\Preview; +use OC\StreamImage; use OCP\Files\File; use OCP\Http\Client\IClientService; use OCP\IConfig; use OCP\IImage; -use OCP\Image; -use OC\StreamImage; +use OCP\Image; use Psr\Log\LoggerInterface; class Imaginary extends ProviderV2 { @@ -78,6 +78,9 @@ class Imaginary extends ProviderV2 { // Object store $stream = $file->fopen('r'); + if (!$stream || !is_resource($stream) || feof($stream)) { + return null; + } $httpClient = $this->service->newClient(); @@ -165,7 +168,7 @@ class Imaginary extends ProviderV2 { 'timeout' => 120, 'connect_timeout' => 3, ]); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->logger->info('Imaginary preview generation failed: ' . $e->getMessage(), [ 'exception' => $e, ]); diff --git a/lib/private/Preview/Office.php b/lib/private/Preview/Office.php index 3ba7c5a21a0..68499a6fea6 100644 --- a/lib/private/Preview/Office.php +++ b/lib/private/Preview/Office.php @@ -31,7 +31,8 @@ namespace OC\Preview; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\IImage; -use Psr\Log\LoggerInterface; +use OCP\ITempManager; +use OCP\Server; abstract class Office extends ProviderV2 { /** @@ -49,51 +50,60 @@ abstract class Office extends ProviderV2 { return null; } - $absPath = $this->getLocalFile($file); - - $tmpDir = \OC::$server->getTempManager()->getTempBaseDir(); + $tempManager = Server::get(ITempManager::class); - $defaultParameters = ' -env:UserInstallation=file://' . escapeshellarg($tmpDir . '/owncloud-' . \OC_Util::getInstanceId() . '/') . ' --headless --nologo --nofirststartwizard --invisible --norestore --convert-to png --outdir '; - $clParameters = \OC::$server->getConfig()->getSystemValue('preview_office_cl_parameters', $defaultParameters); + // The file to generate the preview for. + $absPath = $this->getLocalFile($file); - $cmd = $this->options['officeBinary'] . $clParameters . escapeshellarg($tmpDir) . ' ' . escapeshellarg($absPath); + // The destination for the LibreOffice user profile. + // LibreOffice can rune once per user profile and therefore instance id and file id are included. + $profile = $tempManager->getTemporaryFolder( + 'nextcloud-office-profile-' . \OC_Util::getInstanceId() . '-' . $file->getId() + ); - exec($cmd, $output, $returnCode); + // The destination for the LibreOffice convert result. + $outdir = $tempManager->getTemporaryFolder( + 'nextcloud-office-preview-' . \OC_Util::getInstanceId() . '-' . $file->getId() + ); - if ($returnCode !== 0) { + if ($profile === false || $outdir === false) { $this->cleanTmpFiles(); return null; } - //create imagick object from png - $pngPreview = null; - try { - [$dirname, , , $filename] = array_values(pathinfo($absPath)); - $pngPreview = $tmpDir . '/' . $filename . '.png'; + $parameters = [ + $this->options['officeBinary'], + '-env:UserInstallation=file://' . escapeshellarg($profile), + '--headless', + '--nologo', + '--nofirststartwizard', + '--invisible', + '--norestore', + '--convert-to png', + '--outdir ' . escapeshellarg($outdir), + escapeshellarg($absPath), + ]; - $png = new \Imagick($pngPreview . '[0]'); - $png->setImageFormat('jpg'); - } catch (\Exception $e) { + $cmd = implode(' ', $parameters); + exec($cmd, $output, $returnCode); + + if ($returnCode !== 0) { $this->cleanTmpFiles(); - unlink($pngPreview); - \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ - 'exception' => $e, - 'app' => 'core', - ]); return null; } + $preview = $outdir . pathinfo($absPath, PATHINFO_FILENAME) . '.png'; + $image = new \OCP\Image(); - $image->loadFromData((string) $png); + $image->loadFromFile($preview); $this->cleanTmpFiles(); - unlink($pngPreview); if ($image->valid()) { $image->scaleDownToFit($maxX, $maxY); - return $image; } + return null; } } diff --git a/lib/private/Preview/WatcherConnector.php b/lib/private/Preview/WatcherConnector.php index ffbdf825211..b11a6ab86da 100644 --- a/lib/private/Preview/WatcherConnector.php +++ b/lib/private/Preview/WatcherConnector.php @@ -43,7 +43,7 @@ class WatcherConnector { * @param SystemConfig $config */ public function __construct(IRootFolder $root, - SystemConfig $config) { + SystemConfig $config) { $this->root = $root; $this->config = $config; } diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index 3af6848686e..aedcbbce335 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -366,7 +366,7 @@ class PreviewManager implements IPreview { $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/'); $this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes()); - // SVG, Office and Bitmap require imagick + // SVG and Bitmap require imagick if ($this->imagickSupport->hasExtension()) { $imagickProviders = [ 'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class], @@ -391,27 +391,10 @@ class PreviewManager implements IPreview { $this->registerCoreProvider($class, $provider['mimetype']); } } - - if ($this->imagickSupport->supportsFormat('PDF')) { - // Office requires openoffice or libreoffice - $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', null); - if (!is_string($officeBinary)) { - $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice'); - } - if (!is_string($officeBinary)) { - $officeBinary = $this->binaryFinder->findBinaryPath('openoffice'); - } - - if (is_string($officeBinary)) { - $this->registerCoreProvider(Preview\MSOfficeDoc::class, '/application\/msword/', ["officeBinary" => $officeBinary]); - $this->registerCoreProvider(Preview\MSOffice2003::class, '/application\/vnd.ms-.*/', ["officeBinary" => $officeBinary]); - $this->registerCoreProvider(Preview\MSOffice2007::class, '/application\/vnd.openxmlformats-officedocument.*/', ["officeBinary" => $officeBinary]); - $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/', ["officeBinary" => $officeBinary]); - $this->registerCoreProvider(Preview\StarOffice::class, '/application\/vnd.sun.xml.*/', ["officeBinary" => $officeBinary]); - } - } } + $this->registerCoreProvidersOffice(); + // Video requires avconv or ffmpeg if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) { $movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null); @@ -429,6 +412,43 @@ class PreviewManager implements IPreview { } } + private function registerCoreProvidersOffice(): void { + $officeProviders = [ + ['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class], + ['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class], + ['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class], + ['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class], + ['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class], + ['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class], + ]; + + $findBinary = true; + $officeBinary = false; + + foreach ($officeProviders as $provider) { + $class = $provider['class']; + if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) { + continue; + } + + if ($findBinary) { + // Office requires openoffice or libreoffice + $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false); + if ($officeBinary === false) { + $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice'); + } + if ($officeBinary === false) { + $officeBinary = $this->binaryFinder->findBinaryPath('openoffice'); + } + $findBinary = false; + } + + if ($officeBinary) { + $this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]); + } + } + } + private function registerBootstrapProviders(): void { $context = $this->bootstrapCoordinator->getRegistrationContext(); diff --git a/lib/private/Profile/Actions/FediverseAction.php b/lib/private/Profile/Actions/FediverseAction.php index f96d2c07de4..4c73f785dd0 100644 --- a/lib/private/Profile/Actions/FediverseAction.php +++ b/lib/private/Profile/Actions/FediverseAction.php @@ -26,12 +26,12 @@ declare(strict_types=1); namespace OC\Profile\Actions; -use function substr; use OCP\Accounts\IAccountManager; use OCP\IURLGenerator; use OCP\IUser; use OCP\L10N\IFactory; use OCP\Profile\ILinkAction; +use function substr; class FediverseAction implements ILinkAction { private string $value = ''; diff --git a/lib/private/Profile/Actions/TwitterAction.php b/lib/private/Profile/Actions/TwitterAction.php index d63c2d3ee08..f7f57d4c6d1 100644 --- a/lib/private/Profile/Actions/TwitterAction.php +++ b/lib/private/Profile/Actions/TwitterAction.php @@ -26,12 +26,12 @@ declare(strict_types=1); namespace OC\Profile\Actions; -use function substr; use OCP\Accounts\IAccountManager; use OCP\IURLGenerator; use OCP\IUser; use OCP\L10N\IFactory; use OCP\Profile\ILinkAction; +use function substr; class TwitterAction implements ILinkAction { private string $value = ''; diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php index 39c51ea0e77..c8fb780bbe8 100644 --- a/lib/private/Profile/ProfileManager.php +++ b/lib/private/Profile/ProfileManager.php @@ -26,29 +26,29 @@ declare(strict_types=1); namespace OC\Profile; -use OCP\Profile\IProfileManager; -use function array_flip; -use function usort; use OC\AppFramework\Bootstrap\Coordinator; use OC\Core\Db\ProfileConfig; use OC\Core\Db\ProfileConfigMapper; use OC\KnownUser\KnownUserService; use OC\Profile\Actions\EmailAction; +use OC\Profile\Actions\FediverseAction; use OC\Profile\Actions\PhoneAction; use OC\Profile\Actions\TwitterAction; -use OC\Profile\Actions\FediverseAction; use OC\Profile\Actions\WebsiteAction; use OCP\Accounts\IAccountManager; use OCP\Accounts\PropertyDoesNotExistException; use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\Cache\CappedMemoryCache; use OCP\IConfig; use OCP\IUser; use OCP\L10N\IFactory; use OCP\Profile\ILinkAction; -use OCP\Cache\CappedMemoryCache; +use OCP\Profile\IProfileManager; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use function array_flip; +use function usort; class ProfileManager implements IProfileManager { /** @var ILinkAction[] */ diff --git a/lib/private/Profiler/Profiler.php b/lib/private/Profiler/Profiler.php index 40050b7bf43..d6749c55e3c 100644 --- a/lib/private/Profiler/Profiler.php +++ b/lib/private/Profiler/Profiler.php @@ -27,11 +27,11 @@ declare(strict_types = 1); namespace OC\Profiler; use OC\AppFramework\Http\Request; +use OC\SystemConfig; use OCP\AppFramework\Http\Response; use OCP\DataCollector\IDataCollector; -use OCP\Profiler\IProfiler; use OCP\Profiler\IProfile; -use OC\SystemConfig; +use OCP\Profiler\IProfiler; class Profiler implements IProfiler { /** @var array<string, IDataCollector> */ @@ -95,7 +95,7 @@ class Profiler implements IProfiler { * @return array[] */ public function find(?string $url, ?int $limit, ?string $method, ?int $start, ?int $end, - string $statusCode = null): array { + string $statusCode = null): array { if ($this->storage) { return $this->storage->find($url, $limit, $method, $start, $end, $statusCode); } else { diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 5c68c106993..21caed3e39f 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -34,19 +34,14 @@ */ namespace OC; -use OC\Repair\AddRemoveOldTasksBackgroundJob; -use OC\Repair\CleanUpAbandonedApps; -use OCP\AppFramework\QueryException; -use OCP\AppFramework\Utility\ITimeFactory; -use OCP\Collaboration\Resources\IManager; -use OCP\EventDispatcher\IEventDispatcher; -use OCP\Migration\IOutput; -use OCP\Migration\IRepairStep; use OC\DB\Connection; use OC\DB\ConnectionAdapter; use OC\Repair\AddBruteForceCleanupJob; use OC\Repair\AddCleanupUpdaterBackupsJob; +use OC\Repair\AddMetadataGenerationJob; +use OC\Repair\AddRemoveOldTasksBackgroundJob; use OC\Repair\CleanTags; +use OC\Repair\CleanUpAbandonedApps; use OC\Repair\ClearFrontendCaches; use OC\Repair\ClearGeneratedAvatarCache; use OC\Repair\Collation; @@ -85,6 +80,12 @@ use OC\Repair\RepairDavShares; use OC\Repair\RepairInvalidShares; use OC\Repair\RepairMimeTypes; use OC\Template\JSCombiner; +use OCP\AppFramework\QueryException; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Collaboration\Resources\IManager; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; use Psr\Log\LoggerInterface; use Throwable; @@ -211,6 +212,7 @@ class Repair implements IOutput { \OCP\Server::get(CleanUpAbandonedApps::class), \OCP\Server::get(AddMissingSecretJob::class), \OCP\Server::get(AddRemoveOldTasksBackgroundJob::class), + \OCP\Server::get(AddMetadataGenerationJob::class), ]; } @@ -246,6 +248,9 @@ class Repair implements IOutput { return $steps; } + public function debug(string $message): void { + } + /** * @param string $message */ diff --git a/lib/private/Repair/AddMetadataGenerationJob.php b/lib/private/Repair/AddMetadataGenerationJob.php new file mode 100644 index 00000000000..72e5df03bbd --- /dev/null +++ b/lib/private/Repair/AddMetadataGenerationJob.php @@ -0,0 +1,43 @@ +<?php +/** + * @copyright Copyright (c) 2023 Louis Chmn <louis@chmn.me> + * + * @author Louis Chmn <louis@chmn.me> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Repair; + +use OC\Core\BackgroundJobs\GenerateMetadataJob; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddMetadataGenerationJob implements IRepairStep { + public function __construct( + private IJobList $jobList, + ) { + } + + public function getName() { + return 'Queue a job to generate metadata'; + } + + public function run(IOutput $output) { + $this->jobList->add(GenerateMetadataJob::class); + } +} diff --git a/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php b/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php index 94ae39f2183..00badbb726d 100644 --- a/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php +++ b/lib/private/Repair/AddRemoveOldTasksBackgroundJob.php @@ -25,7 +25,8 @@ declare(strict_types=1); */ namespace OC\Repair; -use OC\TextProcessing\RemoveOldTasksBackgroundJob; +use OC\TextProcessing\RemoveOldTasksBackgroundJob as RemoveOldTextProcessingTasksBackgroundJob; +use OC\TextToImage\RemoveOldTasksBackgroundJob as RemoveOldTextToImageTasksBackgroundJob; use OCP\BackgroundJob\IJobList; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; @@ -38,10 +39,11 @@ class AddRemoveOldTasksBackgroundJob implements IRepairStep { } public function getName(): string { - return 'Add language model tasks cleanup job'; + return 'Add AI tasks cleanup job'; } public function run(IOutput $output) { - $this->jobList->add(RemoveOldTasksBackgroundJob::class); + $this->jobList->add(RemoveOldTextProcessingTasksBackgroundJob::class); + $this->jobList->add(RemoveOldTextToImageTasksBackgroundJob::class); } } diff --git a/lib/private/Repair/ClearFrontendCaches.php b/lib/private/Repair/ClearFrontendCaches.php index bf94e5bfbff..3661560c5f6 100644 --- a/lib/private/Repair/ClearFrontendCaches.php +++ b/lib/private/Repair/ClearFrontendCaches.php @@ -37,7 +37,7 @@ class ClearFrontendCaches implements IRepairStep { protected $jsCombiner; public function __construct(ICacheFactory $cacheFactory, - JSCombiner $JSCombiner) { + JSCombiner $JSCombiner) { $this->cacheFactory = $cacheFactory; $this->jsCombiner = $JSCombiner; } diff --git a/lib/private/Repair/ClearGeneratedAvatarCache.php b/lib/private/Repair/ClearGeneratedAvatarCache.php index fb3b42847dc..88b2b07ead5 100644 --- a/lib/private/Repair/ClearGeneratedAvatarCache.php +++ b/lib/private/Repair/ClearGeneratedAvatarCache.php @@ -25,8 +25,8 @@ namespace OC\Repair; use OC\Avatar\AvatarManager; -use OCP\IConfig; use OCP\BackgroundJob\IJobList; +use OCP\IConfig; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; diff --git a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php index e8513e7a933..5caa74638e5 100644 --- a/lib/private/Repair/ClearGeneratedAvatarCacheJob.php +++ b/lib/private/Repair/ClearGeneratedAvatarCacheJob.php @@ -20,9 +20,9 @@ */ namespace OC\Repair; -use OCP\BackgroundJob\QueuedJob; -use OCP\AppFramework\Utility\ITimeFactory; use OC\Avatar\AvatarManager; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\QueuedJob; class ClearGeneratedAvatarCacheJob extends QueuedJob { protected AvatarManager $avatarManager; diff --git a/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php b/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php index d5ae1d7ab63..185ff3be1be 100644 --- a/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php +++ b/lib/private/Repair/NC18/ResetGeneratedAvatarFlag.php @@ -37,7 +37,7 @@ class ResetGeneratedAvatarFlag implements IRepairStep { private $connection; public function __construct(IConfig $config, - IDBConnection $connection) { + IDBConnection $connection) { $this->config = $config; $this->connection = $connection; } diff --git a/lib/private/Repair/NC20/EncryptionLegacyCipher.php b/lib/private/Repair/NC20/EncryptionLegacyCipher.php index a7d008e87be..42a8778662b 100644 --- a/lib/private/Repair/NC20/EncryptionLegacyCipher.php +++ b/lib/private/Repair/NC20/EncryptionLegacyCipher.php @@ -38,7 +38,7 @@ class EncryptionLegacyCipher implements IRepairStep { private $manager; public function __construct(IConfig $config, - IManager $manager) { + IManager $manager) { $this->config = $config; $this->manager = $manager; } diff --git a/lib/private/Repair/NC20/EncryptionMigration.php b/lib/private/Repair/NC20/EncryptionMigration.php index 239a62c2718..dea51b1b57e 100644 --- a/lib/private/Repair/NC20/EncryptionMigration.php +++ b/lib/private/Repair/NC20/EncryptionMigration.php @@ -38,7 +38,7 @@ class EncryptionMigration implements IRepairStep { private $manager; public function __construct(IConfig $config, - IManager $manager) { + IManager $manager) { $this->config = $config; $this->manager = $manager; } diff --git a/lib/private/Repair/NC21/ValidatePhoneNumber.php b/lib/private/Repair/NC21/ValidatePhoneNumber.php index b3534dbeae8..51120c9d139 100644 --- a/lib/private/Repair/NC21/ValidatePhoneNumber.php +++ b/lib/private/Repair/NC21/ValidatePhoneNumber.php @@ -42,8 +42,8 @@ class ValidatePhoneNumber implements IRepairStep { private $accountManager; public function __construct(IUserManager $userManager, - IAccountManager $accountManager, - IConfig $config) { + IAccountManager $accountManager, + IConfig $config) { $this->config = $config; $this->userManager = $userManager; $this->accountManager = $accountManager; diff --git a/lib/private/Repair/Owncloud/CleanPreviews.php b/lib/private/Repair/Owncloud/CleanPreviews.php index 853a94c8adc..2020ae8bfc1 100644 --- a/lib/private/Repair/Owncloud/CleanPreviews.php +++ b/lib/private/Repair/Owncloud/CleanPreviews.php @@ -47,8 +47,8 @@ class CleanPreviews implements IRepairStep { * @param IConfig $config */ public function __construct(IJobList $jobList, - IUserManager $userManager, - IConfig $config) { + IUserManager $userManager, + IConfig $config) { $this->jobList = $jobList; $this->userManager = $userManager; $this->config = $config; diff --git a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php index 7f4bbc35c17..4ba9ad083e3 100644 --- a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php +++ b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php @@ -51,10 +51,10 @@ class CleanPreviewsBackgroundJob extends QueuedJob { * CleanPreviewsBackgroundJob constructor. */ public function __construct(IRootFolder $rootFolder, - LoggerInterface $logger, - IJobList $jobList, - ITimeFactory $timeFactory, - IUserManager $userManager) { + LoggerInterface $logger, + IJobList $jobList, + ITimeFactory $timeFactory, + IUserManager $userManager) { $this->rootFolder = $rootFolder; $this->logger = $logger; $this->jobList = $jobList; diff --git a/lib/private/Repair/Owncloud/MigrateOauthTables.php b/lib/private/Repair/Owncloud/MigrateOauthTables.php index 5bf0816d8de..ae2b46e1949 100644 --- a/lib/private/Repair/Owncloud/MigrateOauthTables.php +++ b/lib/private/Repair/Owncloud/MigrateOauthTables.php @@ -20,11 +20,11 @@ */ namespace OC\Repair\Owncloud; -use OCP\Migration\IOutput; -use OCP\Migration\IRepairStep; use OC\DB\Connection; use OC\DB\SchemaWrapper; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; class MigrateOauthTables implements IRepairStep { /** @var Connection */ diff --git a/lib/private/Repair/Owncloud/MoveAvatars.php b/lib/private/Repair/Owncloud/MoveAvatars.php index 44ba9b7643b..1ec08710b3a 100644 --- a/lib/private/Repair/Owncloud/MoveAvatars.php +++ b/lib/private/Repair/Owncloud/MoveAvatars.php @@ -41,7 +41,7 @@ class MoveAvatars implements IRepairStep { * @param IConfig $config */ public function __construct(IJobList $jobList, - IConfig $config) { + IConfig $config) { $this->jobList = $jobList; $this->config = $config; } diff --git a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php index e08a0b55a9a..ae8e8bb0743 100644 --- a/lib/private/Repair/Owncloud/UpdateLanguageCodes.php +++ b/lib/private/Repair/Owncloud/UpdateLanguageCodes.php @@ -40,7 +40,7 @@ class UpdateLanguageCodes implements IRepairStep { * @param IConfig $config */ public function __construct(IDBConnection $connection, - IConfig $config) { + IConfig $config) { $this->connection = $connection; $this->config = $config; } diff --git a/lib/private/Repair/RemoveLinkShares.php b/lib/private/Repair/RemoveLinkShares.php index b45a1d83a56..3e47e3233a2 100644 --- a/lib/private/Repair/RemoveLinkShares.php +++ b/lib/private/Repair/RemoveLinkShares.php @@ -54,10 +54,10 @@ class RemoveLinkShares implements IRepairStep { private $timeFactory; public function __construct(IDBConnection $connection, - IConfig $config, - IGroupManager $groupManager, - IManager $notificationManager, - ITimeFactory $timeFactory) { + IConfig $config, + IGroupManager $groupManager, + IManager $notificationManager, + ITimeFactory $timeFactory) { $this->connection = $connection; $this->config = $config; $this->groupManager = $groupManager; diff --git a/lib/private/Repair/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php index ee5a84ad65c..4d15dda45dd 100644 --- a/lib/private/Repair/RepairMimeTypes.php +++ b/lib/private/Repair/RepairMimeTypes.php @@ -49,7 +49,7 @@ class RepairMimeTypes implements IRepairStep { protected $folderMimeTypeId; public function __construct(IConfig $config, - IDBConnection $connection) { + IDBConnection $connection) { $this->config = $config; $this->connection = $connection; } @@ -229,6 +229,22 @@ class RepairMimeTypes implements IRepairStep { return $this->updateMimetypes($updatedMimetypes); } + private function introduceEnhancedMetafileFormatType() { + $updatedMimetypes = [ + 'emf' => 'image/emf', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } + + private function introduceEmlAndMsgFormatType() { + $updatedMimetypes = [ + 'eml' => 'message/rfc822', + 'msg' => 'application/vnd.ms-outlook', + ]; + + return $this->updateMimetypes($updatedMimetypes); + } /** * Fix mime types @@ -286,5 +302,13 @@ class RepairMimeTypes implements IRepairStep { if (version_compare($ocVersionFromBeforeUpdate, '26.0.0.1', '<') && $this->introduceAsciidocType()) { $out->info('Fixed AsciiDoc mime types'); } + + if (version_compare($ocVersionFromBeforeUpdate, '28.0.0.5', '<') && $this->introduceEnhancedMetafileFormatType()) { + $out->info('Fixed Enhanced Metafile Format mime types'); + } + + if (version_compare($ocVersionFromBeforeUpdate, '29.0.0.2', '<') && $this->introduceEmlAndMsgFormatType()) { + $out->info('Fixed eml and msg mime type'); + } } } diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index 5ce6c7c5c8f..65bbf602be0 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -230,9 +230,9 @@ class Router implements IRouter { * @return \OC\Route\Route */ public function create($name, - $pattern, - array $defaults = [], - array $requirements = []) { + $pattern, + array $defaults = [], + array $requirements = []) { $route = new Route($pattern, $defaults, $requirements); $this->collection->add($name, $route); return $route; @@ -354,8 +354,8 @@ class Router implements IRouter { * @return string */ public function generate($name, - $parameters = [], - $absolute = false) { + $parameters = [], + $absolute = false) { $referenceType = UrlGenerator::ABSOLUTE_URL; if ($absolute === false) { $referenceType = UrlGenerator::ABSOLUTE_PATH; diff --git a/lib/private/Search/Filter/BooleanFilter.php b/lib/private/Search/Filter/BooleanFilter.php new file mode 100644 index 00000000000..a64bf17f31c --- /dev/null +++ b/lib/private/Search/Filter/BooleanFilter.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use InvalidArgumentException; +use OCP\Search\IFilter; + +class BooleanFilter implements IFilter { + private bool $value; + + public function __construct(string $value) { + $this->value = match ($value) { + 'true', 'yes', 'y', '1' => true, + 'false', 'no', 'n', '0', '' => false, + default => throw new InvalidArgumentException('Invalid boolean value '. $value), + }; + } + + public function get(): bool { + return $this->value; + } +} diff --git a/lib/private/Search/Filter/DateTimeFilter.php b/lib/private/Search/Filter/DateTimeFilter.php new file mode 100644 index 00000000000..79abf9ad542 --- /dev/null +++ b/lib/private/Search/Filter/DateTimeFilter.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use DateTimeImmutable; +use OCP\Search\IFilter; + +class DateTimeFilter implements IFilter { + private DateTimeImmutable $value; + + public function __construct(string $value) { + if (filter_var($value, FILTER_VALIDATE_INT)) { + $value = '@'.$value; + } + + $this->value = new DateTimeImmutable($value); + } + + public function get(): DateTimeImmutable { + return $this->value; + } +} diff --git a/lib/private/Search/Filter/FloatFilter.php b/lib/private/Search/Filter/FloatFilter.php new file mode 100644 index 00000000000..3db19ded59b --- /dev/null +++ b/lib/private/Search/Filter/FloatFilter.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use InvalidArgumentException; +use OCP\Search\IFilter; + +class FloatFilter implements IFilter { + private float $value; + + public function __construct(string $value) { + $this->value = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($this->value === false) { + throw new InvalidArgumentException('Invalid float value '. $value); + } + } + + public function get(): float { + return $this->value; + } +} diff --git a/lib/private/Search/Filter/GroupFilter.php b/lib/private/Search/Filter/GroupFilter.php new file mode 100644 index 00000000000..f0b34a360ca --- /dev/null +++ b/lib/private/Search/Filter/GroupFilter.php @@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use InvalidArgumentException; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\Search\IFilter; + +class GroupFilter implements IFilter { + private IGroup $group; + + public function __construct( + string $value, + IGroupManager $groupManager, + ) { + $group = $groupManager->get($value); + if ($group === null) { + throw new InvalidArgumentException('Group '.$value.' not found'); + } + $this->group = $group; + } + + public function get(): IGroup { + return $this->group; + } +} diff --git a/lib/private/Search/Filter/IntegerFilter.php b/lib/private/Search/Filter/IntegerFilter.php new file mode 100644 index 00000000000..b5b907b220e --- /dev/null +++ b/lib/private/Search/Filter/IntegerFilter.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use InvalidArgumentException; +use OCP\Search\IFilter; + +class IntegerFilter implements IFilter { + private int $value; + + public function __construct(string $value) { + $this->value = filter_var($value, FILTER_VALIDATE_INT); + if ($this->value === false) { + throw new InvalidArgumentException('Invalid integer value '. $value); + } + } + + public function get(): int { + return $this->value; + } +} diff --git a/lib/private/Search/Filter/StringFilter.php b/lib/private/Search/Filter/StringFilter.php new file mode 100644 index 00000000000..8f754d12051 --- /dev/null +++ b/lib/private/Search/Filter/StringFilter.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use InvalidArgumentException; +use OCP\Search\IFilter; + +class StringFilter implements IFilter { + public function __construct( + private string $value, + ) { + if ($value === '') { + throw new InvalidArgumentException('String filter can’t be empty'); + } + } + + public function get(): string { + return $this->value; + } +} diff --git a/lib/private/Search/Filter/StringsFilter.php b/lib/private/Search/Filter/StringsFilter.php new file mode 100644 index 00000000000..7a8d88768e8 --- /dev/null +++ b/lib/private/Search/Filter/StringsFilter.php @@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use InvalidArgumentException; +use OCP\Search\IFilter; + +class StringsFilter implements IFilter { + /** + * @var string[] + */ + private array $values; + + public function __construct(string ...$values) { + $this->values = array_unique(array_filter($values)); + if (empty($this->values)) { + throw new InvalidArgumentException('Strings filter can’t be empty'); + } + } + + /** + * @return string[] + */ + public function get(): array { + return $this->values; + } +} diff --git a/lib/private/Search/Filter/UserFilter.php b/lib/private/Search/Filter/UserFilter.php new file mode 100644 index 00000000000..963d5e123ac --- /dev/null +++ b/lib/private/Search/Filter/UserFilter.php @@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Search\Filter; + +use InvalidArgumentException; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Search\IFilter; + +class UserFilter implements IFilter { + private IUser $user; + + public function __construct( + string $value, + IUserManager $userManager, + ) { + $user = $userManager->get($value); + if ($user === null) { + throw new InvalidArgumentException('User '.$value.' not found'); + } + $this->user = $user; + } + + public function get(): IUser { + return $this->user; + } +} diff --git a/lib/private/Search/FilterCollection.php b/lib/private/Search/FilterCollection.php new file mode 100644 index 00000000000..15d6695dcac --- /dev/null +++ b/lib/private/Search/FilterCollection.php @@ -0,0 +1,60 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Search; + +use Generator; +use OCP\Search\IFilter; +use OCP\Search\IFilterCollection; + +/** + * Interface for search filters + * + * @since 28.0.0 + */ +class FilterCollection implements IFilterCollection { + /** + * @var IFilter[] + */ + private array $filters; + + public function __construct(IFilter ...$filters) { + $this->filters = $filters; + } + + public function has(string $name): bool { + return isset($this->filters[$name]); + } + + public function get(string $name): ?IFilter { + return $this->filters[$name] ?? null; + } + + public function getIterator(): Generator { + foreach ($this->filters as $k => $v) { + yield $k => $v; + } + } +} diff --git a/lib/private/Search/FilterFactory.php b/lib/private/Search/FilterFactory.php new file mode 100644 index 00000000000..3f4388f405c --- /dev/null +++ b/lib/private/Search/FilterFactory.php @@ -0,0 +1,60 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Search; + +use OCP\IGroupManager; +use OCP\IUserManager; +use OCP\Search\FilterDefinition; +use OCP\Search\IFilter; +use RuntimeException; + +final class FilterFactory { + private const PERSON_TYPE_SEPARATOR = '/'; + + public static function get(string $type, string|array $filter): IFilter { + return match ($type) { + FilterDefinition::TYPE_BOOL => new Filter\BooleanFilter($filter), + FilterDefinition::TYPE_DATETIME => new Filter\DateTimeFilter($filter), + FilterDefinition::TYPE_FLOAT => new Filter\FloatFilter($filter), + FilterDefinition::TYPE_INT => new Filter\IntegerFilter($filter), + FilterDefinition::TYPE_NC_GROUP => new Filter\GroupFilter($filter, \OC::$server->get(IGroupManager::class)), + FilterDefinition::TYPE_NC_USER => new Filter\UserFilter($filter, \OC::$server->get(IUserManager::class)), + FilterDefinition::TYPE_PERSON => self::getPerson($filter), + FilterDefinition::TYPE_STRING => new Filter\StringFilter($filter), + FilterDefinition::TYPE_STRINGS => new Filter\StringsFilter(... (array) $filter), + default => throw new RuntimeException('Invalid filter type '. $type), + }; + } + + private static function getPerson(string $person): IFilter { + $parts = explode(self::PERSON_TYPE_SEPARATOR, $person, 2); + + return match (count($parts)) { + 1 => self::get(FilterDefinition::TYPE_NC_USER, $person), + 2 => self::get(... $parts), + }; + } +} diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php index 4ec73ec54e9..03e84a079fe 100644 --- a/lib/private/Search/SearchComposer.php +++ b/lib/private/Search/SearchComposer.php @@ -28,14 +28,20 @@ declare(strict_types=1); namespace OC\Search; use InvalidArgumentException; -use OCP\AppFramework\QueryException; -use OCP\IServerContainer; +use OC\AppFramework\Bootstrap\Coordinator; +use OCP\IURLGenerator; use OCP\IUser; +use OCP\Search\FilterDefinition; +use OCP\Search\IFilter; +use OCP\Search\IFilteringProvider; +use OCP\Search\IInAppSearch; use OCP\Search\IProvider; use OCP\Search\ISearchQuery; use OCP\Search\SearchResult; -use OC\AppFramework\Bootstrap\Coordinator; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use RuntimeException; use function array_map; /** @@ -58,31 +64,40 @@ use function array_map; * @see IProvider::search() for the arguments of the individual search requests */ class SearchComposer { - /** @var IProvider[] */ - private $providers = []; - - /** @var Coordinator */ - private $bootstrapCoordinator; + /** + * @var array<string, array{appId: string, provider: IProvider}> + */ + private array $providers = []; - /** @var IServerContainer */ - private $container; + private array $commonFilters; + private array $customFilters = []; - private LoggerInterface $logger; + private array $handlers = []; - public function __construct(Coordinator $bootstrapCoordinator, - IServerContainer $container, - LoggerInterface $logger) { - $this->container = $container; - $this->logger = $logger; - $this->bootstrapCoordinator = $bootstrapCoordinator; + public function __construct( + private Coordinator $bootstrapCoordinator, + private ContainerInterface $container, + private IURLGenerator $urlGenerator, + private LoggerInterface $logger + ) { + $this->commonFilters = [ + IFilter::BUILTIN_TERM => new FilterDefinition(IFilter::BUILTIN_TERM, FilterDefinition::TYPE_STRING), + IFilter::BUILTIN_SINCE => new FilterDefinition(IFilter::BUILTIN_SINCE, FilterDefinition::TYPE_DATETIME), + IFilter::BUILTIN_UNTIL => new FilterDefinition(IFilter::BUILTIN_UNTIL, FilterDefinition::TYPE_DATETIME), + IFilter::BUILTIN_TITLE_ONLY => new FilterDefinition(IFilter::BUILTIN_TITLE_ONLY, FilterDefinition::TYPE_BOOL, false), + IFilter::BUILTIN_PERSON => new FilterDefinition(IFilter::BUILTIN_PERSON, FilterDefinition::TYPE_PERSON), + IFilter::BUILTIN_PLACES => new FilterDefinition(IFilter::BUILTIN_PLACES, FilterDefinition::TYPE_STRINGS, false), + IFilter::BUILTIN_PROVIDER => new FilterDefinition(IFilter::BUILTIN_PROVIDER, FilterDefinition::TYPE_STRING, false), + ]; } /** * Load all providers dynamically that were registered through `registerProvider` * + * If $targetProviderId is provided, only this provider is loaded * If a provider can't be loaded we log it but the operation continues nevertheless */ - private function loadLazyProviders(): void { + private function loadLazyProviders(?string $targetProviderId = null): void { $context = $this->bootstrapCoordinator->getRegistrationContext(); if ($context === null) { // Too early, nothing registered yet @@ -93,9 +108,20 @@ class SearchComposer { foreach ($registrations as $registration) { try { /** @var IProvider $provider */ - $provider = $this->container->query($registration->getService()); - $this->providers[$provider->getId()] = $provider; - } catch (QueryException $e) { + $provider = $this->container->get($registration->getService()); + $providerId = $provider->getId(); + if ($targetProviderId !== null && $targetProviderId !== $providerId) { + continue; + } + $this->providers[$providerId] = [ + 'appId' => $registration->getAppId(), + 'provider' => $provider, + ]; + $this->handlers[$providerId] = [$providerId]; + if ($targetProviderId !== null) { + break; + } + } catch (ContainerExceptionInterface $e) { // Log an continue. We can be fault tolerant here. $this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [ 'exception' => $e, @@ -103,6 +129,43 @@ class SearchComposer { ]); } } + + $this->loadFilters(); + } + + private function loadFilters(): void { + foreach ($this->providers as $providerId => $providerData) { + $appId = $providerData['appId']; + $provider = $providerData['provider']; + if (!$provider instanceof IFilteringProvider) { + continue; + } + + foreach ($provider->getCustomFilters() as $filter) { + $this->registerCustomFilter($filter, $providerId); + } + foreach ($provider->getAlternateIds() as $alternateId) { + $this->handlers[$alternateId][] = $providerId; + } + foreach ($provider->getSupportedFilters() as $filterName) { + if ($this->getFilterDefinition($filterName, $providerId) === null) { + throw new InvalidArgumentException('Invalid filter '. $filterName); + } + } + } + } + + private function registerCustomFilter(FilterDefinition $filter, string $providerId): void { + $name = $filter->name(); + if (isset($this->commonFilters[$name])) { + throw new InvalidArgumentException('Filter name is already used'); + } + + if (isset($this->customFilters[$providerId])) { + $this->customFilters[$providerId][$name] = $filter; + } else { + $this->customFilters[$providerId] = [$name => $filter]; + } } /** @@ -117,26 +180,146 @@ class SearchComposer { public function getProviders(string $route, array $routeParameters): array { $this->loadLazyProviders(); - $providers = array_values( - array_map(function (IProvider $provider) use ($route, $routeParameters) { + $providers = array_map( + function (array $providerData) use ($route, $routeParameters) { + $appId = $providerData['appId']; + $provider = $providerData['provider']; + $order = $provider->getOrder($route, $routeParameters); + if ($order === null) { + return; + } + $triggers = [$provider->getId()]; + if ($provider instanceof IFilteringProvider) { + $triggers += $provider->getAlternateIds(); + $filters = $provider->getSupportedFilters(); + } else { + $filters = [IFilter::BUILTIN_TERM]; + } + return [ 'id' => $provider->getId(), + 'appId' => $appId, 'name' => $provider->getName(), - 'order' => $provider->getOrder($route, $routeParameters), + 'icon' => $this->fetchIcon($appId, $provider->getId()), + 'order' => $order, + 'triggers' => $triggers, + 'filters' => $this->getFiltersType($filters, $provider->getId()), + 'inAppSearch' => $provider instanceof IInAppSearch, ]; - }, $this->providers) + }, + $this->providers, ); + $providers = array_filter($providers); + // Sort providers by order and strip associative keys usort($providers, function ($provider1, $provider2) { return $provider1['order'] <=> $provider2['order']; }); - /** - * Return an array with the IDs, but strip the associative keys - */ return $providers; } + private function fetchIcon(string $appId, string $providerId): string { + $icons = [ + [$providerId, $providerId.'.svg'], + [$providerId, 'app.svg'], + [$appId, $providerId.'.svg'], + [$appId, $appId.'.svg'], + [$appId, 'app.svg'], + ['core', 'places/default-app-icon.svg'], + ]; + if ($appId === 'settings' && $providerId === 'users') { + // Conflict: + // the file /apps/settings/users.svg is already used in black version by top right user menu + // Override icon name here + $icons = [['settings', 'users-white.svg']]; + } + foreach ($icons as $i => $icon) { + try { + return $this->urlGenerator->imagePath(... $icon); + } catch (RuntimeException $e) { + // Ignore error + } + } + + return ''; + } + + /** + * @param $filters string[] + * @return array<string, string> + */ + private function getFiltersType(array $filters, string $providerId): array { + $filterList = []; + foreach ($filters as $filter) { + $filterList[$filter] = $this->getFilterDefinition($filter, $providerId)->type(); + } + + return $filterList; + } + + private function getFilterDefinition(string $name, string $providerId): ?FilterDefinition { + if (isset($this->commonFilters[$name])) { + return $this->commonFilters[$name]; + } + if (isset($this->customFilters[$providerId][$name])) { + return $this->customFilters[$providerId][$name]; + } + + return null; + } + + /** + * @param array<string, string> $parameters + */ + public function buildFilterList(string $providerId, array $parameters): FilterCollection { + $this->loadLazyProviders($providerId); + + $list = []; + foreach ($parameters as $name => $value) { + $filter = $this->buildFilter($name, $value, $providerId); + if ($filter === null) { + continue; + } + $list[$name] = $filter; + } + + return new FilterCollection(... $list); + } + + private function buildFilter(string $name, string $value, string $providerId): ?IFilter { + $filterDefinition = $this->getFilterDefinition($name, $providerId); + if ($filterDefinition === null) { + $this->logger->debug('Unable to find {name} definition', [ + 'name' => $name, + 'value' => $value, + ]); + + return null; + } + + if (!$this->filterSupportedByProvider($filterDefinition, $providerId)) { + // FIXME Use dedicated exception and handle it + throw new UnsupportedFilter($name, $providerId); + } + + return FilterFactory::get($filterDefinition->type(), $value); + } + + private function filterSupportedByProvider(FilterDefinition $filterDefinition, string $providerId): bool { + // Non exclusive filters can be ommited by apps + if (!$filterDefinition->exclusive()) { + return true; + } + + $provider = $this->providers[$providerId]['provider']; + $supportedFilters = $provider instanceof IFilteringProvider + ? $provider->getSupportedFilters() + : [IFilter::BUILTIN_TERM]; + + return in_array($filterDefinition->name(), $supportedFilters, true); + } + /** * Query an individual search provider for results * @@ -147,15 +330,18 @@ class SearchComposer { * @return SearchResult * @throws InvalidArgumentException when the $providerId does not correspond to a registered provider */ - public function search(IUser $user, - string $providerId, - ISearchQuery $query): SearchResult { - $this->loadLazyProviders(); + public function search( + IUser $user, + string $providerId, + ISearchQuery $query, + ): SearchResult { + $this->loadLazyProviders($providerId); - $provider = $this->providers[$providerId] ?? null; + $provider = $this->providers[$providerId]['provider'] ?? null; if ($provider === null) { throw new InvalidArgumentException("Provider $providerId is unknown"); } + return $provider->search($user, $query); } } diff --git a/lib/private/Search/SearchQuery.php b/lib/private/Search/SearchQuery.php index c89446d5970..e4295c4ab76 100644 --- a/lib/private/Search/SearchQuery.php +++ b/lib/private/Search/SearchQuery.php @@ -27,89 +27,57 @@ declare(strict_types=1); */ namespace OC\Search; +use OCP\Search\IFilter; +use OCP\Search\IFilterCollection; use OCP\Search\ISearchQuery; class SearchQuery implements ISearchQuery { public const LIMIT_DEFAULT = 5; - /** @var string */ - private $term; - - /** @var int */ - private $sortOrder; - - /** @var int */ - private $limit; - - /** @var int|string|null */ - private $cursor; - - /** @var string */ - private $route; - - /** @var array */ - private $routeParameters; - /** - * @param string $term - * @param int $sortOrder - * @param int $limit - * @param int|string|null $cursor - * @param string $route - * @param array $routeParameters + * @param string[] $params Request query + * @param string[] $routeParameters */ - public function __construct(string $term, - int $sortOrder = ISearchQuery::SORT_DATE_DESC, - int $limit = self::LIMIT_DEFAULT, - $cursor = null, - string $route = '', - array $routeParameters = []) { - $this->term = $term; - $this->sortOrder = $sortOrder; - $this->limit = $limit; - $this->cursor = $cursor; - $this->route = $route; - $this->routeParameters = $routeParameters; + public function __construct( + private IFilterCollection $filters, + private int $sortOrder = ISearchQuery::SORT_DATE_DESC, + private int $limit = self::LIMIT_DEFAULT, + private int|string|null $cursor = null, + private string $route = '', + private array $routeParameters = [], + ) { } - /** - * @inheritDoc - */ public function getTerm(): string { - return $this->term; + return $this->getFilter('term')?->get() ?? ''; + } + + public function getFilter(string $name): ?IFilter { + return $this->filters->has($name) + ? $this->filters->get($name) + : null; + } + + public function getFilters(): IFilterCollection { + return $this->filters; } - /** - * @inheritDoc - */ public function getSortOrder(): int { return $this->sortOrder; } - /** - * @inheritDoc - */ public function getLimit(): int { return $this->limit; } - /** - * @inheritDoc - */ - public function getCursor() { + public function getCursor(): int|string|null { return $this->cursor; } - /** - * @inheritDoc - */ public function getRoute(): string { return $this->route; } - /** - * @inheritDoc - */ public function getRouteParameters(): array { return $this->routeParameters; } diff --git a/lib/private/Search/UnsupportedFilter.php b/lib/private/Search/UnsupportedFilter.php new file mode 100644 index 00000000000..84b6163d2fa --- /dev/null +++ b/lib/private/Search/UnsupportedFilter.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @author Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Search; + +use Exception; + +final class UnsupportedFilter extends Exception { + public function __construct(string $filerName, $providerId) { + parent::__construct('Provider '.$providerId.' doesn’t support filter '.$filerName.'.'); + } +} diff --git a/lib/private/Security/Bruteforce/Capabilities.php b/lib/private/Security/Bruteforce/Capabilities.php index b50eea0b7af..add2bb8d8b5 100644 --- a/lib/private/Security/Bruteforce/Capabilities.php +++ b/lib/private/Security/Bruteforce/Capabilities.php @@ -29,8 +29,8 @@ declare(strict_types=1); */ namespace OC\Security\Bruteforce; -use OCP\Capabilities\IPublicCapability; use OCP\Capabilities\IInitialStateExcludedCapability; +use OCP\Capabilities\IPublicCapability; use OCP\IRequest; use OCP\Security\Bruteforce\IThrottler; diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php index 5316071f25c..7e5f1daa28c 100644 --- a/lib/private/Security/Bruteforce/Throttler.php +++ b/lib/private/Security/Bruteforce/Throttler.php @@ -72,8 +72,8 @@ class Throttler implements IThrottler { * {@inheritDoc} */ public function registerAttempt(string $action, - string $ip, - array $metadata = []): void { + string $ip, + array $metadata = []): void { // No need to log if the bruteforce protection is disabled if (!$this->config->getSystemValueBool('auth.bruteforce.protection.enabled', true)) { return; diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php index eca3e2b6b29..ee525af4c2a 100644 --- a/lib/private/Security/CSP/ContentSecurityPolicy.php +++ b/lib/private/Security/CSP/ContentSecurityPolicy.php @@ -191,4 +191,12 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy public function setStrictDynamicAllowed(bool $strictDynamicAllowed): void { $this->strictDynamicAllowed = $strictDynamicAllowed; } + + public function isStrictDynamicAllowedOnScripts(): bool { + return $this->strictDynamicAllowedOnScripts; + } + + public function setStrictDynamicAllowedOnScripts(bool $strictDynamicAllowedOnScripts): void { + $this->strictDynamicAllowedOnScripts = $strictDynamicAllowedOnScripts; + } } diff --git a/lib/private/Security/Normalizer/IpAddress.php b/lib/private/Security/Normalizer/IpAddress.php index 9aade6c3591..f8e55370da7 100644 --- a/lib/private/Security/Normalizer/IpAddress.php +++ b/lib/private/Security/Normalizer/IpAddress.php @@ -38,7 +38,7 @@ namespace OC\Security\Normalizer; */ class IpAddress { /** - * @param string $ip IP to normalized + * @param string $ip IP to normalize */ public function __construct( private string $ip, @@ -46,24 +46,9 @@ class IpAddress { } /** - * Return the given subnet for an IPv4 address and mask bits + * Return the given subnet for an IPv6 address (64 first bits) */ - private function getIPv4Subnet(string $ip, int $maskBits = 32): string { - $binary = \inet_pton($ip); - for ($i = 32; $i > $maskBits; $i -= 8) { - $j = \intdiv($i, 8) - 1; - $k = \min(8, $i - $maskBits); - $mask = (0xff - ((2 ** $k) - 1)); - $int = \unpack('C', $binary[$j]); - $binary[$j] = \pack('C', $int[1] & $mask); - } - return \inet_ntop($binary).'/'.$maskBits; - } - - /** - * Return the given subnet for an IPv6 address and mask bits - */ - private function getIPv6Subnet(string $ip, int $maskBits = 48): string { + private function getIPv6Subnet(string $ip): string { if ($ip[0] === '[' && $ip[-1] === ']') { // If IP is with brackets, for example [::1] $ip = substr($ip, 1, strlen($ip) - 2); } @@ -71,15 +56,11 @@ class IpAddress { if ($pos !== false) { $ip = substr($ip, 0, $pos - 1); } + $binary = \inet_pton($ip); - for ($i = 128; $i > $maskBits; $i -= 8) { - $j = \intdiv($i, 8) - 1; - $k = \min(8, $i - $maskBits); - $mask = (0xff - ((2 ** $k) - 1)); - $int = \unpack('C', $binary[$j]); - $binary[$j] = \pack('C', $int[1] & $mask); - } - return \inet_ntop($binary).'/'.$maskBits; + $mask = inet_pton('FFFF:FFFF:FFFF:FFFF::'); + + return inet_ntop($binary & $mask).'/64'; } /** @@ -93,24 +74,13 @@ class IpAddress { if (!$binary) { return null; } - for ($i = 0; $i <= 9; $i++) { - if (unpack('C', $binary[$i])[1] !== 0) { - return null; - } - } - for ($i = 10; $i <= 11; $i++) { - if (unpack('C', $binary[$i])[1] !== 255) { - return null; - } - } - - $binary4 = ''; - for ($i = 12; $i < 16; $i++) { - $binary4 .= $binary[$i]; + $mask = inet_pton('::FFFF:FFFF'); + if (($binary & ~$mask) !== inet_pton('::FFFF:0.0.0.0')) { + return null; } - return inet_ntop($binary4); + return inet_ntop(substr($binary, -4)); } @@ -118,25 +88,16 @@ class IpAddress { * Gets either the /32 (IPv4) or the /64 (IPv6) subnet of an IP address */ public function getSubnet(): string { - if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->ip)) { - return $this->getIPv4Subnet( - $this->ip, - 32 - ); + if (filter_var($this->ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return $this->ip.'/32'; } $ipv4 = $this->getEmbeddedIpv4($this->ip); if ($ipv4 !== null) { - return $this->getIPv4Subnet( - $ipv4, - 32 - ); + return $ipv4.'/32'; } - return $this->getIPv6Subnet( - $this->ip, - 64 - ); + return $this->getIPv6Subnet($this->ip); } /** diff --git a/lib/private/Security/RemoteHostValidator.php b/lib/private/Security/RemoteHostValidator.php index 385b38cff98..9cc69594c32 100644 --- a/lib/private/Security/RemoteHostValidator.php +++ b/lib/private/Security/RemoteHostValidator.php @@ -52,6 +52,10 @@ final class RemoteHostValidator implements IRemoteHostValidator { } $host = idn_to_utf8(strtolower(urldecode($host))); + if ($host === false) { + return false; + } + // Remove brackets from IPv6 addresses if (str_starts_with($host, '[') && str_ends_with($host, ']')) { $host = substr($host, 1, -1); diff --git a/lib/private/Security/VerificationToken/CleanUpJob.php b/lib/private/Security/VerificationToken/CleanUpJob.php index 1f4af046451..9c1b27d344d 100644 --- a/lib/private/Security/VerificationToken/CleanUpJob.php +++ b/lib/private/Security/VerificationToken/CleanUpJob.php @@ -27,10 +27,10 @@ declare(strict_types=1); namespace OC\Security\VerificationToken; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\IConfig; -use OCP\IUserManager; use OCP\BackgroundJob\IJobList; use OCP\BackgroundJob\Job; +use OCP\IConfig; +use OCP\IUserManager; use OCP\Security\VerificationToken\InvalidTokenException; use OCP\Security\VerificationToken\IVerificationToken; diff --git a/lib/private/Server.php b/lib/private/Server.php index 37b7669f624..cf4262e2d50 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -102,6 +102,7 @@ use OC\Files\Storage\StorageFactory; use OC\Files\Template\TemplateManager; use OC\Files\Type\Loader; use OC\Files\View; +use OC\FilesMetadata\FilesMetadataManager; use OC\FullTextSearch\FullTextSearchManager; use OC\Http\Client\ClientService; use OC\Http\Client\NegativeDnsCache; @@ -109,8 +110,8 @@ use OC\IntegrityCheck\Checker; use OC\IntegrityCheck\Helpers\AppLocator; use OC\IntegrityCheck\Helpers\EnvironmentHelper; use OC\IntegrityCheck\Helpers\FileAccessHelper; -use OC\LDAP\NullLDAPProviderFactory; use OC\KnownUser\KnownUserService; +use OC\LDAP\NullLDAPProviderFactory; use OC\Lock\DBLockingProvider; use OC\Lock\MemcacheLockingProvider; use OC\Lock\NoopLockingProvider; @@ -120,9 +121,6 @@ use OC\Log\PsrLoggerAdapter; use OC\Mail\Mailer; use OC\Memcache\ArrayCache; use OC\Memcache\Factory; -use OC\Metadata\Capabilities as MetadataCapabilities; -use OC\Metadata\IMetadataManager; -use OC\Metadata\MetadataManager; use OC\Notification\Manager; use OC\OCM\Model\OCMProvider; use OC\OCM\OCMDiscoveryService; @@ -131,6 +129,7 @@ use OC\Preview\GeneratorHelper; use OC\Preview\IMagickSupport; use OC\Preview\MimeIconProvider; use OC\Profile\ProfileManager; +use OC\Profiler\Profiler; use OC\Remote\Api\ApiFactory; use OC\Remote\InstanceFactory; use OC\RichObjectStrings\Validator; @@ -160,10 +159,15 @@ use OC\Tagging\TagMapper; use OC\Talk\Broker; use OC\Template\JSCombiner; use OC\Translation\TranslationManager; +use OC\User\AvailabilityCoordinator; use OC\User\DisplayNameCache; use OC\User\Listeners\BeforeUserDeletedListener; use OC\User\Listeners\UserChangedListener; use OC\User\Session; +use OCA\Files_External\Service\BackendService; +use OCA\Files_External\Service\GlobalStoragesService; +use OCA\Files_External\Service\UserGlobalStoragesService; +use OCA\Files_External\Service\UserStoragesService; use OCA\Theming\ImageManager; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; @@ -196,16 +200,17 @@ use OCP\Files\Lock\ILockManager; use OCP\Files\Mount\IMountManager; use OCP\Files\Storage\IStorageFactory; use OCP\Files\Template\ITemplateManager; +use OCP\FilesMetadata\IFilesMetadataManager; use OCP\FullTextSearch\IFullTextSearchManager; use OCP\GlobalScale\IConfig; use OCP\Group\ISubAdmin; use OCP\Http\Client\IClientService; use OCP\IAppConfig; use OCP\IAvatarManager; +use OCP\IBinaryFinder; use OCP\ICache; use OCP\ICacheFactory; use OCP\ICertificateManager; -use OCP\IBinaryFinder; use OCP\IDateTimeFormatter; use OCP\IDateTimeZone; use OCP\IDBConnection; @@ -236,7 +241,9 @@ use OCP\Log\ILogFactory; use OCP\Mail\IMailer; use OCP\OCM\IOCMDiscoveryService; use OCP\OCM\IOCMProvider; +use OCP\Preview\IMimeIconProvider; use OCP\Profile\IProfileManager; +use OCP\Profiler\IProfiler; use OCP\Remote\Api\IApiFactory; use OCP\Remote\IInstanceFactory; use OCP\RichObjectStrings\IValidator; @@ -266,16 +273,10 @@ use OCP\User\Events\UserChangedEvent; use OCP\User\Events\UserLoggedInEvent; use OCP\User\Events\UserLoggedInWithCookieEvent; use OCP\User\Events\UserLoggedOutEvent; +use OCP\User\IAvailabilityCoordinator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use OCA\Files_External\Service\UserStoragesService; -use OCA\Files_External\Service\UserGlobalStoragesService; -use OCA\Files_External\Service\GlobalStoragesService; -use OCA\Files_External\Service\BackendService; -use OCP\Profiler\IProfiler; -use OC\Profiler\Profiler; -use OCP\Preview\IMimeIconProvider; /** * Class Server @@ -1134,9 +1135,6 @@ class Server extends ServerContainer implements IServerContainer { $manager->registerCapability(function () use ($c) { return $c->get(\OC\Security\Bruteforce\Capabilities::class); }); - $manager->registerCapability(function () use ($c) { - return $c->get(MetadataCapabilities::class); - }); return $manager; }); /** @deprecated 19.0.0 */ @@ -1401,6 +1399,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class); $this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class); + $this->registerAlias(IFilesMetadataManager::class, FilesMetadataManager::class); $this->registerAlias(ISubAdmin::class, SubAdmin::class); @@ -1412,8 +1411,6 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(IBroker::class, Broker::class); - $this->registerAlias(IMetadataManager::class, MetadataManager::class); - $this->registerAlias(\OCP\Files\AppData\IAppDataFactory::class, \OC\Files\AppData\Factory::class); $this->registerAlias(IBinaryFinder::class, BinaryFinder::class); @@ -1428,6 +1425,8 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(\OCP\TextProcessing\IManager::class, \OC\TextProcessing\Manager::class); + $this->registerAlias(\OCP\TextToImage\IManager::class, \OC\TextToImage\Manager::class); + $this->registerAlias(ILimiter::class, Limiter::class); $this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class); @@ -1438,6 +1437,8 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(IProfileManager::class, ProfileManager::class); + $this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class); + $this->connectDispatcher(); } @@ -1478,6 +1479,8 @@ class Server extends ServerContainer implements IServerContainer { $eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class); $eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class); $eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class); + + FilesMetadataManager::loadListeners($eventDispatcher); } /** diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php index 76a214584a6..22d2aba0405 100644 --- a/lib/private/Session/CryptoSessionData.php +++ b/lib/private/Session/CryptoSessionData.php @@ -32,6 +32,7 @@ namespace OC\Session; use OCP\ISession; use OCP\Security\ICrypto; use OCP\Session\Exceptions\SessionNotAvailableException; +use function json_decode; use function OCP\Log\logger; /** @@ -58,8 +59,8 @@ class CryptoSessionData implements \ArrayAccess, ISession { * @param string $passphrase */ public function __construct(ISession $session, - ICrypto $crypto, - string $passphrase) { + ICrypto $crypto, + string $passphrase) { $this->crypto = $crypto; $this->session = $session; $this->passphrase = $passphrase; @@ -80,19 +81,24 @@ class CryptoSessionData implements \ArrayAccess, ISession { protected function initializeSession() { $encryptedSessionData = $this->session->get(self::encryptedSessionName) ?: ''; - try { - $this->sessionValues = json_decode( - $this->crypto->decrypt($encryptedSessionData, $this->passphrase), - true, - 512, - JSON_THROW_ON_ERROR, - ); - } catch (\Exception $e) { - logger('core')->critical('Could not decrypt or decode encrypted session data', [ - 'exception' => $e, - ]); + if ($encryptedSessionData === '') { + // Nothing to decrypt $this->sessionValues = []; - $this->regenerateId(true, false); + } else { + try { + $this->sessionValues = json_decode( + $this->crypto->decrypt($encryptedSessionData, $this->passphrase), + true, + 512, + JSON_THROW_ON_ERROR, + ); + } catch (\Exception $e) { + logger('core')->critical('Could not decrypt or decode encrypted session data', [ + 'exception' => $e, + ]); + $this->sessionValues = []; + $this->regenerateId(true, false); + } } } diff --git a/lib/private/Session/CryptoWrapper.php b/lib/private/Session/CryptoWrapper.php index e98aac3b8bf..5004ebf82cf 100644 --- a/lib/private/Session/CryptoWrapper.php +++ b/lib/private/Session/CryptoWrapper.php @@ -68,9 +68,9 @@ class CryptoWrapper { * @param IRequest $request */ public function __construct(IConfig $config, - ICrypto $crypto, - ISecureRandom $random, - IRequest $request) { + ICrypto $crypto, + ISecureRandom $random, + IRequest $request) { $this->crypto = $crypto; $this->config = $config; $this->random = $random; diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index 2d44ac7d3df..839d3e5ce38 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -12,6 +12,7 @@ * @author Roeland Jago Douma <roeland@famdouma.nl> * @author sualko <klaus@jsxc.org> * @author Carl Schwan <carl@carlschwan.eu> + * @author Kate Döen <kate.doeen@nextcloud.com> * * @license GNU AGPL version 3 or any later version * @@ -90,17 +91,14 @@ class Manager implements IManager { $this->subAdmin = $subAdmin; } - /** @var array */ + /** @var array<self::SETTINGS_*, list<class-string<IIconSection>>> */ protected $sectionClasses = []; - /** @var array */ + /** @var array<self::SETTINGS_*, array<string, IIconSection>> */ protected $sections = []; /** - * @param string $type 'admin' or 'personal' - * @param string $section Class must implement OCP\Settings\IIconSection - * - * @return void + * @inheritdoc */ public function registerSection(string $type, string $section) { if (!isset($this->sectionClasses[$type])) { @@ -111,7 +109,7 @@ class Manager implements IManager { } /** - * @param string $type 'admin' or 'personal' + * @psalm-param self::SETTINGS_* $type * * @return IIconSection[] */ @@ -149,6 +147,9 @@ class Manager implements IManager { return $this->sections[$type]; } + /** + * @inheritdoc + */ public function getSection(string $type, string $sectionId): ?IIconSection { if (isset($this->sections[$type]) && isset($this->sections[$type][$sectionId])) { return $this->sections[$type][$sectionId]; @@ -163,27 +164,23 @@ class Manager implements IManager { ], true); } - /** @var array */ + /** @var array<class-string<ISettings>, self::SETTINGS_*> */ protected $settingClasses = []; - /** @var array */ + /** @var array<self::SETTINGS_*, array<string, list<ISettings>>> */ protected $settings = []; /** - * @psam-param 'admin'|'personal' $type The type of the setting. - * @param string $setting Class must implement OCP\Settings\ISettings - * @param bool $allowedDelegation - * - * @return void + * @inheritdoc */ public function registerSetting(string $type, string $setting) { $this->settingClasses[$setting] = $type; } /** - * @param string $type 'admin' or 'personal' + * @psalm-param self::SETTINGS_* $type The type of the setting. * @param string $section - * @param Closure $filter optional filter to apply on all loaded ISettings + * @param ?Closure $filter optional filter to apply on all loaded ISettings * * @return ISettings[] */ @@ -258,7 +255,7 @@ class Manager implements IManager { /** * @inheritdoc */ - public function getAdminSettings($section, bool $subAdminOnly = false): array { + public function getAdminSettings(string $section, bool $subAdminOnly = false): array { if ($subAdminOnly) { $subAdminSettingsFilter = function (ISettings $settings) { return $settings instanceof ISubAdminSettings; @@ -329,7 +326,7 @@ class Manager implements IManager { /** * @inheritdoc */ - public function getPersonalSettings($section): array { + public function getPersonalSettings(string $section): array { $settings = []; $appSettings = $this->getSettings('personal', $section); @@ -344,6 +341,9 @@ class Manager implements IManager { return $settings; } + /** + * @inheritdoc + */ public function getAllowedAdminSettings(string $section, IUser $user): array { $isAdmin = $this->groupManager->isAdmin($user->getUID()); if ($isAdmin) { @@ -375,6 +375,9 @@ class Manager implements IManager { return $settings; } + /** + * @inheritdoc + */ public function getAllAllowedAdminSettings(IUser $user): array { $this->getSettings('admin', ''); // Make sure all the settings are loaded $settings = []; diff --git a/lib/private/Setup.php b/lib/private/Setup.php index f167d19adeb..ec86a844334 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -53,13 +53,14 @@ use Exception; use InvalidArgumentException; use OC\Authentication\Token\PublicKeyTokenProvider; use OC\Authentication\Token\TokenCleanupJob; -use OC\TextProcessing\RemoveOldTasksBackgroundJob; use OC\Log\Rotate; use OC\Preview\BackgroundCleanupJob; +use OC\TextProcessing\RemoveOldTasksBackgroundJob; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Defaults; use OCP\IGroup; use OCP\IL10N; +use OCP\Migration\IOutput; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; @@ -275,7 +276,7 @@ class Setup { * @param $options * @return array */ - public function install($options) { + public function install($options, ?IOutput $output = null) { $l = $this->l10n; $error = []; @@ -349,6 +350,7 @@ class Setup { $this->config->setValues($newConfigValues); + $this->outputDebug($output, 'Configuring database'); $dbSetup->initialize($options); try { $dbSetup->setupDatabase($username); @@ -367,9 +369,11 @@ class Setup { ]; return $error; } + + $this->outputDebug($output, 'Run server migrations'); try { // apply necessary migrations - $dbSetup->runMigrations(); + $dbSetup->runMigrations($output); } catch (Exception $e) { $error[] = [ 'error' => 'Error while trying to initialise the database: ' . $e->getMessage(), @@ -379,6 +383,7 @@ class Setup { return $error; } + $this->outputDebug($output, 'Create admin user'); //create the user and group $user = null; try { @@ -407,16 +412,19 @@ class Setup { } // Install shipped apps and specified app bundles - Installer::installShippedApps(); + $this->outputDebug($output, 'Install default apps'); + Installer::installShippedApps(false, $output); // create empty file in data dir, so we can later find // out that this is indeed an ownCloud data directory + $this->outputDebug($output, 'Setup data directory'); file_put_contents($config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data') . '/.ocdata', ''); // Update .htaccess files self::updateHtaccess(); self::protectDataDirectory(); + $this->outputDebug($output, 'Install background jobs'); self::installBackgroundJobs(); //and we are done @@ -531,7 +539,7 @@ class Setup { $content .= "\n Options -MultiViews"; $content .= "\n RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]"; $content .= "\n RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]"; - $content .= "\n RewriteCond %{REQUEST_FILENAME} !\\.(css|js|mjs|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav|wasm|tflite)$"; + $content .= "\n RewriteCond %{REQUEST_FILENAME} !\\.(css|js|mjs|svg|gif|png|html|ttf|woff2?|ico|jpg|jpeg|map|webm|mp4|mp3|ogg|wav|flac|wasm|tflite)$"; $content .= "\n RewriteCond %{REQUEST_FILENAME} !/core/ajax/update\\.php"; $content .= "\n RewriteCond %{REQUEST_FILENAME} !/core/img/(favicon\\.ico|manifest\\.json)$"; $content .= "\n RewriteCond %{REQUEST_FILENAME} !/(cron|public|remote|status)\\.php"; @@ -552,6 +560,14 @@ class Setup { } if ($content !== '') { + // Never write file back if disk space should be too low + if (function_exists('disk_free_space')) { + $df = disk_free_space(\OC::$SERVERROOT); + $size = strlen($content) + 10240; + if ($df !== false && $df < (float)$size) { + throw new \Exception(\OC::$SERVERROOT . " does not have enough space for writing the htaccess file! Not writing it back!"); + } + } //suppress errors in case we don't have permissions for it return (bool)@file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent . $content . "\n"); } @@ -616,4 +632,10 @@ class Setup { public function canInstallFileExists() { return is_file(\OC::$configDir.'/CAN_INSTALL'); } + + protected function outputDebug(?IOutput $output, string $message): void { + if ($output instanceof IOutput) { + $output->debug($message); + } + } } diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php index 79f23de8ef8..6bef40338c9 100644 --- a/lib/private/Setup/AbstractDatabase.php +++ b/lib/private/Setup/AbstractDatabase.php @@ -33,6 +33,7 @@ use OC\DB\ConnectionFactory; use OC\DB\MigrationService; use OC\SystemConfig; use OCP\IL10N; +use OCP\Migration\IOutput; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; @@ -150,11 +151,11 @@ abstract class AbstractDatabase { */ abstract public function setupDatabase($username); - public function runMigrations() { + public function runMigrations(?IOutput $output = null) { if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) { return; } - $ms = new MigrationService('core', \OC::$server->get(Connection::class)); + $ms = new MigrationService('core', \OC::$server->get(Connection::class), $output); $ms->migrate('latest', true); } } diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php index 50f566728a9..0d672f324eb 100644 --- a/lib/private/Setup/MySQL.php +++ b/lib/private/Setup/MySQL.php @@ -29,10 +29,10 @@ */ namespace OC\Setup; +use Doctrine\DBAL\Platforms\MySQL80Platform; use OC\DB\ConnectionAdapter; use OC\DB\MySqlTools; use OCP\IDBConnection; -use Doctrine\DBAL\Platforms\MySQL80Platform; use OCP\Security\ISecureRandom; class MySQL extends AbstractDatabase { diff --git a/lib/private/SetupCheck/SetupCheckManager.php b/lib/private/SetupCheck/SetupCheckManager.php index f9e67772019..b8b6cfa11e7 100644 --- a/lib/private/SetupCheck/SetupCheckManager.php +++ b/lib/private/SetupCheck/SetupCheckManager.php @@ -47,9 +47,10 @@ class SetupCheckManager implements ISetupCheckManager { $setupCheckObject = Server::get($setupCheck->getService()); $this->logger->debug('Running check '.get_class($setupCheckObject)); $setupResult = $setupCheckObject->run(); + $setupResult->setName($setupCheckObject->getName()); $category = $setupCheckObject->getCategory(); $results[$category] ??= []; - $results[$category][$setupCheckObject->getName()] = $setupResult; + $results[$category][$setupCheckObject::class] = $setupResult; } return $results; } diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php index 8d14f293e5a..3aa01b3cf29 100644 --- a/lib/private/Share/Share.php +++ b/lib/private/Share/Share.php @@ -243,7 +243,7 @@ class Share extends Constants { * * defacto $parameters and $format is always the default and therefore is removed in the subsequent call */ public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE, - $parameters = null, $includeCollections = false) { + $parameters = null, $includeCollections = false) { return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE, null, -1, $includeCollections); } @@ -349,8 +349,8 @@ class Share extends Constants { * * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call */ public static function getItems($itemType, ?string $item = null, ?int $shareType = null, $shareWith = null, - $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, - $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) { + $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1, + $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) { if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') { return []; } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 4606101b7e6..31f3924f053 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -880,12 +880,12 @@ class Manager implements IManager { * @param \DateTime|null $expiration */ protected function sendMailNotification(IL10N $l, - $filename, - $link, - $initiator, - $shareWith, - \DateTime $expiration = null, - $note = '') { + $filename, + $link, + $initiator, + $shareWith, + \DateTime $expiration = null, + $note = '') { $initiatorUser = $this->userManager->get($initiator); $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator; @@ -1573,14 +1573,8 @@ class Manager implements IManager { * @return bool */ public function checkPassword(IShare $share, $password) { - $passwordProtected = $share->getShareType() !== IShare::TYPE_LINK - || $share->getShareType() !== IShare::TYPE_EMAIL - || $share->getShareType() !== IShare::TYPE_CIRCLE; - if (!$passwordProtected) { - //TODO maybe exception? - return false; - } + // if there is no password on the share object / passsword is null, there is nothing to check if ($password === null || $share->getPassword() === null) { return false; } diff --git a/lib/private/Share20/PublicShareTemplateFactory.php b/lib/private/Share20/PublicShareTemplateFactory.php index 222f327496a..0e9642c306e 100644 --- a/lib/private/Share20/PublicShareTemplateFactory.php +++ b/lib/private/Share20/PublicShareTemplateFactory.php @@ -27,9 +27,9 @@ use Exception; use OC\AppFramework\Bootstrap\Coordinator; use OCA\Files_Sharing\DefaultPublicShareTemplateProvider; use OCP\Server; -use OCP\Share\IShare; use OCP\Share\IPublicShareTemplateFactory; use OCP\Share\IPublicShareTemplateProvider; +use OCP\Share\IShare; class PublicShareTemplateFactory implements IPublicShareTemplateFactory { public function __construct( diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 0a50fa0ccfb..c80d332e9db 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -29,8 +29,8 @@ */ namespace OC\Share20; -use OCP\Files\File; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\File; use OCP\Files\FileInfo; use OCP\Files\IRootFolder; use OCP\Files\Node; diff --git a/lib/private/StreamImage.php b/lib/private/StreamImage.php index 33078310d27..e2f854bc233 100644 --- a/lib/private/StreamImage.php +++ b/lib/private/StreamImage.php @@ -23,8 +23,8 @@ namespace OC; -use OCP\IStreamImage; use OCP\IImage; +use OCP\IStreamImage; /** * Only useful when dealing with transferring streamed previews from an external diff --git a/lib/private/SubAdmin.php b/lib/private/SubAdmin.php index 54f14b8ab88..9f079d30e04 100644 --- a/lib/private/SubAdmin.php +++ b/lib/private/SubAdmin.php @@ -60,9 +60,9 @@ class SubAdmin extends PublicEmitter implements ISubAdmin { * @param IDBConnection $dbConn */ public function __construct(IUserManager $userManager, - IGroupManager $groupManager, - IDBConnection $dbConn, - IEventDispatcher $eventDispatcher) { + IGroupManager $groupManager, + IDBConnection $dbConn, + IEventDispatcher $eventDispatcher) { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->dbConn = $dbConn; diff --git a/lib/private/Support/Subscription/Registry.php b/lib/private/Support/Subscription/Registry.php index eba76ca103e..008cded5dd3 100644 --- a/lib/private/Support/Subscription/Registry.php +++ b/lib/private/Support/Subscription/Registry.php @@ -62,10 +62,10 @@ class Registry implements IRegistry { private $logger; public function __construct(IConfig $config, - IServerContainer $container, - IUserManager $userManager, - IGroupManager $groupManager, - LoggerInterface $logger) { + IServerContainer $container, + IUserManager $userManager, + IGroupManager $groupManager, + LoggerInterface $logger) { $this->config = $config; $this->container = $container; $this->userManager = $userManager; diff --git a/lib/private/SystemTag/ManagerFactory.php b/lib/private/SystemTag/ManagerFactory.php index 6670922407e..d8f7d4d772b 100644 --- a/lib/private/SystemTag/ManagerFactory.php +++ b/lib/private/SystemTag/ManagerFactory.php @@ -40,25 +40,16 @@ use OCP\SystemTag\ISystemTagObjectMapper; */ class ManagerFactory implements ISystemTagManagerFactory { /** - * Server container - * - * @var IServerContainer - */ - private $serverContainer; - - /** * Constructor for the system tag manager factory - * - * @param IServerContainer $serverContainer server container */ - public function __construct(IServerContainer $serverContainer) { - $this->serverContainer = $serverContainer; + public function __construct( + private IServerContainer $serverContainer, + ) { } /** * Creates and returns an instance of the system tag manager * - * @return ISystemTagManager * @since 9.0.0 */ public function getManager(): ISystemTagManager { @@ -73,7 +64,6 @@ class ManagerFactory implements ISystemTagManagerFactory { * Creates and returns an instance of the system tag object * mapper * - * @return ISystemTagObjectMapper * @since 9.0.0 */ public function getObjectMapper(): ISystemTagObjectMapper { diff --git a/lib/private/SystemTag/SystemTag.php b/lib/private/SystemTag/SystemTag.php index da6d4bd4b11..cd8010201d3 100644 --- a/lib/private/SystemTag/SystemTag.php +++ b/lib/private/SystemTag/SystemTag.php @@ -30,39 +30,12 @@ namespace OC\SystemTag; use OCP\SystemTag\ISystemTag; class SystemTag implements ISystemTag { - /** - * @var string - */ - private $id; - - /** - * @var string - */ - private $name; - - /** - * @var bool - */ - private $userVisible; - - /** - * @var bool - */ - private $userAssignable; - - /** - * Constructor. - * - * @param string $id tag id - * @param string $name tag name - * @param bool $userVisible whether the tag is user visible - * @param bool $userAssignable whether the tag is user assignable - */ - public function __construct(string $id, string $name, bool $userVisible, bool $userAssignable) { - $this->id = $id; - $this->name = $name; - $this->userVisible = $userVisible; - $this->userAssignable = $userAssignable; + public function __construct( + private string $id, + private string $name, + private bool $userVisible, + private bool $userAssignable, + ) { } /** @@ -97,14 +70,14 @@ class SystemTag implements ISystemTag { * {@inheritdoc} */ public function getAccessLevel(): int { - if ($this->userVisible) { - if ($this->userAssignable) { - return self::ACCESS_LEVEL_PUBLIC; - } else { - return self::ACCESS_LEVEL_RESTRICTED; - } - } else { + if (!$this->userVisible) { return self::ACCESS_LEVEL_INVISIBLE; } + + if (!$this->userAssignable) { + return self::ACCESS_LEVEL_RESTRICTED; + } + + return self::ACCESS_LEVEL_PUBLIC; } } diff --git a/lib/private/SystemTag/SystemTagManager.php b/lib/private/SystemTag/SystemTagManager.php index c52c350b6f8..67e1a7d921f 100644 --- a/lib/private/SystemTag/SystemTagManager.php +++ b/lib/private/SystemTag/SystemTagManager.php @@ -50,10 +50,8 @@ class SystemTagManager implements ISystemTagManager { /** * Prepared query for selecting tags directly - * - * @var \OCP\DB\QueryBuilder\IQueryBuilder */ - private $selectTagQuery; + private IQueryBuilder $selectTagQuery; public function __construct( protected IDBConnection $connection, @@ -219,7 +217,12 @@ class SystemTagManager implements ISystemTagManager { /** * {@inheritdoc} */ - public function updateTag(string $tagId, string $newName, bool $userVisible, bool $userAssignable) { + public function updateTag( + string $tagId, + string $newName, + bool $userVisible, + bool $userAssignable, + ): void { try { $tags = $this->getTagsByIds($tagId); } catch (TagNotFoundException $e) { @@ -271,7 +274,7 @@ class SystemTagManager implements ISystemTagManager { /** * {@inheritdoc} */ - public function deleteTags($tagIds) { + public function deleteTags($tagIds): void { if (!\is_array($tagIds)) { $tagIds = [$tagIds]; } @@ -363,14 +366,14 @@ class SystemTagManager implements ISystemTagManager { return false; } - private function createSystemTagFromRow($row) { + private function createSystemTagFromRow($row): SystemTag { return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable']); } /** * {@inheritdoc} */ - public function setTagGroups(ISystemTag $tag, array $groupIds) { + public function setTagGroups(ISystemTag $tag, array $groupIds): void { // delete relationships first $this->connection->beginTransaction(); try { diff --git a/lib/private/SystemTag/SystemTagObjectMapper.php b/lib/private/SystemTag/SystemTagObjectMapper.php index 66a21e58609..614d0274add 100644 --- a/lib/private/SystemTag/SystemTagObjectMapper.php +++ b/lib/private/SystemTag/SystemTagObjectMapper.php @@ -81,7 +81,6 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper { $result->closeCursor(); } - return $mapping; } @@ -128,7 +127,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper { /** * {@inheritdoc} */ - public function assignTags(string $objId, string $objectType, $tagIds) { + public function assignTags(string $objId, string $objectType, $tagIds): void { if (!\is_array($tagIds)) { $tagIds = [$tagIds]; } @@ -169,7 +168,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper { /** * {@inheritdoc} */ - public function unassignTags(string $objId, string $objectType, $tagIds) { + public function unassignTags(string $objId, string $objectType, $tagIds): void { if (!\is_array($tagIds)) { $tagIds = [$tagIds]; } @@ -241,7 +240,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper { * * @throws \OCP\SystemTag\TagNotFoundException if at least one tag did not exist */ - private function assertTagsExist($tagIds) { + private function assertTagsExist(array $tagIds): void { $tags = $this->tagManager->getTagsByIds($tagIds); if (\count($tags) !== \count($tagIds)) { // at least one tag missing, bail out diff --git a/lib/private/SystemTag/SystemTagsInFilesDetector.php b/lib/private/SystemTag/SystemTagsInFilesDetector.php index c9f26c58c02..044322733ea 100644 --- a/lib/private/SystemTag/SystemTagsInFilesDetector.php +++ b/lib/private/SystemTag/SystemTagsInFilesDetector.php @@ -36,7 +36,9 @@ use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchComparison; class SystemTagsInFilesDetector { - public function __construct(protected QuerySearchHelper $searchHelper) { + public function __construct( + protected QuerySearchHelper $searchHelper, + ) { } public function detectAssignedSystemTagsIn( diff --git a/lib/private/TagManager.php b/lib/private/TagManager.php index 82c4dd2188d..552113f89dc 100644 --- a/lib/private/TagManager.php +++ b/lib/private/TagManager.php @@ -27,6 +27,7 @@ namespace OC; use OC\Tagging\TagMapper; +use OCP\Db\Exception as DBException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -35,7 +36,6 @@ use OCP\ITagManager; use OCP\ITags; use OCP\IUserSession; use OCP\User\Events\UserDeletedEvent; -use OCP\Db\Exception as DBException; use Psr\Log\LoggerInterface; /** diff --git a/lib/private/Tagging/TagMapper.php b/lib/private/Tagging/TagMapper.php index 1ee9c33acf7..ab227de5f7f 100644 --- a/lib/private/Tagging/TagMapper.php +++ b/lib/private/Tagging/TagMapper.php @@ -27,8 +27,8 @@ namespace OC\Tagging; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\QBMapper; -use OCP\IDBConnection; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; /** * Mapper for Tag entity diff --git a/lib/private/Talk/Broker.php b/lib/private/Talk/Broker.php index 12e6c5fb34b..451e7822790 100644 --- a/lib/private/Talk/Broker.php +++ b/lib/private/Talk/Broker.php @@ -48,8 +48,8 @@ class Broker implements IBroker { private ?ITalkBackend $backend = null; public function __construct(Coordinator $coordinator, - IServerContainer $container, - LoggerInterface $logger) { + IServerContainer $container, + LoggerInterface $logger) { $this->coordinator = $coordinator; $this->container = $container; $this->logger = $logger; @@ -94,8 +94,8 @@ class Broker implements IBroker { } public function createConversation(string $name, - array $moderators, - IConversationOptions $options = null): IConversation { + array $moderators, + IConversationOptions $options = null): IConversation { if (!$this->hasBackend()) { throw new NoBackendException("The Talk broker has no registered backend"); } diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php index b87829360d5..fad69a76ae0 100644 --- a/lib/private/Template/JSCombiner.php +++ b/lib/private/Template/JSCombiner.php @@ -55,10 +55,10 @@ class JSCombiner { private $cacheFactory; public function __construct(IAppData $appData, - IURLGenerator $urlGenerator, - ICacheFactory $cacheFactory, - SystemConfig $config, - LoggerInterface $logger) { + IURLGenerator $urlGenerator, + ICacheFactory $cacheFactory, + SystemConfig $config, + LoggerInterface $logger) { $this->appData = $appData; $this->urlGenerator = $urlGenerator; $this->cacheFactory = $cacheFactory; diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php index 7b6d0a6a346..8cba93f1f4e 100644 --- a/lib/private/Template/JSConfigHelper.php +++ b/lib/private/Template/JSConfigHelper.php @@ -45,9 +45,9 @@ use OCP\IConfig; use OCP\IGroupManager; use OCP\IInitialStateService; use OCP\IL10N; +use OCP\ILogger; use OCP\ISession; use OCP\IURLGenerator; -use OCP\ILogger; use OCP\IUser; use OCP\User\Backend\IPasswordConfirmationBackend; use OCP\Util; @@ -69,16 +69,16 @@ class JSConfigHelper { private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true]; public function __construct(IL10N $l, - Defaults $defaults, - IAppManager $appManager, - ISession $session, - ?IUser $currentUser, - IConfig $config, - IGroupManager $groupManager, - IniGetWrapper $iniWrapper, - IURLGenerator $urlGenerator, - CapabilitiesManager $capabilitiesManager, - IInitialStateService $initialStateService) { + Defaults $defaults, + IAppManager $appManager, + ISession $session, + ?IUser $currentUser, + IConfig $config, + IGroupManager $groupManager, + IniGetWrapper $iniWrapper, + IURLGenerator $urlGenerator, + CapabilitiesManager $capabilitiesManager, + IInitialStateService $initialStateService) { $this->l = $l; $this->defaults = $defaults; $this->appManager = $appManager; @@ -179,7 +179,8 @@ class JSConfigHelper { 'sharing.maxAutocompleteResults' => max(0, $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT)), 'sharing.minSearchStringLength' => $this->config->getSystemValueInt('sharing.minSearchStringLength', 0), 'version' => implode('.', Util::getVersion()), - 'versionstring' => \OC_Util::getVersionString() + 'versionstring' => \OC_Util::getVersionString(), + 'enable_non-accessible_features' => $this->config->getSystemValueBool('enable_non-accessible_features', true), ]; $array = [ diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index b283f0b610f..68a83fa4b73 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -60,8 +60,13 @@ class JSResourceLocator extends ResourceLocator { $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'core/'.$script); $found += $this->appendScriptIfExist($this->serverroot, $script); $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.$script); - $found += $this->appendScriptIfExist($this->serverroot, 'apps/'.$script); - $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.'apps/'.$script); + + foreach (\OC::$APPSROOTS as $appRoot) { + $dirName = basename($appRoot['path']); + $rootPath = dirname($appRoot['path']); + $found += $this->appendScriptIfExist($rootPath, $dirName.'/'.$script); + $found += $this->appendScriptIfExist($this->serverroot, $theme_dir.$dirName.'/'.$script); + } if ($found) { return; diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index e2504363257..96d0ae3e517 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -47,11 +47,13 @@ use OC\Search\SearchQuery; use OC\Template\CSSResourceLocator; use OC\Template\JSConfigHelper; use OC\Template\JSResourceLocator; +use OCP\App\IAppManager; use OCP\AppFramework\Http\TemplateResponse; use OCP\Defaults; use OCP\IConfig; use OCP\IInitialStateService; use OCP\INavigationManager; +use OCP\IURLGenerator; use OCP\IUserSession; use OCP\Support\Subscription\IRegistry; use OCP\Util; @@ -106,11 +108,15 @@ class TemplateLayout extends \OC_Template { $this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry()); $this->initialState->provideInitialState('core', 'apps', $this->navigationManager->getAll()); - $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT)); - $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1)); - $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes'); - Util::addScript('core', 'unified-search', 'core'); + if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) { + $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT)); + $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1)); + $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes'); + Util::addScript('core', 'legacy-unified-search', 'core'); + } else { + Util::addScript('core', 'unified-search', 'core'); + } // Set body data-theme $this->assign('enabledThemes', []); if (\OC::$server->getAppManager()->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) { @@ -199,7 +205,21 @@ class TemplateLayout extends \OC_Template { if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) { $showSimpleSignup = false; } + + $defaultSignUpLink = 'https://nextcloud.com/signup/'; + $signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink); + if ($signUpLink !== $defaultSignUpLink) { + $showSimpleSignup = true; + } + + $appManager = \OCP\Server::get(IAppManager::class); + if ($appManager->isEnabledForUser('registration')) { + $urlGenerator = \OCP\Server::get(IURLGenerator::class); + $signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/'); + } + $this->assign('showSimpleSignUpLink', $showSimpleSignup); + $this->assign('signUpLink', $signUpLink); } else { parent::__construct('core', 'layout.base'); } diff --git a/lib/private/TextProcessing/Db/Task.php b/lib/private/TextProcessing/Db/Task.php index 9c6f16d11ae..5f362d429f3 100644 --- a/lib/private/TextProcessing/Db/Task.php +++ b/lib/private/TextProcessing/Db/Task.php @@ -45,6 +45,8 @@ use OCP\TextProcessing\Task as OCPTask; * @method string getAppId() * @method setIdentifier(string $identifier) * @method string getIdentifier() + * @method setCompletionExpectedAt(null|\DateTime $completionExpectedAt) + * @method null|\DateTime getCompletionExpectedAt() */ class Task extends Entity { protected $lastUpdated; @@ -55,16 +57,17 @@ class Task extends Entity { protected $userId; protected $appId; protected $identifier; + protected $completionExpectedAt; /** * @var string[] */ - public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier']; + public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier', 'completion_expected_at']; /** * @var string[] */ - public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier']; + public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier', 'completionExpectedAt']; public function __construct() { @@ -78,6 +81,7 @@ class Task extends Entity { $this->addType('userId', 'string'); $this->addType('appId', 'string'); $this->addType('identifier', 'string'); + $this->addType('completionExpectedAt', 'datetime'); } public function toRow(): array { @@ -98,6 +102,7 @@ class Task extends Entity { 'userId' => $task->getUserId(), 'appId' => $task->getAppId(), 'identifier' => $task->getIdentifier(), + 'completionExpectedAt' => $task->getCompletionExpectedAt(), ]); return $task; } @@ -107,6 +112,7 @@ class Task extends Entity { $task->setId($this->getId()); $task->setStatus($this->getStatus()); $task->setOutput($this->getOutput()); + $task->setCompletionExpectedAt($this->getCompletionExpectedAt()); return $task; } } diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index 47af57bf3ec..70f5f322aa5 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -27,20 +27,22 @@ namespace OC\TextProcessing; use OC\AppFramework\Bootstrap\Coordinator; use OC\TextProcessing\Db\Task as DbTask; -use OCP\IConfig; -use OCP\TextProcessing\IProviderWithId; -use OCP\TextProcessing\Task; -use OCP\TextProcessing\Task as OCPTask; use OC\TextProcessing\Db\TaskMapper; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\BackgroundJob\IJobList; use OCP\Common\Exception\NotFoundException; use OCP\DB\Exception; +use OCP\IConfig; use OCP\IServerContainer; +use OCP\PreConditionNotMetException; +use OCP\TextProcessing\Exception\TaskFailureException; use OCP\TextProcessing\IManager; use OCP\TextProcessing\IProvider; -use OCP\PreConditionNotMetException; +use OCP\TextProcessing\IProviderWithExpectedRuntime; +use OCP\TextProcessing\IProviderWithId; +use OCP\TextProcessing\Task; +use OCP\TextProcessing\Task as OCPTask; use Psr\Log\LoggerInterface; use RuntimeException; use Throwable; @@ -115,31 +117,16 @@ class Manager implements IManager { if (!$this->canHandleTask($task)) { throw new PreConditionNotMetException('No text processing provider is installed that can handle this task'); } - $providers = $this->getProviders(); - $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', ''); - if ($json !== '') { - $preferences = json_decode($json, true); - if (isset($preferences[$task->getType()])) { - // If a preference for this task type is set, move the preferred provider to the start - $provider = current(array_filter($providers, function ($provider) use ($preferences, $task) { - if ($provider instanceof IProviderWithId) { - return $provider->getId() === $preferences[$task->getType()]; - } - return $provider::class === $preferences[$task->getType()]; - })); - if ($provider !== false) { - $providers = array_filter($providers, fn ($p) => $p !== $provider); - array_unshift($providers, $provider); - } - } - } + $providers = $this->getPreferredProviders($task); foreach ($providers as $provider) { - if (!$task->canUseProvider($provider)) { - continue; - } try { $task->setStatus(OCPTask::STATUS_RUNNING); + if ($provider instanceof IProviderWithExpectedRuntime) { + $completionExpectedAt = new \DateTime('now'); + $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); + $task->setCompletionExpectedAt($completionExpectedAt); + } if ($task->getId() === null) { $taskEntity = $this->taskMapper->insert(DbTask::fromPublicTask($task)); $task->setId($taskEntity->getId()); @@ -151,31 +138,37 @@ class Manager implements IManager { $task->setStatus(OCPTask::STATUS_SUCCESSFUL); $this->taskMapper->update(DbTask::fromPublicTask($task)); return $output; - } catch (\RuntimeException $e) { - $this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]); - $task->setStatus(OCPTask::STATUS_FAILED); - $this->taskMapper->update(DbTask::fromPublicTask($task)); - throw $e; } catch (\Throwable $e) { $this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]); $task->setStatus(OCPTask::STATUS_FAILED); $this->taskMapper->update(DbTask::fromPublicTask($task)); - throw new RuntimeException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e); + throw new TaskFailureException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e); } } - throw new RuntimeException('Could not run task'); + $task->setStatus(OCPTask::STATUS_FAILED); + $this->taskMapper->update(DbTask::fromPublicTask($task)); + throw new TaskFailureException('Could not run task'); } /** * @inheritDoc - * @throws Exception */ public function scheduleTask(OCPTask $task): void { if (!$this->canHandleTask($task)) { throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); } $task->setStatus(OCPTask::STATUS_SCHEDULED); + $providers = $this->getPreferredProviders($task); + if (count($providers) === 0) { + throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); + } + [$provider,] = $providers; + if ($provider instanceof IProviderWithExpectedRuntime) { + $completionExpectedAt = new \DateTime('now'); + $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); + $task->setCompletionExpectedAt($completionExpectedAt); + } $taskEntity = DbTask::fromPublicTask($task); $this->taskMapper->insert($taskEntity); $task->setId($taskEntity->getId()); @@ -187,6 +180,25 @@ class Manager implements IManager { /** * @inheritDoc */ + public function runOrScheduleTask(OCPTask $task): bool { + if (!$this->canHandleTask($task)) { + throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); + } + [$provider,] = $this->getPreferredProviders($task); + $maxExecutionTime = (int) ini_get('max_execution_time'); + // Offload the task to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time + // or if the provider doesn't provide a getExpectedRuntime() method + if (!$provider instanceof IProviderWithExpectedRuntime || $provider->getExpectedRuntime() > $maxExecutionTime * 0.8) { + $this->scheduleTask($task); + return false; + } + $this->runTask($task); + return true; + } + + /** + * @inheritDoc + */ public function deleteTask(Task $task): void { $taskEntity = DbTask::fromPublicTask($task); $this->taskMapper->delete($taskEntity); @@ -259,4 +271,31 @@ class Manager implements IManager { throw new RuntimeException('Failure while trying to find tasks by appId and identifier: ' . $e->getMessage(), 0, $e); } } + + /** + * @param OCPTask $task + * @return IProvider[] + */ + public function getPreferredProviders(OCPTask $task): array { + $providers = $this->getProviders(); + $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', ''); + if ($json !== '') { + $preferences = json_decode($json, true); + if (isset($preferences[$task->getType()])) { + // If a preference for this task type is set, move the preferred provider to the start + $provider = current(array_values(array_filter($providers, function ($provider) use ($preferences, $task) { + if ($provider instanceof IProviderWithId) { + return $provider->getId() === $preferences[$task->getType()]; + } + $provider::class === $preferences[$task->getType()]; + }))); + if ($provider !== false) { + $providers = array_filter($providers, fn ($p) => $p !== $provider); + array_unshift($providers, $provider); + } + } + } + $providers = array_values(array_filter($providers, fn (IProvider $provider) => $task->canUseProvider($provider))); + return $providers; + } } diff --git a/lib/private/TextToImage/Db/Task.php b/lib/private/TextToImage/Db/Task.php new file mode 100644 index 00000000000..96dd6e4e165 --- /dev/null +++ b/lib/private/TextToImage/Db/Task.php @@ -0,0 +1,117 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\TextToImage\Db; + +use DateTime; +use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\TextToImage\Task as OCPTask; + +/** + * @method setLastUpdated(DateTime $lastUpdated) + * @method DateTime getLastUpdated() + * @method setInput(string $type) + * @method string getInput() + * @method setResultPath(string $resultPath) + * @method string getResultPath() + * @method setStatus(int $type) + * @method int getStatus() + * @method setUserId(?string $userId) + * @method string|null getUserId() + * @method setAppId(string $type) + * @method string getAppId() + * @method setIdentifier(string $identifier) + * @method string|null getIdentifier() + * @method setNumberOfImages(int $numberOfImages) + * @method int getNumberOfImages() + * @method setCompletionExpectedAt(DateTime $at) + * @method DateTime getCompletionExpectedAt() + */ +class Task extends Entity { + protected $lastUpdated; + protected $type; + protected $input; + protected $status; + protected $userId; + protected $appId; + protected $identifier; + protected $numberOfImages; + protected $completionExpectedAt; + + /** + * @var string[] + */ + public static array $columns = ['id', 'last_updated', 'input', 'status', 'user_id', 'app_id', 'identifier', 'number_of_images', 'completion_expected_at']; + + /** + * @var string[] + */ + public static array $fields = ['id', 'lastUpdated', 'input', 'status', 'userId', 'appId', 'identifier', 'numberOfImages', 'completionExpectedAt']; + + + public function __construct() { + // add types in constructor + $this->addType('id', 'integer'); + $this->addType('lastUpdated', 'datetime'); + $this->addType('input', 'string'); + $this->addType('status', 'integer'); + $this->addType('userId', 'string'); + $this->addType('appId', 'string'); + $this->addType('identifier', 'string'); + $this->addType('numberOfImages', 'integer'); + $this->addType('completionExpectedAt', 'datetime'); + } + + public function toRow(): array { + return array_combine(self::$columns, array_map(function ($field) { + return $this->{'get'.ucfirst($field)}(); + }, self::$fields)); + } + + public static function fromPublicTask(OCPTask $task): Task { + /** @var Task $dbTask */ + $dbTask = Task::fromParams([ + 'id' => $task->getId(), + 'lastUpdated' => \OCP\Server::get(ITimeFactory::class)->getDateTime(), + 'status' => $task->getStatus(), + 'numberOfImages' => $task->getNumberOfImages(), + 'input' => $task->getInput(), + 'userId' => $task->getUserId(), + 'appId' => $task->getAppId(), + 'identifier' => $task->getIdentifier(), + 'completionExpectedAt' => $task->getCompletionExpectedAt(), + ]); + return $dbTask; + } + + public function toPublicTask(): OCPTask { + $task = new OCPTask($this->getInput(), $this->getAppId(), $this->getNumberOfImages(), $this->getuserId(), $this->getIdentifier()); + $task->setId($this->getId()); + $task->setStatus($this->getStatus()); + $task->setCompletionExpectedAt($this->getCompletionExpectedAt()); + return $task; + } +} diff --git a/lib/private/TextToImage/Db/TaskMapper.php b/lib/private/TextToImage/Db/TaskMapper.php new file mode 100644 index 00000000000..68fdd8f40de --- /dev/null +++ b/lib/private/TextToImage/Db/TaskMapper.php @@ -0,0 +1,127 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\TextToImage\Db; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\AppFramework\Db\QBMapper; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\Exception; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +/** + * @extends QBMapper<Task> + */ +class TaskMapper extends QBMapper { + public function __construct( + IDBConnection $db, + private ITimeFactory $timeFactory, + ) { + parent::__construct($db, 'text2image_tasks', Task::class); + } + + /** + * @param int $id + * @return Task + * @throws Exception + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException + */ + public function find(int $id): Task { + $qb = $this->db->getQueryBuilder(); + $qb->select(Task::$columns) + ->from($this->tableName) + ->where($qb->expr()->eq('id', $qb->createPositionalParameter($id))); + return $this->findEntity($qb); + } + + /** + * @param int $id + * @param string|null $userId + * @return Task + * @throws DoesNotExistException + * @throws Exception + * @throws MultipleObjectsReturnedException + */ + public function findByIdAndUser(int $id, ?string $userId): Task { + $qb = $this->db->getQueryBuilder(); + $qb->select(Task::$columns) + ->from($this->tableName) + ->where($qb->expr()->eq('id', $qb->createPositionalParameter($id))); + if ($userId === null) { + $qb->andWhere($qb->expr()->isNull('user_id')); + } else { + $qb->andWhere($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId))); + } + return $this->findEntity($qb); + } + + /** + * @param string $userId + * @param string $appId + * @param string|null $identifier + * @return array + * @throws Exception + */ + public function findUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array { + $qb = $this->db->getQueryBuilder(); + $qb->select(Task::$columns) + ->from($this->tableName) + ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId))); + if ($identifier !== null) { + $qb->andWhere($qb->expr()->eq('identifier', $qb->createPositionalParameter($identifier))); + } + return $this->findEntities($qb); + } + + /** + * @param int $timeout + * @return Task[] the deleted tasks + * @throws Exception + */ + public function deleteOlderThan(int $timeout): array { + $datetime = $this->timeFactory->getDateTime(); + $datetime->sub(new \DateInterval('PT'.$timeout.'S')); + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->tableName) + ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE))); + $deletedTasks = $this->findEntities($qb); + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->tableName) + ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE))); + $qb->executeStatement(); + return $deletedTasks; + } + + public function update(Entity $entity): Entity { + $entity->setLastUpdated($this->timeFactory->getDateTime()); + return parent::update($entity); + } +} diff --git a/lib/private/TextToImage/Manager.php b/lib/private/TextToImage/Manager.php new file mode 100644 index 00000000000..86212709c42 --- /dev/null +++ b/lib/private/TextToImage/Manager.php @@ -0,0 +1,335 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\TextToImage; + +use OC\AppFramework\Bootstrap\Coordinator; +use OC\TextToImage\Db\Task as DbTask; +use OC\TextToImage\Db\TaskMapper; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\BackgroundJob\IJobList; +use OCP\DB\Exception; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IConfig; +use OCP\IServerContainer; +use OCP\PreConditionNotMetException; +use OCP\TextToImage\Exception\TaskFailureException; +use OCP\TextToImage\Exception\TaskNotFoundException; +use OCP\TextToImage\IManager; +use OCP\TextToImage\IProvider; +use OCP\TextToImage\Task; +use Psr\Log\LoggerInterface; +use RuntimeException; +use Throwable; + +class Manager implements IManager { + /** @var ?IProvider[] */ + private ?array $providers = null; + private IAppData $appData; + + public function __construct( + private IServerContainer $serverContainer, + private Coordinator $coordinator, + private LoggerInterface $logger, + private IJobList $jobList, + private TaskMapper $taskMapper, + private IConfig $config, + IAppDataFactory $appDataFactory, + ) { + $this->appData = $appDataFactory->get('core'); + } + + /** + * @inerhitDocs + */ + public function getProviders(): array { + $context = $this->coordinator->getRegistrationContext(); + if ($context === null) { + return []; + } + + if ($this->providers !== null) { + return $this->providers; + } + + $this->providers = []; + + foreach ($context->getTextToImageProviders() as $providerServiceRegistration) { + $class = $providerServiceRegistration->getService(); + try { + $this->providers[$class] = $this->serverContainer->get($class); + } catch (Throwable $e) { + $this->logger->error('Failed to load Text to image provider ' . $class, [ + 'exception' => $e, + ]); + } + } + + return $this->providers; + } + + /** + * @inheritDoc + */ + public function hasProviders(): bool { + $context = $this->coordinator->getRegistrationContext(); + if ($context === null) { + return false; + } + return count($context->getTextToImageProviders()) > 0; + } + + /** + * @inheritDoc + */ + public function runTask(Task $task): void { + $this->logger->debug('Running TextToImage Task'); + if (!$this->hasProviders()) { + throw new PreConditionNotMetException('No text to image provider is installed that can handle this task'); + } + $providers = $this->getPreferredProviders(); + + foreach ($providers as $provider) { + $this->logger->debug('Trying to run Text2Image provider '.$provider::class); + try { + $task->setStatus(Task::STATUS_RUNNING); + $completionExpectedAt = new \DateTime('now'); + $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); + $task->setCompletionExpectedAt($completionExpectedAt); + if ($task->getId() === null) { + $this->logger->debug('Inserting Text2Image task into DB'); + $taskEntity = $this->taskMapper->insert(DbTask::fromPublicTask($task)); + $task->setId($taskEntity->getId()); + } else { + $this->logger->debug('Updating Text2Image task in DB'); + $this->taskMapper->update(DbTask::fromPublicTask($task)); + } + try { + $folder = $this->appData->getFolder('text2image'); + } catch(NotFoundException) { + $this->logger->debug('Creating folder in appdata for Text2Image results'); + $folder = $this->appData->newFolder('text2image'); + } + try { + $folder = $folder->getFolder((string) $task->getId()); + } catch(NotFoundException) { + $this->logger->debug('Creating new folder in appdata Text2Image results folder'); + $folder = $folder->newFolder((string) $task->getId()); + } + $this->logger->debug('Creating result files for Text2Image task'); + $resources = []; + $files = []; + for ($i = 0; $i < $task->getNumberOfImages(); $i++) { + $file = $folder->newFile((string) $i); + $files[] = $file; + $resource = $file->write(); + if ($resource !== false && $resource !== true && is_resource($resource)) { + $resources[] = $resource; + } else { + throw new RuntimeException('Text2Image generation using provider "' . $provider->getName() . '" failed: Couldn\'t open file to write.'); + } + } + $this->logger->debug('Calling Text2Image provider\'s generate method'); + $provider->generate($task->getInput(), $resources); + for ($i = 0; $i < $task->getNumberOfImages(); $i++) { + if (is_resource($resources[$i])) { + // If $resource hasn't been closed yet, we'll do that here + fclose($resources[$i]); + } + } + $task->setStatus(Task::STATUS_SUCCESSFUL); + $this->logger->debug('Updating Text2Image task in DB'); + $this->taskMapper->update(DbTask::fromPublicTask($task)); + return; + } catch (\RuntimeException|\Throwable $e) { + for ($i = 0; $i < $task->getNumberOfImages(); $i++) { + if (isset($files, $files[$i])) { + try { + $files[$i]->delete(); + } catch(NotPermittedException $e) { + $this->logger->warning('Failed to clean up Text2Image result file after error', ['exception' => $e]); + } + } + } + + $this->logger->info('Text2Image generation using provider "' . $provider->getName() . '" failed', ['exception' => $e]); + $task->setStatus(Task::STATUS_FAILED); + try { + $this->taskMapper->update(DbTask::fromPublicTask($task)); + } catch (Exception $e) { + $this->logger->warning('Failed to update database after Text2Image error', ['exception' => $e]); + } + throw new TaskFailureException('Text2Image generation using provider "' . $provider->getName() . '" failed: ' . $e->getMessage(), 0, $e); + } + } + + $task->setStatus(Task::STATUS_FAILED); + try { + $this->taskMapper->update(DbTask::fromPublicTask($task)); + } catch (Exception $e) { + $this->logger->warning('Failed to update database after Text2Image error', ['exception' => $e]); + } + throw new TaskFailureException('Could not run task'); + } + + /** + * @inheritDoc + */ + public function scheduleTask(Task $task): void { + if (!$this->hasProviders()) { + throw new PreConditionNotMetException('No text to image provider is installed that can handle this task'); + } + $this->logger->debug('Scheduling Text2Image Task'); + $task->setStatus(Task::STATUS_SCHEDULED); + $completionExpectedAt = new \DateTime('now'); + $completionExpectedAt->add(new \DateInterval('PT'.$this->getPreferredProviders()[0]->getExpectedRuntime().'S')); + $task->setCompletionExpectedAt($completionExpectedAt); + $taskEntity = DbTask::fromPublicTask($task); + $this->taskMapper->insert($taskEntity); + $task->setId($taskEntity->getId()); + $this->jobList->add(TaskBackgroundJob::class, [ + 'taskId' => $task->getId() + ]); + } + + /** + * @inheritDoc + */ + public function runOrScheduleTask(Task $task) : void { + if (!$this->hasProviders()) { + throw new PreConditionNotMetException('No text to image provider is installed that can handle this task'); + } + $providers = $this->getPreferredProviders(); + $maxExecutionTime = (int) ini_get('max_execution_time'); + // Offload the task to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time + if ($providers[0]->getExpectedRuntime() > $maxExecutionTime * 0.8) { + $this->scheduleTask($task); + return; + } + $this->runTask($task); + } + + /** + * @inheritDoc + */ + public function deleteTask(Task $task): void { + $taskEntity = DbTask::fromPublicTask($task); + $this->taskMapper->delete($taskEntity); + $this->jobList->remove(TaskBackgroundJob::class, [ + 'taskId' => $task->getId() + ]); + } + + /** + * Get a task from its id + * + * @param int $id The id of the task + * @return Task + * @throws RuntimeException If the query failed + * @throws TaskNotFoundException If the task could not be found + */ + public function getTask(int $id): Task { + try { + $taskEntity = $this->taskMapper->find($id); + return $taskEntity->toPublicTask(); + } catch (DoesNotExistException $e) { + throw new TaskNotFoundException('Could not find task with the provided id'); + } catch (MultipleObjectsReturnedException $e) { + throw new RuntimeException('Could not uniquely identify task with given id', 0, $e); + } catch (Exception $e) { + throw new RuntimeException('Failure while trying to find task by id: ' . $e->getMessage(), 0, $e); + } + } + + /** + * Get a task from its user id and task id + * If userId is null, this can only get a task that was scheduled anonymously + * + * @param int $id The id of the task + * @param string|null $userId The user id that scheduled the task + * @return Task + * @throws RuntimeException If the query failed + * @throws TaskNotFoundException If the task could not be found + */ + public function getUserTask(int $id, ?string $userId): Task { + try { + $taskEntity = $this->taskMapper->findByIdAndUser($id, $userId); + return $taskEntity->toPublicTask(); + } catch (DoesNotExistException $e) { + throw new TaskNotFoundException('Could not find task with the provided id and user id'); + } catch (MultipleObjectsReturnedException $e) { + throw new RuntimeException('Could not uniquely identify task with given id and user id', 0, $e); + } catch (Exception $e) { + throw new RuntimeException('Failure while trying to find task by id and user id: ' . $e->getMessage(), 0, $e); + } + } + + /** + * Get a list of tasks scheduled by a specific user for a specific app + * and optionally with a specific identifier. + * This cannot be used to get anonymously scheduled tasks + * + * @param string $userId + * @param string $appId + * @param string|null $identifier + * @return Task[] + * @throws RuntimeException + */ + public function getUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array { + try { + $taskEntities = $this->taskMapper->findUserTasksByApp($userId, $appId, $identifier); + return array_map(static function (DbTask $taskEntity) { + return $taskEntity->toPublicTask(); + }, $taskEntities); + } catch (Exception $e) { + throw new RuntimeException('Failure while trying to find tasks by appId and identifier: ' . $e->getMessage(), 0, $e); + } + } + + /** + * @return IProvider[] + */ + private function getPreferredProviders() { + $providers = $this->getProviders(); + $json = $this->config->getAppValue('core', 'ai.text2image_provider', ''); + if ($json !== '') { + try { + $id = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + $provider = current(array_filter($providers, fn ($provider) => $provider->getId() === $id)); + if ($provider !== false && $provider !== null) { + $providers = [$provider]; + } + } catch (\JsonException $e) { + $this->logger->warning('Failed to decode Text2Image setting `ai.text2image_provider`', ['exception' => $e]); + } + } + + return $providers; + } +} diff --git a/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php b/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php new file mode 100644 index 00000000000..2ecebc241bf --- /dev/null +++ b/lib/private/TextToImage/RemoveOldTasksBackgroundJob.php @@ -0,0 +1,78 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +namespace OC\TextToImage; + +use OC\TextToImage\Db\TaskMapper; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\TimedJob; +use OCP\DB\Exception; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use Psr\Log\LoggerInterface; + +class RemoveOldTasksBackgroundJob extends TimedJob { + public const MAX_TASK_AGE_SECONDS = 60 * 50 * 24 * 7; // 1 week + + private IAppData $appData; + + public function __construct( + ITimeFactory $timeFactory, + private TaskMapper $taskMapper, + private LoggerInterface $logger, + IAppDataFactory $appDataFactory, + ) { + parent::__construct($timeFactory); + $this->appData = $appDataFactory->get('core'); + $this->setInterval(60 * 60 * 24); + } + + /** + * @param mixed $argument + * @inheritDoc + */ + protected function run($argument) { + try { + $deletedTasks = $this->taskMapper->deleteOlderThan(self::MAX_TASK_AGE_SECONDS); + $folder = $this->appData->getFolder('text2image'); + foreach ($deletedTasks as $deletedTask) { + try { + $folder->getFolder((string)$deletedTask->getId())->delete(); + } catch (NotFoundException) { + // noop + } catch (NotPermittedException $e) { + $this->logger->warning('Failed to delete stale text to image task files', ['exception' => $e]); + } + } + } catch (Exception $e) { + $this->logger->warning('Failed to delete stale text to image tasks', ['exception' => $e]); + } catch(NotFoundException) { + // noop + } + } +} diff --git a/lib/private/TextToImage/TaskBackgroundJob.php b/lib/private/TextToImage/TaskBackgroundJob.php new file mode 100644 index 00000000000..ac5cd6b59b5 --- /dev/null +++ b/lib/private/TextToImage/TaskBackgroundJob.php @@ -0,0 +1,63 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +namespace OC\TextToImage; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\QueuedJob; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\TextToImage\Events\TaskFailedEvent; +use OCP\TextToImage\Events\TaskSuccessfulEvent; +use OCP\TextToImage\IManager; + +class TaskBackgroundJob extends QueuedJob { + public function __construct( + ITimeFactory $timeFactory, + private IManager $text2imageManager, + private IEventDispatcher $eventDispatcher, + ) { + parent::__construct($timeFactory); + // We want to avoid overloading the machine with these jobs + // so we only allow running one job at a time + $this->setAllowParallelRuns(false); + } + + /** + * @param array{taskId: int} $argument + * @inheritDoc + */ + protected function run($argument) { + $taskId = $argument['taskId']; + $task = $this->text2imageManager->getTask($taskId); + try { + $this->text2imageManager->runTask($task); + $event = new TaskSuccessfulEvent($task); + } catch (\Throwable $e) { + $event = new TaskFailedEvent($task, $e->getMessage()); + } + $this->eventDispatcher->dispatchTyped($event); + } +} diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index 57bafc3e18d..3d384de5842 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -70,10 +70,10 @@ class URLGenerator implements IURLGenerator { private ?IAppManager $appManager = null; public function __construct(IConfig $config, - IUserSession $userSession, - ICacheFactory $cacheFactory, - IRequest $request, - Router $router + IUserSession $userSession, + ICacheFactory $cacheFactory, + IRequest $request, + Router $router ) { $this->config = $config; $this->userSession = $userSession; @@ -116,16 +116,25 @@ class URLGenerator implements IURLGenerator { } public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string { + // Returns `/subfolder/index.php/ocsapp/…` with `'htaccess.IgnoreFrontController' => false` in config.php + // And `/subfolder/ocsapp/…` with `'htaccess.IgnoreFrontController' => true` in config.php $route = $this->router->generate('ocs.'.$routeName, $arguments, false); - $indexPhpPos = strpos($route, '/index.php/'); - if ($indexPhpPos !== false) { - $route = substr($route, $indexPhpPos + 10); + // Cut off `/subfolder` + if (\OC::$WEBROOT !== '' && str_starts_with($route, \OC::$WEBROOT)) { + $route = substr($route, \strlen(\OC::$WEBROOT)); } + if (str_starts_with($route, '/index.php/')) { + $route = substr($route, 10); + } + + // Remove `ocsapp/` bit $route = substr($route, 7); + // Prefix with ocs/v2.php endpoint $route = '/ocs/v2.php' . $route; + // Turn into an absolute URL return $this->getAbsoluteURL($route); } diff --git a/lib/private/Updater.php b/lib/private/Updater.php index 5a14bb17507..018e4797232 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -40,13 +40,6 @@ declare(strict_types=1); */ namespace OC; -use OCP\App\IAppManager; -use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\IEventDispatcher; -use OCP\HintException; -use OCP\IConfig; -use OCP\ILogger; -use OCP\Util; use OC\App\AppManager; use OC\DB\Connection; use OC\DB\MigrationService; @@ -61,6 +54,13 @@ use OC\Repair\Events\RepairStartEvent; use OC\Repair\Events\RepairStepEvent; use OC\Repair\Events\RepairWarningEvent; use OC_App; +use OCP\App\IAppManager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\HintException; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Util; use Psr\Log\LoggerInterface; /** @@ -94,9 +94,9 @@ class Updater extends BasicEmitter { ]; public function __construct(IConfig $config, - Checker $checker, - ?LoggerInterface $log, - Installer $installer) { + Checker $checker, + ?LoggerInterface $log, + Installer $installer) { $this->log = $log; $this->config = $config; $this->checker = $checker; diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php index 97f770b6998..e37024ec2c2 100644 --- a/lib/private/Updater/VersionCheck.php +++ b/lib/private/Updater/VersionCheck.php @@ -127,7 +127,9 @@ class VersionCheck { */ protected function getUrlContent($url) { $client = $this->clientService->newClient(); - $response = $client->get($url); + $response = $client->get($url, [ + 'timeout' => 5, + ]); return $response->getBody(); } diff --git a/lib/private/User/AvailabilityCoordinator.php b/lib/private/User/AvailabilityCoordinator.php new file mode 100644 index 00000000000..c32c3005c32 --- /dev/null +++ b/lib/private/User/AvailabilityCoordinator.php @@ -0,0 +1,139 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Richard Steinmetz <richard@steinmetz.cloud> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\User; + +use JsonException; +use OCA\DAV\AppInfo\Application; +use OCA\DAV\CalDAV\TimezoneService; +use OCA\DAV\Service\AbsenceService; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IUser; +use OCP\User\IAvailabilityCoordinator; +use OCP\User\IOutOfOfficeData; +use Psr\Log\LoggerInterface; + +class AvailabilityCoordinator implements IAvailabilityCoordinator { + private ICache $cache; + + public function __construct( + ICacheFactory $cacheFactory, + private IConfig $config, + private AbsenceService $absenceService, + private LoggerInterface $logger, + private TimezoneService $timezoneService, + ) { + $this->cache = $cacheFactory->createLocal('OutOfOfficeData'); + } + + public function isEnabled(): bool { + return $this->config->getAppValue(Application::APP_ID, 'hide_absence_settings', 'no') === 'no'; + } + + private function getCachedOutOfOfficeData(IUser $user): ?OutOfOfficeData { + $cachedString = $this->cache->get($user->getUID()); + if ($cachedString === null) { + return null; + } + + try { + $cachedData = json_decode($cachedString, true, 10, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + $this->logger->error('Failed to deserialize cached out-of-office data: ' . $e->getMessage(), [ + 'exception' => $e, + 'json' => $cachedString, + ]); + return null; + } + + return new OutOfOfficeData( + $cachedData['id'], + $user, + $cachedData['startDate'], + $cachedData['endDate'], + $cachedData['shortMessage'], + $cachedData['message'], + ); + } + + private function setCachedOutOfOfficeData(IOutOfOfficeData $data): void { + try { + $cachedString = json_encode([ + 'id' => $data->getId(), + 'startDate' => $data->getStartDate(), + 'endDate' => $data->getEndDate(), + 'shortMessage' => $data->getShortMessage(), + 'message' => $data->getMessage(), + ], JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + $this->logger->error('Failed to serialize out-of-office data: ' . $e->getMessage(), [ + 'exception' => $e, + ]); + return; + } + + $this->cache->set($data->getUser()->getUID(), $cachedString, 300); + } + + public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData { + $timezone = $this->getCachedTimezone($user->getUID()); + if ($timezone === null) { + $timezone = $this->timezoneService->getUserTimezone($user->getUID()) ?? $this->timezoneService->getDefaultTimezone(); + $this->setCachedTimezone($user->getUID(), $timezone); + } + + $data = $this->getCachedOutOfOfficeData($user); + if ($data === null) { + $absenceData = $this->absenceService->getAbsence($user->getUID()); + if ($absenceData === null) { + return null; + } + $data = $absenceData->toOutOufOfficeData($user, $timezone); + } + + $this->setCachedOutOfOfficeData($data); + return $data; + } + + private function getCachedTimezone(string $userId): ?string { + return $this->cache->get($userId . '_timezone') ?? null; + } + + private function setCachedTimezone(string $userId, string $timezone): void { + $this->cache->set($userId . '_timezone', $timezone, 3600); + } + + public function clearCache(string $userId): void { + $this->cache->set($userId, null, 300); + $this->cache->set($userId . '_timezone', null, 3600); + } + + public function isInEffect(IOutOfOfficeData $data): bool { + return $this->absenceService->isInEffect($data); + } +} diff --git a/lib/private/User/Listeners/BeforeUserDeletedListener.php b/lib/private/User/Listeners/BeforeUserDeletedListener.php index ec1f80c5413..8978c341a13 100644 --- a/lib/private/User/Listeners/BeforeUserDeletedListener.php +++ b/lib/private/User/Listeners/BeforeUserDeletedListener.php @@ -25,9 +25,9 @@ namespace OC\User\Listeners; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; -use OCP\User\Events\BeforeUserDeletedEvent; use OCP\Files\NotFoundException; use OCP\IAvatarManager; +use OCP\User\Events\BeforeUserDeletedEvent; use Psr\Log\LoggerInterface; /** diff --git a/lib/private/User/Listeners/UserChangedListener.php b/lib/private/User/Listeners/UserChangedListener.php index a561db2423d..0fa5ceeb0ed 100644 --- a/lib/private/User/Listeners/UserChangedListener.php +++ b/lib/private/User/Listeners/UserChangedListener.php @@ -25,9 +25,9 @@ namespace OC\User\Listeners; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; -use OCP\User\Events\UserChangedEvent; use OCP\Files\NotFoundException; use OCP\IAvatarManager; +use OCP\User\Events\UserChangedEvent; /** * @template-implements IEventListener<UserChangedEvent> diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 8ec8ef0c4be..5013c9bed7a 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -48,11 +48,11 @@ use OCP\IUserManager; use OCP\L10N\IFactory; use OCP\Server; use OCP\Support\Subscription\IAssertion; -use OCP\User\Backend\IGetRealUIDBackend; -use OCP\User\Backend\ISearchKnownUsersBackend; use OCP\User\Backend\ICheckPasswordBackend; use OCP\User\Backend\ICountUsersBackend; +use OCP\User\Backend\IGetRealUIDBackend; use OCP\User\Backend\IProvideEnabledStateBackend; +use OCP\User\Backend\ISearchKnownUsersBackend; use OCP\User\Events\BeforeUserCreatedEvent; use OCP\User\Events\UserCreatedEvent; use OCP\UserInterface; @@ -97,8 +97,8 @@ class Manager extends PublicEmitter implements IUserManager { private DisplayNameCache $displayNameCache; public function __construct(IConfig $config, - ICacheFactory $cacheFactory, - IEventDispatcher $eventDispatcher) { + ICacheFactory $cacheFactory, + IEventDispatcher $eventDispatcher) { $this->config = $config; $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map')); $cachedUsers = &$this->cachedUsers; diff --git a/lib/private/User/OutOfOfficeData.php b/lib/private/User/OutOfOfficeData.php new file mode 100644 index 00000000000..72e42afab6a --- /dev/null +++ b/lib/private/User/OutOfOfficeData.php @@ -0,0 +1,74 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\User; + +use OCP\IUser; +use OCP\User\IOutOfOfficeData; + +class OutOfOfficeData implements IOutOfOfficeData { + public function __construct(private string $id, + private IUser $user, + private int $startDate, + private int $endDate, + private string $shortMessage, + private string $message) { + } + + public function getId(): string { + return $this->id; + } + + public function getUser(): IUser { + return $this->user; + } + + public function getStartDate(): int { + return $this->startDate; + } + + public function getEndDate(): int { + return $this->endDate; + } + + public function getShortMessage(): string { + return $this->shortMessage; + } + + public function getMessage(): string { + return $this->message; + } + + public function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'userId' => $this->getUser()->getUID(), + 'startDate' => $this->getStartDate(), + 'endDate' => $this->getEndDate(), + 'shortMessage' => $this->getShortMessage(), + 'message' => $this->getMessage(), + ]; + } +} diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index d6971d1486b..5689de3995f 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -120,14 +120,14 @@ class Session implements IUserSession, Emitter { private $dispatcher; public function __construct(Manager $manager, - ISession $session, - ITimeFactory $timeFactory, - ?IProvider $tokenProvider, - IConfig $config, - ISecureRandom $random, - ILockdownManager $lockdownManager, - LoggerInterface $logger, - IEventDispatcher $dispatcher + ISession $session, + ITimeFactory $timeFactory, + ?IProvider $tokenProvider, + IConfig $config, + ISecureRandom $random, + ILockdownManager $lockdownManager, + LoggerInterface $logger, + IEventDispatcher $dispatcher ) { $this->manager = $manager; $this->session = $session; @@ -425,9 +425,9 @@ class Session implements IUserSession, Emitter { * @return boolean */ public function logClientIn($user, - $password, - IRequest $request, - IThrottler $throttler) { + $password, + IRequest $request, + IThrottler $throttler) { $remoteAddress = $request->getRemoteAddress(); $currentDelay = $throttler->sleepDelayOrThrowOnMax($remoteAddress, 'login'); @@ -456,8 +456,17 @@ class Session implements IUserSession, Emitter { $this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password); return false; } - $users = $this->manager->getByEmail($user); - if (!(\count($users) === 1 && $this->login($users[0]->getUID(), $password))) { + + if ($isTokenPassword) { + $dbToken = $this->tokenProvider->getToken($password); + $userFromToken = $this->manager->get($dbToken->getUID()); + $isValidEmailLogin = $userFromToken->getEMailAddress() === $user; + } else { + $users = $this->manager->getByEmail($user); + $isValidEmailLogin = (\count($users) === 1 && $this->login($users[0]->getUID(), $password)); + } + + if (!$isValidEmailLogin) { $this->handleLoginFailed($throttler, $currentDelay, $remoteAddress, $user, $password); return false; } @@ -576,7 +585,7 @@ class Session implements IUserSession, Emitter { * @return boolean if the login was successful */ public function tryBasicAuthLogin(IRequest $request, - IThrottler $throttler) { + IThrottler $throttler) { if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) { try { if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) { @@ -783,7 +792,7 @@ class Session implements IUserSession, Emitter { try { $dbToken = $this->tokenProvider->getToken($token); } catch (InvalidTokenException $ex) { - $this->logger->warning('Session token is invalid because it does not exist', [ + $this->logger->debug('Session token is invalid because it does not exist', [ 'app' => 'core', 'user' => $user, 'exception' => $ex, @@ -916,9 +925,10 @@ class Session implements IUserSession, Emitter { ]); return false; } catch (InvalidTokenException $ex) { - $this->logger->error('Renewing session token failed', [ + $this->logger->error('Renewing session token failed: ' . $ex->getMessage(), [ 'app' => 'core', 'user' => $uid, + 'exception' => $ex, ]); return false; } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 69ef82f3e85..580c590e6eb 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -49,17 +49,17 @@ use OCP\IImage; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserBackend; +use OCP\User\Backend\IGetHomeBackend; +use OCP\User\Backend\IProvideAvatarBackend; +use OCP\User\Backend\IProvideEnabledStateBackend; +use OCP\User\Backend\ISetDisplayNameBackend; +use OCP\User\Backend\ISetPasswordBackend; use OCP\User\Events\BeforePasswordUpdatedEvent; use OCP\User\Events\BeforeUserDeletedEvent; use OCP\User\Events\PasswordUpdatedEvent; use OCP\User\Events\UserChangedEvent; use OCP\User\Events\UserDeletedEvent; use OCP\User\GetQuotaEvent; -use OCP\User\Backend\ISetDisplayNameBackend; -use OCP\User\Backend\ISetPasswordBackend; -use OCP\User\Backend\IProvideAvatarBackend; -use OCP\User\Backend\IProvideEnabledStateBackend; -use OCP\User\Backend\IGetHomeBackend; use OCP\UserInterface; use function json_decode; use function json_encode; @@ -576,7 +576,7 @@ class User implements IUser { public function getAvatarImage($size) { // delay the initialization if (is_null($this->avatarManager)) { - $this->avatarManager = \OC::$server->getAvatarManager(); + $this->avatarManager = \OC::$server->get(IAvatarManager::class); } $avatar = $this->avatarManager->getAvatar($this->uid); diff --git a/lib/private/UserStatus/ISettableProvider.php b/lib/private/UserStatus/ISettableProvider.php index 88a107d1f86..957d3274f1d 100644 --- a/lib/private/UserStatus/ISettableProvider.php +++ b/lib/private/UserStatus/ISettableProvider.php @@ -39,8 +39,9 @@ interface ISettableProvider extends IProvider { * @param string $messageId The new message id. * @param string $status The new status. * @param bool $createBackup If true, this will store the old status so that it is possible to revert it later (e.g. after a call). + * @param string|null $customMessage */ - public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup): void; + public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup, ?string $customMessage = null): void; /** * Revert an automatically set user status. For example after leaving a call, diff --git a/lib/private/UserStatus/Manager.php b/lib/private/UserStatus/Manager.php index 89a1bb455c7..a5594158c1e 100644 --- a/lib/private/UserStatus/Manager.php +++ b/lib/private/UserStatus/Manager.php @@ -51,7 +51,7 @@ class Manager implements IManager { * @param LoggerInterface $logger */ public function __construct(IServerContainer $container, - LoggerInterface $logger) { + LoggerInterface $logger) { $this->container = $container; $this->logger = $logger; } @@ -104,13 +104,13 @@ class Manager implements IManager { $this->provider = $provider; } - public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false): void { + public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false, ?string $customMessage = null): void { $this->setupProvider(); if (!$this->provider || !($this->provider instanceof ISettableProvider)) { return; } - $this->provider->setUserStatus($userId, $messageId, $status, $createBackup); + $this->provider->setUserStatus($userId, $messageId, $status, $createBackup, $customMessage); } public function revertUserStatus(string $userId, string $messageId, string $status): void { diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index 23e0b099e91..395c1f44c03 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -51,18 +51,18 @@ declare(strict_types=1); * */ -use OCP\App\Events\AppUpdateEvent; -use OCP\App\IAppManager; -use OCP\App\ManagerEvent; -use OCP\Authentication\IAlternativeLogin; -use OCP\EventDispatcher\IEventDispatcher; -use OC\AppFramework\Bootstrap\Coordinator; use OC\App\DependencyAnalyzer; use OC\App\Platform; +use OC\AppFramework\Bootstrap\Coordinator; use OC\DB\MigrationService; use OC\Installer; use OC\Repair; use OC\Repair\Events\RepairErrorEvent; +use OCP\App\Events\AppUpdateEvent; +use OCP\App\IAppManager; +use OCP\App\ManagerEvent; +use OCP\Authentication\IAlternativeLogin; +use OCP\EventDispatcher\IEventDispatcher; use Psr\Container\ContainerExceptionInterface; use Psr\Log\LoggerInterface; @@ -251,7 +251,7 @@ class OC_App { * This function set an app as enabled in appconfig. */ public function enable(string $appId, - array $groups = []) { + array $groups = []) { // Check if app is already downloaded /** @var Installer $installer */ $installer = \OCP\Server::get(Installer::class); @@ -564,7 +564,7 @@ class OC_App { $supportedApps = $this->getSupportedApps(); foreach ($installedApps as $app) { - if (array_search($app, $blacklist) === false) { + if (!in_array($app, $blacklist)) { $info = $appManager->getAppInfo($app, false, $langCode); if (!is_array($info)) { \OCP\Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']); diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php index ac0a2bbd0e9..a2f47639e65 100644 --- a/lib/private/legacy/OC_Files.php +++ b/lib/private/legacy/OC_Files.php @@ -43,10 +43,10 @@ use bantu\IniGetWrapper\IniGetWrapper; use OC\Files\View; use OC\Streamer; -use OCP\Lock\ILockingProvider; -use OCP\Files\Events\BeforeZipCreatedEvent; -use OCP\Files\Events\BeforeDirectFileDownloadEvent; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Events\BeforeDirectFileDownloadEvent; +use OCP\Files\Events\BeforeZipCreatedEvent; +use OCP\Lock\ILockingProvider; /** * Class for file server access diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php index cf39d045ae9..37fbf7f5f8f 100644 --- a/lib/private/legacy/OC_Helper.php +++ b/lib/private/legacy/OC_Helper.php @@ -46,8 +46,8 @@ use bantu\IniGetWrapper\IniGetWrapper; use OC\Files\Filesystem; use OCP\Files\Mount\IMountPoint; -use OCP\ICacheFactory; use OCP\IBinaryFinder; +use OCP\ICacheFactory; use OCP\IUser; use OCP\Util; use Psr\Log\LoggerInterface; diff --git a/lib/private/legacy/OC_User.php b/lib/private/legacy/OC_User.php index 5751b813f2c..d2dc2a2389f 100644 --- a/lib/private/legacy/OC_User.php +++ b/lib/private/legacy/OC_User.php @@ -138,7 +138,7 @@ class OC_User { $class = $config['class']; $arguments = $config['arguments']; if (class_exists($class)) { - if (array_search($i, self::$_setupedBackends) === false) { + if (!in_array($i, self::$_setupedBackends)) { // make a reflection object $reflectionObj = new ReflectionClass($class); diff --git a/lib/public/App/IAppManager.php b/lib/public/App/IAppManager.php index 70d137a2cce..4667cf13f0f 100644 --- a/lib/public/App/IAppManager.php +++ b/lib/public/App/IAppManager.php @@ -250,6 +250,8 @@ interface IAppManager { * * @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 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 2398c5ef931..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 * 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..7c3725c9290 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; 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/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/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/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/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/IGroup.php b/lib/public/IGroup.php index ec26cc55b69..51417641e26 100644 --- a/lib/public/IGroup.php +++ b/lib/public/IGroup.php @@ -1,4 +1,7 @@ <?php + +declare(strict_types=1); + /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @@ -38,7 +41,7 @@ interface IGroup { * @return string * @since 8.0.0 */ - public function getGID(); + public function getGID(): string; /** * Returns the group display name @@ -46,7 +49,7 @@ interface IGroup { * @return string * @since 12.0.0 */ - public function getDisplayName(); + public function getDisplayName(): string; /** * Set the group display name @@ -60,43 +63,47 @@ interface IGroup { /** * get all users in the group * - * @return \OCP\IUser[] + * @return IUser[] * @since 8.0.0 */ - public function getUsers(); + public function getUsers(): array; /** * check if a user is in the group * - * @param \OCP\IUser $user + * @param IUser $user + * * @return bool * @since 8.0.0 */ - public function inGroup(IUser $user); + public function inGroup(IUser $user): bool; /** * add a user to the group * - * @param \OCP\IUser $user + * @param IUser $user + * * @since 8.0.0 */ - public function addUser(IUser $user); + public function addUser(IUser $user): void; /** - * remove a user from the group + * Remove a user from the group + * + * @param IUser $user * - * @param \OCP\IUser $user * @since 8.0.0 */ - public function removeUser($user); + public function removeUser(IUser $user): void; /** * search for users in the group by userid * * @param string $search - * @param int $limit - * @param int $offset - * @return \OCP\IUser[] + * @param int|null $limit + * @param int|null $offset + * + * @return IUser[] * @since 8.0.0 */ public function searchUsers(string $search, ?int $limit = null, ?int $offset = null): array; @@ -108,7 +115,7 @@ interface IGroup { * @return int|bool * @since 8.0.0 */ - public function count($search = ''); + public function count(string $search = ''): int|bool; /** * returns the number of disabled users @@ -116,18 +123,19 @@ interface IGroup { * @return int|bool * @since 14.0.0 */ - public function countDisabled(); + public function countDisabled(): int|bool; /** - * search for users in the group by displayname + * Search for users in the group by displayname * * @param string $search - * @param int $limit - * @param int $offset - * @return \OCP\IUser[] + * @param int|null $limit + * @param int|null $offset + * + * @return IUser[] * @since 8.0.0 */ - public function searchDisplayName($search, $limit = null, $offset = null); + public function searchDisplayName(string $search, int $limit = null, int $offset = null): array; /** * Get the names of the backends the group is connected to @@ -135,27 +143,27 @@ interface IGroup { * @return string[] * @since 22.0.0 */ - public function getBackendNames(); + public function getBackendNames(): array; /** - * delete the group + * Delete the group * * @return bool * @since 8.0.0 */ - public function delete(); + public function delete(): bool; /** * @return bool * @since 14.0.0 */ - public function canRemoveUser(); + public function canRemoveUser(): bool; /** * @return bool * @since 14.0.0 */ - public function canAddUser(); + public function canAddUser(): bool; /** * @return bool diff --git a/lib/public/INavigationManager.php b/lib/public/INavigationManager.php index 710cbd1248d..36f80c3293f 100644 --- a/lib/public/INavigationManager.php +++ b/lib/public/INavigationManager.php @@ -33,6 +33,10 @@ namespace OCP; /** + * @psalm-type NavigationEntry = array{id: string, order: int, href: string, name: string, app?: string, icon?: string, classes?: string, type?: string} + */ + +/** * Manages the ownCloud navigation * @since 6.0.0 */ @@ -58,9 +62,11 @@ interface INavigationManager { /** * Creates a new navigation entry * - * @param array|\Closure $entry Array containing: id, name, order, icon and href key + * @param array array|\Closure $entry Array containing: id, name, order, icon and href key + * If a menu entry (type = 'link') is added, you shall also set app to the app that added the entry. * The use of a closure is preferred, because it will avoid * loading the routing of your app, unless required. + * @psalm-param NavigationEntry|callable():NavigationEntry $entry * @return void * @since 6.0.0 */ diff --git a/lib/public/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/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 index 77eeaea4df1..96eb6ddd7da 100644 --- a/lib/public/SetupCheck/ISetupCheck.php +++ b/lib/public/SetupCheck/ISetupCheck.php @@ -36,11 +36,13 @@ namespace OCP\SetupCheck; 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; diff --git a/lib/public/SetupCheck/SetupResult.php b/lib/public/SetupCheck/SetupResult.php index 27026f82815..e4a7744178a 100644 --- a/lib/public/SetupCheck/SetupResult.php +++ b/lib/public/SetupCheck/SetupResult.php @@ -38,6 +38,11 @@ class SetupResult implements \JsonSerializable { public const ERROR = 'error'; /** + * @param string $name Translated name to display to the user + */ + private ?string $name = null; + + /** * @brief Private constructor, use success()/info()/warning()/error() instead * @param self::SUCCESS|self::INFO|self::WARNING|self::ERROR $severity * @since 28.0.0 @@ -51,6 +56,8 @@ class SetupResult implements \JsonSerializable { /** * @brief Create a success result object + * @param ?string $description Translated detailed description to display to the user + * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project * @since 28.0.0 */ public static function success(?string $description = null, ?string $linkToDoc = null): self { @@ -59,6 +66,8 @@ class SetupResult implements \JsonSerializable { /** * @brief Create an info result object + * @param ?string $description Translated detailed description to display to the user + * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project * @since 28.0.0 */ public static function info(?string $description = null, ?string $linkToDoc = null): self { @@ -67,6 +76,8 @@ class SetupResult implements \JsonSerializable { /** * @brief Create a warning result object + * @param ?string $description Translated detailed description to display to the user + * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project * @since 28.0.0 */ public static function warning(?string $description = null, ?string $linkToDoc = null): self { @@ -75,6 +86,8 @@ class SetupResult implements \JsonSerializable { /** * @brief Create an error result object + * @param ?string $description Translated detailed description to display to the user + * @param ?string $linkToDoc URI of related relevent documentation, be it from Nextcloud or another project * @since 28.0.0 */ public static function error(?string $description = null, ?string $linkToDoc = null): self { @@ -101,6 +114,24 @@ class SetupResult implements \JsonSerializable { } /** + * @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 @@ -116,6 +147,7 @@ class SetupResult implements \JsonSerializable { */ public function jsonSerialize(): array { return [ + 'name' => $this->name, 'severity' => $this->severity, 'description' => $this->description, '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/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/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 4604dc4d663..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; /** @@ -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/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..30b88217690 --- /dev/null +++ b/lib/public/TextToImage/IManager.php @@ -0,0 +1,116 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +namespace OCP\TextToImage; + +use OCP\DB\Exception; +use OCP\PreConditionNotMetException; +use OCP\TextToImage\Exception\TaskFailureException; +use OCP\TextToImage\Exception\TaskNotFoundException; +use RuntimeException; + +/** + * API surface for apps interacting with and making use of TextToImage providers + * without knowing which providers are installed + * @since 28.0.0 + */ +interface IManager { + /** + * @since 28.0.0 + */ + public function hasProviders(): bool; + + /** + * @since 28.0.0 + * @return IProvider[] + */ + public function getProviders(): array; + + /** + * @param Task $task The task to run + * @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called + * @throws TaskFailureException If something else failed. When this is thrown task status was already set to failure. + * @since 28.0.0 + */ + public function runTask(Task $task): void; + + /** + * Will schedule a TextToImage process in the background. The result will become available + * with the \OCP\TextToImage\TaskSuccessfulEvent + * If inference fails a \OCP\TextToImage\Events\TaskFailedEvent will be dispatched instead + * + * @param Task $task The task to schedule + * @throws PreConditionNotMetException If no provider was registered but this method was still called + * @throws Exception If there was a problem inserting the task into the database + * @since 28.0.0 + */ + public function scheduleTask(Task $task) : void; + + /** + * @throws Exception if there was a problem inserting the task into the database + * @throws PreConditionNotMetException if no provider is registered + * @throws TaskFailureException If the task run failed + * @since 28.0.0 + */ + public function runOrScheduleTask(Task $task) : void; + + /** + * Delete a task that has been scheduled before + * + * @param Task $task The task to delete + * @since 28.0.0 + */ + public function deleteTask(Task $task): void; + + /** + * @param int $id The id of the task + * @return Task + * @throws RuntimeException If the query failed + * @throws TaskNotFoundException If the task could not be found + * @since 28.0.0 + */ + public function getTask(int $id): Task; + + /** + * @param int $id The id of the task + * @param string|null $userId The user id that scheduled the task + * @return Task + * @throws RuntimeException If the query failed + * @throws TaskNotFoundException If the task could not be found + * @since 28.0.0 + */ + public function getUserTask(int $id, ?string $userId): Task; + + /** + * @param ?string $userId + * @param string $appId + * @param string|null $identifier + * @return Task[] + * @since 28.0.0 + * @throws RuntimeException If the query failed + */ + public function getUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array; +} diff --git a/lib/public/TextToImage/IProvider.php b/lib/public/TextToImage/IProvider.php new file mode 100644 index 00000000000..9f2ff51f599 --- /dev/null +++ b/lib/public/TextToImage/IProvider.php @@ -0,0 +1,64 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCP\TextToImage; + +use RuntimeException; + +/** + * This is the interface that is implemented by apps that + * implement a text to image provider + * @since 28.0.0 + */ +interface IProvider { + /** + * An arbitrary unique text string identifying this provider + * @since 28.0.0 + */ + public function getId(): string; + + /** + * The localized name of this provider + * @since 28.0.0 + */ + public function getName(): string; + + /** + * Processes a text + * + * @param string $prompt The input text + * @param resource[] $resources The file resources to write the images to + * @return void + * @since 28.0.0 + * @throws RuntimeException If the text could not be processed + */ + public function generate(string $prompt, array $resources): void; + + /** + * The expected runtime for one task with this provider in seconds + * @since 28.0.0 + */ + public function getExpectedRuntime(): int; +} diff --git a/lib/public/TextToImage/Task.php b/lib/public/TextToImage/Task.php new file mode 100644 index 00000000000..e610af6aa96 --- /dev/null +++ b/lib/public/TextToImage/Task.php @@ -0,0 +1,212 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net> + * + * @author Marcel Klehr <mklehr@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCP\TextToImage; + +use DateTime; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IImage; +use OCP\Image; + +/** + * This is a text to image task + * + * @since 28.0.0 + */ +final class Task implements \JsonSerializable { + protected ?int $id = null; + + protected ?DateTime $completionExpectedAt = null; + + /** + * @since 28.0.0 + */ + public const STATUS_FAILED = 4; + /** + * @since 28.0.0 + */ + public const STATUS_SUCCESSFUL = 3; + /** + * @since 28.0.0 + */ + public const STATUS_RUNNING = 2; + /** + * @since 28.0.0 + */ + public const STATUS_SCHEDULED = 1; + /** + * @since 28.0.0 + */ + public const STATUS_UNKNOWN = 0; + + /** + * @psalm-var self::STATUS_* + */ + protected int $status = self::STATUS_UNKNOWN; + + /** + * @param string $input + * @param string $appId + * @param int $numberOfImages + * @param string|null $userId + * @param null|string $identifier An arbitrary identifier for this task. max length: 255 chars + * @since 28.0.0 + */ + final public function __construct( + protected string $input, + protected string $appId, + protected int $numberOfImages, + protected ?string $userId, + protected ?string $identifier = '', + ) { + } + + /** + * @return IImage[]|null + * @since 28.0.0 + */ + final public function getOutputImages(): ?array { + $appData = \OCP\Server::get(IAppDataFactory::class)->get('core'); + try { + $folder = $appData->getFolder('text2image')->getFolder((string)$this->getId()); + $images = []; + for ($i = 0; $i < $this->getNumberOfImages(); $i++) { + $image = new Image(); + $image->loadFromFileHandle($folder->getFile((string) $i)->read()); + $images[] = $image; + } + return $images; + } catch (NotFoundException|NotPermittedException) { + return null; + } + } + + /** + * @return int + * @since 28.0.0 + */ + final public function getNumberOfImages(): int { + return $this->numberOfImages; + } + + /** + * @psalm-return self::STATUS_* + * @since 28.0.0 + */ + final public function getStatus(): int { + return $this->status; + } + + /** + * @psalm-param self::STATUS_* $status + * @since 28.0.0 + */ + final public function setStatus(int $status): void { + $this->status = $status; + } + + /** + * @param ?DateTime $at + * @since 28.0.0 + */ + final public function setCompletionExpectedAt(?DateTime $at): void { + $this->completionExpectedAt = $at; + } + + /** + * @return ?DateTime + * @since 28.0.0 + */ + final public function getCompletionExpectedAt(): ?DateTime { + return $this->completionExpectedAt; + } + + /** + * @return int|null + * @since 28.0.0 + */ + final public function getId(): ?int { + return $this->id; + } + + /** + * @param int|null $id + * @since 28.0.0 + */ + final public function setId(?int $id): void { + $this->id = $id; + } + + /** + * @return string + * @since 28.0.0 + */ + final public function getInput(): string { + return $this->input; + } + + /** + * @return string + * @since 28.0.0 + */ + final public function getAppId(): string { + return $this->appId; + } + + /** + * @return null|string + * @since 28.0.0 + */ + final public function getIdentifier(): ?string { + return $this->identifier; + } + + /** + * @return string|null + * @since 28.0.0 + */ + final public function getUserId(): ?string { + return $this->userId; + } + + /** + * @psalm-return array{id: ?int, status: self::STATUS_*, userId: ?string, appId: string, input: string, identifier: ?string, numberOfImages: int, completionExpectedAt: ?int} + * @since 28.0.0 + */ + public function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'status' => $this->getStatus(), + 'userId' => $this->getUserId(), + 'appId' => $this->getAppId(), + 'numberOfImages' => $this->getNumberOfImages(), + 'input' => $this->getInput(), + 'identifier' => $this->getIdentifier(), + 'completionExpectedAt' => $this->getCompletionExpectedAt()->getTimestamp(), + ]; + } +} diff --git a/lib/public/User/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; + } } /** |