refactor solution to PHP through AI

This commit is contained in:
2026-07-20 14:27:17 +02:00
parent a77f6f19cf
commit 151bc70775
21 changed files with 2563 additions and 41 deletions
+190
View File
@@ -0,0 +1,190 @@
---
name: elixforms-php-common-library
description: >
Documentazione della libreria condivisa PHP common/ del progetto elixForms.
Descrive la classe base ElixFormsComponent, i metodi factory per i campi form,
ElixFormsElement per il layout, QueryParamHelper per i query parameter,
e il pattern di ereditarietà per creare pagine custom.
Attiva questa skill quando lavori sui componenti PHP condivisi, crei nuovi tipi di campo,
modifichi la classe base, o integri con la piattaforma elixForms in PHP.
---
# Libreria Common PHP elixForms
## Panoramica
La cartella `php/common/` contiene la libreria condivisa PHP usata da tutte le pagine custom PHP. È basata su **Bootstrap Italia + HTML nativo** e implementa un sistema di form con campi dinamici, analogo alla libreria React in `react/common/`.
## Architettura delle Classi
```
ElixFormsComponent (classe astratta, PHP 8.0+)
└── SceltaCarrieraPage (classe concreta, in scelta-carriera/index.php)
└── [AltrePageCustom] (classi concrete nelle sotto-cartelle)
```
### Namespace
Tutte le classi usano il namespace PSR-4:
```php
namespace ElixForms\Common;
```
Le pagine importano con:
```php
require_once __DIR__ . '/../common/ElixFormsComponent.php';
use ElixForms\Common\ElixFormsComponent;
```
## File della Libreria
### `ElixFormsComponent.php` (Classe Base Astratta)
Equivalente di `ElixFormsComponentAbstract.tsx` in React.
#### Proprietà
```php
protected array $properties; // Equivalente di IElixFormsComponentProperties
```
Chiavi supportate in `$properties`:
| Chiave | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| `moduleName` | string | Sì | Nome del modulo nel breadcrumb |
| `cardTitle` | string | Sì | Titolo della card principale |
| `userDisplayName` | string | No | Nome utente nell'header |
| `headerTitle` | string | No | Titolo sotto il breadcrumb |
| `headerHeroImageSrc` | string | No | URL immagine hero |
| `cardDescription` | string | No | Descrizione nella card |
| `alertInfoTitle` | string | No | Titolo alert informativo |
| `alertInfoMessage` | string | No | Messaggio alert informativo |
| `instructionsTitle` | string | No | Titolo istruzioni |
| `instructionsMessage` | string | No | Messaggio istruzioni |
| `submitText` | string | No | Testo bottone submit (default: 'INVIA') |
| `additionalFieldsJson` | string | No | JSON schema per campi dinamici |
| `isDarkTheme` | bool | No | Tema scuro (riservato per uso futuro) |
#### Metodi Factory (Campi Form)
| Metodo | Tipo Campo | HTML Output |
|---|---|---|
| `createTextInput($name, $label, $required)` | Testo | `<input type="text">` |
| `createTextAreaInput($name, $label, $required)` | Textarea | `<textarea>` |
| `createNumberInput($name, $label, $required)` | Numerico | `<input type="number">` |
| `createBooleanInput($name, $label, $required)` | Booleano (Sì/No) | Radio buttons |
| `createRadioInput($name, $label, $options, $required)` | Radio | Radio buttons |
| `createCheckboxInput($name, $label, $options)` | Checkbox multipli | Checkboxes + hidden |
| `createDropdownInput($name, $label, $options, $required)` | Dropdown | `<select>` |
| `createHiddenInput($name)` | Nascosto | `<input type="hidden">` |
#### Metodi Estensibili (Override)
```php
// Campi custom del form — OBBLIGATORIO sovrascrivere
protected function createCustomFormFields(): string { return ''; }
// Contenuto extra prima del form
protected function renderExtraContentPre(): string { return ''; }
// Contenuto extra dopo il form
protected function renderExtraContentPost(): string { return ''; }
```
#### Metodo Principale
```php
public function render(): string
```
Genera l'HTML **completo** della pagina (`<!DOCTYPE html>` ... `</html>`), includendo:
- `<head>` con CSS Bootstrap Italia e design system UniPR
- Header con logo UniPR e nome utente
- Breadcrumb
- Card con alert, istruzioni, form e campi
- Footer "powered by elixForms"
- Script JS per checkbox
### `ElixFormsElement.php` (Wrapper Layout)
Genera il layout a due colonne per ogni campo form:
```php
$element = new ElixFormsElement($labelHtml, $inputHtml, $separator);
echo $element->render();
```
Output HTML:
```html
<div class="row mb-3 align-items-start">
<div class="col-12 col-md-4">
<div class="text-md-end pe-md-3 mt-2">{label}</div>
</div>
<div class="col-12 col-md-8">
{input}
</div>
</div>
```
### `QueryParamHelper.php` (Utility Query String)
Classe statica per leggere parametri dalla query string `$_GET`:
```php
QueryParamHelper::getCheckedFromQuery('COL0006'); // int[] (checkbox, valori separati da virgola)
QueryParamHelper::getOptionFromQuery('COL0015'); // ?int (radio/dropdown)
QueryParamHelper::getBooleanFromQuery('COL0004'); // ?bool (boolean)
QueryParamHelper::getDecodedTextFromQuery('COL0002'); // ?string (testo URL-decoded)
```
### `HttpClient.php` (Utility Richieste HTTP REST)
Classe orientata agli oggetti per effettuare chiamate HTTP server-to-server sicure verso Web Service esterni (es. endpoint contratti):
```php
use ElixForms\Common\HttpClient;
$client = new HttpClient([
'X-Api-Key' => 'chiave-api-custom',
'Accept' => 'application/json'
]);
$client->setBasicAuth('username', 'password');
$client->setTimeout(10); // timeout in secondi
// Richiesta GET
$response = $client->get('http://api-endpoint/cerca', [
'parametro1' => 'valore1'
]);
```
**Best Practices gestite da HttpClient:**
- **Timeout**: Gestione dei timeout di connessione ed esecuzione per evitare blocchi infiniti del server Apache.
- **Sicurezza delle credenziali**: Permette di eseguire chiamate server-side mantenendo chiavi API e password nascoste al browser del client.
- **Error Handling**: Se il server remoto restituisce codici HTTP diversi da 20x (es. 404, 500), solleva una `RuntimeException` contenente il codice di stato generico senza includere nel messaggio d'errore l'eventuale payload della risposta (es. tag HTML dell'errore di Apache) per motivi di sicurezza ed integrità del JSON finale.
## Differenze rispetto alla versione React
| Aspetto | React | PHP |
|---|---|---|
| **Rendering** | Client-side (JSX → DOM) | Server-side (PHP → HTML) |
| **State** | `this.state` + `this.setState()` | Valori da `$_GET` (stateless) |
| **Checkbox sync** | React state + hidden input | JS client-side + hidden input |
| **Build** | Vite → single bundle JS | Nessuna build necessaria |
| **CSS** | Iniettato nel JS | `<link>` tags nell'HTML |
| **Output** | Singolo file `.js` | File `.php` serviti da Apache |
## Parametri Obbligatori elixForms
Gestiti automaticamente come hidden inputs dalla classe base:
- `RWE2_MODULE_ID`, `RWE2_REQUEST_ID`
- `custom-workflow-back-url`, `custom-workflow-generic-id`
- `custom-workflow-current-tabrel-genid`, `custom-workflow-source-field`
- `crc`, `MODULE_TESTMODE_KEY`, `ELANG`
## Note Importanti
- Il form fa POST a `https://procedure.unipr.it/rwe2/ComeBackToElixAndSave`
- L'encoding charset è `ISO-8859-1` per compatibilità con il backend
- Gli ID dei campi form **non devono mai essere manipolati** (vedi AGENTS.md)
- L'ID dell'hidden input dei checkbox e dei mandatory fields ha suffisso `_hidden`
- I checkbox usano un attributo `data-param` e uno script JS per sincronizzare il valore con l'hidden input
+258
View File
@@ -0,0 +1,258 @@
---
name: elixforms-php-create-new-page
description: >
Guida step-by-step per creare una nuova pagina custom PHP nel progetto elixForms.
Copre la struttura dei file, l'importazione della libreria common,
le convenzioni di naming, e la configurazione per il deploy su Apache.
Attiva questa skill quando devi creare una nuova pagina PHP,
duplicare una pagina esistente, o scaffoldare un nuovo micro-progetto PHP nel monorepo.
---
# Creare una Nuova Pagina PHP elixForms Custom
## Prerequisiti
- PHP 8.0+ installato
- Web server Apache configurato per servire la cartella `php/`
## Step 1: Creare la Cartella della Pagina
```powershell
cd php
mkdir <nome-pagina>
```
Il nome della cartella deve essere in **kebab-case** (es. `scelta-carriera`, `recupero-proposta`).
## Step 2: Creare il File index.php
Creare `php/<nome-pagina>/index.php`:
```php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../common/ElixFormsComponent.php';
use ElixForms\Common\ElixFormsComponent;
/**
* Pagina custom "<NomePagina>" per elixForms.
*/
class NomePaginaPage extends ElixFormsComponent
{
protected function createCustomFormFields(): string
{
$html = '';
// Aggiungi qui i campi del form usando i metodi factory:
// $html .= $this->createTextInput('COL0001', 'Campo Testo', true);
// $html .= $this->createDropdownInput('COL0002', 'Scelta', [...options], true);
return $html;
}
// Opzionale: contenuto extra prima del form
// protected function renderExtraContentPre(): string { return ''; }
// Opzionale: contenuto extra dopo il form
// protected function renderExtraContentPost(): string { return ''; }
}
// Entry point
$page = new NomePaginaPage([
'moduleName' => 'Nome Modulo',
'cardTitle' => 'Titolo Pagina',
'cardDescription' => 'Descrizione della pagina.',
'userDisplayName' => '', // Verrà popolato da elixForms
'headerTitle' => '',
'headerHeroImageSrc' => '',
'alertInfoTitle' => '',
'alertInfoMessage' => '',
'instructionsTitle' => '',
'instructionsMessage' => '',
'submitText' => 'INVIA',
]);
echo $page->render();
```
## Step 3: Definire i Campi Custom
Sovrascrivere `createCustomFormFields()` con i campi necessari:
```php
protected function createCustomFormFields(): string
{
$html = '';
// Dropdown
$html .= $this->createDropdownInput('COL0015', 'Scelta', [
['value' => 0, 'label' => 'Opzione A'],
['value' => 1, 'label' => 'Opzione B'],
['value' => 2, 'label' => 'Opzione C'],
], true);
// Testo
$html .= $this->createTextInput('COL0002', 'Nome', true);
// Textarea
$html .= $this->createTextAreaInput('COL0003', 'Note', false);
// Booleano (Sì/No)
$html .= $this->createBooleanInput('COL0004', 'Conferma', true);
// Radio
$html .= $this->createRadioInput('COL0005', 'Preferenza', [
['value' => 1, 'label' => 'Prima scelta'],
['value' => 2, 'label' => 'Seconda scelta'],
], true);
// Checkbox
$html .= $this->createCheckboxInput('COL0006', 'Opzioni Multiple', [
['value' => 1, 'label' => 'Opzione 1'],
['value' => 2, 'label' => 'Opzione 2'],
]);
// Numerico
$html .= $this->createNumberInput('COL0007', 'Quantità', true);
return $html;
}
```
## Step 4: Opzionale — Aggiungere File di Configurazione
Per pagine complesse, creare un file `config.json` nella cartella della pagina:
```json
{
"component": {
"moduleName": "Nome Modulo",
"cardTitle": "Titolo",
"cardDescription": "Descrizione",
"submitText": "CONFERMA"
},
"elixForms": {
"inputs": {
"codiceFiscale": "COL0009"
},
"outputs": {
"campo1": "COL0001",
"campo2": "COL0002"
}
}
}
```
Caricare nel PHP:
```php
$config = json_decode(file_get_contents(__DIR__ . '/config.json'), true);
$page = new NomePaginaPage($config['component']);
echo $page->render();
```
## Step 4.1: Pagine Interattive e Chiamate Asincrone (AJAX)
Se la pagina richiede autocompletamento o interattività (es. ricerca contratti):
### 1. Proxy Script PHP Locale
Per non esporre credenziali sensibili (API Key, Basic Auth) al client, creare sempre uno script proxy locale (es. `search-contratti.php`) che:
- Riceve i dati dal client.
- Esegue la chiamata protetta tramite `HttpClient`.
- Valida con `json_decode` che la risposta sia JSON valido prima di inviarla.
- Risponde con `500` e un JSON d'errore pulito in caso di eccezioni.
### 2. Risoluzione Percorso URL in JavaScript
Il server integrato PHP non esegue il redirect automatico per aggiungere lo slash alla fine delle cartelle. In JavaScript, calcolare sempre la base directory dinamica per supportare URL sia con che senza slash finale o con `index.php` esplicito:
```javascript
var baseDir = window.location.pathname;
if (!baseDir.endsWith('/') && !baseDir.endsWith('.php')) {
baseDir += '/';
}
var lastSlash = baseDir.lastIndexOf('/');
var dir = baseDir.substring(0, lastSlash + 1);
var url = dir + 'search-contratti.php?term=' + encodeURIComponent(term);
```
### 3. Parsing Sicuro degli Errori HTTP (Fetch)
Non eseguire direttamente `response.json()` su risposte d'errore (500/404) o non-JSON per evitare il crash `Unexpected token '<'`. Utilizzare la tecnica del `response.text()`:
```javascript
fetch(url)
.then(function(response) {
return response.text().then(function(text) {
var data = null;
try {
data = JSON.parse(text);
} catch (e) {
// Risposta non JSON (es. HTML d'errore del server)
}
if (!response.ok) {
var errMsg = (data && data.message) ? data.message :
(data && data.error) ? data.error :
('Errore API: ' + response.status + ' ' + response.statusText);
throw new Error(errMsg);
}
if (data === null) {
throw new Error('Risposta del server non in formato JSON.');
}
return data;
});
})
```
## Step 5: Testing Locale
Per testare le pagine localmente, è necessario avviare il server web dalla cartella radice `php/` (in modo che la cartella `common/` sia accessibile correttamente):
### Opzione 1: PHP CLI Server locale
```powershell
cd php
php -S localhost:8080
```
Aprire nel browser:
```
http://localhost:8080/<nome-pagina>/?RWE2_MODULE_ID=123&RWE2_REQUEST_ID=456&COL0002=test&COL0015=3
```
### Opzione 2: Docker
Dalla root del repository, eseguire:
```bash
docker run --rm -p 8080:8080 -v "${PWD}/php:/app" -w /app php:8.2-cli php -S 0.0.0.0:8080
```
Aprire nel browser:
```
http://localhost:8080/<nome-pagina>/?RWE2_MODULE_ID=123&RWE2_REQUEST_ID=456&COL0002=test&COL0015=3
```
## Step 6: Deploy su Apache
1. Caricare la cartella `php/` (o la singola cartella della pagina) sul server Apache
2. Assicurarsi che Apache abbia `mod_php` o `php-fpm` configurato
3. La pagina sarà accessibile via URL diretto (es. `https://server.example.com/php/nome-pagina/`)
4. elixForms reindirizza l'utente alla pagina passando i parametri in query string
## Struttura Finale
```
php/<nome-pagina>/
├── index.php ← Entry point e classe pagina
└── config.json ← Opzionale: configurazione separata
```
## Checklist Nuova Pagina PHP
- [ ] Cartella creata in `php/<nome-pagina>/`
- [ ] `index.php` con `declare(strict_types=1)` e namespace
- [ ] Classe che estende `ElixFormsComponent`
- [ ] `createCustomFormFields()` sovrascritta con i campi corretti
- [ ] Proprietà di configurazione impostate correttamente
- [ ] `require_once __DIR__ . '/../common/ElixFormsComponent.php'`
- [ ] Test locale con `php -S` o Docker
- [ ] Verifica che i campi form producano l'HTML corretto
- [ ] Verifica che i parametri obbligatori vengano letti da `$_GET`
@@ -0,0 +1,124 @@
---
name: elixforms-php-project-architecture
description: >
Architettura e struttura della parte PHP del progetto elixForms Custom Pages.
Descrive la cartella php/, la libreria common condivisa, il pattern delle pagine,
le convenzioni di naming, e il deploy su Apache.
Attiva questa skill quando lavori sulla parte PHP del progetto,
crei nuove pagine PHP, modifichi l'architettura, o devi capire come è organizzato il codice PHP.
---
# Architettura PHP del Progetto elixForms Custom Pages
## Panoramica
La cartella `php/` contiene le custom page elixForms realizzate in **PHP 8.0+ puro** (nessun framework). Ogni pagina è un file PHP autonomo che include la libreria `common/` e produce HTML server-side con **Bootstrap Italia**.
Questa struttura è l'equivalente PHP del monorepo React in `react/`.
## Struttura del Workspace
```
elixforms-custom-pages/
├── react/ ← Custom pages React (esistente)
└── php/ ← Custom pages PHP
├── AGENTS.md ← Regole dell'agente PHP
├── common/ ← Libreria condivisa PHP
│ ├── ElixFormsComponent.php ← Classe base astratta
│ ├── ElixFormsElement.php ← Wrapper layout label+input
│ └── QueryParamHelper.php ← Utility query string
└── scelta-carriera/ ← Esempio di pagina custom
└── index.php ← Entry point (classe + rendering)
```
## Pattern Architetturale: Singolo File PHP per Pagina
Ogni pagina (es. `scelta-carriera/`) è un file PHP autonomo che:
1. **Include la libreria `common/`** tramite `require_once`
2. **Definisce una classe** che estende `ElixFormsComponent`
3. **Sovrascrive `createCustomFormFields()`** per i campi specifici
4. **Istanzia e renderizza** la pagina con `echo $page->render()`
5. **Genera HTML completo** (dalla `<!DOCTYPE html>` al `</html>`)
6. **Non richiede build** — servito direttamente da Apache
## Namespace e Autoloading
Il progetto usa **namespace PSR-4**:
```php
namespace ElixForms\Common; // Per le classi in common/
```
L'autoloading è gestito manualmente con `require_once` (non Composer):
```php
require_once __DIR__ . '/../common/ElixFormsComponent.php';
// ElixFormsComponent.php include internamente:
// - QueryParamHelper.php
// - ElixFormsElement.php
```
## Requisiti PHP
- **Versione minima**: PHP 8.0+
- **Funzionalità usate**:
- `declare(strict_types=1)` — strict typing
- Typed properties (`protected array $properties`)
- `match` expression (nel renderField)
- Named arguments
- Union types (`int|null`, `string|null`)
- Arrow functions (`fn() =>`)
## CSS Strategy
Le pagine PHP usano `<link>` tags per caricare i CSS (non bundle JS):
```html
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Titillium+Web:wght@300;400;600;700&display=swap">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-italia@2.18.2/dist/css/bootstrap-italia.min.css">
<link rel="stylesheet" href="https://console-unipr.elixforms.it/rwe2/css/design-bs/util.css">
<link rel="stylesheet" href="https://console-unipr.elixforms.it/rwe2/css/themes/blu-italia.css">
```
> **Nota**: Stessi CSS del progetto React, ma caricati via `<link>` anziché `@import url()` nel JS bundle.
## Integrazione con elixForms
Identica alla versione React:
1. **Query parameters**: Parametri obbligatori passati nell'URL da elixForms
2. **Form POST**: Il form fa submit a `https://procedure.unipr.it/rwe2/ComeBackToElixAndSave`
3. **Hidden inputs**: I parametri query vengono inseriti come campi hidden nel form
4. **Encoding**: `accept-charset="ISO-8859-1"` per compatibilità con il backend
## Convenzioni di Naming
- **Cartelle pagina**: kebab-case (es. `scelta-carriera`)
- **File entry point**: sempre `index.php`
- **Classi PHP**: PascalCase con suffisso `Page` (es. `SceltaCarrieraPage`)
- **Namespace**: `ElixForms\Common` per la libreria condivisa
- **Configurazione**: opzionale `config.json` nella cartella della pagina
## Differenze con il Progetto React
| Aspetto | React (`react/`) | PHP (`php/`) |
|---|---|---|
| **Linguaggio** | TypeScript/JSX | PHP 8.0+ |
| **Rendering** | Client-side | Server-side |
| **Build tool** | Vite (single bundle JS) | Nessuno (file serviti direttamente) |
| **Dipendenze** | npm (React, Vite, plugin) | Nessuna (PHP built-in) |
| **CSS loading** | `@import url()` + CSS injection plugin | `<link>` tags |
| **State management** | React state (`this.setState`) | Stateless (`$_GET`) |
| **Deploy** | Upload del bundle `.js` | Upload dei file `.php` |
| **Dev server** | `npm run dev` (Vite HMR) | `php -S` / Docker |
## Deploy su Apache
1. Caricare la cartella `php/` sul server Apache
2. Configurazione Apache minima:
- `mod_php` o `php-fpm` abilitato
- `DirectoryIndex index.php` (default Apache)
3. La pagina è accessibile via URL: `https://server/php/nome-pagina/`
4. elixForms reindirizza l'utente alla pagina passando i parametri via query string