Skip to content

Commit a1cf92c

Browse files
authored
Merge pull request #53540 from nextcloud/feature/add-profile-to-occ
Feature/add profile to occ
2 parents 63b9ea7 + 7265f10 commit a1cf92c

File tree

6 files changed

+722
-27
lines changed

6 files changed

+722
-27
lines changed

core/Command/User/Profile.php

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OC\Core\Command\User;
10+
11+
use OC\Core\Command\Base;
12+
use OCP\Accounts\IAccount;
13+
use OCP\Accounts\IAccountManager;
14+
use OCP\Accounts\PropertyDoesNotExistException;
15+
use OCP\IUser;
16+
use OCP\IUserManager;
17+
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
18+
use Symfony\Component\Console\Input\InputArgument;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Input\InputOption;
21+
use Symfony\Component\Console\Output\OutputInterface;
22+
23+
class Profile extends Base {
24+
public function __construct(
25+
protected IUserManager $userManager,
26+
protected IAccountManager $accountManager,
27+
) {
28+
parent::__construct();
29+
}
30+
31+
protected function configure() {
32+
parent::configure();
33+
$this
34+
->setName('user:profile')
35+
->setDescription('Read and modify user profile properties')
36+
->addArgument(
37+
'uid',
38+
InputArgument::REQUIRED,
39+
'Account ID used to login'
40+
)
41+
->addArgument(
42+
'key',
43+
InputArgument::OPTIONAL,
44+
'Profile property to set, get or delete',
45+
''
46+
)
47+
48+
// Get
49+
->addOption(
50+
'default-value',
51+
null,
52+
InputOption::VALUE_REQUIRED,
53+
'(Only applicable on get) If no default value is set and the property does not exist, the command will exit with 1'
54+
)
55+
56+
// Set
57+
->addArgument(
58+
'value',
59+
InputArgument::OPTIONAL,
60+
'The new value of the property',
61+
null
62+
)
63+
->addOption(
64+
'update-only',
65+
null,
66+
InputOption::VALUE_NONE,
67+
'Only updates the value, if it is not set before, it is not being added'
68+
)
69+
70+
// Delete
71+
->addOption(
72+
'delete',
73+
null,
74+
InputOption::VALUE_NONE,
75+
'Specify this option to delete the property value'
76+
)
77+
->addOption(
78+
'error-if-not-exists',
79+
null,
80+
InputOption::VALUE_NONE,
81+
'Checks whether the property exists before deleting it'
82+
)
83+
;
84+
}
85+
86+
protected function checkInput(InputInterface $input): IUser {
87+
$uid = $input->getArgument('uid');
88+
$user = $this->userManager->get($uid);
89+
if (!$user) {
90+
throw new \InvalidArgumentException('The user "' . $uid . '" does not exist.');
91+
}
92+
// normalize uid
93+
$input->setArgument('uid', $user->getUID());
94+
95+
$key = $input->getArgument('key');
96+
if ($key === '') {
97+
if ($input->hasParameterOption('--default-value')) {
98+
throw new \InvalidArgumentException('The "default-value" option can only be used when specifying a key.');
99+
}
100+
if ($input->getArgument('value') !== null) {
101+
throw new \InvalidArgumentException('The value argument can only be used when specifying a key.');
102+
}
103+
if ($input->getOption('delete')) {
104+
throw new \InvalidArgumentException('The "delete" option can only be used when specifying a key.');
105+
}
106+
}
107+
108+
if ($input->getArgument('value') !== null && $input->hasParameterOption('--default-value')) {
109+
throw new \InvalidArgumentException('The value argument can not be used together with "default-value".');
110+
}
111+
if ($input->getOption('update-only') && $input->getArgument('value') === null) {
112+
throw new \InvalidArgumentException('The "update-only" option can only be used together with "value".');
113+
}
114+
115+
if ($input->getOption('delete') && $input->hasParameterOption('--default-value')) {
116+
throw new \InvalidArgumentException('The "delete" option can not be used together with "default-value".');
117+
}
118+
if ($input->getOption('delete') && $input->getArgument('value') !== null) {
119+
throw new \InvalidArgumentException('The "delete" option can not be used together with "value".');
120+
}
121+
if ($input->getOption('error-if-not-exists') && !$input->getOption('delete')) {
122+
throw new \InvalidArgumentException('The "error-if-not-exists" option can only be used together with "delete".');
123+
}
124+
125+
return $user;
126+
}
127+
128+
protected function execute(InputInterface $input, OutputInterface $output): int {
129+
try {
130+
$user = $this->checkInput($input);
131+
} catch (\InvalidArgumentException $e) {
132+
$output->writeln('<error>' . $e->getMessage() . '</error>');
133+
return self::FAILURE;
134+
}
135+
136+
$uid = $input->getArgument('uid');
137+
$key = $input->getArgument('key');
138+
$userAccount = $this->accountManager->getAccount($user);
139+
140+
if ($key === '') {
141+
$settings = $this->getAllProfileProperties($userAccount);
142+
$this->writeArrayInOutputFormat($input, $output, $settings);
143+
return self::SUCCESS;
144+
}
145+
146+
$value = $this->getStoredValue($userAccount, $key);
147+
$inputValue = $input->getArgument('value');
148+
if ($inputValue !== null) {
149+
if ($input->hasParameterOption('--update-only') && $value === null) {
150+
$output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
151+
return self::FAILURE;
152+
}
153+
154+
return $this->editProfileProperty($output, $userAccount, $key, $inputValue);
155+
} elseif ($input->hasParameterOption('--delete')) {
156+
if ($input->hasParameterOption('--error-if-not-exists') && $value === null) {
157+
$output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
158+
return self::FAILURE;
159+
}
160+
161+
return $this->deleteProfileProperty($output, $userAccount, $key);
162+
} elseif ($value !== null) {
163+
$output->writeln($value);
164+
} elseif ($input->hasParameterOption('--default-value')) {
165+
$output->writeln($input->getOption('default-value'));
166+
} else {
167+
$output->writeln('<error>The property does not exist for user "' . $uid . '".</error>');
168+
return self::FAILURE;
169+
}
170+
171+
return self::SUCCESS;
172+
}
173+
174+
private function deleteProfileProperty(OutputInterface $output, IAccount $userAccount, string $key): int {
175+
return $this->editProfileProperty($output, $userAccount, $key, '');
176+
}
177+
178+
private function editProfileProperty(OutputInterface $output, IAccount $userAccount, string $key, string $value): int {
179+
try {
180+
$userAccount->getProperty($key)->setValue($value);
181+
} catch (PropertyDoesNotExistException $exception) {
182+
$output->writeln('<error>' . $exception->getMessage() . '</error>');
183+
return self::FAILURE;
184+
}
185+
186+
$this->accountManager->updateAccount($userAccount);
187+
return self::SUCCESS;
188+
}
189+
190+
private function getStoredValue(IAccount $userAccount, string $key): ?string {
191+
try {
192+
$property = $userAccount->getProperty($key);
193+
} catch (PropertyDoesNotExistException) {
194+
return null;
195+
}
196+
return $property->getValue() === '' ? null : $property->getValue();
197+
}
198+
199+
private function getAllProfileProperties(IAccount $userAccount): array {
200+
$properties = [];
201+
202+
foreach ($userAccount->getAllProperties() as $property) {
203+
if ($property->getValue() !== '') {
204+
$properties[$property->getName()] = $property->getValue();
205+
}
206+
}
207+
208+
return $properties;
209+
}
210+
211+
/**
212+
* @param string $argumentName
213+
* @param CompletionContext $context
214+
* @return string[]
215+
*/
216+
public function completeArgumentValues($argumentName, CompletionContext $context): array {
217+
if ($argumentName === 'uid') {
218+
return array_map(static fn (IUser $user) => $user->getUID(), $this->userManager->search($context->getCurrentWord()));
219+
}
220+
if ($argumentName === 'key') {
221+
$userId = $context->getWordAtIndex($context->getWordIndex() - 1);
222+
$user = $this->userManager->get($userId);
223+
if (!($user instanceof IUser)) {
224+
return [];
225+
}
226+
227+
$account = $this->accountManager->getAccount($user);
228+
229+
$properties = $this->getAllProfileProperties($account);
230+
return array_keys($properties);
231+
}
232+
return [];
233+
}
234+
}

core/register_command.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
use OC\Core\Command\User\Info;
9393
use OC\Core\Command\User\Keys\Verify;
9494
use OC\Core\Command\User\LastSeen;
95+
use OC\Core\Command\User\Profile;
9596
use OC\Core\Command\User\Report;
9697
use OC\Core\Command\User\ResetPassword;
9798
use OC\Core\Command\User\Setting;
@@ -206,6 +207,7 @@
206207
$application->add(Server::get(Report::class));
207208
$application->add(Server::get(ResetPassword::class));
208209
$application->add(Server::get(Setting::class));
210+
$application->add(Server::get(Profile::class));
209211
$application->add(Server::get(Command\User\ListCommand::class));
210212
$application->add(Server::get(ClearGeneratedAvatarCacheCommand::class));
211213
$application->add(Server::get(Info::class));

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,7 @@
13311331
'OC\\Core\\Command\\User\\Keys\\Verify' => $baseDir . '/core/Command/User/Keys/Verify.php',
13321332
'OC\\Core\\Command\\User\\LastSeen' => $baseDir . '/core/Command/User/LastSeen.php',
13331333
'OC\\Core\\Command\\User\\ListCommand' => $baseDir . '/core/Command/User/ListCommand.php',
1334+
'OC\\Core\\Command\\User\\Profile' => $baseDir . '/core/Command/User/Profile.php',
13341335
'OC\\Core\\Command\\User\\Report' => $baseDir . '/core/Command/User/Report.php',
13351336
'OC\\Core\\Command\\User\\ResetPassword' => $baseDir . '/core/Command/User/ResetPassword.php',
13361337
'OC\\Core\\Command\\User\\Setting' => $baseDir . '/core/Command/User/Setting.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
13721372
'OC\\Core\\Command\\User\\Keys\\Verify' => __DIR__ . '/../../..' . '/core/Command/User/Keys/Verify.php',
13731373
'OC\\Core\\Command\\User\\LastSeen' => __DIR__ . '/../../..' . '/core/Command/User/LastSeen.php',
13741374
'OC\\Core\\Command\\User\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/User/ListCommand.php',
1375+
'OC\\Core\\Command\\User\\Profile' => __DIR__ . '/../../..' . '/core/Command/User/Profile.php',
13751376
'OC\\Core\\Command\\User\\Report' => __DIR__ . '/../../..' . '/core/Command/User/Report.php',
13761377
'OC\\Core\\Command\\User\\ResetPassword' => __DIR__ . '/../../..' . '/core/Command/User/ResetPassword.php',
13771378
'OC\\Core\\Command\\User\\Setting' => __DIR__ . '/../../..' . '/core/Command/User/Setting.php',

0 commit comments

Comments
 (0)