Skip to content

Commit 13e8dd2

Browse files
Internal: Unify timezone handling and remove obsolete extra field - refs BT#22706
1 parent 5527450 commit 13e8dd2

File tree

7 files changed

+133
-74
lines changed

7 files changed

+133
-74
lines changed

public/main/inc/lib/extra_field.lib.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class ExtraField extends Model
2020
public const FIELD_TYPE_DOUBLE_SELECT = 8;
2121
public const FIELD_TYPE_DIVIDER = 9;
2222
public const FIELD_TYPE_TAG = 10;
23-
public const FIELD_TYPE_TIMEZONE = 11;
2423
public const FIELD_TYPE_SOCIAL_PROFILE = 12;
2524
public const FIELD_TYPE_CHECKBOX = 13;
2625
public const FIELD_TYPE_MOBILE_PHONE_NUMBER = 14;
@@ -1573,16 +1572,6 @@ public function set_extra_fields_in_form(
15731572
}
15741573
}
15751574

1576-
break;
1577-
case self::FIELD_TYPE_TIMEZONE:
1578-
$form->addSelect(
1579-
'extra_'.$variable,
1580-
$field_details['display_text'],
1581-
api_get_timezones(),
1582-
);
1583-
if ($freezeElement) {
1584-
$form->freeze('extra_'.$variable);
1585-
}
15861575
break;
15871576
case self::FIELD_TYPE_SOCIAL_PROFILE:
15881577
// get the social network's favicon
@@ -1995,7 +1984,6 @@ public static function get_extra_fields_by_handler($handler)
19951984
$types[self::FIELD_TYPE_DOUBLE_SELECT] = get_lang('Double select');
19961985
$types[self::FIELD_TYPE_DIVIDER] = get_lang('Visual divider');
19971986
$types[self::FIELD_TYPE_TAG] = get_lang('User tag');
1998-
$types[self::FIELD_TYPE_TIMEZONE] = get_lang('Timezone');
19991987
$types[self::FIELD_TYPE_SOCIAL_PROFILE] = get_lang('Social network link');
20001988
$types[self::FIELD_TYPE_MOBILE_PHONE_NUMBER] = get_lang('Mobile phone number');
20011989
$types[self::FIELD_TYPE_CHECKBOX] = get_lang('Checkbox');

public/main/inc/lib/internationalization.lib.php

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,29 +192,28 @@ function api_get_timezones()
192192
*
193193
* @return string The timezone chosen
194194
*/
195-
function api_get_timezone()
195+
function api_get_timezone(): string
196196
{
197197
$timezone = Session::read('system_timezone');
198198
if (empty($timezone)) {
199-
// First, get the default timezone of the server
199+
// 1. Default server timezone
200200
$timezone = date_default_timezone_get();
201-
// Second, see if a timezone has been chosen for the platform
202-
$timezoneFromSettings = api_get_setting('platform.timezone', false, 'timezones');
203201

204-
if (null != $timezoneFromSettings) {
202+
// 2. Platform-specific timezone setting (overrides server default)
203+
$timezoneFromSettings = api_get_setting('platform.timezone', false, 'timezones');
204+
if (!empty($timezoneFromSettings)) {
205205
$timezone = $timezoneFromSettings;
206206
}
207207

208-
// If allowed by the administrator
208+
// 3. User-specific timezone if allowed
209209
$allowUserTimezones = api_get_setting('profile.use_users_timezone', false, 'timezones');
210210
$userId = api_get_user_id();
211211

212212
if ('true' === $allowUserTimezones && !empty($userId)) {
213-
// Get the timezone based on user preference, if it exists
214-
$newExtraField = new ExtraFieldValue('user');
215-
$data = $newExtraField->get_values_by_handler_and_field_variable($userId, 'timezone');
216-
if (!empty($data) && isset($data['timezone']) && !empty($data['timezone'])) {
217-
$timezone = $data['timezone'];
213+
$user = api_get_user_entity($userId);
214+
215+
if ($user && $user->getTimezone()) {
216+
$timezone = $user->getTimezone();
218217
}
219218
}
220219
Session::write('system_timezone', $timezone);

src/CoreBundle/Controller/SocialController.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -552,16 +552,16 @@ private function getExtraFieldBlock(
552552
ExtraField::USER_FIELD_TYPE
553553
);
554554
if (!empty($extraFieldOptions)) {
555-
$optionTexts = array_map(
556-
fn (ExtraFieldOptions $option) => $option['display_text'],
557-
$extraFieldOptions
555+
$fieldValue = implode(
556+
', ',
557+
array_map(
558+
fn (ExtraFieldOptions $opt) => $opt->getDisplayText(),
559+
$extraFieldOptions
560+
)
558561
);
559-
$fieldValue = implode(', ', $optionTexts);
560-
$fieldValue = $extraFieldOptions->getDisplayText();
561562
}
562563

563564
break;
564-
565565
case ExtraField::FIELD_TYPE_GEOLOCALIZATION_COORDINATES:
566566
case ExtraField::FIELD_TYPE_GEOLOCALIZATION:
567567
$geoData = explode('::', $fieldValue);

src/CoreBundle/DataFixtures/SettingsCurrentFixtures.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public static function getExistingSettings(): array
9090
[
9191
'name' => 'use_users_timezone',
9292
'title' => 'Enable users timezones',
93-
'comment' => 'Enable the possibility for users to select their own timezone. The timezone field should be set to visible and changeable in the Profiling menu in the administration section before users can choose their own. Once configured, users will be able to see assignment deadlines and other time references in their own timezone, which will reduce errors at delivery time.',
93+
'comment' => 'Enable the possibility for users to select their own timezone. Once configured, users will be able to see assignment deadlines and other time references in their own timezone, which will reduce errors at delivery time.',
9494
],
9595
[
9696
'name' => 'allow_show_linkedin_url',

src/CoreBundle/Form/ExtraFieldType.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
9393
switch ($extraField->getValueType()) {
9494
case \ExtraField::FIELD_TYPE_DOUBLE_SELECT:
9595
case \ExtraField::FIELD_TYPE_DIVIDER:
96-
case \ExtraField::FIELD_TYPE_TIMEZONE:
9796
case \ExtraField::FIELD_TYPE_FILE_IMAGE:
9897
case \ExtraField::FIELD_TYPE_FILE:
9998
case \ExtraField::FIELD_TYPE_LETTERS_SPACE:

src/CoreBundle/Form/ProfileType.php

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Symfony\Component\Form\Extension\Core\Type\EmailType;
1616
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
1717
use Symfony\Component\Form\Extension\Core\Type\TextType;
18-
use Symfony\Component\Form\Extension\Core\Type\TimezoneType;
1918
use Symfony\Component\Form\FormBuilderInterface;
2019
use Symfony\Component\OptionsResolver\OptionsResolver;
2120

@@ -32,64 +31,83 @@ public function __construct(
3231
public function buildForm(FormBuilderInterface $builder, array $options): void
3332
{
3433
$changeableOptions = $this->settingsManager->getSetting('profile.changeable_options', true) ?? [];
35-
$visibleOptions = $this->settingsManager->getSetting('profile.visible_options', true) ?? [];
34+
$visibleOptions = $this->settingsManager->getSetting('profile.visible_options', true) ?? [];
35+
3636
$languages = array_flip($this->languageRepository->getAllAvailableToArray(true));
3737

3838
$fieldsMap = [
39-
'name' => ['field' => 'firstname', 'type' => TextType::class, 'label' => 'Firstname'],
40-
'officialcode' => ['field' => 'official_code', 'type' => TextType::class, 'label' => 'Official Code'],
41-
'email' => ['field' => 'email', 'type' => EmailType::class, 'label' => 'Email'],
42-
'picture' => [
43-
'field' => 'illustration',
44-
'type' => IllustrationType::class,
45-
'label' => 'Picture',
39+
'name' => ['field' => 'firstname', 'type' => TextType::class, 'label' => 'Firstname'],
40+
'officialcode' => ['field' => 'official_code', 'type' => TextType::class, 'label' => 'Official Code'],
41+
'email' => ['field' => 'email', 'type' => EmailType::class, 'label' => 'Email'],
42+
'picture' => [
43+
'field' => 'illustration',
44+
'type' => IllustrationType::class,
45+
'label' => 'Picture',
4646
'mapped' => false,
4747
],
48-
'login' => ['field' => 'login', 'type' => TextType::class, 'label' => 'Login'],
48+
'login' => ['field' => 'login', 'type' => TextType::class, 'label' => 'Login'],
4949
'password' => [
50-
'field' => 'password',
51-
'type' => PasswordType::class,
52-
'label' => 'Password',
53-
'mapped' => false,
50+
'field' => 'password',
51+
'type' => PasswordType::class,
52+
'label' => 'Password',
53+
'mapped' => false,
5454
'required' => false,
5555
],
5656
'language' => [
57-
'field' => 'locale',
58-
'type' => ChoiceType::class,
59-
'label' => 'Language',
60-
'choices' => $languages,
57+
'field' => 'locale',
58+
'type' => ChoiceType::class,
59+
'label' => 'Language',
60+
'choices' => $languages,
61+
'required' => true,
62+
'placeholder'=> null,
63+
'choice_translation_domain' => false,
6164
],
6265
'phone' => ['field' => 'phone', 'type' => TextType::class, 'label' => 'Phone Number'],
6366
'theme' => ['field' => 'theme', 'type' => TextType::class, 'label' => 'Theme'],
6467
];
6568

6669
foreach ($fieldsMap as $key => $fieldConfig) {
67-
if (\in_array($key, $visibleOptions)) {
68-
$isEditable = \in_array($key, $changeableOptions);
69-
$builder->add(
70-
$fieldConfig['field'],
71-
$fieldConfig['type'],
72-
array_merge(
73-
[
74-
'label' => $fieldConfig['label'],
75-
'required' => $fieldConfig['required'] ?? false,
76-
'mapped' => $fieldConfig['mapped'] ?? true,
77-
'attr' => !$isEditable ? ['readonly' => true] : [],
78-
],
79-
isset($fieldConfig['choices']) ? ['choices' => $fieldConfig['choices']] : []
80-
)
81-
);
70+
if (\in_array($key, $visibleOptions, true)) {
71+
$isEditable = \in_array($key, $changeableOptions, true);
72+
73+
$options = [
74+
'label' => $fieldConfig['label'],
75+
'required' => $fieldConfig['required'] ?? false,
76+
'mapped' => $fieldConfig['mapped'] ?? true,
77+
];
78+
79+
if (isset($fieldConfig['choices'])) {
80+
$options['choices'] = $fieldConfig['choices'];
81+
if (isset($fieldConfig['placeholder'])) {
82+
$options['placeholder'] = $fieldConfig['placeholder'];
83+
}
84+
if (isset($fieldConfig['choice_translation_domain'])) {
85+
$options['choice_translation_domain'] = $fieldConfig['choice_translation_domain'];
86+
}
87+
}
88+
89+
if (!$isEditable) {
90+
$options['disabled'] = true;
91+
}
92+
93+
$builder->add($fieldConfig['field'], $fieldConfig['type'], $options);
8294
}
8395
}
8496

85-
if ('true' === $this->settingsManager->getSetting('use_users_timezone') && \in_array('timezone', $visibleOptions)) {
97+
if ('true' === $this->settingsManager->getSetting('profile.use_users_timezone', true)) {
98+
$timezones = \DateTimeZone::listIdentifiers();
99+
sort($timezones);
100+
$timezoneChoices = array_combine($timezones, $timezones);
101+
86102
$builder->add(
87103
'timezone',
88-
TimezoneType::class,
104+
ChoiceType::class,
89105
[
90-
'label' => 'Timezone',
91-
'required' => true,
92-
'attr' => !\in_array('timezone', $changeableOptions) ? ['readonly' => true] : [],
106+
'label' => 'Timezone',
107+
'choices' => $timezoneChoices,
108+
'required' => false,
109+
'placeholder' => '',
110+
'choice_translation_domain' => false,
93111
]
94112
);
95113
}
@@ -99,10 +117,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
99117

100118
public function configureOptions(OptionsResolver $resolver): void
101119
{
102-
$resolver->setDefaults(
103-
[
104-
'data_class' => User::class,
105-
]
106-
);
120+
$resolver->setDefaults([
121+
'data_class' => User::class,
122+
]);
107123
}
108124
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/* For licensing terms, see /license.txt */
6+
7+
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
8+
9+
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
10+
use Doctrine\DBAL\Schema\Schema;
11+
12+
final class Version20250616220500 extends AbstractMigrationChamilo
13+
{
14+
public function getDescription(): string
15+
{
16+
return 'Migrate timezone from extra_field_values to user.timezone and remove the extra_field.';
17+
}
18+
19+
public function up(Schema $schema): void
20+
{
21+
// 1. Copy values only when user.timezone is empty
22+
$this->addSql("
23+
UPDATE user u
24+
JOIN extra_field f
25+
ON f.variable = 'timezone'
26+
AND f.item_type = 1
27+
JOIN extra_field_values v
28+
ON v.field_id = f.id
29+
AND v.item_id = u.id
30+
SET u.timezone = v.field_value
31+
WHERE v.field_value IS NOT NULL
32+
AND v.field_value <> ''
33+
AND (u.timezone IS NULL OR u.timezone = '')
34+
");
35+
36+
// 2. Remove values from the extra_field
37+
$this->addSql("
38+
DELETE v FROM extra_field_values v
39+
JOIN extra_field f
40+
ON f.id = v.field_id
41+
WHERE f.variable = 'timezone'
42+
AND f.item_type = 1
43+
");
44+
45+
// 3. Remove the extra_field definition itself
46+
$this->addSql("
47+
DELETE FROM extra_field
48+
WHERE variable = 'timezone'
49+
AND item_type = 1
50+
");
51+
}
52+
53+
public function down(Schema $schema): void
54+
{
55+
// No rollback: the legacy extra_field will not be recreated
56+
}
57+
}

0 commit comments

Comments
 (0)