From 26abc86eca7cf6ae95f20e1dc180774d11892aab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?=
Date: Tue, 17 Sep 2024 21:41:45 +0200
Subject: [PATCH 1/2] feat: add profile pronouns
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: John Molakvoæ (skjnldsv)
---
.../lib/Controller/AUserData.php | 1 +
.../lib/Controller/UsersController.php | 8 +++-
.../lib/ResponseDefinitions.php | 2 +
.../openapi-administration.json | 7 +++
apps/provisioning_api/openapi-full.json | 7 +++
apps/provisioning_api/openapi.json | 7 +++
.../tests/Controller/UsersControllerTest.php | 18 +++++++-
.../lib/Controller/UsersController.php | 5 +++
.../lib/Settings/Personal/PersonalInfo.php | 1 +
.../PersonalInfo/PronounsSection.vue | 45 +++++++++++++++++++
.../src/constants/AccountPropertyConstants.js | 13 ++++--
apps/settings/src/main-personal-info.js | 35 ++++++++-------
.../settings/personal/personal.info.php | 3 ++
apps/user_ldap/lib/Configuration.php | 6 +++
apps/user_ldap/lib/Connection.php | 1 +
apps/user_ldap/lib/User/Manager.php | 1 +
apps/user_ldap/lib/User/User.php | 6 +++
apps/user_ldap/templates/settings.php | 1 +
.../features/provisioning-v1.feature | 5 ++-
core/src/views/Profile.vue | 2 +
dist/core-profile.js | 4 +-
dist/core-profile.js.map | 2 +-
...tings-vue-settings-admin-basic-settings.js | 4 +-
...s-vue-settings-admin-basic-settings.js.map | 2 +-
dist/settings-vue-settings-personal-info.js | 4 +-
...settings-vue-settings-personal-info.js.map | 2 +-
lib/private/Accounts/AccountManager.php | 23 ++++++----
lib/private/Profile/ProfileManager.php | 8 +++-
lib/public/Accounts/IAccountManager.php | 22 +++++----
29 files changed, 195 insertions(+), 50 deletions(-)
create mode 100644 apps/settings/src/components/PersonalInfo/PronounsSection.vue
diff --git a/apps/provisioning_api/lib/Controller/AUserData.php b/apps/provisioning_api/lib/Controller/AUserData.php
index 44087a917911b..eb881db45e03e 100644
--- a/apps/provisioning_api/lib/Controller/AUserData.php
+++ b/apps/provisioning_api/lib/Controller/AUserData.php
@@ -179,6 +179,7 @@ protected function getUserData(string $userId, bool $includeScopes = false): ?ar
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
+ IAccountManager::PROPERTY_PRONOUNS,
] as $propertyName) {
$property = $userAccount->getProperty($propertyName);
$data[$propertyName] = $property->getValue();
diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php
index 4cab2341b00be..e65585902bcad 100644
--- a/apps/provisioning_api/lib/Controller/UsersController.php
+++ b/apps/provisioning_api/lib/Controller/UsersController.php
@@ -761,6 +761,7 @@ public function getEditableFieldsForUser(string $userId): DataResponse {
$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 @@ public function editUser(string $userId, string $key, string $value): DataRespon
$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 @@ public function editUser(string $userId, string $key, string $value): DataRespon
$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 @@ public function editUser(string $userId, string $key, string $value): DataRespon
$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 @@ public function editUser(string $userId, string $key, string $value): DataRespon
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 @@ public function editUser(string $userId, string $key, string $value): DataRespon
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);
diff --git a/apps/provisioning_api/lib/ResponseDefinitions.php b/apps/provisioning_api/lib/ResponseDefinitions.php
index 90cc09b345bed..fec68021209a7 100644
--- a/apps/provisioning_api/lib/ResponseDefinitions.php
+++ b/apps/provisioning_api/lib/ResponseDefinitions.php
@@ -56,6 +56,8 @@
* phoneScope?: Provisioning_APIUserDetailsScope,
* profile_enabled: string,
* profile_enabledScope?: Provisioning_APIUserDetailsScope,
+ * pronouns: string,
+ * pronounsScope?: Provisioning_APIUserDetailsScope,
* quota: Provisioning_APIUserDetailsQuota,
* role: string,
* roleScope?: Provisioning_APIUserDetailsScope,
diff --git a/apps/provisioning_api/openapi-administration.json b/apps/provisioning_api/openapi-administration.json
index 3834de8a70049..3490e6829e9cd 100644
--- a/apps/provisioning_api/openapi-administration.json
+++ b/apps/provisioning_api/openapi-administration.json
@@ -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"
},
diff --git a/apps/provisioning_api/openapi-full.json b/apps/provisioning_api/openapi-full.json
index 6577efebed90a..461a9b0734aa4 100644
--- a/apps/provisioning_api/openapi-full.json
+++ b/apps/provisioning_api/openapi-full.json
@@ -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"
},
diff --git a/apps/provisioning_api/openapi.json b/apps/provisioning_api/openapi.json
index ef07072460eed..3a5f253d0b6f1 100644
--- a/apps/provisioning_api/openapi.json
+++ b/apps/provisioning_api/openapi.json
@@ -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"
},
diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php
index 0f82fc53ba640..3fcac1290db80 100644
--- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php
+++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php
@@ -990,6 +990,7 @@ public function testGetUserDataAsAdmin(): void {
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 @@ public function testGetUserDataAsAdmin(): void {
'profile_enabled' => '1',
'notify_email' => null,
'manager' => '',
+ 'pronouns' => 'they/them',
];
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
}
@@ -1171,6 +1173,7 @@ public function testGetUserDataAsSubAdminAndUserIsAccessible(): void {
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 @@ public function testGetUserDataAsSubAdminAndUserIsAccessible(): void {
'profile_enabled' => '1',
'notify_email' => null,
'manager' => '',
+ 'pronouns' => 'they/them',
];
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
}
@@ -1351,6 +1355,7 @@ public function testGetUserDataAsSubAdminSelfLookup(): void {
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 @@ public function testGetUserDataAsSubAdminSelfLookup(): void {
'profile_enabled' => '1',
'notify_email' => null,
'manager' => '',
+ 'pronouns' => 'they/them',
];
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
}
@@ -1729,6 +1735,7 @@ public function selfEditChangePropertyProvider() {
[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 @@ public function selfEditChangePropertyScopeProvider() {
[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 @@ public function testGetCurrentUserLoggedIn(): void {
'role' => 'role',
'headline' => 'headline',
'biography' => 'biography',
- 'profile_enabled' => '1'
+ 'profile_enabled' => '1',
+ 'pronouns' => 'they/them',
]
);
@@ -3711,6 +3720,7 @@ public function testGetCurrentUserLoggedIn(): void {
'headline' => 'headline',
'biography' => 'biography',
'profile_enabled' => '1',
+ 'pronouns' => 'they/them',
];
$this->assertSame($expected, $api->getCurrentUser()->getData());
@@ -3775,7 +3785,8 @@ public function testGetUser(): void {
'role' => 'role',
'headline' => 'headline',
'biography' => 'biography',
- 'profile_enabled' => '1'
+ 'profile_enabled' => '1',
+ 'pronouns' => 'they/them',
];
$api->expects($this->exactly(2))
@@ -4115,6 +4126,7 @@ public function dataGetEditableFields() {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
+ IAccountManager::PROPERTY_PRONOUNS,
]],
[true, ISetDisplayNameBackend::class, [
IAccountManager::PROPERTY_DISPLAYNAME,
@@ -4130,6 +4142,7 @@ public function dataGetEditableFields() {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
+ IAccountManager::PROPERTY_PRONOUNS,
]],
[true, UserInterface::class, [
IAccountManager::PROPERTY_EMAIL,
@@ -4144,6 +4157,7 @@ public function dataGetEditableFields() {
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_PROFILE_ENABLED,
+ IAccountManager::PROPERTY_PRONOUNS,
]],
];
}
diff --git a/apps/settings/lib/Controller/UsersController.php b/apps/settings/lib/Controller/UsersController.php
index 4ee359b6fe9b6..eb401f855360e 100644
--- a/apps/settings/lib/Controller/UsersController.php
+++ b/apps/settings/lib/Controller/UsersController.php
@@ -335,6 +335,8 @@ public function setUserSettings(?string $avatarScope = null,
?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 @@ public function setUserSettings(?string $avatarScope = null,
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 @@ public function setUserSettings(?string $avatarScope = null,
'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'),
],
],
diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php
index bfac996de2ff4..232fea8bd73ef 100644
--- a/apps/settings/lib/Settings/Personal/PersonalInfo.php
+++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php
@@ -143,6 +143,7 @@ public function getForm(): TemplateResponse {
'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 = [
diff --git a/apps/settings/src/components/PersonalInfo/PronounsSection.vue b/apps/settings/src/components/PersonalInfo/PronounsSection.vue
new file mode 100644
index 0000000000000..fb35b1800c595
--- /dev/null
+++ b/apps/settings/src/components/PersonalInfo/PronounsSection.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
diff --git a/apps/settings/src/constants/AccountPropertyConstants.js b/apps/settings/src/constants/AccountPropertyConstants.js
index 8ddfc867ec516..4b475b54ab4c9 100644
--- a/apps/settings/src/constants/AccountPropertyConstants.js
+++ b/apps/settings/src/constants/AccountPropertyConstants.js
@@ -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 */
diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js
index 0a1e983c57684..c28f14ee477d9 100644
--- a/apps/settings/src/main-personal-info.js
+++ b/apps/settings/src/main-personal-info.js
@@ -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)
diff --git a/apps/settings/templates/settings/personal/personal.info.php b/apps/settings/templates/settings/personal/personal.info.php
index 92b29961baf7e..e622663fba7d1 100644
--- a/apps/settings/templates/settings/personal/personal.info.php
+++ b/apps/settings/templates/settings/personal/personal.info.php
@@ -43,6 +43,9 @@
+
diff --git a/apps/user_ldap/lib/Configuration.php b/apps/user_ldap/lib/Configuration.php
index d436c5c4240e9..9de180cad767c 100644
--- a/apps/user_ldap/lib/Configuration.php
+++ b/apps/user_ldap/lib/Configuration.php
@@ -80,6 +80,8 @@
* @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 @@ public function readConfiguration(): void {
case 'ldapAttributeBiography':
case 'ldapAttributeBirthDate':
case 'ldapAttributeAnniversaryDate':
+ case 'ldapAttributePronouns':
$readMethod = 'getLcValue';
break;
case 'ldapUserDisplayName':
@@ -559,6 +563,7 @@ public function getDefaults(): array {
'ldap_admin_group' => '',
'ldap_attr_birthdate' => '',
'ldap_attr_anniversarydate' => '',
+ 'ldap_attr_pronouns' => '',
];
}
@@ -638,6 +643,7 @@ public function getConfigTranslationArray(): array {
'ldap_admin_group' => 'ldapAdminGroup',
'ldap_attr_birthdate' => 'ldapAttributeBirthDate',
'ldap_attr_anniversarydate' => 'ldapAttributeAnniversaryDate',
+ 'ldap_attr_pronouns' => 'ldapAttributePronouns',
];
return $array;
}
diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php
index 54dba6d858a30..b74c964eb7247 100644
--- a/apps/user_ldap/lib/Connection.php
+++ b/apps/user_ldap/lib/Connection.php
@@ -85,6 +85,7 @@
* @property string $ldapAttributeBiography
* @property string $ldapAdminGroup
* @property string $ldapAttributeBirthDate
+ * @property string $ldapAttributePronouns
*/
class Connection extends LDAPUtility {
private ?\LDAP\Connection $ldapConnectionRes = null;
diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php
index 227990401c2df..bf1da54823ccc 100644
--- a/apps/user_ldap/lib/User/Manager.php
+++ b/apps/user_ldap/lib/User/Manager.php
@@ -142,6 +142,7 @@ public function getAttributes($minimal = false) {
$this->access->getConnection()->ldapAttributeHeadline,
$this->access->getConnection()->ldapAttributeBiography,
$this->access->getConnection()->ldapAttributeBirthDate,
+ $this->access->getConnection()->ldapAttributePronouns,
];
$homeRule = (string)$this->access->getConnection()->homeFolderNamingRule;
diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php
index 3423455e819a3..f2a9ba49f2f81 100644
--- a/apps/user_ldap/lib/User/User.php
+++ b/apps/user_ldap/lib/User/User.php
@@ -320,6 +320,12 @@ public function processAttributes($ldapEntry) {
]);
}
}
+ //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
diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php
index 4fb24655000d8..4be24095e9915 100644
--- a/apps/user_ldap/templates/settings.php
+++ b/apps/user_ldap/templates/settings.php
@@ -139,6 +139,7 @@
t('Headline Field')); ?>
t('Biography Field')); ?>
t('Birthdate Field')); ?>
+
t('Pronouns Field')); ?>
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index 58663cde4c9ec..a40afbab76915 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -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"
diff --git a/core/src/views/Profile.vue b/core/src/views/Profile.vue
index a513e0caf78a3..1df8fce66065b 100644
--- a/core/src/views/Profile.vue
+++ b/core/src/views/Profile.vue
@@ -11,6 +11,7 @@