mirror of https://github.com/nextcloud/server
feat(users): Store and load a user's manager
Co-Authored-By: hamza221 <hamzamahjoubi221@gmail.com> Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
parent
1399c88ee1
commit
1381c4c157
|
@ -31,15 +31,19 @@ use Exception;
|
|||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\IImage;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use Sabre\VObject\Component\VCard;
|
||||
use Sabre\VObject\Property\Text;
|
||||
|
||||
class Converter {
|
||||
/** @var IAccountManager */
|
||||
private $accountManager;
|
||||
private IUserManager $userManager;
|
||||
|
||||
public function __construct(IAccountManager $accountManager) {
|
||||
public function __construct(IAccountManager $accountManager,
|
||||
IUserManager $userManager) {
|
||||
$this->accountManager = $accountManager;
|
||||
$this->userManager = $userManager;
|
||||
}
|
||||
|
||||
public function createCardFromUser(IUser $user): ?VCard {
|
||||
|
@ -102,6 +106,20 @@ class Converter {
|
|||
}
|
||||
}
|
||||
|
||||
// Local properties
|
||||
$managers = $user->getManagerUids();
|
||||
// X-MANAGERSNAME only allows a single value, so we take the first manager
|
||||
if (isset($managers[0])) {
|
||||
$displayName = $this->userManager->getDisplayName($managers[0]);
|
||||
// Only set the manager if a user object is found
|
||||
if ($displayName !== null) {
|
||||
$vCard->add(new Text($vCard, 'X-MANAGERSNAME', $displayName, [
|
||||
'uid' => $managers[0],
|
||||
'X-NC-SCOPE' => IAccountManager::SCOPE_LOCAL,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
if ($publish && !empty($cloudId)) {
|
||||
$vCard->add(new Text($vCard, 'CLOUD', $cloudId));
|
||||
$vCard->validate();
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
|
@ -33,6 +36,7 @@ use OCP\Accounts\IAccountManager;
|
|||
use OCP\Accounts\IAccountProperty;
|
||||
use OCP\IImage;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
|
@ -40,11 +44,14 @@ class ConverterTest extends TestCase {
|
|||
|
||||
/** @var IAccountManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $accountManager;
|
||||
/** @var IUserManager|(IUserManager&MockObject)|MockObject */
|
||||
private IUserManager|MockObject $userManager;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->accountManager = $this->createMock(IAccountManager::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,7 +103,7 @@ class ConverterTest extends TestCase {
|
|||
$user = $this->getUserMock((string)$displayName, $eMailAddress, $cloudId);
|
||||
$accountManager = $this->getAccountManager($user);
|
||||
|
||||
$converter = new Converter($accountManager);
|
||||
$converter = new Converter($accountManager, $this->userManager);
|
||||
$vCard = $converter->createCardFromUser($user);
|
||||
if ($expectedVCard !== null) {
|
||||
$this->assertInstanceOf('Sabre\VObject\Component\VCard', $vCard);
|
||||
|
@ -107,6 +114,29 @@ class ConverterTest extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public function testManagerProp(): void {
|
||||
$user = $this->getUserMock("user", "user@domain.tld", "user@cloud.domain.tld");
|
||||
$user->method('getManagerUids')
|
||||
->willReturn(['mgr']);
|
||||
$this->userManager->expects(self::once())
|
||||
->method('getDisplayName')
|
||||
->with('mgr')
|
||||
->willReturn('Manager');
|
||||
$accountManager = $this->getAccountManager($user);
|
||||
|
||||
$converter = new Converter($accountManager, $this->userManager);
|
||||
$vCard = $converter->createCardFromUser($user);
|
||||
|
||||
$this->compareData(
|
||||
[
|
||||
'cloud' => 'user@cloud.domain.tld',
|
||||
'email' => 'user@domain.tld',
|
||||
'x-managersname' => 'Manager',
|
||||
],
|
||||
$vCard->jsonSerialize()
|
||||
);
|
||||
}
|
||||
|
||||
protected function compareData($expected, $data) {
|
||||
foreach ($expected as $key => $value) {
|
||||
$found = false;
|
||||
|
@ -182,7 +212,7 @@ class ConverterTest extends TestCase {
|
|||
* @param $fullName
|
||||
*/
|
||||
public function testNameSplitter($expected, $fullName): void {
|
||||
$converter = new Converter($this->accountManager);
|
||||
$converter = new Converter($this->accountManager, $this->userManager);
|
||||
$r = $converter->splitFullName($fullName);
|
||||
$r = implode(';', $r);
|
||||
$this->assertEquals($expected, $r);
|
||||
|
|
|
@ -60,6 +60,7 @@ abstract class AUserData extends OCSController {
|
|||
public const USER_FIELD_LOCALE = 'locale';
|
||||
public const USER_FIELD_PASSWORD = 'password';
|
||||
public const USER_FIELD_QUOTA = 'quota';
|
||||
public const USER_FIELD_MANAGER = 'manager';
|
||||
public const USER_FIELD_NOTIFICATION_EMAIL = 'notify_email';
|
||||
|
||||
/** @var IUserManager */
|
||||
|
@ -151,6 +152,8 @@ abstract class AUserData extends OCSController {
|
|||
$data['backend'] = $targetUserObject->getBackendClassName();
|
||||
$data['subadmin'] = $this->getUserSubAdminGroupsData($targetUserObject->getUID());
|
||||
$data[self::USER_FIELD_QUOTA] = $this->fillStorageInfo($targetUserObject->getUID());
|
||||
$managerUids = $targetUserObject->getManagerUids();
|
||||
$data[self::USER_FIELD_MANAGER] = empty($managerUids) ? '' : $managerUids[0];
|
||||
|
||||
try {
|
||||
if ($includeScopes) {
|
||||
|
|
|
@ -338,7 +338,8 @@ class UsersController extends AUserData {
|
|||
array $groups = [],
|
||||
array $subadmin = [],
|
||||
string $quota = '',
|
||||
string $language = ''
|
||||
string $language = '',
|
||||
?string $manager = null,
|
||||
): DataResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
$isAdmin = $this->groupManager->isAdmin($user->getUID());
|
||||
|
@ -447,6 +448,15 @@ class UsersController extends AUserData {
|
|||
$this->editUser($userid, self::USER_FIELD_LANGUAGE, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* null -> nothing sent
|
||||
* '' -> unset manager
|
||||
* else -> set manager
|
||||
*/
|
||||
if ($manager !== null) {
|
||||
$this->editUser($userid, self::USER_FIELD_MANAGER, $manager);
|
||||
}
|
||||
|
||||
// Send new user mail only if a mail is set
|
||||
if ($email !== '') {
|
||||
$newUser->setEMailAddress($email);
|
||||
|
@ -800,9 +810,11 @@ class UsersController extends AUserData {
|
|||
|
||||
$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
|
||||
|
||||
// If admin they can edit their own quota
|
||||
// If admin they can edit their own quota and manager
|
||||
if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
|
||||
$permittedFields[] = self::USER_FIELD_QUOTA;
|
||||
$permittedFields[] = self::USER_FIELD_MANAGER;
|
||||
|
||||
}
|
||||
} else {
|
||||
// Check if admin / subadmin
|
||||
|
@ -836,6 +848,7 @@ class UsersController extends AUserData {
|
|||
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
|
||||
$permittedFields[] = self::USER_FIELD_QUOTA;
|
||||
$permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
|
||||
$permittedFields[] = self::USER_FIELD_MANAGER;
|
||||
} else {
|
||||
// No rights
|
||||
throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
|
||||
|
@ -885,6 +898,9 @@ class UsersController extends AUserData {
|
|||
}
|
||||
$targetUser->setQuota($quota);
|
||||
break;
|
||||
case self::USER_FIELD_MANAGER:
|
||||
$targetUser->setManagerUids([$value]);
|
||||
break;
|
||||
case self::USER_FIELD_PASSWORD:
|
||||
try {
|
||||
if (strlen($value) > IUserManager::MAX_PASSWORD_LENGTH) {
|
||||
|
|
|
@ -1093,6 +1093,7 @@ class UsersControllerTest extends TestCase {
|
|||
'biography' => 'biography',
|
||||
'profile_enabled' => '1',
|
||||
'notify_email' => null,
|
||||
'manager' => '',
|
||||
];
|
||||
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
|
||||
}
|
||||
|
@ -1233,6 +1234,7 @@ class UsersControllerTest extends TestCase {
|
|||
'biography' => 'biography',
|
||||
'profile_enabled' => '1',
|
||||
'notify_email' => null,
|
||||
'manager' => '',
|
||||
];
|
||||
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
|
||||
}
|
||||
|
@ -1411,6 +1413,7 @@ class UsersControllerTest extends TestCase {
|
|||
'biography' => 'biography',
|
||||
'profile_enabled' => '1',
|
||||
'notify_email' => null,
|
||||
'manager' => '',
|
||||
];
|
||||
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1350,6 +1350,8 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
minmax($grid-col-min-width, 1fr) // email
|
||||
minmax(1.5*$grid-col-min-width, 1fr) // groups
|
||||
minmax(1.5*$grid-col-min-width, 1fr) // group admins
|
||||
minmax($grid-col-min-width, 1fr) // quota
|
||||
minmax(1.5*$grid-col-min-width, 1fr) // manager
|
||||
repeat(auto-fit, minmax($grid-col-min-width, 1fr));
|
||||
border-bottom: var(--color-border) 1px solid;
|
||||
|
||||
|
@ -1394,6 +1396,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
}
|
||||
}
|
||||
|
||||
.managers,
|
||||
.groups,
|
||||
.subadmins,
|
||||
.quota {
|
||||
|
|
|
@ -146,6 +146,20 @@
|
|||
<div v-if="showConfig.showStoragePath" class="storageLocation" />
|
||||
<div v-if="showConfig.showUserBackend" class="userBackend" />
|
||||
<div v-if="showConfig.showLastLogin" class="lastLogin" />
|
||||
<div :class="{'icon-loading-small': loading.manager}" class="modal__item managers">
|
||||
<NcMultiselect ref="manager"
|
||||
v-model="newUser.manager"
|
||||
:close-on-select="true"
|
||||
:user-select="true"
|
||||
:options="possibleManagers"
|
||||
:placeholder="t('settings', 'Select user manager')"
|
||||
class="multiselect-vue"
|
||||
@search-change="searchUserManager"
|
||||
label="displayname"
|
||||
track-by="id">
|
||||
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||
</NcMultiselect>
|
||||
</div>
|
||||
<div class="user-actions">
|
||||
<NcButton id="newsubmit"
|
||||
type="primary"
|
||||
|
@ -208,7 +222,9 @@
|
|||
class="headerLastLogin lastLogin">
|
||||
{{ t('settings', 'Last login') }}
|
||||
</th>
|
||||
|
||||
<th id="headerManager" class="manager">
|
||||
{{ t('settings', 'Manager') }}
|
||||
</th>
|
||||
<th class="userActions hidden-visually">
|
||||
{{ t('settings', 'User actions') }}
|
||||
</th>
|
||||
|
@ -224,6 +240,7 @@
|
|||
:show-config="showConfig"
|
||||
:sub-admins-groups="subAdminsGroups"
|
||||
:user="user"
|
||||
:users="users"
|
||||
:is-dark-theme="isDarkTheme" />
|
||||
|
||||
<InfiniteLoading ref="infiniteLoading" @infinite="infiniteHandler">
|
||||
|
@ -268,6 +285,7 @@ const newUser = {
|
|||
password: '',
|
||||
mailAddress: '',
|
||||
groups: [],
|
||||
manager: '',
|
||||
subAdminsGroups: [],
|
||||
quota: defaultQuota,
|
||||
language: {
|
||||
|
@ -312,6 +330,7 @@ export default {
|
|||
groups: false,
|
||||
},
|
||||
scrolled: false,
|
||||
possibleManagers: [],
|
||||
searchQuery: '',
|
||||
newUser: Object.assign({}, newUser),
|
||||
}
|
||||
|
@ -422,6 +441,10 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
async beforeMount() {
|
||||
await this.searchUserManager()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (!this.settings.canChangePassword) {
|
||||
OC.Notification.showTemporary(t('settings', 'Password change is disabled because the master key is disabled'))
|
||||
|
@ -449,6 +472,14 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
async searchUserManager(query) {
|
||||
await this.$store.dispatch('searchUsers', { offset: 0, limit: 10, search: query }).then(response => {
|
||||
const users = response?.data ? Object.values(response?.data.ocs.data.users) : []
|
||||
if (users.length > 0) {
|
||||
this.possibleManagers = users
|
||||
}
|
||||
})
|
||||
},
|
||||
onScroll(event) {
|
||||
this.scrolled = event.target.scrollTo > 0
|
||||
},
|
||||
|
@ -532,6 +563,7 @@ export default {
|
|||
subadmin: this.newUser.subAdminsGroups.map(group => group.id),
|
||||
quota: this.newUser.quota.id,
|
||||
language: this.newUser.language.code,
|
||||
manager: this.newUser.manager.id,
|
||||
})
|
||||
.then(() => {
|
||||
this.resetForm()
|
||||
|
|
|
@ -217,6 +217,22 @@
|
|||
track-by="code"
|
||||
@input="setUserLanguage" />
|
||||
</td>
|
||||
<td :class="{'icon-loading-small': loading.manager}" class="managers">
|
||||
<NcMultiselect ref="manager"
|
||||
v-model="currentManager"
|
||||
:close-on-select="true"
|
||||
:user-select="true"
|
||||
:options="possibleManagers"
|
||||
:placeholder="t('settings', 'Select manager')"
|
||||
class="multiselect-vue"
|
||||
label="displayname"
|
||||
track-by="id"
|
||||
@search-change="searchUserManager"
|
||||
@remove="updateUserManager"
|
||||
@select="updateUserManager">
|
||||
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||
</NcMultiselect>
|
||||
</td>
|
||||
|
||||
<!-- don't show this on edit mode -->
|
||||
<td v-if="showConfig.showStoragePath || showConfig.showUserBackend"
|
||||
|
@ -275,6 +291,10 @@ export default {
|
|||
},
|
||||
mixins: [UserRowMixin],
|
||||
props: {
|
||||
users: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -317,6 +337,8 @@ export default {
|
|||
rand: parseInt(Math.random() * 1000),
|
||||
openedMenu: false,
|
||||
feedbackMessage: '',
|
||||
possibleManagers: [],
|
||||
currentManager: '',
|
||||
editing: false,
|
||||
loading: {
|
||||
all: false,
|
||||
|
@ -330,10 +352,12 @@ export default {
|
|||
disable: false,
|
||||
languages: false,
|
||||
wipe: false,
|
||||
manager: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
/* USER POPOVERMENU ACTIONS */
|
||||
userActions() {
|
||||
const actions = [
|
||||
|
@ -363,6 +387,12 @@ export default {
|
|||
return actions.concat(this.externalActions)
|
||||
},
|
||||
},
|
||||
async beforeMount() {
|
||||
await this.searchUserManager()
|
||||
if (this.user.manager) {
|
||||
await this.initManager(this.user.manager)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/* MENU HANDLING */
|
||||
|
@ -399,6 +429,34 @@ export default {
|
|||
)
|
||||
},
|
||||
|
||||
filterManagers(managers) {
|
||||
return managers.filter((manager) => manager.id !== this.user.id)
|
||||
},
|
||||
async initManager(userId) {
|
||||
await this.$store.dispatch('getUser', userId).then(response => {
|
||||
this.currentManager = response?.data.ocs.data
|
||||
})
|
||||
},
|
||||
async searchUserManager(query) {
|
||||
await this.$store.dispatch('searchUsers', { offset: 0, limit: 10, search: query }).then(response => {
|
||||
const users = response?.data ? this.filterManagers(Object.values(response?.data.ocs.data.users)) : []
|
||||
if (users.length > 0) {
|
||||
this.possibleManagers = users
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateUserManager(manager) {
|
||||
this.loading.manager = true
|
||||
this.$store.dispatch('setUserData', {
|
||||
userid: this.user.id,
|
||||
key: 'manager',
|
||||
value: this.currentManager ? this.currentManager.id : '',
|
||||
}).then(() => {
|
||||
this.loading.manager = false
|
||||
})
|
||||
},
|
||||
|
||||
deleteUser() {
|
||||
const userid = this.user.id
|
||||
OC.dialogs.confirmDestructive(
|
||||
|
|
|
@ -55,7 +55,9 @@
|
|||
<td v-if="showConfig.showLastLogin" :title="userLastLoginTooltip" class="lastLogin">
|
||||
{{ userLastLogin }}
|
||||
</td>
|
||||
|
||||
<td class="managers">
|
||||
{{ user.manager }}
|
||||
</td>
|
||||
<td class="userActions">
|
||||
<div v-if="canEdit && !loading.all" class="toggleUserActions">
|
||||
<NcActions>
|
||||
|
|
|
@ -253,6 +253,41 @@ let searchRequestCancelSource = null
|
|||
|
||||
const actions = {
|
||||
|
||||
/**
|
||||
* search users
|
||||
*
|
||||
* @param {object} context store context
|
||||
* @param {object} options destructuring object
|
||||
* @param {number} options.offset List offset to request
|
||||
* @param {number} options.limit List number to return from offset
|
||||
* @param {string} options.search Search amongst users
|
||||
* @return {Promise}
|
||||
*/
|
||||
searchUsers(context, { offset, limit, search }) {
|
||||
search = typeof search === 'string' ? search : ''
|
||||
|
||||
return api.get(generateOcsUrl('cloud/users/details?offset={offset}&limit={limit}&search={search}', { offset, limit, search })).catch((error) => {
|
||||
if (!axios.isCancel(error)) {
|
||||
context.commit('API_FAILURE', error)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Get user details
|
||||
*
|
||||
* @param {object} context store context
|
||||
* @param {string} userId user id
|
||||
* @return {Promise}
|
||||
*/
|
||||
getUser(context, userId) {
|
||||
return api.get(generateOcsUrl(`cloud/users/${userId}`)).catch((error) => {
|
||||
if (!axios.isCancel(error)) {
|
||||
context.commit('API_FAILURE', error)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all users with full details
|
||||
*
|
||||
|
@ -548,11 +583,12 @@ const actions = {
|
|||
* @param {string} options.subadmin User subadmin groups
|
||||
* @param {string} options.quota User email
|
||||
* @param {string} options.language User language
|
||||
* @param {string} options.manager User manager
|
||||
* @return {Promise}
|
||||
*/
|
||||
addUser({ commit, dispatch }, { userid, password, displayName, email, groups, subadmin, quota, language }) {
|
||||
addUser({ commit, dispatch }, { userid, password, displayName, email, groups, subadmin, quota, language, manager }) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(generateOcsUrl('cloud/users'), { userid, password, displayName, email, groups, subadmin, quota, language })
|
||||
return api.post(generateOcsUrl('cloud/users'), { userid, password, displayName, email, groups, subadmin, quota, language, manager })
|
||||
.then((response) => dispatch('addUserData', userid || response.data.ocs.data.id))
|
||||
.catch((error) => { throw error })
|
||||
}).catch((error) => {
|
||||
|
@ -605,8 +641,8 @@ const actions = {
|
|||
* @return {Promise}
|
||||
*/
|
||||
setUserData(context, { userid, key, value }) {
|
||||
const allowedEmpty = ['email', 'displayname']
|
||||
if (['email', 'language', 'quota', 'displayname', 'password'].indexOf(key) !== -1) {
|
||||
const allowedEmpty = ['email', 'displayname', 'manager']
|
||||
if (['email', 'language', 'quota', 'displayname', 'password', 'manager'].indexOf(key) !== -1) {
|
||||
// We allow empty email or displayname
|
||||
if (typeof value === 'string'
|
||||
&& (
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -159,4 +159,12 @@ class LazyUser implements IUser {
|
|||
public function setQuota($quota) {
|
||||
$this->getUser()->setQuota($quota);
|
||||
}
|
||||
|
||||
public function getManagerUids(): array {
|
||||
return $this->getUser()->getManagerUids();
|
||||
}
|
||||
|
||||
public function setManagerUids(array $uids): void {
|
||||
$this->getUser()->setManagerUids($uids);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,8 +59,12 @@ use OCP\User\Backend\IGetHomeBackend;
|
|||
use OCP\UserInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
|
||||
class User implements IUser {
|
||||
private const CONFIG_KEY_MANAGERS = 'manager';
|
||||
|
||||
/** @var IAccountManager */
|
||||
protected $accountManager;
|
||||
/** @var string */
|
||||
|
@ -532,6 +536,27 @@ class User implements IUser {
|
|||
\OC_Helper::clearStorageInfo('/' . $this->uid . '/files');
|
||||
}
|
||||
|
||||
public function getManagerUids(): array {
|
||||
$encodedUids = $this->config->getUserValue(
|
||||
$this->uid,
|
||||
'settings',
|
||||
self::CONFIG_KEY_MANAGERS,
|
||||
'[]'
|
||||
);
|
||||
return json_decode($encodedUids, false, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
public function setManagerUids(array $uids): void {
|
||||
$oldUids = $this->getManagerUids();
|
||||
$this->config->setUserValue(
|
||||
$this->uid,
|
||||
'settings',
|
||||
self::CONFIG_KEY_MANAGERS,
|
||||
json_encode($uids, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
$this->triggerChange('managers', $uids, $oldUids);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the avatar image if it exists
|
||||
*
|
||||
|
|
|
@ -270,4 +270,21 @@ interface IUser {
|
|||
* @since 9.0.0
|
||||
*/
|
||||
public function setQuota($quota);
|
||||
|
||||
/**
|
||||
* Get the user's manager UIDs
|
||||
*
|
||||
* @since 27.0.0
|
||||
* @return string[]
|
||||
*/
|
||||
public function getManagerUids(): array;
|
||||
|
||||
/**
|
||||
* Set the user's manager UIDs
|
||||
*
|
||||
* @param string[] $uids UIDs of all managers
|
||||
* @return void
|
||||
* @since 27.0.0
|
||||
*/
|
||||
public function setManagerUids(array $uids): void;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue