# 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.