From bfda75b29277806260c94ff337de42b959e67b4a Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Tue, 25 Aug 2026 18:00:08 +0200 Subject: [PATCH] =?UTF-8?q?[FIX]=20Organigramma:=20guardia=20anti-ciclo=20?= =?UTF-8?q?(client+server)=20=E2=80=94=20"Nuovo=20Ruolo"=20bloccava=20il?= =?UTF-8?q?=20sito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Segnalazione Simon: in Struttura interna/Organigramma il clic su "Nuovo Ruolo" blocca l'intero sito. Causa: né il tree builder server (OrgRoleController::buildTree, $attach) né il render client (organigramma.js orgNodeHtml) avevano una guardia anti-ciclo su parent_role_id → con una gerarchia ciclica la ricorsione va in loop infinito (hang del backend / freeze del render). - organigramma.js orgNodeHtml(node, seen): salta gli id già visti (rompe il ciclo); orgRender passa un Set condiviso. Live via bind-mount (buster 20260806). - OrgRoleController::buildTree: $attach porta la catena antenati e non ridiscende su un id già visto. ⚠️ Richiede reload php-fpm host (opcache) per andare live. Co-Authored-By: Claude Opus 4.8 (1M context) --- application/controllers/OrgRoleController.php | 19 +++++++++++++++---- public/js/organigramma.js | 11 ++++++++--- public/organigramma.html | 2 +- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/application/controllers/OrgRoleController.php b/application/controllers/OrgRoleController.php index dd68f06..ce87407 100644 --- a/application/controllers/OrgRoleController.php +++ b/application/controllers/OrgRoleController.php @@ -316,11 +316,22 @@ class OrgRoleController extends BaseController $roots[] = $r; } } - $attach = function (array $node) use (&$attach, $childrenByParent) { - $kids = $childrenByParent[(int) $node['id']] ?? []; - $node['children'] = array_map($attach, $kids); + // Guardia anti-ciclo: se parent_role_id formasse un ciclo (dato legacy/corrotto), + // la ricorsione andrebbe in loop infinito → hang del backend e "blocco" della pagina. + // Portiamo la catena di antenati e non ridiscendiamo su un id già visto. + $attach = function (array $node, array $ancestry) use (&$attach, $childrenByParent) { + $id = (int) $node['id']; + $ancestry[$id] = true; + $children = []; + foreach ($childrenByParent[$id] ?? [] as $kid) { + if (isset($ancestry[(int) $kid['id']])) { + continue; // ciclo: interrompi questo ramo invece di ricorrere all'infinito + } + $children[] = $attach($kid, $ancestry); + } + $node['children'] = $children; return $node; }; - return array_map($attach, $roots); + return array_map(static fn(array $r) => $attach($r, []), $roots); } } diff --git a/public/js/organigramma.js b/public/js/organigramma.js index 8178e00..770e014 100644 --- a/public/js/organigramma.js +++ b/public/js/organigramma.js @@ -61,10 +61,15 @@ function orgRender() { el('org-stat-vacant').textContent = vacant; stats.style.display = 'flex'; - tree.innerHTML = ORG.tree.map(orgNodeHtml).join(''); + tree.innerHTML = ORG.tree.map(n => orgNodeHtml(n, new Set())).join(''); } -function orgNodeHtml(node) { +function orgNodeHtml(node, seen) { + // Guardia anti-ciclo: se un id ricompare nella catena il render si fermerebbe in loop + // infinito (il "blocco del sito" segnalato). Rompiamo il ciclo invece di ricorrere. + seen = seen || new Set(); + if (!node || seen.has(node.id)) return ''; + seen.add(node.id); const gov = node.is_governance_body; const holder = node.holder_name ? '
👤 ' + esc(node.holder_name) + '
' @@ -72,7 +77,7 @@ function orgNodeHtml(node) { const desc = node.description ? '
' + esc(node.description) + '
' : ''; const govBadge = gov ? 'Governance · Art. 23' : ''; const children = (node.children && node.children.length) - ? '
' + node.children.map(orgNodeHtml).join('') + '
' + ? '
' + node.children.map(c => orgNodeHtml(c, seen)).join('') + '
' : ''; return '' + diff --git a/public/organigramma.html b/public/organigramma.html index 35e3b72..a926c0e 100644 --- a/public/organigramma.html +++ b/public/organigramma.html @@ -157,7 +157,7 @@ - +