From 0dd8e5e32e5f66bfae90bdb0dfc8c4700636f980 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sat, 16 Aug 2025 23:48:08 +0200 Subject: [PATCH] feat(settings): allow to set own timezone in personal settings Signed-off-by: Ferdinand Thiessen --- .../lib/Controller/AUserDataOCSController.php | 2 + .../lib/Controller/UsersController.php | 8 ++++ .../lib/ResponseDefinitions.php | 1 + .../tests/Controller/UsersControllerTest.php | 3 ++ .../lib/Settings/Personal/PersonalInfo.php | 1 + .../PersonalInfo/TimezoneSection.vue | 43 +++++++++++++++++++ .../src/constants/AccountPropertyConstants.ts | 2 + apps/settings/src/main-personal-info.js | 3 ++ .../settings/personal/personal.info.php | 3 ++ 9 files changed, 66 insertions(+) create mode 100644 apps/settings/src/components/PersonalInfo/TimezoneSection.vue diff --git a/apps/provisioning_api/lib/Controller/AUserDataOCSController.php b/apps/provisioning_api/lib/Controller/AUserDataOCSController.php index d321adf7c8f..5f3f474ec7b 100644 --- a/apps/provisioning_api/lib/Controller/AUserDataOCSController.php +++ b/apps/provisioning_api/lib/Controller/AUserDataOCSController.php @@ -43,6 +43,7 @@ abstract class AUserDataOCSController extends OCSController { public const USER_FIELD_DISPLAYNAME = 'display'; public const USER_FIELD_LANGUAGE = 'language'; public const USER_FIELD_LOCALE = 'locale'; + public const USER_FIELD_TIMEZONE = 'timezone'; public const USER_FIELD_FIRST_DAY_OF_WEEK = 'first_day_of_week'; public const USER_FIELD_PASSWORD = 'password'; public const USER_FIELD_QUOTA = 'quota'; @@ -187,6 +188,7 @@ abstract class AUserDataOCSController extends OCSController { $data['groups'] = $gids; $data[self::USER_FIELD_LANGUAGE] = $this->l10nFactory->getUserLanguage($targetUserObject); $data[self::USER_FIELD_LOCALE] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'locale'); + $data[self::USER_FIELD_TIMEZONE] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'timezone'); $data[self::USER_FIELD_NOTIFICATION_EMAIL] = $targetUserObject->getPrimaryEMailAddress(); $backend = $targetUserObject->getBackend(); diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index 513a27c7df8..12ada1c0213 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -954,6 +954,7 @@ class UsersController extends AUserDataOCSController { $permittedFields[] = self::USER_FIELD_PASSWORD; $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL; + $permittedFields[] = self::USER_FIELD_TIMEZONE; if ( $this->config->getSystemValue('force_language', false) === false || $this->groupManager->isAdmin($currentLoggedInUser->getUID()) @@ -1028,6 +1029,7 @@ class UsersController extends AUserDataOCSController { $permittedFields[] = self::USER_FIELD_PASSWORD; $permittedFields[] = self::USER_FIELD_LANGUAGE; $permittedFields[] = self::USER_FIELD_LOCALE; + $permittedFields[] = self::USER_FIELD_TIMEZONE; $permittedFields[] = self::USER_FIELD_FIRST_DAY_OF_WEEK; $permittedFields[] = IAccountManager::PROPERTY_PHONE; $permittedFields[] = IAccountManager::PROPERTY_ADDRESS; @@ -1122,6 +1124,12 @@ class UsersController extends AUserDataOCSController { } $this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value); break; + case self::USER_FIELD_TIMEZONE: + if (!in_array($value, \DateTimeZone::listIdentifiers())) { + throw new OCSException($this->l10n->t('Invalid timezone'), 101); + } + $this->config->setUserValue($targetUser->getUID(), 'core', 'timezone', $value); + break; case self::USER_FIELD_FIRST_DAY_OF_WEEK: $intValue = (int)$value; if ($intValue < -1 || $intValue > 6) { diff --git a/apps/provisioning_api/lib/ResponseDefinitions.php b/apps/provisioning_api/lib/ResponseDefinitions.php index 62ae4ca577b..c05b5873459 100644 --- a/apps/provisioning_api/lib/ResponseDefinitions.php +++ b/apps/provisioning_api/lib/ResponseDefinitions.php @@ -65,6 +65,7 @@ namespace OCA\Provisioning_API; * roleScope?: Provisioning_APIUserDetailsScope, * storageLocation?: string, * subadmin: list, + * timezone: string, * twitter: string, * twitterScope?: Provisioning_APIUserDetailsScope, * bluesky: string, diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index 0c0a0ae3d74..1a1c7c80fae 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -1225,6 +1225,7 @@ class UsersControllerTest extends TestCase { 'groups' => ['group0', 'group1', 'group2'], 'language' => 'de', 'locale' => null, + 'timezone' => null, 'backendCapabilities' => [ 'setDisplayName' => true, 'setPassword' => true, @@ -1372,6 +1373,7 @@ class UsersControllerTest extends TestCase { 'groups' => [], 'language' => 'da', 'locale' => null, + 'timezone' => null, 'backendCapabilities' => [ 'setDisplayName' => true, 'setPassword' => true, @@ -1557,6 +1559,7 @@ class UsersControllerTest extends TestCase { 'groups' => [], 'language' => 'ru', 'locale' => null, + 'timezone' => null, 'backendCapabilities' => [ 'setDisplayName' => false, 'setPassword' => false, diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index 9a12b18bb5e..afa9f076e1a 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -110,6 +110,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', AUserDataOCSController::USER_FIELD_FIRST_DAY_OF_WEEK), + 'timezone' => $this->config->getUserValue($uid, 'core', 'timezone', ''), 'pronouns' => $this->getProperty($account, IAccountManager::PROPERTY_PRONOUNS), ]; diff --git a/apps/settings/src/components/PersonalInfo/TimezoneSection.vue b/apps/settings/src/components/PersonalInfo/TimezoneSection.vue new file mode 100644 index 00000000000..a6a810e0a48 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/TimezoneSection.vue @@ -0,0 +1,43 @@ + + + + + + + diff --git a/apps/settings/src/constants/AccountPropertyConstants.ts b/apps/settings/src/constants/AccountPropertyConstants.ts index 575a2744cc6..d2d9720ac24 100644 --- a/apps/settings/src/constants/AccountPropertyConstants.ts +++ b/apps/settings/src/constants/AccountPropertyConstants.ts @@ -108,6 +108,7 @@ export const ACCOUNT_SETTING_PROPERTY_ENUM = Object.freeze({ LANGUAGE: 'language', LOCALE: 'locale', FIRST_DAY_OF_WEEK: 'first_day_of_week', + TIMEZONE: 'timezone', }) /** Enum of account setting properties to human readable setting properties */ @@ -115,6 +116,7 @@ export const ACCOUNT_SETTING_PROPERTY_READABLE_ENUM = Object.freeze({ LANGUAGE: t('settings', 'Language'), LOCALE: t('settings', 'Locale'), FIRST_DAY_OF_WEEK: t('settings', 'First day of week'), + TIMEZONE: t('settings', 'timezone'), }) /** Enum of scopes */ diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js index 5ccfc9848c0..d5bac6254bf 100644 --- a/apps/settings/src/main-personal-info.js +++ b/apps/settings/src/main-personal-info.js @@ -26,6 +26,7 @@ import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSect import ProfileVisibilitySection from './components/PersonalInfo/ProfileVisibilitySection/ProfileVisibilitySection.vue' import PronounsSection from './components/PersonalInfo/PronounsSection.vue' import RoleSection from './components/PersonalInfo/RoleSection.vue' +import TimezoneSection from './components/PersonalInfo/TimezoneSection.vue' import TwitterSection from './components/PersonalInfo/TwitterSection.vue' import BlueskySection from './components/PersonalInfo/BlueskySection.vue' import WebsiteSection from './components/PersonalInfo/WebsiteSection.vue' @@ -47,6 +48,7 @@ const DisplayNameView = Vue.extend(DisplayNameSection) const EmailView = Vue.extend(EmailSection) const FediverseView = Vue.extend(FediverseSection) const FirstDayOfWeekView = Vue.extend(FirstDayOfWeekSection) +const TimezoneView = Vue.extend(TimezoneSection) const LanguageView = Vue.extend(LanguageSection) const LocaleView = Vue.extend(LocaleSection) const LocationView = Vue.extend(LocationSection) @@ -69,6 +71,7 @@ new FediverseView().$mount('#vue-fediverse-section') new LanguageView().$mount('#vue-language-section') new LocaleView().$mount('#vue-locale-section') new FirstDayOfWeekView().$mount('#vue-fdow-section') +new TimezoneView().$mount('#vue-timezone-section') new BirthdayView().$mount('#vue-birthday-section') new PronounsView().$mount('#vue-pronouns-section') diff --git a/apps/settings/templates/settings/personal/personal.info.php b/apps/settings/templates/settings/personal/personal.info.php index 38c449a337f..3fb61219149 100644 --- a/apps/settings/templates/settings/personal/personal.info.php +++ b/apps/settings/templates/settings/personal/personal.info.php @@ -67,6 +67,9 @@ script('settings', [
+
+
+