# Guida per creare uno ZIP installabile di un plugin ChronoQuest

Questa guida spiega come preparare uno ZIP che puo essere caricato dalla pagina:

```text
/docenti/plugin
```

Il caricamento e consentito agli utenti amministratori. Durante l'installazione ChronoQuest legge il file `plugin.json`, registra il plugin in `ct_plugin`, esegue lo script SQL indicato e copia i file del plugin nelle cartelle previste.

## 1. Struttura minima dello ZIP

Lo ZIP deve contenere `plugin.json` nella radice del pacchetto.

Struttura consigliata:

```text
plugin.json
sql/install.sql
src/Plugin/MioPlugin/MioPluginController.php
src/Plugin/MioPlugin/MioPluginService.php
src/Plugin/MioPlugin/MioPluginListener.php
public/views/plugin/mio_plugin/docenti/index.php
public/views/plugin/mio_plugin/studenti/index.php
public/css/plugin_mio_plugin/mio-plugin.css
public/js/plugin_mio_plugin/mio-plugin.js
public/assets/plugin_mio_plugin/...
```

Non tutte le cartelle sono obbligatorie. Un plugin puo avere solo lato docente, solo lato studente, oppure solo listener eventi.

## 2. File plugin.json

`plugin.json` e il manifest del plugin. Contiene metadati, menu, rotte, eventi e il percorso dello script SQL.

Esempio:

```json
{
  "name": "Mio Plugin",
  "code": "mio_plugin",
  "version": "1.0",
  "description": "Descrizione breve del plugin.",
  "install_sql": "sql/install.sql",
  "menus": {
    "teacher": [
      {
        "label": "Mio Plugin",
        "url": "/docenti/plugin/mio-plugin",
        "icon": "fas fa-fw fa-puzzle-piece",
        "order": 80
      }
    ],
    "student": [
      {
        "label": "Mio Plugin",
        "url": "/studenti/plugin/mio-plugin",
        "icon": "fas fa-fw fa-puzzle-piece",
        "order": 80
      }
    ]
  },
  "routes": [
    {
      "method": "GET",
      "path": "/docenti/plugin/mio-plugin",
      "handler": "App\\Plugin\\MioPlugin\\MioPluginController@teacherIndex",
      "area": "teacher"
    },
    {
      "method": "GET",
      "path": "/studenti/plugin/mio-plugin",
      "handler": "App\\Plugin\\MioPlugin\\MioPluginController@studentIndex",
      "area": "student"
    }
  ],
  "events": {
    "delivery.evaluated": [
      "App\\Plugin\\MioPlugin\\MioPluginListener@onDeliveryEvaluated"
    ]
  }
}
```

Campi principali:

- `name`: nome visibile del plugin.
- `code`: codice stabile del plugin. Deve usare lettere minuscole, numeri e underscore, per esempio `mio_plugin`.
- `version`: versione del plugin.
- `description`: descrizione mostrata nella gestione plugin.
- `install_sql`: percorso dello script SQL dentro lo ZIP.
- `menus`: voci da aggiungere al menu docente e/o studente.
- `routes`: rotte gestite dal plugin.
- `events`: listener agganciati all'event bus.

## 3. Convenzioni sul codice plugin

Il codice del plugin deve stare sotto:

```text
src/Plugin/NomePlugin/
```

Il nome cartella deve essere in formato StudlyCase derivato dal codice plugin.

Esempi:

```text
mio_plugin          -> src/Plugin/MioPlugin/
fight_the_monster  -> src/Plugin/FightTheMonster/
corse              -> src/Plugin/Corse/
```

Un controller tipico:

```php
<?php

namespace App\Plugin\MioPlugin;

use App\Core\View;

class MioPluginController
{
    public function teacherIndex(): void
    {
        View::render('plugin/mio_plugin/docenti/index', [
            'title' => 'Mio Plugin',
            'useMathJax' => false,
        ], 'mainDocLayout');
    }

    public function studentIndex(): void
    {
        View::render('plugin/mio_plugin/studenti/index', [
            'title' => 'Mio Plugin',
            'useMathJax' => false,
        ], 'mainStudLayout');
    }
}
```

## 4. Rotte dinamiche

Le rotte vengono lette da `plugin.json` e registrate automaticamente.

Formato:

```json
{
  "method": "GET",
  "path": "/docenti/plugin/mio-plugin",
  "handler": "App\\Plugin\\MioPlugin\\MioPluginController@teacherIndex",
  "area": "teacher"
}
```

Valori ammessi per `method`:

- `GET`
- `POST`

Valori consigliati per `area`:

- `teacher`
- `student`

Il sistema controlla automaticamente che:

- l'utente sia nel lato corretto;
- il plugin sia attivo globalmente;
- il plugin sia attivo per la classe selezionata.

## 5. Menu dinamici

Le voci di menu si dichiarano in `menus.teacher` e `menus.student`.

Esempio con label fissa:

```json
{
  "label": "Mio Plugin",
  "url": "/docenti/plugin/mio-plugin",
  "icon": "fas fa-fw fa-puzzle-piece",
  "order": 80
}
```

Esempio con traduzione:

```json
{
  "label_key": "plugin.mio_plugin.menu",
  "url": "/docenti/plugin/mio-plugin",
  "icon": "fas fa-fw fa-puzzle-piece",
  "order": 80
}
```

`order` decide la posizione nel gruppo plugin del menu. Numeri piu bassi vengono mostrati prima.

## 6. Event bus

Un plugin puo ascoltare eventi del programma dichiarandoli in `events`.

Eventi fondamentali disponibili:

```text
delivery.submitted
delivery.evaluated
chest.earned
student.reward.assigned
```

Esempio:

```json
{
  "events": {
    "chest.earned": [
      "App\\Plugin\\MioPlugin\\MioPluginListener@onChestEarned"
    ]
  }
}
```

Listener PHP:

```php
<?php

namespace App\Plugin\MioPlugin;

class MioPluginListener
{
    public function onChestEarned(array $payload, string $eventName = ''): array
    {
        return [
            'extra_chests' => [],
        ];
    }
}
```

## 7. Regole per lo script SQL

Lo script SQL deve essere installabile in sicurezza. Al momento sono accettati solo statement:

```sql
CREATE TABLE ...
CREATE TABLE IF NOT EXISTS ...
```

Ogni tabella deve iniziare con:

```text
ct_plugin_
```

Esempio corretto:

```sql
CREATE TABLE IF NOT EXISTS ct_plugin_mio_plugin_eventi (
  id_evento int NOT NULL AUTO_INCREMENT,
  fk_classe int NOT NULL,
  titolo varchar(255) NOT NULL,
  creato_il datetime NOT NULL,
  PRIMARY KEY (id_evento),
  KEY idx_plugin_mio_eventi_classe (fk_classe)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

Esempi non accettati:

```sql
INSERT INTO ct_plugin ...
ALTER TABLE ...
CREATE TABLE ct_mio_plugin ...
CREATE TABLE users_backup ...
```

Il record in `ct_plugin` viene creato automaticamente dal sistema leggendo `plugin.json`. Non va inserito nello script SQL.

## 8. Nomi tabella consigliati

Usare sempre questo formato:

```text
ct_plugin_{codice_plugin}_{nome_tabella}
```

Esempi:

```text
ct_plugin_corse_cars
ct_plugin_corse_risultati
ct_plugin_fight_the_monster_monsters
ct_plugin_mio_plugin_eventi
```

Evitare nomi generici come:

```text
ct_cars
ct_items
ct_logs
```

Gli indici MySQL hanno un limite di lunghezza. Usare nomi brevi:

```sql
KEY idx_plugin_mio_eventi_classe (fk_classe)
```

## 9. Cartelle che l'installer copia

Per sicurezza, l'installer estrae solo file in cartelle previste.

Per un plugin con codice `mio_plugin`, sono ammessi:

```text
src/Plugin/MioPlugin/
public/views/plugin/mio_plugin/
public/css/plugin_mio_plugin/
public/js/plugin_mio_plugin/
public/assets/plugin_mio_plugin/
translations/it/plugin_mio_plugin.php
translations/en/plugin_mio_plugin.php
```

File fuori da questi percorsi vengono ignorati.

## 10. Checklist prima di creare lo ZIP

Prima di consegnare il plugin:

- `plugin.json` e presente nella radice dello ZIP.
- `code` usa solo minuscole, numeri e underscore.
- `install_sql` punta a un file esistente nello ZIP.
- Lo SQL contiene solo `CREATE TABLE`.
- Tutte le tabelle iniziano con `ct_plugin_`.
- Il codice PHP usa gli stessi nomi tabella presenti nello SQL.
- Le rotte del manifest puntano a classi e metodi esistenti.
- Le viste usate dai controller sono incluse nello ZIP.
- CSS, JS e asset sono in cartelle ammesse.
- Le chiavi `label_key`, se usate, esistono nelle traduzioni.

## 11. Esempio reale: plugin Corse

Struttura semplificata:

```text
plugin.json
sql/install.sql
src/Plugin/Corse/CorsePluginController.php
src/Plugin/Corse/CorsePluginService.php
public/views/plugin/corse/docenti/index.php
public/views/plugin/corse/studenti/index.php
public/css/plugin_corse/corse.css
```

Tabelle installate:

```text
ct_plugin_corse_cars
ct_plugin_corse_car_abilita
ct_plugin_corse_studenti_cars
ct_plugin_corse
ct_plugin_corse_risultati
ct_plugin_corse_risultati_studenti
ct_plugin_corse_premi_riscossi
```

ZIP generato nel progetto:

```text
plugin_zip/corse.zip
```

## 12. Esempio reale: plugin Fight the Monster

Struttura semplificata:

```text
plugin.json
sql/install.sql
src/Plugin/FightTheMonster/FightTheMonsterPluginController.php
src/Plugin/FightTheMonster/FightTheMonsterPluginService.php
src/Plugin/FightTheMonster/FightTheMonsterPluginListener.php
public/views/plugin/fight_the_monster/docenti/index.php
public/views/plugin/fight_the_monster/studenti/index.php
public/css/plugin_fight_the_monster/fight-the-monster.css
```

Tabelle installate:

```text
ct_plugin_fight_the_monster_monsters
ct_plugin_fight_the_monster_heroes
ct_plugin_fight_the_monster_items
ct_plugin_fight_the_monster_student_items
ct_plugin_fight_the_monster_drop_notifications
ct_plugin_fight_the_monster_logs
```

ZIP generato nel progetto:

```text
plugin_zip/fight_the_monster.zip
```

## 13. Procedura consigliata

1. Creare il codice PHP del plugin in `src/Plugin/NomePlugin/`.
2. Creare le viste in `public/views/plugin/codice_plugin/`.
3. Creare eventuali CSS/JS/asset nelle cartelle `public/css`, `public/js`, `public/assets`.
4. Scrivere `sql/install.sql` solo con `CREATE TABLE IF NOT EXISTS ct_plugin_...`.
5. Scrivere `plugin.json` con metadati, menu, rotte ed eventi.
6. Creare lo ZIP mantenendo i percorsi dalla radice del progetto.
7. Caricare lo ZIP da `/docenti/plugin` con un utente amministratore.
8. Attivare il plugin per la classe dalla stessa pagina.

## 14. Note importanti

- L'installer non gestisce ancora aggiornamenti, rollback completo o disinstallazione.
- Se una rotta plugin non appare, controllare che il plugin sia attivo per la classe.
- Se il menu non appare, controllare `menus.teacher` / `menus.student` nel manifest.
- Se l'SQL fallisce, verificare che ogni statement inizi con `CREATE TABLE` e che ogni tabella inizi con `ct_plugin_`.
- Se il controller non viene trovato, verificare namespace, nome classe, nome metodo e percorso in `src/Plugin/...`.

