diff --git a/src/CoreBundle/Controller/Admin/SettingsController.php b/src/CoreBundle/Controller/Admin/SettingsController.php index 2300967121b..219bd7cffa9 100644 --- a/src/CoreBundle/Controller/Admin/SettingsController.php +++ b/src/CoreBundle/Controller/Admin/SettingsController.php @@ -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')] @@ -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, @@ -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) { @@ -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; } @@ -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')] @@ -217,6 +310,7 @@ 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) { @@ -224,10 +318,30 @@ public function updateSetting(Request $request, AccessUrlHelper $accessUrlHelper $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, @@ -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, ]); } diff --git a/src/CoreBundle/Resources/views/Admin/Settings/default.html.twig b/src/CoreBundle/Resources/views/Admin/Settings/default.html.twig index cf699afce2b..cd960cc604c 100644 --- a/src/CoreBundle/Resources/views/Admin/Settings/default.html.twig +++ b/src/CoreBundle/Resources/views/Admin/Settings/default.html.twig @@ -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) }} @@ -73,6 +80,7 @@
+ {# Optional JSON template helper link #} {% if template_map[fieldName] is defined %} {% endif %} - + {# Debug icon on test servers (keep original behavior) #} {% set serverType = platform.server_type ?? (settings.platform.server_type ?? null) %} {% if serverType == 'test' %} - + {% if template_map[fieldName] is defined %} + data-template-id="{{ template_map[fieldName] }}" + {% endif %} + > + + {% 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 #} + + {% else %} + {# Other URLs or non-privileged admins: read-only indicator #} + + {% if changeable %} + + {% else %} + + {% endif %} + + {% endif %} + {% endif %}
@@ -110,19 +153,25 @@
{% for child in field %} + {% set childAttr = isDisabledOnSubUrl ? { disabled: 'disabled' } : {} %} {% endfor %}
{% 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 %} @@ -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 + ? '' + : ''; + } 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); + }); + }); {% endblock %} {% block javascripts %} diff --git a/src/CoreBundle/Resources/views/Admin/Settings/search.html.twig b/src/CoreBundle/Resources/views/Admin/Settings/search.html.twig index 6d2d82ca6e3..65127070ae6 100644 --- a/src/CoreBundle/Resources/views/Admin/Settings/search.html.twig +++ b/src/CoreBundle/Resources/views/Admin/Settings/search.html.twig @@ -52,6 +52,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 changeable_map[fieldName] == 0 + %} {% if isHidden %} {{ form_widget(field) }} {% else %} @@ -59,23 +66,61 @@

{{ field.vars.label|trans }}

