feat: add profile pronouns

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
pull/44013/head
John Molakvoæ (skjnldsv) 2 months ago
parent dc71cb7c3a
commit 26abc86eca
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
  1. 1
      apps/provisioning_api/lib/Controller/AUserData.php
  2. 8
      apps/provisioning_api/lib/Controller/UsersController.php
  3. 2
      apps/provisioning_api/lib/ResponseDefinitions.php
  4. 7
      apps/provisioning_api/openapi-administration.json
  5. 7
      apps/provisioning_api/openapi-full.json
  6. 7
      apps/provisioning_api/openapi.json
  7. 18
      apps/provisioning_api/tests/Controller/UsersControllerTest.php
  8. 5
      apps/settings/lib/Controller/UsersController.php
  9. 1
      apps/settings/lib/Settings/Personal/PersonalInfo.php
  10. 45
      apps/settings/src/components/PersonalInfo/PronounsSection.vue
  11. 13
      apps/settings/src/constants/AccountPropertyConstants.js
  12. 35
      apps/settings/src/main-personal-info.js
  13. 3
      apps/settings/templates/settings/personal/personal.info.php
  14. 6
      apps/user_ldap/lib/Configuration.php
  15. 1
      apps/user_ldap/lib/Connection.php
  16. 1
      apps/user_ldap/lib/User/Manager.php
  17. 6
      apps/user_ldap/lib/User/User.php
  18. 1
      apps/user_ldap/templates/settings.php
  19. 5
      build/integration/features/provisioning-v1.feature
  20. 2
      core/src/views/Profile.vue
  21. 4
      dist/core-profile.js
  22. 2
      dist/core-profile.js.map
  23. 4
      dist/settings-vue-settings-admin-basic-settings.js
  24. 2
      dist/settings-vue-settings-admin-basic-settings.js.map
  25. 4
      dist/settings-vue-settings-personal-info.js
  26. 2
      dist/settings-vue-settings-personal-info.js.map
  27. 23
      lib/private/Accounts/AccountManager.php
  28. 8
      lib/private/Profile/ProfileManager.php
  29. 22
      lib/public/Accounts/IAccountManager.php

