Skip to content

Commit c3f1ecc

Browse files
Admin: Restore MultiURL settings eye toggle per URL - refs #6207
1 parent 1afd8f6 commit c3f1ecc

File tree

4 files changed

+524
-87
lines changed

4 files changed

+524
-87
lines changed

src/CoreBundle/Controller/Admin/SettingsController.php

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,73 @@ public function index(): Response
4141
}
4242

4343
/**
44-
* Edit configuration with given namespace.
44+
* Toggle access_url_changeable for a given setting variable.
45+
* Only platform admins on the main URL (ID = 1) are allowed to change it,
46+
*/
47+
#[IsGranted('ROLE_ADMIN')]
48+
#[Route('/settings/toggle_changeable', name: 'settings_toggle_changeable', methods: ['POST'])]
49+
public function toggleChangeable(Request $request, AccessUrlHelper $accessUrlHelper): JsonResponse
50+
{
51+
// Security: only admins.
52+
if (!$this->isGranted('ROLE_ADMIN')) {
53+
return $this->json([
54+
'error' => 'Only platform admins can modify this flag.',
55+
], 403);
56+
}
57+
58+
$currentUrl = $accessUrlHelper->getCurrent();
59+
$currentUrlId = $currentUrl->getId();
60+
61+
// Only main URL (ID = 1) can toggle the flag.
62+
if (1 !== $currentUrlId) {
63+
return $this->json([
64+
'error' => 'Only the main URL (ID 1) can toggle this setting.',
65+
], 403);
66+
}
67+
68+
$payload = json_decode($request->getContent(), true);
69+
70+
if (!\is_array($payload) || !isset($payload['variable'], $payload['status'])) {
71+
return $this->json([
72+
'error' => 'Invalid payload.',
73+
], 400);
74+
}
75+
76+
$variable = (string) $payload['variable'];
77+
$status = (int) $payload['status'];
78+
79+
$repo = $this->entityManager->getRepository(SettingsCurrent::class);
80+
81+
// We search by variable + current main AccessUrl entity.
82+
$setting = $repo->findOneBy([
83+
'variable' => $variable,
84+
'url' => $currentUrl,
85+
]);
86+
87+
if (!$setting) {
88+
return $this->json([
89+
'error' => 'Setting not found.',
90+
], 404);
91+
}
92+
93+
try {
94+
$setting->setAccessUrlChangeable($status);
95+
$this->entityManager->flush();
96+
97+
return $this->json([
98+
'result' => 1,
99+
'status' => $status,
100+
]);
101+
} catch (\Throwable $e) {
102+
return $this->json([
103+
'error' => 'Unable to update setting.',
104+
'details' => $e->getMessage(),
105+
], 500);
106+
}
107+
}
108+
109+
/**
110+
* Edit configuration with given namespace (search page).
45111
*/
46112
#[IsGranted('ROLE_ADMIN')]
47113
#[Route('/settings/search_settings', name: 'chamilo_platform_settings_search')]
@@ -69,6 +135,35 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
69135
$schemas = $manager->getSchemas();
70136
[$ordered, $labelMap] = $this->computeOrderedNamespacesByTranslatedLabel($schemas, $request);
71137

138+
// Template map for current URL (existing behavior – JSON helper)
139+
$settingsRepo = $this->entityManager->getRepository(SettingsCurrent::class);
140+
$settingsWithTemplate = $settingsRepo->findBy(['url' => $url]);
141+
142+
foreach ($settingsWithTemplate as $s) {
143+
if ($s->getValueTemplate()) {
144+
$templateMap[$s->getVariable()] = $s->getValueTemplate()->getId();
145+
}
146+
}
147+
148+
// MultiURL changeable flags: read from main URL (ID = 1) only
149+
$changeableMap = [];
150+
$mainUrlRows = $settingsRepo->createQueryBuilder('sc')
151+
->join('sc.url', 'u')
152+
->andWhere('u.id = :mainId')
153+
->setParameter('mainId', 1)
154+
->getQuery()
155+
->getResult();
156+
157+
foreach ($mainUrlRows as $row) {
158+
if ($row instanceof SettingsCurrent) {
159+
$changeableMap[$row->getVariable()] = $row->getAccessUrlChangeable();
160+
}
161+
}
162+
163+
$currentUrlId = $url->getId();
164+
// Only platform admins on the main URL can toggle the MultiURL flag.
165+
$canToggleMultiUrlSetting = $this->isGranted('ROLE_ADMIN') && 1 === $currentUrlId;
166+
72167
if ('' === $keyword) {
73168
return $this->render('@ChamiloCore/Admin/Settings/search.html.twig', [
74169
'keyword' => $keyword,
@@ -80,17 +175,12 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
80175
'template_map_by_category' => $templateMapByCategory,
81176
'ordered_namespaces' => $ordered,
82177
'namespace_labels' => $labelMap,
178+
'changeable_map' => $changeableMap,
179+
'current_url_id' => $currentUrlId,
180+
'can_toggle_multiurl_setting' => $canToggleMultiUrlSetting,
83181
]);
84182
}
85183

86-
$settingsRepo = $this->entityManager->getRepository(SettingsCurrent::class);
87-
$settingsWithTemplate = $settingsRepo->findBy(['url' => $url]);
88-
foreach ($settingsWithTemplate as $s) {
89-
if ($s->getValueTemplate()) {
90-
$templateMap[$s->getVariable()] = $s->getValueTemplate()->getId();
91-
}
92-
}
93-
94184
$settingsFromKeyword = $manager->getParametersFromKeywordOrderedByCategory($keyword);
95185
if (!empty($settingsFromKeyword)) {
96186
foreach ($settingsFromKeyword as $category => $parameterList) {
@@ -110,7 +200,7 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
110200
// Convert category to schema alias and validate it BEFORE loading/creating the form
111201
$schemaAlias = $manager->convertNameSpaceToService($category);
112202

113-
// skip unknown/legacy categories (e.g., "tools")
203+
// Skip unknown/legacy categories (e.g., "tools")
114204
if (!isset($schemas[$schemaAlias])) {
115205
continue;
116206
}
@@ -139,11 +229,14 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
139229
'template_map_by_category' => $templateMapByCategory,
140230
'ordered_namespaces' => $ordered,
141231
'namespace_labels' => $labelMap,
232+
'changeable_map' => $changeableMap,
233+
'current_url_id' => $currentUrlId,
234+
'can_toggle_multiurl_setting' => $canToggleMultiUrlSetting,
142235
]);
143236
}
144237

