Files
urbackup/GLPIDEV.md
T
2026-08-07 14:27:33 +02:00

32 KiB

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)

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)

$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

$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

$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

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_PROFILEProfile::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:90GLPIKey::getInstance() NON esiste):

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

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.1 Dropdown

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 11Glpi\Asset\Capacity\AbstractCapacity (vedi §7.4).

7.2 Metodi lifecycle sovrascritti nel plugin

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

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)

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

$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

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)://<ip>:<port>/x?a=<action> (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 > 0hash_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

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 ipaglpi_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 urbackupPlugin::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 $.getfront/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.