@ -179,6 +179,7 @@ abstract class AUserData extends OCSController {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
IAccountManager::PROPERTY_PRONOUNS,
] as $propertyName) {
$property = $userAccount->getProperty($propertyName);
$data[$propertyName] = $property->getValue();

@ -761,6 +761,7 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
$permittedFields[] = IAccountManager::PROPERTY_PRONOUNS;
return new DataResponse($permittedFields);
}
@ -944,6 +945,8 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDATE;
$permittedFields[] = IAccountManager::PROPERTY_PRONOUNS;
$permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
@ -955,8 +958,8 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_BIRTHDATE . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
$permittedFields[] = IAccountManager::PROPERTY_PRONOUNS . self::SCOPE_SUFFIX;
// If admin they can edit their own quota and manager
$isAdmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID());
@ -997,6 +1000,7 @@ class UsersController extends AUserData {
$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
$permittedFields[] = IAccountManager::PROPERTY_PRONOUNS;
$permittedFields[] = self::USER_FIELD_QUOTA;
$permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
$permittedFields[] = self::USER_FIELD_MANAGER;
@ -1141,6 +1145,7 @@ class UsersController extends AUserData {
case IAccountManager::PROPERTY_HEADLINE:
case IAccountManager::PROPERTY_BIOGRAPHY:
case IAccountManager::PROPERTY_BIRTHDATE:
case IAccountManager::PROPERTY_PRONOUNS:
$userAccount = $this->accountManager->getAccount($targetUser);
try {
$userProperty = $userAccount->getProperty($key);
@ -1189,6 +1194,7 @@ class UsersController extends AUserData {
case IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_BIRTHDATE . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
case IAccountManager::PROPERTY_PRONOUNS . self::SCOPE_SUFFIX:
$propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
$userAccount = $this->accountManager->getAccount($targetUser);
$userProperty = $userAccount->getProperty($propertyName);

@ -56,6 +56,8 @@ namespace OCA\Provisioning_API;
* phoneScope?: Provisioning_APIUserDetailsScope,
* profile_enabled: string,
* profile_enabledScope?: Provisioning_APIUserDetailsScope,
* pronouns: string,
* pronounsScope?: Provisioning_APIUserDetailsScope,
* quota: Provisioning_APIUserDetailsQuota,
* role: string,
* roleScope?: Provisioning_APIUserDetailsScope,

@ -99,6 +99,7 @@
"organisation",
"phone",
"profile_enabled",
"pronouns",
"quota",
"role",
"subadmin",
@ -226,6 +227,12 @@
"profile_enabledScope": {
"$ref": "#/components/schemas/UserDetailsScope"
},
"pronouns": {
"type": "string"
},
"pronounsScope": {
"$ref": "#/components/schemas/UserDetailsScope"
},
"quota": {
"$ref": "#/components/schemas/UserDetailsQuota"
},

@ -146,6 +146,7 @@
"organisation",
"phone",
"profile_enabled",
"pronouns",
"quota",
"role",
"subadmin",
@ -273,6 +274,12 @@
"profile_enabledScope": {
"$ref": "#/components/schemas/UserDetailsScope"
},
"pronouns": {
"type": "string"
},
"pronounsScope": {
"$ref": "#/components/schemas/UserDetailsScope"
},
"quota": {
"$ref": "#/components/schemas/UserDetailsQuota"
},

@ -146,6 +146,7 @@
"organisation",
"phone",
"profile_enabled",
"pronouns",
"quota",
"role",
"subadmin",
@ -273,6 +274,12 @@
"profile_enabledScope": {
"$ref": "#/components/schemas/UserDetailsScope"
},
"pronouns": {
"type": "string"
},
"pronounsScope": {
"$ref": "#/components/schemas/UserDetailsScope"
},
"quota": {
"$ref": "#/components/schemas/UserDetailsQuota"
},

@ -990,6 +990,7 @@ class UsersControllerTest extends TestCase {
IAccountManager::PROPERTY_HEADLINE => ['value' => 'headline'],
IAccountManager::PROPERTY_BIOGRAPHY => ['value' => 'biography'],
IAccountManager::PROPERTY_PROFILE_ENABLED => ['value' => '1'],
IAccountManager::PROPERTY_PRONOUNS => ['value' => 'they/them'],
]);
$this->config
->method('getUserValue')
@ -1068,6 +1069,7 @@ class UsersControllerTest extends TestCase {
'profile_enabled' => '1',
'notify_email' => null,
'manager' => '',
'pronouns' => 'they/them',
];
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
}
@ -1171,6 +1173,7 @@ class UsersControllerTest extends TestCase {
IAccountManager::PROPERTY_HEADLINE => ['value' => 'headline'],
IAccountManager::PROPERTY_BIOGRAPHY => ['value' => 'biography'],
IAccountManager::PROPERTY_PROFILE_ENABLED => ['value' => '1'],
IAccountManager::PROPERTY_PRONOUNS => ['value' => 'they/them'],
]);
$this->l10nFactory
@ -1209,6 +1212,7 @@ class UsersControllerTest extends TestCase {
'profile_enabled' => '1',
'notify_email' => null,
'manager' => '',
'pronouns' => 'they/them',
];
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
}
@ -1351,6 +1355,7 @@ class UsersControllerTest extends TestCase {
IAccountManager::PROPERTY_HEADLINE => ['value' => 'headline'],
IAccountManager::PROPERTY_BIOGRAPHY => ['value' => 'biography'],
IAccountManager::PROPERTY_PROFILE_ENABLED => ['value' => '1'],
IAccountManager::PROPERTY_PRONOUNS => ['value' => 'they/them'],
]);
$this->l10nFactory
@ -1388,6 +1393,7 @@ class UsersControllerTest extends TestCase {
'profile_enabled' => '1',
'notify_email' => null,
'manager' => '',
'pronouns' => 'they/them',
];
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
}
@ -1729,6 +1735,7 @@ class UsersControllerTest extends TestCase {
[IAccountManager::PROPERTY_HEADLINE, 'Hi', 'Hello'],
[IAccountManager::PROPERTY_BIOGRAPHY, 'A biography', 'Another biography'],
[IAccountManager::PROPERTY_PROFILE_ENABLED, '1', '0'],
[IAccountManager::PROPERTY_PRONOUNS, 'they/them', 'he/him'],
];
}
@ -1806,6 +1813,7 @@ class UsersControllerTest extends TestCase {
[IAccountManager::PROPERTY_HEADLINE, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_BIOGRAPHY, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_PROFILE_ENABLED, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
[IAccountManager::PROPERTY_PRONOUNS, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED],
];
}
@ -3690,7 +3698,8 @@ class UsersControllerTest extends TestCase {
'role' => 'role',
'headline' => 'headline',
'biography' => 'biography',
'profile_enabled' => '1'
'profile_enabled' => '1',
'pronouns' => 'they/them',
]
);
@ -3711,6 +3720,7 @@ class UsersControllerTest extends TestCase {
'headline' => 'headline',
'biography' => 'biography',
'profile_enabled' => '1',
'pronouns' => 'they/them',
];
$this->assertSame($expected, $api->getCurrentUser()->getData());
@ -3775,7 +3785,8 @@ class UsersControllerTest extends TestCase {
'role' => 'role',
'headline' => 'headline',
'biography' => 'biography',
'profile_enabled' => '1'
'profile_enabled' => '1',
'pronouns' => 'they/them',
];
$api->expects($this->exactly(2))
@ -4115,6 +4126,7 @@ class UsersControllerTest extends TestCase {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
IAccountManager::PROPERTY_PRONOUNS,
]],
[true, ISetDisplayNameBackend::class, [
IAccountManager::PROPERTY_DISPLAYNAME,
@ -4130,6 +4142,7 @@ class UsersControllerTest extends TestCase {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
IAccountManager::PROPERTY_PRONOUNS,
]],
[true, UserInterface::class, [
IAccountManager::PROPERTY_EMAIL,
@ -4144,6 +4157,7 @@ class UsersControllerTest extends TestCase {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
IAccountManager::PROPERTY_PRONOUNS,
]],
];
}

