{"elements":[{"id":7012,"title":"Sistema Menu","body":"<section class=\"da-section\">\n  <div class=\"container\">\n    <h2>Gestione visuale dei menu<\/h2>\n    <p class=\"lead\">In DressAPI, creare e organizzare i menu di navigazione del sito &egrave; un'operazione completamente visuale. Dal pannello di amministrazione puoi aggiungere voci, creare sottomenu a pi&ugrave; livelli, riordinare gli elementi con il drag-and-drop e controllare la visibilit&agrave; in base ai ruoli utente. Il sistema supporta anche megamenu e voci generate dinamicamente da query sul database.<\/p>\n    <div class=\"text-center my-4\"><img src=\"\/assets\/images\/menu_editor.png\" alt=\"Editor visuale dei menu\" class=\"img-fluid rounded shadow\" style=\"max-width: 80%;\"><\/div>\n\n    <h3 class=\"mt-4\">Struttura della tabella cmsmenu<\/h3>\n    <p>La tabella <code>cmsmenu<\/code> memorizza tutte le voci di menu in una singola tabella auto-referenziante:<\/p>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">Schema cmsmenu<\/div>\n<span class=\"kw\">CREATE TABLE<\/span> <span class=\"fn\">cmsmenu<\/span> (\n  <span class=\"str\">id<\/span>           <span class=\"type\">INT UNSIGNED<\/span> <span class=\"kw\">NOT NULL AUTO_INCREMENT<\/span>,\n  <span class=\"str\">name<\/span>         <span class=\"type\">VARCHAR(80)<\/span> <span class=\"kw\">NOT NULL<\/span>,\n  <span class=\"str\">icon<\/span>         <span class=\"type\">VARCHAR(80)<\/span> <span class=\"kw\">NOT NULL<\/span>,\n  <span class=\"str\">query<\/span>        <span class=\"type\">VARCHAR(500)<\/span> <span class=\"kw\">NOT NULL<\/span>,\n  <span class=\"str\">specialtype<\/span>  <span class=\"type\">ENUM('normal','megamenu')<\/span> <span class=\"kw\">DEFAULT<\/span> <span class=\"str\">'normal'<\/span>,\n  <span class=\"str\">id_cmsmenu<\/span>   <span class=\"type\">INT UNSIGNED<\/span> <span class=\"kw\">DEFAULT NULL<\/span>,\n  <span class=\"str\">priority<\/span>     <span class=\"type\">INT UNSIGNED<\/span> <span class=\"kw\">NOT NULL<\/span>,\n  <span class=\"str\">id__role<\/span>     <span class=\"type\">INT UNSIGNED<\/span> <span class=\"kw\">DEFAULT NULL<\/span>,\n  <span class=\"kw\">PRIMARY KEY<\/span> (<span class=\"str\">id<\/span>),\n  <span class=\"kw\">FOREIGN KEY<\/span> (<span class=\"str\">id__role<\/span>) <span class=\"kw\">REFERENCES<\/span> <span class=\"fn\">_role<\/span>(<span class=\"str\">id<\/span>),\n  <span class=\"kw\">FOREIGN KEY<\/span> (<span class=\"str\">id_cmsmenu<\/span>) <span class=\"kw\">REFERENCES<\/span> <span class=\"fn\">cmsmenu<\/span>(<span class=\"str\">id<\/span>)\n    <span class=\"kw\">ON DELETE CASCADE ON UPDATE CASCADE<\/span>\n);<\/div>\n\n    <div class=\"table-responsive mt-3\">\n      <table class=\"table table-striped\">\n        <thead class=\"table-dark\">\n          <tr><th>Campo<\/th><th>Tipo<\/th><th>Descrizione<\/th><\/tr>\n        <\/thead>\n        <tbody>\n          <tr><td><code>id<\/code><\/td><td>INT UNSIGNED<\/td><td>Chiave primaria, auto-incremento<\/td><\/tr>\n          <tr><td><code>name<\/code><\/td><td>VARCHAR(80)<\/td><td>Nome visualizzato della voce di menu<\/td><\/tr>\n          <tr><td><code>icon<\/code><\/td><td>VARCHAR(80)<\/td><td>Identificativo icona (es. <code>it-home<\/code>, <code>it-burger<\/code> per sprite SVG Bootstrap Italia)<\/td><\/tr>\n          <tr><td><code>query<\/code><\/td><td>VARCHAR(500)<\/td><td>URL, ancora, query speciale (<code>[[...]]<\/code>), o testo statico (<code>#text:...<\/code>)<\/td><\/tr>\n          <tr><td><code>specialtype<\/code><\/td><td>ENUM<\/td><td><code>normal<\/code> (dropdown standard) o <code>megamenu<\/code> (layout multi-colonna)<\/td><\/tr>\n          <tr><td><code>id_cmsmenu<\/code><\/td><td>INT UNSIGNED<\/td><td>FK auto-referenziante al menu padre. <code>NULL<\/code> per i gruppi di primo livello<\/td><\/tr>\n          <tr><td><code>priority<\/code><\/td><td>INT UNSIGNED<\/td><td>Ordine all'interno dello stesso padre. Valori pi&ugrave; bassi appaiono prima (multipli di 10)<\/td><\/tr>\n          <tr><td><code>id__role<\/code><\/td><td>INT UNSIGNED<\/td><td>FK a <code>_role.id<\/code>. <code>NULL<\/code> = visibile a tutti<\/td><\/tr>\n        <\/tbody>\n      <\/table>\n    <\/div>\n\n    <h3 class=\"mt-5\">Gerarchia del menu<\/h3>\n    <p>Il sistema menu usa una <strong>gerarchia a tre livelli<\/strong>:<\/p>\n    <ul>\n      <li><strong>Gruppi menu<\/strong> (livello 0): voci di primo livello con <code>id_cmsmenu IS NULL<\/code><\/li>\n      <li><strong>Sottomenu<\/strong> (livello 1): figli diretti di un gruppo<\/li>\n      <li><strong>Voci<\/strong> (livello 2): figli dei sottomenu (elementi foglia)<\/li>\n    <\/ul>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">Esempio gerarchia<\/div>\nMainMenu (id=3, id_cmsmenu=NULL)\n  +-- Home (id=300, query=\"\/\", priority=1)\n  +-- Amministrazione (id=310, query=\"#\", priority=10)\n  |     +-- Sindaco e Giunta (id=311, priority=10)\n  |     +-- Consiglio Comunale (id=312, priority=20)\n  |     +-- Aree e Uffici (id=313, priority=30)\n  +-- Servizi (id=320, query=\"#\", priority=20)\n        +-- Pagamenti (id=321, priority=10)<\/div>\n    <p class=\"mt-2\">Un sottomenu con <code>query=\"#\"<\/code> funge da genitore dropdown senza essere un link navigabile. L'eliminazione di un padre si propaga ai figli grazie a <code>ON DELETE CASCADE<\/code>.<\/p>\n\n    <h3 class=\"mt-5\">7 Gruppi menu predefiniti<\/h3>\n    <div class=\"table-responsive\">\n      <table class=\"table table-striped\">\n        <thead class=\"table-dark\">\n          <tr><th>ID<\/th><th>Nome<\/th><th>Posizione<\/th><th>Scopo<\/th><\/tr>\n        <\/thead>\n        <tbody>\n          <tr><td>1<\/td><td><strong>TopLeftMenu<\/strong><\/td><td>Header slim, lato sinistro<\/td><td>Navigazione secondaria (link all'organizzazione)<\/td><\/tr>\n          <tr><td>2<\/td><td><strong>TopRightMenu<\/strong><\/td><td>Header slim, lato destro<\/td><td>Strumenti admin (Traduttore, ACL, Scheduler, Menu, Tabelle, Moduli, Utenti)<\/td><\/tr>\n          <tr><td>3<\/td><td><strong>MainMenu<\/strong><\/td><td>Navbar primaria<\/td><td>Navigazione principale del sito (Home, sezioni con dropdown)<\/td><\/tr>\n          <tr><td>4<\/td><td><strong>FooterMenu<\/strong><\/td><td>Footer, sezione superiore<\/td><td>Link a colonne nel footer; spesso usa query dinamiche <code>[[pages:...]]<\/code><\/td><\/tr>\n          <tr><td>5<\/td><td><strong>TextMenu<\/strong><\/td><td>Footer, sezione centrale<\/td><td>Contenuti testuali (indirizzi, testi legali via <code>#text:...<\/code>)<\/td><\/tr>\n          <tr><td>6<\/td><td><strong>LegalMenu<\/strong><\/td><td>Footer, barra inferiore<\/td><td>Link legali (Media policy, Privacy, Sitemap)<\/td><\/tr>\n          <tr><td>7<\/td><td><strong>SocialMenu<\/strong><\/td><td>Barra social header + Footer<\/td><td>Link ai social media (Facebook, Instagram, YouTube)<\/td><\/tr>\n        <\/tbody>\n      <\/table>\n    <\/div>\n\n    <h3 class=\"mt-5\">Tipi di query speciali<\/h3>\n    <p>Il campo <code>query<\/code> supporta diverse sintassi speciali. Quando <strong>CMenuBuilder<\/strong> incontra una query racchiusa in <code>[[ ]]<\/code>, genera dinamicamente le voci del sottomenu.<\/p>\n\n    <h4 class=\"mt-3\"><code>[[table-list]]<\/code><\/h4>\n    <p>Genera una voce di sottomenu per ogni tabella del database accessibile all'utente corrente. Ogni voce generata punta a <code>\/admin\/table\/{nometabella}<\/code>.<\/p>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">Esempio SQL<\/div>\n<span class=\"cm\">-- Dropdown \"Tabelle\" visibile solo all'admin (id__role=1)<\/span>\n(209, 'Tables', 'it-piattaforme', '[[table-list]]', 'normal', 2, 70, 1)<\/div>\n\n    <h4 class=\"mt-3\"><code>[[module-list]]<\/code><\/h4>\n    <p>Genera una voce di sottomenu per ogni modulo registrato nella tabella <code>_module<\/code> e accessibile all'utente corrente. Ogni voce punta a <code>\/{nome_modulo}<\/code>.<\/p>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">Esempio SQL<\/div>\n<span class=\"cm\">-- Dropdown \"Moduli\" visibile solo all'admin<\/span>\n(7028, 'Modules', 'it-piattaforme', '[[module-list]]', 'normal', 2, 70, 1)<\/div>\n\n    <h4 class=\"mt-3\"><code>[[modulo:campo=valore]]<\/code><\/h4>\n    <p>Genera voci di sottomenu interrogando la tabella del modulo con condizioni di filtro. Le condizioni dopo i due punti diventano una clausola SQL <code>WHERE<\/code>. I risultati sono ordinati per <code>priority ASC, id ASC<\/code>.<\/p>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">Esempio: voci FooterMenu dinamiche<\/div>\n<span class=\"cm\">-- Genera voci dal modulo \"pages\" filtrate per gruppo<\/span>\n(401, 'Amministrazione', '', '[[pages:groupname=Amministrazione,visible=yes]]', 'normal', 4, 40, NULL)\n(402, 'Servizi',         '', '[[pages:groupname=Servizi,visible=yes]]',         'normal', 4, 50, NULL)<\/div>\n\n    <h4 class=\"mt-3\"><code>#text:...<\/code><\/h4>\n    <p>Il testo dopo <code>#text:<\/code> viene renderizzato direttamente nel footer come contenuto HTML, senza essere racchiuso in un link.<\/p>\n\n    <h4 class=\"mt-3\"><code>#<\/code> (Ancora \/ Genitore senza link)<\/h4>\n    <p>Un valore <code>query<\/code> di <code>#<\/code> indica un genitore di menu che non naviga da nessuna parte. Viene usato per i dropdown.<\/p>\n\n    <h3 class=\"mt-5\">Visibilit&agrave; basata sui ruoli<\/h3>\n    <p>La visibilit&agrave; &egrave; controllata dalla colonna <code>id__role<\/code> che referenzia la tabella <code>_role<\/code>:<\/p>\n    <ul>\n      <li><strong><code>id__role = NULL<\/code><\/strong>: visibile a <strong>tutti<\/strong>, inclusi gli utenti anonimi<\/li>\n      <li><strong><code>id__role = {id_ruolo}<\/code><\/strong>: visibile <strong>solo<\/strong> agli utenti con quel ruolo assegnato nella tabella <code>_user_role<\/code><\/li>\n    <\/ul>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">Query di filtro per ruolo<\/div>\n<span class=\"kw\">SELECT<\/span> * <span class=\"kw\">FROM<\/span> <span class=\"fn\">cmsmenu<\/span>\n<span class=\"kw\">WHERE<\/span> <span class=\"str\">id_cmsmenu<\/span> <span class=\"kw\">IS NULL<\/span>\n  <span class=\"kw\">AND<\/span> (<span class=\"str\">id__role<\/span> <span class=\"kw\">IS NULL OR<\/span> <span class=\"str\">id__role<\/span> <span class=\"kw\">IN<\/span> (1, 2, 3))\n<span class=\"kw\">ORDER BY<\/span> <span class=\"str\">priority<\/span> <span class=\"kw\">ASC<\/span><\/div>\n    <p class=\"mt-2\">Per gli utenti anonimi (non autenticati), il modello applica solo <code>id__role IS NULL<\/code>, rendendo visibili esclusivamente le voci pubbliche.<\/p>\n\n    <h3 class=\"mt-5\">Supporto Megamenu<\/h3>\n    <p>Il campo <code>specialtype<\/code> determina come il template header del CMS renderizza una voce del MainMenu:<\/p>\n    <ul>\n      <li><strong><code>normal<\/code><\/strong> (predefinito): dropdown Bootstrap standard con <code>&lt;ul class=\"link-list\"&gt;<\/code><\/li>\n      <li><strong><code>megamenu<\/code><\/strong>: layout multi-colonna. Ogni sottomenu diventa una colonna con intestazione e lista di link<\/li>\n    <\/ul>\n    <p>Con i megamenu la gerarchia &egrave; effettivamente a tre livelli: il genitore megamenu, le intestazioni di colonna (livello 2), e i link effettivi (livello 3).<\/p>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">Creare una voce megamenu<\/div>\n<span class=\"kw\">INSERT INTO<\/span> <span class=\"fn\">cmsmenu<\/span> (<span class=\"str\">name<\/span>, <span class=\"str\">icon<\/span>, <span class=\"str\">query<\/span>, <span class=\"str\">specialtype<\/span>, <span class=\"str\">id_cmsmenu<\/span>, <span class=\"str\">priority<\/span>, <span class=\"str\">id__role<\/span>)\n<span class=\"kw\">VALUES<\/span> ('MegaSezione', 'it-pa', '#', 'megamenu', 3, 10, NULL);<\/div>\n    <p class=\"mt-2\">Il template controlla <code>$voice['specialtype'] == 'megamenu'<\/code> per decidere quale layout usare.<\/p>\n\n    <h3 class=\"mt-5\">Riordinamento Drag-and-Drop<\/h3>\n    <p>L'interfaccia di amministrazione menu fornisce un editor drag-and-drop visuale basato su <strong>SortableJS<\/strong>.<\/p>\n    <ul>\n      <li><strong>Template<\/strong>: <code>cms\/plugins\/menu\/views\/bi5\/page-frames\/EditForm.tmpl.php<\/code><\/li>\n      <li><strong>Controller<\/strong>: <code>core\/src\/modules\/menu\/controller\/CMenuController.php<\/code><\/li>\n    <\/ul>\n    <p>L'editor divide la pagina in due colonne: a sinistra i campi del gruppo menu con guida alla sintassi, a destra l'albero interattivo SortableJS.<\/p>\n    <p>Ci sono due istanze SortableJS: una per il livello principale (sottomenu, sfondo arancione) e una per il livello interno (voci, sfondo blu). Ogni voce ha handle di trascinamento, nome modificabile inline (<code>contenteditable<\/code>), pannello collassabile per icona\/query\/tipo\/ruolo, e pulsanti azione.<\/p>\n\n    <h4 class=\"mt-3\">ReadTree(): raccolta dello stato<\/h4>\n    <p>Quando l'utente clicca \"Salva\", la funzione JavaScript <code>ReadTree()<\/code> attraversa il DOM per costruire una rappresentazione JSON dell'albero corrente:<\/p>\n    <div class=\"da-snippet\">\n      <div class=\"da-snippet-label\">ReadTree() - serializzazione<\/div>\n<span class=\"kw\">function<\/span> <span class=\"fn\">ReadTree<\/span>()\n{\n    <span class=\"kw\">let<\/span> <span class=\"str\">menus<\/span> = document.querySelectorAll('.js-sortable-connected > li');\n    <span class=\"kw\">let<\/span> <span class=\"str\">output<\/span> = [];\n    <span class=\"cm\">\/\/ Per ogni sottomenu: raccoglie id, name, icon, query, specialtype, id__role<\/span>\n    <span class=\"cm\">\/\/ Per ogni figlio (sons): raccoglie gli stessi campi<\/span>\n    document.getElementsByName('menu_tree')[0].value = JSON.stringify(output);\n    <span class=\"kw\">return true<\/span>;\n}<\/div>\n\n    <h4 class=\"mt-3\">CMenuController::DoAfterOperation()<\/h4>\n    <p>Alla sottomissione del form, il metodo processa il JSON <code>menu_tree<\/code>:<\/p>\n    <ul>\n      <li>Voci con <code>id<\/code> numerico: <strong>aggiorna<\/strong> priority, name, icon, query, id__role<\/li>\n      <li>Voci con <code>id<\/code> non numerico (es. <code>new_sub_menu_1<\/code>): <strong>inserisce<\/strong> come nuova voce<\/li>\n      <li>Voci marcate per la rimozione (campi nascosti <code>voice_to_remove[]<\/code> e <code>sub_to_remove[]<\/code>): <strong>elimina<\/strong><\/li>\n      <li>Ricalcolo priorit&agrave;: <code>(posizione + 1) * 10<\/code> per ogni voce<\/li>\n    <\/ul>\n\n    <h3 class=\"mt-5\">Rendering del menu nei template<\/h3>\n    <p><strong>CMenuBuilder<\/strong> assembla l'albero completo e lo rende disponibile ai template come <code>$this-&gt;data['menu']<\/code>.<\/p>\n    <p>Il ciclo di vita: <strong>BuildMenuTree()<\/strong> interroga ricorsivamente le voci filtrate per ruolo e risolve le query speciali, poi <strong>PostProcessMenu()<\/strong> aggiunge i flag <code>is_active<\/code> e <code>is_external<\/code> su ogni voce.<\/p>\n\n    <h4 class=\"mt-3\">Template Header<\/h4>\n    <p>Il template Header renderizza TopLeftMenu, TopRightMenu, SocialMenu e MainMenu. Per il MainMenu, controlla <code>specialtype<\/code> per scegliere il layout dropdown normale o megamenu.<\/p>\n\n    <h4 class=\"mt-3\">Template Footer<\/h4>\n    <p>Il template Footer renderizza FooterMenu (colonne con link), TextMenu (blocchi di testo con <code>#text:...<\/code>), SocialMenu (icone social) e LegalMenu (barra orizzontale inferiore).<\/p>\n\n    <h4 class=\"mt-3\">Funzioni helper<\/h4>\n    <p>Il file <code>menu-helpers.php<\/code> fornisce utilit&agrave; condivise tra Header e Footer:<\/p>\n    <div class=\"table-responsive\">\n      <table class=\"table table-striped\">\n        <thead class=\"table-dark\">\n          <tr><th>Funzione<\/th><th>Descrizione<\/th><\/tr>\n        <\/thead>\n        <tbody>\n          <tr><td><code>MenuTitle($voice)<\/code><\/td><td>Restituisce il titolo tradotto e formattato della voce<\/td><\/tr>\n          <tr><td><code>IsExternalUrl($url)<\/code><\/td><td>Restituisce <code>true<\/code> se l'URL &egrave; esterno al dominio<\/td><\/tr>\n          <tr><td><code>ExternalTarget($url)<\/code><\/td><td>Restituisce <code>target=\"_blank\"<\/code> per URL esterni<\/td><\/tr>\n          <tr><td><code>MenuActiveClass($current, $item)<\/code><\/td><td>Restituisce la classe CSS <code>active<\/code> se l'URL corrente inizia con l'URL della voce<\/td><\/tr>\n        <\/tbody>\n      <\/table>\n    <\/div>\n\n    <h3 class=\"mt-5\">Cache<\/h3>\n    <p><strong>CMenuBuilder<\/strong> supporta il caching dell'albero menu per evitare query ripetute al database:<\/p>\n    <ul>\n      <li><strong>Chiave cache<\/strong>: <code>menu_uid_{id_utente}<\/code> &mdash; unica per utente poich&eacute; ruoli diversi comportano visibilit&agrave; diverse<\/li>\n      <li><strong>Utenti anonimi<\/strong>: la cache viene controllata per prima; tutti gli anonimi vedono lo stesso menu<\/li>\n      <li><strong>Utenti autenticati<\/strong>: l'albero viene sempre ricostruito e poi messo in cache<\/li>\n      <li><strong>PostProcessMenu<\/strong> viene sempre applicato su una copia, cos&igrave; la cache base non viene modificata<\/li>\n      <li><strong>Cache moduli<\/strong>: <code>GetAvailableModules()<\/code> usa una cache separata con chiave <code>all_db_modules<\/code><\/li>\n    <\/ul>\n  <\/div>\n<\/section>\n\n<section class=\"da-section text-center\">\n  <a href=\"\/tutorials\" class=\"btn btn-outline-primary\">&larr; Torna ai Tutorial<\/a>\n<\/section>","description":"Menu gerarchici con drag-and-drop","meta_keywords":"","visible":"yes","status":"public","creation_date":"0000-00-00","id__user":1,"priority":0,"groupname":"TUTORIAL"}],"module":"pages","permissions":{"can_read":true},"main_item_title":"title","related_tables":[{"name":"Newsonpage","module":"newsonpage","FK":"id_cmspage","permissions":[]}],"page_info":{"table":"","key":"id","url":"\/pages","url_list":"\/pages","filters":{"id":["=","7012"]},"pagination":{"total_items":23,"page":1,"total_pages":2,"step":1,"items_per_page":20,"url_prev":"\/pages","url_next":"\/pages\/p\/2","url_tmpl":"\/pages\/p\/[[p]]","url_tmpl_with_order":"\/pages\/p\/[[p]]\/ob\/[[ob]]"},"order":{"field":"id","type":"DESC","url_tmpl":"\/pages\/p\/[[p]]"},"module":{"title":"Pages","description":"List of pages","name":"pages"},"current":{"title":"Pages","description":"List of pages","name":"pages","url":"\/tutorials\/menu"}},"components":{"alert":{"alert1":[{"type":"warning","message":"OK COMPONENT!"},{"type":"danger","message":"ERROR COMPONENT!"}]}},"messages":[]}