- {# Optional JSON template helper link #} - {% set templateId = null %} - {% if template_map is defined and template_map[fieldName] is defined %} - {% set templateId = template_map[fieldName] %} - {% elseif template_map_by_category is defined - and template_map_by_category[category] is defined - and template_map_by_category[category][fieldName] is defined %} - {% set templateId = template_map_by_category[category][fieldName] %} - {% endif %} +
+ {# Optional JSON template helper link #} + {% set templateId = null %} + {% if template_map is defined and template_map[fieldName] is defined %} + {% set templateId = template_map[fieldName] %} + {% elseif template_map_by_category is defined + and template_map_by_category[category] is defined + and template_map_by_category[category][fieldName] is defined %} + {% set templateId = template_map_by_category[category][fieldName] %} + {% endif %} - {% if templateId %} - - - {{ 'Show JSON Template'|trans }} - - {% endif %} + {% if templateId %} + + + {{ 'Show JSON Template'|trans }} + + {% endif %} + + {# MultiURL changeable eye on search results #} + {% 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 #} + + {% else %} + {# Other URLs or non-privileged admins: read-only indicator #} + + {% if changeable %} + + {% else %} + + {% endif %} + + {% endif %} + {% endif %} +
@@ -89,16 +134,24 @@ {% if (field.vars.name in verticalChoiceFields) and isExpanded and isMultiple %}
{% for child in field %} + {% set childAttr = isDisabledOnSubUrl ? { disabled: 'disabled' } : {} %} {% endfor %}
{% else %} - {{ form_widget(field, { attr: { + {# Build base attributes and add disabled only when needed #} + {% set baseAttr = { class: 'w-full max-w-none 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 %}
@@ -198,5 +251,38 @@ if (e.key === 'Escape') closeModal(); }); })(); + + // MultiURL eye toggle on search results + 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 ? 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 + ? '' + : ''; + } 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)); + }); {% endblock %} diff --git a/src/CoreBundle/Settings/SettingsManager.php b/src/CoreBundle/Settings/SettingsManager.php index 7bcacac551e..ffccf353090 100644 --- a/src/CoreBundle/Settings/SettingsManager.php +++ b/src/CoreBundle/Settings/SettingsManager.php @@ -288,20 +288,24 @@ public function update(SettingsInterface $settings): void $raw = $settings->getParameters(); $raw = $this->normalizeNullsBeforeResolve($raw, $settingsBuilder); $parameters = $settingsBuilder->resolve($raw); - // Transform value. Example array to string using transformer. Example: - // 1. Setting "tool_visible_by_default_at_creation" it's a multiple select - // 2. Is defined as an array in class DocumentSettingsSchema - // 3. Add transformer for that variable "ArrayToIdentifierTransformer" - // 4. Here we recover the transformer and convert the array to string + foreach ($parameters as $parameter => $value) { $parameters[$parameter] = $this->transformToString($value); } $settings->setParameters($parameters); $category = $this->convertServiceToNameSpace($settings->getSchemaAlias()); - $persistedParameters = $this->repository->findBy([ + + // Restrict lookup to current URL so we do not override settings from other URLs. + $criteria = [ 'category' => $category, - ]); + ]; + + if (null !== $this->url) { + $criteria['url'] = $this->url; + } + + $persistedParameters = $this->repository->findBy($criteria); $persistedParametersMap = []; @@ -314,22 +318,18 @@ public function update(SettingsInterface $settings): void $simpleCategoryName = str_replace('chamilo_core.settings.', '', $namespace); foreach ($parameters as $name => $value) { + // MultiURL: respect access_url_changeable defined on main URL. + if (!$this->isSettingChangeableForCurrentUrl($simpleCategoryName, $name)) { + continue; + } + if (isset($persistedParametersMap[$name])) { $parameter = $persistedParametersMap[$name]; $parameter->setSelectedValue($value); $parameter->setCategory($simpleCategoryName); $this->manager->persist($parameter); } else { - $parameter = (new SettingsCurrent()) - ->setVariable($name) - ->setCategory($simpleCategoryName) - ->setTitle($name) - ->setSelectedValue($value) - ->setUrl($url) - ->setAccessUrlChangeable(1) - ->setAccessUrlLocked(1) - ; - + $parameter = $this->createSettingForCurrentUrl($simpleCategoryName, $name, $value); $this->manager->persist($parameter); } } @@ -352,18 +352,21 @@ public function save(SettingsInterface $settings): void $raw = $settings->getParameters(); $raw = $this->normalizeNullsBeforeResolve($raw, $settingsBuilder); $parameters = $settingsBuilder->resolve($raw); - // Transform value. Example array to string using transformer. Example: - // 1. Setting "tool_visible_by_default_at_creation" it's a multiple select - // 2. Is defined as an array in class DocumentSettingsSchema - // 3. Add transformer for that variable "ArrayToIdentifierTransformer" - // 4. Here we recover the transformer and convert the array to string + foreach ($parameters as $parameter => $value) { $parameters[$parameter] = $this->transformToString($value); } $settings->setParameters($parameters); - $persistedParameters = $this->repository->findBy([ + $criteria = [ 'category' => $this->convertServiceToNameSpace($settings->getSchemaAlias()), - ]); + ]; + + // Limit to current URL so we do not write across all URLs. + if (null !== $this->url) { + $criteria['url'] = $this->url; + } + + $persistedParameters = $this->repository->findBy($criteria); $persistedParametersMap = []; foreach ($persistedParameters as $parameter) { $persistedParametersMap[$parameter->getVariable()] = $parameter; @@ -373,20 +376,16 @@ public function save(SettingsInterface $settings): void $simpleCategoryName = str_replace('chamilo_core.settings.', '', $namespace); foreach ($parameters as $name => $value) { + // MultiURL: respect access_url_changeable defined on main URL. + if (!$this->isSettingChangeableForCurrentUrl($simpleCategoryName, $name)) { + continue; + } + if (isset($persistedParametersMap[$name])) { $parameter = $persistedParametersMap[$name]; $parameter->setSelectedValue($value); } else { - $parameter = (new SettingsCurrent()) - ->setVariable($name) - ->setCategory($simpleCategoryName) - ->setTitle($name) - ->setSelectedValue($value) - ->setUrl($url) - ->setAccessUrlChangeable(1) - ->setAccessUrlLocked(1) - ; - + $parameter = $this->createSettingForCurrentUrl($simpleCategoryName, $name, $value); $this->manager->persist($parameter); } } @@ -399,11 +398,18 @@ public function save(SettingsInterface $settings): void */ public function getParametersFromKeywordOrderedByCategory($keyword): array { - $query = $this->repository->createQueryBuilder('s') + $qb = $this->repository->createQueryBuilder('s') ->where('s.variable LIKE :keyword OR s.title LIKE :keyword') - ->setParameter('keyword', "%{$keyword}%") - ; - $parametersFromDb = $query->getQuery()->getResult(); + ->setParameter('keyword', "%{$keyword}%"); + + // Restrict search to current URL when available. + if (null !== $this->url) { + $qb + ->andWhere('s.url = :url') + ->setParameter('url', $this->url); + } + + $parametersFromDb = $qb->getQuery()->getResult(); $parameters = []; foreach ($parametersFromDb as $parameter) { @@ -443,13 +449,24 @@ public function getParametersFromKeyword($namespace, $keyword = '', $returnObjec $criteria = [ 'category' => $namespace, ]; + + if (null !== $this->url) { + $criteria['url'] = $this->url; + } + $parametersFromDb = $this->repository->findBy($criteria); } else { - $query = $this->repository->createQueryBuilder('s') + $qb = $this->repository->createQueryBuilder('s') ->where('s.variable LIKE :keyword') - ->setParameter('keyword', "%{$keyword}%") - ; - $parametersFromDb = $query->getQuery()->getResult(); + ->setParameter('keyword', "%{$keyword}%"); + + if (null !== $this->url) { + $qb + ->andWhere('s.url = :url') + ->setParameter('url', $this->url); + } + + $parametersFromDb = $qb->getQuery()->getResult(); } if ($returnObjects) { @@ -503,7 +520,15 @@ private function validateSetting(string $name): string private function getParameters($namespace) { $parameters = []; - $category = $this->repository->findBy(['category' => $namespace]); + + $criteria = ['category' => $namespace]; + + // MultiURL: only parameters for current URL (if set). + if (null !== $this->url) { + $criteria['url'] = $this->url; + } + + $category = $this->repository->findBy($criteria); /** @var SettingsCurrent $parameter */ foreach ($category as $parameter) { @@ -516,7 +541,13 @@ private function getParameters($namespace) private function getAllParametersByCategory() { $parameters = []; - $all = $this->repository->findAll(); + + // MultiURL: either all parameters (single URL mode) or only for current URL. + if (null !== $this->url) { + $all = $this->repository->findBy(['url' => $this->url]); + } else { + $all = $this->repository->findAll(); + } /** @var SettingsCurrent $parameter */ foreach ($all as $parameter) { @@ -526,6 +557,123 @@ private function getAllParametersByCategory() return $parameters; } + /** + * Check if a setting is changeable for the current URL, using the + * access_url_changeable flag from the main URL (ID = 1). + */ + private function isSettingChangeableForCurrentUrl(string $category, string $variable): bool + { + // No URL bound: behave as legacy single-URL platform. + if (null === $this->url) { + return true; + } + + // Main URL can always edit settings. UI already restricts who can see/edit fields. + if (1 === $this->url->getId()) { + return true; + } + + // Try to load main (canonical) URL. + $mainUrl = $this->manager->getRepository(AccessUrl::class)->find(1); + if (null === $mainUrl) { + // If main URL is missing, fallback to permissive behaviour. + return true; + } + + /** @var SettingsCurrent|null $mainSetting */ + $mainSetting = $this->repository->findOneBy([ + 'category' => $category, + 'variable' => $variable, + 'url' => $mainUrl, + ]); + + if (null === $mainSetting) { + // If there is no canonical row, do not block changes. + return true; + } + + // When access_url_changeable is false/0 on main URL, + // secondary URLs must not override the value. + return (bool) $mainSetting->getAccessUrlChangeable(); + } + + private function createSettingForCurrentUrl(string $category, string $variable, string $value): SettingsCurrent + { + $url = $this->getUrl(); + + // Try to reuse metadata from main URL (ID = 1) as canonical definition. + $mainUrl = $this->manager->getRepository(AccessUrl::class)->find(1); + + $reference = null; + + if (null !== $mainUrl) { + $reference = $this->repository->findOneBy([ + 'category' => $category, + 'variable' => $variable, + 'url' => $mainUrl, + ]); + } + + if (!$reference instanceof SettingsCurrent) { + // Fallback: any existing row for this category + variable (legacy / no-URL case). + $reference = $this->repository->findOneBy([ + 'category' => $category, + 'variable' => $variable, + ]); + } + + $setting = (new SettingsCurrent()) + ->setVariable($variable) + ->setCategory($category) + ->setSelectedValue($value) + ->setUrl($url) + ; + + if ($reference instanceof SettingsCurrent) { + // Copy descriptive metadata so the new URL row behaves like the canonical one. + $setting->setTitle($reference->getTitle()); + + // These fields may or may not exist in the entity in Chamilo 2, + // so we check for method existence to stay safe. + if (method_exists($setting, 'setType') && method_exists($reference, 'getType')) { + $setting->setType($reference->getType()); + } + + if (method_exists($setting, 'setComment') && method_exists($reference, 'getComment')) { + $setting->setComment($reference->getComment()); + } + + if (method_exists($setting, 'setScope') && method_exists($reference, 'getScope')) { + $setting->setScope($reference->getScope()); + } + + if (method_exists($setting, 'setSubkey') && method_exists($reference, 'getSubkey')) { + $setting->setSubkey($reference->getSubkey()); + } + + if (method_exists($setting, 'setSubkeytext') && method_exists($reference, 'getSubkeytext')) { + $setting->setSubkeytext($reference->getSubkeytext()); + } + + // Copy flags and template; the "changeable" flag is still interpreted from main URL. + $setting->setAccessUrlChangeable($reference->getAccessUrlChangeable()); + $setting->setAccessUrlLocked($reference->getAccessUrlLocked()); + + if (method_exists($setting, 'setValueTemplate') && method_exists($reference, 'getValueTemplate')) { + $setting->setValueTemplate($reference->getValueTemplate()); + } + } else { + // Fallback: minimal metadata if no canonical definition was found. + $setting + ->setTitle($variable) + ->setAccessUrlChangeable(1) + ->setAccessUrlLocked(1) + ; + } + + return $setting; + } + /*private function transformParameters(SettingsBuilder $settingsBuilder, array $parameters) * { * $transformedParameters = $parameters;