145238
/**
146-
* Edit configuration with given namespace.
239+
* Edit configuration with given namespace (main settings page).
147240
*/
148241
#[IsGranted('ROLE_ADMIN')]
149242
#[Route('/settings/{namespace}', name: 'chamilo_platform_settings')]
@@ -217,17 +310,38 @@ public function updateSetting(Request $request, AccessUrlHelper $accessUrlHelper
217310
$templateMap = [];
218311
$settingsRepo = $this->entityManager->getRepository(SettingsCurrent::class);
219312

313+
// Template map for current URL (existing behavior – JSON helper)
220314
$settingsWithTemplate = $settingsRepo->findBy(['url' => $url]);
221315

222316
foreach ($settingsWithTemplate as $s) {
223317
if ($s->getValueTemplate()) {
224318
$templateMap[$s->getVariable()] = $s->getValueTemplate()->getId();
225319
}
226320
}
321+
322+
// MultiURL changeable flags: read from main URL (ID = 1) only
323+
$changeableMap = [];
324+
$mainUrlRows = $settingsRepo->createQueryBuilder('sc')
325+
->join('sc.url', 'u')
326+
->andWhere('u.id = :mainId')
327+
->setParameter('mainId', 1)
328+
->getQuery()
329+
->getResult();
330+
331+
foreach ($mainUrlRows as $row) {
332+
if ($row instanceof SettingsCurrent) {
333+
$changeableMap[$row->getVariable()] = $row->getAccessUrlChangeable();
334+
}
335+
}
336+
227337
$platform = [
228338
'server_type' => (string) $manager->getSetting('platform.server_type', true),
229339
];
230340

341+
$currentUrlId = $url->getId();
342+
// Only platform admins on the main URL can toggle the MultiURL flag.
343+
$canToggleMultiUrlSetting = $this->isGranted('ROLE_ADMIN') && 1 === $currentUrlId;
344+
231345
return $this->render('@ChamiloCore/Admin/Settings/default.html.twig', [
232346
'schemas' => $schemas,
233347
'settings' => $settings,
@@ -238,6 +352,9 @@ public function updateSetting(Request $request, AccessUrlHelper $accessUrlHelper
238352
'ordered_namespaces' => $ordered,
239353
'namespace_labels' => $labelMap,
240354
'platform' => $platform,
355+
'changeable_map' => $changeableMap,
356+
'current_url_id' => $currentUrlId,
357+
'can_toggle_multiurl_setting' => $canToggleMultiUrlSetting,
241358
]);
242359
}
243360

src/CoreBundle/Resources/views/Admin/Settings/default.html.twig

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@
6262
{% for field in form %}
6363
{% set fieldName = field.vars.name %}
6464
{% set isHidden = 'hidden' in field.vars.block_prefixes %}
65+
{% set isDisabledOnSubUrl =
66+
current_url_id is defined
67+
and current_url_id != 1
68+
and changeable_map is defined
69+
and changeable_map[fieldName] is defined
70+
and not changeable_map[fieldName]
71+
%}
6572

6673
{% if isHidden %}
6774
{{ form_widget(field) }}
@@ -73,6 +80,7 @@
7380
</h3>
7481

7582
<div class="flex items-center gap-2">
83+
{# Optional JSON template helper link #}
7684
{% if template_map[fieldName] is defined %}
7785
<a href="#"
7886
class="text-info hover:text-info-dark show-template flex items-center gap-2"
@@ -83,23 +91,58 @@
8391
</a>
8492
{% endif %}
8593

86-
94+
{# Debug icon on test servers (keep original behavior) #}
8795
{% set serverType = platform.server_type ?? (settings.platform.server_type ?? null) %}
8896
{% if serverType == 'test' %}
89-
<span
90-
class="ml-3 text-gray-60 text-lg flex items-center cursor-default debug-setting-icon"
97+
<span
98+
class="ml-1 text-gray-60 text-lg flex items-center cursor-default debug-setting-icon"
9199
title="{{ namespace ~ '.' ~ fieldName }}"
92100
role="img"
93101
aria-hidden="true"
94102
data-field-id="{{ field.vars.id }}"
95103
data-namespace="{{ namespace }}"
96104
data-field-name="{{ fieldName }}"
97-
{% if template_map[fieldName] is defined %} data-template-id="{{ template_map[fieldName] }}" {% endif %}
98-
>
99-
<i class="mdi mdi-alphabetical course-tool__icon bg-primary-bgdisabled text-base" aria-hidden="true"></i>
100-
</span>
105+
{% if template_map[fieldName] is defined %}
106+
data-template-id="{{ template_map[fieldName] }}"
107+
{% endif %}
108+
>
109+
<i class="mdi mdi-alphabetical course-tool__icon bg-primary-bgdisabled text-base" aria-hidden="true"></i>
110+
</span>
101111
{% endif %}
102112

113+
{# MultiURL changeable eye #}
114+
{% if changeable_map is defined %}
115+
{% set changeable = changeable_map[fieldName] is defined ? changeable_map[fieldName] : 1 %}
116+
117+
{% if can_toggle_multiurl_setting %}
118+
{# Main URL + admin: clickable toggle #}
119+
<button
120+
type="button"
121+
class="ml-1 toggle-changeable flex items-center justify-center"
122+
data-variable="{{ fieldName }}"
123+
data-status="{{ changeable }}"
124+
title="{{ changeable ? 'Setting is editable on sub-URLs'|trans : 'Setting locked for sub-URLs'|trans }}"
125+
>
126+
{% if changeable %}
127+
<i class="mdi mdi-eye text-primary text-xl"></i>
128+
{% else %}
129+
<i class="mdi mdi-eye-off text-gray-50 text-xl"></i>
130+
{% endif %}
131+
</button>
132+
{% else %}
133+
{# Other URLs or non-privileged admins: read-only indicator #}
134+
<span
135+
class="ml-1 text-gray-50 text-lg flex items-center cursor-default"
136+
title="{{ changeable ? 'Setting is editable on sub-URLs'|trans : 'Setting locked for sub-URLs'|trans }}"
137+
>
138+
{% if changeable %}
139+
<i class="mdi mdi-eye text-gray-50 text-xl"></i>
140+
{% else %}
141+
<i class="mdi mdi-eye-off text-gray-50 text-xl"></i>
142+
{% endif %}
143+
</span>
144+
{% endif %}
145+
{% endif %}
103146
</div>
104147
</div>
105148

@@ -110,19 +153,25 @@
110153

111154
<div class="flex flex-col gap-2">
112155
{% for child in field %}
156+
{% set childAttr = isDisabledOnSubUrl ? { disabled: 'disabled' } : {} %}
113157
<label class="inline-flex items-center gap-2">
114-
{{ form_widget(child) }}
158+
{{ form_widget(child, { attr: childAttr }) }}
115159
<span>{{ child.vars.label }}</span>
116160
</label>
117161
{% endfor %}
118162
</div>
119163

120164
{% else %}
121-
{{ form_widget(field, {
122-
attr: {
123-
class: 'w-full rounded border border-gray-25 focus:border-primary focus:ring focus:ring-primary/30 transition'
124-
}
125-
}) }}
165+
{# Build base attributes and add disabled only when needed #}
166+
{% set baseAttr = {
167+
class: 'w-full rounded border border-gray-25 focus:border-primary focus:ring focus:ring-primary/30 transition'
168+
} %}
169+
{% set widgetAttr = isDisabledOnSubUrl
170+
? baseAttr|merge({ disabled: 'disabled' })
171+
: baseAttr
172+
%}
173+
174+
{{ form_widget(field, { attr: widgetAttr }) }}
126175
{% endif %}
127176
</div>
128177

@@ -203,6 +252,43 @@
203252
document.getElementById('jsonTemplateModal').classList.add('hidden');
204253
});
205254
});
255+
256+
// MultiURL eye toggle: send AJAX to toggle access_url_changeable
257+
document.addEventListener('click', function (e) {
258+
const btn = e.target.closest('.toggle-changeable');
259+
if (!btn) {
260+
return;
261+
}
262+
263+
const variable = btn.dataset.variable;
264+
const current = btn.dataset.status === '1' ? 1 : 0;
265+
const next = current === 1 ? 0 : 1;
266+
267+
fetch('/admin/settings/toggle_changeable', {
268+
method: 'POST',
269+
headers: {
270+
'Content-Type': 'application/json'
271+
},
272+
body: JSON.stringify({ variable: variable, status: next })
273+
})
274+
.then(r => r.json())
275+
.then(data => {
276+
if (data.result === 1) {
277+
btn.dataset.status = String(next);
278+
btn.innerHTML = next
279+
? '<i class="mdi mdi-eye text-primary text-xl"></i>'
280+
: '<i class="mdi mdi-eye-off text-gray-50 text-xl"></i>';
281+
} else if (data.error) {
282+
console.error('Failed to update changeable state:', data.error);
283+
alert(data.error);
284+
} else {
285+
console.error('Failed to update changeable state:', data);
286+
}
287+
})
288+
.catch(err => {
289+
console.error('AJAX error:', err);
290+
});
291+
});
206292
</script>
207293
{% endblock %}
208294
{% block javascripts %}

0 commit comments

Comments
 (0)