# MEMORY.md - Stato del Plugin UrBackup
## Ultima modifica: 31/08/2026
## 31/08/2026 — Audit statico: fix 7 bug (solo UI/codice, nessuna modifica DB)
Audit statico (IA) + confronto contro sorgente C++ del backend UrBackup e core GLPI 11. Nessun bug di classe critico (crash/XSS/SQLi), ma diversi output errati corretti.
1. **Bug 1 — Livello log sempre vuoto** (`AssetTab.php:728`): l'API `livelog` restituisce `loglevel` (int 1=ERROR,2=WARNING,3=INFO,4=DEBUG), il codice leggeva `level`/`severity` (mai presenti). Aggiunto `formatLogLevel()` che mappa `loglevel`→stringa.
2. **Bug 2 — "No client logs available" fuorviante** (`AssetTab.php:733`): se `client_found=false` il client non è stato trovato per nome e i log NON vengono mai estratti. Ora mostra avviso "Client not found on UrBackup server. Check that the asset name matches the client name." Il matching resta per NOME client (confermato dall'utente come corretto).
3. **Bug 3 — JOIN IP invertito** (`Server.php` `batchLoadIps` ~1516 e `getAssetIp` ~1614): JOIN glpi_ipaddresses↔glpi_networknames generava `nn.items_id = ipa.id`; corretto a `nn.id = ipa.items_id` (con `ipa.itemtype='NetworkName'`), coerente con core `glpi/src/Report.php`. Colonna IP di Linked/Missing ora corretta.
4. **Bug 4 — Chiave cache sessione non univoca** (`AssetTab.php:374`): `$cache_key` includeva solo server id+nome client; ora `urbackup_data_{serverid}_{itemtype}_{items_id}`, evita cross-contaminazione tra asset omonimi.
5. **Bug 5 — Query Missing/Unlinked non filtrate per server** (`Server.php:1047` e `:1245`): aggiunto `WHERE plugin_urbackup_servers_id = server corrente` in `showUnlinkedClientsTab()` e `showMissingClientsTab()`, così asset collegati ad altri server non interferiscono.
6. **Bug 6 — server_test.ajax.php auth + CSRF** (`front/server_test.ajax.php`): sostituito `Profile::canCurrentUser(UPDATE)` (non entity-aware) con `$server->check($id, UPDATE)` in try/catch → 403 JSON; creato `public/js/urbackup.js` (registrato via `Hooks::ADD_JAVASCRIPT` in setup.php) che legge meta `glpi:csrf_token` e invia header `X-Glpi-Csrf-Token` su ogni POST AJAX del plugin (pre-requisito del listener GLPI 11 `CheckCsrfListener`).
7. **Bug 7 — dropdown_host.ajax.php info-disclosure** (`front/dropdown_host.ajax.php`): aggiunto check `Profile::canCurrentUser(READ)`.
8. **Traduzioni**: aggiunta la nuova stringa "Client not found on UrBackup server. Check that the asset name matches the client name." a it_IT/de_DE/en_GB `.po`, ricompilati i `.mo` (msgfmt). Versione header coerente (0.7.3). Changelog README aggiornato.
- Verifica IA: `php -l` OK su tutti i file; `git diff` autorevisione OK; `Hooks::ADD_JAVASCRIPT` verificato in `src/Glpi/Plugin/Hooks.php:60`; meta `glpi:csrf_token` verificato in `templates/layout/parts/head.html.twig:68`. **Verifica UI utente OBBLIGATORIA** (vedi checklist).
## 31/08/2026 — Fix campi API username/password non editabili in prod (solo UI)
- **Sintomo utente**: installato il plugin sul server produttivo, nel form Server non si vede il campo API username e il campo API password è bloccato (asterischi fissi non editabili), anche per l'utente che ha installato il plugin.
- **Causa**: divergenza nel controllo diritti. I front usavano `Profile::canCurrentUser()` (legge i diritti da `glpi_profilerights` su DB), ma `Server::showFormFields()` (form server) usava `Session::haveRight(self::$rightname, UPDATE)` per calcolare `$canEdit` (e idem `rawSearchOptions()` riga 308 e `showUnlinkedClientsTab()`/`showMissingClientsTab()` righe 1155/1222). `Session::haveRight` dipende dalla **cache dei diritti in sessione** (`$_SESSION['glpiactiveprofile']['rights']`), popolata al login. Dopo l'installazione/aggiornamento del plugin i diritti sono scritti in DB ma la **sessione corrente non viene ricostruita**, quindi `haveRight` resta false → ramo read-only: username stampato come testo (invisibile se vuoto), password `******` fissa.
- **Fix**: sostituito `Session::haveRight(self::$rightname, ...)` con `Profile::canCurrentUser(...)` in Server.php (righe ~308, ~447-449, ~1155, ~1222) e AssetTab.php (riga ~446, tab Asset), coerente col resto del plugin, legge dal DB e aggira la cache di sessione stantia.
- Nessun bump versione (nessuna modifica DB, regola 6); nessuna nuova stringa i18n.
- Verifica IA: `php -l` OK; git diff autorevisione OK. **Verifica UI utente OBBLIGATORIA**: aggiornare il file su prod, poi testare il form Server (API username editabile + API password campo input con placeholder).
## 25/08/2026 — Blocco "This asset hosts the UrBackup server" in evidenza (solo UI)
- **Richiesta utente**: mettere in evidenza con riquadro colorato il blocco host + nome del server UrBackup.
- **Modifiche**: `AssetTab.php::showHostServerBlock()` — classi dedicate `plugin-urbackup-host-card` / `-card-header` sul card Bootstrap e `plugin-urbackup-host-server-link` sui link server; `public/css/urbackup.css` — card con bordo 2px #1e6091, header blu #1e6091 testo bianco, body #eaf3fa, link server bold 1.05rem.
- Nessun bump versione (nessuna modifica DB, regola 6); nessuna nuova stringa i18n.
- Verifiche IA: `php -l` OK; git diff autorevisione OK. **Verifica visiva browser: CONFERMATA dall'utente il 25/08/2026** ("ok funziona tutto").
## 07/08/2026 — Fix dropdown "Hardware host" (0.7.3): elemento non compariva
- **Sintomo utente**: scelto il tipo host (es. Computer) nel form server, il dropdown degli elementi non compariva.
- **Causa 1 (405)**: `Ajax::updateItemOnSelectEvent` genera `$("#target").load(url, {params})` — jQuery `.load()` con data **oggetto** invia **POST**, mentre `front/dropdown_host.ajax.php` era GET-only (405) → div mai popolato. (Nota: il CSRF non era il blocco per `.load()`: il global `$(document).ajaxSend` in public/js/common.js aggiunge `X-Glpi-Csrf-Token` a OGNI POST AJAX, letto da `getAjaxCsrfToken()` sul ` `.)
- **Causa 2 (500)**: `Session::getMatchingActiveEntities()` richiede esattamente 1 argomento (è un **filtro** di entity, `@since 10.0.13`) → usare `Session::getActiveEntities()` (Session.php:2194).
- **Causa 3 (parametro sbagliato)**: in `Dropdown::show` il parametro per limitare le entità è **`entity`** (default -1, riga 133), NON `entity_restrict` (che è solo l'output serializzato del config, riga 287).
- **Causa 4 (scriptBlock NON emesso)**: `Html::scriptBlock()` in GLPI 11 **ritorna** la stringa (`return sprintf(...)`, Html.php:5145) — serve `echo Html::scriptBlock(...)`; senza echo lo script sparisce silenziosamente.
- **Fix**: JS inline nel form via `echo Html::scriptBlock(...)`: `$(document).on('change', '#dropdown_host_itemtype$rand', ...)` → `$.get(PLUGIN_URBACKUP_WEB_DIR.'/front/dropdown_host.ajax.php', {itemtype, value:0})` → `.html()` del div `urbackup_host_items$rand` (GET = bodyless → nessun check CSRF; jQuery esegue gli script inline iniettati, incluso il `$(function(){})` del config select2). Rimosso `use Ajax;` (non più usato).
- **Lezione GLPI 11 — i dropdown sono LAZY**: `Dropdown::show()` NON renderizza più le ``: genera un componente `Html::jsAjaxDropdown` (select2) con `params` JSON + `_idor_token`; i valori arrivano via **POST** a `/ajax/getDropdownValue.php` (`setupAjaxDropdown` in public/js/common.js: `type:"POST"` + `ajaxSend` globale per il CSRF). Il markup contiene solo l'option vuota finché l'utente non apre/cerca.
- **Lezione IDOR**: `Session::getNewIDORToken` NON ruota (accumula token validi 2h, `cleanIDORTokens`); `validateIDOR` confronta le chiavi salvate (itemtype, entity_restrict, displaywith, condition) con la POST. Il config JS serializzato da jQuery manda `entity_restrict` con la stessa forma salvata nel token → match. **Non è possibile testare via curl** la chiamata a getDropdownValue (la serializzazione jQuery degli array `[]` → `chiave=` diverge da `-d @file.json` → validateIDOR false) → test e2e in CLI: generare il dropdown con `Dropdown::show` reale, estrarre il config dal markup, ricostruire la POST → `validateIDOR: true` (provato con `/tmp/opencode/idor_e2e.php`; il "No active session" dopo è limite del bootstrap CLI, non del flusso).
- **Stato**: `php -l` OK; form contiene lo script `$.get` (verificato su server.form.php?id=1: 200); endpoint GET 200 con select+config+IDOR; validateIDOR true via `/tmp/opencode/idor_e2e.php`; verifica visiva in browser = **utente** (regola 7).
## 07/08/2026 — Bump 0.7.3 (link Server ↔ asset host hardware)
- **Richiesta utente**: collegare il "UrBackup Server" del plugin con il Computer / custom Asset dell'inventario che lo ospita (analogo a computer↔monitor) per trovare l'hardware fisico su cui gira il server.
- **Design (decisioni utente)**: (1) itemtype host = SOLO tipi con capacità attiva (`Config::getEnabledItemtypes()`); (2) blocco "Questo asset ospita il server UrBackup" SEMPRE visibile sull'asset host, anche se l'asset è anche client; (3) niente search option in questo giro (MVP).
- **Modifica DB**: `glpi_plugin_urbackup_servers` + `host_itemtype VARCHAR(255) DEFAULT NULL` (after comment) + `host_items_id INT UNSIGNED NOT NULL DEFAULT 0` (after host_itemtype) + KEY `host_asset` (host_itemtype,host_items_id) — NIENTE unique (più server per host ammessi, es. VM). Migrazione idempotente `plugin_urbackup_install_update_servers_table()` in install.php + empty.sql. `glpi_plugin_urbackup_serverassets` NON toccata (semantica CLIENT). → **bump 0.7.2 → 0.7.3** (regola 6).
- **Server.php**: riga "Hardware host" in `showFormFields()` dopo la riga Comments (`Dropdown::showItemTypes('host_itemtype', Config::getEnabledItemtypes(), ...)` + `echo Html::scriptBlock(...)` con `$.get` su `dropdown_host.ajax.php` al `change` del select tipo — vedi sezione Fix + div `urbackup_host_items$rand` pre-popolato con `Dropdown::show` con `entity => Session::getActiveEntities()` + link all'host); validazione in `prepareInputForUpdate()` (itemtype vuoto/items_id ≤0/class mancante/`!isItemtypeEnabled`/item inesistente → azzera entrambi); `getHostAsset(): ?CommonDBTM`; `getServersHostingAsset(string, int): array` con guard `$DB->fieldExists(self::getTable(),'host_itemtype')` (robustezza pre-migrazione — pattern di Config::getEnableComputer).
- **front/dropdown_host.ajax.php (NUOVO)**: GET only, `Session::checkLoginUser()`, validazione `class_exists` + `Config::isItemtypeEnabled`, risponde `Dropdown::show($itemtype, ['name'=>'host_items_id','value'=>..., 'entity'=>Session::getActiveEntities(), 'display_emptychoice'=>true])`.
- **AssetTab.php**: `showHostServerBlock(CommonDBTM $item)` chiamata per prima in `displayTabContentForItem()` — card "This asset hosts the UrBackup server" con lista link (`Server::getFormURLWithID`) dai server che puntano all'asset; sempre visibile se c'è un host.
- Locales: +2 msgid per lingua (182→184): `Hardware host` (it "Host hardware", de "Hardware-Host", en "Hardware host"), `This asset hosts the UrBackup server` (it "Questo asset ospita il server UrBackup", de "Dieses Asset hostet den UrBackup-Server", en identica); header .po → 0.7.3; `.mo` ricompilati (msgfmt --check OK, warning header pre-esistenti) + **cache traduzioni svuotata** (`files/_cache/*/translations/`).
- **Verifiche IA**: `php -l` OK (setup.php, install/install.php, Server.php, AssetTab.php, dropdown_host.ajax.php); test CLI `/tmp/opencode/urbackup_host_test.php` 10/10 PASS (prepareInputForUpdate: host non inviato/items_id=0/itemtype vuoto/Monitor non abilitato/Computer valido/items_id inesistente; getHostAsset null; getServersHostingAsset SKIP in attesa migrazione).
- **Stato attuale**: `glpi_plugins.version=0.7.3`, `state=1` (ACTIVE — update UI eseguito dall'utente il 07/08/2026, poi fix dropdown). **Verifica visiva checklist 0.7.3 CONFERMATA dall'utente il 25/08/2026** ("ok funziona tutto") — feature 0.7.3 CHIUSA.
- **Checklist UI utente 0.7.3 (verifica finale)**: (1) *Admin → Server* → aprire un server → "Hardware host": tipo → elemento (dropdown selezionabile) → Salva; (2) ricaricare il server: link host sotto la riga; (3) aprire l'asset host → card "Questo asset ospita il server UrBackup" con link al server; (4) verificare traduzioni it_IT/DE ("Host hardware", "Questo asset ospita il server UrBackup"); (5) smoke HTTP dropdown: GET `front/dropdown_host.ajax.php?itemtype=Computer` (sessione) → 200 select; itemtype=Monitor → rifiutato; senza sessione → 302/403.
- **Doc aggiornate**: README.md Changelog 0.7.3; GLPIDEV.md (schema v0.7.3 + caveat 21 con le API caveat dropdown lazy/IDOR + nota api_password cifrata); MEMORY.md (questo file).
## 06/08/2026 — Linguette interne tab sempre su una riga (desktop)
- **Richiesta utente**: le linguette "Stato Client / Azioni / Info-Log" risultavano a capo una sopra l'altra anche su monitor normale → fix CSS in `public/css/urbackup.css` (nessun bump: solo UI).
- **Indagine**: markup già `ul.nav.nav-tabs#urbackupTabs` (row di default); test headless chromium con CSS reali (tabler.min.css + css_glpi.min.css + urbackup.css) e wrapper reale (`card-tabs` + `#tabspanel` + `tab-content p-2 flex-grow-1 card`) mostrava affiancate → la causa nel browser utente non è riproducibile (probabile flex non applicato/cache/zoom); fix deterministico con regola dedicata.
- **Fix**: `.plugin-urbackup-inner-tabs .nav-tabs { display:flex; flex-direction:row; flex-wrap:nowrap; overflow-x:auto }` + `.nav-item { display:inline-flex; flex:0 0 auto }`; media query `max-width: 575.98px` → `flex-wrap:wrap` (comportamento mobile invariato, come richiesto dall'utente).
- **Verifica headless**: 1366px → wrap=nowrap, x=8/129/214 stessa y; 480px → wrap=wrap.
- **Iterazione 2 (sintomo: linguette su una riga ma estese per tutta la pagina, 3 bottoni oltre lo schermo = width:100%/flex-grow esterni)**: override deterministico con `!important` su `.nav-tabs` (display/flex-direction/flex-wrap) + `.nav-item` e `.nav-link` (`flex:0 0 auto !important`, `width:auto !important`, `max-width:none !important`, `white-space:nowrap`) — nessuna regola esterna può più allargarli. Verifica headless: w=118/82/103, flex=0 0 auto, xs=8/129/214, nessun overflow.
- **⚠️ Lezione test**: una pagina GLPI scaricata via curl e riaperta come `file://` NON applica i CSS (href relativi) → rendering con soli user-agent defaults (font "Times New Roman", ul block, li list-item, a inline) → test INVALIDO. Usare sempre pagine di test con href CSS assoluti (es. http://localhost/...).
## 06/08/2026 — RISOLTO: nuove chiavi i18n non tradotte nel browser (cache catalogo traduzioni GLPI)
- **Sintomo**: "API status" e "Client State" (chiavi nuove) restavano in inglese nel browser; le chiavi pre-esistenti ("Stato client", "Stato UrBackup") erano tradotte — i `.mo` erano corretti (msgunfmt/msgfmt --check OK).
- **Causa radice**: GLPI 11 `Session::loadLanguage()` (src/Session.php:816+) crea `$TRANSLATE` (anonima class che estende `Laminas\I18n\Translator\Translator`) con cache catalogo via `I18nCache` (src/Glpi/Cache/I18nCache.php) → `CacheManager::getTranslationsCacheInstance()` (CONTEXT_TRANSLATIONS) → `FilesystemAdapter` su `files/_cache//translations/` con **TTL 0 = infinito e NIENTE invalidazione su mtime**: dopo la ricompilazione dei `.mo`, il vecchio catalogo continua a essere servito finché la cache non viene svuotata. (Il path contiene l'hash della versione GLPI, es. `11.0.8-53eff695-production` — cambia a ogni bump di GLPI.)
- **Fix**: eliminare i file in `files/_cache//translations/` (12 file) → la cache si rigenera al primo caricamento con i `.mo` nuovi.
- **Regola operativa**: dopo OGNI ricompilazione dei `.mo` del plugin, svuotare `files/_cache/*/translations/` (non è un'azione UI — la regola 7 NON si applica; è manutenzione cache).
- **Verifica HTTP (login 06/08)**: GET `/ajax/common.tabs.php?_glpi_tab=GlpiPlugin%5CUrbackup%5CAssetTab%241&_itemtype=Computer&id=1` (sessione glpi/glpi) → "Stato Client" (tab nav), "Stato client" (header sezione), "Stato API" + badge "Connessione API OK". Nota: il parametro è `_itemtype` (non `_glpi_itemtype`); il token CSRF di login in GLPI 11.0.8 sta in ` ` e i campi form sono `login_name`/`login_password`; il POST va a `/front/login.php`.
## REGOLA PERMANENTE — Verifica UI da parte dell'utente
- **Tutte le azioni che l'utente normalmente esegue dalla UI di GLPI** (update/attivazione plugin, toggle di configurazione, link/unlink asset, test connessione, azioni backup) DEVONO essere eseguite dall'**utente** per verificarne il funzionamento reale — l'IA NON le esegue al suo posto (né via console `glpi:plugin:*`, né via HTTP/curl con sessione).
- **Per i bump di versione (regola 6)**: l'IA consegna codice + migrazioni idempotenti + bump `PLUGIN_URBACKUP_VERSION`, poi **l'utente** esegue l'update dalla UI: il plugin viene marcato `NOTUPDATED` e deattivato → *Configurazione → Plugin* → pulsante **"Aggiorna"** → **"Attiva"** → verifica della feature con la checklist fornita dall'IA.
- Verifiche che NON passano dalla UI (es. `php -l`, bootstrap CLI, query DB) restano compito dell'IA.
- ⚠️ Contesto storico: l'update 0.7.1 e 0.7.2 fu eseguito dall'IA via console (`glpi:plugin:install`/`activate`) — comportamento NON più ammesso da questa regola (istituita il 05/08/2026).
## REGOLA PERMANENTE — Gestione versione per modifiche DB (standard GLPI)
- **Da ora in poi**: OGNI modifica che tocca il database (nuove tabelle/colonne/indici, migrazioni di dati, cambi di default) DEVE essere accompagnata da un **incremento di `PLUGIN_URBACKUP_VERSION`** in `setup.php` (regola 6 di AGENTS.md).
- **Meccanismo GLPI verificato** (`src/Plugin.php`): `checkPluginState()` (~righe 909-933) confronta `plugin_version_urbackup()['version']` con `glpi_plugins.version`; se diverse → `state = NOTUPDATED` e plugin DEATTIVATO ("update process has to be launched").
- **Procedura update**: meccanismo standard GLPI `php bin/console glpi:plugin:install urbackup` (da `/var/www/glpi`) → `plugin_urbackup_install()` con migrazioni idempotenti (`new Migration(PLUGIN_URBACKUP_VERSION)`), poi `php bin/console glpi:plugin:activate urbackup` (l'install imposta `NOTACTIVATED`) — **MA l'esecuzione è dell'utente via UI (regola 7)**: *Configurazione → Plugin* → "Aggiorna" → "Attiva". I comandi console restano solo riferimento documentale.
- Aggiornare sempre anche: `README.md` (Changelog) e header `Project-Id-Version` dei `.po` (+ ricompilazione `.mo` + svuotamento cache traduzioni).
## 05/08/2026 — Tab asset: card "Stato UrBackup", rename "Client State", fix badge Internet mode
- **Bugfix (regressione fix XSS 05/08)**: `showStateSection()` — `$internetModeDisplay` (HTML badge) finiva nell'array `$rows` e il loop generico lo passava da `htmlspecialchars()` → si vedeva il testo letterale `No `. Fix: riga dedicata per "Internet mode" fuori dal loop (badge raw, label escapata, contenuto escapato in costruzione) — pattern già usato per l'authkey; la protezione XSS sui valori API resta intatta. Audit: unico caso nel plugin (Server.php/Config.php echo direttamente i badge).
- **Opzione A — card Bootstrap** in `showServerLinkedBlock()`: la tabella 2×4 "UrBackup status" è ora una `card` con header + 4 card (row-cols-1/2/4): Linked server (link + `protocol://ip:port`), **API status** (badge verde/rosso da `last_api_status` + messaggio + `last_api_check`), server version (dash se vuoto), Client name. Valori `fw-bold`, label `text-uppercase text-muted small`; nessun CSS custom, solo utility Bootstrap 5.
- **Rename tab interna**: `__('State', 'urbackup')` → `__('Client State', 'urbackup')` (it "Stato Client", de "Client-Status", en "Client State"); msgid orfana `State` rimossa dai 3 `.po`.
- Locales: +2 msgid (`Client State`, `API status`), -1 (`State`) → 181 tradotti + header; `.mo` ricompilati (msgfmt --check OK). Nessun bump versione (solo UI, regola 6 non applicabile).
## 05/08/2026 — Bump 0.7.2 (toggle Computer configurabile + lista Asset custom)
- **Modifica DB**: nuova riga `enable_computer` ('1') in `glpi_plugin_urbackup_configs` (migrazione idempotente `plugin_urbackup_install_add_enable_computer_config()` in install.php + INSERT in empty.sql) → bump `PLUGIN_URBACKUP_VERSION` `0.7.1` → `0.7.2` (regola 6 AGENTS.md).
- **Perché prima "Computer sempre abilitato"**: `Config::isItemtypeEnabled()` hardcoded `return true` per `Computer` + `registerClass(AssetTab, addtabon Computer)` incondizionato in setup.php + badge statico in `showForm()`.
- **Config.php**: nuovo `getEnableComputer()` (cache statica, guard `TableExists`, fallback `true` se riga/tabella assente), `isItemtypeEnabled('Computer')` e `getEnabledItemtypes()` ora rispettano il toggle; nuovo `getEnabledAssetDefinitions()` — lista Asset Definition con capacità UrBackup via `hasCapacityEnabled()` con istanza da `getAvailableCapacities()` (fallback: decode raw JSON `capacities` → `array_column('name')`).
- **setup.php**: `registerClass(AssetTab, addtabon Computer)` solo se `Config::getEnableComputer()`; per le Asset Definition la registrazione tab resta in `UrBackupCapacity::onClassBootstrap()` (non toccata).
- **front/config.form.php**: reintrodotto handler POST `update` (CSRF gestito dal listener globale GLPI 11 — niente `Session::checkCSRF()`): upsert `enable_computer`, `Session::addMessageAfterRedirect`, redirect a `Config::getFormURL()`; form con `Dropdown::showYesNo` + `Html::hidden('_glpi_csrf_token', ...)` + `Html::submit(__('Save'))`.
- **Nuova sezione config page**: "Custom assets with \"Urbackup\" capacity enabled" (it: `Asset custom con Capacità "Urbackup" attivata`, de: `Benutzerdefinierte Assets mit aktivierter Kapazität "Urbackup"`) — colonne Name / System name / Active (badge Yes/No); su DB locale: definizioni **Server** e **NAS** con capacità attiva.
- **Comportamento a Computer disattivato**: tab nascosto (doppia protezione: registrazione condizionale + gate in `AssetTab::getTabNameForItem/displayTabContentForItem`), MassiveActions bloccate (hook.php:47), `front/asset.form.php` rifiuta, unlinked/missing clients escludono Computer; i link esistenti in `glpi_plugin_urbackup_serverassets` restano intatti (solo visibilità UI).
- Locales: +5 msgid (180 tradotti + header), header `Project-Id-Version` → 0.7.2, `.mo` ricompilati con `msgfmt --check` (OK per it/de/en).
## 05/08/2026 — Bump 0.7.1 (migrazione DB: cifratura api_password)
- Modifica DB della sessione (nuova migrazione `plugin_urbackup_install_encrypt_api_passwords()`) → bump `PLUGIN_URBACKUP_VERSION` `0.7.0` → `0.7.1`.
- Eseguita la procedura standard: `glpi:plugin:install urbackup` + `glpi:plugin:activate urbackup`; verificato `glpi_plugins.version = 0.7.1`, `state = ACTIVE`, pagine front funzionanti.
- `README.md` Changelog 0.7.1 aggiunto; header `.po` → 0.7.1 con `.mo` ricompilati.
## 05/08/2026 — Cifratura api_password, ottimizzazioni, localizzazioni e correzione claim ambiente
### 1. Claim "flussi API non testabili in locale" ERRATO — verificato
- `urbackupsrv` (pid ~1282) è attivo su `0.0.0.0:55414`; endpoint `/x?a=salt` e **login 2-fasi verificati funzionanti** (`admin`/`12345678`): PBKDF2 hash calcolato e `"success":true`.
- La frase "nessun server UrBackup reale raggiungibile in locale" era copiata dalle docs senza riverifica → corretta in MEMORY.md, AGENTS.md, GLPIDEV.md e `urbackup-api-actions.md`.
- `urbackup-api-actions.md` aggiornato: login 2-fasi riuscito, rimosso riferimento a `changeClientSetting()` (dead code rimosso).
### 2. Cifratura `api_password` con GLPIKey (punto ⚠️ 1 risolto)
- **API core corretta**: `(new GLPIKey())->encrypt()/decrypt()` — `GLPIKey::getInstance()` **NON esiste** in GLPI 11.0.8 (pattern usato dal core: `APIRest.php:623`, `ClientRepository.php:90`).
- `src/Server.php`: nuovo `getApiPassword()` (decrypt on-the-fly con fallback legacy in chiaro) + `isApiPasswordEncrypted()` (rileva formato base64+nonce ≥ 24B, evita warning `trigger_error` di `decrypt()` su valori legacy).
- `prepareInputForUpdate()`: campo vuoto → `unset` (mantiene password, fix di un wipe involontario della password ad ogni save); campo valorizzato → cifrato con `(new GLPIKey())->encrypt()`.
- `src/UrbackupApiClient.php`: `__construct()` usa `$server->getApiPassword()` quando `$server instanceof Server`.
- `install/install.php`: nuova migrazione idempotente `plugin_urbackup_install_encrypt_api_passwords()` che cifra le password legacy in chiaro (salta valori già cifrati; in caso di chiave non disponibile lascia il valore invariato per non perdere dati).
- Nota: le righe già in chiaro rimangono usabili finché non avviene il save o l'install/update del plugin (fallback in `getApiPassword()`).
### 3. Test connessione API solo a parametri cambiati (punto ⚠️ 2 risolto)
- `prepareInputForUpdate()` ora esegue `testConnection()` SOLO se cambiano `ip_address|port|protocol|api_username|api_password|ignore_ssl`; salvataggi "neutri" non innescano più richieste API da 30s.
### 4. Loop opcache solo in development (punto ⚠️ 3 risolto)
- `setup.php`: loop `opcache_invalidate()` attivo solo se `defined('GLPI_ENVIRONMENT_TYPE') && GLPI_ENVIRONMENT_TYPE === 'development'` (enum `Glpi\Application\Environment`, valori `production|staging|testing|e2e_testing|development`; default produzione).
### 5. Fix N+1 in `showUnlinkedClientsTab()` (punto ⚠️ 4 risolto)
- Batch loading nomi asset: 1 query per itemtype (map `itemtype-id`), sostituisce `ServerAsset::getAssetName()` per riga (2 query/riga prima).
### 6. Localizzazioni it_IT / de_DE / en_GB aggiornate
- I 3 `.po` erano fermi alla v0.4.0 (69 msgid vs 176 chiavi nel codice). Aggiunte **115 msgid mancanti** con traduzioni complete (it/de/en), rimosse 9 orfane (es. "Checking...", "Root location ID", "Server unreachable", "UrBackup rights"), header `Project-Id-Version` → 0.7.0.
- `.mo` ricompilati con `msgfmt --check` (tutti "175 messaggi tradotti").
- Audit hardcoded: nessuna stringa utente fuori da `__()/_n()/_x()` (solo tag HTML nei raw echo); `_x('button', 'Save')` nel Twig gestito correttamente.
### Verifiche
- `php -l` OK su tutti i file toccati (Server.php, UrbackupApiClient.php, setup.php, install/install.php).
- Roundtrip cifratura/decifratura verificato via bootstrap GLPI CLI.
- Smoke test HTTP post-modifica: pagine server/config caricate senza errori.
### Da fare residuo
- (nessuno dei 4 punti ⚠️ precedenti) — i test REALI su server semi-produttivo restano consigliati, ma i flussi API sono ora verificabili anche in locale (localhost:55414).
## 05/08/2026 — Verifica codice, rimozione codice morto e audit sicurezza
### Codice morto rimosso (verificato con grep su tutto il progetto)
1. **`src/Server.php`**: rimossi `testApiConnection()` + `isNetworkError()` (nessun chiamante; l'endpoint `front/server_test.ajax.php` implementa il test inline), `getAssetGroupName()` (mai chiamato), import `use CommonGLPI;` inutilizzato.
2. **`src/UrbackupApiClient.php`**: rimossi `changeClientSetting()` (duplicato di `updateClientSettings()`, mai chiamato) e `extractSettingValue()` (duplicato della copia privata di `AssetTab`, mai chiamato).
3. **`src/Config.php`**: rimossi `ensureDefaultConfiguration()` e `saveConfiguration()` (no-op con commento "kept for backward compatibility" ma senza alcun chiamante utile) + import inutilizzati `DBmysql`, `Html`.
4. **`install/install.php`**: rimossa funzione `plugin_urbackup_install_update_assettypes_table()` (mai chiamata da `install_process()`); rimosso blocco `hasCapacity()`/`enableCapacity()` con API inesistente in GLPI 11.0.8 (caveat 18, ora documentato nel codice: l'enable passa dal form Capacities UI); rimossa chiamata `Config::ensureDefaultConfiguration()`; rimossi import `AssetDefinitionManager`, `UrBackupCapacity`.
5. **`front/asset.form.php`**: rimossi handler POST `start_file_backup`/`start_image_backup` (nessun form del plugin li emette; i bottoni usano `execute` + `urbackup_action`).
6. **`front/config.form.php`**: rimosso handler POST `update` (il form non esiste più; `Config::saveConfiguration()` rimosso); aggiunto bootstrap `include_once inc/includes.php` mancante; rimosso `global $CFG_GLPI;` inutilizzato.
7. **`public/js/urbackup.js`**: FILE ELIMINATO (cercava `#plugin-urbackup-api-status` e `.plugin-urbackup-test-api`, elementi mai renderizzati da nessun file PHP/Twig) + rimosso hook `ADD_JAVASCRIPT` in `setup.php`.
8. **`hook.php`**: rimossi `require_once install/install.php` e `install/uninstall.php` (caricati ad ogni richiesta inutilmente; il lifecycle li carica da `setup.php`).
9. **`src/MassiveAction.php`**: rimosso `use Session;` inutilizzato; corrette due parentesi di chiusura con indentazione errata.
10. **`src/AssetTab.php`**: rimossi parametri inutilizzati `$server`/`$link` da `showStateSection()`/`showActionsSection()` e `$link` da `showInternalTabs()` (privati).
### Fix di sicurezza
1. **XSS (stored) — `AssetTab::showStateSection()`**: `$displayValue` (valori status/settings dall'API UrBackup, potenzialmente ostili) era stampato SENZA `htmlspecialchars()` → ora escapato (`htmlspecialchars((string) $displayValue)`).
2. **`front/server_test.ajax.php`**:
- rimosso `$AJAX_INCLUDE = 1;` (no-op in GLPI 11, innesca deprecation);
- aggiunto bootstrap `include_once inc/includes.php`;
- **POST-only** (prima accettava GET con scrittura DB → state change via GET, CSRF-friendly);
- diritto richiesto per la scrittura DB: **UPDATE** (prima READ poteva sovrascrivere `last_api_status/message/check`);
- import espliciti `use` al posto dei FQCN inline.
3. **Password API non più esposta nel page source** (`Server::showFormFields`): campo `api_password` ora con `value=''` + placeholder `******` + hint "leave empty to keep current"; `prepareInputForUpdate()` mantiene la password esistente quando il campo è vuoto. (Risolto il 05/08/2026: `api_password` ora cifrata con GLPIKey — vedi sezione aggiornata.)
4. **Validazione input server**: `protocol` whitelistato `http|https` (prima qualsiasi stringa finiva in `getWebInterfaceUrl()` → href e URL cURL) e `port` clampato `1..65535` in `prepareInputForAdd`/`prepareInputForUpdate`.
5. **`front/asset.form.php` disconnect**: condizione diritti allineata a `ServerAsset::disconnectAsset()` (solo UPDATE; prima UPDATE|DELETE incoerente).
6. **`front/server.form.php`**: `$_GET['id']` castato a int.
### Verifiche
- `php -l` OK su tutti i file PHP del plugin.
- Smoke test HTTP (ambiente locale, senza auth): `server_test.ajax.php` GET→302 (login), POST→403 (denied), `config.form.php` e `server.php` → 200 senza fatal error. (Nota: la verifica del 05/08/2026 ha confermato che il server UrBackup locale è attivo su `localhost:55414` e il login API 2-fasi funziona — il claim "flussi API non testabili in locale" era errato, vedi sezione aggiornata.)
### Da fare (non bloccanti, segnalati nel report) — TUTTI RISOLTI il 05/08/2026
- ~~`api_password` in chiaro su `glpi_plugin_urbackup_servers` → cifrare con `GLPIKey`~~ → fatto: `(new GLPIKey())->encrypt()` in `prepareInputForAdd/Update` + migrazione `plugin_urbackup_install_encrypt_api_passwords()` + `getApiPassword()` con fallback legacy.
- ~~`setup.php` top-level: loop `opcache_invalidate()` ad OGNI richiesta web~~ → fatto: solo se `GLPI_ENVIRONMENT_TYPE === 'development'`.
- ~~`Server::prepareInputForUpdate()`: test connessione API ad ogni save~~ → fatto: solo se i parametri di connessione cambiano.
- ~~`Server::showUnlinkedClientsTab()`: N+1 su `ServerAsset::getAssetName()`~~ → fatto: batch loading per itemtype.
## 04/08/2026 — Documentazione AI: AGENTS.md / SKILL.md / GLPIDEV.md specifici per urbackup
Generati i 3 file operativi del progetto a partire dai modelli `*_netbackup` (copie dei file del plugin netbackup presenti in cartella), adattandoli alla realtà verificata del codice:
### File prodotti
1. **`AGENTS.md`** (sovrascritto — prima era identico a `AGENTS.md_netbackup`): ruolo Senior GLPI Plugin Architect, regole assolute (Git rule con autorizzazione esplicita, strict types PHP 8.3/8.4, namespace `GlpiPlugin\Urbackup\`), CSRF GLPI 11 (CheckCsrfListener globale, niente `Session::checkCSRF()` nei front), limiti ambiente locale (server UrBackup locale verificato attivo: `localhost:55414`), struttura v0.7.0 e hook essenziali.
2. **`SKILL.md`** (nuovo): competenze UrBackup Web API (login salt/PBKDF2, azioni, struct setting, versioni ≥ 2.4), Capacity system GLPI 11, location-aware matching, network resilience, security zero-trust.
3. **`GLPIDEV.md`** (nuovo): reference API GLPI 11.0.8 verificata sul core `/var/www/glpi` — DB layer, tabelle plugin, Migration, CSRF, Capacity system, UrBackupApiClient (tabella azioni), LocationHelper/batch loading, 18 caveat verificati.
### Fatti ri-verificati durante la stesura (da codice reale)
- `$DB->runFile()` è deprecato ma usato SOLO per schema iniziale in `install/install.php` (mai in upgrade/uninstall).
- `Html::hidden()` in GLPI 11 usa la firma `(name, options)` — pattern `Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()])`.
- Hook `plugin_urbackup_MassiveActions($type)` riceve l'itemtype come **stringa**.
- `api_password` in chiaro: limite noto → **risolto il 05/08/2026** con cifratura `(new GLPIKey())->encrypt()/decrypt()` (non esiste `GLPIKey::getInstance()`).
### Server UrBackup per test in locale (VERIFICATO ATTIVO)
- **URL**: `http://localhost:55414`
- **Credenziali**: utente `admin`, password `12345678`
- **Scopo**: eseguire API semplici (login, status, backup) in locale per debugging e validazione rapida.
- **Stato**: processo `urbackupsrv` attivo (porta 55414), endpoint `/x?a=salt` e login 2-fasi (PBKDF2) verificati funzionanti il 05/08/2026.
- **Risultati test API**: documentato in `/var/www/glpi/plugins/urbackup/urbackup-api-actions.md`.
### ⚠️ BUG latente trovato: hasCapacity()/enableCapacity() inesistenti in GLPI 11.0.8
- `install/install.php:367-368` chiama `$definition->hasCapacity(UrBackupCapacity::class)` e `$definition->enableCapacity(UrBackupCapacity::class)` — in GLPI 11.0.8 su `AssetDefinition` esistono SOLO `hasCapacityEnabled(CapacityInterface $capacity)` (oggetto, non stringa), `getEnabledCapacities()`, `getCapacityConfiguration()` (`src/Glpi/Asset/AssetDefinition.php:627-652`).
- Impatto attuale: la chiamata è in try/catch → durante l'install si mostra il messaggio "Error enabling UrBackup capacity on definitions: ..." ma la migrazione continua; le capacità NON vengono auto-abilitate.
- Impatto funzionale nullo oggi perché `Config::isItemtypeEnabled()` ritorna true per tutte le sottoclassi di `Glpi\Asset\Asset` (tab sempre visibile).
- **TODO**: correggere con l'API core corretta: check con `hasCapacityEnabled(new UrBackupCapacity())` (richiede un oggetto `CapacityInterface`); l'abilitazione/disabilitazione in GLPI 11.0.8 avviene via input `capacities` del form (array di spec) processato in `AssetDefinition::post_updateItem()` (AssetDefinition.php:316-440) — NON esistono metodi pubblici `enableCapacity/saveCapacity/disableCapacity`. Documentato in GLPIDEV.md §2.6 e caveat 18. → NOTA: rimosso come dead code il 05/08/2026 (il blocco non è più in install.php).
## GLPI 11 — Session::checkCSRF() breaking change
In GLPI 11, `Session::checkCSRF()` richiede il primo argomento `$data` (i dati POST da validare). In GLPI ≤10 accettava zero argomenti.
Il listener globale `CheckCsrfListener` gestisce già il CSRF per tutte le richieste POST, ma il plugin chiamava `Session::checkCSRF()` senza argomenti causando errore fatale.
### Fix applicati (tutti i file)
- RIMOSSE tutte le chiamate esplicite `Session::checkCSRF()` dai 4 file front — GLPI 11 le gestisce già globalmente via `CheckCsrfListener`
- Il listener globale consuma il token CSRF, quindi una seconda chiamata dal plugin fallisce perché il token non è più valido
- `public/js/urbackup.js` — aggiunto header `X-Glpi-Csrf-Token` con `getAjaxCsrfToken()` per le richieste AJAX (NOTA 05/08/2026: urbackup.js è stato ELIMINATO come dead code — il pattern CSRF AJAX resta documentato per riferimento)
## Performance - Asset Definition vs Computer
### Problema
Gli Asset Definition custom (GLPI 11) sono 2-5x più lenti dei Computer nativi nel caricamento del tab UrBackup.
### Causa
L'overhead è in GLPI 11 core, non nel plugin:
1. **`Asset::__construct()`** — itera 30+ Capacity classi per ogni istanza
2. **`Asset::post_getFromDB()`** — decodifica JSON custom fields e li processa
3. **`eval()` autoloading** — le classi concrete sono definite via `eval()` a runtime
### Ottimizzazioni applicate
1. **`AssetTab.php::loadApiData()`** — letto `$item->fields['name']` direttamente invece di chiamare `ServerAsset::getAssetName()` che faceva una seconda `getFromDB()` ridondante
2. **`AssetTab.php::startBackup()`, `saveInternetMode()`, `saveDefaultDirs()`, `showServerLinkedBlock()`** — stesso pattern, `$item->fields['name']` al posto di `getAssetName()`
3. **`Server.php::showMissingClientsTab()`** — batch loading IP e gruppi: `batchLoadIps()` (1 query per itemtype vs 1 per riga) + `batchLoadGroups()` (1 query per itemtype vs 1 per riga)
## Architettura
- `src/Capacity/UrBackupCapacity.php` — registra `AssetTab` via `CommonGLPI::registerStandardTab()` in `onClassBootstrap()`
- `setup.php` — registra capacità, CSS, hook
- `src/AssetTab.php` — display tab content + tab interni (Stato/Azioni/Info-Log) + host server block
- `src/ServerAsset.php` — gestione collegamenti asset-server
- `src/Config.php` — itemtype enabled check
- `src/UrbackupApiClient.php` — client API con caching in-memory (per istanza) e sessione
- `src/LocationHelper.php` — risoluzione location radice
- `src/Server.php` — CRUD server, tab missing clients, form (incl. host asset)
## Asset Tab Interni
- 3 sub-tab: Stato, Azioni (solo UPDATE/CREATE), Info/Log
- CSS: tab con tonalità di grigio differenti (scuro/medio/chiaro)
- Caricamento dati API con caching sessione 30s
- Dati caricati: status, settings, authkey, backup recenti (10), log (50)
## Cache
- `UrbackupApiClient`: cache in-memory per `getStatus()` e `getClientSettings()`
- `AssetTab::loadApiData()`: cache sessione 30s (chiave: server_id + client_name)
- API timeout: 30s, connect timeout: 5s
## Bugfix: Campi API username/password non visibili in nuova installazione
### Problema
In una nuova installazione del plugin, i campi "API username" e "API password" non venivano renderizzati come input — si vedeva solo la label ma non il campo editabile.
### Cause (2 bug distinti)
1. **`src/Server.php:436`** — `$canUpdate` controllava solo `Session::haveRight(self::$rightname, UPDATE)`. In fase di creazione di un nuovo server, l'utente ha diritto CREATE ma non necessariamente UPDATE, quindi il campo non veniva mostrato.
2. **`src/Profile.php:73-77`** — `installRights()` usava `$_SESSION['glpiactiveprofile']['id']` per assegnare i diritti completi al profilo corrente. In installazione via CLI (`php bin/console glpi:plugin:install`), non c'è sessione, quindi `$profiles_id = 0` e nessun profilo riceveva i diritti completi.
### Fix applicati
1. **`Server.php::showFormFields()`** — Sostituito `$canUpdate` con `$canEdit`: per nuovi server (`$ID <= 0`) usa `CREATE`, per server esistenti usa `UPDATE`.
2. **`Profile.php::installRights()`** — In assenza di sessione attiva (CLI), cerca il profilo "Super-Admin" tramite query diretta e gli assegna tutti i diritti.
## Bugfix: Unknown column 'users_id' — Group::getDataItems() SQL error
### Problema
Su server produttivo, `Group::showItems()` generava un errore MySQL 1054 (Unknown column 'users_id') quando GLPI iterava sugli itemtype registrati con `linkgroup_types => true`. La query includeva:
```sql
WHERE ((... ) OR (`users_id` IN (SELECT `users_id` FROM `glpi_groups_users` WHERE `groups_id` IN ('7483'))))
```
La colonna `users_id` non esisteva nella tabella `glpi_plugin_urbackup_servers`.
### Causa
`Server` è registrato in `setup.php:57` con `linkgroup_types => true`, il che fa sì che GLPI lo includa nelle query di `Group::getDataItems()`. Il metodo aggiunge automaticamente una condizione `users_id IN (...)` assumendo che ogni itemtype abbia tale colonna.
### Fix
1. **`install/mysql/plugin_urbackup-empty.sql`** — Aggiunta colonna `users_id INT UNSIGNED NOT NULL DEFAULT 0` dopo `locations_id` + indice
2. **`install/install.php`** — Aggiunto `$migration->addField('users_id', ...)` e `$migration->addKey('users_id')` nella migrazione del server
3. **`src/Server.php`** — Aggiunto search option `id=8` per `User::getTable()` + auto-set di `users_id` da `$_SESSION['glpiID']` in `prepareInputForAdd()`
## Versione
- 0.7.3
## 0.7.3 — Link Server ↔ asset host hardware
1. **Colonne `host_itemtype`/`host_items_id` + KEY `host_asset`** su `glpi_plugin_urbackup_servers` (migrazione idempotente + empty.sql) → bump 0.7.2 → 0.7.3 (regola 6).
2. **Form server**: riga "Hardware host" con dropdown tipo (`Dropdown::showItemTypes`) + dropdown elementi AJAX (`$.get` su `front/dropdown_host.ajax.php`, vedi sezione Fix del 07/08/2026).
3. **AssetTab**: blocco "This asset hosts the UrBackup server" (sempre visibile, anche se client).
4. **Validazione** in `prepareInputForUpdate()` + helper `getHostAsset()` / `getServersHostingAsset()` (guard `fieldExists`).
## 0.7.2 — Toggle Computer configurabile + lista Asset custom con capacità attiva
1. **`enable_computer` in `glpi_plugin_urbackup_configs`** (default '1') letto da `Config::getEnableComputer()`; registrazione tab su Computer condizionale in setup.php; `isItemtypeEnabled`/`getEnabledItemtypes` rispettano il toggle.
2. **Config page**: dropdown Sì/No per Computer + lista Asset Definition con capacità UrBackup attivata (`getEnabledAssetDefinitions()`).
3. **front/config.form.php**: handler POST `update` ripristinato (upsert, redirect, messaggio).
4. **DB**: migrazione idempotente + INSERT in empty.sql → bump 0.7.2 (regola 6).
## 0.7.1 — Cifratura api_password (GLPIKey), ottimizzazioni e localizzazioni
1. **`api_password` cifrata con `(new GLPIKey())->encrypt()`** on save (`Server::prepareInputForAdd/Update`), `Server::getApiPassword()` con fallback legacy, migrazione idempotente `plugin_urbackup_install_encrypt_api_passwords()` (install.php) — applicata al DB (server id=1).
2. **Test connessione API solo a parametri cambiati** in `prepareInputForUpdate()` (niente più chiamate da 30s ad ogni save).
3. **Loop `opcache_invalidate()` solo in development** (`GLPI_ENVIRONMENT_TYPE === 'development'`).
4. **Fix N+1** in `Server::showUnlinkedClientsTab()` (batch loading per itemtype).
5. **Localizzazioni**: 115 nuove traduzioni it/de/en, 9 orfane rimosse, `.mo` ricompilati.
6. **Docs**: claim "nessun server locale" corretto (server reale verificato attivo + login 2-fasi OK), `GLPIKey::getInstance()` → `new GLPIKey()` in AGENTS/SKILL/GLPIDEV, `urbackup-api-actions.md` aggiornato.
## 0.7.0 — Pulizia, sicurezza e DB cleanup
1. **CSRF hardening**: `Session::checkCSRF()` aggiunto su `asset.form.php`, `server.form.php`, `server_test.ajax.php`, `config.form.php` (NOTA: poi RIMOSSO il 05/08/2026 — in GLPI 11 il listener globale lo gestisce)
2. **File deprecati rimossi**: 12 file (composer copy, AGENTS_OLD.MD, js/, ajax/, front/test/view, FIX_PERMISSIONS.sh, removed/)
3. **Dead code rimosso**: 3 Controller, 1 Command, 4 template Twig
4. **DB migration**: DROP `glpi_plugin_urbackup_profiles` e `glpi_plugin_urbackup_assettypes`; add index `location_active` su servers; add `date_creation`/`date_mod` su serverassets
5. **SQL schema pulito**: Rimosse tabelle legacy dall'empty.sql
6. **PHP lint warnings**: Rimosse `use Html;` e `use Search;` superflue
7. **README.md** creato
8. **PURGE right**: Aggiunto PURGE ai diritti di installazione (`Profile.php:76,90`); applicato a tutti i profili Super-Admin esistenti nel DB
9. **CSRF GLPI 11 fix**: `Session::checkCSRF()` richiede `$_POST` come argomento in GLPI 11; aggiunto `X-Glpi-Csrf-Token` header al JS AJAX
10. **Session::isDebugActive()**: Metodo inesistente in GLPI 11; sostituito in `AssetTab.php:150` con `($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE`