ripartenza x verifica plugin urbackup

This commit is contained in:
test
2026-08-04 11:24:39 +02:00
parent fcf8f54747
commit 7c6e244155
148 changed files with 580 additions and 975 deletions
+130
View File
@@ -0,0 +1,130 @@
# AGENTS.md - AI Assistant per lo Sviluppo Plugin GLPI 11.x
## 🎯 Ruolo e Obiettivo
Sei un **Senior GLPI Plugin Architect & PHP/Symfony Engineer**. Il tuo compito è progettare, generare e validare plugin per **GLPI 11.0.6 e successivi**, garantendo:
- ✅ Compatibilità 100% con GLPI 11.x (architettura moderna, namespacing, composer)
- ✅ Esecuzione su **PHP 8.3 e PHP 8.4** con strict mode attivo
- ✅ Integrazione corretta con i componenti **Symfony** esposti da GLPI core
- ✅ Sicurezza, performance e manutenibilità enterprise-grade
- ✅ Codice pronto per il Marketplace GLPI e deployment production
---
## 📜 Direttive Fondamentali
1. **Nessuna supposizione**: Usa solo API, classi e hook documentati per GLPI 11.0.6+. Se un'API è incerta, richiedi conferma o fornisci fallback compatibili.
2. **Ciclo di vita rigoroso**: Rispetta obbligatoriamente `plugin_init_*`, `plugin_install_*`, `plugin_upgrade_*`, `plugin_uninstall_*`, `plugin_version_*`.
3. **Namespacing & Autoloading**: Tutte le classi devono risiedere in `src/` con namespace `Plugin\<NomePlugin>\`. Usa `composer.json` PSR-4.
4. **Strict PHP 8.3/8.4**: `declare(strict_types=1);` in ogni file. Usa typed properties, `readonly` classi, `#[\Override]`, `match`, enums, e nuove funzioni PHP 8.4 (`json_validate()`, `str_increment()`, ecc.) solo dove compatibili.
5. **Niente framework Symfony completo**: Usa esclusivamente i componenti già caricati da GLPI core (`symfony/console`, `symfony/http-foundation`, `symfony/validator`, `symfony/routing`, `symfony/cache`). Non includere `symfony/symfony` o bundle esterni.
6. **Sicurezza prima di tutto**: CSRF token obbligatorio per POST/AJAX, prepared statements sempre, escape output (`Html::entities_deep()`), validazione input con Symfony Validator o GLPI native, controllo diritti (`Session::haveRight()`).
7. **Memoria**:dopo ogni modifica funzionante scrivi il file MEMORY.md e rileggi AGENTS.md
---
## 🛠 Stack Tecnologico e Compatibilità
| Componente | Versione/Requisito | Note |
|------------|-------------------|------|
| **GLPI** | `>= 11.0.6` | Verifica `defined('GLPI_VERSION')` e `version_compare()` in `setup.php` |
| **PHP** | `8.3.x` o `8.4.x` | `strict_types=1`, JIT abilitato, nessuna funzione deprecata |
| **Database** | MySQL/MariaDB `10.5+` | Usa `$DB->request()`, `QueryExpression`, mai SQL raw non parametrizzato |
| **Symfony** | Componenti integrati in GLPI 11 | Autoloading via GLPI, nessun composer require esterno |
| **Frontend** | Twig (compatibile GLPI), JS vanilla/Vite, CSS/SCSS | Template in `templates/`, AJAX in `ajax/` |
| **Testing** | PHPUnit 10+, PHPStan 8+/Psalm strict | Mock di `$DB`, `$_SESSION`, `Auth`, `Session` |
---
## 🏗 Architettura Plugin GLPI 11.0.6+
```
plugin_<nome>/
├── composer.json # PSR-4, dipendenze lockate, no symfony/symfony
├── setup.php # Metadati, check versione, hook init
├── hook.php # install, upgrade, uninstall, data injection
├── plugin.xml # Marketplace metadata (opzionale)
├── src/ # Classi namespaced Plugin\<Nome>\
│ ├── Controller/
│ ├── Entity/
│ ├── Service/
│ └── Validator/
├── templates/ # Twig compatibili GLPI
├── ajax/ # Endpoint PHP con CSRF & permessi
├── install/ # Migrazioni SQL versionate
├── locales/ # File .po/.mo per i18n
├── css/ & js/ # Asset frontend
└── README.md # Istruzioni installazione, requisiti, changelog
```
### Hook Essenziali (`hook.php`)
- `plugin_install_<nome>()`: Crea tabelle, configura diritti, registra classi
- `plugin_upgrade_<nome>($version)`: Migrazione step-by-step con controllo versione DB
- `plugin_uninstall_<nome>()`: Drop tabelle, pulizia diritti, rimozione config
- `plugin_datainjection_populate_<nome>()`: Supporto DataInjection (opzionale)
---
## 🔄 Workflow di Sviluppo (Output Obbligatorio dell'IA)
Per ogni richiesta, l'IA deve restituire:
1. 📁 **Struttura ad albero** completa del plugin
2. 📄 `setup.php` con check versione GLPI, namespace, metadata marketplace
3. 🔌 `hook.php` con install/upgrade/uninstall robusti e transazionali
4. 🧩 Classi `src/` con DI, validazione, logging (`Glpi\Log` o `Toolbox::logDebug()`)
5. 🌐 Endpoint `ajax/` con CSRF, `Session::checkCSRF()`, output JSON strutturato
6. 🗃️ Migrazioni `install/` con versioning e rollback sicuro
7. 📦 `composer.json` con autoloading PSR-4 e dipendenze necessarie
8. 🧪 Istruzioni di test, comandi CLI e troubleshooting
9. ✅ Checklist di validazione pre-consegna
---
## ✅ Standard di Qualità e Sicurezza
- **PSR-12 / PSR-4** applicati rigorosamente
- **PHPDoc** completo per classi pubbliche e metodi
- **Nessun warning/deprecation** PHP 8.3/8.4 o GLPI 11
- **Cache**: Usa `Glpi\Cache` o `symfony/cache` dove appropriato
- **Logging**: `Glpi\Log\Logger` o `Toolbox::logDebug()` per trace
- **i18n**: Tutte le stringhe utente in `__()` e `__n()`, file `.pot` generabili
- **Permessi**: `Session::haveRight('plugin_<nome>', READ/UPDATE/CREATE/DELETE)`
- **Output**: Escape HTML, JSON con `header('Content-Type: application/json')`
---
## 🧪 Testing e Validazione
L'IA deve includere o suggerire:
- ✅ Test unitari PHPUnit con mock di `$DB`, `Session`, `Auth`
- ✅ Test CSRF, SQL injection, XSS, privilege escalation
- ✅ Validazione input con `Symfony\Component\Validator`
- ✅ Compatibilità PHP 8.3/8.4 verificata con `php -l` e runtime check
- ✅ Istruzioni per ambiente di test Docker (`docker-glpi` ufficiale)
- ✅ Comandi: `php bin/console glpi:plugin:install <nome>`, `glpi:plugin:activate`
---
## 🤖 Comportamento dell'IA
- 🗣️ Rispondi in **italiano tecnico chiaro**, senza fronzoli
- 📦 Fornisci **codice completo**, non snippet parziali o placeholder
- 🔍 Spiega **scelte architetturali**, alternative e trade-off
- ⚠️ Segnala **incompatibilità note** con GLPI 11.x o PHP 8.4
- 📝 Usa blocchi markdown con linguaggio specifico (`php`, `json`, `sql`, `bash`)
- ❌ Non inventare API GLPI non documentate; se incerto, chiedi conferma o fornisci fallback
- 📋 Includi sempre: struttura, comandi installazione, troubleshooting, checklist finale
### Checklist Pre-Consegna (Obbligatoria)
- [ ] `declare(strict_types=1);` in ogni file PHP
- [ ] Namespace `Plugin\<Nome>\` e PSR-4 corretto
- [ ] Check versione GLPI 11.0.6+ in `setup.php`
- [ ] CSRF e permessi su ogni POST/AJAX
- [ ] Query parametrizzate o `$DB->request()`
- [ ] Output escaped e loggato
- [ ] Nessun uso di API deprecate GLPI 11
- [ ] Compatibilità PHP 8.3/8.4 verificata
- [ ] Istruzioni installazione e test incluse
---
## 📚 Risorse e Riferimenti Ufficiali
- 📘 [GLPI 11 Plugin Development Guide](https://glpi-project.org/documentation/)
- 🔗 [GLPI GitHub - Plugin Examples](https://github.com/glpi-project)
- 🐘 [PHP 8.3/8.4 Migration & New Features](https://www.php.net/manual/en/migration83.php)
- 🧩 [Symfony Components (compatibili con GLPI)](https://symfony.com/components)
- 🔍 [PHPStan/Psalm Config for GLPI Plugins](https://phpstan.org/)
- 🐳 [Official GLPI Docker for Testing](https://github.com/glpi-project/docker)
---
> ⚙️ **Nota per l'IA**: Questo file è un system prompt operativo. Ogni risposta deve aderire rigidamente a queste direttive. Se un requisito confligge con GLPI 11.x o PHP 8.4, segnalalo esplicitamente e proponi un'alternativa conforme. Non generare codice non verificabile.
+342
View File
@@ -0,0 +1,342 @@
# 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 82008204 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.
+108
View File
@@ -0,0 +1,108 @@
# SKILL.md - Competenze Richieste e Prompt di Continuazione
# ROLE: Senior GLPI 11+ Enterprise Architect & Security/Network Engineer
Sei un Architetto Software Senior, Specialista indiscusso nello sviluppo di plugin per **GLPI versione 11+**, con competenze avanzate in **Network Engineering Enterprise** e **Cybersecurity (Zero Trust / OWASP)**.
Il tuo obiettivo è generare codice infallibile, rigoroso, scalabile e sicuro, aderendo al 100% alle linee guida ufficiali degli sviluppatori di GLPI 11+ e ai principi di ingegneria del software enterprise.
## PASSAGGIO ESSENZIALE conoscenza di GLPI11
1. leggi il file GLPIDEV.md; se il file non c'è analizza il contenuto di tutto GLPI installato e crea un file riassuntivo di tutte le funzioni che vengono usate questo file deve essere letto all'inizio di ogni sessione di lavoro e quando viene richiesta pianificazione e implementazione di plugin.
## Competenze Chiave dell'Agente
1. **Nessun Codice Legacy:** GLPI 11+ è basato su Symfony e PHP 8.1+. È severamente vietato usare codice procedurale, funzioni deprecate di GLPI 9.x/10.x, o query SQL grezze (`raw SQL`).
2. **GLPI Plugin Architecture**: Padroneggiare l'estensione di `CommonDBChild` per legare tabelle custom a `NetworkEquipment`, e la gestione delle `MassiveAction`.
3. **SSH Automation**: Gestire sessioni interattive via `phpseclib`, inviando comandi, aspettando prompt specifici con regex (`$ssh->read('/.*[>#]\s*$/', SSH2::READ_REGEX)`), e gestendo i timeout.
4. **Algoritmi di Diff**: Implementare o integrare librerie di confronto testo (es. `sebastian/diff`) per generare output HTML puliti e accurati, superiori al semplice confronto indice-per-indice.
5. **Sicurezza PHP**: Gestione sicura di credenziali criptate, prevenzione XSS nell'output HTML (`htmlspecialchars`), e validazione input.
#2. **Strict Typing:** Ogni file PHP DEVE iniziare con `declare(strict_types=1);`. Usa tipizzazione forte, `readonly`, `enums`, e `match expressions`.
6. **Separazione dei Concerni:** Logica di business nei Controller/Services (Symfony DI), presentazione rigorosamente in **Twig**. Mai logica PHP nei template.
7. **Sicurezza by Design:** Ogni input è considerato ostile. Ogni output deve essere escapato. Nessun segreto hardcoded.
8. **Network Resilience:** Qualsiasi comunicazione di rete (API esterne, webhook, SNMP, WMI) deve prevedere timeout, retry logic, fallback, e validazione dei certificati TLS.
9. **urbackup** conoscenza approfondita software Urbackup e sue API
---
## 🏗️ ARCHITETTURA E STACK GLPI 11+
Quando scrivi codice per GLPI 11+, devi utilizzare esclusivamente i seguenti pattern:
### 1. Struttura del Plugin
Rispetta la struttura standardizzata di GLPI 11+:
- `src/`: Codice PHP (Namespace `GlpiPlugin\NomePlugin\`).
- `templates/`: File Twig.
- `locales/`: File `.po` / `.mo`.
- `css/` & `js/`: Asset frontend (compilati, no inline JS).
- `migrations/`: Script di migrazione DB versionati.
- `composer.json`: Dipendenze gestite rigorosamente via Composer.
### 2. Backend & Symfony Integration
- **Dependency Injection:** Usa i Service Container di Symfony. Inietta le dipendenze nei costruttori.
- **Routing:** Usa le annotazioni/attributi PHP 8 per le route (`#[Route]`).
- **Event Dispatcher:** Usa il sistema di eventi di GLPI/Symfony per le integrazioni (es. `item.add`, `item.update`).
- **Database:** Usa `DBmysqlIterator` o i Repository Doctrine/ORM se previsti. Usa le classi di Migrazione di GLPI per gli schema update.
### 3. Frontend (Twig)
- Usa `{{ var|e('html') }}` o affidati all'auto-escaping di Twig.
- Usa le macro e i template ereditati da GLPI 11 (`@glpi/...`) per mantenere la coerenza della UI (Design System GLPI).
---
## 🛡️ SECURITY & ZERO TRUST (ENTERPRISE MINDSET)
La sicurezza non è un'opzione, è il fondamento. Applica la "Defense in Depth":
1. **Autenticazione & Autorizzazione (RBAC):**
- Verifica SEMPRE i diritti GLPI prima di ogni azione: `Session::checkRight('plugin_nomeplugin_item', READ/UPDATE/DELETE/PURGE)`.
- Integra i nuovi profili e diritti usando le interfacce GLPI 11+.
2. **Protezione Input/Output:**
- **CSRF:** Usa `Html::hiddenField('_glpi_csrf_token', ...)` o i token Symfony in tutti i form.
- **XSS:** Valida e sanitizza. Usa `Html::cleanInputText()` per i dati testuali, `Html::entities_deep()` per gli array.
- **SQLi:** Usa SEMPRE i prepared statements o l'Iterator di GLPI. Mai concatenare variabili nelle query.
3. **Gestione Segreti:**
- Mai password o API key nel codice. Usa le variabili d'ambiente (`$_ENV`, `getenv()`) o la configurazione crittografata di GLPI.
4. **Audit & Logging:**
- Logga le azioni critiche usando il Logger di Symfony/GLPI. Includi `user_id`, `ip_address`, `action`, e `target_item`.
---
## 🌐 NETWORK ENGINEERING & INTEGRATIONS
Quando il plugin comunica con l'esterno (es. sync con Active Directory, API di monitoring, webhook verso ticketing esterno):
1. **Client HTTP Sicuri:**
- Usa `Guzzle` o `Symfony HttpClient`.
- Imposta SEMPRE `timeout` (es. 5s) e `connect_timeout`.
- Disabilita il fallback a HTTP non cifrato. Forza TLS 1.2/1.3.
- Supporta la validazione di certificati CA custom (per reti enterprise con CA interne).
2. **Webhooks & API Inbound:**
- Se esponi API, usa OAuth2 o Token API di GLPI.
- Se ricevi Webhooks, implementa la verifica della firma (es. HMAC-SHA256) per garantire l'integrità e la provenienza del payload.
3. **Gestione Code (Message Queue):**
- Per task di rete pesanti o lenti, NON bloccare il thread HTTP. Usa **Symfony Messenger** o le code asincrone native di GLPI 11+ per processare in background.
4. **Resilienza:**
- Implementa il pattern *Circuit Breaker* per le API esterne. Se un servizio di rete è down, il plugin non deve degradare le prestazioni di GLPI.
---
## ⚙️ WORKFLOW DI SVILUPPO (COME DEVI RAGIONARE)
Ogni volta che ti chiedo di sviluppare una feature, segui rigorosamente questo processo:
1. **Analisi & Threat Modeling:** Identifica i requisiti, i flussi di dati e le potenziali vulnerabilità (STRIDE).
2. **Design dell'Architettura:** Definisci le entità DB, le route, i servizi e i template Twig necessari.
3. **Implementazione (Codice):**
- Scrivi il codice PHP 8.1+ con tipizzazione stretta.
- Scrivi le query DB sicure.
- Scrivi i template Twig puliti.
4. **Review di Sicurezza e Performance:**
- Controlla se ci sono N+1 query problems.
- Verifica che tutti gli input siano validati (usa `Symfony\Component\Validator`).
- Assicurati che i cache (Symfony Cache) siano usati per dati statici o calcoli pesanti.
5. **Output:** Fornisci il codice strutturato per file, con commenti PHPDoc completi e spiegazioni brevi ma tecniche delle scelte di sicurezza/architettura.
---
## 🚨 FORMATO DI RISPOSTA RICHIESTO
- **Nessun preambolo inutile.** Inizia direttamente con l'analisi tecnica o il codice.
- Usa blocchi di codice markdown specificando il linguaggio e il percorso del file (es. `src/Controller/MyController.php`).
- Se una richiesta dell'utente viola le best practice di GLPI 11+ o la sicurezza enterprise, **RIFIUTALA educatamente**, spiega il rischio (es. "Questa richiesta richiede SQL grezzo, che viola la policy di sicurezza. Ecco l'alternativa sicura con DBmysqlIterator...") e fornisci la soluzione corretta.
- Includi sempre i comandi per la generazione delle migrazioni DB e il clearing della cache di Symfony/GLPI.
**Se hai compreso il tuo ruolo e le regole, rispondi esclusivamente con:**
"🛡️ *GLPI 11+ Enterprise Architect & Security Engineer initialized. Strict mode ON. Awaiting requirements for secure, scalable, and network-resilient plugin development.*"
-1
View File
@@ -1 +0,0 @@
Merge branch 'dev'
-1
View File
@@ -1 +0,0 @@
c78dce76a359499e4d9aac8987ae5a7f7f937309 branch 'main' of https://git.lavorain.cloud/mbenzi/urbackup
-1
View File
@@ -1 +0,0 @@
ref: refs/heads/dev
-1
View File
@@ -1 +0,0 @@
4b3ededa083d208e7ce6e42b8632d295735b2982
-16
View File
@@ -1,16 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://mbenzi:Portalnet68@git.lavorain.cloud/mbenzi/urbackup.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main
vscode-merge-base = origin/main
[branch "dev"]
remote = origin
merge = refs/heads/dev
vscode-merge-base = origin/dev
-1
View File
@@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.
-15
View File
@@ -1,15 +0,0 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:
-24
View File
@@ -1,24 +0,0 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}
-174
View File
@@ -1,174 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 2) and last update token
# formatted as a string and outputs to stdout a new update token and
# all files that have been modified since the update token. Paths must
# be relative to the root of the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $last_update_token) = @ARGV;
# Uncomment for debugging
# print STDERR "$0 $version $last_update_token\n";
# Check the hook interface version
if ($version ne 2) {
die "Unsupported query-fsmonitor hook version '$version'.\n" .
"Falling back to scanning...\n";
}
my $git_work_tree = get_working_dir();
my $retry = 1;
my $json_pkg;
eval {
require JSON::XS;
$json_pkg = "JSON::XS";
1;
} or do {
require JSON::PP;
$json_pkg = "JSON::PP";
};
launch_watchman();
sub launch_watchman {
my $o = watchman_query();
if (is_work_tree_watched($o)) {
output_result($o->{clock}, @{$o->{files}});
}
}
sub output_result {
my ($clockid, @files) = @_;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# binmode $fh, ":utf8";
# print $fh "$clockid\n@files\n";
# close $fh;
binmode STDOUT, ":utf8";
print $clockid;
print "\0";
local $, = "\0";
print @files;
}
sub watchman_clock {
my $response = qx/watchman clock "$git_work_tree"/;
die "Failed to get clock id on '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
return $json_pkg->new->utf8->decode($response);
}
sub watchman_query {
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
or die "open2() failed: $!\n" .
"Falling back to scanning...\n";
# In the query expression below we're asking for names of files that
# changed since $last_update_token but not from the .git folder.
#
# To accomplish this, we're using the "since" generator to use the
# recency index to select candidate nodes and "fields" to limit the
# output to file names only. Then we're using the "expression" term to
# further constrain the results.
my $last_update_line = "";
if (substr($last_update_token, 0, 1) eq "c") {
$last_update_token = "\"$last_update_token\"";
$last_update_line = qq[\n"since": $last_update_token,];
}
my $query = <<" END";
["query", "$git_work_tree", {$last_update_line
"fields": ["name"],
"expression": ["not", ["dirname", ".git"]]
}]
END
# Uncomment for debugging the watchman query
# open (my $fh, ">", ".git/watchman-query.json");
# print $fh $query;
# close $fh;
print CHLD_IN $query;
close CHLD_IN;
my $response = do {local $/; <CHLD_OUT>};
# Uncomment for debugging the watch response
# open ($fh, ">", ".git/watchman-response.json");
# print $fh $response;
# close $fh;
die "Watchman: command returned no output.\n" .
"Falling back to scanning...\n" if $response eq "";
die "Watchman: command returned invalid output: $response\n" .
"Falling back to scanning...\n" unless $response =~ /^\{/;
return $json_pkg->new->utf8->decode($response);
}
sub is_work_tree_watched {
my ($output) = @_;
my $error = $output->{error};
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
$retry--;
my $response = qx/watchman watch "$git_work_tree"/;
die "Failed to make watchman watch '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
$output = $json_pkg->new->utf8->decode($response);
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# close $fh;
# Watchman will always return all files on the first query so
# return the fast "everything is dirty" flag to git and do the
# Watchman query just to get it over with now so we won't pay
# the cost in git to look up each individual file.
my $o = watchman_clock();
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
output_result($o->{clock}, ("/"));
$last_update_token = $o->{clock};
eval { launch_watchman() };
return 0;
}
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
return 1;
}
sub get_working_dir {
my $working_dir;
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
$working_dir = Win32::GetCwd();
$working_dir =~ tr/\\/\//;
} else {
require Cwd;
$working_dir = Cwd::cwd();
}
return $working_dir;
}
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info
-14
View File
@@ -1,14 +0,0 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:
-49
View File
@@ -1,49 +0,0 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
-13
View File
@@ -1,13 +0,0 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:
-53
View File
@@ -1,53 +0,0 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0
-169
View File
@@ -1,169 +0,0 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END
-24
View File
@@ -1,24 +0,0 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi
-42
View File
@@ -1,42 +0,0 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi
-78
View File
@@ -1,78 +0,0 @@
#!/bin/sh
# An example hook script to update a checked-out tree on a git push.
#
# This hook is invoked by git-receive-pack(1) when it reacts to git
# push and updates reference(s) in its repository, and when the push
# tries to update the branch that is currently checked out and the
# receive.denyCurrentBranch configuration variable is set to
# updateInstead.
#
# By default, such a push is refused if the working tree and the index
# of the remote repository has any difference from the currently
# checked out commit; when both the working tree and the index match
# the current commit, they are updated to match the newly pushed tip
# of the branch. This hook is to be used to override the default
# behaviour; however the code below reimplements the default behaviour
# as a starting point for convenient modification.
#
# The hook receives the commit with which the tip of the current
# branch is going to be updated:
commit=$1
# It can exit with a non-zero status to refuse the push (when it does
# so, it must not modify the index or the working tree).
die () {
echo >&2 "$*"
exit 1
}
# Or it can make any necessary changes to the working tree and to the
# index to bring them to the desired state when the tip of the current
# branch is updated to the new commit, and exit with a zero status.
#
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
# in order to emulate git fetch that is run in the reverse direction
# with git push, as the two-tree form of git read-tree -u -m is
# essentially the same as git switch or git checkout that switches
# branches while keeping the local changes in the working tree that do
# not interfere with the difference between the branches.
# The below is a more-or-less exact translation to shell of the C code
# for the default behaviour for git's push-to-checkout hook defined in
# the push_to_deploy() function in builtin/receive-pack.c.
#
# Note that the hook will be executed from the repository directory,
# not from the working tree, so if you want to perform operations on
# the working tree, you will have to adapt your code accordingly, e.g.
# by adding "cd .." or using relative paths.
if ! git update-index -q --ignore-submodules --refresh
then
die "Up-to-date check failed"
fi
if ! git diff-files --quiet --ignore-submodules --
then
die "Working directory has unstaged changes"
fi
# This is a rough translation of:
#
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
if git cat-file -e HEAD 2>/dev/null
then
head=HEAD
else
head=$(git hash-object -t tree --stdin </dev/null)
fi
if ! git diff-index --quiet --cached --ignore-submodules $head --
then
die "Working directory has staged changes"
fi
if ! git read-tree -u -m "$commit"
then
die "Could not update working tree to new HEAD"
fi
-77
View File
@@ -1,77 +0,0 @@
#!/bin/sh
# An example hook script to validate a patch (and/or patch series) before
# sending it via email.
#
# The hook should exit with non-zero status after issuing an appropriate
# message if it wants to prevent the email(s) from being sent.
#
# To enable this hook, rename this file to "sendemail-validate".
#
# By default, it will only check that the patch(es) can be applied on top of
# the default upstream branch without conflicts in a secondary worktree. After
# validation (successful or not) of the last patch of a series, the worktree
# will be deleted.
#
# The following config variables can be set to change the default remote and
# remote ref that are used to apply the patches against:
#
# sendemail.validateRemote (default: origin)
# sendemail.validateRemoteRef (default: HEAD)
#
# Replace the TODO placeholders with appropriate checks according to your
# needs.
validate_cover_letter () {
file="$1"
# TODO: Replace with appropriate checks (e.g. spell checking).
true
}
validate_patch () {
file="$1"
# Ensure that the patch applies without conflicts.
git am -3 "$file" || return
# TODO: Replace with appropriate checks for this patch
# (e.g. checkpatch.pl).
true
}
validate_series () {
# TODO: Replace with appropriate checks for the whole series
# (e.g. quick build, coding style checks, etc.).
true
}
# main -------------------------------------------------------------------------
if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1
then
remote=$(git config --default origin --get sendemail.validateRemote) &&
ref=$(git config --default HEAD --get sendemail.validateRemoteRef) &&
worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) &&
git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" &&
git config --replace-all sendemail.validateWorktree "$worktree"
else
worktree=$(git config --get sendemail.validateWorktree)
fi || {
echo "sendemail-validate: error: failed to prepare worktree" >&2
exit 1
}
unset GIT_DIR GIT_WORK_TREE
cd "$worktree" &&
if grep -q "^diff --git " "$1"
then
validate_patch "$1"
else
validate_cover_letter "$1"
fi &&
if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
then
git config --unset-all sendemail.validateWorktree &&
trap 'git worktree remove -ff "$worktree"' EXIT &&
validate_series
fi
-128
View File
@@ -1,128 +0,0 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --type=bool hooks.allowunannotated)
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0
BIN
View File
Binary file not shown.
-6
View File
@@ -1,6 +0,0 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
-12
View File
@@ -1,12 +0,0 @@
0000000000000000000000000000000000000000 c78dce76a359499e4d9aac8987ae5a7f7f937309 mariano <mariano@localhost.local> 1777454275 +0200 clone: from https://git.lavorain.cloud/mbenzi/urbackup.git
c78dce76a359499e4d9aac8987ae5a7f7f937309 6493631fb88f9a570c95cfab46b89a144a9e9503 test <test.localhost.local> 1777455335 +0200 commit: start opencode
6493631fb88f9a570c95cfab46b89a144a9e9503 9bed80d88c7128c3587329fee6dc3cf2f0efc9fd test <test.localhost.local> 1777455383 +0200 checkout: moving from main to dev
9bed80d88c7128c3587329fee6dc3cf2f0efc9fd 98dc9fafeb52a5c6d0130be29d5716133e086821 test <test.localhost.local> 1777456724 +0200 commit: modifiche da opencode
98dc9fafeb52a5c6d0130be29d5716133e086821 9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 test <test.localhost.local> 1777456887 +0200 commit: opencode dopo commit 1
9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 311accf4bc2817584dd35d611c7483bfd1f9d70d mariano <mariano@localhost.local> 1777462043 +0200 commit: modifica x instalalzione - opencode
311accf4bc2817584dd35d611c7483bfd1f9d70d b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 mariano <mariano@localhost.local> 1778580538 +0200 commit: sisetmazione instalalzione nuovo model
b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 mariano <mariano@localhost.local> 1779256743 +0200 commit: finito parte computer
bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 27aac99d1555a75d373756d8727afeefd6b69376 mariano <mariano@localhost.local> 1779260579 +0200 commit: commit - stable -
27aac99d1555a75d373756d8727afeefd6b69376 6493631fb88f9a570c95cfab46b89a144a9e9503 mariano <mariano@localhost.local> 1779260625 +0200 checkout: moving from dev to main
6493631fb88f9a570c95cfab46b89a144a9e9503 4b3ededa083d208e7ce6e42b8632d295735b2982 mariano <mariano@localhost.local> 1779260803 +0200 commit (merge): Merge branch 'dev'
4b3ededa083d208e7ce6e42b8632d295735b2982 27aac99d1555a75d373756d8727afeefd6b69376 mariano <mariano@localhost.local> 1779260816 +0200 checkout: moving from main to dev
-7
View File
@@ -1,7 +0,0 @@
0000000000000000000000000000000000000000 9bed80d88c7128c3587329fee6dc3cf2f0efc9fd test <test.localhost.local> 1777455383 +0200 branch: Created from refs/remotes/origin/dev
9bed80d88c7128c3587329fee6dc3cf2f0efc9fd 98dc9fafeb52a5c6d0130be29d5716133e086821 test <test.localhost.local> 1777456724 +0200 commit: modifiche da opencode
98dc9fafeb52a5c6d0130be29d5716133e086821 9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 test <test.localhost.local> 1777456887 +0200 commit: opencode dopo commit 1
9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 311accf4bc2817584dd35d611c7483bfd1f9d70d mariano <mariano@localhost.local> 1777462043 +0200 commit: modifica x instalalzione - opencode
311accf4bc2817584dd35d611c7483bfd1f9d70d b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 mariano <mariano@localhost.local> 1778580538 +0200 commit: sisetmazione instalalzione nuovo model
b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 mariano <mariano@localhost.local> 1779256743 +0200 commit: finito parte computer
bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 27aac99d1555a75d373756d8727afeefd6b69376 mariano <mariano@localhost.local> 1779260579 +0200 commit: commit - stable -
-3
View File
@@ -1,3 +0,0 @@
0000000000000000000000000000000000000000 c78dce76a359499e4d9aac8987ae5a7f7f937309 mariano <mariano@localhost.local> 1777454275 +0200 clone: from https://git.lavorain.cloud/mbenzi/urbackup.git
c78dce76a359499e4d9aac8987ae5a7f7f937309 6493631fb88f9a570c95cfab46b89a144a9e9503 test <test.localhost.local> 1777455335 +0200 commit: start opencode
6493631fb88f9a570c95cfab46b89a144a9e9503 4b3ededa083d208e7ce6e42b8632d295735b2982 mariano <mariano@localhost.local> 1779260803 +0200 commit (merge): Merge branch 'dev'
-1
View File
@@ -1 +0,0 @@
0000000000000000000000000000000000000000 c78dce76a359499e4d9aac8987ae5a7f7f937309 mariano <mariano@localhost.local> 1777454275 +0200 clone: from https://git.lavorain.cloud/mbenzi/urbackup.git
-5
View File
@@ -1,5 +0,0 @@
9bed80d88c7128c3587329fee6dc3cf2f0efc9fd 9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 test <test.localhost.local> 1777456894 +0200 update by push
9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 311accf4bc2817584dd35d611c7483bfd1f9d70d mariano <mariano@localhost.local> 1777462051 +0200 update by push
311accf4bc2817584dd35d611c7483bfd1f9d70d b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 mariano <mariano@localhost.local> 1778580554 +0200 update by push
b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 mariano <mariano@localhost.local> 1779256765 +0200 update by push
bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 27aac99d1555a75d373756d8727afeefd6b69376 mariano <mariano@localhost.local> 1779260580 +0200 update by push
-1
View File
@@ -1 +0,0 @@
c78dce76a359499e4d9aac8987ae5a7f7f937309 4b3ededa083d208e7ce6e42b8632d295735b2982 mariano <mariano@localhost.local> 1779260811 +0200 update by push
@@ -1,7 +0,0 @@
xµVmoÛ6ÞgýŠ+f@rfÏM±/Sk‰ã¼ EjØIÂ6 Y¢m"©‘T2£ðß‘¢^¬Ä¶µúd“w¼»ç¹çÈUÌWðî÷w¿ýôá,ݦŽ‘0ñ¤4TKµK‰ìŸ¶ß;NïäÄè~¯Oö .‚ð1K!³
e°æ®?Žo¿w žã° !2
B×qJÇ&ÞüA¬Lüù3%x•fòu£)OÚàØþXð5Éqƒ"ÜyJ‡1%Lå¦Ó]²æl7¿ÈX“ù•ÀTŸ¹x´ÿ«Üæç+ä%UµtxÂ')gxð|Â3EÙf~ÎW¢œ™%\±²¿Q*½â˜€µ%fD6ò;jý‡äl‚èr&m€•Ä65"%F/þ"Ü"ÄØdRBhU ¿a‘„—u:_Àïç™.Œxn/ï˜^fìICŽt; ™öÁ-v–ùÎ2¦RánBÔ–GÒ‡™{=ºwí…98ÍV1
a±PƒÚ:OOzmž8Œ]ž†N…®Á{“›ø~°Ï”<{í6TÚJáû•iì&t³U#!¸ðPQz[{§ü™[oIá•«Ú¨´5y‡5zÓZßÇElhÄwY²BÏv»sàÜZNG“Ï£ÉÌߌñ÷Ç+wqháQB™{¸XÄ4d•êY 3Òß”h>}_nù3¦£åXhnëVyUkΕÎ-/ÜýO|ö¾ÒhœS÷N; °© JÁPM#úpçÑ/G)ׇå5x”)hÑèuâ[Ò
}`äÙ£Ë*QÑÞ0€·Ív°¾ÝA¸%᣶êÀdt~iQјîÄ8€Û¨á×=íÀýîG5Gí\ÚaKŒ’Tí
¼ÍÉUŠ¥íLoæZˆ0¥Å¿äHá¤8Αݭénüiz\xÚ'#F‡Þ$DÐÒŒâRRŸ6ó
*£I;†(‡™ØH™gpn"ˆÊ3LÖÏõf%9ý¹2 CTœi¤u€ªÅØà
@@ -1,3 +0,0 @@
xŽM
ƒ0F»ö³/–ü'¥ô*“L‚‚šÓûWÄtõ½Íã}©®ëÜAi}ë-gH±0¢.î_,Î…`¼rBbd¢“jøPË[‡˜’ŠÚ2I61&e
Ë"YÚ¤b.HÆad;зOµÁJm¦­Âó‚÷R-SÝûã¤HïñˆYpJˆ!{þK¾löNqÉ0?¯ËJÇ
@@ -1 +0,0 @@
x+)JMU01f040031Q(N-*K-/I-.ׁ+ָ(`¨4zִ¸(…u¾¾ֲד«1בֺ9¢ת¥¿/
@@ -1,5 +0,0 @@
x¥TÛnÚ@í³¿bYZHUA."$¡´4E¤V Z-ö€·1¶»»ÎEÿÞÙµ¹´úúì™™3gÎì4ɦpøîýÑ›ã³<νÆî®»ÐþØþ
˜Fy&S³LA{ЃÚXïX£mD A·O®™Êè`.æH?Æ Jé`Ãóþ(¤Bž¥!œ_ô†œÃ>°c ïù#N5ªT<ÌÒ™œïVkyÞœ€‰üÎU—Û
dŠQD¨‚•ÂÔÔGÏ96Aäy"Cad6¾ë,mA ¥ÑœŒGWõ£›¬Ñ€Ž-I6—©wƒZÓñfÓaè[µ¤‚ߎ*9öä ‚·ë€X<àÐ:'ÅñBMEx_äl†—í‹Z
~z@†qÇ4Ì" n™.Â*389…™H4î[Hs6vAŽj!:6!8.Ó“4-oéy~Å”ŒàL
‚ÀçÝËÑ-“›ÀÙø|ðåfëû f›²MlEŸÀÁÿàì¥"!%è]üïg"‚’%¡I4ž_½P·ìÆ
@@ -1,6 +0,0 @@
xµT]kÛ0ݳÅì„&!#ݺ-k amÒz0(Å–m1E2úèFÿ{%ù+dcÃóS¤èÞ{î¹çÜ;˜½yûîÕûeQÁt4
`ã¾>—, œü0%”Ìä”C&$,¯7«¾ ùZœr¥1cPJ¥Új0›Mú®8
£,YI7¾·‡Xî|¯)2ÊÈyÐ ¢×)É('iºÆÑv½¾‡Cø€ýRJ¢ðNHy˜Àwa Áœ
8ñøuAm6•}'I¢ÙaσçvVqײÇàºt~DÉa'³çižh*x=dj¬È4PMZ4œWaÀÁžæûÐÀÉO¸iÎÑæ:^®nQ¼]|þò5Þ oWÛ»ÕúÖô­u‘ã‹”ª’áÃ
ÎI„Pž*£â‹…g6-[uÊšØù¼}¼¥y¡UÔ<¨TÖuׂG©%ÒxÇHÔ³Er;?tf©p¬+G¶›Òéÿÿ˜VùH$VŠèÿ“ºï¬ª>”½3‘žÑÜÃýM+ä‰$F“NgÍpkMki¬µ:\ÚÉnŒ1èB
X[c”Xâ}wŽ.ýá ñVª"”–”çN ƒª„ûyïôïÝËcß=
@@ -1,3 +0,0 @@
xK
Β0@]ηΩ ’Ιo2 ήe’L©Π6¥ο/
ΐΝγmΌ6Φυ©Φ±T#W½+9KζZ+Φ. ) „ΙyHΝΞ‡lj–ή3‡DHb'ζV¨ KbpΆ€Α‘α—Ξγ°*§Ϊϋ‡·e4^ζρ³‡D)…μΥyηLϋΞ©ό™™SωP;vΩΪθbή{οE
@@ -1,2 +0,0 @@
xMQKkÛ@îY¿bÀ»¤&PBJÁWuœ[@;ZM”IW»ËîHÔþõéióÍ|i]háû·Ë/ ¸ã¿ðÀYB:TÕï‘ô¯ì$¡µŒÀÐŽ=ØÄ–!qN"% ‰AW¾# %ÇçUµXÀŽú #
èþgýëuUÁ³°ãã±´d¢&'{afœÂ­ÃœÊ-]ƒ¹w‘k7öì_žS‹öÏ_ÌJ‡mƒ‡ñs C°˜„œ^ û2uÙ‘rX,ø:Ñ+ç@°U”ªÖB‹¹ôJÒÄ™¥?Xš¯F»6oŒƒ¦BÉ+T}q`•ß4Üõâ›_Û§ýîöq»Âù MGê,Ñ2Kb+"å›õê‡özϧÀë‡úÿðô Wç—%¸MðÚ;Z]Ć ¬ì%]µH¾#ol)hÃŽ° ÞNRž±¡ïItu< £Z×](r¯bº¬XÊ‚^ò§ô÷|3¡cM‹–«¢x";¯ÜÌ5õº¹RäœGAÕ?øcÔš
@@ -1,2 +0,0 @@
x•ŽA
Â0E]ç³d&m2)ˆwIf&ThRãýEŸù¼Í{|iëzïà}<õà "‰Ô¨<Nès¡2šBMš•µúZÖš8¸=¶u˜’ÊTsµ|iÀb~ÒÀi SLž\~ö¹ÐíÑáúÞËÒ$/sûÑ

Some files were not shown because too many files have changed in this diff Show More