Skip to content

Commit 6006335

Browse files
Merge pull request #6791 from christianbeeznest/ofaj-22823-3
User: Adjust settings logic in profile edit - refs BT#22823
2 parents 23583c4 + bd7d910 commit 6006335

File tree

2 files changed

+112
-78
lines changed

2 files changed

+112
-78
lines changed

src/CoreBundle/Form/ExtraFieldType.php

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,27 @@ public function __construct(
4747

4848
public function buildForm(FormBuilderInterface $builder, array $options): void
4949
{
50+
/** Prefer the bound item (user) passed by ProfileType; fallback to Security user. */
5051
/** @var User|null $item */
51-
$item = $this->security->getUser();
52-
if (null === $item) {
53-
return;
54-
}
52+
$item = $options['item'] instanceof User ? $options['item'] : ($this->security->getUser() instanceof User ? $this->security->getUser() : null);
5553

5654
$extraFieldType = ExtraField::USER_FIELD_TYPE;
5755

58-
// Load all extra fields for user
56+
// Load all extra fields for user type
5957
$extraFields = $this->extraFieldRepository->getExtraFields($extraFieldType);
6058

6159
// Optional allowlist/editable map provided by parent form
6260
/** @var string[] $allowlist */
6361
$allowlist = $options['visibility_allowlist'] ?? [];
6462
/** @var array<string,bool> $editableMap */
6563
$editableMap = $options['visibility_editable_map'] ?? [];
64+
$strict = (bool)($options['visibility_strict'] ?? false);
6665

67-
// Determine Google Maps plugin state (enabled + API on)
66+
if ($strict && empty($allowlist)) {
67+
return;
68+
}
69+
70+
// Google Maps plugin state
6871
$pluginEnabled = $this->pluginHelper->isPluginEnabled('google_maps');
6972
$gMapsPlugin = GoogleMapsPlugin::create();
7073
$apiEnabled = ('true' === $gMapsPlugin->get('enable_api'));
@@ -90,11 +93,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
9093
}
9194
}
9295

