Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 128 additions & 11 deletions src/CoreBundle/Controller/Admin/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,73 @@ public function index(): Response
}

/**
* Edit configuration with given namespace.
* Toggle access_url_changeable for a given setting variable.
* Only platform admins on the main URL (ID = 1) are allowed to change it,
*/
#[IsGranted('ROLE_ADMIN')]
#[Route('/settings/toggle_changeable', name: 'settings_toggle_changeable', methods: ['POST'])]
public function toggleChangeable(Request $request, AccessUrlHelper $accessUrlHelper): JsonResponse
{
// Security: only admins.
if (!$this->isGranted('ROLE_ADMIN')) {
return $this->json([
'error' => 'Only platform admins can modify this flag.',
], 403);
}

$currentUrl = $accessUrlHelper->getCurrent();
$currentUrlId = $currentUrl->getId();

// Only main URL (ID = 1) can toggle the flag.
if (1 !== $currentUrlId) {
return $this->json([
'error' => 'Only the main URL (ID 1) can toggle this setting.',
], 403);
}

$payload = json_decode($request->getContent(), true);

if (!\is_array($payload) || !isset($payload['variable'], $payload['status'])) {
return $this->json([
'error' => 'Invalid payload.',
], 400);
}

$variable = (string) $payload['variable'];
$status = (int) $payload['status'];

$repo = $this->entityManager->getRepository(SettingsCurrent::class);

// We search by variable + current main AccessUrl entity.
$setting = $repo->findOneBy([
'variable' => $variable,
'url' => $currentUrl,
]);

if (!$setting) {
return $this->json([
'error' => 'Setting not found.',
], 404);
}

try {
$setting->setAccessUrlChangeable($status);
$this->entityManager->flush();

return $this->json([
'result' => 1,
'status' => $status,
]);
} catch (\Throwable $e) {
return $this->json([
'error' => 'Unable to update setting.',
'details' => $e->getMessage(),
], 500);
}
}

/**
* Edit configuration with given namespace (search page).
*/
#[IsGranted('ROLE_ADMIN')]
#[Route('/settings/search_settings', name: 'chamilo_platform_settings_search')]
Expand Down Expand Up @@ -69,6 +135,35 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
$schemas = $manager->getSchemas();
[$ordered, $labelMap] = $this->computeOrderedNamespacesByTranslatedLabel($schemas, $request);

// Template map for current URL (existing behavior – JSON helper)
$settingsRepo = $this->entityManager->getRepository(SettingsCurrent::class);
$settingsWithTemplate = $settingsRepo->findBy(['url' => $url]);

foreach ($settingsWithTemplate as $s) {
if ($s->getValueTemplate()) {
$templateMap[$s->getVariable()] = $s->getValueTemplate()->getId();
}
}

// MultiURL changeable flags: read from main URL (ID = 1) only
$changeableMap = [];
$mainUrlRows = $settingsRepo->createQueryBuilder('sc')
->join('sc.url', 'u')
->andWhere('u.id = :mainId')
->setParameter('mainId', 1)
->getQuery()
->getResult();

foreach ($mainUrlRows as $row) {
if ($row instanceof SettingsCurrent) {
$changeableMap[$row->getVariable()] = $row->getAccessUrlChangeable();
}
}

$currentUrlId = $url->getId();
// Only platform admins on the main URL can toggle the MultiURL flag.
$canToggleMultiUrlSetting = $this->isGranted('ROLE_ADMIN') && 1 === $currentUrlId;

if ('' === $keyword) {
return $this->render('@ChamiloCore/Admin/Settings/search.html.twig', [
'keyword' => $keyword,
Expand All @@ -80,17 +175,12 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
'template_map_by_category' => $templateMapByCategory,
'ordered_namespaces' => $ordered,
'namespace_labels' => $labelMap,
'changeable_map' => $changeableMap,
'current_url_id' => $currentUrlId,
'can_toggle_multiurl_setting' => $canToggleMultiUrlSetting,
]);
}

$settingsRepo = $this->entityManager->getRepository(SettingsCurrent::class);
$settingsWithTemplate = $settingsRepo->findBy(['url' => $url]);
foreach ($settingsWithTemplate as $s) {
if ($s->getValueTemplate()) {
$templateMap[$s->getVariable()] = $s->getValueTemplate()->getId();
}
}

$settingsFromKeyword = $manager->getParametersFromKeywordOrderedByCategory($keyword);
if (!empty($settingsFromKeyword)) {
foreach ($settingsFromKeyword as $category => $parameterList) {
Expand All @@ -110,7 +200,7 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
// Convert category to schema alias and validate it BEFORE loading/creating the form
$schemaAlias = $manager->convertNameSpaceToService($category);

// skip unknown/legacy categories (e.g., "tools")
// Skip unknown/legacy categories (e.g., "tools")
if (!isset($schemas[$schemaAlias])) {
continue;
}
Expand Down Expand Up @@ -139,11 +229,14 @@ public function searchSetting(Request $request, AccessUrlHelper $accessUrlHelper
'template_map_by_category' => $templateMapByCategory,
'ordered_namespaces' => $ordered,
'namespace_labels' => $labelMap,
'changeable_map' => $changeableMap,
'current_url_id' => $currentUrlId,
'can_toggle_multiurl_setting' => $canToggleMultiUrlSetting,
]);
}

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

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

foreach ($settingsWithTemplate as $s) {
if ($s->getValueTemplate()) {
$templateMap[$s->getVariable()] = $s->getValueTemplate()->getId();
}
}

// MultiURL changeable flags: read from main URL (ID = 1) only
$changeableMap = [];
$mainUrlRows = $settingsRepo->createQueryBuilder('sc')
->join('sc.url', 'u')
->andWhere('u.id = :mainId')
->setParameter('mainId', 1)
->getQuery()
->getResult();

foreach ($mainUrlRows as $row) {
if ($row instanceof SettingsCurrent) {
$changeableMap[$row->getVariable()] = $row->getAccessUrlChangeable();
}
}

$platform = [
'server_type' => (string) $manager->getSetting('platform.server_type', true),
];

$currentUrlId = $url->getId();
// Only platform admins on the main URL can toggle the MultiURL flag.
$canToggleMultiUrlSetting = $this->isGranted('ROLE_ADMIN') && 1 === $currentUrlId;

return $this->render('@ChamiloCore/Admin/Settings/default.html.twig', [
'schemas' => $schemas,
'settings' => $settings,
Expand All @@ -238,6 +352,9 @@ public function updateSetting(Request $request, AccessUrlHelper $accessUrlHelper
'ordered_namespaces' => $ordered,
'namespace_labels' => $labelMap,
'platform' => $platform,
'changeable_map' => $changeableMap,
'current_url_id' => $currentUrlId,
'can_toggle_multiurl_setting' => $canToggleMultiUrlSetting,
]);
}

Expand Down
112 changes: 99 additions & 13 deletions src/CoreBundle/Resources/views/Admin/Settings/default.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@
{% for field in form %}
{% set fieldName = field.vars.name %}
{% set isHidden = 'hidden' in field.vars.block_prefixes %}
{% set isDisabledOnSubUrl =
current_url_id is defined
and current_url_id != 1
and changeable_map is defined
and changeable_map[fieldName] is defined
and not changeable_map[fieldName]
%}

{% if isHidden %}
{{ form_widget(field) }}
Expand All @@ -73,6 +80,7 @@
</h3>

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


