aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/base.php1
-rw-r--r--lib/composer/composer/autoload_classmap.php9
-rw-r--r--lib/composer/composer/autoload_static.php9
-rw-r--r--lib/l10n/be.js7
-rw-r--r--lib/l10n/be.json7
-rw-r--r--lib/l10n/de.js2
-rw-r--r--lib/l10n/de.json2
-rw-r--r--lib/l10n/de_DE.js2
-rw-r--r--lib/l10n/de_DE.json2
-rw-r--r--lib/l10n/en_GB.js2
-rw-r--r--lib/l10n/en_GB.json2
-rw-r--r--lib/l10n/es.js9
-rw-r--r--lib/l10n/es.json9
-rw-r--r--lib/l10n/et_EE.js2
-rw-r--r--lib/l10n/et_EE.json2
-rw-r--r--lib/l10n/ga.js2
-rw-r--r--lib/l10n/ga.json2
-rw-r--r--lib/l10n/ja.js18
-rw-r--r--lib/l10n/ja.json18
-rw-r--r--lib/l10n/mk.js3
-rw-r--r--lib/l10n/mk.json3
-rw-r--r--lib/l10n/pl.js2
-rw-r--r--lib/l10n/pl.json2
-rw-r--r--lib/l10n/pt_BR.js2
-rw-r--r--lib/l10n/pt_BR.json2
-rw-r--r--lib/l10n/sr.js2
-rw-r--r--lib/l10n/sr.json2
-rw-r--r--lib/l10n/sw.js7
-rw-r--r--lib/l10n/sw.json7
-rw-r--r--lib/l10n/uk.js2
-rw-r--r--lib/l10n/uk.json2
-rw-r--r--lib/l10n/zh_CN.js4
-rw-r--r--lib/l10n/zh_CN.json4
-rw-r--r--lib/l10n/zh_HK.js2
-rw-r--r--lib/l10n/zh_HK.json2
-rw-r--r--lib/l10n/zh_TW.js2
-rw-r--r--lib/l10n/zh_TW.json2
-rw-r--r--lib/private/Accounts/AccountManager.php58
-rw-r--r--lib/private/Activity/Event.php1
-rw-r--r--lib/private/Activity/Manager.php39
-rw-r--r--lib/private/AppConfig.php26
-rw-r--r--lib/private/AppFramework/App.php47
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php7
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php4
-rw-r--r--lib/private/Avatar/Avatar.php51
-rw-r--r--lib/private/Avatar/AvatarManager.php6
-rw-r--r--lib/private/Avatar/GuestAvatar.php4
-rw-r--r--lib/private/Avatar/PlaceholderAvatar.php9
-rw-r--r--lib/private/Avatar/UserAvatar.php21
-rw-r--r--lib/private/Config/ConfigManager.php24
-rw-r--r--lib/private/Config/PresetManager.php48
-rw-r--r--lib/private/Config/UserConfig.php19
-rw-r--r--lib/private/DB/Connection.php9
-rw-r--r--lib/private/DB/ConnectionAdapter.php6
-rw-r--r--lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php4
-rw-r--r--lib/private/DB/QueryBuilder/QueryBuilder.php4
-rw-r--r--lib/private/Files/Node/Root.php2
-rw-r--r--lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php13
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php42
-rw-r--r--lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php121
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php33
-rw-r--r--lib/private/Files/Type/Detection.php33
-rw-r--r--lib/private/Files/View.php44
-rw-r--r--lib/private/InitialStateService.php22
-rw-r--r--lib/private/Mail/EMailTemplate.php42
-rw-r--r--lib/private/Notification/Manager.php12
-rw-r--r--lib/private/Profile/Actions/BlueskyAction.php65
-rw-r--r--lib/private/Profile/ProfileManager.php31
-rw-r--r--lib/private/Search/SearchComposer.php21
-rw-r--r--lib/private/Security/IdentityProof/Manager.php26
-rw-r--r--lib/private/Server.php3
-rw-r--r--lib/private/Setup/MySQL.php27
-rw-r--r--lib/private/Share20/Manager.php2
-rw-r--r--lib/private/Streamer.php13
-rw-r--r--lib/private/SystemConfig.php6
-rw-r--r--lib/private/Tags.php1
-rw-r--r--lib/private/TaskProcessing/Db/Task.php10
-rw-r--r--lib/private/TaskProcessing/Db/TaskMapper.php25
-rw-r--r--lib/private/TaskProcessing/Manager.php113
-rw-r--r--lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php44
-rw-r--r--lib/private/Template/JSConfigHelper.php6
-rw-r--r--lib/private/TemplateLayout.php3
-rw-r--r--lib/private/User/DisplayNameCache.php6
-rw-r--r--lib/private/UserStatus/Manager.php38
-rw-r--r--lib/public/Accounts/IAccountManager.php7
-rw-r--r--lib/public/Activity/IBulkConsumer.php24
-rw-r--r--lib/public/Activity/IManager.php14
-rw-r--r--lib/public/IDBConnection.php13
-rw-r--r--lib/public/IInitialStateService.php8
-rw-r--r--lib/public/Notification/IManager.php2
-rw-r--r--lib/public/Notification/INotifier.php5
-rw-r--r--lib/public/Notification/IPreloadableNotifier.php31
-rw-r--r--lib/public/Profile/IProfileManager.php1
-rw-r--r--lib/public/Search/IExternalProvider.php25
-rw-r--r--lib/public/TaskProcessing/IManager.php11
-rw-r--r--lib/public/TaskProcessing/Task.php20
96 files changed, 1154 insertions, 357 deletions
diff --git a/lib/base.php b/lib/base.php
index a603f67d78e..ab838d366b1 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -243,6 +243,7 @@ class OC {
// render error page
$template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest');
\OCP\Util::addScript('core', 'maintenance');
+ \OCP\Util::addScript('core', 'common');
\OCP\Util::addStyle('core', 'guest');
$template->printPage();
die();
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 3f2dbe5edf9..8d20f898dc3 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -49,6 +49,7 @@ return array(
'OCP\\Activity\\Exceptions\\InvalidValueException' => $baseDir . '/lib/public/Activity/Exceptions/InvalidValueException.php',
'OCP\\Activity\\Exceptions\\SettingNotFoundException' => $baseDir . '/lib/public/Activity/Exceptions/SettingNotFoundException.php',
'OCP\\Activity\\Exceptions\\UnknownActivityException' => $baseDir . '/lib/public/Activity/Exceptions/UnknownActivityException.php',
+ 'OCP\\Activity\\IBulkConsumer' => $baseDir . '/lib/public/Activity/IBulkConsumer.php',
'OCP\\Activity\\IConsumer' => $baseDir . '/lib/public/Activity/IConsumer.php',
'OCP\\Activity\\IEvent' => $baseDir . '/lib/public/Activity/IEvent.php',
'OCP\\Activity\\IEventMerger' => $baseDir . '/lib/public/Activity/IEventMerger.php',
@@ -696,6 +697,7 @@ return array(
'OCP\\Notification\\IManager' => $baseDir . '/lib/public/Notification/IManager.php',
'OCP\\Notification\\INotification' => $baseDir . '/lib/public/Notification/INotification.php',
'OCP\\Notification\\INotifier' => $baseDir . '/lib/public/Notification/INotifier.php',
+ 'OCP\\Notification\\IPreloadableNotifier' => $baseDir . '/lib/public/Notification/IPreloadableNotifier.php',
'OCP\\Notification\\IncompleteNotificationException' => $baseDir . '/lib/public/Notification/IncompleteNotificationException.php',
'OCP\\Notification\\IncompleteParsedNotificationException' => $baseDir . '/lib/public/Notification/IncompleteParsedNotificationException.php',
'OCP\\Notification\\InvalidValueException' => $baseDir . '/lib/public/Notification/InvalidValueException.php',
@@ -737,6 +739,7 @@ return array(
'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\\IExternalProvider' => $baseDir . '/lib/public/Search/IExternalProvider.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',
@@ -1218,6 +1221,7 @@ return array(
'OC\\Comments\\ManagerFactory' => $baseDir . '/lib/private/Comments/ManagerFactory.php',
'OC\\Config' => $baseDir . '/lib/private/Config.php',
'OC\\Config\\ConfigManager' => $baseDir . '/lib/private/Config/ConfigManager.php',
+ 'OC\\Config\\PresetManager' => $baseDir . '/lib/private/Config/PresetManager.php',
'OC\\Config\\UserConfig' => $baseDir . '/lib/private/Config/UserConfig.php',
'OC\\Console\\Application' => $baseDir . '/lib/private/Console/Application.php',
'OC\\Console\\TimestampFormatter' => $baseDir . '/lib/private/Console/TimestampFormatter.php',
@@ -1345,6 +1349,7 @@ return array(
'OC\\Core\\Command\\SystemTag\\Delete' => $baseDir . '/core/Command/SystemTag/Delete.php',
'OC\\Core\\Command\\SystemTag\\Edit' => $baseDir . '/core/Command/SystemTag/Edit.php',
'OC\\Core\\Command\\SystemTag\\ListCommand' => $baseDir . '/core/Command/SystemTag/ListCommand.php',
+ 'OC\\Core\\Command\\TaskProcessing\\Cleanup' => $baseDir . '/core/Command/TaskProcessing/Cleanup.php',
'OC\\Core\\Command\\TaskProcessing\\EnabledCommand' => $baseDir . '/core/Command/TaskProcessing/EnabledCommand.php',
'OC\\Core\\Command\\TaskProcessing\\GetCommand' => $baseDir . '/core/Command/TaskProcessing/GetCommand.php',
'OC\\Core\\Command\\TaskProcessing\\ListCommand' => $baseDir . '/core/Command/TaskProcessing/ListCommand.php',
@@ -1510,6 +1515,8 @@ return array(
'OC\\Core\\Migrations\\Version31000Date20240814184402' => $baseDir . '/core/Migrations/Version31000Date20240814184402.php',
'OC\\Core\\Migrations\\Version31000Date20250213102442' => $baseDir . '/core/Migrations/Version31000Date20250213102442.php',
'OC\\Core\\Migrations\\Version32000Date20250620081925' => $baseDir . '/core/Migrations/Version32000Date20250620081925.php',
+ 'OC\\Core\\Migrations\\Version32000Date20250731062008' => $baseDir . '/core/Migrations/Version32000Date20250731062008.php',
+ 'OC\\Core\\Migrations\\Version32000Date20250806110519' => $baseDir . '/core/Migrations/Version32000Date20250806110519.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php',
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
@@ -1684,6 +1691,7 @@ return array(
'OC\\Files\\ObjectStore\\AppdataPreviewObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php',
'OC\\Files\\ObjectStore\\Azure' => $baseDir . '/lib/private/Files/ObjectStore/Azure.php',
'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php',
+ 'OC\\Files\\ObjectStore\\InvalidObjectStoreConfigurationException' => $baseDir . '/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php',
'OC\\Files\\ObjectStore\\Mapper' => $baseDir . '/lib/private/Files/ObjectStore/Mapper.php',
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
@@ -1902,6 +1910,7 @@ return array(
'OC\\Preview\\WatcherConnector' => $baseDir . '/lib/private/Preview/WatcherConnector.php',
'OC\\Preview\\WebP' => $baseDir . '/lib/private/Preview/WebP.php',
'OC\\Preview\\XBitmap' => $baseDir . '/lib/private/Preview/XBitmap.php',
+ 'OC\\Profile\\Actions\\BlueskyAction' => $baseDir . '/lib/private/Profile/Actions/BlueskyAction.php',
'OC\\Profile\\Actions\\EmailAction' => $baseDir . '/lib/private/Profile/Actions/EmailAction.php',
'OC\\Profile\\Actions\\FediverseAction' => $baseDir . '/lib/private/Profile/Actions/FediverseAction.php',
'OC\\Profile\\Actions\\PhoneAction' => $baseDir . '/lib/private/Profile/Actions/PhoneAction.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 233b667add9..ef0e6d35740 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -90,6 +90,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Activity\\Exceptions\\InvalidValueException' => __DIR__ . '/../../..' . '/lib/public/Activity/Exceptions/InvalidValueException.php',
'OCP\\Activity\\Exceptions\\SettingNotFoundException' => __DIR__ . '/../../..' . '/lib/public/Activity/Exceptions/SettingNotFoundException.php',
'OCP\\Activity\\Exceptions\\UnknownActivityException' => __DIR__ . '/../../..' . '/lib/public/Activity/Exceptions/UnknownActivityException.php',
+ 'OCP\\Activity\\IBulkConsumer' => __DIR__ . '/../../..' . '/lib/public/Activity/IBulkConsumer.php',
'OCP\\Activity\\IConsumer' => __DIR__ . '/../../..' . '/lib/public/Activity/IConsumer.php',
'OCP\\Activity\\IEvent' => __DIR__ . '/../../..' . '/lib/public/Activity/IEvent.php',
'OCP\\Activity\\IEventMerger' => __DIR__ . '/../../..' . '/lib/public/Activity/IEventMerger.php',
@@ -737,6 +738,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Notification\\IManager' => __DIR__ . '/../../..' . '/lib/public/Notification/IManager.php',
'OCP\\Notification\\INotification' => __DIR__ . '/../../..' . '/lib/public/Notification/INotification.php',
'OCP\\Notification\\INotifier' => __DIR__ . '/../../..' . '/lib/public/Notification/INotifier.php',
+ 'OCP\\Notification\\IPreloadableNotifier' => __DIR__ . '/../../..' . '/lib/public/Notification/IPreloadableNotifier.php',
'OCP\\Notification\\IncompleteNotificationException' => __DIR__ . '/../../..' . '/lib/public/Notification/IncompleteNotificationException.php',
'OCP\\Notification\\IncompleteParsedNotificationException' => __DIR__ . '/../../..' . '/lib/public/Notification/IncompleteParsedNotificationException.php',
'OCP\\Notification\\InvalidValueException' => __DIR__ . '/../../..' . '/lib/public/Notification/InvalidValueException.php',
@@ -778,6 +780,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'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\\IExternalProvider' => __DIR__ . '/../../..' . '/lib/public/Search/IExternalProvider.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',
@@ -1259,6 +1262,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Comments\\ManagerFactory' => __DIR__ . '/../../..' . '/lib/private/Comments/ManagerFactory.php',
'OC\\Config' => __DIR__ . '/../../..' . '/lib/private/Config.php',
'OC\\Config\\ConfigManager' => __DIR__ . '/../../..' . '/lib/private/Config/ConfigManager.php',
+ 'OC\\Config\\PresetManager' => __DIR__ . '/../../..' . '/lib/private/Config/PresetManager.php',
'OC\\Config\\UserConfig' => __DIR__ . '/../../..' . '/lib/private/Config/UserConfig.php',
'OC\\Console\\Application' => __DIR__ . '/../../..' . '/lib/private/Console/Application.php',
'OC\\Console\\TimestampFormatter' => __DIR__ . '/../../..' . '/lib/private/Console/TimestampFormatter.php',
@@ -1386,6 +1390,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Command\\SystemTag\\Delete' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Delete.php',
'OC\\Core\\Command\\SystemTag\\Edit' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Edit.php',
'OC\\Core\\Command\\SystemTag\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/SystemTag/ListCommand.php',
+ 'OC\\Core\\Command\\TaskProcessing\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/Cleanup.php',
'OC\\Core\\Command\\TaskProcessing\\EnabledCommand' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/EnabledCommand.php',
'OC\\Core\\Command\\TaskProcessing\\GetCommand' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/GetCommand.php',
'OC\\Core\\Command\\TaskProcessing\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/ListCommand.php',
@@ -1551,6 +1556,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Migrations\\Version31000Date20240814184402' => __DIR__ . '/../../..' . '/core/Migrations/Version31000Date20240814184402.php',
'OC\\Core\\Migrations\\Version31000Date20250213102442' => __DIR__ . '/../../..' . '/core/Migrations/Version31000Date20250213102442.php',
'OC\\Core\\Migrations\\Version32000Date20250620081925' => __DIR__ . '/../../..' . '/core/Migrations/Version32000Date20250620081925.php',
+ 'OC\\Core\\Migrations\\Version32000Date20250731062008' => __DIR__ . '/../../..' . '/core/Migrations/Version32000Date20250731062008.php',
+ 'OC\\Core\\Migrations\\Version32000Date20250806110519' => __DIR__ . '/../../..' . '/core/Migrations/Version32000Date20250806110519.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php',
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
@@ -1725,6 +1732,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Files\\ObjectStore\\AppdataPreviewObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php',
'OC\\Files\\ObjectStore\\Azure' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Azure.php',
'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php',
+ 'OC\\Files\\ObjectStore\\InvalidObjectStoreConfigurationException' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php',
'OC\\Files\\ObjectStore\\Mapper' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Mapper.php',
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
@@ -1943,6 +1951,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Preview\\WatcherConnector' => __DIR__ . '/../../..' . '/lib/private/Preview/WatcherConnector.php',
'OC\\Preview\\WebP' => __DIR__ . '/../../..' . '/lib/private/Preview/WebP.php',
'OC\\Preview\\XBitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/XBitmap.php',
+ 'OC\\Profile\\Actions\\BlueskyAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/BlueskyAction.php',
'OC\\Profile\\Actions\\EmailAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/EmailAction.php',
'OC\\Profile\\Actions\\FediverseAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/FediverseAction.php',
'OC\\Profile\\Actions\\PhoneAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/PhoneAction.php',
diff --git a/lib/l10n/be.js b/lib/l10n/be.js
index 258f628ed37..4d73f079394 100644
--- a/lib/l10n/be.js
+++ b/lib/l10n/be.js
@@ -19,13 +19,20 @@ OC.L10N.register(
"_%n day ago_::_%n days ago_" : ["%n дзень таму","%n дні таму","%n дзён таму","%n дзён таму"],
"next month" : "у наступным месяцы",
"last month" : "у мінулым месяцы",
+ "_in %n month_::_in %n months_" : ["праз %n месяц","праз %n месяцы","праз %n месяцаў","праз %n месяцаў"],
+ "_%n month ago_::_%n months ago_" : ["%n месяц таму","%n месяцы таму","%n месяцаў таму","%n месяцаў таму"],
"next year" : "у наступным годзе",
"last year" : "у мінулым годзе",
"_in %n year_::_in %n years_" : ["праз %n год","праз %n гады","праз %n гадоў","праз %n гадоў"],
"_%n year ago_::_%n years ago_" : ["%n год таму","%n гады таму","%n гадоў таму","%n гадоў таму"],
+ "_in %n hour_::_in %n hours_" : ["праз %n гадзіну","праз %n гадзіны","праз %n гадзін","праз %n гадзін"],
+ "_%n hour ago_::_%n hours ago_" : ["%n гадзіну таму","%n гадзіны таму","%n гадзін таму","%n гадзін таму"],
+ "_in %n minute_::_in %n minutes_" : ["праз %n хвіліну","праз %n хвіліны","праз %n хвілін","праз %n хвілін"],
+ "_%n minute ago_::_%n minutes ago_" : ["%n хвіліну таму","%n хвіліны таму","%n хвілін таму","%n хвілін таму"],
"in a few seconds" : "праз некалькі секунд",
"seconds ago" : "с таму",
"Empty file" : "Пусты файл",
+ "Could not convert file" : "Не атрымалася канвертаваць файл",
"%1$s (renamed)" : "%1$s (перайменаваны)",
"Templates" : "Шаблоны",
"__language_name__" : "Беларуская",
diff --git a/lib/l10n/be.json b/lib/l10n/be.json
index 96d8f861d9b..9a410c17f7d 100644
--- a/lib/l10n/be.json
+++ b/lib/l10n/be.json
@@ -17,13 +17,20 @@
"_%n day ago_::_%n days ago_" : ["%n дзень таму","%n дні таму","%n дзён таму","%n дзён таму"],
"next month" : "у наступным месяцы",
"last month" : "у мінулым месяцы",
+ "_in %n month_::_in %n months_" : ["праз %n месяц","праз %n месяцы","праз %n месяцаў","праз %n месяцаў"],
+ "_%n month ago_::_%n months ago_" : ["%n месяц таму","%n месяцы таму","%n месяцаў таму","%n месяцаў таму"],
"next year" : "у наступным годзе",
"last year" : "у мінулым годзе",
"_in %n year_::_in %n years_" : ["праз %n год","праз %n гады","праз %n гадоў","праз %n гадоў"],
"_%n year ago_::_%n years ago_" : ["%n год таму","%n гады таму","%n гадоў таму","%n гадоў таму"],
+ "_in %n hour_::_in %n hours_" : ["праз %n гадзіну","праз %n гадзіны","праз %n гадзін","праз %n гадзін"],
+ "_%n hour ago_::_%n hours ago_" : ["%n гадзіну таму","%n гадзіны таму","%n гадзін таму","%n гадзін таму"],
+ "_in %n minute_::_in %n minutes_" : ["праз %n хвіліну","праз %n хвіліны","праз %n хвілін","праз %n хвілін"],
+ "_%n minute ago_::_%n minutes ago_" : ["%n хвіліну таму","%n хвіліны таму","%n хвілін таму","%n хвілін таму"],
"in a few seconds" : "праз некалькі секунд",
"seconds ago" : "с таму",
"Empty file" : "Пусты файл",
+ "Could not convert file" : "Не атрымалася канвертаваць файл",
"%1$s (renamed)" : "%1$s (перайменаваны)",
"Templates" : "Шаблоны",
"__language_name__" : "Беларуская",
diff --git a/lib/l10n/de.js b/lib/l10n/de.js
index db04c57acff..de09d348eaf 100644
--- a/lib/l10n/de.js
+++ b/lib/l10n/de.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Einstellungen",
"Log out" : "Abmelden",
"Accounts" : "Konten",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "%s auf Bluesky anzeigen",
"Email" : "E-Mail",
"Mail %s" : "E-Mail %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/de.json b/lib/l10n/de.json
index c7ad725a2b6..bd676f0ec80 100644
--- a/lib/l10n/de.json
+++ b/lib/l10n/de.json
@@ -127,6 +127,8 @@
"Settings" : "Einstellungen",
"Log out" : "Abmelden",
"Accounts" : "Konten",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "%s auf Bluesky anzeigen",
"Email" : "E-Mail",
"Mail %s" : "E-Mail %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/de_DE.js b/lib/l10n/de_DE.js
index bea2fe3ba6d..8329f013a24 100644
--- a/lib/l10n/de_DE.js
+++ b/lib/l10n/de_DE.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Einstellungen",
"Log out" : "Abmelden",
"Accounts" : "Konten",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "%s auf Bluesky anzeigen",
"Email" : "E-Mail",
"Mail %s" : "E-Mail %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/de_DE.json b/lib/l10n/de_DE.json
index d754563af01..26c052d4233 100644
--- a/lib/l10n/de_DE.json
+++ b/lib/l10n/de_DE.json
@@ -127,6 +127,8 @@
"Settings" : "Einstellungen",
"Log out" : "Abmelden",
"Accounts" : "Konten",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "%s auf Bluesky anzeigen",
"Email" : "E-Mail",
"Mail %s" : "E-Mail %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/en_GB.js b/lib/l10n/en_GB.js
index b406616574a..05dc12e854e 100644
--- a/lib/l10n/en_GB.js
+++ b/lib/l10n/en_GB.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Settings",
"Log out" : "Log out",
"Accounts" : "Accounts",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "View %s on Bluesky",
"Email" : "Email",
"Mail %s" : "Mail %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/en_GB.json b/lib/l10n/en_GB.json
index cfe65d44222..e195477c74b 100644
--- a/lib/l10n/en_GB.json
+++ b/lib/l10n/en_GB.json
@@ -127,6 +127,8 @@
"Settings" : "Settings",
"Log out" : "Log out",
"Accounts" : "Accounts",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "View %s on Bluesky",
"Email" : "Email",
"Mail %s" : "Mail %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/es.js b/lib/l10n/es.js
index 9c7ea8f5f41..91f0c8920c5 100644
--- a/lib/l10n/es.js
+++ b/lib/l10n/es.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Ajustes",
"Log out" : "Cerrar sesión",
"Accounts" : "Cuentas",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Ver %s en Bluesky",
"Email" : "Correo electrónico",
"Mail %s" : "Correo %s",
"Fediverse" : "Fediverso",
@@ -275,6 +277,7 @@ OC.L10N.register(
"A valid Login must be provided" : "Se debe proporcionar un usuario válido",
"Login contains whitespace at the beginning or at the end" : "El usuario contiene espacios en blanco al inicio o al final",
"Login must not consist of dots only" : "El usuario no debe consistir sólo de puntos",
+ "Username is too long" : "El nombre de usuario es demasiado largo",
"Login is invalid because files already exist for this user" : "El nombre de inicio de sesión es inválido porque ya existen archivos para este usuario",
"Account disabled" : "Cuenta deshabilitada",
"Login canceled by app" : "Login cancelado por la app",
@@ -328,13 +331,17 @@ OC.L10N.register(
"Images" : "Imágenes",
"Images to ask a question about" : "Imágenes sobre las cuales se formulará una pregunta",
"Question" : "Pregunta",
+ "What to ask about the images." : "Que preguntar sobre las imágenes.",
"Generated response" : "Respuesta generada",
"The answer to the question" : "La respuesta a la pregunta",
+ "Audio chat" : "Chat de audio",
"Voice chat with the assistant" : "Chat de voz con el asistente",
"System prompt" : "Prompt del sistema",
"Define rules and assumptions that the assistant should follow during the conversation." : "Definir las reglas y supuestos que el asistente debe seguir durante la conversación.",
"Chat voice message" : "Mensaje de voz del chat",
+ "Describe a task that you want the assistant to do or ask a question." : "Describa una tarea que desea que el asistente realice o haga una pregunta.",
"Chat history" : "Historial de la conversación",
+ "The history of chat messages before the current message, starting with a message by the user." : "El historial de mensajes de chat anterior al mensaje actual, comenzando con un mensaje de parte del usuario.",
"Input transcript" : "Transcripción de entrada",
"Transcription of the audio input" : "Transcripción de la entrada de audio",
"Response voice message" : "Mensaje de voz de respuesta",
@@ -347,6 +354,8 @@ OC.L10N.register(
"The audio to transcribe" : "El audio a transcribir",
"Transcription" : "Transcripción",
"The transcribed text" : "El texto transcrito",
+ "Chat by voice with an agent" : "Chatear a través de voz con un agente",
+ "Describe a task that you want the agent to do or ask a question." : "Describa una tarea que desea que el asistente realice o haga una pregunta.",
"Confirmation" : "Confirmación",
"Whether to confirm previously requested actions: 0 for denial and 1 for confirmation." : "Si se deben confirmar acciones solicitadas anteriormente: 0 para denegar y 1 para confirmar.",
"Conversation token" : "Token de conversación",
diff --git a/lib/l10n/es.json b/lib/l10n/es.json
index 29ee23631f9..fd606bfb8a0 100644
--- a/lib/l10n/es.json
+++ b/lib/l10n/es.json
@@ -127,6 +127,8 @@
"Settings" : "Ajustes",
"Log out" : "Cerrar sesión",
"Accounts" : "Cuentas",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Ver %s en Bluesky",
"Email" : "Correo electrónico",
"Mail %s" : "Correo %s",
"Fediverse" : "Fediverso",
@@ -273,6 +275,7 @@
"A valid Login must be provided" : "Se debe proporcionar un usuario válido",
"Login contains whitespace at the beginning or at the end" : "El usuario contiene espacios en blanco al inicio o al final",
"Login must not consist of dots only" : "El usuario no debe consistir sólo de puntos",
+ "Username is too long" : "El nombre de usuario es demasiado largo",
"Login is invalid because files already exist for this user" : "El nombre de inicio de sesión es inválido porque ya existen archivos para este usuario",
"Account disabled" : "Cuenta deshabilitada",
"Login canceled by app" : "Login cancelado por la app",
@@ -326,13 +329,17 @@
"Images" : "Imágenes",
"Images to ask a question about" : "Imágenes sobre las cuales se formulará una pregunta",
"Question" : "Pregunta",
+ "What to ask about the images." : "Que preguntar sobre las imágenes.",
"Generated response" : "Respuesta generada",
"The answer to the question" : "La respuesta a la pregunta",
+ "Audio chat" : "Chat de audio",
"Voice chat with the assistant" : "Chat de voz con el asistente",
"System prompt" : "Prompt del sistema",
"Define rules and assumptions that the assistant should follow during the conversation." : "Definir las reglas y supuestos que el asistente debe seguir durante la conversación.",
"Chat voice message" : "Mensaje de voz del chat",
+ "Describe a task that you want the assistant to do or ask a question." : "Describa una tarea que desea que el asistente realice o haga una pregunta.",
"Chat history" : "Historial de la conversación",
+ "The history of chat messages before the current message, starting with a message by the user." : "El historial de mensajes de chat anterior al mensaje actual, comenzando con un mensaje de parte del usuario.",
"Input transcript" : "Transcripción de entrada",
"Transcription of the audio input" : "Transcripción de la entrada de audio",
"Response voice message" : "Mensaje de voz de respuesta",
@@ -345,6 +352,8 @@
"The audio to transcribe" : "El audio a transcribir",
"Transcription" : "Transcripción",
"The transcribed text" : "El texto transcrito",
+ "Chat by voice with an agent" : "Chatear a través de voz con un agente",
+ "Describe a task that you want the agent to do or ask a question." : "Describa una tarea que desea que el asistente realice o haga una pregunta.",
"Confirmation" : "Confirmación",
"Whether to confirm previously requested actions: 0 for denial and 1 for confirmation." : "Si se deben confirmar acciones solicitadas anteriormente: 0 para denegar y 1 para confirmar.",
"Conversation token" : "Token de conversación",
diff --git a/lib/l10n/et_EE.js b/lib/l10n/et_EE.js
index 92080235dd5..8371f694041 100644
--- a/lib/l10n/et_EE.js
+++ b/lib/l10n/et_EE.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Seaded",
"Log out" : "Logi välja",
"Accounts" : "Kasutajakontod",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Vaata Bluesky's: %s",
"Email" : "E-post",
"Mail %s" : "E-post %s",
"Fediverse" : "Födiversum",
diff --git a/lib/l10n/et_EE.json b/lib/l10n/et_EE.json
index b8e16d36993..54d54acf434 100644
--- a/lib/l10n/et_EE.json
+++ b/lib/l10n/et_EE.json
@@ -127,6 +127,8 @@
"Settings" : "Seaded",
"Log out" : "Logi välja",
"Accounts" : "Kasutajakontod",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Vaata Bluesky's: %s",
"Email" : "E-post",
"Mail %s" : "E-post %s",
"Fediverse" : "Födiversum",
diff --git a/lib/l10n/ga.js b/lib/l10n/ga.js
index 2de5f97ab4c..1eb9610c265 100644
--- a/lib/l10n/ga.js
+++ b/lib/l10n/ga.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Socruithe",
"Log out" : "Logáil Amach",
"Accounts" : "Cuntais",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Féach %s ar Bluesky",
"Email" : "Ríomhphost",
"Mail %s" : "Ríomhphost %s",
"Fediverse" : "Feidearálach",
diff --git a/lib/l10n/ga.json b/lib/l10n/ga.json
index a81cebc37e0..c719a58f656 100644
--- a/lib/l10n/ga.json
+++ b/lib/l10n/ga.json
@@ -127,6 +127,8 @@
"Settings" : "Socruithe",
"Log out" : "Logáil Amach",
"Accounts" : "Cuntais",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Féach %s ar Bluesky",
"Email" : "Ríomhphost",
"Mail %s" : "Ríomhphost %s",
"Fediverse" : "Feidearálach",
diff --git a/lib/l10n/ja.js b/lib/l10n/ja.js
index 002812cb4a3..77759ed722f 100644
--- a/lib/l10n/ja.js
+++ b/lib/l10n/ja.js
@@ -324,18 +324,36 @@ OC.L10N.register(
"Storage is temporarily not available" : "ストレージは一時的に利用できません",
"Storage connection timeout. %s" : "ストレージへの接続がタイムアウト。 %s",
"To allow this check to run you have to make sure that your Web server can connect to itself. Therefore it must be able to resolve and connect to at least one of its `trusted_domains` or the `overwrite.cli.url`. This failure may be the result of a server-side DNS mismatch or outbound firewall rule." : "このチェックを実行させるには、Webサーバーが自分自身に接続できることを確認しなければならない。そのため、少なくとも一つの `trusted_domains` または `overwrite.cli.url` を解決して接続できなければなりません。この失敗は、サーバ側のDNSの不一致やアウトバウンドファイアウォールルールの結果かもしれません。",
+ "Analyze images" : "画像を分析する",
+ "Ask a question about the given images." : "与えられた画像について質問してください。",
"Images" : "画像",
+ "Images to ask a question about" : "質問したい画像",
"Question" : "質問",
+ "What to ask about the images." : "画像について質問すべきこと。",
"Generated response" : "生成された応答",
+ "The answer to the question" : "質問への回答",
+ "Audio chat" : "音声チャット",
+ "Voice chat with the assistant" : "アシスタントとの音声チャット",
"System prompt" : "システムプロンプト",
"Define rules and assumptions that the assistant should follow during the conversation." : "会話中にアシスタントが従うべきルールと前提条件を定義します。",
+ "Chat voice message" : "チャット音声メッセージ",
+ "Describe a task that you want the assistant to do or ask a question." : "アシスタントに実行してほしいタスクを説明するか質問を投げかけてください。",
"Chat history" : "チャット履歴",
+ "The history of chat messages before the current message, starting with a message by the user." : "現在のメッセージ以前に送信されたチャットメッセージの履歴で、ユーザーが送信したメッセージから始まるもの。",
+ "Input transcript" : "文字起こしを入力",
+ "Transcription of the audio input" : "音声入力の文字起こし",
+ "Response voice message" : "応答音声メッセージ",
+ "The generated voice response as part of the conversation" : "会話の一部として生成された音声応答",
+ "Output transcript" : "文字起こしを出力",
+ "Transcription of the audio output" : "音声出力の文字起こし",
"Transcribe audio" : "音声の書き起こし",
"Transcribe the things said in an audio" : "音声で言ったことを書き起こす",
"Audio input" : "音声入力",
"The audio to transcribe" : "文字起こしする音声",
"Transcription" : "書き起こし",
"The transcribed text" : "書き起こされたテキスト",
+ "Chat by voice with an agent" : "エージェントと音声でチャットする",
+ "Describe a task that you want the agent to do or ask a question." : "エージェントに実行してほしいタスクを説明するか質問を投げかけてください。エージェントと音声でチャットできます。",
"Confirmation" : "確認",
"Whether to confirm previously requested actions: 0 for denial and 1 for confirmation." : "以前に要求されたアクションを承認するかどうか: 0なら拒否、1なら承認。",
"Conversation token" : "会話トークン",
diff --git a/lib/l10n/ja.json b/lib/l10n/ja.json
index 1fdcad48c1c..31f59295d19 100644
--- a/lib/l10n/ja.json
+++ b/lib/l10n/ja.json
@@ -322,18 +322,36 @@
"Storage is temporarily not available" : "ストレージは一時的に利用できません",
"Storage connection timeout. %s" : "ストレージへの接続がタイムアウト。 %s",
"To allow this check to run you have to make sure that your Web server can connect to itself. Therefore it must be able to resolve and connect to at least one of its `trusted_domains` or the `overwrite.cli.url`. This failure may be the result of a server-side DNS mismatch or outbound firewall rule." : "このチェックを実行させるには、Webサーバーが自分自身に接続できることを確認しなければならない。そのため、少なくとも一つの `trusted_domains` または `overwrite.cli.url` を解決して接続できなければなりません。この失敗は、サーバ側のDNSの不一致やアウトバウンドファイアウォールルールの結果かもしれません。",
+ "Analyze images" : "画像を分析する",
+ "Ask a question about the given images." : "与えられた画像について質問してください。",
"Images" : "画像",
+ "Images to ask a question about" : "質問したい画像",
"Question" : "質問",
+ "What to ask about the images." : "画像について質問すべきこと。",
"Generated response" : "生成された応答",
+ "The answer to the question" : "質問への回答",
+ "Audio chat" : "音声チャット",
+ "Voice chat with the assistant" : "アシスタントとの音声チャット",
"System prompt" : "システムプロンプト",
"Define rules and assumptions that the assistant should follow during the conversation." : "会話中にアシスタントが従うべきルールと前提条件を定義します。",
+ "Chat voice message" : "チャット音声メッセージ",
+ "Describe a task that you want the assistant to do or ask a question." : "アシスタントに実行してほしいタスクを説明するか質問を投げかけてください。",
"Chat history" : "チャット履歴",
+ "The history of chat messages before the current message, starting with a message by the user." : "現在のメッセージ以前に送信されたチャットメッセージの履歴で、ユーザーが送信したメッセージから始まるもの。",
+ "Input transcript" : "文字起こしを入力",
+ "Transcription of the audio input" : "音声入力の文字起こし",
+ "Response voice message" : "応答音声メッセージ",
+ "The generated voice response as part of the conversation" : "会話の一部として生成された音声応答",
+ "Output transcript" : "文字起こしを出力",
+ "Transcription of the audio output" : "音声出力の文字起こし",
"Transcribe audio" : "音声の書き起こし",
"Transcribe the things said in an audio" : "音声で言ったことを書き起こす",
"Audio input" : "音声入力",
"The audio to transcribe" : "文字起こしする音声",
"Transcription" : "書き起こし",
"The transcribed text" : "書き起こされたテキスト",
+ "Chat by voice with an agent" : "エージェントと音声でチャットする",
+ "Describe a task that you want the agent to do or ask a question." : "エージェントに実行してほしいタスクを説明するか質問を投げかけてください。エージェントと音声でチャットできます。",
"Confirmation" : "確認",
"Whether to confirm previously requested actions: 0 for denial and 1 for confirmation." : "以前に要求されたアクションを承認するかどうか: 0なら拒否、1なら承認。",
"Conversation token" : "会話トークン",
diff --git a/lib/l10n/mk.js b/lib/l10n/mk.js
index 8b49f0914e5..102926f9e1d 100644
--- a/lib/l10n/mk.js
+++ b/lib/l10n/mk.js
@@ -77,6 +77,9 @@ OC.L10N.register(
"Empty file" : "Празна датотека",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модул со ИД: %s не постои. Овозможете го во параметрите на апликациите или контактирајте администратор.",
"Dot files are not allowed" : "Датотеки само со точки не се дозволени",
+ "%1$s (renamed)" : "%1$s (преименувано)",
+ "renamed file" : "преименувана датотека",
+ "Filenames must not end with \"%1$s\"." : "Името неможе да завршува со \"%1$s\".",
"File already exists" : "Датотека веќе постои",
"Invalid path" : "Невалидна патека",
"Failed to create file from template" : "Неуспешно креирање на датотека од шаблон",
diff --git a/lib/l10n/mk.json b/lib/l10n/mk.json
index db83bccd774..d9de1976caf 100644
--- a/lib/l10n/mk.json
+++ b/lib/l10n/mk.json
@@ -75,6 +75,9 @@
"Empty file" : "Празна датотека",
"Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "Модул со ИД: %s не постои. Овозможете го во параметрите на апликациите или контактирајте администратор.",
"Dot files are not allowed" : "Датотеки само со точки не се дозволени",
+ "%1$s (renamed)" : "%1$s (преименувано)",
+ "renamed file" : "преименувана датотека",
+ "Filenames must not end with \"%1$s\"." : "Името неможе да завршува со \"%1$s\".",
"File already exists" : "Датотека веќе постои",
"Invalid path" : "Невалидна патека",
"Failed to create file from template" : "Неуспешно креирање на датотека од шаблон",
diff --git a/lib/l10n/pl.js b/lib/l10n/pl.js
index 8392d2b48b8..eb66a56742e 100644
--- a/lib/l10n/pl.js
+++ b/lib/l10n/pl.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Ustawienia",
"Log out" : "Wyloguj",
"Accounts" : "Konta",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Wyświetl %s na Bluesky",
"Email" : "E-mail",
"Mail %s" : "Poczta %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json
index 10b4a8379a5..0138279e46b 100644
--- a/lib/l10n/pl.json
+++ b/lib/l10n/pl.json
@@ -127,6 +127,8 @@
"Settings" : "Ustawienia",
"Log out" : "Wyloguj",
"Accounts" : "Konta",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Wyświetl %s na Bluesky",
"Email" : "E-mail",
"Mail %s" : "Poczta %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/pt_BR.js b/lib/l10n/pt_BR.js
index e8373461705..0e28c2f9102 100644
--- a/lib/l10n/pt_BR.js
+++ b/lib/l10n/pt_BR.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Configurações",
"Log out" : "Sair",
"Accounts" : "Contas",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Ver %s no Bluesky",
"Email" : "E-mail",
"Mail %s" : "Enviar e-mail para %s",
"Fediverse" : "Fediverso",
diff --git a/lib/l10n/pt_BR.json b/lib/l10n/pt_BR.json
index 7293cdb880a..40b5c4e5807 100644
--- a/lib/l10n/pt_BR.json
+++ b/lib/l10n/pt_BR.json
@@ -127,6 +127,8 @@
"Settings" : "Configurações",
"Log out" : "Sair",
"Accounts" : "Contas",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Ver %s no Bluesky",
"Email" : "E-mail",
"Mail %s" : "Enviar e-mail para %s",
"Fediverse" : "Fediverso",
diff --git a/lib/l10n/sr.js b/lib/l10n/sr.js
index ac9d3bb0520..42f9cdf5fdb 100644
--- a/lib/l10n/sr.js
+++ b/lib/l10n/sr.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Поставке",
"Log out" : "Одјава",
"Accounts" : "Налози",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Прикажи %s на Bluesky",
"Email" : "Е-пошта",
"Mail %s" : "Пошта %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/sr.json b/lib/l10n/sr.json
index 0cf5937a91d..8d5a255ef96 100644
--- a/lib/l10n/sr.json
+++ b/lib/l10n/sr.json
@@ -127,6 +127,8 @@
"Settings" : "Поставке",
"Log out" : "Одјава",
"Accounts" : "Налози",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Прикажи %s на Bluesky",
"Email" : "Е-пошта",
"Mail %s" : "Пошта %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/sw.js b/lib/l10n/sw.js
index 0eca41da17e..293e9450d1d 100644
--- a/lib/l10n/sw.js
+++ b/lib/l10n/sw.js
@@ -1,9 +1,12 @@
OC.L10N.register(
"lib",
{
+ "Other activities" : "Other activities",
+ "%1$s and %2$s" : "%1$s na %2$s",
"Authentication" : "Uthibitisho",
"Unknown filetype" : "Aina ya faili haijulikani",
"Invalid image" : "Taswira si halisi",
+ "Avatar image is not square" : "Avatar image is not square",
"Files" : "Mafaili",
"View profile" : "Angalia wasifu",
"Local time: %s" : "Muda wa kawaida: %s",
@@ -22,12 +25,14 @@ OC.L10N.register(
"Settings" : "Mipangilio",
"Log out" : "Ondoka",
"Accounts" : "Akaunti",
+ "Bluesky" : "Bluesky",
"Email" : "Barua pepe",
"Phone" : "Simu",
"Twitter" : "Twitter",
"Website" : "Wavuti",
"Address" : "Anwani",
"About" : "Kuhusu",
+ "Display name" : "Display name",
"Additional settings" : "Mipangilio ya nyongeza",
"%1$s shared %2$s with you" : "%1$s ameshirikisha %2$s na wewe",
"Open %s" : "Fungua %s",
@@ -59,7 +64,9 @@ OC.L10N.register(
"Transcribe audio" : "Transcribe audio",
"Audio input" : "Audio input",
"Confirmation" : "Uthibitisho",
+ "Generate image" : "Generate image",
"Prompt" : "Prompt",
+ "Describe the image you want to generate" : "Describe the image you want to generate",
"Describe a task that you want the assistant to do or ask a question" : "Describe a task that you want the assistant to do or ask a question",
"The history of chat messages before the current message, starting with a message by the user" : "The history of chat messages before the current message, starting with a message by the user",
"Text" : "Maandishi",
diff --git a/lib/l10n/sw.json b/lib/l10n/sw.json
index 5759a8f07f4..708f4eb58cb 100644
--- a/lib/l10n/sw.json
+++ b/lib/l10n/sw.json
@@ -1,7 +1,10 @@
{ "translations": {
+ "Other activities" : "Other activities",
+ "%1$s and %2$s" : "%1$s na %2$s",
"Authentication" : "Uthibitisho",
"Unknown filetype" : "Aina ya faili haijulikani",
"Invalid image" : "Taswira si halisi",
+ "Avatar image is not square" : "Avatar image is not square",
"Files" : "Mafaili",
"View profile" : "Angalia wasifu",
"Local time: %s" : "Muda wa kawaida: %s",
@@ -20,12 +23,14 @@
"Settings" : "Mipangilio",
"Log out" : "Ondoka",
"Accounts" : "Akaunti",
+ "Bluesky" : "Bluesky",
"Email" : "Barua pepe",
"Phone" : "Simu",
"Twitter" : "Twitter",
"Website" : "Wavuti",
"Address" : "Anwani",
"About" : "Kuhusu",
+ "Display name" : "Display name",
"Additional settings" : "Mipangilio ya nyongeza",
"%1$s shared %2$s with you" : "%1$s ameshirikisha %2$s na wewe",
"Open %s" : "Fungua %s",
@@ -57,7 +62,9 @@
"Transcribe audio" : "Transcribe audio",
"Audio input" : "Audio input",
"Confirmation" : "Uthibitisho",
+ "Generate image" : "Generate image",
"Prompt" : "Prompt",
+ "Describe the image you want to generate" : "Describe the image you want to generate",
"Describe a task that you want the assistant to do or ask a question" : "Describe a task that you want the assistant to do or ask a question",
"The history of chat messages before the current message, starting with a message by the user" : "The history of chat messages before the current message, starting with a message by the user",
"Text" : "Maandishi",
diff --git a/lib/l10n/uk.js b/lib/l10n/uk.js
index 4e871b4130f..6bbd064cdef 100644
--- a/lib/l10n/uk.js
+++ b/lib/l10n/uk.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "Налаштування",
"Log out" : "Вихід",
"Accounts" : "Облікові записи",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Переглянути %s в Bluesky",
"Email" : "Електронна пошта",
"Mail %s" : "Пошта %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/uk.json b/lib/l10n/uk.json
index 285c39ff532..dec165c8acb 100644
--- a/lib/l10n/uk.json
+++ b/lib/l10n/uk.json
@@ -127,6 +127,8 @@
"Settings" : "Налаштування",
"Log out" : "Вихід",
"Accounts" : "Облікові записи",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "Переглянути %s в Bluesky",
"Email" : "Електронна пошта",
"Mail %s" : "Пошта %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/zh_CN.js b/lib/l10n/zh_CN.js
index bb1294601aa..5b78898b2db 100644
--- a/lib/l10n/zh_CN.js
+++ b/lib/l10n/zh_CN.js
@@ -1,7 +1,7 @@
OC.L10N.register(
"lib",
{
- "Cannot write into \"config\" directory!" : "无法写入 \"config\" 目录!",
+ "Cannot write into \"config\" directory!" : "无法写入“config”目录!",
"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",
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "设置",
"Log out" : "注销",
"Accounts" : "账号",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "在 Bluesky 上查看 %s",
"Email" : "电子邮箱",
"Mail %s" : "发邮件给 %s",
"Fediverse" : "联邦宇宙",
diff --git a/lib/l10n/zh_CN.json b/lib/l10n/zh_CN.json
index 28e3497decd..857e1ced95b 100644
--- a/lib/l10n/zh_CN.json
+++ b/lib/l10n/zh_CN.json
@@ -1,5 +1,5 @@
{ "translations": {
- "Cannot write into \"config\" directory!" : "无法写入 \"config\" 目录!",
+ "Cannot write into \"config\" directory!" : "无法写入“config”目录!",
"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",
@@ -127,6 +127,8 @@
"Settings" : "设置",
"Log out" : "注销",
"Accounts" : "账号",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "在 Bluesky 上查看 %s",
"Email" : "电子邮箱",
"Mail %s" : "发邮件给 %s",
"Fediverse" : "联邦宇宙",
diff --git a/lib/l10n/zh_HK.js b/lib/l10n/zh_HK.js
index 89b8457ef51..86f2a61e2ed 100644
--- a/lib/l10n/zh_HK.js
+++ b/lib/l10n/zh_HK.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "設定",
"Log out" : "登出",
"Accounts" : "帳戶",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "在 Bluesky 上查看 %s",
"Email" : "電郵地址",
"Mail %s" : "郵件 %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/zh_HK.json b/lib/l10n/zh_HK.json
index e9b3544b539..202410d95dd 100644
--- a/lib/l10n/zh_HK.json
+++ b/lib/l10n/zh_HK.json
@@ -127,6 +127,8 @@
"Settings" : "設定",
"Log out" : "登出",
"Accounts" : "帳戶",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "在 Bluesky 上查看 %s",
"Email" : "電郵地址",
"Mail %s" : "郵件 %s",
"Fediverse" : "Fediverse",
diff --git a/lib/l10n/zh_TW.js b/lib/l10n/zh_TW.js
index a12ad471433..c6ff09d8cac 100644
--- a/lib/l10n/zh_TW.js
+++ b/lib/l10n/zh_TW.js
@@ -129,6 +129,8 @@ OC.L10N.register(
"Settings" : "設定",
"Log out" : "登出",
"Accounts" : "帳號",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "在 Bluesky 上檢視 %s",
"Email" : "電子郵件",
"Mail %s" : "寄送郵件給 %s",
"Fediverse" : "聯邦宇宙",
diff --git a/lib/l10n/zh_TW.json b/lib/l10n/zh_TW.json
index 0e01a122735..01e63631f3b 100644
--- a/lib/l10n/zh_TW.json
+++ b/lib/l10n/zh_TW.json
@@ -127,6 +127,8 @@
"Settings" : "設定",
"Log out" : "登出",
"Accounts" : "帳號",
+ "Bluesky" : "Bluesky",
+ "View %s on Bluesky" : "在 Bluesky 上檢視 %s",
"Email" : "電子郵件",
"Mail %s" : "寄送郵件給 %s",
"Fediverse" : "聯邦宇宙",
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
index 9c7c35d4a6b..d00b1d2e9a3 100644
--- a/lib/private/Accounts/AccountManager.php
+++ b/lib/private/Accounts/AccountManager.php
@@ -78,6 +78,7 @@ class AccountManager implements IAccountManager {
self::PROPERTY_PRONOUNS => self::SCOPE_FEDERATED,
self::PROPERTY_ROLE => self::SCOPE_LOCAL,
self::PROPERTY_TWITTER => self::SCOPE_LOCAL,
+ self::PROPERTY_BLUESKY => self::SCOPE_LOCAL,
self::PROPERTY_WEBSITE => self::SCOPE_LOCAL,
];
@@ -564,6 +565,13 @@ class AccountManager implements IAccountManager {
],
[
+ 'name' => self::PROPERTY_BLUESKY,
+ 'value' => '',
+ 'scope' => $scopes[self::PROPERTY_BLUESKY],
+ 'verified' => self::NOT_VERIFIED,
+ ],
+
+ [
'name' => self::PROPERTY_FEDIVERSE,
'value' => '',
'scope' => $scopes[self::PROPERTY_FEDIVERSE],
@@ -713,6 +721,47 @@ class AccountManager implements IAccountManager {
}
}
+ private function validateBlueSkyHandle(string $text): bool {
+ if ($text === '') {
+ return true;
+ }
+
+ $lowerText = strtolower($text);
+
+ if ($lowerText === 'bsky.social') {
+ // "bsky.social" itself is not a valid handle
+ return false;
+ }
+
+ if (str_ends_with($lowerText, '.bsky.social')) {
+ $parts = explode('.', $lowerText);
+
+ // Must be exactly: username.bsky.social → 3 parts
+ if (count($parts) !== 3 || $parts[1] !== 'bsky' || $parts[2] !== 'social') {
+ return false;
+ }
+
+ $username = $parts[0];
+
+ // Must be 3–18 chars, alphanumeric/hyphen, no start/end hyphen
+ return preg_match('/^[a-z0-9][a-z0-9-]{2,17}$/', $username) === 1;
+ }
+
+ // Allow custom domains (Bluesky handle via personal domain)
+ return filter_var($text, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false;
+ }
+
+
+ private function sanitizePropertyBluesky(IAccountProperty $property): void {
+ if ($property->getName() === self::PROPERTY_BLUESKY) {
+ if (!$this->validateBlueSkyHandle($property->getValue())) {
+ throw new InvalidArgumentException(self::PROPERTY_BLUESKY);
+ }
+
+ $property->setValue($property->getValue());
+ }
+ }
+
/**
* @throws InvalidArgumentException If the property value is not a valid fediverse handle (username@instance where instance is a valid domain)
*/
@@ -805,6 +854,15 @@ class AccountManager implements IAccountManager {
}
try {
+ $property = $account->getProperty(self::PROPERTY_BLUESKY);
+ if ($property->getValue() !== '') {
+ $this->sanitizePropertyBluesky($property);
+ }
+ } catch (PropertyDoesNotExistException $e) {
+ // valid case, nothing to do
+ }
+
+ try {
$property = $account->getProperty(self::PROPERTY_FEDIVERSE);
if ($property->getValue() !== '') {
$this->sanitizePropertyFediverse($property);
diff --git a/lib/private/Activity/Event.php b/lib/private/Activity/Event.php
index 39cdc12b3fb..0ccad1d0a4e 100644
--- a/lib/private/Activity/Event.php
+++ b/lib/private/Activity/Event.php
@@ -450,7 +450,6 @@ class Event implements IEvent {
return
$this->getApp() !== ''
&& $this->getType() !== ''
- && $this->getAffectedUser() !== ''
&& $this->getTimestamp() !== 0
/**
* Disabled for BC with old activities
diff --git a/lib/private/Activity/Manager.php b/lib/private/Activity/Manager.php
index 4e10f8a0c1a..7471b7d708a 100644
--- a/lib/private/Activity/Manager.php
+++ b/lib/private/Activity/Manager.php
@@ -11,12 +11,14 @@ use OCP\Activity\ActivitySettings;
use OCP\Activity\Exceptions\FilterNotFoundException;
use OCP\Activity\Exceptions\IncompleteActivityException;
use OCP\Activity\Exceptions\SettingNotFoundException;
+use OCP\Activity\IBulkConsumer;
use OCP\Activity\IConsumer;
use OCP\Activity\IEvent;
use OCP\Activity\IFilter;
use OCP\Activity\IManager;
use OCP\Activity\IProvider;
use OCP\Activity\ISetting;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
@@ -46,6 +48,7 @@ class Manager implements IManager {
protected IValidator $validator,
protected IRichTextFormatter $richTextFormatter,
protected IL10N $l10n,
+ protected ITimeFactory $timeFactory,
) {
}
@@ -96,6 +99,31 @@ class Manager implements IManager {
* {@inheritDoc}
*/
public function publish(IEvent $event): void {
+ if ($event->getAuthor() === '' && $this->session->getUser() instanceof IUser) {
+ $event->setAuthor($this->session->getUser()->getUID());
+ }
+
+ if (!$event->getTimestamp()) {
+ $event->setTimestamp($this->timeFactory->getTime());
+ }
+
+ if ($event->getAffectedUser() === '' || !$event->isValid()) {
+ throw new IncompleteActivityException('The given event is invalid');
+ }
+
+ foreach ($this->getConsumers() as $c) {
+ $c->receive($event);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function bulkPublish(IEvent $event, array $affectedUserIds, ISetting $setting): void {
+ if (empty($affectedUserIds)) {
+ throw new IncompleteActivityException('The given event is invalid');
+ }
+
if ($event->getAuthor() === '') {
if ($this->session->getUser() instanceof IUser) {
$event->setAuthor($this->session->getUser()->getUID());
@@ -103,7 +131,7 @@ class Manager implements IManager {
}
if (!$event->getTimestamp()) {
- $event->setTimestamp(time());
+ $event->setTimestamp($this->timeFactory->getTime());
}
if (!$event->isValid()) {
@@ -111,10 +139,17 @@ class Manager implements IManager {
}
foreach ($this->getConsumers() as $c) {
- $c->receive($event);
+ if ($c instanceof IBulkConsumer) {
+ $c->bulkReceive($event, $affectedUserIds, $setting);
+ }
+ foreach ($affectedUserIds as $affectedUserId) {
+ $event->setAffectedUser($affectedUserId);
+ $c->receive($event);
+ }
}
}
+
/**
* In order to improve lazy loading a closure can be registered which will be called in case
* activity consumers are actually requested
diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php
index 2280ac1a79f..cef612536d6 100644
--- a/lib/private/AppConfig.php
+++ b/lib/private/AppConfig.php
@@ -13,9 +13,9 @@ use InvalidArgumentException;
use JsonException;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Config\ConfigManager;
+use OC\Config\PresetManager;
use OCP\Config\Lexicon\Entry;
use OCP\Config\Lexicon\ILexicon;
-use OCP\Config\Lexicon\Preset;
use OCP\Config\Lexicon\Strictness;
use OCP\Config\ValueType;
use OCP\DB\Exception as DBException;
@@ -27,7 +27,6 @@ use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Security\ICrypto;
-use OCP\Server;
use Psr\Log\LoggerInterface;
/**
@@ -66,13 +65,14 @@ class AppConfig implements IAppConfig {
/** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
private array $configLexiconDetails = [];
private bool $ignoreLexiconAliases = false;
- private ?Preset $configLexiconPreset = null;
/** @var ?array<string, string> */
private ?array $appVersionsCache = null;
public function __construct(
protected IDBConnection $connection,
protected IConfig $config,
+ private readonly ConfigManager $configManager,
+ private readonly PresetManager $presetManager,
protected LoggerInterface $logger,
protected ICrypto $crypto,
) {
@@ -520,8 +520,7 @@ class AppConfig implements IAppConfig {
// interested to check options in case a modification of the value is needed
// ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
if ($origKey !== $key && $type === self::VALUE_BOOL) {
- $configManager = Server::get(ConfigManager::class);
- $value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
+ $value = ($this->configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
}
return $value;
@@ -1108,7 +1107,7 @@ class AppConfig implements IAppConfig {
$this->assertParams($app, $key);
try {
$details = $this->getDetails($app, $key);
- } catch (AppConfigUnknownKeyException $e) {
+ } catch (AppConfigUnknownKeyException) {
$details = [
'app' => $app,
'key' => $key
@@ -1129,13 +1128,13 @@ class AppConfig implements IAppConfig {
'valueType' => $lexiconEntry->getValueType(),
'valueTypeName' => $lexiconEntry->getValueType()->name,
'sensitive' => $lexiconEntry->isFlagged(self::FLAG_SENSITIVE),
- 'default' => $lexiconEntry->getDefault($this->getLexiconPreset()),
+ 'default' => $lexiconEntry->getDefault($this->presetManager->getLexiconPreset()),
'definition' => $lexiconEntry->getDefinition(),
'note' => $lexiconEntry->getNote(),
]);
}
- return array_filter($details);
+ return array_filter($details, static fn ($v): bool => ($v !== null));
}
/**
@@ -1228,7 +1227,6 @@ class AppConfig implements IAppConfig {
public function clearCache(bool $reload = false): void {
$this->lazyLoaded = $this->fastLoaded = false;
$this->lazyCache = $this->fastCache = $this->valueTypes = $this->configLexiconDetails = [];
- $this->configLexiconPreset = null;
if (!$reload) {
return;
@@ -1714,7 +1712,7 @@ class AppConfig implements IAppConfig {
$lazy = $lexiconEntry->isLazy();
// only look for default if needed, default from Lexicon got priority
if ($default !== null) {
- $default = $lexiconEntry->getDefault($this->getLexiconPreset()) ?? $default;
+ $default = $lexiconEntry->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
}
if ($lexiconEntry->isFlagged(self::FLAG_SENSITIVE)) {
@@ -1802,14 +1800,6 @@ class AppConfig implements IAppConfig {
$this->ignoreLexiconAliases = $ignore;
}
- private function getLexiconPreset(): Preset {
- if ($this->configLexiconPreset === null) {
- $this->configLexiconPreset = Preset::tryFrom($this->config->getSystemValueInt(ConfigManager::PRESET_CONFIGKEY, 0)) ?? Preset::NONE;
- }
-
- return $this->configLexiconPreset;
- }
-
/**
* Returns the installed versions of all apps
*
diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php
index e719ea19f90..7bf32852209 100644
--- a/lib/private/AppFramework/App.php
+++ b/lib/private/AppFramework/App.php
@@ -50,19 +50,8 @@ class App {
if (isset($appInfo['namespace'])) {
self::$nameSpaceCache[$appId] = trim($appInfo['namespace']);
} else {
- if ($appId !== 'spreed') {
- // if the tag is not found, fall back to uppercasing the first letter
- self::$nameSpaceCache[$appId] = ucfirst($appId);
- } else {
- // For the Talk app (appid spreed) the above fallback doesn't work.
- // This leads to a problem when trying to install it freshly,
- // because the apps namespace is already registered before the
- // app is downloaded from the appstore, because of the hackish
- // global route index.php/call/{token} which is registered via
- // the core/routes.php so it does not have the app namespace.
- // @ref https://github.com/nextcloud/server/pull/19433
- self::$nameSpaceCache[$appId] = 'Talk';
- }
+ // if the tag is not found, fall back to uppercasing the first letter
+ self::$nameSpaceCache[$appId] = ucfirst($appId);
}
return $topNamespace . self::$nameSpaceCache[$appId];
@@ -82,7 +71,6 @@ class App {
return null;
}
-
/**
* Shortcut for calling a controller method and printing the result
*
@@ -93,7 +81,12 @@ class App {
* @param array $urlParams list of URL parameters (optional)
* @throws HintException
*/
- public static function main(string $controllerName, string $methodName, DIContainer $container, ?array $urlParams = null) {
+ public static function main(
+ string $controllerName,
+ string $methodName,
+ DIContainer $container,
+ ?array $urlParams = null,
+ ): void {
/** @var IProfiler $profiler */
$profiler = $container->get(IProfiler::class);
$eventLogger = $container->get(IEventLogger::class);
@@ -145,8 +138,7 @@ class App {
$eventLogger->start('app:controller:dispatcher', 'Initialize dispatcher and pre-middleware');
// initialize the dispatcher and run all the middleware before the controller
- /** @var Dispatcher $dispatcher */
- $dispatcher = $container['Dispatcher'];
+ $dispatcher = $container->get(Dispatcher::class);
$eventLogger->end('app:controller:dispatcher');
@@ -222,25 +214,4 @@ class App {
}
}
}
-
- /**
- * Shortcut for calling a controller method and printing the result.
- * Similar to App:main except that no headers will be sent.
- *
- * @param string $controllerName the name of the controller under which it is
- * stored in the DI container
- * @param string $methodName the method that you want to call
- * @param array $urlParams an array with variables extracted from the routes
- * @param DIContainer $container an instance of a pimple container.
- */
- public static function part(string $controllerName, string $methodName, array $urlParams,
- DIContainer $container) {
- $container['urlParams'] = $urlParams;
- $controller = $container[$controllerName];
-
- $dispatcher = $container['Dispatcher'];
-
- [, , $output] = $dispatcher->dispatch($controller, $methodName);
- return $output;
- }
}
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 5ccc1b7d348..76261fe6b92 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -38,6 +38,7 @@ use OC\Log\PsrLoggerAdapter;
use OC\ServerContainer;
use OC\Settings\AuthorizedGroupMapper;
use OCA\WorkflowEngine\Manager;
+use OCP\App\IAppManager;
use OCP\AppFramework\Http\IOutput;
use OCP\AppFramework\IAppContainer;
use OCP\AppFramework\QueryException;
@@ -63,7 +64,7 @@ use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class DIContainer extends SimpleContainer implements IAppContainer {
- private string $appName;
+ protected string $appName;
private array $middleWares = [];
private ServerContainer $server;
@@ -152,7 +153,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$this->registerDeprecatedAlias('Dispatcher', Dispatcher::class);
$this->registerService(Dispatcher::class, function (ContainerInterface $c) {
return new Dispatcher(
- $c->get('Protocol'),
+ $c->get(Http::class),
$c->get(MiddlewareDispatcher::class),
$c->get(IControllerMethodReflector::class),
$c->get(IRequest::class),
@@ -200,7 +201,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$server->getUserSession()->isLoggedIn(),
$c->get(IGroupManager::class),
$c->get(ISubAdmin::class),
- $server->getAppManager(),
+ $c->get(IAppManager::class),
$server->getL10N('lib'),
$c->get(AuthorizedGroupMapper::class),
$c->get(IUserSession::class),
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index ed26e75ec89..0db3bfc1c77 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -196,7 +196,9 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
$this->registerService($alias, function (ContainerInterface $container) use ($target, $alias): mixed {
try {
$logger = $container->get(LoggerInterface::class);
- $logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']);
+ $logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', [
+ 'app' => $this->appName ?? 'serverDI',
+ ]);
} catch (ContainerExceptionInterface $e) {
// Could not get logger. Continue
}
diff --git a/lib/private/Avatar/Avatar.php b/lib/private/Avatar/Avatar.php
index 7aa2d220b88..dc65c9d5743 100644
--- a/lib/private/Avatar/Avatar.php
+++ b/lib/private/Avatar/Avatar.php
@@ -10,17 +10,17 @@ declare(strict_types=1);
namespace OC\Avatar;
use Imagick;
+use OC\User\User;
use OCP\Color;
use OCP\Files\NotFoundException;
use OCP\IAvatar;
+use OCP\IConfig;
use Psr\Log\LoggerInterface;
/**
* This class gets and sets users avatars.
*/
abstract class Avatar implements IAvatar {
- protected LoggerInterface $logger;
-
/**
* https://github.com/sebdesign/cap-height -- for 500px height
* Automated check: https://codepen.io/skjnldsv/pen/PydLBK/
@@ -35,8 +35,10 @@ abstract class Avatar implements IAvatar {
<text x="50%" y="350" style="font-weight:normal;font-size:280px;font-family:\'Noto Sans\';text-anchor:middle;fill:#{fgFill}">{letter}</text>
</svg>';
- public function __construct(LoggerInterface $logger) {
- $this->logger = $logger;
+ public function __construct(
+ protected IConfig $config,
+ protected LoggerInterface $logger,
+ ) {
}
/**
@@ -84,8 +86,7 @@ abstract class Avatar implements IAvatar {
* @return string
*
*/
- protected function getAvatarVector(int $size, bool $darkTheme): string {
- $userDisplayName = $this->getDisplayName();
+ protected function getAvatarVector(string $userDisplayName, int $size, bool $darkTheme): string {
$fgRGB = $this->avatarBackgroundColor($userDisplayName);
$bgRGB = $fgRGB->alphaBlending(0.1, $darkTheme ? new Color(0, 0, 0) : new Color(255, 255, 255));
$fill = sprintf('%02x%02x%02x', $bgRGB->red(), $bgRGB->green(), $bgRGB->blue());
@@ -96,9 +97,30 @@ abstract class Avatar implements IAvatar {
}
/**
+ * Select the rendering font based on the user's display name and language
+ */
+ private function getFont(string $userDisplayName): string {
+ if (preg_match('/\p{Han}/u', $userDisplayName) === 1) {
+ switch ($this->getAvatarLanguage()) {
+ case 'zh_TW':
+ return __DIR__ . '/../../../core/fonts/NotoSansTC-Regular.ttf';
+ case 'zh_HK':
+ return __DIR__ . '/../../../core/fonts/NotoSansHK-Regular.ttf';
+ case 'ja':
+ return __DIR__ . '/../../../core/fonts/NotoSansJP-Regular.ttf';
+ case 'ko':
+ return __DIR__ . '/../../../core/fonts/NotoSansKR-Regular.ttf';
+ default:
+ return __DIR__ . '/../../../core/fonts/NotoSansSC-Regular.ttf';
+ }
+ }
+ return __DIR__ . '/../../../core/fonts/NotoSans-Regular.ttf';
+ }
+
+ /**
* Generate png avatar from svg with Imagick
*/
- protected function generateAvatarFromSvg(int $size, bool $darkTheme): ?string {
+ protected function generateAvatarFromSvg(string $userDisplayName, int $size, bool $darkTheme): ?string {
if (!extension_loaded('imagick')) {
return null;
}
@@ -107,9 +129,10 @@ abstract class Avatar implements IAvatar {
if (in_array('RSVG', $formats, true)) {
return null;
}
+ $text = $this->getAvatarText();
try {
- $font = __DIR__ . '/../../../core/fonts/NotoSans-Regular.ttf';
- $svg = $this->getAvatarVector($size, $darkTheme);
+ $font = $this->getFont($text);
+ $svg = $this->getAvatarVector($userDisplayName, $size, $darkTheme);
$avatar = new Imagick();
$avatar->setFont($font);
$avatar->readImageBlob($svg);
@@ -151,7 +174,7 @@ abstract class Avatar implements IAvatar {
}
imagefilledrectangle($im, 0, 0, $size, $size, $background);
- $font = __DIR__ . '/../../../core/fonts/NotoSans-Regular.ttf';
+ $font = $this->getFont($text);
$fontSize = $size * 0.4;
[$x, $y] = $this->imageTTFCenter(
@@ -258,4 +281,12 @@ abstract class Avatar implements IAvatar {
return $finalPalette[$this->hashToInt($hash, $steps * 3)];
}
+
+ /**
+ * Get the language to be used for avatar generation.
+ * This is used to determine the font to use for the avatar text (e.g. CJK characters).
+ */
+ protected function getAvatarLanguage(): string {
+ return $this->config->getSystemValueString('default_language', 'en');
+ }
}
diff --git a/lib/private/Avatar/AvatarManager.php b/lib/private/Avatar/AvatarManager.php
index 60a3d358bf4..c68467085f0 100644
--- a/lib/private/Avatar/AvatarManager.php
+++ b/lib/private/Avatar/AvatarManager.php
@@ -92,10 +92,10 @@ class AvatarManager implements IAvatarManager {
return new UserAvatar($folder, $this->l, $user, $this->logger, $this->config);
default:
// use a placeholder avatar which caches the generated images
- return new PlaceholderAvatar($folder, $user, $this->logger);
+ return new PlaceholderAvatar($folder, $user, $this->config, $this->logger);
}
- return new PlaceholderAvatar($folder, $user, $this->logger);
+ return new PlaceholderAvatar($folder, $user, $this->config, $this->logger);
}
/**
@@ -129,6 +129,6 @@ class AvatarManager implements IAvatarManager {
* @param string $name The guest name, e.g. "Albert".
*/
public function getGuestAvatar(string $name): IAvatar {
- return new GuestAvatar($name, $this->logger);
+ return new GuestAvatar($name, $this->config, $this->logger);
}
}
diff --git a/lib/private/Avatar/GuestAvatar.php b/lib/private/Avatar/GuestAvatar.php
index 7ae633f1260..c0c7de0c078 100644
--- a/lib/private/Avatar/GuestAvatar.php
+++ b/lib/private/Avatar/GuestAvatar.php
@@ -10,6 +10,7 @@ namespace OC\Avatar;
use OCP\Files\SimpleFS\InMemoryFile;
use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\IConfig;
use Psr\Log\LoggerInterface;
/**
@@ -23,9 +24,10 @@ class GuestAvatar extends Avatar {
*/
public function __construct(
private string $userDisplayName,
+ IConfig $config,
LoggerInterface $logger,
) {
- parent::__construct($logger);
+ parent::__construct($config, $logger);
}
/**
diff --git a/lib/private/Avatar/PlaceholderAvatar.php b/lib/private/Avatar/PlaceholderAvatar.php
index 07c54f62713..f5f49fb7cb2 100644
--- a/lib/private/Avatar/PlaceholderAvatar.php
+++ b/lib/private/Avatar/PlaceholderAvatar.php
@@ -14,6 +14,7 @@ use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
+use OCP\IConfig;
use OCP\IImage;
use Psr\Log\LoggerInterface;
@@ -27,9 +28,10 @@ class PlaceholderAvatar extends Avatar {
public function __construct(
private ISimpleFolder $folder,
private User $user,
+ IConfig $config,
LoggerInterface $logger,
) {
- parent::__construct($logger);
+ parent::__construct($config, $logger);
}
/**
@@ -87,8 +89,9 @@ class PlaceholderAvatar extends Avatar {
throw new NotFoundException;
}
- if (!$data = $this->generateAvatarFromSvg($size, $darkTheme)) {
- $data = $this->generateAvatar($this->getDisplayName(), $size, $darkTheme);
+ $userDisplayName = $this->getDisplayName();
+ if (!$data = $this->generateAvatarFromSvg($userDisplayName, $size, $darkTheme)) {
+ $data = $this->generateAvatar($userDisplayName, $size, $darkTheme);
}
try {
diff --git a/lib/private/Avatar/UserAvatar.php b/lib/private/Avatar/UserAvatar.php
index bef0a20e7b8..aca2aa574bc 100644
--- a/lib/private/Avatar/UserAvatar.php
+++ b/lib/private/Avatar/UserAvatar.php
@@ -26,11 +26,11 @@ class UserAvatar extends Avatar {
public function __construct(
private ISimpleFolder $folder,
private IL10N $l,
- private User $user,
+ protected User $user,
LoggerInterface $logger,
- private IConfig $config,
+ IConfig $config,
) {
- parent::__construct($logger);
+ parent::__construct($config, $logger);
}
/**
@@ -201,8 +201,9 @@ class UserAvatar extends Avatar {
try {
$ext = $this->getExtension($generated, $darkTheme);
} catch (NotFoundException $e) {
- if (!$data = $this->generateAvatarFromSvg(1024, $darkTheme)) {
- $data = $this->generateAvatar($this->getDisplayName(), 1024, $darkTheme);
+ $userDisplayName = $this->getDisplayName();
+ if (!$data = $this->generateAvatarFromSvg($userDisplayName, 1024, $darkTheme)) {
+ $data = $this->generateAvatar($userDisplayName, 1024, $darkTheme);
}
$avatar = $this->folder->newFile($darkTheme ? 'avatar-dark.png' : 'avatar.png');
$avatar->putContent($data);
@@ -234,8 +235,9 @@ class UserAvatar extends Avatar {
throw new NotFoundException;
}
if ($generated) {
- if (!$data = $this->generateAvatarFromSvg($size, $darkTheme)) {
- $data = $this->generateAvatar($this->getDisplayName(), $size, $darkTheme);
+ $userDisplayName = $this->getDisplayName();
+ if (!$data = $this->generateAvatarFromSvg($userDisplayName, $size, $darkTheme)) {
+ $data = $this->generateAvatar($userDisplayName, $size, $darkTheme);
}
} else {
$avatar = new \OCP\Image();
@@ -293,4 +295,9 @@ class UserAvatar extends Avatar {
public function isCustomAvatar(): bool {
return $this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', 'false') !== 'true';
}
+
+ #[\Override]
+ protected function getAvatarLanguage(): string {
+ return $this->config->getUserValue($this->user->getUID(), 'core', 'lang', parent::getAvatarLanguage());
+ }
}
diff --git a/lib/private/Config/ConfigManager.php b/lib/private/Config/ConfigManager.php
index ed516abdcbf..28397402249 100644
--- a/lib/private/Config/ConfigManager.php
+++ b/lib/private/Config/ConfigManager.php
@@ -14,10 +14,8 @@ use OCP\App\IAppManager;
use OCP\Config\Exceptions\TypeConflictException;
use OCP\Config\IUserConfig;
use OCP\Config\Lexicon\Entry;
-use OCP\Config\Lexicon\Preset;
use OCP\Config\ValueType;
use OCP\IAppConfig;
-use OCP\IConfig;
use OCP\Server;
use Psr\Log\LoggerInterface;
@@ -27,20 +25,23 @@ use Psr\Log\LoggerInterface;
* @since 32.0.0
*/
class ConfigManager {
- /** @since 32.0.0 */
- public const PRESET_CONFIGKEY = 'config_preset';
-
/** @var AppConfig|null $appConfig */
private ?IAppConfig $appConfig = null;
/** @var UserConfig|null $userConfig */
private ?IUserConfig $userConfig = null;
public function __construct(
- private readonly IConfig $config,
private readonly LoggerInterface $logger,
) {
}
+ public function clearConfigCaches(): void {
+ $this->loadConfigServices();
+ $this->appConfig->clearCache();
+ $this->userConfig->clearCacheAll();
+ }
+
+
/**
* Use the rename values from the list of ConfigLexiconEntry defined in each app ConfigLexicon
* to migrate config value to a new config key.
@@ -82,17 +83,6 @@ class ConfigManager {
}
/**
- * store in config.php the new preset
- * refresh cached preset
- */
- public function setLexiconPreset(Preset $preset): void {
- $this->config->setSystemValue(self::PRESET_CONFIGKEY, $preset->value);
- $this->loadConfigServices();
- $this->appConfig->clearCache();
- $this->userConfig->clearCacheAll();
- }
-
- /**
* config services cannot be load at __construct() or install will fail
*/
private function loadConfigServices(): void {
diff --git a/lib/private/Config/PresetManager.php b/lib/private/Config/PresetManager.php
new file mode 100644
index 00000000000..d9c029d15c2
--- /dev/null
+++ b/lib/private/Config/PresetManager.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Config;
+
+use OCP\Config\Lexicon\Preset;
+use OCP\IConfig;
+
+/**
+ * tools to maintains configurations
+ */
+class PresetManager {
+ private const PRESET_CONFIGKEY = 'config_preset';
+
+ private ?Preset $configLexiconPreset = null;
+
+ public function __construct(
+ private readonly IConfig $config,
+ private readonly ConfigManager $configManager,
+ ) {
+ }
+
+ /**
+ * store in config.php the new preset
+ * refresh cached preset
+ */
+ public function setLexiconPreset(Preset $preset): void {
+ $this->config->setSystemValue(self::PRESET_CONFIGKEY, $preset->value);
+ $this->configLexiconPreset = $preset;
+ $this->configManager->clearConfigCaches();
+ }
+
+ /**
+ * returns currently selected Preset
+ */
+ public function getLexiconPreset(): Preset {
+ if ($this->configLexiconPreset === null) {
+ $this->configLexiconPreset = Preset::tryFrom($this->config->getSystemValueInt(self::PRESET_CONFIGKEY, 0)) ?? Preset::NONE;
+ }
+
+ return $this->configLexiconPreset;
+ }
+}
diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php
index 04ba0e29db0..4ddad3ec2f2 100644
--- a/lib/private/Config/UserConfig.php
+++ b/lib/private/Config/UserConfig.php
@@ -18,7 +18,6 @@ use OCP\Config\Exceptions\UnknownKeyException;
use OCP\Config\IUserConfig;
use OCP\Config\Lexicon\Entry;
use OCP\Config\Lexicon\ILexicon;
-use OCP\Config\Lexicon\Preset;
use OCP\Config\Lexicon\Strictness;
use OCP\Config\ValueType;
use OCP\DB\Exception as DBException;
@@ -27,7 +26,6 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Security\ICrypto;
-use OCP\Server;
use Psr\Log\LoggerInterface;
/**
@@ -68,11 +66,12 @@ class UserConfig implements IUserConfig {
/** @var array<string, array{entries: array<string, Entry>, aliases: array<string, string>, strictness: Strictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
private array $configLexiconDetails = [];
private bool $ignoreLexiconAliases = false;
- private ?Preset $configLexiconPreset = null;
public function __construct(
protected IDBConnection $connection,
protected IConfig $config,
+ private readonly ConfigManager $configManager,
+ private readonly PresetManager $presetManager,
protected LoggerInterface $logger,
protected ICrypto $crypto,
) {
@@ -772,8 +771,7 @@ class UserConfig implements IUserConfig {
// interested to check options in case a modification of the value is needed
// ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
if ($origKey !== $key && $type === ValueType::BOOL) {
- $configManager = Server::get(ConfigManager::class);
- $value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
+ $value = ($this->configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
}
return $value;
@@ -1636,7 +1634,6 @@ class UserConfig implements IUserConfig {
public function clearCacheAll(): void {
$this->lazyLoaded = $this->fastLoaded = [];
$this->lazyCache = $this->fastCache = $this->valueDetails = $this->configLexiconDetails = [];
- $this->configLexiconPreset = null;
}
/**
@@ -1937,7 +1934,7 @@ class UserConfig implements IUserConfig {
// only look for default if needed, default from Lexicon got priority if not overwritten by admin
if ($default !== null) {
- $default = $this->getSystemDefault($app, $configValue) ?? $configValue->getDefault($this->getLexiconPreset()) ?? $default;
+ $default = $this->getSystemDefault($app, $configValue) ?? $configValue->getDefault($this->presetManager->getLexiconPreset()) ?? $default;
}
// returning false will make get() returning $default and set() not changing value in database
@@ -2039,12 +2036,4 @@ class UserConfig implements IUserConfig {
public function ignoreLexiconAliases(bool $ignore): void {
$this->ignoreLexiconAliases = $ignore;
}
-
- private function getLexiconPreset(): Preset {
- if ($this->configLexiconPreset === null) {
- $this->configLexiconPreset = Preset::tryFrom($this->config->getSystemValueInt(ConfigManager::PRESET_CONFIGKEY, 0)) ?? Preset::NONE;
- }
-
- return $this->configLexiconPreset;
- }
}
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index 88bdc377e2b..f86cbc341a4 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -16,6 +16,7 @@ use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\ConnectionLost;
+use Doctrine\DBAL\Platforms\MariaDBPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
@@ -915,11 +916,13 @@ class Connection extends PrimaryReadReplicaConnection {
}
/**
- * @return IDBConnection::PLATFORM_MYSQL|IDBConnection::PLATFORM_ORACLE|IDBConnection::PLATFORM_POSTGRES|IDBConnection::PLATFORM_SQLITE
+ * @return IDBConnection::PLATFORM_MYSQL|IDBConnection::PLATFORM_ORACLE|IDBConnection::PLATFORM_POSTGRES|IDBConnection::PLATFORM_SQLITE|IDBConnection::PLATFORM_MARIADB
*/
- public function getDatabaseProvider(): string {
+ public function getDatabaseProvider(bool $strict = false): string {
$platform = $this->getDatabasePlatform();
- if ($platform instanceof MySQLPlatform) {
+ if ($strict && $platform instanceof MariaDBPlatform) {
+ return IDBConnection::PLATFORM_MARIADB;
+ } elseif ($platform instanceof MySQLPlatform) {
return IDBConnection::PLATFORM_MYSQL;
} elseif ($platform instanceof OraclePlatform) {
return IDBConnection::PLATFORM_ORACLE;
diff --git a/lib/private/DB/ConnectionAdapter.php b/lib/private/DB/ConnectionAdapter.php
index 78ca780f218..d9ccb3c54f2 100644
--- a/lib/private/DB/ConnectionAdapter.php
+++ b/lib/private/DB/ConnectionAdapter.php
@@ -237,10 +237,10 @@ class ConnectionAdapter implements IDBConnection {
}
/**
- * @return self::PLATFORM_MYSQL|self::PLATFORM_ORACLE|self::PLATFORM_POSTGRES|self::PLATFORM_SQLITE
+ * @return self::PLATFORM_MYSQL|self::PLATFORM_ORACLE|self::PLATFORM_POSTGRES|self::PLATFORM_SQLITE|self::PLATFORM_MARIADB
*/
- public function getDatabaseProvider(): string {
- return $this->inner->getDatabaseProvider();
+ public function getDatabaseProvider(bool $strict = false): string {
+ return $this->inner->getDatabaseProvider($strict);
}
/**
diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php
index 8fae6275916..47a8eaa6fd0 100644
--- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php
+++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php
@@ -81,12 +81,12 @@ class OCIFunctionBuilder extends FunctionBuilder {
public function octetLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
- return new QueryFunction('LENGTHB(' . $quotedName . ')' . $alias);
+ return new QueryFunction('COALESCE(LENGTHB(' . $quotedName . '), 0)' . $alias);
}
public function charLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
- return new QueryFunction('LENGTH(' . $quotedName . ')' . $alias);
+ return new QueryFunction('COALESCE(LENGTH(' . $quotedName . '), 0)' . $alias);
}
}
diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php
index 8b224c28dfe..1d44c049793 100644
--- a/lib/private/DB/QueryBuilder/QueryBuilder.php
+++ b/lib/private/DB/QueryBuilder/QueryBuilder.php
@@ -96,6 +96,7 @@ class QueryBuilder implements IQueryBuilder {
return match($this->connection->getDatabaseProvider()) {
IDBConnection::PLATFORM_ORACLE => new OCIExpressionBuilder($this->connection, $this, $this->logger),
IDBConnection::PLATFORM_POSTGRES => new PgSqlExpressionBuilder($this->connection, $this, $this->logger),
+ IDBConnection::PLATFORM_MARIADB,
IDBConnection::PLATFORM_MYSQL => new MySqlExpressionBuilder($this->connection, $this, $this->logger),
IDBConnection::PLATFORM_SQLITE => new SqliteExpressionBuilder($this->connection, $this, $this->logger),
};
@@ -121,6 +122,7 @@ class QueryBuilder implements IQueryBuilder {
return match($this->connection->getDatabaseProvider()) {
IDBConnection::PLATFORM_ORACLE => new OCIFunctionBuilder($this->connection, $this, $this->helper),
IDBConnection::PLATFORM_POSTGRES => new PgSqlFunctionBuilder($this->connection, $this, $this->helper),
+ IDBConnection::PLATFORM_MARIADB,
IDBConnection::PLATFORM_MYSQL => new FunctionBuilder($this->connection, $this, $this->helper),
IDBConnection::PLATFORM_SQLITE => new SqliteFunctionBuilder($this->connection, $this, $this->helper),
};
@@ -161,7 +163,7 @@ class QueryBuilder implements IQueryBuilder {
try {
$params = [];
foreach ($this->getParameters() as $placeholder => $value) {
- if ($value instanceof \DateTime) {
+ if ($value instanceof \DateTimeInterface) {
$params[] = $placeholder . ' => DateTime:\'' . $value->format('c') . '\'';
} elseif (is_array($value)) {
$params[] = $placeholder . ' => (\'' . implode('\', \'', $value) . '\')';
diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php
index d82ec08f362..76afca9dee8 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -415,7 +415,7 @@ class Root extends Folder implements IRootFolder {
*/
public function getByIdInPath(int $id, string $path): array {
$mountCache = $this->getUserMountCache();
- if (strpos($path, '/', 1) > 0) {
+ if ($path !== '' && strpos($path, '/', 1) > 0) {
[, $user] = explode('/', $path);
} else {
$user = null;
diff --git a/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php b/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php
new file mode 100644
index 00000000000..369182b069d
--- /dev/null
+++ b/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php
@@ -0,0 +1,13 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Files\ObjectStore;
+
+class InvalidObjectStoreConfigurationException extends \Exception {
+
+}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 10ee6aec167..9ab11f8a3df 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -475,6 +475,9 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
'original-storage' => $this->getId(),
'original-path' => $path,
];
+ if ($size) {
+ $metadata['size'] = $size;
+ }
$stat['mimetype'] = $mimetype;
$stat['etag'] = $this->getETag($path);
@@ -496,32 +499,27 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
$urn = $this->getURN($fileId);
try {
//upload to object storage
- if ($size === null) {
- $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
+
+ $totalWritten = 0;
+ $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, $size, $exists, &$totalWritten) {
+ if (is_null($size) && !$exists) {
$this->getCache()->update($fileId, [
'size' => $writtenSize,
]);
- $size = $writtenSize;
- });
- if ($this->objectStore instanceof IObjectStoreMetaData) {
- $this->objectStore->writeObjectWithMetaData($urn, $countStream, $metadata);
- } else {
- $this->objectStore->writeObject($urn, $countStream, $metadata['mimetype']);
}
- if (is_resource($countStream)) {
- fclose($countStream);
- }
- $stat['size'] = $size;
+ $totalWritten = $writtenSize;
+ });
+
+ if ($this->objectStore instanceof IObjectStoreMetaData) {
+ $this->objectStore->writeObjectWithMetaData($urn, $countStream, $metadata);
} else {
- if ($this->objectStore instanceof IObjectStoreMetaData) {
- $this->objectStore->writeObjectWithMetaData($urn, $stream, $metadata);
- } else {
- $this->objectStore->writeObject($urn, $stream, $metadata['mimetype']);
- }
- if (is_resource($stream)) {
- fclose($stream);
- }
+ $this->objectStore->writeObject($urn, $countStream, $metadata['mimetype']);
}
+ if (is_resource($countStream)) {
+ fclose($countStream);
+ }
+
+ $stat['size'] = $totalWritten;
} catch (\Exception $ex) {
if (!$exists) {
/*
@@ -545,7 +543,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
]
);
}
- throw $ex; // make this bubble up
+ throw new GenericFileException('Error while writing stream to object store', 0, $ex);
}
if ($exists) {
@@ -561,7 +559,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
}
- return $size;
+ return $totalWritten;
}
public function getObjectStore(): IObjectStore {
diff --git a/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
index fdfe989addc..ffc33687340 100644
--- a/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
+++ b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
@@ -34,9 +34,13 @@ class PrimaryObjectStoreConfig {
* @return ?ObjectStoreConfig
*/
public function getObjectStoreConfigForRoot(): ?array {
- $config = $this->getObjectStoreConfig();
+ if (!$this->hasObjectStore()) {
+ return null;
+ }
+
+ $config = $this->getObjectStoreConfiguration('root');
- if ($config && $config['arguments']['multibucket']) {
+ if ($config['arguments']['multibucket']) {
if (!isset($config['arguments']['bucket'])) {
$config['arguments']['bucket'] = '';
}
@@ -51,38 +55,102 @@ class PrimaryObjectStoreConfig {
* @return ?ObjectStoreConfig
*/
public function getObjectStoreConfigForUser(IUser $user): ?array {
- $config = $this->getObjectStoreConfig();
+ if (!$this->hasObjectStore()) {
+ return null;
+ }
- if ($config && $config['arguments']['multibucket']) {
+ $store = $this->getObjectStoreForUser($user);
+ $config = $this->getObjectStoreConfiguration($store);
+
+ if ($config['arguments']['multibucket']) {
$config['arguments']['bucket'] = $this->getBucketForUser($user, $config);
}
return $config;
}
/**
- * @return ?ObjectStoreConfig
+ * @param string $name
+ * @return ObjectStoreConfig
*/
- private function getObjectStoreConfig(): ?array {
+ public function getObjectStoreConfiguration(string $name): array {
+ $configs = $this->getObjectStoreConfigs();
+ $name = $this->resolveAlias($name);
+ if (!isset($configs[$name])) {
+ throw new \Exception("Object store configuration for '$name' not found");
+ }
+ if (is_string($configs[$name])) {
+ throw new \Exception("Object store configuration for '{$configs[$name]}' not found");
+ }
+ return $configs[$name];
+ }
+
+ public function resolveAlias(string $name): string {
+ $configs = $this->getObjectStoreConfigs();
+
+ while (isset($configs[$name]) && is_string($configs[$name])) {
+ $name = $configs[$name];
+ }
+ return $name;
+ }
+
+ public function hasObjectStore(): bool {
+ $objectStore = $this->config->getSystemValue('objectstore', null);
+ $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
+ return $objectStore || $objectStoreMultiBucket;
+ }
+
+ public function hasMultipleObjectStorages(): bool {
+ $objectStore = $this->config->getSystemValue('objectstore', []);
+ return isset($objectStore['default']);
+ }
+
+ /**
+ * @return ?array<string, ObjectStoreConfig|string>
+ * @throws InvalidObjectStoreConfigurationException
+ */
+ public function getObjectStoreConfigs(): ?array {
$objectStore = $this->config->getSystemValue('objectstore', null);
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
// new-style multibucket config uses the same 'objectstore' key but sets `'multibucket' => true`, transparently upgrade older style config
if ($objectStoreMultiBucket) {
$objectStoreMultiBucket['arguments']['multibucket'] = true;
- return $this->validateObjectStoreConfig($objectStoreMultiBucket);
+ return [
+ 'default' => 'server1',
+ 'server1' => $this->validateObjectStoreConfig($objectStoreMultiBucket),
+ 'root' => 'server1',
+ ];
} elseif ($objectStore) {
- return $this->validateObjectStoreConfig($objectStore);
+ if (!isset($objectStore['default'])) {
+ $objectStore = [
+ 'default' => 'server1',
+ 'root' => 'server1',
+ 'server1' => $objectStore,
+ ];
+ }
+ if (!isset($objectStore['root'])) {
+ $objectStore['root'] = 'default';
+ }
+
+ if (!is_string($objectStore['default'])) {
+ throw new InvalidObjectStoreConfigurationException('The \'default\' object storage configuration is required to be a reference to another configuration.');
+ }
+ return array_map($this->validateObjectStoreConfig(...), $objectStore);
} else {
return null;
}
}
/**
- * @return ObjectStoreConfig
+ * @param array|string $config
+ * @return string|ObjectStoreConfig
*/
- private function validateObjectStoreConfig(array $config) {
+ private function validateObjectStoreConfig(array|string $config): array|string {
+ if (is_string($config)) {
+ return $config;
+ }
if (!isset($config['class'])) {
- throw new \Exception('No class configured for object store');
+ throw new InvalidObjectStoreConfigurationException('No class configured for object store');
}
if (!isset($config['arguments'])) {
$config['arguments'] = [];
@@ -90,17 +158,17 @@ class PrimaryObjectStoreConfig {
$class = $config['class'];
$arguments = $config['arguments'];
if (!is_array($arguments)) {
- throw new \Exception('Configured object store arguments are not an array');
+ throw new InvalidObjectStoreConfigurationException('Configured object store arguments are not an array');
}
if (!isset($arguments['multibucket'])) {
$arguments['multibucket'] = false;
}
if (!is_bool($arguments['multibucket'])) {
- throw new \Exception('arguments.multibucket must be a boolean in object store configuration');
+ throw new InvalidObjectStoreConfigurationException('arguments.multibucket must be a boolean in object store configuration');
}
if (!is_string($class)) {
- throw new \Exception('Configured class for object store is not a string');
+ throw new InvalidObjectStoreConfigurationException('Configured class for object store is not a string');
}
if (str_starts_with($class, 'OCA\\') && substr_count($class, '\\') >= 2) {
@@ -109,7 +177,7 @@ class PrimaryObjectStoreConfig {
}
if (!is_a($class, IObjectStore::class, true)) {
- throw new \Exception('Configured class for object store is not an object store');
+ throw new InvalidObjectStoreConfigurationException('Configured class for object store is not an object store');
}
return [
'class' => $class,
@@ -117,8 +185,8 @@ class PrimaryObjectStoreConfig {
];
}
- private function getBucketForUser(IUser $user, array $config): string {
- $bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
+ public function getBucketForUser(IUser $user, array $config): string {
+ $bucket = $this->getSetBucketForUser($user);
if ($bucket === null) {
/*
@@ -129,7 +197,7 @@ class PrimaryObjectStoreConfig {
$config['arguments']['bucket'] = '';
}
$mapper = new Mapper($user, $this->config);
- $numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64;
+ $numBuckets = $config['arguments']['num_buckets'] ?? 64;
$bucket = $config['arguments']['bucket'] . $mapper->getBucket($numBuckets);
$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $bucket);
@@ -137,4 +205,21 @@ class PrimaryObjectStoreConfig {
return $bucket;
}
+
+ public function getSetBucketForUser(IUser $user): ?string {
+ return $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
+ }
+
+ public function getObjectStoreForUser(IUser $user): string {
+ if ($this->hasMultipleObjectStorages()) {
+ $value = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'objectstore', null);
+ if ($value === null) {
+ $value = $this->resolveAlias('default');
+ $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'objectstore', $value);
+ }
+ return $value;
+ } else {
+ return 'default';
+ }
+ }
}
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index 5e6dcf88a42..89405de2e8e 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -6,6 +6,8 @@
*/
namespace OC\Files\ObjectStore;
+use Aws\Command;
+use Aws\Exception\MultipartUploadException;
use Aws\S3\Exception\S3MultipartUploadException;
use Aws\S3\MultipartCopy;
use Aws\S3\MultipartUploader;
@@ -96,7 +98,9 @@ trait S3ObjectTrait {
protected function writeSingle(string $urn, StreamInterface $stream, array $metaData): void {
$mimetype = $metaData['mimetype'] ?? null;
unset($metaData['mimetype']);
- $this->getConnection()->putObject([
+ unset($metaData['size']);
+
+ $args = [
'Bucket' => $this->bucket,
'Key' => $urn,
'Body' => $stream,
@@ -104,7 +108,13 @@ trait S3ObjectTrait {
'ContentType' => $mimetype,
'Metadata' => $this->buildS3Metadata($metaData),
'StorageClass' => $this->storageClass,
- ] + $this->getSSECParameters());
+ ] + $this->getSSECParameters();
+
+ if ($size = $stream->getSize()) {
+ $args['ContentLength'] = $size;
+ }
+
+ $this->getConnection()->putObject($args);
}
@@ -119,12 +129,15 @@ trait S3ObjectTrait {
protected function writeMultiPart(string $urn, StreamInterface $stream, array $metaData): void {
$mimetype = $metaData['mimetype'] ?? null;
unset($metaData['mimetype']);
+ unset($metaData['size']);
$attempts = 0;
$uploaded = false;
$concurrency = $this->concurrency;
$exception = null;
$state = null;
+ $size = $stream->getSize();
+ $totalWritten = 0;
// retry multipart upload once with concurrency at half on failure
while (!$uploaded && $attempts <= 1) {
@@ -139,6 +152,15 @@ trait S3ObjectTrait {
'Metadata' => $this->buildS3Metadata($metaData),
'StorageClass' => $this->storageClass,
] + $this->getSSECParameters(),
+ 'before_upload' => function (Command $command) use (&$totalWritten) {
+ $totalWritten += $command['ContentLength'];
+ },
+ 'before_complete' => function ($_command) use (&$totalWritten, $size, &$uploader, &$attempts) {
+ if ($size !== null && $totalWritten != $size) {
+ $e = new \Exception('Incomplete multi part upload, expected ' . $size . ' bytes, wrote ' . $totalWritten);
+ throw new MultipartUploadException($uploader->getState(), $e);
+ }
+ },
]);
try {
@@ -155,6 +177,9 @@ trait S3ObjectTrait {
if ($stream->isSeekable()) {
$stream->rewind();
}
+ } catch (MultipartUploadException $e) {
+ $exception = $e;
+ break;
}
}
@@ -180,7 +205,9 @@ trait S3ObjectTrait {
public function writeObjectWithMetaData(string $urn, $stream, array $metaData): void {
$canSeek = fseek($stream, 0, SEEK_CUR) === 0;
- $psrStream = Utils::streamFor($stream);
+ $psrStream = Utils::streamFor($stream, [
+ 'size' => $metaData['size'] ?? null,
+ ]);
$size = $psrStream->getSize();
diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php
index d5810a90868..6af6ce1a0b1 100644
--- a/lib/private/Files/Type/Detection.php
+++ b/lib/private/Files/Type/Detection.php
@@ -55,7 +55,8 @@ class Detection implements IMimeTypeDetector {
* @param string $mimeType
* @param string|null $secureMimeType
*/
- public function registerType(string $extension,
+ public function registerType(
+ string $extension,
string $mimeType,
?string $secureMimeType = null): void {
// Make sure the extension is a string
@@ -217,14 +218,10 @@ class Detection implements IMimeTypeDetector {
return 'httpd/unix-directory';
}
- if (function_exists('finfo_open')
- && function_exists('finfo_file')
- && $finfo = finfo_open(FILEINFO_MIME)) {
- $info = @finfo_file($finfo, $path);
- finfo_close($finfo);
- if ($info) {
- $info = strtolower($info);
- $mimeType = str_contains($info, ';') ? substr($info, 0, strpos($info, ';')) : $info;
+ if (class_exists(finfo::class)) {
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ $mimeType = @$finfo->file($path);
+ if ($mimeType) {
$mimeType = $this->getSecureMimeType($mimeType);
if ($mimeType !== 'application/octet-stream') {
return $mimeType;
@@ -240,7 +237,7 @@ class Detection implements IMimeTypeDetector {
if (function_exists('mime_content_type')) {
// use mime magic extension if available
$mimeType = mime_content_type($path);
- if ($mimeType !== false) {
+ if ($mimeType) {
$mimeType = $this->getSecureMimeType($mimeType);
if ($mimeType !== 'application/octet-stream') {
return $mimeType;
@@ -258,7 +255,7 @@ class Detection implements IMimeTypeDetector {
if ($fp !== false) {
$mimeType = fgets($fp);
pclose($fp);
- if ($mimeType !== false) {
+ if ($mimeType) {
//trim the newline
$mimeType = trim($mimeType);
$mimeType = $this->getSecureMimeType($mimeType);
@@ -293,19 +290,21 @@ class Detection implements IMimeTypeDetector {
* @return string
*/
public function detectString($data): string {
- if (function_exists('finfo_open') && function_exists('finfo_file')) {
- $finfo = finfo_open(FILEINFO_MIME);
- $info = finfo_buffer($finfo, $data);
- return str_contains($info, ';') ? substr($info, 0, strpos($info, ';')) : $info;
+ if (class_exists(finfo::class)) {
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ $mimeType = $finfo->buffer($data);
+ if ($mimeType) {
+ return $mimeType;
+ }
}
$tmpFile = \OCP\Server::get(ITempManager::class)->getTemporaryFile();
$fh = fopen($tmpFile, 'wb');
fwrite($fh, $data, 8024);
fclose($fh);
- $mime = $this->detect($tmpFile);
+ $mimeType = $this->detect($tmpFile);
unset($tmpFile);
- return $mime;
+ return $mimeType;
}
/**
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 63eecf5e1d6..a852f453963 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -1828,43 +1828,25 @@ class View {
* @return string
* @throws NotFoundException
*/
- public function getPath($id, ?int $storageId = null) {
+ public function getPath($id, ?int $storageId = null): string {
$id = (int)$id;
- $manager = Filesystem::getMountManager();
- $mounts = $manager->findIn($this->fakeRoot);
- $mounts[] = $manager->find($this->fakeRoot);
- $mounts = array_filter($mounts);
- // reverse the array, so we start with the storage this view is in
- // which is the most likely to contain the file we're looking for
- $mounts = array_reverse($mounts);
-
- // put non-shared mounts in front of the shared mount
- // this prevents unneeded recursion into shares
- usort($mounts, function (IMountPoint $a, IMountPoint $b) {
- return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1;
- });
+ $rootFolder = Server::get(Files\IRootFolder::class);
- if (!is_null($storageId)) {
- $mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) {
- return $mount->getNumericStorageId() === $storageId;
- });
+ $node = $rootFolder->getFirstNodeByIdInPath($id, $this->getRoot());
+ if ($node) {
+ if ($storageId === null || $storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
+ return $this->getRelativePath($node->getPath()) ?? '';
+ }
+ } else {
+ throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
}
- foreach ($mounts as $mount) {
- /**
- * @var \OC\Files\Mount\MountPoint $mount
- */
- if ($mount->getStorage()) {
- $cache = $mount->getStorage()->getCache();
- $internalPath = $cache->getPathById($id);
- if (is_string($internalPath)) {
- $fullPath = $mount->getMountPoint() . $internalPath;
- if (!is_null($path = $this->getRelativePath($fullPath))) {
- return $path;
- }
- }
+ foreach ($rootFolder->getByIdInPath($id, $this->getRoot()) as $node) {
+ if ($storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
+ return $this->getRelativePath($node->getPath()) ?? '';
}
}
+
throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
}
diff --git a/lib/private/InitialStateService.php b/lib/private/InitialStateService.php
index c930ffd9466..300aa238397 100644
--- a/lib/private/InitialStateService.php
+++ b/lib/private/InitialStateService.php
@@ -13,29 +13,23 @@ use OC\AppFramework\Bootstrap\Coordinator;
use OCP\AppFramework\QueryException;
use OCP\AppFramework\Services\InitialStateProvider;
use OCP\IInitialStateService;
-use OCP\IServerContainer;
+use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class InitialStateService implements IInitialStateService {
- /** @var LoggerInterface */
- private $logger;
/** @var string[][] */
- private $states = [];
+ private array $states = [];
/** @var Closure[][] */
- private $lazyStates = [];
+ private array $lazyStates = [];
- /** @var Coordinator */
- private $bootstrapCoordinator;
- /** @var IServerContainer */
- private $container;
-
- public function __construct(LoggerInterface $logger, Coordinator $bootstrapCoordinator, IServerContainer $container) {
- $this->logger = $logger;
- $this->bootstrapCoordinator = $bootstrapCoordinator;
- $this->container = $container;
+ public function __construct(
+ private LoggerInterface $logger,
+ private Coordinator $bootstrapCoordinator,
+ private ContainerInterface $container,
+ ) {
}
public function provideInitialState(string $appName, string $key, $data): void {
diff --git a/lib/private/Mail/EMailTemplate.php b/lib/private/Mail/EMailTemplate.php
index 1d19f00b0a1..a327109cc12 100644
--- a/lib/private/Mail/EMailTemplate.php
+++ b/lib/private/Mail/EMailTemplate.php
@@ -190,32 +190,46 @@ EOF;
<tr style="padding:0;text-align:left;vertical-align:top">
<th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left">
<center data-parsed="" style="min-width:490px;width:100%%">
- <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;background-color:%1\$s;color:#fefefe;">
- <tr style="padding:0;text-align:left;vertical-align:top">
- <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
- <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
+ <!--[if (gte mso 9)|(IE)]>
+ <table>
+ <tr>
+ <td>
+ <![endif]-->
+ <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;background-color:%1\$s;color:#fefefe;">
<tr style="padding:0;text-align:left;vertical-align:top">
- <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
- <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid %6\$s;text-decoration:none">%7\$s</a>
+ <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
+ <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
+ <tr style="padding:0;text-align:left;vertical-align:top">
+ <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
+ <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid %6\$s;text-decoration:none">%7\$s</a>
+ </td>
+ </tr>
+ </table>
</td>
</tr>
</table>
+ <!--[if (gte mso 9)|(IE)]>
</td>
- </tr>
- </table>
- <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto">
- <tr style="padding:0;text-align:left;vertical-align:top">
- <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
- <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
+ <td>
+ <![endif]-->
+ <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto">
<tr style="padding:0;text-align:left;vertical-align:top">
- <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
- <a href="%8\$s" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">%9\$s</a>
+ <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
+ <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
+ <tr style="padding:0;text-align:left;vertical-align:top">
+ <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
+ <a href="%8\$s" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">%9\$s</a>
+ </td>
+ </tr>
+ </table>
</td>
</tr>
</table>
+ <!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
+ <![endif]-->
</center>
</th>
<th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th>
diff --git a/lib/private/Notification/Manager.php b/lib/private/Notification/Manager.php
index 8c457db8beb..0cbda651a8b 100644
--- a/lib/private/Notification/Manager.php
+++ b/lib/private/Notification/Manager.php
@@ -21,6 +21,7 @@ use OCP\Notification\IncompleteNotificationException;
use OCP\Notification\IncompleteParsedNotificationException;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
+use OCP\Notification\IPreloadableNotifier;
use OCP\Notification\UnknownNotificationException;
use OCP\RichObjectStrings\IRichTextFormatter;
use OCP\RichObjectStrings\IValidator;
@@ -390,6 +391,17 @@ class Manager implements IManager {
return $notification;
}
+ public function preloadDataForParsing(array $notifications, string $languageCode): void {
+ $notifiers = $this->getNotifiers();
+ foreach ($notifiers as $notifier) {
+ if (!($notifier instanceof IPreloadableNotifier)) {
+ continue;
+ }
+
+ $notifier->preloadDataForParsing($notifications, $languageCode);
+ }
+ }
+
/**
* @param INotification $notification
*/
diff --git a/lib/private/Profile/Actions/BlueskyAction.php b/lib/private/Profile/Actions/BlueskyAction.php
new file mode 100644
index 00000000000..d05682aac1a
--- /dev/null
+++ b/lib/private/Profile/Actions/BlueskyAction.php
@@ -0,0 +1,65 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Profile\Actions;
+
+use OCP\Accounts\IAccountManager;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\L10N\IFactory;
+use OCP\Profile\ILinkAction;
+
+class BlueskyAction implements ILinkAction {
+ private string $value = '';
+
+ public function __construct(
+ private IAccountManager $accountManager,
+ private IFactory $l10nFactory,
+ private IURLGenerator $urlGenerator,
+ ) {
+ }
+
+ public function preload(IUser $targetUser): void {
+ $account = $this->accountManager->getAccount($targetUser);
+ $this->value = $account->getProperty(IAccountManager::PROPERTY_BLUESKY)->getValue();
+ }
+
+ public function getAppId(): string {
+ return 'core';
+ }
+
+ public function getId(): string {
+ return IAccountManager::PROPERTY_BLUESKY;
+ }
+
+ public function getDisplayId(): string {
+ return $this->l10nFactory->get('lib')->t('Bluesky');
+ }
+
+ public function getTitle(): string {
+ $displayUsername = $this->value;
+ return $this->l10nFactory->get('lib')->t('View %s on Bluesky', [$displayUsername]);
+ }
+
+ public function getPriority(): int {
+ return 60;
+ }
+
+ public function getIcon(): string {
+ return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/bluesky.svg'));
+ }
+
+ public function getTarget(): ?string {
+ if (empty($this->value)) {
+ return null;
+ }
+ $username = $this->value;
+ return 'https://bsky.app/profile/' . $username;
+ }
+}
diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php
index 1ade208fbcf..c38412f6bd0 100644
--- a/lib/private/Profile/ProfileManager.php
+++ b/lib/private/Profile/ProfileManager.php
@@ -10,10 +10,12 @@ declare(strict_types=1);
namespace OC\Profile;
use OC\AppFramework\Bootstrap\Coordinator;
+use OC\Config\PresetManager;
use OC\Core\Db\ProfileConfig;
use OC\Core\Db\ProfileConfigMapper;
use OC\Core\ResponseDefinitions;
use OC\KnownUser\KnownUserService;
+use OC\Profile\Actions\BlueskyAction;
use OC\Profile\Actions\EmailAction;
use OC\Profile\Actions\FediverseAction;
use OC\Profile\Actions\PhoneAction;
@@ -24,6 +26,7 @@ use OCP\Accounts\PropertyDoesNotExistException;
use OCP\App\IAppManager;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Cache\CappedMemoryCache;
+use OCP\Config\Lexicon\Preset;
use OCP\IConfig;
use OCP\IUser;
use OCP\L10N\IFactory;
@@ -56,6 +59,7 @@ class ProfileManager implements IProfileManager {
PhoneAction::class,
WebsiteAction::class,
TwitterAction::class,
+ BlueskyAction::class,
FediverseAction::class,
];
@@ -83,6 +87,7 @@ class ProfileManager implements IProfileManager {
private IFactory $l10nFactory,
private LoggerInterface $logger,
private Coordinator $coordinator,
+ private readonly PresetManager $presetManager,
) {
$this->configCache = new CappedMemoryCache();
}
@@ -313,6 +318,7 @@ class ProfileManager implements IProfileManager {
// Construct the default config for account properties
$propertiesConfig = [];
foreach (self::DEFAULT_PROPERTY_VISIBILITY as $property => $visibility) {
+ $this->applyDefaultProfilePreset($property, $visibility);
$propertiesConfig[$property] = ['visibility' => $visibility];
}
@@ -320,6 +326,31 @@ class ProfileManager implements IProfileManager {
}
/**
+ * modify property visibility, based on current Preset
+ *
+ * @psalm-suppress UnhandledMatchCondition if conditions are not met, we do not change $visibility
+ */
+ private function applyDefaultProfilePreset(string $property, string &$visibility): void {
+ try {
+ $overwrite = match ($this->presetManager->getLexiconPreset()) {
+ Preset::SHARED, Preset::SCHOOL, Preset::UNIVERSITY => match ($property) {
+ IAccountManager::PROPERTY_ADDRESS, IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_PHONE => self::VISIBILITY_HIDE,
+ },
+ Preset::PRIVATE, Preset::FAMILY, Preset::CLUB => match ($property) {
+ IAccountManager::PROPERTY_EMAIL => self::VISIBILITY_SHOW,
+ },
+ Preset::SMALL, Preset::MEDIUM, Preset::LARGE => match ($property) {
+ IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_PHONE => self::VISIBILITY_SHOW,
+ },
+ };
+ } catch (\UnhandledMatchError) {
+ return;
+ }
+
+ $visibility = $overwrite;
+ }
+
+ /**
* Return the profile config of the target user,
* if a config does not already exist a default config is created and returned
*/
diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php
index 77d7ca62311..be366e8ba6c 100644
--- a/lib/private/Search/SearchComposer.php
+++ b/lib/private/Search/SearchComposer.php
@@ -15,6 +15,7 @@ use OCP\IAppConfig;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\FilterDefinition;
+use OCP\Search\IExternalProvider;
use OCP\Search\IFilter;
use OCP\Search\IFilteringProvider;
use OCP\Search\IInAppSearch;
@@ -118,7 +119,7 @@ class SearchComposer {
}
}
- $this->providers = $this->filterProviders($this->providers);
+ $this->filterProviders();
$this->loadFilters();
}
@@ -178,6 +179,7 @@ class SearchComposer {
if ($order === null) {
return;
}
+ $isExternalProvider = $provider instanceof IExternalProvider ? $provider->isExternalProvider() : false;
$triggers = [$provider->getId()];
if ($provider instanceof IFilteringProvider) {
$triggers += $provider->getAlternateIds();
@@ -192,6 +194,7 @@ class SearchComposer {
'name' => $provider->getName(),
'icon' => $this->fetchIcon($appId, $provider->getId()),
'order' => $order,
+ 'isExternalProvider' => $isExternalProvider,
'triggers' => array_values($triggers),
'filters' => $this->getFiltersType($filters, $provider->getId()),
'inAppSearch' => $provider instanceof IInAppSearch,
@@ -211,19 +214,21 @@ class SearchComposer {
/**
* Filter providers based on 'unified_search.providers_allowed' core app config array
- * @param array $providers
- * @return array
+ * Will remove providers that are not in the allowed list
*/
- private function filterProviders(array $providers): array {
+ private function filterProviders(): void {
$allowedProviders = $this->appConfig->getValueArray('core', 'unified_search.providers_allowed');
if (empty($allowedProviders)) {
- return $providers;
+ return;
}
- return array_values(array_filter($providers, function ($p) use ($allowedProviders) {
- return in_array($p['id'], $allowedProviders);
- }));
+ foreach (array_keys($this->providers) as $providerId) {
+ if (!in_array($providerId, $allowedProviders, true)) {
+ unset($this->providers[$providerId]);
+ unset($this->handlers[$providerId]);
+ }
+ }
}
private function fetchIcon(string $appId, string $providerId): string {
diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php
index 935c18bb81d..c16b8314beb 100644
--- a/lib/private/Security/IdentityProof/Manager.php
+++ b/lib/private/Security/IdentityProof/Manager.php
@@ -11,6 +11,8 @@ namespace OC\Security\IdentityProof;
use OC\Files\AppData\Factory;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
+use OCP\ICache;
+use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IUser;
use OCP\Security\ICrypto;
@@ -19,13 +21,17 @@ use Psr\Log\LoggerInterface;
class Manager {
private IAppData $appData;
+ protected ICache $cache;
+
public function __construct(
Factory $appDataFactory,
private ICrypto $crypto,
private IConfig $config,
private LoggerInterface $logger,
+ private ICacheFactory $cacheFactory,
) {
$this->appData = $appDataFactory->get('identityproof');
+ $this->cache = $this->cacheFactory->createDistributed('identityproof::');
}
/**
@@ -96,12 +102,24 @@ class Manager {
*/
protected function retrieveKey(string $id): Key {
try {
+ $cachedPublicKey = $this->cache->get($id . '-public');
+ $cachedPrivateKey = $this->cache->get($id . '-private');
+
+ if ($cachedPublicKey !== null && $cachedPrivateKey !== null) {
+ $decryptedPrivateKey = $this->crypto->decrypt($cachedPrivateKey);
+
+ return new Key($cachedPublicKey, $decryptedPrivateKey);
+ }
+
$folder = $this->appData->getFolder($id);
- $privateKey = $this->crypto->decrypt(
- $folder->getFile('private')->getContent()
- );
+ $privateKey = $folder->getFile('private')->getContent();
$publicKey = $folder->getFile('public')->getContent();
- return new Key($publicKey, $privateKey);
+
+ $this->cache->set($id . '-public', $publicKey);
+ $this->cache->set($id . '-private', $privateKey);
+
+ $decryptedPrivateKey = $this->crypto->decrypt($privateKey);
+ return new Key($publicKey, $decryptedPrivateKey);
} catch (\Exception $e) {
return $this->generateKey($id);
}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 171fee2afa1..22cd13438b8 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -657,7 +657,8 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(\OCP\IConfig::class),
$c->get(IValidator::class),
$c->get(IRichTextFormatter::class),
- $l10n
+ $l10n,
+ $c->get(ITimeFactory::class),
);
});
diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php
index 1e2dda4c609..c4794a86743 100644
--- a/lib/private/Setup/MySQL.php
+++ b/lib/private/Setup/MySQL.php
@@ -8,6 +8,7 @@
namespace OC\Setup;
use Doctrine\DBAL\Platforms\MySQL80Platform;
+use Doctrine\DBAL\Platforms\MySQL84Platform;
use OC\DB\ConnectionAdapter;
use OC\DB\MySqlTools;
use OCP\IDBConnection;
@@ -92,22 +93,29 @@ class MySQL extends AbstractDatabase {
* @throws \OC\DatabaseSetupException
*/
private function createDBUser($connection): void {
+ $name = $this->dbUser;
+ $password = $this->dbPassword;
+
try {
- $name = $this->dbUser;
- $password = $this->dbPassword;
// we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one,
// the anonymous user would take precedence when there is one.
- if ($connection->getDatabasePlatform() instanceof Mysql80Platform) {
+ if ($connection->getDatabasePlatform() instanceof MySQL84Platform) {
+ $query = "CREATE USER ?@'localhost' IDENTIFIED WITH caching_sha2_password BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ $query = "CREATE USER ?@'%' IDENTIFIED WITH caching_sha2_password BY ?";
+ $connection->executeStatement($query, [$name,$password]);
+ } elseif ($connection->getDatabasePlatform() instanceof Mysql80Platform) {
+ // TODO: Remove this elseif section as soon as MySQL 8.0 is out-of-support (after April 2026)
$query = "CREATE USER ?@'localhost' IDENTIFIED WITH mysql_native_password BY ?";
- $connection->executeUpdate($query, [$name,$password]);
+ $connection->executeStatement($query, [$name,$password]);
$query = "CREATE USER ?@'%' IDENTIFIED WITH mysql_native_password BY ?";
- $connection->executeUpdate($query, [$name,$password]);
+ $connection->executeStatement($query, [$name,$password]);
} else {
$query = "CREATE USER ?@'localhost' IDENTIFIED BY ?";
- $connection->executeUpdate($query, [$name,$password]);
+ $connection->executeStatement($query, [$name,$password]);
$query = "CREATE USER ?@'%' IDENTIFIED BY ?";
- $connection->executeUpdate($query, [$name,$password]);
+ $connection->executeStatement($query, [$name,$password]);
}
} catch (\Exception $ex) {
$this->logger->error('Database user creation failed.', [
@@ -158,6 +166,11 @@ class MySQL extends AbstractDatabase {
//use the admin login data for the new database user
$this->dbUser = $adminUser;
$this->createDBUser($connection);
+ // if sharding is used we need to manually call this for every shard as those also need the user setup!
+ /** @var ConnectionAdapter $connection */
+ foreach ($connection->getInner()->getShardConnections() as $shard) {
+ $this->createDBUser($shard);
+ }
break;
} else {
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 855bb173d56..28f29d6b20f 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -1778,7 +1778,7 @@ class Manager implements IManager {
}
}
}
- return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
+ return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
}
/**
diff --git a/lib/private/Streamer.php b/lib/private/Streamer.php
index de663f66e0d..e579c42c0d7 100644
--- a/lib/private/Streamer.php
+++ b/lib/private/Streamer.php
@@ -170,11 +170,16 @@ class Streamer {
/**
* Add an empty directory entry to the archive.
*
- * @param string $dirName Directory Path and name to be added to the archive.
- * @return bool $success
+ * @param $dirName - Directory Path and name to be added to the archive.
+ * @param $timestamp - Modification time of the directory (defaults to current time)
*/
- public function addEmptyDir($dirName) {
- return $this->streamerInstance->addEmptyDir($dirName);
+ public function addEmptyDir(string $dirName, int $timestamp = 0): bool {
+ $options = null;
+ if ($timestamp > 0) {
+ $options = ['timestamp' => $timestamp];
+ }
+
+ return $this->streamerInstance->addEmptyDir($dirName, $options);
}
/**
diff --git a/lib/private/SystemConfig.php b/lib/private/SystemConfig.php
index 57777b06ed6..7e8946f4d05 100644
--- a/lib/private/SystemConfig.php
+++ b/lib/private/SystemConfig.php
@@ -15,8 +15,9 @@ use OCP\IConfig;
* fixes cyclic DI: AllConfig needs AppConfig needs Database needs AllConfig
*/
class SystemConfig {
- /** @var array */
- protected $sensitiveValues = [
+ protected array $sensitiveValues;
+
+ protected const DEFAULT_SENSITIVE_VALUES = [
'instanceid' => true,
'datadirectory' => true,
'dbname' => true,
@@ -114,6 +115,7 @@ class SystemConfig {
public function __construct(
private Config $config,
) {
+ $this->sensitiveValues = array_merge(self::DEFAULT_SENSITIVE_VALUES, $this->config->getValue('config_extra_sensitive_values', []));
}
/**
diff --git a/lib/private/Tags.php b/lib/private/Tags.php
index 0a37f4c9f4e..fe4a4137e10 100644
--- a/lib/private/Tags.php
+++ b/lib/private/Tags.php
@@ -273,7 +273,6 @@ class Tags implements ITags {
return false;
}
if ($this->userHasTag($name, $this->user)) {
- // TODO use unique db properties instead of an additional check
$this->logger->debug(__METHOD__ . ' Tag with name already exists', ['app' => 'core']);
return false;
}
diff --git a/lib/private/TaskProcessing/Db/Task.php b/lib/private/TaskProcessing/Db/Task.php
index 4d919deaf94..05c0ae9ac74 100644
--- a/lib/private/TaskProcessing/Db/Task.php
+++ b/lib/private/TaskProcessing/Db/Task.php
@@ -45,6 +45,8 @@ use OCP\TaskProcessing\Task as OCPTask;
* @method int getStartedAt()
* @method setEndedAt(int $endedAt)
* @method int getEndedAt()
+ * @method setAllowCleanup(int $allowCleanup)
+ * @method int getAllowCleanup()
*/
class Task extends Entity {
protected $lastUpdated;
@@ -63,16 +65,17 @@ class Task extends Entity {
protected $scheduledAt;
protected $startedAt;
protected $endedAt;
+ protected $allowCleanup;
/**
* @var string[]
*/
- public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method', 'scheduled_at', 'started_at', 'ended_at'];
+ public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress', 'webhook_uri', 'webhook_method', 'scheduled_at', 'started_at', 'ended_at', 'allow_cleanup'];
/**
* @var string[]
*/
- public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod', 'scheduledAt', 'startedAt', 'endedAt'];
+ public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress', 'webhookUri', 'webhookMethod', 'scheduledAt', 'startedAt', 'endedAt', 'allowCleanup'];
public function __construct() {
@@ -94,6 +97,7 @@ class Task extends Entity {
$this->addType('scheduledAt', 'integer');
$this->addType('startedAt', 'integer');
$this->addType('endedAt', 'integer');
+ $this->addType('allowCleanup', 'integer');
}
public function toRow(): array {
@@ -122,6 +126,7 @@ class Task extends Entity {
'scheduledAt' => $task->getScheduledAt(),
'startedAt' => $task->getStartedAt(),
'endedAt' => $task->getEndedAt(),
+ 'allowCleanup' => $task->getAllowCleanup() ? 1 : 0,
]);
return $taskEntity;
}
@@ -144,6 +149,7 @@ class Task extends Entity {
$task->setScheduledAt($this->getScheduledAt());
$task->setStartedAt($this->getStartedAt());
$task->setEndedAt($this->getEndedAt());
+ $task->setAllowCleanup($this->getAllowCleanup() !== 0);
return $task;
}
}
diff --git a/lib/private/TaskProcessing/Db/TaskMapper.php b/lib/private/TaskProcessing/Db/TaskMapper.php
index 91fd68820ae..fee96534633 100644
--- a/lib/private/TaskProcessing/Db/TaskMapper.php
+++ b/lib/private/TaskProcessing/Db/TaskMapper.php
@@ -183,16 +183,39 @@ class TaskMapper extends QBMapper {
/**
* @param int $timeout
+ * @param bool $force If true, ignore the allow_cleanup flag
* @return int the number of deleted tasks
* @throws Exception
*/
- public function deleteOlderThan(int $timeout): int {
+ public function deleteOlderThan(int $timeout, bool $force = false): int {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->tableName)
->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($this->timeFactory->getDateTime()->getTimestamp() - $timeout)));
+ if (!$force) {
+ $qb->andWhere($qb->expr()->eq('allow_cleanup', $qb->createPositionalParameter(1, IQueryBuilder::PARAM_INT)));
+ }
return $qb->executeStatement();
}
+ /**
+ * @param int $timeout
+ * @param bool $force If true, ignore the allow_cleanup flag
+ * @return \Generator<Task>
+ * @throws Exception
+ */
+ public function getTasksToCleanup(int $timeout, bool $force = false): \Generator {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(Task::$columns)
+ ->from($this->tableName)
+ ->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($this->timeFactory->getDateTime()->getTimestamp() - $timeout)));
+ if (!$force) {
+ $qb->andWhere($qb->expr()->eq('allow_cleanup', $qb->createPositionalParameter(1, IQueryBuilder::PARAM_INT)));
+ }
+ foreach ($this->yieldEntities($qb) as $entity) {
+ yield $entity;
+ };
+ }
+
public function update(Entity $entity): Entity {
$entity->setLastUpdated($this->timeFactory->now()->getTimestamp());
return parent::update($entity);
diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php
index a9c9f1e1ca2..e288f2981a8 100644
--- a/lib/private/TaskProcessing/Manager.php
+++ b/lib/private/TaskProcessing/Manager.php
@@ -30,10 +30,11 @@ use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
use OCP\Http\Client\IClientService;
+use OCP\IAppConfig;
use OCP\ICache;
use OCP\ICacheFactory;
-use OCP\IConfig;
use OCP\IL10N;
use OCP\IServerContainer;
use OCP\IUserManager;
@@ -73,6 +74,13 @@ class Manager implements IManager {
public const LEGACY_PREFIX_TEXTTOIMAGE = 'legacy:TextToImage:';
public const LEGACY_PREFIX_SPEECHTOTEXT = 'legacy:SpeechToText:';
+ public const LAZY_CONFIG_KEYS = [
+ 'ai.taskprocessing_type_preferences',
+ 'ai.taskprocessing_provider_preferences',
+ ];
+
+ public const MAX_TASK_AGE_SECONDS = 60 * 60 * 24 * 30 * 4; // 4 months
+
/** @var list<IProvider>|null */
private ?array $providers = null;
@@ -92,7 +100,7 @@ class Manager implements IManager {
private ?GetTaskProcessingProvidersEvent $eventResult = null;
public function __construct(
- private IConfig $config,
+ private IAppConfig $appConfig,
private Coordinator $coordinator,
private IServerContainer $serverContainer,
private LoggerInterface $logger,
@@ -630,7 +638,7 @@ class Manager implements IManager {
*/
private function _getTaskTypeSettings(): array {
try {
- $json = $this->config->getAppValue('core', 'ai.taskprocessing_type_preferences', '');
+ $json = $this->appConfig->getValueString('core', 'ai.taskprocessing_type_preferences', '', lazy: true);
if ($json === '') {
return [];
}
@@ -788,7 +796,11 @@ class Manager implements IManager {
if ($this->preferences === null) {
$this->preferences = $this->distributedCache->get('ai.taskprocessing_provider_preferences');
if ($this->preferences === null) {
- $this->preferences = json_decode($this->config->getAppValue('core', 'ai.taskprocessing_provider_preferences', 'null'), associative: true, flags: JSON_THROW_ON_ERROR);
+ $this->preferences = json_decode(
+ $this->appConfig->getValueString('core', 'ai.taskprocessing_provider_preferences', 'null', lazy: true),
+ associative: true,
+ flags: JSON_THROW_ON_ERROR,
+ );
$this->distributedCache->set('ai.taskprocessing_provider_preferences', $this->preferences, 60 * 3);
}
}
@@ -889,7 +901,7 @@ class Manager implements IManager {
$user = $this->userManager->get($userId);
}
- $guestsAllowed = $this->config->getAppValue('core', 'ai.taskprocessing_guests', 'false');
+ $guestsAllowed = $this->appConfig->getValueString('core', 'ai.taskprocessing_guests', 'false');
if ($guestsAllowed == 'true' || !class_exists(\OCA\Guests\UserBackend::class) || !($user->getBackend() instanceof \OCA\Guests\UserBackend)) {
return true;
}
@@ -1440,6 +1452,97 @@ class Manager implements IManager {
}
/**
+ * @param Task $task
+ * @return list<int>
+ * @throws NotFoundException
+ */
+ public function extractFileIdsFromTask(Task $task): array {
+ $ids = [];
+ $taskTypes = $this->getAvailableTaskTypes();
+ if (!isset($taskTypes[$task->getTaskTypeId()])) {
+ throw new NotFoundException('Could not find task type');
+ }
+ $taskType = $taskTypes[$task->getTaskTypeId()];
+ foreach ($taskType['inputShape'] + $taskType['optionalInputShape'] as $key => $descriptor) {
+ if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
+ /** @var int|list<int> $inputSlot */
+ $inputSlot = $task->getInput()[$key];
+ if (is_array($inputSlot)) {
+ $ids = array_merge($inputSlot, $ids);
+ } else {
+ $ids[] = $inputSlot;
+ }
+ }
+ }
+ if ($task->getOutput() !== null) {
+ foreach ($taskType['outputShape'] + $taskType['optionalOutputShape'] as $key => $descriptor) {
+ if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
+ /** @var int|list<int> $outputSlot */
+ $outputSlot = $task->getOutput()[$key];
+ if (is_array($outputSlot)) {
+ $ids = array_merge($outputSlot, $ids);
+ } else {
+ $ids[] = $outputSlot;
+ }
+ }
+ }
+ }
+ return $ids;
+ }
+
+ /**
+ * @param ISimpleFolder $folder
+ * @param int $ageInSeconds
+ * @return \Generator
+ */
+ public function clearFilesOlderThan(ISimpleFolder $folder, int $ageInSeconds = self::MAX_TASK_AGE_SECONDS): \Generator {
+ foreach ($folder->getDirectoryListing() as $file) {
+ if ($file->getMTime() < time() - $ageInSeconds) {
+ try {
+ $fileName = $file->getName();
+ $file->delete();
+ yield $fileName;
+ } catch (NotPermittedException $e) {
+ $this->logger->warning('Failed to delete a stale task processing file', ['exception' => $e]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param int $ageInSeconds
+ * @return \Generator
+ * @throws Exception
+ * @throws InvalidPathException
+ * @throws NotFoundException
+ * @throws \JsonException
+ * @throws \OCP\Files\NotFoundException
+ */
+ public function cleanupTaskProcessingTaskFiles(int $ageInSeconds = self::MAX_TASK_AGE_SECONDS): \Generator {
+ $taskIdsToCleanup = [];
+ foreach ($this->taskMapper->getTasksToCleanup($ageInSeconds) as $task) {
+ $taskIdsToCleanup[] = $task->getId();
+ $ocpTask = $task->toPublicTask();
+ $fileIds = $this->extractFileIdsFromTask($ocpTask);
+ foreach ($fileIds as $fileId) {
+ // only look for output files stored in appData/TaskProcessing/
+ $file = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/core/TaskProcessing/');
+ if ($file instanceof File) {
+ try {
+ $fileId = $file->getId();
+ $fileName = $file->getName();
+ $file->delete();
+ yield ['task_id' => $task->getId(), 'file_id' => $fileId, 'file_name' => $fileName];
+ } catch (NotPermittedException $e) {
+ $this->logger->warning('Failed to delete a stale task processing file', ['exception' => $e]);
+ }
+ }
+ }
+ }
+ return $taskIdsToCleanup;
+ }
+
+ /**
* Make a request to the task's webhookUri if necessary
*
* @param Task $task
diff --git a/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php b/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
index 42d073a024d..52fc204b752 100644
--- a/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
+++ b/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
@@ -10,17 +10,14 @@ use OC\TaskProcessing\Db\TaskMapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Files\AppData\IAppDataFactory;
-use OCP\Files\NotFoundException;
-use OCP\Files\NotPermittedException;
-use OCP\Files\SimpleFS\ISimpleFolder;
use Psr\Log\LoggerInterface;
class RemoveOldTasksBackgroundJob extends TimedJob {
- public const MAX_TASK_AGE_SECONDS = 60 * 60 * 24 * 30 * 4; // 4 months
private \OCP\Files\IAppData $appData;
public function __construct(
ITimeFactory $timeFactory,
+ private Manager $taskProcessingManager,
private TaskMapper $taskMapper,
private LoggerInterface $logger,
IAppDataFactory $appDataFactory,
@@ -32,48 +29,29 @@ class RemoveOldTasksBackgroundJob extends TimedJob {
$this->appData = $appDataFactory->get('core');
}
-
/**
* @inheritDoc
*/
protected function run($argument): void {
try {
- $this->taskMapper->deleteOlderThan(self::MAX_TASK_AGE_SECONDS);
- } catch (\OCP\DB\Exception $e) {
- $this->logger->warning('Failed to delete stale task processing tasks', ['exception' => $e]);
+ iterator_to_array($this->taskProcessingManager->cleanupTaskProcessingTaskFiles());
+ } catch (\Exception $e) {
+ $this->logger->warning('Failed to delete stale task processing tasks files', ['exception' => $e]);
}
try {
- $this->clearFilesOlderThan($this->appData->getFolder('text2image'), self::MAX_TASK_AGE_SECONDS);
- } catch (NotFoundException $e) {
- // noop
+ $this->taskMapper->deleteOlderThan(Manager::MAX_TASK_AGE_SECONDS);
+ } catch (\OCP\DB\Exception $e) {
+ $this->logger->warning('Failed to delete stale task processing tasks', ['exception' => $e]);
}
try {
- $this->clearFilesOlderThan($this->appData->getFolder('audio2text'), self::MAX_TASK_AGE_SECONDS);
- } catch (NotFoundException $e) {
+ iterator_to_array($this->taskProcessingManager->clearFilesOlderThan($this->appData->getFolder('text2image')));
+ } catch (\OCP\Files\NotFoundException $e) {
// noop
}
try {
- $this->clearFilesOlderThan($this->appData->getFolder('TaskProcessing'), self::MAX_TASK_AGE_SECONDS);
- } catch (NotFoundException $e) {
+ iterator_to_array($this->taskProcessingManager->clearFilesOlderThan($this->appData->getFolder('audio2text')));
+ } catch (\OCP\Files\NotFoundException $e) {
// noop
}
}
-
- /**
- * @param ISimpleFolder $folder
- * @param int $ageInSeconds
- * @return void
- */
- private function clearFilesOlderThan(ISimpleFolder $folder, int $ageInSeconds): void {
- foreach ($folder->getDirectoryListing() as $file) {
- if ($file->getMTime() < time() - $ageInSeconds) {
- try {
- $file->delete();
- } catch (NotPermittedException $e) {
- $this->logger->warning('Failed to delete a stale task processing file', ['exception' => $e]);
- }
- }
- }
- }
-
}
diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php
index 07e557d0706..044fa8147a0 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -10,6 +10,7 @@ namespace OC\Template;
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Authentication\Token\IProvider;
use OC\CapabilitiesManager;
+use OC\Core\AppInfo\ConfigLexicon;
use OC\Files\FilenameValidator;
use OC\Share\Share;
use OCA\Provisioning_API\Controller\AUserDataOCSController;
@@ -22,6 +23,7 @@ use OCP\Authentication\Token\IToken;
use OCP\Constants;
use OCP\Defaults;
use OCP\Files\FileInfo;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IInitialStateService;
@@ -50,6 +52,7 @@ class JSConfigHelper {
protected ISession $session,
protected ?IUser $currentUser,
protected IConfig $config,
+ protected readonly IAppConfig $appConfig,
protected IGroupManager $groupManager,
protected IniGetWrapper $iniWrapper,
protected IURLGenerator $urlGenerator,
@@ -94,8 +97,7 @@ class JSConfigHelper {
}
}
- $enableLinkPasswordByDefault = $this->config->getAppValue('core', 'shareapi_enable_link_password_by_default', 'no');
- $enableLinkPasswordByDefault = $enableLinkPasswordByDefault === 'yes';
+ $enableLinkPasswordByDefault = $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_DEFAULT);
$defaultExpireDateEnabled = $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
$defaultExpireDate = $enforceDefaultExpireDate = null;
if ($defaultExpireDateEnabled) {
diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php
index cfc387d2164..42861eddc0d 100644
--- a/lib/private/TemplateLayout.php
+++ b/lib/private/TemplateLayout.php
@@ -20,6 +20,7 @@ use OC\Template\JSResourceLocator;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\INavigationManager;
@@ -44,6 +45,7 @@ class TemplateLayout {
public function __construct(
private IConfig $config,
+ private readonly IAppConfig $appConfig,
private IAppManager $appManager,
private InitialStateService $initialState,
private INavigationManager $navigationManager,
@@ -223,6 +225,7 @@ class TemplateLayout {
\OC::$server->getSession(),
\OC::$server->getUserSession()->getUser(),
$this->config,
+ $this->appConfig,
\OC::$server->getGroupManager(),
\OC::$server->get(IniGetWrapper::class),
\OC::$server->getURLGenerator(),
diff --git a/lib/private/User/DisplayNameCache.php b/lib/private/User/DisplayNameCache.php
index 40e1c752589..4321d95f88e 100644
--- a/lib/private/User/DisplayNameCache.php
+++ b/lib/private/User/DisplayNameCache.php
@@ -25,6 +25,8 @@ use OCP\User\Events\UserDeletedEvent;
* @template-implements IEventListener<UserChangedEvent|UserDeletedEvent>
*/
class DisplayNameCache implements IEventListener {
+ private const CACHE_TTL = 24 * 60 * 60; // 1 day
+
private array $cache = [];
private ICache $memCache;
private IUserManager $userManager;
@@ -56,7 +58,7 @@ class DisplayNameCache implements IEventListener {
$displayName = null;
}
$this->cache[$userId] = $displayName;
- $this->memCache->set($userId, $displayName, 60 * 10); // 10 minutes
+ $this->memCache->set($userId, $displayName, self::CACHE_TTL);
return $displayName;
}
@@ -71,7 +73,7 @@ class DisplayNameCache implements IEventListener {
$userId = $event->getUser()->getUID();
$newDisplayName = $event->getValue();
$this->cache[$userId] = $newDisplayName;
- $this->memCache->set($userId, $newDisplayName, 60 * 10); // 10 minutes
+ $this->memCache->set($userId, $newDisplayName, self::CACHE_TTL);
}
if ($event instanceof UserDeletedEvent) {
$userId = $event->getUser()->getUID();
diff --git a/lib/private/UserStatus/Manager.php b/lib/private/UserStatus/Manager.php
index 4658f61df82..4cfd1c18e79 100644
--- a/lib/private/UserStatus/Manager.php
+++ b/lib/private/UserStatus/Manager.php
@@ -8,35 +8,21 @@ declare(strict_types=1);
*/
namespace OC\UserStatus;
-use OCP\IServerContainer;
use OCP\UserStatus\IManager;
use OCP\UserStatus\IProvider;
use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class Manager implements IManager {
- /** @var IServerContainer */
- private $container;
-
- /** @var LoggerInterface */
- private $logger;
-
- /** @var class-string */
- private $providerClass;
-
- /** @var IProvider */
- private $provider;
-
- /**
- * Manager constructor.
- *
- * @param IServerContainer $container
- * @param LoggerInterface $logger
- */
- public function __construct(IServerContainer $container,
- LoggerInterface $logger) {
- $this->container = $container;
- $this->logger = $logger;
+ /** @var ?class-string */
+ private ?string $providerClass = null;
+ private ?IProvider $provider = null;
+
+ public function __construct(
+ private ContainerInterface $container,
+ private LoggerInterface $logger,
+ ) {
}
/**
@@ -89,7 +75,7 @@ class Manager implements IManager {
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)) {
+ if (!$this->provider instanceof ISettableProvider) {
return;
}
@@ -98,7 +84,7 @@ class Manager implements IManager {
public function revertUserStatus(string $userId, string $messageId, string $status): void {
$this->setupProvider();
- if (!$this->provider || !($this->provider instanceof ISettableProvider)) {
+ if (!$this->provider instanceof ISettableProvider) {
return;
}
$this->provider->revertUserStatus($userId, $messageId, $status);
@@ -106,7 +92,7 @@ class Manager implements IManager {
public function revertMultipleUserStatus(array $userIds, string $messageId, string $status): void {
$this->setupProvider();
- if (!$this->provider || !($this->provider instanceof ISettableProvider)) {
+ if (!$this->provider instanceof ISettableProvider) {
return;
}
$this->provider->revertMultipleUserStatus($userIds, $messageId, $status);
diff --git a/lib/public/Accounts/IAccountManager.php b/lib/public/Accounts/IAccountManager.php
index 92fc0002674..ae5535ef13b 100644
--- a/lib/public/Accounts/IAccountManager.php
+++ b/lib/public/Accounts/IAccountManager.php
@@ -97,10 +97,16 @@ interface IAccountManager {
/**
* @since 15.0.0
+ * @deprecated 32.0.0
*/
public const PROPERTY_TWITTER = 'twitter';
/**
+ * @since 32.0.0
+ */
+ public const PROPERTY_BLUESKY = 'bluesky';
+
+ /**
* @since 26.0.0
*/
public const PROPERTY_FEDIVERSE = 'fediverse';
@@ -160,6 +166,7 @@ interface IAccountManager {
self::PROPERTY_PRONOUNS,
self::PROPERTY_ROLE,
self::PROPERTY_TWITTER,
+ self::PROPERTY_BLUESKY,
self::PROPERTY_WEBSITE,
];
diff --git a/lib/public/Activity/IBulkConsumer.php b/lib/public/Activity/IBulkConsumer.php
new file mode 100644
index 00000000000..9fdf3516b9a
--- /dev/null
+++ b/lib/public/Activity/IBulkConsumer.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\Activity;
+
+/**
+ * Interface IBulkConsumer
+ *
+ * @since 32.0.0
+ */
+interface IBulkConsumer extends IConsumer {
+ /**
+ * @param IEvent $event
+ * @param array $affectedUserIds
+ * @param ISetting $setting
+ * @return void
+ * @since 32.0.0
+ */
+ public function bulkReceive(IEvent $event, array $affectedUserIds, ISetting $setting): void;
+}
diff --git a/lib/public/Activity/IManager.php b/lib/public/Activity/IManager.php
index 30020b6eced..d638b8b2c6b 100644
--- a/lib/public/Activity/IManager.php
+++ b/lib/public/Activity/IManager.php
@@ -52,6 +52,20 @@ interface IManager {
public function publish(IEvent $event): void;
/**
+ * Bulk publish an event for multiple users
+ * taking into account the app specific activity settings
+ *
+ * Make sure to call at least the following methods before sending an Event:
+ * - setApp()
+ * - setType()
+ *
+ * @param IEvent $event
+ * @throws IncompleteActivityException if required values have not been set
+ * @since 32.0.0
+ */
+ public function bulkPublish(IEvent $event, array $affectedUserIds, ISetting $setting): void;
+
+ /**
* In order to improve lazy loading a closure can be registered which will be called in case
* activity consumers are actually requested
*
diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php
index e0fe603ec57..ea9b71d8958 100644
--- a/lib/public/IDBConnection.php
+++ b/lib/public/IDBConnection.php
@@ -45,6 +45,11 @@ interface IDBConnection {
public const PLATFORM_SQLITE = 'sqlite';
/**
+ * @since 32.0.0
+ */
+ public const PLATFORM_MARIADB = 'mariadb';
+
+ /**
* Gets the QueryBuilder for the connection.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder
@@ -357,11 +362,15 @@ interface IDBConnection {
/**
* Returns the database provider name
+ *
* @link https://github.com/nextcloud/server/issues/30877
+ *
+ * @param bool $strict differentiate between database flavors, e.g. MySQL vs MariaDB
+ * @return self::PLATFORM_MYSQL|self::PLATFORM_ORACLE|self::PLATFORM_POSTGRES|self::PLATFORM_SQLITE|self::PLATFORM_MARIADB
+ * @since 32.0.0 Optional parameter $strict was added
* @since 28.0.0
- * @return self::PLATFORM_MYSQL|self::PLATFORM_ORACLE|self::PLATFORM_POSTGRES|self::PLATFORM_SQLITE
*/
- public function getDatabaseProvider(): string;
+ public function getDatabaseProvider(bool $strict = false): string;
/**
* Get the shard definition by name, if configured
diff --git a/lib/public/IInitialStateService.php b/lib/public/IInitialStateService.php
index 672c086b22d..6c482c2cf20 100644
--- a/lib/public/IInitialStateService.php
+++ b/lib/public/IInitialStateService.php
@@ -11,13 +11,13 @@ use Closure;
/**
* @since 16.0.0
- * @deprecated 21 Use OCP\AppFramework\Services\IInitialState or OCP\AppFramework\Services\InitialStateProvider
+ * @deprecated 21 Use {@see \OCP\AppFramework\Services\IInitialState} or {@see \OCP\AppFramework\Services\InitialStateProvider}
* @see \OCP\AppFramework\Services\IInitialState
*/
interface IInitialStateService {
/**
* Allows an app to provide its initial state to the template system.
- * Use this if you know your initial state sill be used for example if
+ * Use this if you know your initial state still be used for example if
* you are in the render function of you controller.
*
* @since 16.0.0
@@ -26,7 +26,7 @@ interface IInitialStateService {
* @param string $key
* @param bool|int|float|string|array|\JsonSerializable $data
*
- * @deprecated 21 Use OCP\AppFramework\Services\IInitialState or OCP\AppFramework\Services\InitialStateProvider
+ * @deprecated 21 Use {@see \OCP\AppFramework\Services\IInitialState} or {@see \OCP\AppFramework\Services\InitialStateProvider}
* @see \OCP\AppFramework\Services\IInitialState::provideInitialState()
*/
public function provideInitialState(string $appName, string $key, $data): void;
@@ -44,7 +44,7 @@ interface IInitialStateService {
* @param string $key
* @param Closure $closure returns a primitive or an object that implements JsonSerializable
*
- * @deprecated 21 Use OCP\AppFramework\Services\IInitialState or OCP\AppFramework\Services\InitialStateProvider
+ * @deprecated 21 Use {@see \OCP\AppFramework\Services\IInitialState} or {@see \OCP\AppFramework\Services\InitialStateProvider}
* @see \OCP\AppFramework\Services\IInitialState::provideLazyInitialState()
*/
public function provideLazyInitialState(string $appName, string $key, Closure $closure): void;
diff --git a/lib/public/Notification/IManager.php b/lib/public/Notification/IManager.php
index 23664af17cd..207a89344b0 100644
--- a/lib/public/Notification/IManager.php
+++ b/lib/public/Notification/IManager.php
@@ -11,7 +11,7 @@ namespace OCP\Notification;
use OCP\AppFramework\Attribute\Consumable;
#[Consumable(since: '9.0.0')]
-interface IManager extends IApp, INotifier {
+interface IManager extends IApp, IPreloadableNotifier {
/**
* @param string $appClass The service must implement IApp, otherwise a
* \InvalidArgumentException is thrown later
diff --git a/lib/public/Notification/INotifier.php b/lib/public/Notification/INotifier.php
index bdc7207216f..b6851e3dfb3 100644
--- a/lib/public/Notification/INotifier.php
+++ b/lib/public/Notification/INotifier.php
@@ -10,6 +10,11 @@ namespace OCP\Notification;
use OCP\AppFramework\Attribute\Implementable;
+/**
+ * Please consider implementing {@see IPreloadableNotifier} to improve performance. It allows to
+ * preload and cache data for many notifications at once instead of loading the data for each
+ * prepared notification separately.
+ */
#[Implementable(since: '9.0.0')]
interface INotifier {
/**
diff --git a/lib/public/Notification/IPreloadableNotifier.php b/lib/public/Notification/IPreloadableNotifier.php
new file mode 100644
index 00000000000..2bdcd84d254
--- /dev/null
+++ b/lib/public/Notification/IPreloadableNotifier.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\Notification;
+
+use OCP\AppFramework\Attribute\Implementable;
+
+/**
+ * Allow notifier implementations to preload and cache data for many notifications at once to
+ * improve performance by, for example, bundling SQL queries.
+ */
+#[Implementable(since: '32.0.0')]
+interface IPreloadableNotifier extends INotifier {
+ /**
+ * This method provides a way for notifier implementations to preload and cache data for many
+ * notifications. The data is meant to be consumed later in the {@see INotifier::prepare()}
+ * method to improve performance.
+ *
+ * @since 32.0.0
+ *
+ * @param INotification[] $notifications The notifications which are about to be prepared in the next step.
+ * @param string $languageCode The code of the language that should be used to prepare the notification.
+ */
+ public function preloadDataForParsing(array $notifications, string $languageCode): void;
+}
diff --git a/lib/public/Profile/IProfileManager.php b/lib/public/Profile/IProfileManager.php
index f4e90e39d12..aec06fb4c86 100644
--- a/lib/public/Profile/IProfileManager.php
+++ b/lib/public/Profile/IProfileManager.php
@@ -55,6 +55,7 @@ interface IProfileManager {
IAccountManager::PROPERTY_EMAIL => self::VISIBILITY_SHOW_USERS_ONLY,
IAccountManager::PROPERTY_PHONE => self::VISIBILITY_SHOW_USERS_ONLY,
IAccountManager::PROPERTY_TWITTER => self::VISIBILITY_SHOW,
+ IAccountManager::PROPERTY_BLUESKY => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_WEBSITE => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_PRONOUNS => self::VISIBILITY_SHOW,
];
diff --git a/lib/public/Search/IExternalProvider.php b/lib/public/Search/IExternalProvider.php
new file mode 100644
index 00000000000..a9a8e3ad6ba
--- /dev/null
+++ b/lib/public/Search/IExternalProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\Search;
+
+/**
+ * Interface for search providers that forward user queries to external services.
+ *
+ * @since 32.0.0
+ */
+interface IExternalProvider extends IProvider {
+ /**
+ * Indicates whether this search provider queries external (3rd-party) resources.
+ * This is used by the Unified Search modal filter (toggle switch). By default, searching through external providers is disabled.
+ *
+ * @return bool default false
+ *
+ * @since 32.0.0
+ */
+ public function isExternalProvider(): bool;
+}
diff --git a/lib/public/TaskProcessing/IManager.php b/lib/public/TaskProcessing/IManager.php
index f161030f5f4..731250d7aa1 100644
--- a/lib/public/TaskProcessing/IManager.php
+++ b/lib/public/TaskProcessing/IManager.php
@@ -26,6 +26,7 @@ use OCP\TaskProcessing\Exception\ValidationException;
* @since 30.0.0
*/
interface IManager {
+
/**
* @since 30.0.0
*/
@@ -233,4 +234,14 @@ interface IManager {
* @since 30.0.0
*/
public function setTaskStatus(Task $task, int $status): void;
+
+ /**
+ * Extract all input and output file IDs from a task
+ *
+ * @param Task $task
+ * @return list<int>
+ * @throws NotFoundException
+ * @since 32.0.0
+ */
+ public function extractFileIdsFromTask(Task $task): array;
}
diff --git a/lib/public/TaskProcessing/Task.php b/lib/public/TaskProcessing/Task.php
index 71c271cd43d..06dc84d59ff 100644
--- a/lib/public/TaskProcessing/Task.php
+++ b/lib/public/TaskProcessing/Task.php
@@ -66,6 +66,7 @@ final class Task implements \JsonSerializable {
protected ?int $scheduledAt = null;
protected ?int $startedAt = null;
protected ?int $endedAt = null;
+ protected bool $allowCleanup = true;
/**
* @param string $taskTypeId
@@ -253,7 +254,23 @@ final class Task implements \JsonSerializable {
}
/**
- * @psalm-return array{id: int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array<string, list<numeric|string>|numeric|string>, output: ?array<string, list<numeric|string>|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float, scheduledAt: ?int, startedAt: ?int, endedAt: ?int}
+ * @return bool
+ * @since 32.0.0
+ */
+ final public function getAllowCleanup(): bool {
+ return $this->allowCleanup;
+ }
+
+ /**
+ * @param bool $allowCleanup
+ * @since 32.0.0
+ */
+ final public function setAllowCleanup(bool $allowCleanup): void {
+ $this->allowCleanup = $allowCleanup;
+ }
+
+ /**
+ * @psalm-return array{id: int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array<string, list<numeric|string>|numeric|string>, output: ?array<string, list<numeric|string>|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float, scheduledAt: ?int, startedAt: ?int, endedAt: ?int, allowCleanup: bool}
* @since 30.0.0
*/
final public function jsonSerialize(): array {
@@ -272,6 +289,7 @@ final class Task implements \JsonSerializable {
'scheduledAt' => $this->getScheduledAt(),
'startedAt' => $this->getStartedAt(),
'endedAt' => $this->getEndedAt(),
+ 'allowCleanup' => $this->getAllowCleanup(),
];
}