diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 2 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 2 | ||||
-rw-r--r-- | lib/l10n/ja.js | 18 | ||||
-rw-r--r-- | lib/l10n/ja.json | 18 | ||||
-rw-r--r-- | lib/private/Accounts/AccountManager.php | 58 | ||||
-rw-r--r-- | lib/private/AppConfig.php | 26 | ||||
-rw-r--r-- | lib/private/AppFramework/App.php | 32 | ||||
-rw-r--r-- | lib/private/AppFramework/DependencyInjection/DIContainer.php | 4 | ||||
-rw-r--r-- | lib/private/AppFramework/Utility/SimpleContainer.php | 4 | ||||
-rw-r--r-- | lib/private/Config/ConfigManager.php | 24 | ||||
-rw-r--r-- | lib/private/Config/PresetManager.php | 48 | ||||
-rw-r--r-- | lib/private/Config/UserConfig.php | 19 | ||||
-rw-r--r-- | lib/private/Profile/Actions/BlueskyAction.php | 65 | ||||
-rw-r--r-- | lib/private/Profile/ProfileManager.php | 31 | ||||
-rw-r--r-- | lib/public/Accounts/IAccountManager.php | 7 | ||||
-rw-r--r-- | lib/public/Profile/IProfileManager.php | 1 |
16 files changed, 281 insertions, 78 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index fda8798fe43..acded1ed539 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1219,6 +1219,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', @@ -1904,6 +1905,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 69e3c41284b..bb20a68eae3 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1260,6 +1260,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', @@ -1945,6 +1946,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/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/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/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 77135986d5f..7bf32852209 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -71,7 +71,6 @@ class App { return null; } - /** * Shortcut for calling a controller method and printing the result * @@ -82,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); @@ -134,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'); @@ -211,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..0bce8ac193b 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -63,7 +63,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 +152,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), 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/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/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/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/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, ]; |