38 KiB
MEMORY.md - Stato del Plugin UrBackup
Ultima modifica: 07/08/2026
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::updateItemOnSelectEventgenera$("#target").load(url, {params})— jQuery.load()con data oggetto invia POST, mentrefront/dropdown_host.ajax.phpera GET-only (405) → div mai popolato. (Nota: il CSRF non era il blocco per.load(): il global$(document).ajaxSendin public/js/common.js aggiungeX-Glpi-Csrf-Tokena OGNI POST AJAX, letto dagetAjaxCsrfToken()sul<meta property="glpi:csrf_token">.) - Causa 2 (500):
Session::getMatchingActiveEntities()richiede esattamente 1 argomento (è un filtro di entity,@since 10.0.13) → usareSession::getActiveEntities()(Session.php:2194). - Causa 3 (parametro sbagliato): in
Dropdown::showil parametro per limitare le entità èentity(default -1, riga 133), NONentity_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) — serveecho 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 divurbackup_host_items$rand(GET = bodyless → nessun check CSRF; jQuery esegue gli script inline iniettati, incluso il$(function(){})del config select2). Rimossouse Ajax;(non più usato). - Lezione GLPI 11 — i dropdown sono LAZY:
Dropdown::show()NON renderizza più le<option>: genera un componenteHtml::jsAjaxDropdown(select2) conparamsJSON +_idor_token; i valori arrivano via POST a/ajax/getDropdownValue.php(setupAjaxDropdownin public/js/common.js:type:"POST"+ajaxSendglobale per il CSRF). Il markup contiene solo l'option vuota finché l'utente non apre/cerca. - Lezione IDOR:
Session::getNewIDORTokenNON ruota (accumula token validi 2h,cleanIDORTokens);validateIDORconfronta le chiavi salvate (itemtype, entity_restrict, displaywith, condition) con la POST. Il config JS serializzato da jQuery mandaentity_restrictcon 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 conDropdown::showreale, 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 -lOK; 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) + KEYhost_asset(host_itemtype,host_items_id) — NIENTE unique (più server per host ammessi, es. VM). Migrazione idempotenteplugin_urbackup_install_update_servers_table()in install.php + empty.sql.glpi_plugin_urbackup_serverassetsNON 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$.getsudropdown_host.ajax.phpalchangedel select tipo — vedi sezione Fix + divurbackup_host_items$randpre-popolato conDropdown::showconentity => Session::getActiveEntities()+ link all'host); validazione inprepareInputForUpdate()(itemtype vuoto/items_id ≤0/class mancante/!isItemtypeEnabled/item inesistente → azzera entrambi);getHostAsset(): ?CommonDBTM;getServersHostingAsset(string, int): arraycon guard$DB->fieldExists(self::getTable(),'host_itemtype')(robustezza pre-migrazione — pattern di Config::getEnableComputer). - front/dropdown_host.ajax.php (NUOVO): GET only,
Session::checkLoginUser(), validazioneclass_exists+Config::isItemtypeEnabled, rispondeDropdown::show($itemtype, ['name'=>'host_items_id','value'=>..., 'entity'=>Session::getActiveEntities(), 'display_emptychoice'=>true]). - AssetTab.php:
showHostServerBlock(CommonDBTM $item)chiamata per prima indisplayTabContentForItem()— 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;.moricompilati (msgfmt --check OK, warning header pre-esistenti) + cache traduzioni svuotata (files/_cache/*/translations/). - Verifiche IA:
php -lOK (setup.php, install/install.php, Server.php, AssetTab.php, dropdown_host.ajax.php); test CLI/tmp/opencode/urbackup_host_test.php10/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) → attende verifica visiva finale dell'utente. - 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 querymax-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
!importantsu.nav-tabs(display/flex-direction/flex-wrap) +.nav-iteme.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
.moerano corretti (msgunfmt/msgfmt --check OK). - Causa radice: GLPI 11
Session::loadLanguage()(src/Session.php:816+) crea$TRANSLATE(anonima class che estendeLaminas\I18n\Translator\Translator) con cache catalogo viaI18nCache(src/Glpi/Cache/I18nCache.php) →CacheManager::getTranslationsCacheInstance()(CONTEXT_TRANSLATIONS) →FilesystemAdaptersufiles/_cache/<env-hash>/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/<env-hash>/translations/(12 file) → la cache si rigenera al primo caricamento con i.monuovi. - Regola operativa: dopo OGNI ricompilazione dei
.model plugin, svuotarefiles/_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<meta property="glpi:csrf_token">e i campi form sonologin_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 marcatoNOTUPDATEDe 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_VERSIONinsetup.php(regola 6 di AGENTS.md). - Meccanismo GLPI verificato (
src/Plugin.php):checkPluginState()(~righe 909-933) confrontaplugin_version_urbackup()['version']conglpi_plugins.version; se diverse →state = NOTUPDATEDe 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)), poiphp bin/console glpi:plugin:activate urbackup(l'install impostaNOTACTIVATED) — 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 headerProject-Id-Versiondei.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$rowse il loop generico lo passava dahtmlspecialchars()→ si vedeva il testo letterale<span class="badge bg-secondary">No</span>. 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 unacardcon header + 4 card (row-cols-1/2/4): Linked server (link +protocol://ip:port), API status (badge verde/rosso dalast_api_status+ messaggio +last_api_check), server version (dash se vuoto), Client name. Valorifw-bold, labeltext-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 orfanaStaterimossa dai 3.po. - Locales: +2 msgid (
Client State,API status), -1 (State) → 181 tradotti + header;.moricompilati (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') inglpi_plugin_urbackup_configs(migrazione idempotenteplugin_urbackup_install_add_enable_computer_config()in install.php + INSERT in empty.sql) → bumpPLUGIN_URBACKUP_VERSION0.7.1→0.7.2(regola 6 AGENTS.md). - Perché prima "Computer sempre abilitato":
Config::isItemtypeEnabled()hardcodedreturn trueperComputer+registerClass(AssetTab, addtabon Computer)incondizionato in setup.php + badge statico inshowForm(). - Config.php: nuovo
getEnableComputer()(cache statica, guardTableExists, fallbacktruese riga/tabella assente),isItemtypeEnabled('Computer')egetEnabledItemtypes()ora rispettano il toggle; nuovogetEnabledAssetDefinitions()— lista Asset Definition con capacità UrBackup viahasCapacityEnabled()con istanza dagetAvailableCapacities()(fallback: decode raw JSONcapacities→array_column('name')). - setup.php:
registerClass(AssetTab, addtabon Computer)solo seConfig::getEnableComputer(); per le Asset Definition la registrazione tab resta inUrBackupCapacity::onClassBootstrap()(non toccata). - front/config.form.php: reintrodotto handler POST
update(CSRF gestito dal listener globale GLPI 11 — nienteSession::checkCSRF()): upsertenable_computer,Session::addMessageAfterRedirect, redirect aConfig::getFormURL(); form conDropdown::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.phprifiuta, unlinked/missing clients escludono Computer; i link esistenti inglpi_plugin_urbackup_serverassetsrestano intatti (solo visibilità UI). - Locales: +5 msgid (180 tradotti + header), header
Project-Id-Version→ 0.7.2,.moricompilati conmsgfmt --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()) → bumpPLUGIN_URBACKUP_VERSION0.7.0→0.7.1. - Eseguita la procedura standard:
glpi:plugin:install urbackup+glpi:plugin:activate urbackup; verificatoglpi_plugins.version = 0.7.1,state = ACTIVE, pagine front funzionanti. README.mdChangelog 0.7.1 aggiunto; header.po→ 0.7.1 con.moricompilati.
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 su0.0.0.0:55414; endpoint/x?a=salte 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.mdaggiornato: login 2-fasi riuscito, rimosso riferimento achangeClientSetting()(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: nuovogetApiPassword()(decrypt on-the-fly con fallback legacy in chiaro) +isApiPasswordEncrypted()(rileva formato base64+nonce ≥ 24B, evita warningtrigger_errordidecrypt()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 idempotenteplugin_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 eseguetestConnection()SOLO se cambianoip_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: loopopcache_invalidate()attivo solo sedefined('GLPI_ENVIRONMENT_TYPE') && GLPI_ENVIRONMENT_TYPE === 'development'(enumGlpi\Application\Environment, valoriproduction|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), sostituisceServerAsset::getAssetName()per riga (2 query/riga prima).
6. Localizzazioni it_IT / de_DE / en_GB aggiornate
- I 3
.poerano 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"), headerProject-Id-Version→ 0.7.0. .moricompilati conmsgfmt --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 -lOK 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)
src/Server.php: rimossitestApiConnection()+isNetworkError()(nessun chiamante; l'endpointfront/server_test.ajax.phpimplementa il test inline),getAssetGroupName()(mai chiamato), importuse CommonGLPI;inutilizzato.src/UrbackupApiClient.php: rimossichangeClientSetting()(duplicato diupdateClientSettings(), mai chiamato) eextractSettingValue()(duplicato della copia privata diAssetTab, mai chiamato).src/Config.php: rimossiensureDefaultConfiguration()esaveConfiguration()(no-op con commento "kept for backward compatibility" ma senza alcun chiamante utile) + import inutilizzatiDBmysql,Html.install/install.php: rimossa funzioneplugin_urbackup_install_update_assettypes_table()(mai chiamata dainstall_process()); rimosso bloccohasCapacity()/enableCapacity()con API inesistente in GLPI 11.0.8 (caveat 18, ora documentato nel codice: l'enable passa dal form Capacities UI); rimossa chiamataConfig::ensureDefaultConfiguration(); rimossi importAssetDefinitionManager,UrBackupCapacity.front/asset.form.php: rimossi handler POSTstart_file_backup/start_image_backup(nessun form del plugin li emette; i bottoni usanoexecute+urbackup_action).front/config.form.php: rimosso handler POSTupdate(il form non esiste più;Config::saveConfiguration()rimosso); aggiunto bootstrapinclude_once inc/includes.phpmancante; rimossoglobal $CFG_GLPI;inutilizzato.public/js/urbackup.js: FILE ELIMINATO (cercava#plugin-urbackup-api-statuse.plugin-urbackup-test-api, elementi mai renderizzati da nessun file PHP/Twig) + rimosso hookADD_JAVASCRIPTinsetup.php.hook.php: rimossirequire_once install/install.phpeinstall/uninstall.php(caricati ad ogni richiesta inutilmente; il lifecycle li carica dasetup.php).src/MassiveAction.php: rimossouse Session;inutilizzato; corrette due parentesi di chiusura con indentazione errata.src/AssetTab.php: rimossi parametri inutilizzati$server/$linkdashowStateSection()/showActionsSection()e$linkdashowInternalTabs()(privati).
Fix di sicurezza
- XSS (stored) —
AssetTab::showStateSection():$displayValue(valori status/settings dall'API UrBackup, potenzialmente ostili) era stampato SENZAhtmlspecialchars()→ ora escapato (htmlspecialchars((string) $displayValue)). 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
useal posto dei FQCN inline.
- rimosso
- Password API non più esposta nel page source (
Server::showFormFields): campoapi_passwordora convalue=''+ placeholder******+ hint "leave empty to keep current";prepareInputForUpdate()mantiene la password esistente quando il campo è vuoto. (Risolto il 05/08/2026:api_passwordora cifrata con GLPIKey — vedi sezione aggiornata.) - Validazione input server:
protocolwhitelistatohttp|https(prima qualsiasi stringa finiva ingetWebInterfaceUrl()→ href e URL cURL) eportclampato1..65535inprepareInputForAdd/prepareInputForUpdate. front/asset.form.phpdisconnect: condizione diritti allineata aServerAsset::disconnectAsset()(solo UPDATE; prima UPDATE|DELETE incoerente).front/server.form.php:$_GET['id']castato a int.
Verifiche
php -lOK su tutti i file PHP del plugin.- Smoke test HTTP (ambiente locale, senza auth):
server_test.ajax.phpGET→302 (login), POST→403 (denied),config.form.phpeserver.php→ 200 senza fatal error. (Nota: la verifica del 05/08/2026 ha confermato che il server UrBackup locale è attivo sulocalhost:55414e 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
→ fatto:api_passwordin chiaro suglpi_plugin_urbackup_servers→ cifrare conGLPIKey(new GLPIKey())->encrypt()inprepareInputForAdd/Update+ migrazioneplugin_urbackup_install_encrypt_api_passwords()+getApiPassword()con fallback legacy.→ fatto: solo sesetup.phptop-level: loopopcache_invalidate()ad OGNI richiesta webGLPI_ENVIRONMENT_TYPE === 'development'.→ fatto: solo se i parametri di connessione cambiano.Server::prepareInputForUpdate(): test connessione API ad ogni save→ fatto: batch loading per itemtype.Server::showUnlinkedClientsTab(): N+1 suServerAsset::getAssetName()
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
AGENTS.md(sovrascritto — prima era identico aAGENTS.md_netbackup): ruolo Senior GLPI Plugin Architect, regole assolute (Git rule con autorizzazione esplicita, strict types PHP 8.3/8.4, namespaceGlpiPlugin\Urbackup\), CSRF GLPI 11 (CheckCsrfListener globale, nienteSession::checkCSRF()nei front), limiti ambiente locale (server UrBackup locale verificato attivo:localhost:55414), struttura v0.7.0 e hook essenziali.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.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 ininstall/install.php(mai in upgrade/uninstall).Html::hidden()in GLPI 11 usa la firma(name, options)— patternHtml::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]).- Hook
plugin_urbackup_MassiveActions($type)riceve l'itemtype come stringa. api_passwordin chiaro: limite noto → risolto il 05/08/2026 con cifratura(new GLPIKey())->encrypt()/decrypt()(non esisteGLPIKey::getInstance()).
Server UrBackup per test in locale (VERIFICATO ATTIVO)
- URL:
http://localhost:55414 - Credenziali: utente
admin, password12345678 - Scopo: eseguire API semplici (login, status, backup) in locale per debugging e validazione rapida.
- Stato: processo
urbackupsrvattivo (porta 55414), endpoint/x?a=salte 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-368chiama$definition->hasCapacity(UrBackupCapacity::class)e$definition->enableCapacity(UrBackupCapacity::class)— in GLPI 11.0.8 suAssetDefinitionesistono SOLOhasCapacityEnabled(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 diGlpi\Asset\Asset(tab sempre visibile). - TODO: correggere con l'API core corretta: check con
hasCapacityEnabled(new UrBackupCapacity())(richiede un oggettoCapacityInterface); l'abilitazione/disabilitazione in GLPI 11.0.8 avviene via inputcapacitiesdel form (array di spec) processato inAssetDefinition::post_updateItem()(AssetDefinition.php:316-440) — NON esistono metodi pubblicienableCapacity/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 viaCheckCsrfListener - 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 headerX-Glpi-Csrf-TokencongetAjaxCsrfToken()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:
Asset::__construct()— itera 30+ Capacity classi per ogni istanzaAsset::post_getFromDB()— decodifica JSON custom fields e li processaeval()autoloading — le classi concrete sono definite viaeval()a runtime
Ottimizzazioni applicate
AssetTab.php::loadApiData()— letto$item->fields['name']direttamente invece di chiamareServerAsset::getAssetName()che faceva una secondagetFromDB()ridondanteAssetTab.php::startBackup(),saveInternetMode(),saveDefaultDirs(),showServerLinkedBlock()— stesso pattern,$item->fields['name']al posto digetAssetName()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— registraAssetTabviaCommonGLPI::registerStandardTab()inonClassBootstrap()setup.php— registra capacità, CSS, hooksrc/AssetTab.php— display tab content + tab interni (Stato/Azioni/Info-Log) + host server blocksrc/ServerAsset.php— gestione collegamenti asset-serversrc/Config.php— itemtype enabled checksrc/UrbackupApiClient.php— client API con caching in-memory (per istanza) e sessionesrc/LocationHelper.php— risoluzione location radicesrc/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 pergetStatus()egetClientSettings()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)
src/Server.php:436—$canUpdatecontrollava soloSession::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.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 = 0e nessun profilo riceveva i diritti completi.
Fix applicati
Server.php::showFormFields()— Sostituito$canUpdatecon$canEdit: per nuovi server ($ID <= 0) usaCREATE, per server esistenti usaUPDATE.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:
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
install/mysql/plugin_urbackup-empty.sql— Aggiunta colonnausers_id INT UNSIGNED NOT NULL DEFAULT 0dopolocations_id+ indiceinstall/install.php— Aggiunto$migration->addField('users_id', ...)e$migration->addKey('users_id')nella migrazione del serversrc/Server.php— Aggiunto search optionid=8perUser::getTable()+ auto-set diusers_idda$_SESSION['glpiID']inprepareInputForAdd()
Versione
- 0.7.3
0.7.3 — Link Server ↔ asset host hardware
- Colonne
host_itemtype/host_items_id+ KEYhost_assetsuglpi_plugin_urbackup_servers(migrazione idempotente + empty.sql) → bump 0.7.2 → 0.7.3 (regola 6). - Form server: riga "Hardware host" con dropdown tipo (
Dropdown::showItemTypes) + dropdown elementi AJAX ($.getsufront/dropdown_host.ajax.php, vedi sezione Fix del 07/08/2026). - AssetTab: blocco "This asset hosts the UrBackup server" (sempre visibile, anche se client).
- Validazione in
prepareInputForUpdate()+ helpergetHostAsset()/getServersHostingAsset()(guardfieldExists).
0.7.2 — Toggle Computer configurabile + lista Asset custom con capacità attiva
enable_computeringlpi_plugin_urbackup_configs(default '1') letto daConfig::getEnableComputer(); registrazione tab su Computer condizionale in setup.php;isItemtypeEnabled/getEnabledItemtypesrispettano il toggle.- Config page: dropdown Sì/No per Computer + lista Asset Definition con capacità UrBackup attivata (
getEnabledAssetDefinitions()). - front/config.form.php: handler POST
updateripristinato (upsert, redirect, messaggio). - DB: migrazione idempotente + INSERT in empty.sql → bump 0.7.2 (regola 6).
0.7.1 — Cifratura api_password (GLPIKey), ottimizzazioni e localizzazioni
api_passwordcifrata con(new GLPIKey())->encrypt()on save (Server::prepareInputForAdd/Update),Server::getApiPassword()con fallback legacy, migrazione idempotenteplugin_urbackup_install_encrypt_api_passwords()(install.php) — applicata al DB (server id=1).- Test connessione API solo a parametri cambiati in
prepareInputForUpdate()(niente più chiamate da 30s ad ogni save). - Loop
opcache_invalidate()solo in development (GLPI_ENVIRONMENT_TYPE === 'development'). - Fix N+1 in
Server::showUnlinkedClientsTab()(batch loading per itemtype). - Localizzazioni: 115 nuove traduzioni it/de/en, 9 orfane rimosse,
.moricompilati. - 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.mdaggiornato.
0.7.0 — Pulizia, sicurezza e DB cleanup
- CSRF hardening:
Session::checkCSRF()aggiunto suasset.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) - File deprecati rimossi: 12 file (composer copy, AGENTS_OLD.MD, js/, ajax/, front/test/view, FIX_PERMISSIONS.sh, removed/)
- Dead code rimosso: 3 Controller, 1 Command, 4 template Twig
- DB migration: DROP
glpi_plugin_urbackup_profileseglpi_plugin_urbackup_assettypes; add indexlocation_activesu servers; adddate_creation/date_modsu serverassets - SQL schema pulito: Rimosse tabelle legacy dall'empty.sql
- PHP lint warnings: Rimosse
use Html;euse Search;superflue - README.md creato
- PURGE right: Aggiunto PURGE ai diritti di installazione (
Profile.php:76,90); applicato a tutti i profili Super-Admin esistenti nel DB - CSRF GLPI 11 fix:
Session::checkCSRF()richiede$_POSTcome argomento in GLPI 11; aggiuntoX-Glpi-Csrf-Tokenheader al JS AJAX - Session::isDebugActive(): Metodo inesistente in GLPI 11; sostituito in
AssetTab.php:150con($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE