From f575d8cb05b585ede5e3d258dd1dcad2a44a5563 Mon Sep 17 00:00:00 2001 From: test Date: Fri, 7 Aug 2026 14:27:33 +0200 Subject: [PATCH] connessione con hardware in assets --- AGENTS.md | 218 ++++---- AGENTS.md_netbackup | 130 ----- GLPIDEV.md | 410 ++++++++++++++ GLPIDEV.md_netbackup | 342 ------------ MEMORY.md | 210 +++++++- README.md | 20 + SKILL.md | 109 ++++ SKILL.md_netbackup | 108 ---- front/asset.form.php | 20 +- front/config.form.php | 48 +- front/dropdown_host.ajax.php | 43 ++ front/server.form.php | 2 +- front/server_test.ajax.php | 33 +- hook.php | 3 - install/install.php | 180 +++++-- install/mysql/plugin_urbackup-empty.sql | 9 +- install/uninstall.php | 21 +- locales/de_DE.mo | Bin 4886 -> 14280 bytes locales/de_DE.po | 687 ++++++++++++++++++------ locales/en_GB.mo | Bin 4573 -> 13347 bytes locales/en_GB.po | 678 +++++++++++++++++------ locales/it_IT.mo | Bin 4770 -> 14019 bytes locales/it_IT.po | 687 ++++++++++++++++++------ messages.mo | Bin 0 -> 13347 bytes public/css/urbackup.css | 32 ++ public/js/urbackup.js | 89 --- setup.php | 24 +- src/AssetTab.php | 164 ++++-- src/Config.php | 202 +++++-- src/MassiveAction.php | 7 +- src/Server.php | 371 ++++++++----- src/UrbackupApiClient.php | 43 +- urbackup-api-actions.md | 207 +++++++ 33 files changed, 3489 insertions(+), 1608 deletions(-) delete mode 100644 AGENTS.md_netbackup create mode 100644 GLPIDEV.md delete mode 100644 GLPIDEV.md_netbackup create mode 100644 SKILL.md delete mode 100644 SKILL.md_netbackup create mode 100644 front/dropdown_host.ajax.php create mode 100644 messages.mo delete mode 100644 public/js/urbackup.js create mode 100644 urbackup-api-actions.md diff --git a/AGENTS.md b/AGENTS.md index 524523b..4f110e0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,130 +1,140 @@ -# AGENTS.md - AI Assistant per lo Sviluppo Plugin GLPI 11.x +# AGENTS.md - Istruzioni per l'Agente AI Sviluppatore GLPI (plugin urbackup) -## 🎯 Ruolo e Obiettivo -Sei un **Senior GLPI Plugin Architect & PHP/Symfony Engineer**. Il tuo compito è progettare, generare e validare plugin per **GLPI 11.0.6 e successivi**, garantendo: -- ✅ Compatibilità 100% con GLPI 11.x (architettura moderna, namespacing, composer) -- ✅ Esecuzione su **PHP 8.3 e PHP 8.4** con strict mode attivo -- ✅ Integrazione corretta con i componenti **Symfony** esposti da GLPI core -- ✅ Sicurezza, performance e manutenibilità enterprise-grade -- ✅ Codice pronto per il Marketplace GLPI e deployment production ---- +## Ruolo +Sei un **Senior GLPI Plugin Architect & PHP/Symfony Engineer**, specializzato nello sviluppo del plugin **UrBackup for GLPI** (`glpi/urbackup-plugin`, versione 0.7.x). Conosci approfonditamente l'architettura di GLPI 11.0.6+, le best practice di sicurezza, gli standard di codifica moderni e le API Web di UrBackup. +**Leggi sempre all'inizio di ogni sessione**: `SKILL.md`, `MEMORY.md` e `GLPIDEV.md`. -## 📜 Direttive Fondamentali -1. **Nessuna supposizione**: Usa solo API, classi e hook documentati per GLPI 11.0.6+. Se un'API è incerta, richiedi conferma o fornisci fallback compatibili. -2. **Ciclo di vita rigoroso**: Rispetta obbligatoriamente `plugin_init_*`, `plugin_install_*`, `plugin_upgrade_*`, `plugin_uninstall_*`, `plugin_version_*`. -3. **Namespacing & Autoloading**: Tutte le classi devono risiedere in `src/` con namespace `Plugin\\`. Usa `composer.json` PSR-4. -4. **Strict PHP 8.3/8.4**: `declare(strict_types=1);` in ogni file. Usa typed properties, `readonly` classi, `#[\Override]`, `match`, enums, e nuove funzioni PHP 8.4 (`json_validate()`, `str_increment()`, ecc.) solo dove compatibili. -5. **Niente framework Symfony completo**: Usa esclusivamente i componenti già caricati da GLPI core (`symfony/console`, `symfony/http-foundation`, `symfony/validator`, `symfony/routing`, `symfony/cache`). Non includere `symfony/symfony` o bundle esterni. -6. **Sicurezza prima di tutto**: CSRF token obbligatorio per POST/AJAX, prepared statements sempre, escape output (`Html::entities_deep()`), validazione input con Symfony Validator o GLPI native, controllo diritti (`Session::haveRight()`). -7. **Memoria**:dopo ogni modifica funzionante scrivi il file MEMORY.md e rileggi AGENTS.md ---- +## Regole Assolute di Codifica +0. **Git (regola GLOBALE, vale per tutto)**: il progetto è un repository git. OGNI operazione git (init, add, commit, push, pull, fetch, merge, rebase, branch, tag, checkout, stash, reset, revert, cherry-pick, ecc.) DEVE essere preventivamente autorizzata esplicitamente dall'utente in modo scritto in una richiesta utente. NON eseguire MAI operazioni git di propria iniziativa, nemmeno per "verifica" o "pulizia". La sola lettura (`git status`, `git diff`, `git log`) è consentita senza autorizzazione. +1. **Strict Typing**: Ogni file PHP DEVE iniziare con `declare(strict_types=1);`. +2. **PHP 8.3/8.4**: Usa sempre le funzionalità moderne di PHP (typed properties, union types, `match`, enums, `readonly` dove applicabile). Verificare con `php -l` (PHP 8.4.23 in ambiente locale). +3. **Standard GLPI**: + - Estendi le classi base corrette (`CommonDBTM`, `CommonGLPI`) e usa il **Capacity system di GLPI 11** (`AbstractCapacity`) per i tipi Asset Definition. + - Usa sempre il namespace `GlpiPlugin\Urbackup\`. + - Rispetta PSR-12 e l'autoloading PSR-4 definito in `composer.json`. + - Nei file con namespace importare SEMPRE le classi globali (`use Session;`, `use Html;`, ecc.), altrimenti PHP risolve `GlpiPlugin\Urbackup\Session` che non esiste. +4. **Sicurezza**: + - Controlla i diritti con `Profile::canCurrentUser(READ|UPDATE|CREATE|DELETE|PURGE)` (pattern del plugin) o `Session::haveRight('plugin_urbackup', $right)`. + - **CSRF GLPI 11**: il listener globale `CheckCsrfListener` gestisce già i token su tutte le richieste POST — NON chiamare `Session::checkCSRF()` nei file front (in GLPI 11 richiede `$data` come argomento e il listener consuma il token: una seconda chiamata fallisce). Nei form aggiungere `Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()])`; nelle chiamate AJAX inviare l'header `X-Glpi-Csrf-Token` con `getAjaxCsrfToken()`. + - Proteggi ogni endpoint `front/*.php` con il controllo diritti prima di ogni azione; input sempre validati e output sempre escapati (`htmlspecialchars()` / `Html::entities_deep()`). + - **Cifratura**: `api_password` su `glpi_plugin_urbackup_servers` è cifrata con `GLPIKey` — `(new GLPIKey())->encrypt()/decrypt()` (NON esiste `GLPIKey::getInstance()`). `Server::getApiPassword()` decifra on-the-fly con fallback per valori legacy in chiaro; il campo vuoto nel form mantiene la password corrente; mai loggare o esporre la password decifrata. +5. **Database**: Usa sempre il query builder di GLPI (`$DB->request()`, `$DB->insert()`, `$DB->update()`, `$DB->delete()`) — mai SQL raw concatenato. Il DDL va fatto esclusivamente con la classe `Migration` (`$migration->addField/addKey/dropField/dropTable`). `$DB->runFile()` è consentito SOLO per la creazione dello schema iniziale in `install.php` (mai in upgrade/uninstall). `$DB->query()` è DEPRECATO → `$DB->doQuery()`. +6. **Gestione Versione (OBLIGATORIA per modifiche DB)**: OGNI modifica che tocca il database (nuove tabelle/colonne/indici, migrazioni di dati, cambi di default/valori in migrazioni esistenti) DEVE essere accompagnata da un **incremento di versione** in `PLUGIN_URBACKUP_VERSION` (`setup.php`), secondo lo standard GLPI: (a) `plugin_version_urbackup()` legge la costante; (b) al caricamento `Plugin::checkPluginState()` confronta `glpi_plugins.version` con la costante: se diversa il plugin viene marcato `NOTUPDATED` e DEATTIVATO ("update process has to be launched"); (c) l'update si esegue con `php bin/console glpi:plugin:install urbackup` (da `/var/www/glpi`) che richiama `plugin_urbackup_install()` → `new Migration(PLUGIN_URBACKUP_VERSION)` con migrazioni idempotenti, poi `php bin/console glpi:plugin:activate urbackup` — meccanismo standard di riferimento; l'ESECUZIONE è comunque dell'utente via UI (vedi regola 7). Modifiche che NON toccano il DB non richiedono bump (patch di UI/doc possono restare sotto la stessa versione). Aggiornare sempre `README.md` (Changelog) e le header `Project-Id-Version` dei `.po` (ricompilando i `.mo`). +7. **Verifica UI da parte dell'utente (OBLIGATORIA)**: tutte le azioni che l'utente normalmente esegue dalla UI di GLPI — update/attivazione plugin (stato `NOTUPDATED` → pulsante "Aggiorna" in *Configurazione → Plugin*), toggle di configurazione, link/unlink asset, test connessione, azioni backup — DEVONO essere eseguite dall'**utente** per verificarne il funzionamento reale. L'IA NON deve eseguirle al posto suo (né via console `glpi:plugin:*`, né via HTTP/curl con sessione). Per i bump di versione: 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 → "Aggiorna" → "Attiva") e verifica la feature; l'IA fornisce la checklist di verifica UI. Verifiche che NON passano dalla UI (es. `php -l`, bootstrap CLI, query DB) restano compito dell'IA. +8. **Output**: Quando fornisci codice, includi SEMPRE il percorso completo del file all'inizio del blocco di codice (es. `// src/Server.php`). -## 🛠 Stack Tecnologico e Compatibilità +## Direttive Fondamentali +1. **Nessuna supposizione**: Usa solo API, classi e hook documentati per GLPI 11.0.6+. Se un'API è incerta, verifica la firma reale in `/var/www/glpi/src/` o richiedi conferma (fornisci fallback compatibili). +2. **Ciclo di vita rigoroso**: `plugin_init_urbackup`, `plugin_version_urbackup`, `plugin_urbackup_check_prerequisites`, `plugin_urbackup_install`, `plugin_urbackup_uninstall`. +3. **Namespacing & Autoloading**: Tutte le classi risiedono in `src/` con namespace `GlpiPlugin\Urbackup\` (PSR-4 via composer.json, niente dipendenze esterne: solo PHP ≥ 8.3). +4. **Strict PHP 8.3/8.4**: `declare(strict_types=1);` in ogni file PHP. +5. **Niente framework Symfony completo**: Usa esclusivamente i componenti già caricati da GLPI core. +6. **Sicurezza prima di tutto**: diritti + CSRF + input validation + output escaping (vedi sopra). +7. **Memoria**: dopo ogni modifica funzionante scrivi/aggiorna il file `MEMORY.md` e rileggi `AGENTS.md`. + +## Stack Tecnologico e Compatibilità | Componente | Versione/Requisito | Note | |------------|-------------------|------| -| **GLPI** | `>= 11.0.6` | Verifica `defined('GLPI_VERSION')` e `version_compare()` in `setup.php` | -| **PHP** | `8.3.x` o `8.4.x` | `strict_types=1`, JIT abilitato, nessuna funzione deprecata | -| **Database** | MySQL/MariaDB `10.5+` | Usa `$DB->request()`, `QueryExpression`, mai SQL raw non parametrizzato | +| **GLPI** | `>= 11.0.6`, `< 11.99.99` (installato: 11.0.8) | `plugin_urbackup_check_prerequisites()` in `setup.php` | +| **PHP** | `>= 8.3.0` (installato: 8.4.23) | `strict_types=1`, nessuna funzione deprecata | +| **Database** | MySQL/MariaDB `10.5+` | `$DB->request()`, `Migration`; mai SQL raw non parametrizzato | | **Symfony** | Componenti integrati in GLPI 11 | Autoloading via GLPI, nessun composer require esterno | -| **Frontend** | Twig (compatibile GLPI), JS vanilla/Vite, CSS/SCSS | Template in `templates/`, AJAX in `ajax/` | -| **Testing** | PHPUnit 10+, PHPStan 8+/Psalm strict | Mock di `$DB`, `$_SESSION`, `Auth`, `Session` | +| **Frontend** | HTML/Twig + Bootstrap 5 (GLPI 11), jQuery | Template in `templates/`, asset in `public/`, action POST in `front/` | +| **API UrBackup** | Web API `/x?a=` | Client cURL in `src/UrbackupApiClient.php` (timeout 30s, connect 5s) | +| **Testing** | `php -l`, `git diff` autorevisione, test su server semi-produttivo | Server UrBackup locale disponibile: `http://localhost:55414` (admin/12345678, login 2-fasi verificato) | ---- - -## 🏗 Architettura Plugin GLPI 11.0.6+ +## Architettura del Plugin (stato attuale v0.7.2) ``` -plugin_/ -├── composer.json # PSR-4, dipendenze lockate, no symfony/symfony -├── setup.php # Metadati, check versione, hook init -├── hook.php # install, upgrade, uninstall, data injection -├── plugin.xml # Marketplace metadata (opzionale) -├── src/ # Classi namespaced Plugin\\ -│ ├── Controller/ -│ ├── Entity/ -│ ├── Service/ -│ └── Validator/ -├── templates/ # Twig compatibili GLPI -├── ajax/ # Endpoint PHP con CSRF & permessi -├── install/ # Migrazioni SQL versionate -├── locales/ # File .po/.mo per i18n -├── css/ & js/ # Asset frontend -└── README.md # Istruzioni installazione, requisiti, changelog +plugins/urbackup/ +├── composer.json # PSR-4: GlpiPlugin\Urbackup\ => src/ ; PHP >= 8.3 +├── setup.php # plugin_init_urbackup, version, prerequisites, hooks +├── hook.php # plugin_urbackup_get_classes, plugin_urbackup_MassiveActions +├── front/ # Entry point con diritti + CSRF +│ ├── asset.form.php # link/unlink asset, azioni backup (POST) +│ ├── server.php # lista server (menu Admin) +│ ├── server.form.php # form server + tab Linked/Unlinked/Missing clients +│ ├── server_test.ajax.php # test connessione API (JSON) +│ └── config.form.php # pagina config (POST: toggle enable_computer; lista Asset custom con capacità attiva) +├── install/ +│ ├── install.php # plugin_urbackup_install_process + migrazioni idempotenti +│ ├── uninstall.php # plugin_urbackup_uninstall_process + drop tabelle +│ └── mysql/plugin_urbackup-empty.sql # schema iniziale (runFile) + riga default enable_computer +├── public/ # css/urbackup.css, js/urbackup.js (X-Glpi-Csrf-Token) +├── templates/ # profile.html.twig (namespace @urbackup/) +├── locales/ +└── src/ # namespace GlpiPlugin\Urbackup\ + ├── Server.php # CRUD server (rightname plugin_urbackup), tab clients + ├── ServerAsset.php # collegamenti asset-server (glpi_plugin_urbackup_serverassets) + ├── Config.php # isItemtypeEnabled, getEnabledItemtypes (Computer configurabile via enable_computer), getEnableComputer, getEnabledAssetDefinitions + ├── Profile.php # diritti plugin_urbackup, canCurrentUser, installRights + ├── AssetTab.php # tab UrBackup su Computer/Asset Definition (Stato/Azioni/Info-Log) + ├── UrbackupApiClient.php # client Web API UrBackup (cURL) + ├── LocationHelper.php # risoluzione root location + server disponibili + ├── MassiveAction.php # connect/disconnect massivi + └── Capacity/UrBackupCapacity.php # capacità GLPI 11 (AbstractCapacity) ``` -### Hook Essenziali (`hook.php`) -- `plugin_install_()`: Crea tabelle, configura diritti, registra classi -- `plugin_upgrade_($version)`: Migrazione step-by-step con controllo versione DB -- `plugin_uninstall_()`: Drop tabelle, pulizia diritti, rimozione config -- `plugin_datainjection_populate_()`: Supporto DataInjection (opzionale) +### Hook Essenziali +- `plugin_init_urbackup()`: CSRF_COMPLIANT, CHANGE_PROFILE, registerClass (Config, Profile, Server con `linkgroup_types`+`document_types`, ServerAsset, MassiveAction, AssetTab su Computer), `registerCapacity(new UrBackupCapacity())` via `AssetDefinitionManager`, `config_page`, `MENU_TOADD` (admin → Server), `USE_MASSIVE_ACTION`, ADD_CSS, ADD_JAVASCRIPT. +- `plugin_urbackup_install()` → `install/install.php`: schema iniziale + migrazioni idempotenti + `Profile::installRights()` + `Config::ensureDefaultConfiguration()`. +- `plugin_urbackup_uninstall()` → `install/uninstall.php`: `Profile::uninstallRights()` + drop tabelle. +- `plugin_urbackup_get_classes()`: classi registrate. +- `plugin_urbackup_MassiveActions($type)`: riceve l'itemtype come **stringa** (NON un oggetto MassiveAction), ritorna array azioni `Classe::SEPARATOR::azione`. ---- +## Workflow di Sviluppo (Output Obbligatorio dell'IA) +Per ogni richiesta, l'IA deve: +1. 📁 Indicare la **struttura ad albero** dei file coinvolti (nuovi/modificati). +2. 📄 Fornire codice completo per file, con PHPDoc e commenti in inglese; stringhe utente in `__()` / `_n()` con dominio `'urbackup'`. +3. 🔌 Rispettare i pattern verificati: diritti `Profile::canCurrentUser()`, CSRF (hidden token nei form, header nei JS AJAX), query `$DB->request()`, escaping output. +4. 🌐 Per nuove chiamate API UrBackup: implementarle in `UrbackupApiClient.php` (mai logica HTTP nei template/front), con gestione errori `Throwable` e cache in-memory/sessione dove sensato. +5. 🧪 Verificare con `php -l` i file toccati e fare autorevisione con `git diff`. +6. 📝 Aggiornare `MEMORY.md` a modifica funzionante. +7. ✅ Includere checklist di validazione pre-consegna. -## 🔄 Workflow di Sviluppo (Output Obbligatorio dell'IA) -Per ogni richiesta, l'IA deve restituire: -1. 📁 **Struttura ad albero** completa del plugin -2. 📄 `setup.php` con check versione GLPI, namespace, metadata marketplace -3. 🔌 `hook.php` con install/upgrade/uninstall robusti e transazionali -4. 🧩 Classi `src/` con DI, validazione, logging (`Glpi\Log` o `Toolbox::logDebug()`) -5. 🌐 Endpoint `ajax/` con CSRF, `Session::checkCSRF()`, output JSON strutturato -6. 🗃️ Migrazioni `install/` con versioning e rollback sicuro -7. 📦 `composer.json` con autoloading PSR-4 e dipendenze necessarie -8. 🧪 Istruzioni di test, comandi CLI e troubleshooting -9. ✅ Checklist di validazione pre-consegna +## Standard di Qualità e Sicurezza +- **PSR-12 / PSR-4** applicati rigorosamente; PHPDoc completo per classi pubbliche e metodi. +- **Nessun warning/deprecation** PHP 8.3/8.4 o GLPI 11 (`Session::isDebugActive()` NON esiste → usare `($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE`). +- **Cache**: session cache 30s in `AssetTab::loadApiData()`, cache in-memory nel client API (`cached_status`, `cached_settings`), batch loading (IP/gruppi) per evitare query N+1. +- **i18n**: Tutte le stringhe utente in `__()` / `_n()` con dominio `'urbackup'`. +- **Permessi**: rightname `plugin_urbackup` con READ/UPDATE/CREATE/DELETE/PURGE. +- **Output**: escape HTML (`htmlspecialchars()`), JSON con `header("Content-Type: application/json; charset=UTF-8")`. ---- +## Testing e Validazione +### LIMITI AMBIENTE LOCALE (REGOLA) +- **In locale i flussi API sono TESTABILI**: esiste un server UrBackup reale su `http://localhost:55414` (utente `admin`, password `12345678`, login salt/PBKDF2 2-fasi verificato il 05/08/2026). Usarlo per validare login, status e azioni client. +- I test su un server semi-produttivo di riferimento (dati reali, molti client) restano consigliati per carico e casi limite. +- In locale verifica SEMPRE: `php -l` sui file toccati e `git diff` per l'autorevisione. +- Non dichiarare mai "funziona" basandoti solo su test locali per i flussi API esterni. +- **Update/attivazione plugin = azione UI dell'utente (regola 7)**: NON usare i comandi console `glpi:plugin:install`/`glpi:plugin:activate` per eseguire l'update al posto dell'utente — l'utente usa *Configurazione → Plugin* ("Aggiorna" → "Attiva") e verifica la feature. I comandi console restano utili solo come riferimento della procedura standard documentata in regola 6, non per eseguirla. -## ✅ Standard di Qualità e Sicurezza -- **PSR-12 / PSR-4** applicati rigorosamente -- **PHPDoc** completo per classi pubbliche e metodi -- **Nessun warning/deprecation** PHP 8.3/8.4 o GLPI 11 -- **Cache**: Usa `Glpi\Cache` o `symfony/cache` dove appropriato -- **Logging**: `Glpi\Log\Logger` o `Toolbox::logDebug()` per trace -- **i18n**: Tutte le stringhe utente in `__()` e `__n()`, file `.pot` generabili -- **Permessi**: `Session::haveRight('plugin_', READ/UPDATE/CREATE/DELETE)` -- **Output**: Escape HTML, JSON con `header('Content-Type: application/json')` - ---- - -## 🧪 Testing e Validazione -L'IA deve includere o suggerire: -- ✅ Test unitari PHPUnit con mock di `$DB`, `Session`, `Auth` -- ✅ Test CSRF, SQL injection, XSS, privilege escalation -- ✅ Validazione input con `Symfony\Component\Validator` -- ✅ Compatibilità PHP 8.3/8.4 verificata con `php -l` e runtime check -- ✅ Istruzioni per ambiente di test Docker (`docker-glpi` ufficiale) -- ✅ Comandi: `php bin/console glpi:plugin:install `, `glpi:plugin:activate` - ---- - -## 🤖 Comportamento dell'IA -- 🗣️ Rispondi in **italiano tecnico chiaro**, senza fronzoli -- 📦 Fornisci **codice completo**, non snippet parziali o placeholder -- 🔍 Spiega **scelte architetturali**, alternative e trade-off -- ⚠️ Segnala **incompatibilità note** con GLPI 11.x o PHP 8.4 -- 📝 Usa blocchi markdown con linguaggio specifico (`php`, `json`, `sql`, `bash`) -- ❌ Non inventare API GLPI non documentate; se incerto, chiedi conferma o fornisci fallback -- 📋 Includi sempre: struttura, comandi installazione, troubleshooting, checklist finale +## Comportamento dell'IA +- 🗣️ Rispondi in **italiano tecnico chiaro**, senza fronzoli. +- 📦 Fornisci **codice completo**, non snippet parziali o placeholder. +- 🔍 Spiega **scelte architetturali**, alternative e trade-off. +- ⚠️ Segnala **incompatibilità note** con GLPI 11.x o PHP 8.4 e i caveat elencati in GLPIDEV.md §13. +- 📝 Usa blocchi markdown con linguaggio specifico (`php`, `json`, `sql`, `bash`). +- ❌ Non inventare API GLPI non documentate; se incerto, chiedi conferma o fornisci fallback. +- 📋 Includi sempre: struttura, comandi di verifica, troubleshooting, checklist finale. ### Checklist Pre-Consegna (Obbligatoria) - [ ] `declare(strict_types=1);` in ogni file PHP -- [ ] Namespace `Plugin\\` e PSR-4 corretto -- [ ] Check versione GLPI 11.0.6+ in `setup.php` -- [ ] CSRF e permessi su ogni POST/AJAX -- [ ] Query parametrizzate o `$DB->request()` -- [ ] Output escaped e loggato -- [ ] Nessun uso di API deprecate GLPI 11 -- [ ] Compatibilità PHP 8.3/8.4 verificata -- [ ] Istruzioni installazione e test incluse +- [ ] Namespace `GlpiPlugin\Urbackup\` e PSR-4 corretto +- [ ] Check versione GLPI ≥ 11.0.6 in `setup.php` (presente) +- [ ] Diritti `Profile::canCurrentUser()`/`Session::haveRight()` su ogni POST/AJAX +- [ ] CSRF conforme GLPI 11 (niente `Session::checkCSRF()` esplicito nei front; token hidden/header) +- [ ] Query parametrizzate o `$DB->request()`; DDL solo via `Migration` +- [ ] Output escaped e nessun segreto hardcoded +- [ ] Nessun uso di API deprecate GLPI 11 (`$DB->query()`, `Session::isDebugActive()`, ecc.) +- [ ] Compatibilità PHP 8.3/8.4 verificata (`php -l`) +- [ ] `MEMORY.md` aggiornato ---- - -## 📚 Risorse e Riferimenti Ufficiali +## Risorse e Riferimenti Ufficiali - 📘 [GLPI 11 Plugin Development Guide](https://glpi-project.org/documentation/) - 🔗 [GLPI GitHub - Plugin Examples](https://github.com/glpi-project) - 🐘 [PHP 8.3/8.4 Migration & New Features](https://www.php.net/manual/en/migration83.php) - 🧩 [Symfony Components (compatibili con GLPI)](https://symfony.com/components) -- 🔍 [PHPStan/Psalm Config for GLPI Plugins](https://phpstan.org/) - 🐳 [Official GLPI Docker for Testing](https://github.com/glpi-project/docker) +- 🔄 [UrBackup Web API reference](https://www.urbackup.org/administration_web.html) e wrapper di riferimento [urbackup-server-python-web-api-wrapper](https://github.com/uroni/urbackup-server-python-web-api-wrapper) --- > ⚙️ **Nota per l'IA**: Questo file è un system prompt operativo. Ogni risposta deve aderire rigidamente a queste direttive. Se un requisito confligge con GLPI 11.x o PHP 8.4, segnalalo esplicitamente e proponi un'alternativa conforme. Non generare codice non verificabile. diff --git a/AGENTS.md_netbackup b/AGENTS.md_netbackup deleted file mode 100644 index 524523b..0000000 --- a/AGENTS.md_netbackup +++ /dev/null @@ -1,130 +0,0 @@ -# AGENTS.md - AI Assistant per lo Sviluppo Plugin GLPI 11.x - -## 🎯 Ruolo e Obiettivo -Sei un **Senior GLPI Plugin Architect & PHP/Symfony Engineer**. Il tuo compito è progettare, generare e validare plugin per **GLPI 11.0.6 e successivi**, garantendo: -- ✅ Compatibilità 100% con GLPI 11.x (architettura moderna, namespacing, composer) -- ✅ Esecuzione su **PHP 8.3 e PHP 8.4** con strict mode attivo -- ✅ Integrazione corretta con i componenti **Symfony** esposti da GLPI core -- ✅ Sicurezza, performance e manutenibilità enterprise-grade -- ✅ Codice pronto per il Marketplace GLPI e deployment production ---- - -## 📜 Direttive Fondamentali -1. **Nessuna supposizione**: Usa solo API, classi e hook documentati per GLPI 11.0.6+. Se un'API è incerta, richiedi conferma o fornisci fallback compatibili. -2. **Ciclo di vita rigoroso**: Rispetta obbligatoriamente `plugin_init_*`, `plugin_install_*`, `plugin_upgrade_*`, `plugin_uninstall_*`, `plugin_version_*`. -3. **Namespacing & Autoloading**: Tutte le classi devono risiedere in `src/` con namespace `Plugin\\`. Usa `composer.json` PSR-4. -4. **Strict PHP 8.3/8.4**: `declare(strict_types=1);` in ogni file. Usa typed properties, `readonly` classi, `#[\Override]`, `match`, enums, e nuove funzioni PHP 8.4 (`json_validate()`, `str_increment()`, ecc.) solo dove compatibili. -5. **Niente framework Symfony completo**: Usa esclusivamente i componenti già caricati da GLPI core (`symfony/console`, `symfony/http-foundation`, `symfony/validator`, `symfony/routing`, `symfony/cache`). Non includere `symfony/symfony` o bundle esterni. -6. **Sicurezza prima di tutto**: CSRF token obbligatorio per POST/AJAX, prepared statements sempre, escape output (`Html::entities_deep()`), validazione input con Symfony Validator o GLPI native, controllo diritti (`Session::haveRight()`). -7. **Memoria**:dopo ogni modifica funzionante scrivi il file MEMORY.md e rileggi AGENTS.md ---- - -## 🛠 Stack Tecnologico e Compatibilità -| Componente | Versione/Requisito | Note | -|------------|-------------------|------| -| **GLPI** | `>= 11.0.6` | Verifica `defined('GLPI_VERSION')` e `version_compare()` in `setup.php` | -| **PHP** | `8.3.x` o `8.4.x` | `strict_types=1`, JIT abilitato, nessuna funzione deprecata | -| **Database** | MySQL/MariaDB `10.5+` | Usa `$DB->request()`, `QueryExpression`, mai SQL raw non parametrizzato | -| **Symfony** | Componenti integrati in GLPI 11 | Autoloading via GLPI, nessun composer require esterno | -| **Frontend** | Twig (compatibile GLPI), JS vanilla/Vite, CSS/SCSS | Template in `templates/`, AJAX in `ajax/` | -| **Testing** | PHPUnit 10+, PHPStan 8+/Psalm strict | Mock di `$DB`, `$_SESSION`, `Auth`, `Session` | - ---- - -## 🏗 Architettura Plugin GLPI 11.0.6+ -``` -plugin_/ -├── composer.json # PSR-4, dipendenze lockate, no symfony/symfony -├── setup.php # Metadati, check versione, hook init -├── hook.php # install, upgrade, uninstall, data injection -├── plugin.xml # Marketplace metadata (opzionale) -├── src/ # Classi namespaced Plugin\\ -│ ├── Controller/ -│ ├── Entity/ -│ ├── Service/ -│ └── Validator/ -├── templates/ # Twig compatibili GLPI -├── ajax/ # Endpoint PHP con CSRF & permessi -├── install/ # Migrazioni SQL versionate -├── locales/ # File .po/.mo per i18n -├── css/ & js/ # Asset frontend -└── README.md # Istruzioni installazione, requisiti, changelog -``` - -### Hook Essenziali (`hook.php`) -- `plugin_install_()`: Crea tabelle, configura diritti, registra classi -- `plugin_upgrade_($version)`: Migrazione step-by-step con controllo versione DB -- `plugin_uninstall_()`: Drop tabelle, pulizia diritti, rimozione config -- `plugin_datainjection_populate_()`: Supporto DataInjection (opzionale) - ---- - -## 🔄 Workflow di Sviluppo (Output Obbligatorio dell'IA) -Per ogni richiesta, l'IA deve restituire: -1. 📁 **Struttura ad albero** completa del plugin -2. 📄 `setup.php` con check versione GLPI, namespace, metadata marketplace -3. 🔌 `hook.php` con install/upgrade/uninstall robusti e transazionali -4. 🧩 Classi `src/` con DI, validazione, logging (`Glpi\Log` o `Toolbox::logDebug()`) -5. 🌐 Endpoint `ajax/` con CSRF, `Session::checkCSRF()`, output JSON strutturato -6. 🗃️ Migrazioni `install/` con versioning e rollback sicuro -7. 📦 `composer.json` con autoloading PSR-4 e dipendenze necessarie -8. 🧪 Istruzioni di test, comandi CLI e troubleshooting -9. ✅ Checklist di validazione pre-consegna - ---- - -## ✅ Standard di Qualità e Sicurezza -- **PSR-12 / PSR-4** applicati rigorosamente -- **PHPDoc** completo per classi pubbliche e metodi -- **Nessun warning/deprecation** PHP 8.3/8.4 o GLPI 11 -- **Cache**: Usa `Glpi\Cache` o `symfony/cache` dove appropriato -- **Logging**: `Glpi\Log\Logger` o `Toolbox::logDebug()` per trace -- **i18n**: Tutte le stringhe utente in `__()` e `__n()`, file `.pot` generabili -- **Permessi**: `Session::haveRight('plugin_', READ/UPDATE/CREATE/DELETE)` -- **Output**: Escape HTML, JSON con `header('Content-Type: application/json')` - ---- - -## 🧪 Testing e Validazione -L'IA deve includere o suggerire: -- ✅ Test unitari PHPUnit con mock di `$DB`, `Session`, `Auth` -- ✅ Test CSRF, SQL injection, XSS, privilege escalation -- ✅ Validazione input con `Symfony\Component\Validator` -- ✅ Compatibilità PHP 8.3/8.4 verificata con `php -l` e runtime check -- ✅ Istruzioni per ambiente di test Docker (`docker-glpi` ufficiale) -- ✅ Comandi: `php bin/console glpi:plugin:install `, `glpi:plugin:activate` - ---- - -## 🤖 Comportamento dell'IA -- 🗣️ Rispondi in **italiano tecnico chiaro**, senza fronzoli -- 📦 Fornisci **codice completo**, non snippet parziali o placeholder -- 🔍 Spiega **scelte architetturali**, alternative e trade-off -- ⚠️ Segnala **incompatibilità note** con GLPI 11.x o PHP 8.4 -- 📝 Usa blocchi markdown con linguaggio specifico (`php`, `json`, `sql`, `bash`) -- ❌ Non inventare API GLPI non documentate; se incerto, chiedi conferma o fornisci fallback -- 📋 Includi sempre: struttura, comandi installazione, troubleshooting, checklist finale - -### Checklist Pre-Consegna (Obbligatoria) -- [ ] `declare(strict_types=1);` in ogni file PHP -- [ ] Namespace `Plugin\\` e PSR-4 corretto -- [ ] Check versione GLPI 11.0.6+ in `setup.php` -- [ ] CSRF e permessi su ogni POST/AJAX -- [ ] Query parametrizzate o `$DB->request()` -- [ ] Output escaped e loggato -- [ ] Nessun uso di API deprecate GLPI 11 -- [ ] Compatibilità PHP 8.3/8.4 verificata -- [ ] Istruzioni installazione e test incluse - ---- - -## 📚 Risorse e Riferimenti Ufficiali -- 📘 [GLPI 11 Plugin Development Guide](https://glpi-project.org/documentation/) -- 🔗 [GLPI GitHub - Plugin Examples](https://github.com/glpi-project) -- 🐘 [PHP 8.3/8.4 Migration & New Features](https://www.php.net/manual/en/migration83.php) -- 🧩 [Symfony Components (compatibili con GLPI)](https://symfony.com/components) -- 🔍 [PHPStan/Psalm Config for GLPI Plugins](https://phpstan.org/) -- 🐳 [Official GLPI Docker for Testing](https://github.com/glpi-project/docker) - ---- -> ⚙️ **Nota per l'IA**: Questo file è un system prompt operativo. Ogni risposta deve aderire rigidamente a queste direttive. Se un requisito confligge con GLPI 11.x o PHP 8.4, segnalalo esplicitamente e proponi un'alternativa conforme. Non generare codice non verificabile. diff --git a/GLPIDEV.md b/GLPIDEV.md new file mode 100644 index 0000000..d5c684d --- /dev/null +++ b/GLPIDEV.md @@ -0,0 +1,410 @@ +# GLPIDEV.md — API GLPI 11.0.8 Reference (per plugin urbackup) + +> **DA LEGGERE ALL'INIZIO DI OGNI SESSIONE DI LAVORO**, insieme a SKILL.md e MEMORY.md. +> File riassuntivo dell'API GLPI 11 usata dal plugin, generato analizzando il core reale in `/var/www/glpi` (versione **11.0.8**). +> Verificato su: `/var/www/glpi/src`, `/var/www/glpi/inc`, `/var/www/glpi/plugins/urbackup`. + +--- + +## 1. Ambiente + +| Voce | Valore | +|------|--------| +| GLPI | 11.0.8 (`/var/www/glpi`, definito in `src/autoload/constants.php`) | +| PHP | 8.3+ (installato: 8.4.23) — `declare(strict_types=1);` obbligatorio | +| Struttura | tutto il codice core in `src/` (PSR-4, namespace `Glpi\`); `inc/includes.php` bootstrap; `front/`, `ajax/`, `routes/` (Symfony routing), `templates/` Twig, `var/` cache/log | +| Plugin | `GlpiPlugin\Urbackup\` (PSR-4 via composer.json, PHP >= 8.3) | +| Versioni plugin | 0.7.3 (setup.php: `PLUGIN_URBACKUP_VERSION`; min GLPI 11.0.6, max 11.99.99) | +| Costanti plugin | `PLUGIN_URBACKUP_DIR` (cartella plugin), `PLUGIN_URBACKUP_WEB_DIR` (`Plugin::getWebDir('urbackup')`) | + +**Regola chiave**: in GLPI 11 il codice procedurale e le classi legacy di GLPI 9/10 in `inc/` NON esistono più — le classi core sono in `src/` (es. `Glpi\...`). L'unica classe globale restante in `inc/` è `includes.php`. + +--- + +## 2. Database Layer + +### 2.1 `$DB` globale +- `global $DB;` — istanza `class DB extends DBmysql` (generata in `config/` da `DBConnection`). +- Sotto: mysqli. `$DB->update()` ritorna **sempre `true`** → per verificare l'esito usare `$DB->affectedRows()`. +- `$DB->runFile()` è DEPRECATO ma il plugin lo usa SOLO per la creazione dello schema iniziale in `install/install.php` (`plugin_urbackup_install_create_initial_schema()`, `install/mysql/plugin_urbackup-empty.sql`). MAI in upgrade/uninstall (lì si usa `$migration->dropTable()`). + +### 2.2 Tabelle del plugin (schema v0.7.3) +```sql +glpi_plugin_urbackup_configs -- id, name, value, date_creation, date_mod (KEY name) + -- riga default in 0.7.2: name='enable_computer', value='1' + -- (toggle tab UrBackup su Computer, letto da Config::getEnableComputer()) +glpi_plugin_urbackup_servers -- id, entities_id, is_recursive, name, locations_id, users_id, + -- ip_address, port (55414), protocol (http|https), + -- server_version, api_username, api_password (TEXT, CIFRATO con GLPIKey da 0.7.1), + -- ignore_ssl, is_active, last_api_status, last_api_message, + -- last_api_check, comment, + -- host_itemtype (VARCHAR NULL), host_items_id (INT UNSIGNED DEFAULT 0) -- 0.7.3: asset host + -- date_creation, date_mod + -- KEY name/entities_id/locations_id/users_id/is_active/location_active(locations_id,is_active)/host_asset(host_itemtype,host_items_id) +glpi_plugin_urbackup_serverassets-- id, plugin_urbackup_servers_id, itemtype, items_id, + -- date_creation, date_mod (KEY plugin_urbackup_servers_id, item(itemtype,items_id)) +``` +- Tabelle legacy DROPPED in 0.7.0: `glpi_plugin_urbackup_profiles`, `glpi_plugin_urbackup_assettypes`. + +### 2.3 Query builder (lettura) +```php +$iterator = $DB->request([ + 'FROM' => Server::getTable(), + 'WHERE' => [ + 'locations_id' => $locations_id, + 'is_active' => 1, + ], + 'ORDER' => 'name', +]); +foreach ($iterator as $row) { ... } // iterazione diretta +$iterator->count(); // numero righe +$iterator->current(); // riga corrente +``` +- `DBmysqlIterator` implementa `SeekableIterator, Countable`. +- **Mai concatenare variabili nelle query**: i criteri vengono parametrizzati dal builder. +- `COUNT` con `$DB->request()` è il pattern per i controlli di esistenza idempotenti (vedi `Profile::registerRights()`). + +### 2.4 Scrittura +```php +$DB->insert($table, $params); // INSERT +$DB->update($table, $params, $where); // UPDATE (ritorna true sempre → affectedRows) +$DB->delete($table, $where); // DELETE +``` + +### 2.5 DDL e introspezione +```php +$DB->doQuery("ALTER TABLE ..."); // DDL — query() è DEPRECATO in GLPI 11 +$DB->tableExists($tablename); // introspezione (cache) +$DB->fieldExists($table, $field); +$DB->listFields($table); // array colonne (['Field' => ...]) — usato in install.php +$DB->insertId(); +$DB->affectedRows(); +``` +- **`$DB->query()` deprecato** → usare `$DB->doQuery()`. + +### 2.6 Pattern di migrazione (`install/install.php`) +- `plugin_urbackup_install_process()`: crea `Migration(PLUGIN_URBACKUP_VERSION)`, esegue in ordine: schema iniziale (se mancano tabelle → `$DB->runFile()`), `update_configs_table`, `convert_assettypes_to_capacities`, `update_servers_table`, `update_serverassets_table`, `encrypt_api_passwords`, `add_enable_computer_config` (inserisce `('enable_computer','1')` se assente), `drop_legacy_tables`, poi `$migration->executeMigration()`; infine `Profile::installRights()`. +- Ogni `addField()`/`addKey()`/`dropField()`/`dropTable()` è idempotente e guardato da `tableExists()`/`fieldExists()`. +- `convert_assettypes_to_capacities()`: legge `glpi_plugin_urbackup_assettypes` (se esiste), auto-abilita la capacità `UrBackupCapacity` su TUTTI gli Asset Definition via `AssetDefinitionManager`, poi droppa la colonna `is_default`. +- **⚠️ CAVEAT verificato**: il codice chiama `$definition->hasCapacity(UrBackupCapacity::class)` e `$definition->enableCapacity(UrBackupCapacity::class)` (install.php:367-368) ma in GLPI 11.0.8 questi metodi NON esistono: su `AssetDefinition` ci sono solo `hasCapacityEnabled(CapacityInterface $capacity)` (richiede un oggetto), `getEnabledCapacities()`, `getCapacityConfiguration()` (AssetDefinition.php:627-652), e l'abilitazione/disabilitazione passa dall'input `capacities` del form processato in `post_updateItem()` (AssetDefinition.php:316-440) — nessun metodo pubblico `enableCapacity/saveCapacity/disableCapacity`. La chiamata è in try/catch → il messaggio d'errore viene mostrato ma la migrazione continua. L'impatto è nullo in pratica perché `Config::isItemtypeEnabled()` ritorna true per tutte le sottoclassi di `Glpi\Asset\Asset` (tab sempre visibile out-of-the-box); da rivedere comunque con l'API core corretta. +- `uninstall.php`: `Profile::uninstallRights()` + `plugin_urbackup_migration_drop_table()` per ogni tabella (drop via `$migration->dropTable()` se esiste). + +--- + +## 3. Session & Sicurezza + +### 3.1 Diritti +```php +Session::checkRight($module, $right); // muore con errore 403 se senza diritto +Session::checkLoginUser(); // solo login richiesto (server_test.ajax.php) +Session::haveRight($module, $right); // booleano (senza morte) +Session::getLoginUserID(); +Session::getPluralNumber(); +Session::addMessageAfterRedirect(...); +Session::getNewCSRFToken(bool $standalone = false); +``` +- **IMPORTANTE**: nei file con namespace plugin (`GlpiPlugin\Urbackup\...`) importare `use Session;` (e `Html`, `Toolbox`, `GLPIKey`, ecc.), altrimenti PHP risolve `GlpiPlugin\Urbackup\Session` che non esiste. +- **Pattern diritti del plugin**: `Profile::canCurrentUser(int $right): bool` (src/Profile.php) — risolve il profilo attivo da `$_SESSION['glpiactiveprofile']['id']`, con fallback su `glpi_profiles_users` (profilo statico) e su `Session::haveRight()`; il rightname è `'plugin_urbackup'`. +- `Server::canView()/canCreate()/canUpdate()/canDelete()/canPurge()` delegano a `Profile::canCurrentUser(...)`. +- `Server::$rightname = 'plugin_urbackup'` → `getRights()` espone READ/UPDATE/CREATE/DELETE/PURGE nella UI standard dei profili GLPI. + +### 3.2 CSRF — GLPI 11 breaking change +- Hook `Hooks::CSRF_COMPLIANT = 'csrf_compliant'` registrato in `setup.php` → GLPI gestisce i token automaticamente. +- **Il listener globale `CheckCsrfListener` consuma il token su ogni POST** → una seconda chiamata esplicita a `Session::checkCSRF()` fallisce (in GLPI 11 richiede `$data` come argomento). Il plugin NON la chiama nei front (fix 0.7.0). +- Form POST: `Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()])` (pattern in AssetTab/Server/MassiveAction). +- AJAX (`public/js/urbackup.js`): header `X-Glpi-Csrf-Token` con `getAjaxCsrfToken()`. + +### 3.3 Profili e diritti (pattern verificato 0.7.x) +- `Profile::registerRights()` → `ProfileRight::addProfileRights(['plugin_urbackup'])` se assente in `glpi_profilerights`. +- `Profile::installRights()`: profilo attivo da sessione; **in CLI (install via console) non c'è sessione** → cerca il profilo "Super-Admin" con query diretta e assegna `READ|UPDATE|CREATE|DELETE|PURGE`; agli altri profili garantisce `READ` se non esiste record. +- `Profile::uninstallRights()` → `$DB->delete('glpi_profilerights', ['name' => 'plugin_urbackup'])` + `ProfileRight::deleteProfileRights()`. +- Hook `Hooks::CHANGE_PROFILE` → `Profile::initProfile($profile)`: se il nuovo profilo non ha diritti, assegna READ. + +--- + +## 4. Criptazione Segreti + +**⚠️ `Toolbox::encrypt()/decrypt()` NON ESISTE in GLPI 11** (verificato in `src/Toolbox.php`). API corretta (verificata in core: `APIRest.php:623`, `ClientRepository.php:90` — `GLPIKey::getInstance()` **NON esiste**): +```php +use GLPIKey; // classe globale, serve solo nei file con namespace plugin +(new GLPIKey())->encrypt(string $string, ?string $key = null): string; +(new GLPIKey())->decrypt(?string $string, ?string $key = null): ?string; +``` +- `encrypt()` ritorna `''` se la chiave non è leggibile (mai dati in chiaro propagati); `decrypt()` su valore non cifrato emette `trigger_error` e ritorna `''` → rilevare il formato prima di decifrare (`Server::isApiPasswordEncrypted()`: base64 strict + nonce ≥ 24 byte). +- **STATO ATTUALE (05/08/2026)**: `api_password` su `glpi_plugin_urbackup_servers` è **cifrata con GLPIKey** — encrypt in `Server::prepareInputForAdd/Update` (campo vuoto = mantieni), `Server::getApiPassword()` decifra on-the-fly con fallback per valori legacy in chiaro, migrazione idempotente `plugin_urbackup_install_encrypt_api_passwords()` in install.php. Mai loggare o esporre la password decifrata. + +--- + +## 5. Html & Escaping + +```php +Html::header($title, $url, 'admin', 'GlpiPlugin\Urbackup\Server'); // header pagina +Html::footer(); +Html::redirect($url); +Html::hidden($name, ['value' => $value]); // campo hidden (NOTA: in GLPI 11 la firma è (name, options)) +Html::submit($label, ['name' => ..., 'class' => ...]); +Html::input($name, ['value' => ..., 'size' => ...]); // input testuale +Html::convDate($date); +Html::convDateTime($date); +Html::displayRightError(); // errore diritti insufficienti +Html::displayNotFoundError(); // item non trovato +Html::closeForm(); +Html::header_nocache(); // endpoint AJAX +``` +### Escaping (XSS) +- `htmlspecialchars()` su TUTTI gli output dinamici (pattern diffuso in AssetTab/Server/ServerAsset). +- `Html::entities_deep($array)` — sanitizzazione array di input. +- Twig: auto-escaping (vedi §11). + +--- + +## 6. Dropdown & Search + +### 6.1 Dropdown +```php +Dropdown::showFromArray($name, $values, $options); // dropdown generico da array (protocollo, server list) +Dropdown::showYesNo($name, $value); // is_active, ignore_ssl, ecc. +Entity::dropdown(['name' => 'entities_id', 'value' => ...]); +Location::dropdown(['name' => 'locations_id', 'value' => ...]); +Server::dropdown([...]); // dropdown itemtype del plugin (MassiveAction) +Dropdown::getDropdownName($table, $id); +``` + +### 6.2 Search options (`Server::rawSearchOptions()`) +- ID usati: `common` (Characteristics), 1 name (itemlink), 2 ip_address (string), 3 port (integer), 4 protocol (string), 5 server_version (string), 6 Entity (dropdown), 7 Location (dropdown), 8 User (dropdown — richiede colonna `users_id`), 9 is_active (bool), 10 last_api_status (bool), 11 last_api_check (datetime), 12 date_creation, 13 date_mod, 14 id (raw, `searchtype => 'view'`, solo con UPDATE). +- `Search::getOptions($itemtype)` / `Search::show($itemtype, $params)` disponibili nel core (`src/Search.php`). + +--- + +## 7. CommonDBTM / CommonGLPI / Capacity system GLPI 11 + +### 7.1 Classi base +- `CommonDBTM` — tabella + CRUD generico: `Server`, `ServerAsset`, `Config`, `AssetTab`, `MassiveAction` (del plugin). +- `CommonGLPI` — item senza tabella (tab, UI). +- **Capacity system GLPI 11** — `Glpi\Asset\Capacity\AbstractCapacity` (vedi §7.4). + +### 7.2 Metodi lifecycle sovrascritti nel plugin +```php +getTable($classname = null): string; // nome tabella custom +getTypeName($nb = 0): string; // _n(..., 'urbackup') +getRights($interface = 'central'); // READ/UPDATE/CREATE/DELETE/PURGE +canView()/canCreate()/canUpdate()/canDelete()/canPurge(); +defineTabs($options); // Server: default form + ServerAsset + Log +rawSearchOptions(); // §6.2 +showForm($ID, $options); // form edit + showFormHeader/showFormFields/showFormButtons +prepareInputForAdd($input); // default port 55414, protocol http, users_id da sessione +prepareInputForUpdate($input); // test connessione API in salvataggio (last_api_status/message/check) +getMenuName()/getMenuContent(); // menu Admin (icona 'ti ti-cloud-up') +``` + +### 7.3 Tabs +```php +getTabNameForItem(CommonGLPI $item, $withtemplate = 0): string; // ritorna createTabEntry(...) o '' +displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0): bool; +``` +- `AssetTab` su **Computer** (registerClass `addtabon => ['Computer']`) e su **Asset Definition** via capacity (registrazione tab in `onClassBootstrap`). +- `Profile` (del plugin) su `\Profile` (tab diritti, render Twig `@urbackup/profile.html.twig`). +- `Server` → tab `ServerAsset` (Linked assets) + `Log`. +- Sub-tab interni in `AssetTab::showInternalTabs()`: **Stato / Azioni (solo UPDATE|CREATE) / Info-Log**, resi con Bootstrap tabs (`nav-tabs`). + +### 7.4 Capacity system GLPI 11 (URBackupCapacity — `src/Capacity/UrBackupCapacity.php`) +```php +use Glpi\Asset\Capacity\AbstractCapacity; +final class UrBackupCapacity extends AbstractCapacity { + getLabel(): string; // 'UrBackup' + getIcon(): string; // 'ti ti-cloud-up' + getDescription(): string; + onClassBootstrap(string $classname, CapacityConfig $config): void; + // -> CommonGLPI::registerStandardTab($classname, AssetTab::class) + onCapacityDisabled(string $classname, CapacityConfig $config): void; + // -> pulizia ServerAsset::deleteByCriteria(['itemtype' => $classname], force: true) + deleteRelationLogs() + isUsed(string $classname): bool; // countAssetsLinkedToPeerItem(... ServerAsset) > 0 + getCapacityUsageDescription(string $classname): string; +} +``` +- Registrazione in `plugin_init_urbackup()`: `AssetDefinitionManager::getInstance()->registerCapacity(new UrBackupCapacity())`. +- `onClassBootstrap` viene chiamato da `bootDefinitions()` durante l'evento kernel **PostBoot** → i tab vengono registrati su tutti i definition abilitati anche se creati dopo l'init del plugin. +- `Config::isItemtypeEnabled()`: `Computer` sempre true; qualsiasi sottoclasse di `Glpi\Asset\Asset` sempre true (visibilità sempre on; la capacità serve per usage tracking e cleanup). +- `Config::getEnabledItemtypes()`: `['Computer', ...classi asset definition]` via `AssetDefinitionManager::getDefinitions()`. + +### 7.5 CRUD istanza +```php +$obj = new Server(); +$obj->getEmpty(); +$obj->getFromDB($id); +$obj->add($input); $obj->update($input); $obj->delete($input); +$obj->getField('colonna'); +$obj->check($id, RIGHT); // autorizzazione (muore se senza diritto) +``` + +### 7.6 Massive Actions (plugin) +- Hook `Hooks::USE_MASSIVE_ACTION = 'use_massive_action'` in `setup.php` + `plugin_urbackup_MassiveActions($type)` in `hook.php`. +- **ATTENZIONE**: l'hook `plugin_urbackup_MassiveActions($type)` riceve l'itemtype come **stringa** (es. `'Computer'`), NON un oggetto MassiveAction; ritorna `['Classe::SEPARATOR::azione' => 'Label']` con `\MassiveAction::CLASS_ACTION_SEPARATOR`. +- Azioni: `ACTION_CONNECT_SERVER = 'connect_server'` (UPDATE|CREATE), `ACTION_DISCONNECT_SERVER = 'disconnect_server'` (UPDATE). +- `showMassiveActionsSubForm(\MassiveAction $ma)`: dropdown `Server::dropdown(['condition' => ['is_active' => 1]])`. +- `processMassiveActionsForOneItemtype(\MassiveAction $ma, CommonDBTM $item, array $ids)`: verifica `Config::isItemtypeEnabled()` e diritti, chiama `ServerAsset::connectAssetToServer()` / `disconnectAsset()`, ritorna `$ma->itemDone($itemtype, $id, \MassiveAction::ACTION_OK|ACTION_KO)`. + +--- + +## 8. Bootstrap Plugin + +### 8.1 setup.php +```php +function plugin_init_urbackup(): void { + global $PLUGIN_HOOKS; + $PLUGIN_HOOKS[Hooks::CSRF_COMPLIANT]['urbackup'] = true; + $PLUGIN_HOOKS[Hooks::CHANGE_PROFILE]['urbackup'] = [Profile::class, 'initProfile']; + Profile::registerRights(); + Plugin::registerClass(Config::class); + Plugin::registerClass(Profile::class, ['addtabon' => 'Profile']); + Plugin::registerClass(Server::class, ['linkgroup_types' => true, 'document_types' => true]); + Plugin::registerClass(ServerAsset::class); + Plugin::registerClass(PluginUrbackupMassiveAction::class); + Plugin::registerClass(AssetTab::class, ['addtabon' => ['Computer']]); + if (class_exists(AssetDefinitionManager::class)) { + AssetDefinitionManager::getInstance()->registerCapacity(new UrBackupCapacity()); + } + $PLUGIN_HOOKS['config_page']['urbackup'] = 'front/config.form.php'; + $PLUGIN_HOOKS[Hooks::MENU_TOADD]['urbackup'] = ['admin' => Server::class]; + $PLUGIN_HOOKS[Hooks::USE_MASSIVE_ACTION]['urbackup'] = true; + $PLUGIN_HOOKS[Hooks::ADD_CSS]['urbackup'] = ['public/css/urbackup.css']; + $PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['urbackup'] = ['public/js/urbackup.js']; +} +function plugin_version_urbackup(): array { /* name, version 0.7.2, requires GLPI 11.0.6-11.99.99, PHP >= 8.3 */ } +function plugin_urbackup_check_prerequisites(): bool { /* version_compare GLPI + PHP */ } +function plugin_urbackup_check_config(bool $verbose = false): bool { return true; } +function plugin_urbackup_install(): bool { require install/install.php; return plugin_urbackup_install_process(); } +function plugin_urbackup_uninstall(): bool { require install/uninstall.php; return plugin_urbackup_uninstall_process(); } +``` +- NOTA: top-level di setup.php invalida l'OPcache dei file del plugin via `opcache_invalidate()` (sviluppo web). + +### 8.2 hook.php — funzioni standard +| Funzione | Ruolo | +|----------|-------| +| `plugin_urbackup_get_classes()` | array classi: Config, Profile, Server, ServerAsset, PluginUrbackupMassiveAction | +| `plugin_urbackup_MassiveActions($type)` | azioni massivie per itemtype abilitato (stringa in ingresso!) | + +--- + +## 9. Install / Upgrade / Uninstall +- `plugin_urbackup_install_process()` (§2.6) — migrazioni **idempotenti**, schema iniziale con `runFile` (SOLO qui), `Profile::installRights()` con fallback CLI "Super-Admin", `Config::ensureDefaultConfiguration()`. +- `plugin_urbackup_uninstall_process()` — `Profile::uninstallRights()` + `plugin_urbackup_migration_drop_table()` per configs/servers/serverassets (+ legacy profiles/assettypes). +- Comandi: `php bin/console glpi:plugin:install urbackup`, `php bin/console glpi:plugin:activate urbackup`, disinstallazione via `glpi:plugin:uninstall urbackup`. + +--- + +## 10. UrBackup Web API — `src/UrbackupApiClient.php` + +### 10.1 Panoramica +- Endpoint: `http(s)://:/x?a=` (base URL: `rtrim($server->getWebInterfaceUrl(), '/') . '/x'`). +- cURL: `CURLOPT_TIMEOUT = 30`, `CURLOPT_CONNECTTIMEOUT = 5`, `SSL_VERIFYPEER/VERIFYHOST` secondo `ignore_ssl`, header `Accept: application/json` + `Content-Type: application/x-www-form-urlencoded; charset=UTF-8`, sempre POST (tranne ove indicato). +- Errori: `RuntimeException` con messaggi traducibili (`__()`, dominio 'urbackup'); risposte non-JSON (HTML) rilevate da `str_starts_with(trim($raw), '<')`. + +### 10.2 Autenticazione (flusso login) +1. `request('login', [], 'POST', false)` — se `success === true` ok (session id da `login['session']`). +2. Altrimenti `request('salt', ['username' => ...], 'POST', false)` → `ses` (session), `salt`, `rnd`, `pbkdf2_rounds`. +3. Hash password: `md5($salt_str . $password, true)` → hex; se `pbkdf2_rounds > 0` → `hash_pbkdf2('sha256', $bin, $salt_str, $rounds)`; finale `md5($rnd . $passwordMd5)`. +4. `request('login', ['username', 'password' => hash, 'ses'])` → `logged_in = true`. +- Tutte le azioni autenticate passano `ses` nei parametri (`apiAction()`). + +### 10.3 Azioni implementate +| Metodo | API action | Parametri chiave | +|--------|-----------|------------------| +| `testConnection()` | `server_identity` (post login) | — | +| `getStatus()` | `status` | — (campi: name, online, status, ip/client_ip, client_version_string, file_lastbackup, image_lastbackup, file_ok, image_ok, lastbackup...) | +| `getClientStatusByName()` | — (filtra `getStatus()`) | match case-insensitive su name/clientname/hostname | +| `getClientIdByName()` | — | id/clientid/client_id | +| `getClientSettings()` | `settings` | `sa=clientsettings`, `t_clientid` | +| `updateClientSettings()` | `settings` | `sa=clientsettings_save`, `t_clientid`, `overwrite=true`, `$key=$value` | +| `saveInternetMode()` | `settings` | chiave `internet_mode_enabled` (≥ 2.4) o `internet_mode` | +| `getClientAuthKey()` | — | setting `internet_authkey` | +| `addClient()` | `add_client` | `clientname` | +| `removeClient()` | `remove_client` | `clientname`, `clientid` | +| `startIncrementalFileBackup()` | `start_backup` | `start_client`, `start_type=incr_file` | +| `startFullFileBackup()` | `start_backup` | `start_type=full_file` | +| `startIncrementalImageBackup()` | `start_backup` | `start_type=incr_image` | +| `startFullImageBackup()` | `start_backup` | `start_type=full_image` | +| `getRecentBackups()` | `backups` | `sa=backups`, `clientid` (file `backups[]` + image `backup_images[]`, ordinati per time desc, default 40) | +| `getClientLogs()` | `livelog` | `clientid`, `lastid` (logdata; aggiorna `lastlogid`) | + +### 10.4 Note di compatibilità versioni +- `detectVersion2_4OrHigher()`: version ≥ 2.4 → `internet_mode_enabled`, altrimenti `internet_mode`. +- `extractSettingValue()`: i setting API possono essere struct `{"use":N, "value":..., "value_client":..., "value_group":...}` → estrae `['value']`. +- `responseIsSuccess()`: accetta `success`, `ok`, `saved_ok`, `result === 'ok'`, `start_ok`. +- Cache: `cached_status` (in-memory), `cached_settings[clientid]` (invalidata dopo save). + +--- + +## 11. Twig / TemplateRenderer + +```php +use Glpi\Application\View\TemplateRenderer; +$twig = TemplateRenderer::getInstance(); +$twig->display('@urbackup/profile.html.twig', ['id' => ..., 'profile' => ..., 'title' => ..., 'rights' => ...]); +``` +- Namespace template plugin: `@urbackup/` (directory `templates/` del plugin). +- Auto-escaping Twig attivo: `{{ var }}` escapato; mai logica PHP nei template. +- Pattern verificato in `src/Profile.php::displayTabContentForItem()` (tab diritti su Profilo). + +--- + +## 12. Integrazione Asset / Location (pattern chiave) + +### 12.1 Associazione asset ↔ server (`ServerAsset`) +- Tabella `glpi_plugin_urbackup_serverassets` (itemtype polimorfo + items_id + server). +- `connectAssetToServer($itemtype, $items_id, $server_id)`: diritti UPDATE|CREATE, `Config::isItemtypeEnabled()`, upsert (update se link esistente, insert altrimenti). +- `disconnectAsset($itemtype, $items_id)`: diritto UPDATE, delete. +- `getLinkForAsset($itemtype, $items_id, $active_only = true)`: singolo link. +- `extractAssetIp()`: legge `$item->fields['ip_address']` (se presente). + +### 12.2 Location-aware (`LocationHelper`) +- **Regola di business**: se l'asset è in una sub-location, il server UrBackup di riferimento è quello assegnato alla **root location**. +- `getRootLocationId(int $locations_id)`: risale `glpi_locations.locations_id` finché `locations_id = 0` (loop `Location::getFromDB()`). +- `getActiveServersForRootLocation(int $locations_id)`: server con `locations_id = $root AND is_active = 1` (query `$DB->request()`). +- `getAvailableServersForAsset()` / `assetIsInSubLocation()`. +- Pattern UI: in `AssetTab::showNoServerLinkedBlock()` il dropdown server è filtrato per root location dell'asset; in `Server::showMissingClientsTab()` gli asset candidati sono filtrati per root location del server. + +### 12.3 Batch loading (anti N+1) — `Server::showMissingClientsTab()` +- `batchLoadIps()`: **1 query per itemtype** — INNER JOIN `glpi_ipaddresses AS ipa` → `glpi_networknames AS nn` (ON `nn.items_id = ipa.id` AND `ipa.itemtype='NetworkName'`) → `glpi_networkports AS np` (ON `np.id = nn.items_id` AND `nn.itemtype='NetworkPort'`), WHERE `np.itemtype = $itemtype AND np.items_id IN ($ids)`. Ritorna `"itemtype:items_id" => ip`. +- `batchLoadGroups()`: **1 query per itemtype** su `glpi_groups_items` (WHERE `itemtype`, `items_id IN`, `type = \Group_Item::GROUP_TYPE_NORMAL`). +- `getCachedName($classname, $id, &$cache)`: cache in-memory per Entity/Location/State/User/Group (`completename` ?? `name`). +- `formatLastBackup()`: timestamp Unix (1..2e9) → `date('Y-m-d H:i:s')`. + +### 12.4 Server form (front/server.form.php) +- 4 tab: **Server** (form standard), **Linked clients**, **Unlinked clients**, **Missing clients**. +- `showLinkedClientsTab()`: server con `last_api_status = 1` → confronta asset collegati vs client API `getStatus()` (match case-insensitive su name). +- `showUnlinkedClientsTab()`: client API non ancora collegati; se esiste un asset GLPI con lo stesso nome nella root location del server → bottone **Connect** (POST `link_asset`). +- `showMissingClientsTab()`: asset GLPI della root location non collegati e non presenti su UrBackup (tabella sortable/search in JS, inline). +- Badge stato: `renderOnlineBadge()` (Online/Offline + badge ok/minor_problems/major_problems/paused). + +--- + +## 13. Caveat GLPI 11 verificati (raccolti da MEMORY.md + core) + +1. `$DB->query()` **deprecato** → `$DB->doQuery()` per SQL raw. +2. `$DB->runFile()` **deprecato** → usarlo SOLO per lo schema iniziale in install.php; mai in upgrade/uninstall (drop via `$migration->dropTable()`). +3. `Toolbox::encrypt/decrypt` **non esiste** → `(new GLPIKey())->encrypt()/decrypt()` (`GLPIKey::getInstance()` non esiste). +4. **CSRF GLPI 11**: `Session::checkCSRF()` richiede `$data` come argomento; il listener globale `CheckCsrfListener` consuma il token → il plugin NON la chiama nei front; form con hidden token, AJAX con header `X-Glpi-Csrf-Token`. +5. `Session::isDebugActive()` **non esiste** in GLPI 11 → `($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE`. +6. `Session`/`Html`/`Toolbox` ecc. sono classi globali → `use Session;` nei namespace plugin. +7. `$DB->update()` ritorna sempre `true` → verificare con `$DB->affectedRows()`. +8. GLPI environment enum: `production`, `development`, `testing`, `staging`, `e2e_testing` — MAI `prod`. +9. **`linkgroup_types => true` richiede la colonna `users_id`** sulla tabella del plugin: senza, `Group::getDataItems()` fallisce con MySQL 1054 (Unknown column 'users_id') — colonna presente su `glpi_plugin_urbackup_servers` (fix 0.7.0). +10. `Profile::installRights()` in **CLI non ha sessione** → fallback: query diretta sul profilo "Super-Admin" e assegnazione diritti completi. +11. **Asset Definition vs Computer**: le asset class di GLPI 11 sono 2-5x più lente (overhead core: Capacity iteration, JSON custom fields, `eval()` autoloading) → usare `$item->fields['name']` diretto (mai `getFromDB()` ridondanti) e batch loading. +12. **`api_password` cifrata con GLPIKey** (05/08/2026): encrypt on save, `Server::getApiPassword()` con fallback legacy, migrazione `plugin_urbackup_install_encrypt_api_passwords()`; campo form vuoto = mantieni. +13. **UrBackup versioni**: setting `internet_mode_enabled` solo da UrBackup ≥ 2.4; struct setting `{"use":N,"value":...}` da estrarre con `extractSettingValue()`. +14. Heredoc JS: non chiamare `__()` dentro heredoc (Server.php usa heredoc per JS inline — stringhe non tradotte lì). +15. Hook `plugin_urbackup_MassiveActions($type)` riceve l'**itemtype stringa**, non un oggetto MassiveAction. +16. Menu: `Hooks::MENU_TOADD['urbackup'] = ['admin' => Server::class]` + `Server::getMenuContent()` (icona `ti ti-cloud-up`). +17. Nessun uso di cron/notifiche SSH nel plugin: tutta la comunicazione è API HTTP verso il server UrBackup. +18. **`hasCapacity()`/`enableCapacity()` non esistono in GLPI 11.0.8** su `AssetDefinition` → check con `hasCapacityEnabled(CapacityInterface $capacity)` (oggetto, non stringa), `getEnabledCapacities()`, `getCapacityConfiguration()` (AssetDefinition.php:627-652); l'enable/disable passa dall'input `capacities` del form in `post_updateItem()` (AssetDefinition.php:316-440). Il codice install.php:367-368 usa i metodi inesistenti dentro try/catch (vedi §2.6). +19. **Standard versione plugin per modifiche DB (regola 6 di AGENTS.md)**: qualsiasi modifica DB richiede bump di `PLUGIN_URBACKUP_VERSION` in setup.php. Meccanismo GLPI verificato in `src/Plugin.php`: `checkPluginState()` (righe ~909-933) confronta `plugin_version_urbackup()['version']` con `glpi_plugins.version`; se diversa aggiorna la riga e imposta `state = NOTUPDATED` (messaggio "Plugin version changed. It has been deactivated as its update process has to be launched."). L'update si esegue con `php bin/console glpi:plugin:install urbackup` → `Plugin::install()` (riga 1197) chiama `plugin_urbackup_install()` e poi imposta `state = NOTACTIVATED` → quindi serve `php bin/console glpi:plugin:activate urbackup`. Le migrazioni in `plugin_urbackup_install_process()` usano `new Migration(PLUGIN_URBACKUP_VERSION)` e DEVONO restare idempotenti. Comandi console disponibili: `plugin:list`, `plugin:install`, `plugin:activate`, `plugin:deactivate`, `plugin:uninstall` (`src/Glpi/Console/Plugin/`). +20. **Toggle "Computer" configurabile (0.7.2)**: il tab UrBackup su `Computer` NON è una capacità (le capacità valgono solo per Asset Definition) → lo stato è salvato in `glpi_plugin_urbackup_configs` (`enable_computer`). `Config::getEnableComputer()` ha cache statica + guard `TableExists` + fallback `true`; setup.php registra `addtabon Computer` solo se true; `getEnabledAssetDefinitions()` usa `hasCapacityEnabled()` con istanza da `AssetDefinitionManager::getAvailableCapacities()`. Disattivare Computer non tocca i link esistenti in `glpi_plugin_urbackup_serverassets` (solo visibilità UI). +21. **Hardware host del server (0.7.3)**: `glpi_plugin_urbackup_servers.host_itemtype`/`host_items_id` (polimorfici, NULL/0 = nessuno) identificano l'asset (Computer o Asset Definition con capacità attiva) su cui gira il server UrBackup — NON usare `glpi_plugin_urbackup_serverassets` (semantica CLIENT: asset backup da quel server). UI: `Dropdown::showItemTypes` + `Html::scriptBlock` con `$.get` → `front/dropdown_host.ajax.php` (GET, `Session::checkLoginUser()`, itemtype validato con `class_exists` + `Config::isItemtypeEnabled`) che risponde `Dropdown::show($itemtype, ['entity' => Session::getActiveEntities(), ...])`. Validazione in `Server::prepareInputForUpdate()`: se `host_items_id > 0` ma itemtype mancante/non abilitato/item inesistente → azzeramento entrambi. Più server sullo stesso host consentiti (niente unique). Blocco "This asset hosts the UrBackup server" in AssetTab (sempre visibile, anche se l'asset è client). **Caveat API GLPI 11**: (a) `Ajax::updateItemOnSelectEvent` genera `$("#x").load(url, {params})` = **POST** (data oggetto) → endpoint target deve accettare POST + CSRF AJAX (header `X-Glpi-Csrf-Token` automatico via `$(document).ajaxSend` in public/js/common.js); preferire `$.get` inline via `Html::scriptBlock` per dropdown read-only. (b) `Dropdown::show` è **lazy** (`Html::jsAjaxDropdown`): il markup NON contiene le opzioni, che arrivano via POST select2 a `/ajax/getDropdownValue.php`; il parametro per le entità è **`entity`** (NON `entity_restrict`, che è solo l'output serializzato); `Session::getMatchingActiveEntities()` è un filtro che richiede 1 argomento → usare `Session::getActiveEntities()`. + +--- + +## 14. Conclusione + +Questo file è la mappa dell'API GLPI 11 usata dal plugin urbackup. Se una modifica del core GLPI richiede nuove funzioni, aggiornare questo file e verificare la firma reale in `/var/www/glpi/src/` prima di scrivere codice. diff --git a/GLPIDEV.md_netbackup b/GLPIDEV.md_netbackup deleted file mode 100644 index bcc24a6..0000000 --- a/GLPIDEV.md_netbackup +++ /dev/null @@ -1,342 +0,0 @@ -# GLPIDEV.md — API GLPI 11.0.8 Reference (per plugin netbackup) - -> **DA LEGGERE ALL'INIZIO DI OGNI SESSIONE DI LAVORO**, insieme a SKILL.md e MEMORY.md. -> File riassuntivo dell'API GLPI 11 usata dal plugin, generato analizzando il core reale in `/var/www/glpi` (versione **11.0.8**). -> Verificato su: `/var/www/glpi/src`, `/var/www/glpi/inc`, `/var/www/glpi/plugins/netbackup`. - ---- - -## 1. Ambiente - -| Voce | Valore | -|------|--------| -| GLPI | 11.0.8 (`/var/www/glpi`) | -| PHP | 8.2+ | -| Struttura | tutto il codice core in `src/` (PSR-4, namespace `Glpi\`); `inc/includes.php` bootstrap; `front/`, `ajax/`, `routes/` (Symfony routing), `templates/` Twig, `var/` cache/log | -| Plugin | `GlpiPlugin\Netbackup\` (PSR-4 via composer.json) | - -**Regola chiave**: in GLPI 11 il codice procedurale e le classi legacy di GLPI 9/10 in `inc/` NON esistono più — le classi core sono in `src/` (es. `Glpi\...`). L'unica classe globale restante in `inc/` è `includes.php`. - ---- - -## 2. Database Layer - -### 2.1 `$DB` globale -- `global $DB;` — istanza `class DB extends DBmysql` (generata in `config/` da `DBConnection`, vedi `src/DBConnection.php:164`). -- Sotto: mysqli. `$DB->update()` ritorna **sempre `true`** → per verificare l'esito usare `$DB->affectedRows()` (legge `mysqli::$affected_rows`). Verificato: pattern `claimJob` in `src/BackupJob.php` usa `$DB->affectedRows() === 1` su UPDATE condizionale `WHERE status='pending'`. - -### 2.2 Query builder (lettura) -```php -$iterator = $DB->request([ - 'FROM' => self::getTable(), - 'WHERE' => [ - 'networkequipments_id' => $id, - 'is_active' => 1, - 'OR' => [ - ['field' => ['LIKE', '%x%']], - ['field' => null], - ], - ], - 'ORDER' => ['date DESC', 'id DESC'], - 'LIMIT' => 10, - 'OFFSET' => 0, - 'LEFT JOIN' => [ - 'glpi_networkequipments' => [ - 'FKEY' => ['glpi_plugin_netbackup_equipments' => 'networkequipments_id', 'glpi_networkequipments' => 'id'], - ], - ], - 'COUNT' => 'cpt', // SELECT COUNT(*) AS cpt -]); -foreach ($iterator as $row) { ... } // iterazione diretta -$iterator->count(); // numero righe -$iterator->numrows(); // alias -$iterator->fetchFields(); -$iterator->current(); // riga corrente -``` -- `DBmysqlIterator` (`src/DBmysqlIterator.php`) implementa `SeekableIterator, Countable`. -- **Mai concatenare variabili nelle query**: i criteri vengono parametrizzati dal builder. -- `COUNT` con `$DB->request()` è il pattern per i controlli di esistenza idempotenti (vedi MEMORY.md, `insert_missing_vendors()`). - -### 2.3 Scrittura -```php -$DB->insert($table, $params); // INSERT -$DB->update($table, $params, $where); // UPDATE (ritorna true sempre → affectedRows) -$DB->delete($table, $where); // DELETE -$DB->updateOrInsert($table, $params, $where, $onlyone = true); -``` - -### 2.4 DDL e introspezione -```php -$DB->doQuery("ALTER TABLE ... ADD COLUMN ..."); // DDL — query() è DEPRECATO in GLPI 11 -$DB->doQueryOrDie($query, $message); -$DB->tableExists($tablename); // introspezione (cache) -$DB->fieldExists($table, $field); -$DB->getField(string $table, string $field, $usecache = true): ?array; // ritorna l'array dei campi della tabella -$DB->insertId(); -$DB->affectedRows(); -``` -- **`$DB->query()` deprecato** → usare `$DB->doQuery()`. -- **`$DB->runFile()` deprecato** → MAI usare (MEMORY.md: uninstall stabile con TRUNCATE + doQuery, mai runFile). - -### 2.5 Pattern di migrazione -- `migrate_tables()` idempotente: ogni `ADD COLUMN` guardato da `tableExists()`/`fieldExists()`. -- Ogni `update_X_Y_Z()` chiama `plugin_netbackup_migrate_tables()` + funzioni di inserimento dati idempotenti (check `COUNT` prima di INSERT). -- MAI inserire dati nel DB con SQL manuale / `mysql` CLI: solo logica di migrazione PHP. - ---- - -## 3. Session & Sicurezza - -### 3.1 Diritti -```php -Session::checkRight($module, $right); // muore con errore 403 se senza diritto (protezione front/*.php) -Session::checkRightsOr($module, $rights = []); -Session::checkLoginUser(); // solo login richiesto -Session::haveRight($module, $right); // booleano (senza morte) -Session::haveRightsAnd($module, $rights); -Session::haveRightsOr($module, $rights); -Session::getLoginUserID(); // id utente corrente -Session::getPluralNumber(); // per stringhe pluralizzate -Session::addMessageAfterRedirect(...); // messaggi UI post-redirect -Session::getNewCSRFToken(bool $standalone = false); -``` -- **IMPORTANTE**: nei file con namespace plugin (`GlpiPlugin\Netbackup\...`) importare `use Session;` (e `Html`, `Toolbox`, `GLPIKey`, ecc.), altrimenti PHP risolve `GlpiPlugin\Netbackup\Session` che non esiste. - -### 3.2 CSRF -- Hook `Hooks::CSRF_COMPLIANT = 'csrf_compliant'` registrato in `setup.php` → GLPI gestisce i token automaticamente per i form del plugin. -- Nei POST manuali: campo hidden `_glpi_csrf_token` con `Session::getNewCSRFToken()`. -- Tutti i form POST del plugin validano il token (pattern: `Html::hidden('_glpi_csrf_token', Session::getNewCSRFToken())`). - -### 3.3 Profili e diritti (pattern verificato v1.6.x) -- `Profile::registerRights()` → `ProfileRight::addProfileRights()` → **bump `last_rights_update`** per tutti i profili, altrimenti la sessione non si aggiorna (`Session::haveRight` torna false — bug sessione stale, MEMORY.md "Fix Session Rights"). -- `Profile::initProfile()` (hook `Hooks::CHANGE_PROFILE = 'change_profile'`) sincronizza i diritti di sessione dopo il cambio profilo. -- `plugin_init_netbackup()` confronta DB vs sessione a ogni page load e aggiorna se diverso. -- Right costanti: `READ`, `UPDATE`, `CREATE`, `DELETE`, `PURGE`, `ALLSTANDARDRIGHT`. - ---- - -## 4. Criptazione Segreti - -**⚠️ `Toolbox::encrypt()/decrypt()` NON ESISTE in GLPI 11** (verificato: nessuna funzione encrypt/decrypt in `src/Toolbox.php`). API corretta: - -```php -use GLPIKey; - -GLPIKey::getInstance()->encrypt(string $string, ?string $key = null): string; -GLPIKey::getInstance()->decrypt(?string $string, ?string $key = null): ?string; -``` -- `src/GLPIKey.php:432` / `:463`. Per compatibilità: `decryptUsingLegacyKey()` (`:526`). -- Il plugin usa già `GLPIKey` (6 occorrenze in src/). -- Mai segreti in chiaro nel DB; mai chiavi hardcoded nel codice. - ---- - -## 5. Html & Escaping - -```php -Html::header(...); // header pagina (con titolo) -Html::footer(...); -Html::back(); // pulsante indietro -Html::redirect($url); -Html::hidden($name, $value); // campo hidden -Html::submit($name, $value); // pulsante submit -Html::scriptBlock($js); // blocco script (polling JS del plugin) -Html::convDate($date); -Html::convDateTime($date); -Html::displayRightError(); // errore diritti insufficienti -``` - -### Escaping (XSS) -- `Html::cleanInputText($value)` — input testuali. -- `Html::entities_deep($array)` — sanitizzazione array di input. -- `htmlescape($string)` (global helper GLPI) / `htmlspecialchars()` — output. -- Twig: auto-escaping (vedi §11). - ---- - -## 6. Dropdown & Search - -### 6.1 Dropdown -```php -Dropdown::showFromArray($name, $values, $options); // dropdown generico da array -Dropdown::showYesNo($name, $value); -Dropdown::getDropdownName($table, $id); // nome dropdown da id -``` - -### 6.2 Search (colonne custom NetworkEquipment) -Hook di GLPI in `hook.php`: -- `plugin_netbackup_getAddSearchOptions(string $itemtype): array` — registra le search options 8200–8204 con `'jointype' => 'child'` su `glpi_plugin_netbackup_equipments` (`alias.networkequipments_id = glpi_networkequipments.id`). -- `plugin_netbackup_giveItem(string $type, int $ID, array $data, string $num): string` — rendering colonne (`giveItem`). -- `plugin_netbackup_searchOptionsValues(array $PARAM): bool` — hook `Hooks::AUTO_SEARCH_OPTION_VALUES = 'searchOptionsValues'`. - -**⚠️ CAVEAT verificato (MEMORY.md)**: l'hook `searchOptionsValues` NON viene mai chiamato da GLPI per i dropdown `datatype => 'specific'` — il core usa sempre l'output di `getValueToSelect()` (input). **Il pattern funzionante è l'override del metodo `Equipment::getSpecificValueToSelect()`** che ritorna `Dropdown::showFromArray(...)`. Non rimuovere l'override! - -- datatype usati: `'bool'` (8200), `'specific'` nosearch (8201), `'varchar'` (8202), `'datetime'` (8203), `'specific'` con `searchtype => ['equals','empty']` (8204). -- `Search::getOptions($itemtype)` / `Search::show($itemtype, $params)` disponibili nel core (`src/Search.php`). - ---- - -## 7. CommonDBTM / CommonDBChild / CommonGLPI - -### 7.1 Classi base -- `CommonDBTM` — tabella + CRUD generico. -- `CommonDBChild` — riga figlia di un item (pattern: `glpi_plugin_netbackup_equipments.networkequipments_id`). -- `CommonDropdown` — dropdown. -- `CommonGLPI` — item senza tabella (tab, UI). - -### 7.2 Metodi lifecycle sovrascritti nel plugin -```php -getTypeName($nb = 0); // nome tipo (traducibile, _n()) -getIcon(): string; // icona (ti ti-*) -getEmpty(); // riga vuota con default -prepareInputForAdd($input); // sanitizzazione/validazione pre-add -prepareInputForUpdate($input); -post_addItem() / post_updateItem() / post_purgeItem(); -getField($field); // valore campo dalla riga caricata -getAdditionalFields(); -showForm($ID, $options = []); // form edit -``` - -### 7.3 Tabs (integrazione su NetworkEquipment e Profile) -```php -getTabNameForItem(CommonGLPI $item, $withtemplate = 0): string; // ritorna ['1' => 'Backup settings'] -displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0): bool; -``` -Pattern verificato in `src/Equipment.php:48-55` (tab "Backup settings" su NetworkEquipment) e `src/Profile.php` (tab diritti). - -### 7.4 CRUD istanza -```php -$obj = new MyClass(); -$obj->getEmpty(); -$obj->add($input); -$obj->update($input); -$obj->delete($input, $force = false); -$obj->getFromDB($id); -$obj->getField('colonna'); -$obj->getTable(); // nome tabella -``` - -### 7.5 Massive Actions -Hook `Hooks::USE_MASSIVE_ACTION = 'use_massive_action'` in `setup.php` + hook `'massiveaction'` in hook.php. -- `getSpecificMassiveActions()` → array `['key' => __('label'), 'sep' => ..., 'classe' => ...]` (separatore `MassiveAction::CLASS_ACTION_SEPARATOR`). -- `showMassiveActionsSubForm(MassiveAction $ma)` — form parametri. -- `processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, array $ids)` — esecuzione. -- Ritorni: `MassiveAction::ACTION_OK` / `MassiveAction::ACTION_KO` (classe `src/MassiveAction.php`). -- Azioni del plugin: `activate_netbackup`, `deactivate_netbackup`, `enable_custom`, `disable_custom`, `bulk_set_custom`, `manual_backup`, `kill_stuck_workers`. - ---- - -## 8. Bootstrap Plugin - -### 8.1 setup.php -```php -function plugin_init_netbackup(): void { - global $PLUGIN_HOOKS; - $PLUGIN_HOOKS[Hooks::CSRF_COMPLIANT]['netbackup'] = true; - $PLUGIN_HOOKS[Hooks::CHANGE_PROFILE]['netbackup'] = [Profile::class, 'initProfile']; - $PLUGIN_HOOKS[Hooks::USE_MASSIVE_ACTION]['netbackup'] = 1; - Plugin::registerClass(BackupJob::class, ['notificationtemplates_types' => true]); - // ... -} -function plugin_version_netbackup(): array { /* name, version, requires, author, license, homepage */ } -``` -- `set_time_limit(0)` a top-level di setup.php (non in hook) — evita timeout durante uninstall su DB grandi. -- Versionamento: `PLUGIN_NETBACKUP_VERSION` in setup.php, aggiornato a ogni release. - -### 8.2 hook.php — funzioni standard -| Funzione | Ruolo | -|----------|-------| -| `plugin_netbackup_init()` | ogni page load: ensureDefaults vendor, warning configs/ non scrivibile, check patch vendor phpseclib | -| `plugin_netbackup_install()` | install: schema, defaults, notifiche, cron | -| `plugin_netbackup_uninstall()` | TRUNCATE prima di DROP, CronTask::unregister, cancellazione notifiche | -| `plugin_netbackup_migrate_tables()` | schema idempotente (tableExists/fieldExists guard) | -| `plugin_netbackup_update_X_Y_Z()` | migrazioni di versione (ogni step singolo, chiama migrate_tables) | -| `plugin_netbackup_getAddSearchOptions()` | search options NetworkEquipment | -| `plugin_netbackup_giveItem()` | rendering colonne lista | -| `plugin_netbackup_searchOptionsValues()` | hook AUTO_SEARCH_OPTION_VALUES (non scatta per 'specific' — vedi §6.2) | -| `plugin_netbackup_getDropdown()` | hook `AUTO_GET_DROPDOWN = 'getDropdown'` | - ---- - -## 9. Cron - -```php -CronTask::register(string $itemtype, string $name, int $frequency, array $options = []): bool; -CronTask::unregister(string $plugin); -``` -- Core: `src/CronTask.php:966` (`register`, lowercase; PHP è case-insensitive, `CronTask::Register` funziona). -- `$itemtype` = classe del plugin con i metodi cron (`'GlpiPlugin\Netbackup\Cron'`), `$name` = nome task. -- `$options`: `allowmode` (`MODE_INTERNAL | MODE_EXTERNAL`), `mode`, `state`, `param`, `hourmin`, `hourmax`, `comment`. -- Se `GLPI_SYSTEM_CRON` è definito e allowmode ha MODE_EXTERNAL → `mode = MODE_EXTERNAL`. -- Callback (in `src/Cron.php`): -```php -public static function cronInfo($name): array; // ['name' => ..., 'description' => ..., 'state' => 0|1] -public static function cronNetbackup($task): int; // ritorna numero job enqueueati (0 = ok) -``` -- **REGOLA ARCHITETTURALE (MEMORY.md)**: il cron GLPI NON esegue MAI SSH — solo enqueue in `glpi_plugin_netbackup_backupjobs`. Il worker CLI (`front/worker.php`, crontab) esegue materialmente i backup. -- Task registrati: `Netbackup` (60s), `NetbackupReport` (86400s). - ---- - -## 10. Notifiche - -- `Plugin::registerClass(BackupJob::class, ['notificationtemplates_types' => true])` → `NotificationTargetBackupJob` auto-scoperto dall'itemtype. -- Classe: `src/NotificationTargetBackupJob.php` estende `NotificationTarget`: - - `getEvents()` → `['backup_success' => ..., 'backup_failed' => ..., 'backup_warning' => ..., 'backup_report' => ...]` - - `addDataForTemplate()` → placeholders `##device.name_html##`, `##device.status_html##`, ecc. (devono corrispondere ESATTAMENTE al template DB) - - `getTags()` → lista tag - - `addAdditionalTargets()` → registra `GLOBAL_ADMINISTRATOR`, `ENTITY_ADMINISTRATOR` - - `getEventsToSendImmediately()` → override: `backup_success`, `backup_failed`, `backup_warning`, `backup_report` → invio immediato, NON in coda `glpi_queuednotifications` -- Emissione: `NotificationEvent::raiseEvent('backup_success', $backup, ['entities_id' => ...])` — solo per backup manuali (`users_id > 0`); i backup schedulati dal cron NON emettono notifiche per-device (fix v1.4.1). -- Template in DB: `glpi_notificationtemplates`, `glpi_notificationtemplatetranslations` (EN/IT/DE). -- **REGOLA UPGRADE**: mai sovrascrivere notifiche personalizzate — `plugin_netbackup_backup_notifications_to_sql()` (backup SQL in `backups/notifications/`) PRIMA di qualsiasi operazione; `install_notifications_netbackup()` skippa se esistono; `plugin_netbackup_regenerate_notifications()` solo per forzatura esplicita. - ---- - -## 11. Twig / TemplateRenderer - -```php -use Glpi\Application\View\TemplateRenderer; -$twig = TemplateRenderer::getInstance(); -$twig->display('@netbackup/profile.html.twig', ['key' => $value]); -``` -- Namespace template plugin: `@netbackup/` (directory `templates/` del plugin). -- Auto-escaping Twig attivo: `{{ var }}` escapato; mai logica PHP nei template. -- Pattern verificato in `src/Profile.php:41-42`. - ---- - -## 12. Integrazione NetworkEquipment & IP - -- Tabelle custom legate a `glpi_networkequipments.id` (FKEY). -- **⚠️ `glpi_networkequipments.ip` NON esiste in GLPI 11** — gli IP vivono in `glpi_ipaddresses` (IPAM, `src/IPAddress.php`). Il plugin risolve l'IP dal tab IPAM del device. -- Tab "Backup settings" via `getTabNameForItem`/`displayTabContentForItem` (§7.3). -- Search options con `'jointype' => 'child'` + nome tabella corretto (con 's') — un custom `condition` da solo causa join auto-FK sbagliato (MEMORY.md). -- Massive actions integrate nella lista NetworkEquipment (§7.5). - ---- - -## 13. Caveat GLPI 11 verificati (raccolti da MEMORY.md + core) - -1. `$DB->query()` **deprecato** → `$DB->doQuery()` per SQL raw. -2. `$DB->runFile()` **deprecato** → mai usare (uninstall: TRUNCATE prima di DROP). -3. `Toolbox::encrypt/decrypt` **non esiste** → `GLPIKey::getInstance()->encrypt()/decrypt()`. -4. `CronTask::Register/Unregister` → `CronTask::register/unregister` (case-insensitive). -5. `Session`/`Html`/`Toolbox` ecc. sono classi globali → `use Session;` nei namespace plugin. -6. `$DB->update()` ritorna sempre `true` → verificare con `$DB->affectedRows()`. -7. GLPI environment enum: `production`, `development`, `testing`, `staging`, `e2e_testing` — MAI `prod`. -8. CSRF: hook `Hooks::CSRF_COMPLIANT` + token `_glpi_csrf_token` nei POST. -9. Hook `searchOptionsValues` (AUTO_SEARCH_OPTION_VALUES) non scatta per datatype `specific` → override `getSpecificValueToSelect()`. -10. `set_time_limit(0)` in setup.php top-level (non in hook) — uninstall su DB grandi. -11. Notifiche: se cancellate e ricreate si perdono i destinatari → backup SQL prima. -12. Heredoc e `__()`: NON chiamare `__()` dentro heredoc — pre-calcolare le stringhe tradotte. -13. phpseclib patchate (`vendor/phpseclib/.../Net/SSH2.php`): riapplicare dopo ogni `composer update` (applicatore in `update_1_6_7`, verifica via `scripts/run_tests.php`). -14. `status` nei DB del plugin: Backup usa `success`/`failed`/`warning`; BackupJob usa `pending`/`running`/`success`/`failed`. -15. `config_data` è `LONGTEXT` (da 1.6.10) — nessun rischio troncamento; il flusso è file-first (`files/_plugins/netbackup/configs/*.cfg`). -16. Permessi `configs/`: directory deve essere scrivibile da `www-data` (`sudo chown -R www-data:www-data files/_plugins/netbackup/`); warning in `plugin_netbackup_init()` se non scrivibile. - ---- - -## 14. Conclusione - -Questo file è la mappa dell'API GLPI 11 usata dal plugin. Se una modifica del core GLPI richiede nuove funzioni, aggiornare questo file e verificare la firma reale in `/var/www/glpi/src/` prima di scrivere codice. diff --git a/MEMORY.md b/MEMORY.md index 2aa2aad..05c9936 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -1,6 +1,180 @@ # MEMORY.md - Stato del Plugin UrBackup -## Ultima modifica: 28/05/2026 +## 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::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 `