93-
// Fetch current values for the logged user
94-
$values = $this->extraFieldValuesRepository->getExtraFieldValuesFromItem($item, $extraFieldType);
96+
// Current values for the (possibly null) user
9597
$data = [];
96-
foreach ($values as $value) {
97-
$data[$value->getField()->getVariable()] = $value->getFieldValue();
98+
if ($item instanceof User) {
99+
$values = $this->extraFieldValuesRepository->getExtraFieldValuesFromItem($item, $extraFieldType);
100+
foreach ($values as $value) {
101+
$data[$value->getField()->getVariable()] = $value->getFieldValue();
102+
}
98103
}
99104

100105
// Build form fields
@@ -153,16 +158,18 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
153158

154159
// Preload existing user tags as choices (if any)
155160
$class = 'select2_extra_rel_tag';
156-
$tags = $this->tagRepository->getTagsByUser($extraField, $item);
157161
$choices = [];
158162
$choicesAttributes = [];
159-
foreach ($tags as $tag) {
160-
$stringTag = $tag->getTag();
161-
if ($stringTag === '') {
162-
continue;
163+
if ($item instanceof User) {
164+
$tags = $this->tagRepository->getTagsByUser($extraField, $item);
165+
foreach ($tags as $tag) {
166+
$stringTag = $tag->getTag();
167+
if ($stringTag === '') {
168+
continue;
169+
}
170+
$choices[$stringTag] = $stringTag;
171+
$choicesAttributes[$stringTag] = ['data-id' => $tag->getId()];
163172
}
164-
$choices[$stringTag] = $stringTag;
165-
$choicesAttributes[$stringTag] = ['data-id' => $tag->getId()];
166173
}
167174
$defaultOptions['choices'] = $choices;
168175
$defaultOptions['choice_attr'] = $choicesAttributes;
@@ -189,20 +196,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
189196
break;
190197

191198
case \ExtraField::FIELD_TYPE_DATE:
192-
$defaultOptions['data'] = null;
193-
if (!empty($value)) {
194-
$defaultOptions['data'] = new DateTime((string) $value);
195-
}
199+
$defaultOptions['data'] = !empty($value) ? new DateTime((string) $value) : null;
196200
$defaultOptions['widget'] = 'single_text';
197201
$builder->add($variable, DateType::class, $defaultOptions);
198-
199202
break;
200203

201204
case \ExtraField::FIELD_TYPE_DATETIME:
202-
$defaultOptions['data'] = null;
203-
if (!empty($value)) {
204-
$defaultOptions['data'] = new DateTime((string) $value);
205-
}
205+
$defaultOptions['data'] = !empty($value) ? new DateTime((string) $value) : null;
206206
$defaultOptions['widget'] = 'single_text';
207207
$builder->add($variable, DateTimeType::class, $defaultOptions);
208208

@@ -274,11 +274,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
274274
$builder->addEventListener(
275275
FormEvents::PRE_SUBMIT,
276276
function (FormEvent $event) use ($item, $extraFields): void {
277+
// If item is missing, we cannot persist, but we still built fields for UX
278+
if (!$item instanceof User) {
279+
return;
280+
}
281+
277282
$data = $event->getData() ?? [];
278283
foreach ($extraFields as $extraField) {
279284
$variable = $extraField->getVariable();
280-
281-
// If field wasn't built (e.g., filtered by allowlist), skip
282285
if (!\array_key_exists($variable, $data)) {
283286
continue;
284287
}
@@ -322,13 +325,15 @@ function (FormEvent $event) use ($item, $extraFields): void {
322325
public function configureOptions(OptionsResolver $resolver): void
323326
{
324327
$resolver->setDefaults([
325-
// Optional: list of extra variables to render. Empty = render all.
326328
'visibility_allowlist' => [],
327-
// Optional: control editable per variable. Empty = fallback to ExtraField::isChangeable()
328329
'visibility_editable_map' => [],
330+
'visibility_strict' => false,
331+
'item' => null,
329332
]);
330333

331334
$resolver->setAllowedTypes('visibility_allowlist', ['array']);
332335
$resolver->setAllowedTypes('visibility_editable_map', ['array']);
336+
$resolver->setAllowedTypes('visibility_strict', ['bool']);
337+
$resolver->setAllowedTypes('item', ['null', User::class]);
333338
}
334339
}

src/CoreBundle/Form/ProfileType.php

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,26 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
3636
$changeableOptions = $this->settingsManager->getSetting('profile.changeable_options', true) ?? [];
3737
$visibleOptions = $this->settingsManager->getSetting('profile.visible_options', true) ?? [];
3838

39-
// Fine-grained (category + key): profile.profile_fields_visibility (JSON)
40-
$visibilitySetting = $this->settingsManager->getSetting('profile.profile_fields_visibility', true) ?? [];
41-
if (\is_string($visibilitySetting)) {
39+
// Fine-grained JSON (authoritative if present)
40+
$rawFine = $this->settingsManager->getSetting('profile.profile_fields_visibility', true) ?? [];
41+
if (\is_string($rawFine)) {
4242
try {
43-
$decoded = \json_decode($visibilitySetting, true, 512, \JSON_THROW_ON_ERROR);
44-
if (\is_array($decoded)) {
45-
$visibilitySetting = $decoded;
46-
}
43+
$decoded = \json_decode($rawFine, true, 512, \JSON_THROW_ON_ERROR);
44+
$rawFine = \is_array($decoded) ? $decoded : [];
4745
} catch (\Throwable) {
48-
$visibilitySetting = [];
46+
$rawFine = [];
4947
}
5048
}
5149
$fieldsVisibility = [];
52-
if (\is_array($visibilitySetting)) {
53-
$fieldsVisibility = $visibilitySetting['options'] ?? $visibilitySetting;
50+
if (\is_array($rawFine)) {
51+
$fieldsVisibility = $rawFine['options'] ?? $rawFine;
5452
if (!\is_array($fieldsVisibility)) {
5553
$fieldsVisibility = [];
5654
}
5755
}
56+
$hasFine = !empty($fieldsVisibility); // strict mode if true
5857

59-
// Expand aliases used by high-level settings
58+
// Expand aliases used by high-level settings (fallbacks only)
6059
$expandMap = [
6160
'name' => ['firstname', 'lastname'],
6261
'surname' => ['lastname'],
@@ -103,7 +102,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
103102
'label' => 'Date of birth',
104103
'required' => false,
105104
'form_options' => [
106-
// Plain input + flatpickr in Twig (better UX) - html5 off to avoid native picker
107105
'widget' => 'single_text',
108106
'html5' => false,
109107
'format' => 'yyyy-MM-dd',
@@ -116,26 +114,49 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
116114
],
117115
],
118116
],
117+
// Timezone will be added below if visible (fine JSON or fallback)
118+
'timezone' => [
119+
'field' => 'timezone',
120+
'type' => ChoiceType::class,
121+
'label' => 'Timezone',
122+
'required' => false,
123+
'form_options' => static function (): array {
124+
$timezones = DateTimeZone::listIdentifiers();
125+
sort($timezones);
126+
$choices = array_combine($timezones, $timezones);
127+
return [
128+
'choices' => $choices,
129+
'placeholder' => '',
130+
'choice_translation_domain' => false,
131+
];
132+
},
133+
],
119134
];
120135

121-
// Priority rules (core):
122-
// - If key exists in fine-grained map: boolean means editable=true/false; presence means visible
123-
// - If not present: visible/editable fall back to high-level lists
124-
$isCoreVisible = function (string $key) use ($fieldsVisibility, $visibleHigh): bool {
125-
if (\array_key_exists($key, $fieldsVisibility)) {
126-
return true; // listed → visible
136+
// Visibility (core):
137+
// Strict when $hasFine: only keys present in $fieldsVisibility are visible.
138+
// Otherwise, fallback to visible_options.
139+
$isCoreVisible = function (string $key) use ($fieldsVisibility, $visibleHigh, $hasFine): bool {
140+
if ($hasFine) {
141+
return \array_key_exists($key, $fieldsVisibility);
127142
}
128143
return \in_array($key, $visibleHigh, true);
129144
};
145+
146+
// Editability (core):
147+
// If key is in fine JSON, its boolean decides; otherwise fallback to changeable_options.
130148
$isCoreEditable = function (string $key) use ($fieldsVisibility, $editableHigh): bool {
131149
if (\array_key_exists($key, $fieldsVisibility)) {
132-
return (bool) $fieldsVisibility[$key]; // json boolean drives editability
150+
return (bool) $fieldsVisibility[$key];
133151
}
134152
return \in_array($key, $editableHigh, true);
135153
};
136154

137-
// Build core fields
155+
// Build core fields (except timezone; decide after)
138156
foreach ($fieldsMap as $key => $fieldConfig) {
157+
if ($key === 'timezone') {
158+
continue;
159+
}
139160
if (!$isCoreVisible($key)) {
140161
continue;
141162
}
@@ -156,8 +177,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
156177
}
157178
}
158179

159-
if (isset($fieldConfig['form_options']) && \is_array($fieldConfig['form_options'])) {
160-
$opts = array_merge($opts, $fieldConfig['form_options']);
180+
if (isset($fieldConfig['form_options'])) {
181+
$extra = \is_callable($fieldConfig['form_options'])
182+
? ($fieldConfig['form_options'])()
183+
: (array) $fieldConfig['form_options'];
184+
$opts = array_merge($opts, $extra);
161185
}
162186

163187
if (!$isCoreEditable($key)) {
@@ -167,43 +191,48 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
167191
$builder->add($fieldConfig['field'], $fieldConfig['type'], $opts);
168192
}
169193

170-
// Timezone (optional, independent from the visibility settings above)
171-
if ('true' === $this->settingsManager->getSetting('profile.use_users_timezone', true)) {
172-
$timezones = DateTimeZone::listIdentifiers();
173-
sort($timezones);
174-
$timezoneChoices = array_combine($timezones, $timezones);
175-
176-
$builder->add(
177-
'timezone',
178-
ChoiceType::class,
179-
[
180-
'label' => 'Timezone',
181-
'choices' => $timezoneChoices,
182-
'required' => false,
183-
'placeholder' => '',
184-
'choice_translation_domain' => false,
185-
]
186-
);
194+
// Timezone: only show if visible (fine JSON present with key, or fallback says visible)
195+
if ($isCoreVisible('timezone')) {
196+
$tzCfg = $fieldsMap['timezone'];
197+
$opts = [
198+
'label' => $tzCfg['label'],
199+
'required' => $tzCfg['required'],
200+
'mapped' => true,
201+
];
202+
$extra = ($tzCfg['form_options'])();
203+
$opts = array_merge($opts, $extra);
204+
if (!$isCoreEditable('timezone')) {
205+
$opts['disabled'] = true;
206+
}
207+
$builder->add($tzCfg['field'], $tzCfg['type'], $opts);
187208
}
188209

189-
// Build ExtraFieldType with allowlist + editable map derived from JSON
190-
// Consider as "core" the keys present in $fieldsMap; the rest of JSON keys affect "extra" fields.
210+
// Build ExtraFieldType with allowlist + editable map derived from fine JSON (strict when present)
191211
$coreKeys = array_keys($fieldsMap);
192-
193212
$extraAllowlist = [];
194213
$extraEditableMap = [];
195-
foreach ($fieldsVisibility as $key => $bool) {
196-
if (!\in_array($key, $coreKeys, true)) {
197-
$extraAllowlist[] = $key; // presence in JSON ⇒ visible
198-
$extraEditableMap[$key] = (bool) $bool; // boolean ⇒ editable
214+
215+
if ($hasFine) {
216+
// Strict: only extras listed in fine JSON
217+
foreach ($fieldsVisibility as $key => $bool) {
218+
if (!\in_array($key, $coreKeys, true)) {
219+
$extraAllowlist[] = $key; // visible
220+
$extraEditableMap[$key] = (bool) $bool; // editable
221+
}
199222
}
223+
} else {
224+
// Fallback: show all extras (no allowlist) and let ExtraField configuration drive editability
225+
$extraAllowlist = []; // empty = render all extras
226+
$extraEditableMap = []; // let EF config decide
200227
}
201228

202229
$builder->add('extra_fields', ExtraFieldType::class, [
203230
'mapped' => false,
204231
'label' => false,
205-
'visibility_allowlist' => $extraAllowlist, // empty → show all; non-empty → filter
206-
'visibility_editable_map' => $extraEditableMap, // editable control per extra
232+
'visibility_allowlist' => $extraAllowlist,
233+
'visibility_editable_map' => $extraEditableMap,
234+
'visibility_strict' => $hasFine,
235+
'item' => $builder->getData(),
207236
]);
208237
}
209238

0 commit comments

Comments
 (0)