aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/Command/User/Profile.php234
-rw-r--r--core/l10n/gl.js2
-rw-r--r--core/l10n/gl.json2
-rw-r--r--core/l10n/lv.js9
-rw-r--r--core/l10n/lv.json9
-rw-r--r--core/l10n/nl.js1
-rw-r--r--core/l10n/nl.json1
-rw-r--r--core/l10n/ru.js5
-rw-r--r--core/l10n/ru.json5
-rw-r--r--core/register_command.php2
-rw-r--r--core/src/OC/eventsource.js4
11 files changed, 262 insertions, 12 deletions
diff --git a/core/Command/User/Profile.php b/core/Command/User/Profile.php
new file mode 100644
index 00000000000..fd5fbed08cd
--- /dev/null
+++ b/core/Command/User/Profile.php
@@ -0,0 +1,234 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Core\Command\User;
+
+use OC\Core\Command\Base;
+use OCP\Accounts\IAccount;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\PropertyDoesNotExistException;
+use OCP\IUser;
+use OCP\IUserManager;
+use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Profile extends Base {
+ public function __construct(
+ protected IUserManager $userManager,
+ protected IAccountManager $accountManager,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ parent::configure();
+ $this
+ ->setName('user:profile')
+ ->setDescription('Read and modify user profile properties')
+ ->addArgument(
+ 'uid',
+ InputArgument::REQUIRED,
+ 'Account ID used to login'
+ )
+ ->addArgument(
+ 'key',
+ InputArgument::OPTIONAL,
+ 'Profile property to set, get or delete',
+ ''
+ )
+
+ // Get
+ ->addOption(
+ 'default-value',
+ null,
+ InputOption::VALUE_REQUIRED,
+ '(Only applicable on get) If no default value is set and the property does not exist, the command will exit with 1'
+ )
+
+ // Set
+ ->addArgument(
+ 'value',
+ InputArgument::OPTIONAL,
+ 'The new value of the property',
+ null
+ )
+ ->addOption(
+ 'update-only',
+ null,
+ InputOption::VALUE_NONE,
+ 'Only updates the value, if it is not set before, it is not being added'
+ )
+
+ // Delete
+ ->addOption(
+ 'delete',
+ null,
+ InputOption::VALUE_NONE,
+ 'Specify this option to delete the property value'
+ )
+ ->addOption(
+ 'error-if-not-exists',
+ null,
+ InputOption::VALUE_NONE,
+ 'Checks whether the property exists before deleting it'
+ )
+ ;
+ }
+
+ protected function checkInput(InputInterface $input): IUser {
+ $uid = $input->getArgument('uid');
+ $user = $this->userManager->get($uid);
+ if (!$user) {
+ throw new \InvalidArgumentException('The user "' . $uid . '" does not exist.');
+ }
+ // normalize uid
+ $input->setArgument('uid', $user->getUID());
+
+ $key = $input->getArgument('key');
+ if ($key === '') {
+ if ($input->hasParameterOption('--default-value')) {
+ throw new \InvalidArgumentException('The "default-value" option can only be used when specifying a key.');
+ }
+ if ($input->getArgument('value') !== null) {
+ throw new \InvalidArgumentException('The value argument can only be used when specifying a key.');
+ }
+ if ($input->getOption('delete')) {
+ throw new \InvalidArgumentException('The "delete" option can only be used when specifying a key.');
+ }
+ }
+
+ if ($input->getArgument('value') !== null && $input->hasParameterOption('--default-value')) {
+ throw new \InvalidArgumentException('The value argument can not be used together with "default-value".');
+ }
+ if ($input->getOption('update-only') && $input->getArgument('value') === null) {
+ throw new \InvalidArgumentException('The "update-only" option can only be used together with "value".');
+ }
+
+ if ($input->getOption('delete') && $input->hasParameterOption('--default-value')) {
+ throw new \InvalidArgumentException('The "delete" option can not be used together with "default-value".');
+ }
+ if ($input->getOption('delete') && $input->getArgument('value') !== null) {
+ throw new \InvalidArgumentException('The "delete" option can not be used together with "value".');
+ }
+ if ($input->getOption('error-if-not-exists') && !$input->getOption('delete')) {
+ throw new \InvalidArgumentException('The "error-if-not-exists" option can only be used together with "delete".');
+ }
+
+ return $user;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ try {
+ $user = $this->checkInput($input);
+ } catch (\InvalidArgumentException $e) {
+ $output->writeln('<error>' . $e->getMessage() . '</error>');
+ return self::FAILURE;
+ }
+
+ $uid = $input->getArgument('uid');
+ $key = $input->getArgument('key');
+ $userAccount = $this->accountManager->getAccount($user);
+
+ if ($key === '') {
+ $settings = $this->getAllProfileProperties($userAccount);
+ $this->writeArrayInOutputFormat($input, $output, $settings);
+ return self::SUCCESS;
+ }
+
+ $value = $this->getStoredValue($userAccount, $key);
+ $inputValue = $input->getArgument('value');
+ if ($inputValue !== null) {
+ if ($input->hasParameterOption('--update-only') && $value === null) {
+ $output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
+ return self::FAILURE;
+ }
+
+ return $this->editProfileProperty($output, $userAccount, $key, $inputValue);
+ } elseif ($input->hasParameterOption('--delete')) {
+ if ($input->hasParameterOption('--error-if-not-exists') && $value === null) {
+ $output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
+ return self::FAILURE;
+ }
+
+ return $this->deleteProfileProperty($output, $userAccount, $key);
+ } elseif ($value !== null) {
+ $output->writeln($value);
+ } elseif ($input->hasParameterOption('--default-value')) {
+ $output->writeln($input->getOption('default-value'));
+ } else {
+ $output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
+ return self::FAILURE;
+ }
+
+ return self::SUCCESS;
+ }
+
+ private function deleteProfileProperty(OutputInterface $output, IAccount $userAccount, string $key): int {
+ return $this->editProfileProperty($output, $userAccount, $key, '');
+ }
+
+ private function editProfileProperty(OutputInterface $output, IAccount $userAccount, string $key, string $value): int {
+ try {
+ $userAccount->getProperty($key)->setValue($value);
+ } catch (PropertyDoesNotExistException $exception) {
+ $output->writeln('<error>' . $exception->getMessage() . '</error>');
+ return self::FAILURE;
+ }
+
+ $this->accountManager->updateAccount($userAccount);
+ return self::SUCCESS;
+ }
+
+ private function getStoredValue(IAccount $userAccount, string $key): ?string {
+ try {
+ $property = $userAccount->getProperty($key);
+ } catch (PropertyDoesNotExistException) {
+ return null;
+ }
+ return $property->getValue() === '' ? null : $property->getValue();
+ }
+
+ private function getAllProfileProperties(IAccount $userAccount): array {
+ $properties = [];
+
+ foreach ($userAccount->getAllProperties() as $property) {
+ if ($property->getValue() !== '') {
+ $properties[$property->getName()] = $property->getValue();
+ }
+ }
+
+ return $properties;
+ }
+
+ /**
+ * @param string $argumentName
+ * @param CompletionContext $context
+ * @return string[]
+ */
+ public function completeArgumentValues($argumentName, CompletionContext $context): array {
+ if ($argumentName === 'uid') {
+ return array_map(static fn (IUser $user) => $user->getUID(), $this->userManager->search($context->getCurrentWord()));
+ }
+ if ($argumentName === 'key') {
+ $userId = $context->getWordAtIndex($context->getWordIndex() - 1);
+ $user = $this->userManager->get($userId);
+ if (!($user instanceof IUser)) {
+ return [];
+ }
+
+ $account = $this->accountManager->getAccount($user);
+
+ $properties = $this->getAllProfileProperties($account);
+ return array_keys($properties);
+ }
+ return [];
+ }
+}
diff --git a/core/l10n/gl.js b/core/l10n/gl.js
index 92a28bcbc32..6dcb3d53731 100644
--- a/core/l10n/gl.js
+++ b/core/l10n/gl.js
@@ -232,7 +232,7 @@ OC.L10N.register(
"Continue with this unsupported browser" : "Continuar con este navegador non compatíbel",
"Supported versions" : "Versións compatíbeis",
"Search {types} …" : "Buscando {types}…",
- "Choose {file}" : "Escoller {file}",
+ "Choose {file}" : "Escoller {file}",
"Choose" : "Escoller",
"Copy to {target}" : "Copiar en {target}",
"Copy" : "Copiar",
diff --git a/core/l10n/gl.json b/core/l10n/gl.json
index bd56103b250..a3c2e30ff40 100644
--- a/core/l10n/gl.json
+++ b/core/l10n/gl.json
@@ -230,7 +230,7 @@
"Continue with this unsupported browser" : "Continuar con este navegador non compatíbel",
"Supported versions" : "Versións compatíbeis",
"Search {types} …" : "Buscando {types}…",
- "Choose {file}" : "Escoller {file}",
+ "Choose {file}" : "Escoller {file}",
"Choose" : "Escoller",
"Copy to {target}" : "Copiar en {target}",
"Copy" : "Copiar",
diff --git a/core/l10n/lv.js b/core/l10n/lv.js
index b5c4eb91dbf..701a410cef0 100644
--- a/core/l10n/lv.js
+++ b/core/l10n/lv.js
@@ -27,19 +27,20 @@ OC.L10N.register(
"Could not complete login" : "Nevarēja pabeigt pieteikšanos",
"State token missing" : "Trūkst stāvokļa tekstvienības",
"Your login token is invalid or has expired" : "Pieteikšanās pilnvara nav derīga vai ir beigusies",
- "This community release of Nextcloud is unsupported and push notifications are limited." : "Šī Nextcloud kopienas versija nav atbalstīta un push paziņojumi ir ierobežoti.",
+ "Please use original client" : "Lūgums izmantot sākotnējo klientu",
+ "This community release of Nextcloud is unsupported and push notifications are limited." : "Šis Nextcloud kopienas laidiens nav atbalstīts un pašpiegādes paziņojumi ir ierobežoti.",
"Login" : "Pieteikumvārds",
- "Unsupported email length (>255)" : "Neatbalstāms e-pasta garums (>255)",
+ "Unsupported email length (>255)" : "Neatbalstīts e-pasta adreses garums (>255)",
"Password reset is disabled" : "Paroles atiestatīšana ir atspējota",
"Could not reset password because the token is expired" : "Nevarēja atiestatīt paroli, jo ir beidzies tekstvienības derīgums",
"Could not reset password because the token is invalid" : "Nevarēja atiestatīt paroli, jo tekstvienība ir nederīga",
"Password is too long. Maximum allowed length is 469 characters." : "Parole ir pārāk gara. Lielākais atļautais garums ir 469 rakstzīmes.",
- "%s password reset" : "%s paroles maiņa",
+ "%s password reset" : "%s paroles atiestatīšana",
"Password reset" : "Parole atiestatīta",
"Click the following button to reset your password. If you have not requested the password reset, then ignore this email." : "Jānospiež zemāk esošā poga, lai atiestatītu savu paroli. Šis e-pasta ziņojums nav jāņem vērā, ja paroles atiestatīšana netika pieprasīta.",
"Click the following link to reset your password. If you have not requested the password reset, then ignore this email." : "Jāklikšķina uz zemāk esošās saites, lai atiestatītu savu paroli. Šis e-pasta ziņojums nav jāņem vērā, ja paroles atiestatīšana netika pieprasīta.",
"Reset your password" : "Atiestatīt paroli",
- "The given provider is not available" : "Norādītājs pakalpojuma sniedzējs nav pieejams",
+ "The given provider is not available" : "Norādītais nodrošinātājs nav pieejams",
"Task not found" : "Uzdevums nav atrasts",
"Internal error" : "Iekšēja kļūda",
"Not found" : "Nav atrasts",
diff --git a/core/l10n/lv.json b/core/l10n/lv.json
index b0e26a06ba7..e49b799e59b 100644
--- a/core/l10n/lv.json
+++ b/core/l10n/lv.json
@@ -25,19 +25,20 @@
"Could not complete login" : "Nevarēja pabeigt pieteikšanos",
"State token missing" : "Trūkst stāvokļa tekstvienības",
"Your login token is invalid or has expired" : "Pieteikšanās pilnvara nav derīga vai ir beigusies",
- "This community release of Nextcloud is unsupported and push notifications are limited." : "Šī Nextcloud kopienas versija nav atbalstīta un push paziņojumi ir ierobežoti.",
+ "Please use original client" : "Lūgums izmantot sākotnējo klientu",
+ "This community release of Nextcloud is unsupported and push notifications are limited." : "Šis Nextcloud kopienas laidiens nav atbalstīts un pašpiegādes paziņojumi ir ierobežoti.",
"Login" : "Pieteikumvārds",
- "Unsupported email length (>255)" : "Neatbalstāms e-pasta garums (>255)",
+ "Unsupported email length (>255)" : "Neatbalstīts e-pasta adreses garums (>255)",
"Password reset is disabled" : "Paroles atiestatīšana ir atspējota",
"Could not reset password because the token is expired" : "Nevarēja atiestatīt paroli, jo ir beidzies tekstvienības derīgums",
"Could not reset password because the token is invalid" : "Nevarēja atiestatīt paroli, jo tekstvienība ir nederīga",
"Password is too long. Maximum allowed length is 469 characters." : "Parole ir pārāk gara. Lielākais atļautais garums ir 469 rakstzīmes.",
- "%s password reset" : "%s paroles maiņa",
+ "%s password reset" : "%s paroles atiestatīšana",
"Password reset" : "Parole atiestatīta",
"Click the following button to reset your password. If you have not requested the password reset, then ignore this email." : "Jānospiež zemāk esošā poga, lai atiestatītu savu paroli. Šis e-pasta ziņojums nav jāņem vērā, ja paroles atiestatīšana netika pieprasīta.",
"Click the following link to reset your password. If you have not requested the password reset, then ignore this email." : "Jāklikšķina uz zemāk esošās saites, lai atiestatītu savu paroli. Šis e-pasta ziņojums nav jāņem vērā, ja paroles atiestatīšana netika pieprasīta.",
"Reset your password" : "Atiestatīt paroli",
- "The given provider is not available" : "Norādītājs pakalpojuma sniedzējs nav pieejams",
+ "The given provider is not available" : "Norādītais nodrošinātājs nav pieejams",
"Task not found" : "Uzdevums nav atrasts",
"Internal error" : "Iekšēja kļūda",
"Not found" : "Nav atrasts",
diff --git a/core/l10n/nl.js b/core/l10n/nl.js
index b5de7558cdc..04a11c30493 100644
--- a/core/l10n/nl.js
+++ b/core/l10n/nl.js
@@ -27,6 +27,7 @@ OC.L10N.register(
"Could not complete login" : "De login kon niet worden voltooid",
"State token missing" : "Toestandstoken bestaat niet",
"Your login token is invalid or has expired" : "Je inlogtoken is ongeldig of is vervallen",
+ "Please use original client" : "Gebruik alsjeblieft de originele client",
"This community release of Nextcloud is unsupported and push notifications are limited." : "Deze community release van Nextcloud wordt niet ondersteund en meldingen zijn beperkt",
"Login" : "Inloggen",
"Unsupported email length (>255)" : "Niet ondersteunde e-maillengte (>255)",
diff --git a/core/l10n/nl.json b/core/l10n/nl.json
index d096601a4cb..cf651e8ed18 100644
--- a/core/l10n/nl.json
+++ b/core/l10n/nl.json
@@ -25,6 +25,7 @@
"Could not complete login" : "De login kon niet worden voltooid",
"State token missing" : "Toestandstoken bestaat niet",
"Your login token is invalid or has expired" : "Je inlogtoken is ongeldig of is vervallen",
+ "Please use original client" : "Gebruik alsjeblieft de originele client",
"This community release of Nextcloud is unsupported and push notifications are limited." : "Deze community release van Nextcloud wordt niet ondersteund en meldingen zijn beperkt",
"Login" : "Inloggen",
"Unsupported email length (>255)" : "Niet ondersteunde e-maillengte (>255)",
diff --git a/core/l10n/ru.js b/core/l10n/ru.js
index 9a8542a6abe..3b3a18387d5 100644
--- a/core/l10n/ru.js
+++ b/core/l10n/ru.js
@@ -328,6 +328,11 @@ OC.L10N.register(
"Login form is disabled." : "Форма входа отключена.",
"The Nextcloud login form is disabled. Use another login option if available or contact your administration." : "Диалог входа отключен. Используйте другой способ входа или свяжитесь с администратором.",
"More actions" : "Больше действий",
+ "User menu" : "Меню пользователя",
+ "You will be identified as {user} by the account owner." : "Владелец учётной записи будет видеть вас как {user}.",
+ "You are currently not identified." : "В данный момент вы не идентифицированы.",
+ "Set public name" : "Задать публичное имя",
+ "Change public name" : "Изменить публичное имя",
"Password is too weak" : "Пароль слишком слабый",
"Password is weak" : "Пароль слабый",
"Password is average" : "Пароль средний",
diff --git a/core/l10n/ru.json b/core/l10n/ru.json
index 9e765e2dc41..f4f2442d2e4 100644
--- a/core/l10n/ru.json
+++ b/core/l10n/ru.json
@@ -326,6 +326,11 @@
"Login form is disabled." : "Форма входа отключена.",
"The Nextcloud login form is disabled. Use another login option if available or contact your administration." : "Диалог входа отключен. Используйте другой способ входа или свяжитесь с администратором.",
"More actions" : "Больше действий",
+ "User menu" : "Меню пользователя",
+ "You will be identified as {user} by the account owner." : "Владелец учётной записи будет видеть вас как {user}.",
+ "You are currently not identified." : "В данный момент вы не идентифицированы.",
+ "Set public name" : "Задать публичное имя",
+ "Change public name" : "Изменить публичное имя",
"Password is too weak" : "Пароль слишком слабый",
"Password is weak" : "Пароль слабый",
"Password is average" : "Пароль средний",
diff --git a/core/register_command.php b/core/register_command.php
index 72a4b70f059..488317d2f5d 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -92,6 +92,7 @@ use OC\Core\Command\User\ClearGeneratedAvatarCacheCommand;
use OC\Core\Command\User\Info;
use OC\Core\Command\User\Keys\Verify;
use OC\Core\Command\User\LastSeen;
+use OC\Core\Command\User\Profile;
use OC\Core\Command\User\Report;
use OC\Core\Command\User\ResetPassword;
use OC\Core\Command\User\Setting;
@@ -206,6 +207,7 @@ if ($config->getSystemValueBool('installed', false)) {
$application->add(Server::get(Report::class));
$application->add(Server::get(ResetPassword::class));
$application->add(Server::get(Setting::class));
+ $application->add(Server::get(Profile::class));
$application->add(Server::get(Command\User\ListCommand::class));
$application->add(Server::get(ClearGeneratedAvatarCacheCommand::class));
$application->add(Server::get(Info::class));
diff --git a/core/src/OC/eventsource.js b/core/src/OC/eventsource.js
index bfda0a73ad0..090c351c057 100644
--- a/core/src/OC/eventsource.js
+++ b/core/src/OC/eventsource.js
@@ -7,7 +7,7 @@
/* eslint-disable */
import $ from 'jquery'
-import { getToken } from './requesttoken.ts'
+import { getRequestToken } from './requesttoken.ts'
/**
* Create a new event source
@@ -28,7 +28,7 @@ const OCEventSource = function(src, data) {
dataStr += name + '=' + encodeURIComponent(data[name]) + '&'
}
}
- dataStr += 'requesttoken=' + encodeURIComponent(getToken())
+ dataStr += 'requesttoken=' + encodeURIComponent(getRequestToken())
if (!this.useFallBack && typeof EventSource !== 'undefined') {
joinChar = '&'
if (src.indexOf('?') === -1) {