343 lines
18 KiB
Plaintext
343 lines
18 KiB
Plaintext
# 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.
|