{# Debug icon on test servers (keep original behavior) #}
{% set serverType = platform.server_type ?? (settings.platform.server_type ?? null) %}
{% if serverType == 'test' %}
<span
class="ml-3 text-gray-60 text-lg flex items-center cursor-default debug-setting-icon"
<span
class="ml-1 text-gray-60 text-lg flex items-center cursor-default debug-setting-icon"
title="{{ namespace ~ '.' ~ fieldName }}"
role="img"
aria-hidden="true"
data-field-id="{{ field.vars.id }}"
data-namespace="{{ namespace }}"
data-field-name="{{ fieldName }}"
{% if template_map[fieldName] is defined %} data-template-id="{{ template_map[fieldName] }}" {% endif %}
>
<i class="mdi mdi-alphabetical course-tool__icon bg-primary-bgdisabled text-base" aria-hidden="true"></i>
</span>
{% if template_map[fieldName] is defined %}
data-template-id="{{ template_map[fieldName] }}"
{% endif %}
>
<i class="mdi mdi-alphabetical course-tool__icon bg-primary-bgdisabled text-base" aria-hidden="true"></i>
</span>
{% endif %}

{# MultiURL changeable eye #}
{% if changeable_map is defined %}
{% set changeable = changeable_map[fieldName] is defined ? changeable_map[fieldName] : 1 %}

{% if can_toggle_multiurl_setting %}
{# Main URL + admin: clickable toggle #}
<button
type="button"
class="ml-1 toggle-changeable flex items-center justify-center"
data-variable="{{ fieldName }}"
data-status="{{ changeable }}"
title="{{ changeable ? 'Setting is editable on sub-URLs'|trans : 'Setting locked for sub-URLs'|trans }}"
>
{% if changeable %}
<i class="mdi mdi-eye text-primary text-xl"></i>
{% else %}
<i class="mdi mdi-eye-off text-gray-50 text-xl"></i>
{% endif %}
</button>
{% else %}
{# Other URLs or non-privileged admins: read-only indicator #}
<span
class="ml-1 text-gray-50 text-lg flex items-center cursor-default"
title="{{ changeable ? 'Setting is editable on sub-URLs'|trans : 'Setting locked for sub-URLs'|trans }}"
>
{% if changeable %}
<i class="mdi mdi-eye text-gray-50 text-xl"></i>
{% else %}
<i class="mdi mdi-eye-off text-gray-50 text-xl"></i>
{% endif %}
</span>
{% endif %}
{% endif %}
</div>
</div>

Expand All @@ -110,19 +153,25 @@

<div class="flex flex-col gap-2">
{% for child in field %}
{% set childAttr = isDisabledOnSubUrl ? { disabled: 'disabled' } : {} %}
<label class="inline-flex items-center gap-2">
{{ form_widget(child) }}
{{ form_widget(child, { attr: childAttr }) }}
<span>{{ child.vars.label }}</span>
</label>
{% endfor %}
</div>

{% else %}
{{ form_widget(field, {
attr: {
class: 'w-full rounded border border-gray-25 focus:border-primary focus:ring focus:ring-primary/30 transition'
}
}) }}
{# Build base attributes and add disabled only when needed #}
{% set baseAttr = {
class: 'w-full rounded border border-gray-25 focus:border-primary focus:ring focus:ring-primary/30 transition'
} %}
{% set widgetAttr = isDisabledOnSubUrl
? baseAttr|merge({ disabled: 'disabled' })
: baseAttr
%}

{{ form_widget(field, { attr: widgetAttr }) }}
{% endif %}
</div>

Expand Down Expand Up @@ -203,6 +252,43 @@
document.getElementById('jsonTemplateModal').classList.add('hidden');
});
});

// MultiURL eye toggle: send AJAX to toggle access_url_changeable
document.addEventListener('click', function (e) {
const btn = e.target.closest('.toggle-changeable');
if (!btn) {
return;
}

const variable = btn.dataset.variable;
const current = btn.dataset.status === '1' ? 1 : 0;
const next = current === 1 ? 0 : 1;

fetch('/admin/settings/toggle_changeable', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ variable: variable, status: next })
})
.then(r => r.json())
.then(data => {
if (data.result === 1) {
btn.dataset.status = String(next);
btn.innerHTML = next
? '<i class="mdi mdi-eye text-primary text-xl"></i>'
: '<i class="mdi mdi-eye-off text-gray-50 text-xl"></i>';
} else if (data.error) {
console.error('Failed to update changeable state:', data.error);
alert(data.error);
} else {
console.error('Failed to update changeable state:', data);
}
})
.catch(err => {
console.error('AJAX error:', err);
});
});
</script>
{% endblock %}
{% block javascripts %}
Expand Down
Loading
Loading