@ -335,6 +335,8 @@ class UsersController extends Controller {
?string $fediverseScope = null,
?string $birthdate = null,
?string $birthdateScope = null,
?string $pronouns = null,
?string $pronounsScope = null
) {
$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
@ -375,6 +377,7 @@ class UsersController extends Controller {
IAccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope],
IAccountManager::PROPERTY_FEDIVERSE => ['value' => $fediverse, 'scope' => $fediverseScope],
IAccountManager::PROPERTY_BIRTHDATE => ['value' => $birthdate, 'scope' => $birthdateScope],
IAccountManager::PROPERTY_PRONOUNS => ['value' => $pronouns, 'scope' => $pronounsScope],
];
$allowUserToChangeDisplayName = $this->config->getSystemValueBool('allow_user_to_change_display_name', true);
foreach ($updatable as $property => $data) {
@ -418,6 +421,8 @@ class UsersController extends Controller {
'fediverseScope' => $userAccount->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getScope(),
'birthdate' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getValue(),
'birthdateScope' => $userAccount->getProperty(IAccountManager::PROPERTY_BIRTHDATE)->getScope(),
'pronouns' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getValue(),
'pronounsScope' => $userAccount->getProperty(IAccountManager::PROPERTY_PRONOUNS)->getScope(),
'message' => $this->l10n->t('Settings saved'),
],
],

