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;— istanzaclass DB extends DBmysql(generata inconfig/daDBConnection).- Sotto: mysqli.
$DB->update()ritorna sempretrue→ per verificare l'esito usare$DB->affectedRows(). $DB->runFile()è DEPRECATO ma il plugin lo usa SOLO per la creazione dello schema iniziale ininstall/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
DBmysqlIteratorimplementaSeekableIterator, Countable.- Mai concatenare variabili nelle query: i criteri vengono parametrizzati dal builder.
COUNTcon$DB->request()è il pattern per i controlli di esistenza idempotenti (vediProfile::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(): creaMigration(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(); infineProfile::installRights().- Ogni
addField()/addKey()/dropField()/dropTable()è idempotente e guardato datableExists()/fieldExists(). convert_assettypes_to_capacities(): leggeglpi_plugin_urbackup_assettypes(se esiste), auto-abilita la capacitàUrBackupCapacitysu TUTTI gli Asset Definition viaAssetDefinitionManager, poi droppa la colonnais_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: suAssetDefinitionci sono solohasCapacityEnabled(CapacityInterface $capacity)(richiede un oggetto),getEnabledCapacities(),getCapacityConfiguration()(AssetDefinition.php:627-652), e l'abilitazione/disabilitazione passa dall'inputcapacitiesdel form processato inpost_updateItem()(AssetDefinition.php:316-440) — nessun metodo pubblicoenableCapacity/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 diGlpi\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\...) importareuse Session;(eHtml,Toolbox,GLPIKey, ecc.), altrimenti PHP risolveGlpiPlugin\Urbackup\Sessionche non esiste. - Pattern diritti del plugin:
Profile::canCurrentUser(int $right): bool(src/Profile.php) — risolve il profilo attivo da$_SESSION['glpiactiveprofile']['id'], con fallback suglpi_profiles_users(profilo statico) e suSession::haveRight(); il rightname è'plugin_urbackup'. Server::canView()/canCreate()/canUpdate()/canDelete()/canPurge()delegano aProfile::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 insetup.php→ GLPI gestisce i token automaticamente. - Il listener globale
CheckCsrfListenerconsuma il token su ogni POST → una seconda chiamata esplicita aSession::checkCSRF()fallisce (in GLPI 11 richiede$datacome 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): headerX-Glpi-Csrf-TokencongetAjaxCsrfToken().
3.3 Profili e diritti (pattern verificato 0.7.x)
Profile::registerRights()→ProfileRight::addProfileRights(['plugin_urbackup'])se assente inglpi_profilerights.Profile::installRights(): profilo attivo da sessione; in CLI (install via console) non c'è sessione → cerca il profilo "Super-Admin" con query diretta e assegnaREAD|UPDATE|CREATE|DELETE|PURGE; agli altri profili garantisceREADse 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):
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 emettetrigger_errore ritorna''→ rilevare il formato prima di decifrare (Server::isApiPasswordEncrypted(): base64 strict + nonce ≥ 24 byte).- STATO ATTUALE (05/08/2026):
api_passwordsuglpi_plugin_urbackup_serversè cifrata con GLPIKey — encrypt inServer::prepareInputForAdd/Update(campo vuoto = mantieni),Server::getApiPassword()decifra on-the-fly con fallback per valori legacy in chiaro, migrazione idempotenteplugin_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. Dropdown & Search
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 colonnausers_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
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;
AssetTabsu Computer (registerClassaddtabon => ['Computer']) e su Asset Definition via capacity (registrazione tab inonClassBootstrap).Profile(del plugin) su\Profile(tab diritti, render Twig@urbackup/profile.html.twig).Server→ tabServerAsset(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()). onClassBootstrapviene chiamato dabootDefinitions()durante l'evento kernel PostBoot → i tab vengono registrati su tutti i definition abilitati anche se creati dopo l'init del plugin.Config::isItemtypeEnabled():Computersempre true; qualsiasi sottoclasse diGlpi\Asset\Assetsempre true (visibilità sempre on; la capacità serve per usage tracking e cleanup).Config::getEnabledItemtypes():['Computer', ...classi asset definition]viaAssetDefinitionManager::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'insetup.php+plugin_urbackup_MassiveActions($type)inhook.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): dropdownServer::dropdown(['condition' => ['is_active' => 1]]).processMassiveActionsForOneItemtype(\MassiveAction $ma, CommonDBTM $item, array $ids): verificaConfig::isItemtypeEnabled()e diritti, chiamaServerAsset::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 conrunFile(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 viaglpi: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/VERIFYHOSTsecondoignore_ssl, headerAccept: application/json+Content-Type: application/x-www-form-urlencoded; charset=UTF-8, sempre POST (tranne ove indicato). - Errori:
RuntimeExceptioncon messaggi traducibili (__(), dominio 'urbackup'); risposte non-JSON (HTML) rilevate dastr_starts_with(trim($raw), '<').
10.2 Autenticazione (flusso login)
request('login', [], 'POST', false)— sesuccess === trueok (session id dalogin['session']).- Altrimenti
request('salt', ['username' => ...], 'POST', false)→ses(session),salt,rnd,pbkdf2_rounds. - Hash password:
md5($salt_str . $password, true)→ hex; sepbkdf2_rounds > 0→hash_pbkdf2('sha256', $bin, $salt_str, $rounds); finalemd5($rnd . $passwordMd5). request('login', ['username', 'password' => hash, 'ses'])→logged_in = true.
- Tutte le azioni autenticate passano
sesnei 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, altrimentiinternet_mode.extractSettingValue(): i setting API possono essere struct{"use":N, "value":..., "value_client":..., "value_group":...}→ estrae['value'].responseIsSuccess(): accettasuccess,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/(directorytemplates/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): risaleglpi_locations.locations_idfinchélocations_id = 0(loopLocation::getFromDB()).getActiveServersForRootLocation(int $locations_id): server conlocations_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; inServer::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 JOINglpi_ipaddresses AS ipa→glpi_networknames AS nn(ONnn.items_id = ipa.idANDipa.itemtype='NetworkName') →glpi_networkports AS np(ONnp.id = nn.items_idANDnn.itemtype='NetworkPort'), WHEREnp.itemtype = $itemtype AND np.items_id IN ($ids). Ritorna"itemtype:items_id" => ip.batchLoadGroups(): 1 query per itemtype suglpi_groups_items(WHEREitemtype,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 conlast_api_status = 1→ confronta asset collegati vs client APIgetStatus()(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 (POSTlink_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)
$DB->query()deprecato →$DB->doQuery()per SQL raw.$DB->runFile()deprecato → usarlo SOLO per lo schema iniziale in install.php; mai in upgrade/uninstall (drop via$migration->dropTable()).Toolbox::encrypt/decryptnon esiste →(new GLPIKey())->encrypt()/decrypt()(GLPIKey::getInstance()non esiste).- CSRF GLPI 11:
Session::checkCSRF()richiede$datacome argomento; il listener globaleCheckCsrfListenerconsuma il token → il plugin NON la chiama nei front; form con hidden token, AJAX con headerX-Glpi-Csrf-Token. Session::isDebugActive()non esiste in GLPI 11 →($_SESSION['glpi_use_mode'] ?? Session::NORMAL_MODE) === Session::DEBUG_MODE.Session/Html/Toolboxecc. sono classi globali →use Session;nei namespace plugin.$DB->update()ritorna sempretrue→ verificare con$DB->affectedRows().- GLPI environment enum:
production,development,testing,staging,e2e_testing— MAIprod. linkgroup_types => truerichiede la colonnausers_idsulla tabella del plugin: senza,Group::getDataItems()fallisce con MySQL 1054 (Unknown column 'users_id') — colonna presente suglpi_plugin_urbackup_servers(fix 0.7.0).Profile::installRights()in CLI non ha sessione → fallback: query diretta sul profilo "Super-Admin" e assegnazione diritti completi.- 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 (maigetFromDB()ridondanti) e batch loading. api_passwordcifrata con GLPIKey (05/08/2026): encrypt on save,Server::getApiPassword()con fallback legacy, migrazioneplugin_urbackup_install_encrypt_api_passwords(); campo form vuoto = mantieni.- UrBackup versioni: setting
internet_mode_enabledsolo da UrBackup ≥ 2.4; struct setting{"use":N,"value":...}da estrarre conextractSettingValue(). - Heredoc JS: non chiamare
__()dentro heredoc (Server.php usa heredoc per JS inline — stringhe non tradotte lì). - Hook
plugin_urbackup_MassiveActions($type)riceve l'itemtype stringa, non un oggetto MassiveAction. - Menu:
Hooks::MENU_TOADD['urbackup'] = ['admin' => Server::class]+Server::getMenuContent()(iconati ti-cloud-up). - Nessun uso di cron/notifiche SSH nel plugin: tutta la comunicazione è API HTTP verso il server UrBackup.
hasCapacity()/enableCapacity()non esistono in GLPI 11.0.8 suAssetDefinition→ check conhasCapacityEnabled(CapacityInterface $capacity)(oggetto, non stringa),getEnabledCapacities(),getCapacityConfiguration()(AssetDefinition.php:627-652); l'enable/disable passa dall'inputcapacitiesdel form inpost_updateItem()(AssetDefinition.php:316-440). Il codice install.php:367-368 usa i metodi inesistenti dentro try/catch (vedi §2.6).- Standard versione plugin per modifiche DB (regola 6 di AGENTS.md): qualsiasi modifica DB richiede bump di
PLUGIN_URBACKUP_VERSIONin setup.php. Meccanismo GLPI verificato insrc/Plugin.php:checkPluginState()(righe ~909-933) confrontaplugin_version_urbackup()['version']conglpi_plugins.version; se diversa aggiorna la riga e impostastate = NOTUPDATED(messaggio "Plugin version changed. It has been deactivated as its update process has to be launched."). L'update si esegue conphp bin/console glpi:plugin:install urbackup→Plugin::install()(riga 1197) chiamaplugin_urbackup_install()e poi impostastate = NOTACTIVATED→ quindi servephp bin/console glpi:plugin:activate urbackup. Le migrazioni inplugin_urbackup_install_process()usanonew 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/). - Toggle "Computer" configurabile (0.7.2): il tab UrBackup su
ComputerNON è una capacità (le capacità valgono solo per Asset Definition) → lo stato è salvato inglpi_plugin_urbackup_configs(enable_computer).Config::getEnableComputer()ha cache statica + guardTableExists+ fallbacktrue; setup.php registraaddtabon Computersolo se true;getEnabledAssetDefinitions()usahasCapacityEnabled()con istanza daAssetDefinitionManager::getAvailableCapacities(). Disattivare Computer non tocca i link esistenti inglpi_plugin_urbackup_serverassets(solo visibilità UI). - 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 usareglpi_plugin_urbackup_serverassets(semantica CLIENT: asset backup da quel server). UI:Dropdown::showItemTypes+Html::scriptBlockcon$.get→front/dropdown_host.ajax.php(GET,Session::checkLoginUser(), itemtype validato conclass_exists+Config::isItemtypeEnabled) che rispondeDropdown::show($itemtype, ['entity' => Session::getActiveEntities(), ...]). Validazione inServer::prepareInputForUpdate(): sehost_items_id > 0ma 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::updateItemOnSelectEventgenera$("#x").load(url, {params})= POST (data oggetto) → endpoint target deve accettare POST + CSRF AJAX (headerX-Glpi-Csrf-Tokenautomatico via$(document).ajaxSendin public/js/common.js); preferire$.getinline viaHtml::scriptBlockper 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(NONentity_restrict, che è solo l'output serializzato);Session::getMatchingActiveEntities()è un filtro che richiede 1 argomento → usareSession::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.