@ -143,6 +143,7 @@ class PersonalInfo implements ISettings {
'biography' => $this->getProperty($account, IAccountManager::PROPERTY_BIOGRAPHY),
'birthdate' => $this->getProperty($account, IAccountManager::PROPERTY_BIRTHDATE),
'firstDayOfWeek' => $this->config->getUserValue($uid, 'core', AUserData::USER_FIELD_FIRST_DAY_OF_WEEK),
'pronouns' => $this->getProperty($account, IAccountManager::PROPERTY_PRONOUNS),
];
$accountParameters = [

@ -0,0 +1,45 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<AccountPropertySection v-bind.sync="pronouns"
:placeholder="randomPronounsPlaceholder" />
</template>
<script>
import { loadState } from '@nextcloud/initial-state'
import AccountPropertySection from './shared/AccountPropertySection.vue'
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
const { pronouns } = loadState('settings', 'personalInfoParameters', {})
export default {
name: 'PronounsSection',
components: {
AccountPropertySection,
},
data() {
return {
pronouns: { ...pronouns, readable: NAME_READABLE_ENUM[pronouns.name] },
}
},
computed: {
randomPronounsPlaceholder() {
const pronouns = [
this.t('settings', 'she/her'),
this.t('settings', 'he/him'),
this.t('settings', 'they/them'),
]
const pronounsExample = pronouns[Math.floor(Math.random() * pronouns.length)]
return this.t('settings', `Your pronouns. E.g. ${pronounsExample}`, { pronounsExample })
},
},
}
</script>

@ -15,19 +15,20 @@ export const ACCOUNT_PROPERTY_ENUM = Object.freeze({
ADDRESS: 'address',
AVATAR: 'avatar',
BIOGRAPHY: 'biography',
BIRTHDATE: 'birthdate',
DISPLAYNAME: 'displayname',
EMAIL_COLLECTION: 'additional_mail',
EMAIL: 'email',
FEDIVERSE: 'fediverse',
HEADLINE: 'headline',
NOTIFICATION_EMAIL: 'notify_email',
FEDIVERSE: 'fediverse',
ORGANISATION: 'organisation',
PHONE: 'phone',
PROFILE_ENABLED: 'profile_enabled',
PRONOUNS: 'pronouns',
ROLE: 'role',
TWITTER: 'twitter',
WEBSITE: 'website',
BIRTHDATE: 'birthdate',
})
/** Enum of account properties to human readable account property names */
@ -35,18 +36,19 @@ export const ACCOUNT_PROPERTY_READABLE_ENUM = Object.freeze({
ADDRESS: t('settings', 'Location'),
AVATAR: t('settings', 'Profile picture'),
BIOGRAPHY: t('settings', 'About'),
BIRTHDATE: t('settings', 'Date of birth'),
DISPLAYNAME: t('settings', 'Full name'),
EMAIL_COLLECTION: t('settings', 'Additional email'),
EMAIL: t('settings', 'Email'),
FEDIVERSE: t('settings', 'Fediverse (e.g. Mastodon)'),
HEADLINE: t('settings', 'Headline'),
ORGANISATION: t('settings', 'Organisation'),
PHONE: t('settings', 'Phone number'),
PROFILE_ENABLED: t('settings', 'Profile'),
PRONOUNS: t('settings', 'Pronouns'),
ROLE: t('settings', 'Role'),
TWITTER: t('settings', 'X (formerly Twitter)'),
FEDIVERSE: t('settings', 'Fediverse (e.g. Mastodon)'),
WEBSITE: t('settings', 'Website'),
BIRTHDATE: t('settings', 'Date of birth'),
})
export const NAME_READABLE_ENUM = Object.freeze({
@ -65,6 +67,7 @@ export const NAME_READABLE_ENUM = Object.freeze({
[ACCOUNT_PROPERTY_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE,
[ACCOUNT_PROPERTY_ENUM.WEBSITE]: ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE,
[ACCOUNT_PROPERTY_ENUM.BIRTHDATE]: ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE,
[ACCOUNT_PROPERTY_ENUM.PRONOUNS]: ACCOUNT_PROPERTY_READABLE_ENUM.PRONOUNS,
})
/** Enum of profile specific sections to human readable names */
@ -89,6 +92,7 @@ export const PROPERTY_READABLE_KEYS_ENUM = Object.freeze({
[ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: ACCOUNT_PROPERTY_ENUM.FEDIVERSE,
[ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: ACCOUNT_PROPERTY_ENUM.WEBSITE,
[ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE]: ACCOUNT_PROPERTY_ENUM.BIRTHDATE,
[ACCOUNT_PROPERTY_READABLE_ENUM.PRONOUNS]: ACCOUNT_PROPERTY_ENUM.PRONOUNS,
})
/**
@ -134,6 +138,7 @@ export const PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM = Object.freeze({
[ACCOUNT_PROPERTY_READABLE_ENUM.FEDIVERSE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
[ACCOUNT_PROPERTY_READABLE_ENUM.WEBSITE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
[ACCOUNT_PROPERTY_READABLE_ENUM.BIRTHDATE]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
[ACCOUNT_PROPERTY_READABLE_ENUM.PRONOUNS]: [SCOPE_ENUM.LOCAL, SCOPE_ENUM.PRIVATE],
})
/** List of readable account properties which aren't published to the lookup server */

@ -9,24 +9,25 @@ import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import AvatarSection from './components/PersonalInfo/AvatarSection.vue'
import BiographySection from './components/PersonalInfo/BiographySection.vue'
import BirthdaySection from './components/PersonalInfo/BirthdaySection.vue'
import DetailsSection from './components/PersonalInfo/DetailsSection.vue'
import DisplayNameSection from './components/PersonalInfo/DisplayNameSection.vue'
import EmailSection from './components/PersonalInfo/EmailSection/EmailSection.vue'
import PhoneSection from './components/PersonalInfo/PhoneSection.vue'
import LocationSection from './components/PersonalInfo/LocationSection.vue'
import WebsiteSection from './components/PersonalInfo/WebsiteSection.vue'
import TwitterSection from './components/PersonalInfo/TwitterSection.vue'
import FediverseSection from './components/PersonalInfo/FediverseSection.vue'
import FirstDayOfWeekSection from './components/PersonalInfo/FirstDayOfWeekSection.vue'
import HeadlineSection from './components/PersonalInfo/HeadlineSection.vue'
import LanguageSection from './components/PersonalInfo/LanguageSection/LanguageSection.vue'
import LocaleSection from './components/PersonalInfo/LocaleSection/LocaleSection.vue'
import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSection.vue'
import LocationSection from './components/PersonalInfo/LocationSection.vue'
import OrganisationSection from './components/PersonalInfo/OrganisationSection.vue'
import RoleSection from './components/PersonalInfo/RoleSection.vue'
import HeadlineSection from './components/PersonalInfo/HeadlineSection.vue'
import BiographySection from './components/PersonalInfo/BiographySection.vue'
import PhoneSection from './components/PersonalInfo/PhoneSection.vue'
import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSection.vue'
import ProfileVisibilitySection from './components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue'
import BirthdaySection from './components/PersonalInfo/BirthdaySection.vue'
import FirstDayOfWeekSection from './components/PersonalInfo/FirstDayOfWeekSection.vue'
import PronounsSection from './components/PersonalInfo/PronounsSection.vue'
import RoleSection from './components/PersonalInfo/RoleSection.vue'
import TwitterSection from './components/PersonalInfo/TwitterSection.vue'
import WebsiteSection from './components/PersonalInfo/WebsiteSection.vue'
__webpack_nonce__ = getCSPNonce()
@ -39,18 +40,19 @@ Vue.mixin({
})
const AvatarView = Vue.extend(AvatarSection)
const BirthdayView = Vue.extend(BirthdaySection)
const DetailsView = Vue.extend(DetailsSection)
const DisplayNameView = Vue.extend(DisplayNameSection)
const EmailView = Vue.extend(EmailSection)
const PhoneView = Vue.extend(PhoneSection)
const LocationView = Vue.extend(LocationSection)
const WebsiteView = Vue.extend(WebsiteSection)
const TwitterView = Vue.extend(TwitterSection)
const FediverseView = Vue.extend(FediverseSection)
const FirstDayOfWeekView = Vue.extend(FirstDayOfWeekSection)
const LanguageView = Vue.extend(LanguageSection)
const LocaleView = Vue.extend(LocaleSection)
const BirthdayView = Vue.extend(BirthdaySection)
const FirstDayOfWeekView = Vue.extend(FirstDayOfWeekSection)
const LocationView = Vue.extend(LocationSection)
const PhoneView = Vue.extend(PhoneSection)
const PronounsView = Vue.extend(PronounsSection)
const TwitterView = Vue.extend(TwitterSection)
const WebsiteView = Vue.extend(WebsiteSection)
new AvatarView().$mount('#vue-avatar-section')
new DetailsView().$mount('#vue-details-section')
@ -65,6 +67,7 @@ new LanguageView().$mount('#vue-language-section')
new LocaleView().$mount('#vue-locale-section')
new FirstDayOfWeekView().$mount('#vue-fdow-section')
new BirthdayView().$mount('#vue-birthday-section')
new PronounsView().$mount('#vue-pronouns-section')
if (profileEnabledGlobally) {
const ProfileView = Vue.extend(ProfileSection)

@ -43,6 +43,9 @@ script('settings', [
<div class="personal-settings-setting-box">
<div id="vue-displayname-section"></div>
</div>
<div class="personal-settings-setting-box">
<div id="vue-pronouns-section"></div>
</div>
<div class="personal-settings-setting-box">
<div id="vue-email-section"></div>
</div>

@ -80,6 +80,8 @@ use Psr\Log\LoggerInterface;
* @property string $ldapAttributeHeadline
* @property string $ldapAttributeBiography
* @property string $ldapAdminGroup
* @property string $ldapAttributeBirthDate
* @property string $ldapAttributePronouns
*/
class Configuration {
public const AVATAR_PREFIX_DEFAULT = 'default';
@ -179,6 +181,7 @@ class Configuration {
'ldapAdminGroup' => '',
'ldapAttributeBirthDate' => null,
'ldapAttributeAnniversaryDate' => null,
'ldapAttributePronouns' => null,
];
public function __construct(string $configPrefix, bool $autoRead = true) {
@ -315,6 +318,7 @@ class Configuration {
case 'ldapAttributeBiography':
case 'ldapAttributeBirthDate':
case 'ldapAttributeAnniversaryDate':
case 'ldapAttributePronouns':
$readMethod = 'getLcValue';
break;
case 'ldapUserDisplayName':
@ -559,6 +563,7 @@ class Configuration {
'ldap_admin_group' => '',
'ldap_attr_birthdate' => '',
'ldap_attr_anniversarydate' => '',
'ldap_attr_pronouns' => '',
];
}
@ -638,6 +643,7 @@ class Configuration {
'ldap_admin_group' => 'ldapAdminGroup',
'ldap_attr_birthdate' => 'ldapAttributeBirthDate',
'ldap_attr_anniversarydate' => 'ldapAttributeAnniversaryDate',
'ldap_attr_pronouns' => 'ldapAttributePronouns',
];
return $array;
}

@ -85,6 +85,7 @@ use Psr\Log\LoggerInterface;
* @property string $ldapAttributeBiography
* @property string $ldapAdminGroup
* @property string $ldapAttributeBirthDate
* @property string $ldapAttributePronouns
*/
class Connection extends LDAPUtility {
private ?\LDAP\Connection $ldapConnectionRes = null;

@ -142,6 +142,7 @@ class Manager {
$this->access->getConnection()->ldapAttributeHeadline,
$this->access->getConnection()->ldapAttributeBiography,
$this->access->getConnection()->ldapAttributeBirthDate,
$this->access->getConnection()->ldapAttributePronouns,
];
$homeRule = (string)$this->access->getConnection()->homeFolderNamingRule;

@ -320,6 +320,12 @@ class User {
]);
}
}
//User Profile Field - pronouns
$attr = strtolower($this->connection->ldapAttributePronouns);
if (!empty($attr)) {
$profileValues[\OCP\Accounts\IAccountManager::PROPERTY_PRONOUNS]
= $ldapEntry[$attr][0] ?? '';
}
// check for changed data and cache just for TTL checking
$checksum = hash('sha256', json_encode($profileValues));
$this->connection->writeToCache($cacheKey, $checksum // write array to cache. is waste of cache space

@ -139,6 +139,7 @@ style('user_ldap', 'settings');
<p><label for="ldap_attr_headline"> <?php p($l->t('Headline Field')); ?></label><input type="text" id="ldap_attr_headline" name="ldap_attr_headline" title="<?php p($l->t('User profile Headline will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_headline_default']); ?>"></p>
<p><label for="ldap_attr_biography"> <?php p($l->t('Biography Field')); ?></label><input type="text" id="ldap_attr_biography" name="ldap_attr_biography" title="<?php p($l->t('User profile Biography will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_biography_default']); ?>"></p>
<p><label for="ldap_attr_birthdate"> <?php p($l->t('Birthdate Field')); ?></label><input type="text" id="ldap_attr_birthdate" name="ldap_attr_birthdate" title="<?php p($l->t('User profile Date of birth will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_birthdate_default']); ?>"></p>
<p></p><label for="ldap_attr_pronouns"> <?php p($l->t('Pronouns Field')); ?></label><input type="text" id="ldap_attr_pronouns" name="ldap_attr_pronouns" title="<?php p($l->t('User profile Pronouns will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_pronouns_default']); ?>"></p>
</div>
</div>
<?php print_unescaped($_['settingControls']); ?>

@ -75,6 +75,7 @@ Feature: provisioning
| role |
| headline |
| biography |
| pronouns |
| profile_enabled |
Given As an "brand-new-user"
Then user "brand-new-user" has editable fields
@ -90,6 +91,7 @@ Feature: provisioning
| role |
| headline |
| biography |
| pronouns |
| profile_enabled |
Then user "self" has editable fields
| displayname |
@ -104,6 +106,7 @@ Feature: provisioning
| role |
| headline |
| biography |
| pronouns |
| profile_enabled |
Scenario: Edit a user
@ -570,7 +573,7 @@ Feature: provisioning
And group "new-group" does not exist
Scenario: Delete a group with special characters
Given As an "admin"
Given As an "admin"
And group "España" exists
When sending "DELETE" to "/cloud/groups/España"
Then the OCS status code should be "100"

@ -11,6 +11,7 @@
<div class="profile__header__container__placeholder" />
<div class="profile__header__container__displayname">
<h2>{{ displayname || userId }}</h2>
<span v-if="pronouns" class="profile__header__container__pronouns">· {{ pronouns }}</span>
<NcButton v-if="isCurrentUser"
type="primary"
:href="settingsUrl">
@ -177,6 +178,7 @@ export default defineComponent({
biography: null as string|null,
actions: [] as IProfileAction[],
isUserAvatarVisible: false,
pronouns: null as string|null,
})
return {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -64,19 +64,20 @@ class AccountManager implements IAccountManager {
* The list of default scopes for each property.
*/
public const DEFAULT_SCOPES = [
self::PROPERTY_DISPLAYNAME => self::SCOPE_FEDERATED,
self::PROPERTY_ADDRESS => self::SCOPE_LOCAL,
self::PROPERTY_WEBSITE => self::SCOPE_LOCAL,
self::PROPERTY_EMAIL => self::SCOPE_FEDERATED,
self::PROPERTY_AVATAR => self::SCOPE_FEDERATED,
self::PROPERTY_PHONE => self::SCOPE_LOCAL,
self::PROPERTY_TWITTER => self::SCOPE_LOCAL,
self::PROPERTY_BIOGRAPHY => self::SCOPE_LOCAL,
self::PROPERTY_BIRTHDATE => self::SCOPE_LOCAL,
self::PROPERTY_DISPLAYNAME => self::SCOPE_FEDERATED,
self::PROPERTY_EMAIL => self::SCOPE_FEDERATED,
self::PROPERTY_FEDIVERSE => self::SCOPE_LOCAL,
self::PROPERTY_HEADLINE => self::SCOPE_LOCAL,
self::PROPERTY_ORGANISATION => self::SCOPE_LOCAL,
self::PROPERTY_PHONE => self::SCOPE_LOCAL,
self::PROPERTY_PRONOUNS => self::SCOPE_FEDERATED,
self::PROPERTY_ROLE => self::SCOPE_LOCAL,
self::PROPERTY_HEADLINE => self::SCOPE_LOCAL,
self::PROPERTY_BIOGRAPHY => self::SCOPE_LOCAL,
self::PROPERTY_BIRTHDATE => self::SCOPE_LOCAL,
self::PROPERTY_TWITTER => self::SCOPE_LOCAL,
self::PROPERTY_WEBSITE => self::SCOPE_LOCAL,
];
public function __construct(
@ -679,6 +680,12 @@ class AccountManager implements IAccountManager {
'name' => self::PROPERTY_PROFILE_ENABLED,
'value' => $this->isProfileEnabledByDefault($this->config) ? '1' : '0',
],
[
'name' => self::PROPERTY_PRONOUNS,
'value' => '',
'scope' => $scopes[self::PROPERTY_PRONOUNS],
],
];
}

@ -66,6 +66,7 @@ class ProfileManager implements IProfileManager {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_ORGANISATION,
IAccountManager::PROPERTY_ROLE,
IAccountManager::PROPERTY_PRONOUNS,
];
public function __construct(
@ -222,7 +223,7 @@ class ProfileManager implements IProfileManager {
/**
* Return the profile parameters of the target user that are visible to the visiting user
* in an associative array
* @return array{userId: string, address?: string|null, biography?: string|null, displayname?: string|null, headline?: string|null, isUserAvatarVisible?: bool, organisation?: string|null, role?: string|null, actions: list<array{id: string, icon: string, title: string, target: ?string}>}
* @return array{userId: string, address?: string|null, biography?: string|null, displayname?: string|null, headline?: string|null, isUserAvatarVisible?: bool, organisation?: string|null, pronouns?: non-falsy-string|null, role?: string|null, actions: list<array{id: string, icon: string, title: string, target: ?string}>}
*/
public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array {
$account = $this->accountManager->getAccount($targetUser);
@ -241,6 +242,7 @@ class ProfileManager implements IProfileManager {
case IAccountManager::PROPERTY_HEADLINE:
case IAccountManager::PROPERTY_ORGANISATION:
case IAccountManager::PROPERTY_ROLE:
case IAccountManager::PROPERTY_PRONOUNS:
$profileParameters[$property] =
$this->isProfileFieldVisible($property, $targetUser, $visitingUser)
// Explicitly set to null when value is empty string
@ -399,6 +401,10 @@ class ProfileManager implements IProfileManager {
'appId' => self::CORE_APP_ID,
'displayId' => $this->l10nFactory->get('lib')->t('Role'),
],
IAccountManager::PROPERTY_PRONOUNS => [
'appId' => self::CORE_APP_ID,
'displayId' => $this->l10nFactory->get('lib')->t('Pronouns'),
],
];
$paramMetadata = array_merge($actionsMetadata, $propertiesMetadata);

@ -161,26 +161,32 @@ interface IAccountManager {
*/
public const PROPERTY_BIRTHDATE = 'birthdate';
/**
* @since 31.0.0
*/
public const PROPERTY_PRONOUNS = 'pronouns';
/**
* The list of allowed properties
*
* @since 25.0.0
*/
public const ALLOWED_PROPERTIES = [
self::PROPERTY_ADDRESS,
self::PROPERTY_AVATAR,
self::PROPERTY_BIOGRAPHY,
self::PROPERTY_BIRTHDATE,
self::PROPERTY_DISPLAYNAME,
self::PROPERTY_PHONE,
self::PROPERTY_EMAIL,
self::PROPERTY_WEBSITE,
self::PROPERTY_ADDRESS,
self::PROPERTY_TWITTER,
self::PROPERTY_FEDIVERSE,
self::PROPERTY_ORGANISATION,
self::PROPERTY_ROLE,
self::PROPERTY_HEADLINE,
self::PROPERTY_BIOGRAPHY,
self::PROPERTY_ORGANISATION,
self::PROPERTY_PHONE,
self::PROPERTY_PROFILE_ENABLED,
self::PROPERTY_BIRTHDATE,
self::PROPERTY_PRONOUNS,
self::PROPERTY_ROLE,
self::PROPERTY_TWITTER,
self::PROPERTY_WEBSITE,
];

Loading…
Cancel
Save