Compare commits

21 Commits

Author SHA1 Message Date
inext 7d7d384a0d fix 1.2.8 - logo app 2026-06-08 20:10:33 +02:00
inext 8194b8c034 fix 1.2.7 2026-06-08 20:03:14 +02:00
inext 90093a086b fix 1.2.6 2026-06-08 12:09:45 +02:00
inext cbe3470c8c 1.2.5 - fix for email settings 2026-06-08 09:47:24 +02:00
inext 469e8c014f 9Merge branch 'dev'
fix piccoli
2026-06-07 23:06:37 +02:00
inext 4dd4da7f4b fix 1.2.4 2026-06-07 23:05:48 +02:00
inext cd10d100ec final 1.2.3 - file sh per generare zip 2026-06-07 20:56:38 +02:00
inext 406985eb1e Merge branch 'dev'
final 1.2.2
2026-06-07 17:27:25 +02:00
inext f9c1268466 finale 1.2.2 2026-06-07 17:27:00 +02:00
inext 89382fe4ff latest fix 2026-06-07 16:28:13 +02:00
inext dd76b473f7 Merge branch 'dev'
1.2.1 fix gitignore
2026-06-06 23:44:56 +02:00
inext cf0ca170ce 1.2.1 fix gitignore 2026-06-06 23:41:16 +02:00
inext 82c54002e4 Merge branch 'dev'
versione finale 1.2
2026-06-06 23:20:49 +02:00
inext bc57d0dcd0 final 1.2 2026-06-06 23:19:55 +02:00
inext 139098bffc fix 1.1.1 2026-06-06 23:16:51 +02:00
inext a8c841bdbe - import gruppi ed eventi + docker 2026-06-06 23:00:07 +02:00
inext 00004d8b50 sistemazione installa e tabelle - ver 1.1 2026-06-06 20:43:56 +02:00
inext 2fd1ed8295 commit finale 662026 2026-06-06 18:13:11 +02:00
inext 8aa1d3d031 reset password user 2026-06-06 11:50:22 +02:00
inext d57431b10b fix -last 2026-06-05 12:23:13 +02:00
inext 05f1a4c396 git ignore image tart 2026-06-04 08:49:42 +02:00
2479 changed files with 1322 additions and 330289 deletions
+3
View File
@@ -0,0 +1,3 @@
glastree*.tar.gz
glastree*.tar
.env
+152 -51
View File
@@ -1,61 +1,162 @@
# 🧠 MEMORY.md — Stato Progetto # 🧠 MEMORY.md — Stato Progetto
## Goal ## Obiettivo
- Risolvere login non funzionante dopo fresh install (causa: tinker senza escaping + `2>/dev/null`) App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.
- Sostituire tinker per creazione admin con PDO prepared statement + SQL file per schema
## Constraints & Preferences ## Funzionalità Implementate
- Laravel 13, PHP 8.4, AdminLTE 4, MySQL/MariaDB (install.sql solo MySQL)
- Zero placeholder, zero TODO, codice funzionante
- Compatibilità cross-DB MySQL ↔ SQLite (fallback migration per SQLite)
## Progress ### Page-length selector
### Done - **4 pagine**: Individui, Eventi, Email, Gruppi
- **Creazione `database/install.sql`** — schema MySQL completo + seed data (~1059 righe): - **Valori**: 10, 20, 25, 50, 100 (default 20)
- Tutte le 45 tabelle nello stato finale (consolidate da 58 migration) - Dropdown nel card-footer con campi hidden per preservare query params
- Seed: 119 diocesi, 54 comuni, 8 tipologie documenti, 8 tipologie eventi, 8 ruoli, 23 permessi Spatie, 2 ruoli Spatie, 3 role preset, 5 email folders, 1 riga app_settings - GruppoController: usa `LengthAwarePaginator` per paginare la collezione ordinata gerarchicamente
- **NON contiene l'admin** (creato via PDO da install.php)
- `SET FOREIGN_KEY_CHECKS=0` per ordine tabelle, riattivato alla fine
- **Riscritto `install.php`** — eliminata dipendenza da tinker per la creazione admin:
- Aggiunta funzione `createAdminUser()`: PDO prepared statement + `password_hash()` + Spatie role assignment
- Fresh Install Apache: importa `install.sql` via `mysql` CLI, poi crea admin via PDO
- Fresh Install Docker: copia `install.sql` nel container, importa via mysql CLI, crea admin via script PHP temporaneo con `var_export()` (safe)
- Restore: crea admin via PDO se non esiste
- Fallback SQLite: mantiene `php artisan migrate --seed` + tinker ma con `var_export()` per escaping
- Step 5 rinominato da "MIGRATION E SEED" a "SCHEMA DATABASE E DATI BASE"
- Step 6 usa PDO invece di tinker (MySQL)
- **Unified MySQL setup**: singolo tentativo PDO con `dbname` nel DSN — se fallisce (1044/1045/1049), passa automaticamente in root-mode: chiede password root, crea database + utente + GRANT ALL su `localhost` e `%`
- **Sostituito loop 30-retry** con connessione singola PDO (timeout 5s)
- **Fix parse error**: `\$adminName``$adminName` in `var_export()` nel ramo SQLite
- **Fix composer**: rimosso `sudo -u www-data` da composer (causa git dubious ownership + permission denied), aggiunto `COMPOSER_ROOT_VERSION=dev-main`; `vendor` aggiunto al chown finale
### In Progress ### CSV Import Gruppi
- *(nessuno)* - Stesso pattern di `IndividuoController` (import GET, importStore POST, downloadTemplate GET)
- Colonne: nome, descrizione, parent_id, diocesi_id, indirizzo_incontro, cap_incontro, città_incontro, sigla_provincia_incontro
- Fault-tolerant: skip righe vuote, log errori, try/catch su ogni riga
### Blocked ### ICS Import Eventi
- *(nessuno)* - Usa `Sabre\VObject\Reader::read()` già in vendor/
- Mapping: SUMMARY→nome_evento, DESCRIPTION→descrizione, DTSTART→data_specifica+ora_inizio, DTEND→durata_minuti, LOCATION→luogo_indirizzo, UID→uid_esterno
- Dedup via uid_esterno
- RRULE: import come evento singolo con prima occorrenza
## Key Decisions ### build-dist.sh (script di distribuzione)
- **PDO > tinker**: `createAdminUser()` usa PDO prepared statement con `password_hash()`, niente interpolazione PHP in stringhe shell → zero escaping issues - Genera `glastree-YYYYMMDD_HHMM.tar.gz`
- **`install.sql` > migration sequenziali**: unico file SQL per fresh install, più veloce e debuggabile (si può lanciare `mysql < install.sql` a mano) - Include: sorgenti, vendor (production)
- **`var_export()` per tinker fallback**: nei rari casi SQLite, si usa `var_export()` che produce stringhe PHP valide con escaping automatico - Esclude: .git, node_modules, tests, cache, storage content, vendor/docs/tests, Docker
- **`2>/dev/null` rimosso** dal MySQL main path (non serve più, non c'è tinker) - **Istruzioni post-estrazione complete**: mkdir, permessi, configurazione, cache clear
- **Docker MySQL credenziali**: hardcoded `mysql:glastree:secret:glastree` (dal docker-compose.mysql.yml)
- **Composer senza sudo**: `COMPOSER_ROOT_VERSION=dev-main` per evitare git dubious ownership; composer eseguito come utente corrente (root), non più come www-data — dopo, `chown -R www-data vendor` per permessi lettura webserver
- **Unified MySQL setup**: PDO connect con `dbname` nel DSN → se fallisce 1044/1045/1049, root-mode: crea DB + utente + GRANT ALL su `localhost` e `%` + FLUSH
## Next Steps ## Bug Fix Recenti
- *(nessuno)*
## Critical Context ### 2026-06-07 — Colonne mancanti in eventi e gruppo_individuo
- `install.sql` è solo per MySQL. SQLite usa `php artisan migrate --seed` + `DatabaseSeeder` - `descrizione_evento` e `is_incontro_gruppo` mancanti in install.sql → creata migration + ALTER TABLE
- `DatabaseSeeder` crea ancora admin `admin@glastree.local / password` ma per MySQL viene cancellato dal PDO script - `ruolo_nel_gruppo` mancante in install.sql per pivot `gruppo_individuo`
- `createAdminUser()` setta `is_admin=1`, `status='active'`, `permissions` completo, `role_preset_id=1`, e Spatie `model_has_roles` con role_id=1 - `VistaReport.php`: aggiunto `is_default` a `$fillable`
- `password_hash($pass, PASSWORD_BCRYPT)` produce hash `$2y$...` compatibile con `Hash::check()` di Laravel
- Composer ora eseguito come utente corrente (root) con `COMPOSER_ROOT_VERSION=dev-main` per evitare git dubious ownership e permission denied su `vendor/`
- `vendor/` aggiunto al `chown` finale in entrambi i modi (Apache e Restore) per garantire leggibilità da www-data
- `ensureMysqlUserAndDb()` sanitizza input (rimuove `' " \0 \`) prima di passarli a SQL exec
## Relevant Files ### 2026-06-08 — build-dist.sh: "provide valid cache path" su server remoto
- `database/install.sql`: NEW — schema MySQL completo + seed data (1059 righe) **Problema**: Le esclusioni in `build-dist.sh` usavano `--exclude='storage/framework/cache'` (e simili per sessions, views, logs), che escludevano **l'intera directory** dall'archivio. Dopo l'estrazione, le directory necessarie a Laravel non esistevano.
- `install.php`: MODIFIED — `createAdminUser()` + `ensureMysqlUserAndDb()` + composer fix + auto-create MySQL user
**Fix**:
1. Cambiati exclude patterns per **contenuti soltanto** (non la directory):
- `--exclude='storage/framework/cache/*'` (non `/cache`)
- `--exclude='storage/framework/sessions/*'`
- `--exclude='storage/framework/views/*'`
- `--exclude='storage/logs/*'`
- `--exclude='storage/debugbar/*'`
2. Aggiunte esclusioni per upload utente:
- `--exclude='storage/app/public'`
- `--exclude='storage/app/private'`
3. Creato `.gitignore` in `storage/framework/views/` (mancante)
4. Istruzioni post-estrazione riscritte con:
- `mkdir -p` per tutte le directory necessarie (cache, sessions, views, logs, public, backups, documenti, bootstrap/cache)
- `chmod -R 775` e `chown -R www-data:www-data` per permessi
- `php artisan config:clear`, `route:clear`, `view:clear`
## Problema 419 (Page Expired) su Produzione
Il codice NON ha errori di CSRF — tutte le form hanno `@csrf`. Il problema è configurazione sessione del server:
1. `storage/framework/sessions/` non scrivibile dal web server
2. `APP_URL` in `.env` non corrisponde all'URL reale
3. `SESSION_SECURE_COOKIE=true` ma sito in HTTP
Soluzione:
```bash
chmod -R 775 storage/framework/sessions
php artisan config:clear
```
### 2026-06-08 — build-dist.sh: impossibilità salvare logo su server remoto
**Problema**:
- `public/storage` era un symlink **assoluto** (`/var/www/html/glastree/storage/app/public`) incluso nell'archivio → all'estrazione su altro server puntava a path inesistente.
- `php artisan storage:link` falliva perché il symlink rotto esisteva già.
- Sottodirectory `logos/`, `documenti/eventi/`, `avatars/gruppi/` non create dal `mkdir -p`.
**Fix**:
1. Aggiunto `--exclude='public/storage'` al tar — il symlink va ricreato sul target.
2. Aggiunto `rm -f public/storage` prima di `php artisan storage:link` per evitare conflitto.
3. Usato `ln -sf ../storage/app/public public/storage` (invece di `php artisan storage:link --relative` che richiede `symfony/filesystem` non sempre presente).
4. Aggiunte al `mkdir -p` le sottodirectory mancanti:
- `storage/app/public/logos` (logo upload)
- `storage/app/public/documenti/eventi` (event documenti)
- `storage/app/private/avatars/gruppi` (avatar gruppi)
5. Aggiunta creazione `.gitignore` in `storage/app/public/` (placeholder Laravel).
### Fix form annidato salvataggio email
**2026-06-08 — Problema**: Il form di DELETE era **annidato dentro** il form di SAVE in entrambe le pagine:
- `resources/views/impostazioni/index.blade.php`
- `resources/views/admin/email-settings/index.blade.php`
- HTML invalido: il browser chiudeva implicitamente il form SAVE all'apertura del form DELETE, causando l'invio alla route sbagliata.
**Fix**: Spostato il form DELETE fuori dal form SAVE, separato in un `div` autonomo.
### Fix campi mancanti in validazione EmailSettingsController@save
**2026-06-08**: `signature`, `signature_enabled`, `smtp_username`, `smtp_password` non erano nelle regole di validazione di `save()`. Laravel scarta i campi non validati, quindi non venivano mai scritti nel DB.
**Fix**: Aggiunte le regole mancanti a `$request->validate()`.
### DiagnoseEmail Command
- `php artisan diagnose:email` — controlla APP_KEY, tabella email_settings, record, decrittazione password, sender_accounts, log errori, estensioni PHP.
### 2026-06-08 — Firma email ancora non salvata (secondo tentativo)
**Problema**: La firma (campo `signature`) non veniva salvata nonostante le regole di validazione fossero state aggiunte.
**Root cause analysis**:
1. HTML checkbox `signature_enabled` senza hidden fallback: quando non spuntato, il campo non veniva inviato → non presente in `$validated``updateOrCreate` non lo aggiornava mai a `false`.
2. La vecchia pagina `/impostazioni/` aveva l'handler `shown.bs.tab` per il tab Firma (`#email-firma`) in un solo punto su due, causando possibile mancata inizializzazione dell'editor Quill su alcuni flussi di navigazione.
3. La verifica nel controller controllava solo `imap_host` e `email_address` — anche se signature/signature_enabled fallivano, l'utente vedeva "salvato con successo".
**Fix**:
1. `resources/views/admin/email-settings/index.blade.php:268` — Aggiunto `<input type="hidden" name="signature_enabled" value="0">` prima della checkbox
2. `resources/views/impostazioni/index.blade.php:603` — Stesso hidden fallback
3. `resources/views/impostazioni/index.blade.php:1633` — Aggiunto `#email-firma` al primo handler `shown.bs.tab` (il secondo handler esisteva già ma il primo no)
4. `app/Http/Controllers/Admin/EmailSettingsController.php:70-82` — Aggiunto log di warning se signature o signature_enabled non corrispondono dopo il salvataggio
### 2026-06-08 — Sync email: "Nessuna configurazione email attiva" nonostante test IMAP funzioni
**Problema**: Cliccando "Sincronizza Ora" nelle impostazioni email o "Ricevi/Invia" nella pagina email, viene mostrato "Nessuna configurazione email attiva". Il test IMAP funziona correttamente.
**Root cause**: Stesso bug dei checkbox senza hidden fallback. Il campo `is_active` nel tab Sincronizzazione non veniva inviato quando non spuntato. Alla prima configurazione, se l'utente non visitava esplicitamente il tab Sync e attivava lo switch, `is_active` rimaneva `false` (default DB). Il test IMAP usa `EmailSetting::first()` (ignora `is_active`), mentre `syncEmails()` e `syncNow()` usano `EmailSetting::getActive()` che filtra per `is_active = true`.
**Fix**:
1. `resources/views/admin/email-settings/index.blade.php:251` — Aggiunto `<input type="hidden" name="is_active" value="0">` prima della checkbox
2. `resources/views/impostazioni/index.blade.php:586` — Stesso hidden fallback
3. `EmailSetting::getActive()` ora riceve sempre `is_active` dal form (0 o 1) grazie all'hidden fallback
## 2026-06-08 — Help page: aggiunto tab "Calendario" con documentazione OAuth
- **`resources/views/help/index.blade.php`**: Aggiunto tab "Calendario" nel menu laterale (dopo Google Drive) e relativo tab-pane content con:
- Requisiti (account Google, API Calendar, OAuth credentials)
- Passo 1: Google Cloud Console — abilitare Google Calendar API, creare OAuth Client ID (Web Application), redirect URI `https://developers.google.com/oauthplayground`
- Passo 2: Ottenere Refresh Token via Google OAuth Playground (Use your own OAuth credentials, scope `calendar`)
- Passo 3: Configurazione nell'app (Impostazioni → Calendario, nuova connessione Google Calendar, inserire Client ID/Secret/Refresh Token)
- Test connessione e sincronizzazione
- Nota: refresh token permanente finché non revocato
- **`resources/views/help/pdf.blade.php`**: Aggiunta sezione "3. Google Calendar" nel TOC e contenuto, rinumerazione da 4 a 8 per sezioni successive
### Differenza chiave Google Calendar vs Google Drive
- **Drive**: endpoint server `/auth/google-drive/callback` che gestisce automaticamente il callback OAuth e salva refresh token in `storage_repositories`
- **Calendar**: **NON** ha endpoint server dedicato. Refresh token ottenuto manualmente via Google OAuth Playground e incollato nel campo "Refresh Token" del form
## 2026-06-08 — Pulsante Sync nella pagina calendario eventi
- **`app/Http/Controllers/CalendarioConnessioneController.php`**: Aggiunto metodo `syncAll()` che itera tutte le connessioni attive (`is_active = true`), esegue `syncEvents()` per ognuna, e restituisce JSON con risultati aggregati.
- **`routes/web.php`**: Aggiunta route `POST /calendario-connessioni/sync-all``syncAll` (name: `calendario-connessioni.sync-all`).
- **`resources/views/eventi/calendar.blade.php`**: Aggiunto pulsante "Sync" nel card-tools che:
- Invia POST AJAX a `sync-all`
- Mostra spinner durante l'operazione
- Al completamento mostra alert con riepilogo (connessioni, importati, esportati)
- Richiama `window.calendar.refetchEvents()` per aggiornare la vista FullCalendar
- Esposto `window.calendar` globalmente per accesso dall'handler sync
## 2026-06-08 — Fix logo non visibile su installazione remota
**Problema**: Il logo uploadato non veniva visualizzato su installazioni remote, pur salvandosi correttamente su disco.
**Root cause**: `getLogoPath()` e `getLogoSmallPath()` in `AppSetting.php` usavano `file_exists(public_path('storage/' . $path))` per verificare l'esistenza del file prima di restituire l'URL. Questo controllo dipendeva dal symlink `public/storage → storage/app/public`. Su installazioni remote dove il symlink era assente o mal configurato, `file_exists` restituiva `false` → metodo restituiva `null` → logo non mostrato.
**Fix**:
1. **`app/Models/AppSetting.php`**: Sostituito `file_exists(public_path('storage/' . $path))` con `Storage::disk('public')->exists($path)` e `asset('storage/' . $path)` con `Storage::disk('public')->url($path)`. Il controllo ora avviene direttamente su `storage/app/public/` (disk root), bypassando il symlink.
2. **`app/Http/Controllers/ImpostazioniController.php`**: In `uploadLogo()`, aggiunta creazione automatica del symlink `public/storage → storage/app/public` se mancante, per garantire che il browser possa servire l'immagine.
## Prossimi Passi
- *(nessuno — in attesa di nuove richieste)*
+167
View File
@@ -0,0 +1,167 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\EmailSetting;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
class DiagnoseEmail extends Command
{
protected $signature = 'diagnose:email';
protected $description = 'Diagnostica configurazione email e credenziali';
public function handle(): int
{
$this->info('=== Diagnostica Email Settings ===');
$this->newLine();
// 1. APP_KEY
$this->section('APP_KEY');
$appKey = config('app.key');
if ($appKey && str_starts_with($appKey, 'base64:')) {
$this->line('APP_KEY: presente e valida ✓');
} elseif ($appKey) {
$this->warn('APP_KEY: presente ma non in formato base64 (potrebbe funzionare lo stesso)');
} else {
$this->error('APP_KEY: NON IMPOSTATA — crittografia password non funzionerà!');
}
// 2. Tabella email_settings
$this->newLine();
$this->section('Tabella email_settings');
if (Schema::hasTable('email_settings')) {
$this->line('Tabella email_settings: esiste ✓');
$columns = Schema::getColumnListing('email_settings');
$this->line('Colonne: ' . implode(', ', $columns));
$required = ['imap_host', 'imap_username', 'imap_password', 'email_address'];
$missing = array_diff($required, $columns);
if ($missing) {
$this->error('Colonne mancanti: ' . implode(', ', $missing));
} else {
$this->line('Colonne richieste presenti ✓');
}
} else {
$this->error('Tabella email_settings NON ESISTE! Esegui: php artisan migrate');
}
// 3. Record email_settings
$this->newLine();
$this->section('Record email_settings');
$settings = EmailSetting::first();
if ($settings) {
$this->line('Record trovato (ID: ' . $settings->id . ')');
$this->line(' imap_host: ' . ($settings->imap_host ?: 'VUOTO'));
$this->line(' imap_username: ' . ($settings->imap_username ?: 'VUOTO'));
$this->line(' imap_password: ' . ($settings->imap_password ? '[CRITTATO]' : 'VUOTO'));
$this->line(' email_address: ' . ($settings->email_address ?: 'VUOTO'));
$this->line(' smtp_host: ' . ($settings->smtp_host ?: 'VUOTO'));
// Prova decrittazione
if ($settings->imap_password) {
try {
$decrypted = Crypt::decryptString($settings->imap_password);
$this->line(' -> password IMAP decrittata: ' . (strlen($decrypted) > 0 ? 'OK (' . strlen($decrypted) . ' caratteri)' : 'VUOTA'));
} catch (\Exception $e) {
$this->error(' -> ERRORE decrittazione password IMAP: ' . $e->getMessage());
$this->warn(' Causa probabile: APP_KEY diversa da quando è stata salvata.');
}
}
} else {
$this->warn('NESSUN record in email_settings — nessuna configurazione salvata.');
$this->line('Vai su /impostazioni#email-imap, compila e clicca "Salva Impostazioni Email"');
}
// 4. Tabella sender_accounts
$this->newLine();
$this->section('Tabella sender_accounts');
if (Schema::hasTable('sender_accounts')) {
$this->line('Tabella sender_accounts: esiste ✓');
$count = DB::table('sender_accounts')->count();
$this->line('Record presenti: ' . $count);
} else {
$this->error('Tabella sender_accounts NON ESISTE!');
}
// 5. Log recenti
$this->newLine();
$this->section('Errori recenti (storage/logs/laravel.log)');
$logPath = storage_path('logs/laravel.log');
if (file_exists($logPath)) {
$lines = $this->getLastErrorLines($logPath, 20);
if ($lines) {
foreach ($lines as $line) {
$this->line($line);
}
} else {
$this->line('Nessun errore recente trovato.');
}
} else {
$this->warn('File di log non trovato: ' . $logPath);
}
// 6. Estensioni PHP
$this->newLine();
$this->section('Estensioni PHP');
$requiredExts = ['openssl', 'json', 'mbstring', 'pdo', 'fileinfo'];
foreach ($requiredExts as $ext) {
if (extension_loaded($ext)) {
$this->line($ext . ': OK ✓');
} else {
$this->error($ext . ': MANCANTE!');
}
}
$this->newLine();
$this->info('=== Diagnostica completata ===');
return self::SUCCESS;
}
private function section(string $title): void
{
$this->line("--- {$title} ---");
}
private function getLastErrorLines(string $path, int $maxLines): array
{
$lines = [];
$fp = fopen($path, 'r');
if (!$fp) {
return $lines;
}
fseek($fp, -1, SEEK_END);
$pos = ftell($fp);
$buffer = '';
while ($pos > 0 && count($lines) < $maxLines) {
$char = fgetc($fp);
if ($char === "\n") {
if (trim($buffer) !== '') {
$lines[] = trim($buffer);
}
$buffer = '';
} else {
$buffer = $char . $buffer;
}
$pos--;
fseek($fp, $pos);
}
if (trim($buffer) !== '') {
$lines[] = trim($buffer);
}
fclose($fp);
return $lines;
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class ResetUserPassword extends Command
{
protected $signature = 'user:reset-password
{email? : Email dell\'utente}
{--password= : Nuova password (se omessa, richiesta interattiva)}';
protected $description = 'Cambia la password di un utente tramite email';
public function handle(): int
{
$email = $this->argument('email');
if (empty($email)) {
$email = $this->ask('Email dell\'utente');
}
$user = User::where('email', $email)->first();
if ($user === null) {
$this->error("Nessun utente trovato con email: {$email}");
return self::FAILURE;
}
$this->line("Utente trovato: {$user->name} ({$user->email})");
$password = $this->option('password');
if (empty($password)) {
$password = $this->secret('Nuova password');
$confirm = $this->secret('Conferma nuova password');
if ($password !== $confirm) {
$this->error('Le password non corrispondono');
return self::FAILURE;
}
}
$validator = Validator::make(
['password' => $password],
['password' => ['required', 'string', 'min:12']]
);
if ($validator->fails()) {
$this->error('Password troppo corta: minimo 12 caratteri');
return self::FAILURE;
}
$user->password = Hash::make($password);
$user->save();
if ($user->wasChanged('password')) {
$this->info("Password aggiornata con successo per {$user->email}");
return self::SUCCESS;
}
$this->error('Errore durante l\'aggiornamento della password');
return self::FAILURE;
}
}
@@ -34,11 +34,15 @@ class EmailSettingsController extends Controller
'smtp_host' => 'nullable|string|max:255', 'smtp_host' => 'nullable|string|max:255',
'smtp_port' => 'nullable|integer|min:1|max:65535', 'smtp_port' => 'nullable|integer|min:1|max:65535',
'smtp_encryption' => 'nullable|in:ssl,tls,none', 'smtp_encryption' => 'nullable|in:ssl,tls,none',
'smtp_username' => 'nullable|string|max:255',
'smtp_password' => 'nullable|string',
'email_address' => 'required|email', 'email_address' => 'required|email',
'email_name' => 'nullable|string|max:255', 'email_name' => 'nullable|string|max:255',
'reply_to' => 'nullable|email', 'reply_to' => 'nullable|email',
'sync_interval_minutes' => 'nullable|integer|min:1|max:60', 'sync_interval_minutes' => 'nullable|integer|min:1|max:60',
'is_active' => 'boolean', 'is_active' => 'boolean',
'signature' => 'nullable|string',
'signature_enabled' => 'boolean',
]); ]);
if (!empty($validated['imap_password'])) { if (!empty($validated['imap_password'])) {
@@ -62,6 +66,24 @@ class EmailSettingsController extends Controller
if ($verify && if ($verify &&
$verify->imap_host === $validated['imap_host'] && $verify->imap_host === $validated['imap_host'] &&
$verify->email_address === $validated['email_address']) { $verify->email_address === $validated['email_address']) {
$signatureMatch = !array_key_exists('signature', $validated) || $verify->signature === $validated['signature'];
$signatureEnabledMatch = !array_key_exists('signature_enabled', $validated) || (bool)$verify->signature_enabled === (bool)$validated['signature_enabled'];
if (!$signatureMatch) {
\Illuminate\Support\Facades\Log::warning('Signature mismatch after save', [
'expected' => $validated['signature'] ?? null,
'actual' => $verify->signature,
]);
}
if (!$signatureEnabledMatch) {
\Illuminate\Support\Facades\Log::warning('Signature enabled mismatch after save', [
'expected' => $validated['signature_enabled'] ?? null,
'actual' => $verify->signature_enabled,
]);
}
return back()->with('success', 'Impostazioni email salvate e verificate nel database.'); return back()->with('success', 'Impostazioni email salvate e verificate nel database.');
} else { } else {
return back()->with('error', 'Errore nel salvataggio: i dati non sono stati salvati correttamente.'); return back()->with('error', 'Errore nel salvataggio: i dati non sono stati salvati correttamente.');
@@ -126,6 +126,52 @@ class CalendarioConnessioneController extends Controller
return redirect('/impostazioni#calendario')->with('error', 'Errore sync: ' . $errorMsg); return redirect('/impostazioni#calendario')->with('error', 'Errore sync: ' . $errorMsg);
} }
public function syncAll(): JsonResponse
{
$this->authorizeWrite('settings');
$connections = CalendarioConnessione::where('is_active', true)->get();
$results = [];
foreach ($connections as $connessione) {
$result = match ($connessione->tipo) {
'caldav' => app(CalDavService::class)->syncEvents($connessione),
'google_calendar' => app(GoogleCalendarSyncService::class)->syncEvents($connessione),
default => ['success' => false, 'imported' => 0, 'exported' => 0, 'errors' => ['Tipo sconosciuto']],
};
$connessione->update(['last_sync_at' => now()]);
if ($result['success']) {
$connessione->update(['stato' => 'connesso']);
} else {
$errorMsg = implode('; ', $result['errors']);
$connessione->update(['stato' => 'errore', 'last_error_at' => now(), 'last_error_message' => $errorMsg]);
}
$results[] = [
'id' => $connessione->id,
'nome' => $connessione->nome,
'success' => $result['success'],
'imported' => $result['imported'],
'exported' => $result['exported'],
'errors' => $result['errors'],
];
}
$totalImported = array_sum(array_column($results, 'imported'));
$totalExported = array_sum(array_column($results, 'exported'));
$hasErrors = in_array(false, array_column($results, 'success'), true);
return response()->json([
'success' => !$hasErrors,
'results' => $results,
'total_imported' => $totalImported,
'total_exported' => $totalExported,
'connections' => count($results),
]);
}
public function reorder(Request $request): JsonResponse public function reorder(Request $request): JsonResponse
{ {
$this->authorizeWrite('settings'); $this->authorizeWrite('settings');
+2 -1
View File
@@ -59,7 +59,8 @@ class EmailController extends Controller
$query->orderBy($sortField === 'sent_at' ? 'received_at' : $sortField, $sortDir); $query->orderBy($sortField === 'sent_at' ? 'received_at' : $sortField, $sortDir);
$messages = $query->paginate(20)->withQueryString(); $perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
$messages = $query->paginate($perPage)->withQueryString();
$folders = EmailFolder::orderBy('sort_order')->get(); $folders = EmailFolder::orderBy('sort_order')->get();
$mailingLists = MailingList::where('attiva', true)->get(); $mailingLists = MailingList::where('attiva', true)->get();
$gruppi = Gruppo::orderBy('nome')->get(); $gruppi = Gruppo::orderBy('nome')->get();
+100 -1
View File
@@ -8,6 +8,7 @@ use App\Models\Individuo;
use App\Models\TipologiaEvento; use App\Models\TipologiaEvento;
use App\Services\IcsExportService; use App\Services\IcsExportService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
class EventoController extends Controller class EventoController extends Controller
@@ -43,7 +44,8 @@ class EventoController extends Controller
$query->orderByDesc('created_at'); $query->orderByDesc('created_at');
} }
$eventi = $query->paginate(20); $perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
$eventi = $query->paginate($perPage);
$gruppi = Gruppo::orderBy('nome')->get(); $gruppi = Gruppo::orderBy('nome')->get();
return view('eventi.index', compact('eventi', 'gruppi')); return view('eventi.index', compact('eventi', 'gruppi'));
@@ -477,4 +479,101 @@ class EventoController extends Controller
return $colors['primary']; return $colors['primary'];
} }
public function importIcs()
{
$this->authorizeWrite('eventi');
return view('eventi.import');
}
public function importIcsStore(Request $request)
{
$this->authorizeWrite('eventi');
$request->validate([
'file' => 'required|file|mimes:ics,text/calendar',
]);
$file = $request->file('file');
$content = file_get_contents($file->path());
try {
$vcalendar = \Sabre\VObject\Reader::read($content);
} catch (\Exception $e) {
Log::warning('Errore lettura file ICS: ' . $e->getMessage());
return back()->with('error', 'Il file ICS non è valido: ' . $e->getMessage());
}
$imported = 0;
$skipped = 0;
$errors = [];
foreach ($vcalendar->VEVENT as $vevent) {
try {
$uid = (string) ($vevent->UID ?? '');
if (!empty($uid)) {
$existing = Evento::where('uid_esterno', $uid)->first();
if ($existing) {
$skipped++;
continue;
}
}
$nomeEvento = (string) ($vevent->SUMMARY ?? '');
if (empty($nomeEvento)) {
$skipped++;
continue;
}
$dtStart = $vevent->DTSTART ?? null;
$dtEnd = $vevent->DTEND ?? null;
$dataSpecifica = null;
$oraInizio = null;
$durataMinuti = null;
if ($dtStart) {
$dtStartDateTime = new \DateTime((string) $dtStart);
$dataSpecifica = $dtStartDateTime->format('Y-m-d');
$oraInizio = $dtStartDateTime->format('H:i');
if ($dtEnd) {
$dtEndDateTime = new \DateTime((string) $dtEnd);
$durataMinuti = (int) ($dtEndDateTime->getTimestamp() - $dtStartDateTime->getTimestamp()) / 60;
if ($durataMinuti < 1) {
$durataMinuti = null;
}
}
}
$descrizione = (string) ($vevent->DESCRIPTION ?? '');
$luogo = (string) ($vevent->LOCATION ?? '');
Evento::create([
'nome_evento' => $nomeEvento,
'descrizione' => !empty($descrizione) ? $descrizione : null,
'tipo_recorrenza' => 'singolo',
'data_specifica' => $dataSpecifica,
'ora_inizio' => $oraInizio,
'durata_minuti' => $durataMinuti,
'luogo_indirizzo' => !empty($luogo) ? $luogo : null,
'uid_esterno' => !empty($uid) ? $uid : null,
]);
$imported++;
} catch (\Exception $e) {
Log::warning('Errore import evento ICS: ' . $e->getMessage());
$errors[] = 'Errore: ' . $e->getMessage();
}
}
$message = 'Importati ' . $imported . ' eventi.';
if ($skipped > 0) {
$message .= ' Saltati ' . $skipped . ' eventi (già presenti o senza nome).';
}
if (count($errors) > 0) {
return back()->with('error', $message . ' Errori: ' . implode('; ', $errors));
}
return redirect('/eventi')->with('success', $message);
}
} }
+111 -1
View File
@@ -8,6 +8,9 @@ use App\Models\Gruppo;
use App\Models\Individuo; use App\Models\Individuo;
use App\Models\Diocesi; use App\Models\Diocesi;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Log;
class GruppoController extends Controller class GruppoController extends Controller
{ {
@@ -16,6 +19,7 @@ class GruppoController extends Controller
$this->authorizeRead('gruppi'); $this->authorizeRead('gruppi');
$viewMode = request('view', 'table'); $viewMode = request('view', 'table');
$perPage = in_array((int) request('perPage'), [10, 20, 25, 50, 100]) ? (int) request('perPage') : 20;
$user = auth()->user(); $user = auth()->user();
$vista = \App\Models\VistaReport::where('user_id', $user->id) $vista = \App\Models\VistaReport::where('user_id', $user->id)
@@ -47,7 +51,16 @@ class GruppoController extends Controller
}); });
if ($viewMode === 'table') { if ($viewMode === 'table') {
$gruppi = $this->sortGruppiHierarchically($allGruppi); $sorted = $this->sortGruppiHierarchically($allGruppi);
$page = Paginator::resolveCurrentPage();
$gruppi = new LengthAwarePaginator(
$sorted->forPage($page, $perPage)->values(),
$sorted->count(),
$perPage,
$page,
['path' => Paginator::resolveCurrentPath()]
);
$gruppi->appends(request()->except('page'));
} else { } else {
$gruppi = $allGruppi; $gruppi = $allGruppi;
} }
@@ -300,4 +313,101 @@ class GruppoController extends Controller
return redirect()->route('gruppi.index')->with('success', 'Vista eliminata.'); return redirect()->route('gruppi.index')->with('success', 'Vista eliminata.');
} }
public function import()
{
$this->authorizeWrite('gruppi');
return view('gruppi.import');
}
public function importStore(Request $request)
{
$this->authorizeWrite('gruppi');
$request->validate([
'file' => 'required|file|mimes:csv,txt',
]);
$file = $request->file('file');
$handle = fopen($file->path(), 'r');
$header = fgetcsv($handle, 1000, ',');
if ($header === false) {
fclose($handle);
return back()->with('error', 'Il file CSV non è valido.');
}
$header = array_map('trim', $header);
$imported = 0;
$skipped = 0;
$errors = [];
$rowNumber = 1;
while (($row = fgetcsv($handle, 1000, ',')) !== false) {
$rowNumber++;
if (count($row) < count($header)) {
$skipped++;
continue;
}
$data = array_combine($header, $row);
if ($data === false) {
$skipped++;
continue;
}
$data = array_map('trim', $data);
$nome = $data['nome'] ?? '';
if (empty($nome)) {
$skipped++;
continue;
}
try {
Gruppo::create([
'nome' => $nome,
'descrizione' => $data['descrizione'] ?? null,
'parent_id' => !empty($data['parent_id']) ? (int) $data['parent_id'] : null,
'diocesi_id' => !empty($data['diocesi_id']) ? (int) $data['diocesi_id'] : null,
'indirizzo_incontro' => $data['indirizzo_incontro'] ?? null,
'cap_incontro' => $data['cap_incontro'] ?? null,
'città_incontro' => $data['città_incontro'] ?? null,
'sigla_provincia_incontro' => $data['sigla_provincia_incontro'] ?? null,
]);
$imported++;
} catch (\Exception $e) {
Log::warning('Errore import gruppo riga ' . $rowNumber . ': ' . $e->getMessage());
$errors[] = 'Errore riga ' . $rowNumber . ': ' . $e->getMessage();
}
}
fclose($handle);
$message = 'Importati ' . $imported . ' gruppi.';
if ($skipped > 0) {
$message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).';
}
if (count($errors) > 0) {
return back()->with('error', $message . ' Errori: ' . implode('; ', $errors));
}
return redirect()->route('gruppi.index')->with('success', $message);
}
public function downloadTemplate()
{
$this->authorizeRead('gruppi');
$headers = ['nome', 'descrizione', 'parent_id', 'diocesi_id', 'indirizzo_incontro', 'cap_incontro', 'città_incontro', 'sigla_provincia_incontro'];
$example = ['Gruppo Giovani', 'Giovani dai 18 ai 30 anni', '', '', 'Via Roma 10', '00100', 'Roma', 'RM'];
$csv = implode(',', $headers) . "\n" . implode(',', $example);
return response($csv, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="template_gruppi.csv"',
]);
}
} }
@@ -398,6 +398,15 @@ class ImpostazioniController extends Controller
$settings->save(); $settings->save();
} }
$linkPath = public_path('storage');
if (!file_exists($linkPath) && !is_link($linkPath)) {
try {
symlink(storage_path('app/public'), $linkPath);
} catch (\Throwable $e) {
Log::warning('Impossibile creare symlink storage: ' . $e->getMessage());
}
}
return redirect('/impostazioni#logo')->with('success', 'Logo caricato con successo.'); return redirect('/impostazioni#logo')->with('success', 'Logo caricato con successo.');
} }
+2 -1
View File
@@ -17,7 +17,8 @@ class IndividuoController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$this->authorizeRead('individui'); $this->authorizeRead('individui');
$individui = Individuo::with('contatti')->orderBy('cognome')->orderBy('nome')->paginate(20); $perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
$individui = Individuo::with('contatti')->orderBy('cognome')->orderBy('nome')->paginate($perPage);
$vista = null; $vista = null;
+14 -12
View File
@@ -5,7 +5,9 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
class AppSetting extends Model class AppSetting extends Model
{ {
@@ -107,38 +109,38 @@ class AppSetting extends Model
public function getLogoUrlInstance(): ?string public function getLogoUrlInstance(): ?string
{ {
if ($this->logo && file_exists(public_path('storage/' . $this->logo))) { if ($this->logo && Storage::disk('public')->exists($this->logo)) {
return asset('storage/' . $this->logo); return Storage::disk('public')->url($this->logo);
} }
return null; return null;
} }
public function getLogoSmallUrlInstance(): ?string public function getLogoSmallUrlInstance(): ?string
{ {
if ($this->logo_small && file_exists(public_path('storage/' . $this->logo_small))) { if ($this->logo_small && Storage::disk('public')->exists($this->logo_small)) {
return asset('storage/' . $this->logo_small); return Storage::disk('public')->url($this->logo_small);
} }
return null; return null;
} }
public function getLogoPath(): ?string public function getLogoPath(): ?string
{ {
if ($this->logo_path && file_exists(public_path('storage/' . $this->logo_path))) { if ($this->logo_path && Storage::disk('public')->exists($this->logo_path)) {
return asset('storage/' . $this->logo_path); return Storage::disk('public')->url($this->logo_path);
} }
if ($this->logo && file_exists(public_path('storage/' . $this->logo))) { if ($this->logo && Storage::disk('public')->exists($this->logo)) {
return asset('storage/' . $this->logo); return Storage::disk('public')->url($this->logo);
} }
return null; return null;
} }
public function getLogoSmallPath(): ?string public function getLogoSmallPath(): ?string
{ {
if ($this->logo_small_path && file_exists(public_path('storage/' . $this->logo_small_path))) { if ($this->logo_small_path && Storage::disk('public')->exists($this->logo_small_path)) {
return asset('storage/' . $this->logo_small_path); return Storage::disk('public')->url($this->logo_small_path);
} }
if ($this->logo_small && file_exists(public_path('storage/' . $this->logo_small))) { if ($this->logo_small && Storage::disk('public')->exists($this->logo_small)) {
return asset('storage/' . $this->logo_small); return Storage::disk('public')->url($this->logo_small);
} }
return null; return null;
} }
+1
View File
@@ -30,6 +30,7 @@ class Evento extends Model
'is_incontro_gruppo', 'is_incontro_gruppo',
'luogo_indirizzo', 'luogo_indirizzo',
'luogo_url_maps', 'luogo_url_maps',
'uid_esterno',
]; ];
protected $casts = [ protected $casts = [
+2 -2
View File
@@ -9,8 +9,8 @@ class VistaReport extends Model
{ {
protected $table = 'viste_report'; protected $table = 'viste_report';
protected $fillable = [ protected $fillable = [
'user_id', 'nome', 'tipo', 'colonne_visibili', 'user_id', 'nome', 'tipo', 'colonne_visibili',
'colonne_ordinamento', 'filtri', 'ricerca' 'colonne_ordinamento', 'filtri', 'ricerca', 'is_default'
]; ];
protected $casts = [ protected $casts = [
Executable
+96
View File
@@ -0,0 +1,96 @@
#!/bin/bash
set -euo pipefail
NAME="${APP_NAME:-glastree}"
VERSION="$(date +%Y%m%d_%H%M)"
ARCHIVE="${NAME}-${VERSION}.tar.gz"
echo "==> Installazione dipendenze PHP production..."
composer install --no-dev --optimize-autoloader --no-scripts --quiet
echo " OK"
echo "==> Build asset Vite..."
if [ -f "node_modules/.package-lock.json" ] || [ -d "node_modules" ]; then
npm run build --quiet 2>/dev/null && echo " OK" || echo " (skip — node_modules non presente)"
else
echo " (skip — node_modules assente, build Vite non eseguita)"
fi
echo "==> Pulizia cache..."
rm -rf bootstrap/cache/*.php storage/framework/cache/data/* storage/framework/sessions/* storage/framework/views/* storage/logs/* 2>/dev/null || true
echo " OK"
echo "==> Creazione archivio: ${ARCHIVE}"
tar czf "${ARCHIVE}" \
--exclude='.git' \
--exclude='.gitignore' \
--exclude='.env' \
--exclude='.env.example' \
--exclude='node_modules' \
--exclude='tests' \
--exclude='vendor/bin' \
--exclude='vendor/*/tests' \
--exclude='vendor/*/test' \
--exclude='vendor/*/.git' \
--exclude='vendor/*/docs' \
--exclude='vendor/*/doc' \
--exclude='storage/app/backups' \
--exclude='storage/app/documenti' \
--exclude='storage/app/public' \
--exclude='storage/app/private' \
--exclude='public/storage' \
--exclude='storage/framework/cache/*' \
--exclude='storage/framework/sessions/*' \
--exclude='storage/framework/views/*' \
--exclude='storage/logs/*' \
--exclude='storage/debugbar/*' \
--exclude='bootstrap/cache/*.php' \
--exclude='public/hot' \
--exclude='*.tar.gz' \
--exclude='*.tar' \
--exclude='MEMORY.md' \
--exclude='AGENTS.md' \
--exclude='build-dist.sh' \
--exclude='Dockerfile' \
--exclude='docker-compose.yml' \
--exclude='docker-compose.mysql.yml' \
--exclude='docker-entrypoint.sh' \
--exclude='.dockerignore' \
--warning=no-file-changed \
.
echo ""
echo "============================================"
echo " Archivio creato: ${ARCHIVE}"
echo " Dimensione: $(du -h "${ARCHIVE}" | cut -f1)"
echo "============================================"
echo ""
echo "Per estrarre sul server di destinazione:"
echo " tar xzf ${ARCHIVE}"
echo ""
echo "Poi esegui:"
echo " # 1. Crea directory necessarie (se non esistono già)"
echo ' mkdir -p storage/framework/cache/data storage/framework/sessions storage/framework/views storage/logs bootstrap/cache storage/app/public storage/app/public/logos storage/app/public/documenti/eventi storage/app/backups storage/app/documenti storage/app/private/avatars/gruppi'
echo ""
echo " # 2. Crea file .gitignore in storage/app/public (serve a Laravel)"
echo ' echo "*" > storage/app/public/.gitignore'
echo ' echo "!.gitignore" >> storage/app/public/.gitignore'
echo ""
echo " # 3. Permessi (web server = www-data)"
echo ' chmod -R 775 storage bootstrap/cache'
echo ' chown -R www-data:www-data storage bootstrap/cache'
echo ""
echo " # 4. Configura ambiente"
echo " cp .env.example .env # modifica DB, APP_URL, etc."
echo " php artisan key:generate"
echo ""
echo " # 5. Pulisci cache e ricrea symlink storage (relativo per portabilità)"
echo ' rm -f public/storage'
echo ' ln -sf ../storage/app/public public/storage'
echo " php artisan config:clear"
echo " php artisan route:clear"
echo " php artisan view:clear"
echo ""
echo " # 6. Avvia installazione MySQL"
echo " php install.php"
+3
View File
@@ -197,6 +197,7 @@ CREATE TABLE `gruppo_individuo` (
`gruppo_id` bigint unsigned NOT NULL, `gruppo_id` bigint unsigned NOT NULL,
`individuo_id` bigint unsigned NOT NULL, `individuo_id` bigint unsigned NOT NULL,
`ruolo_ids` json DEFAULT NULL, `ruolo_ids` json DEFAULT NULL,
`ruolo_nel_gruppo` varchar(255) DEFAULT NULL,
`data_adesione` date DEFAULT NULL, `data_adesione` date DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
@@ -511,6 +512,7 @@ CREATE TABLE `eventi` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint unsigned NOT NULL AUTO_INCREMENT,
`tenant_id` bigint unsigned DEFAULT NULL, `tenant_id` bigint unsigned DEFAULT NULL,
`nome_evento` varchar(255) NOT NULL, `nome_evento` varchar(255) NOT NULL,
`descrizione_evento` text,
`tipo_evento` varchar(255) DEFAULT NULL, `tipo_evento` varchar(255) DEFAULT NULL,
`tipo_recorrenza` enum('singolo','settimanale','mensile','annuale','altro') DEFAULT 'singolo', `tipo_recorrenza` enum('singolo','settimanale','mensile','annuale','altro') DEFAULT 'singolo',
`giorno_settimana` tinyint unsigned DEFAULT NULL, `giorno_settimana` tinyint unsigned DEFAULT NULL,
@@ -526,6 +528,7 @@ CREATE TABLE `eventi` (
`luogo_indirizzo` varchar(500) DEFAULT NULL, `luogo_indirizzo` varchar(500) DEFAULT NULL,
`luogo_url_maps` text, `luogo_url_maps` text,
`uid_esterno` varchar(500) DEFAULT NULL, `uid_esterno` varchar(500) DEFAULT NULL,
`is_incontro_gruppo` tinyint(1) NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (!Schema::hasColumn('eventi', 'descrizione_evento')) {
Schema::table('eventi', function (Blueprint $table) {
$table->text('descrizione_evento')->nullable()->after('nome_evento');
});
}
if (!Schema::hasColumn('eventi', 'is_incontro_gruppo')) {
Schema::table('eventi', function (Blueprint $table) {
$table->boolean('is_incontro_gruppo')->default(false)->after('uid_esterno');
});
}
}
public function down(): void
{
}
};
@@ -248,6 +248,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" class="custom-control-input" id="is_active" <input type="checkbox" class="custom-control-input" id="is_active"
name="is_active" value="1" {{ ($settings->is_active ?? false) ? 'checked' : '' }}> name="is_active" value="1" {{ ($settings->is_active ?? false) ? 'checked' : '' }}>
<label class="custom-control-label" for="is_active">Account Email Attivo</label> <label class="custom-control-label" for="is_active">Account Email Attivo</label>
@@ -265,6 +266,7 @@
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="hidden" name="signature_enabled" value="0">
<input type="checkbox" class="custom-control-input" id="signature_enabled" <input type="checkbox" class="custom-control-input" id="signature_enabled"
name="signature_enabled" value="1" {{ ($settings->signature_enabled ?? true) ? 'checked' : '' }}> name="signature_enabled" value="1" {{ ($settings->signature_enabled ?? true) ? 'checked' : '' }}>
<label class="custom-control-label" for="signature_enabled">Attiva firma automatica</label> <label class="custom-control-label" for="signature_enabled">Attiva firma automatica</label>
@@ -390,14 +392,17 @@
<a href="{{ route('impostazioni.email.sync') }}" class="btn btn-warning ml-2"> <a href="{{ route('impostazioni.email.sync') }}" class="btn btn-warning ml-2">
<i class="fas fa-sync mr-1"></i> Sincronizza Ora <i class="fas fa-sync mr-1"></i> Sincronizza Ora
</a> </a>
<form action="{{ route('impostazioni.email.destroy') }}" method="POST" class="ml-auto" onsubmit="return confirm('Eliminare definitivamente la configurazione email? Tutti i dati di connessione verranno rimossi.')">
@csrf @method('DELETE')
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash mr-1"></i> Elimina Configurazione
</button>
</form>
</div> </div>
</form> </form>
<div class="d-flex justify-content-end mt-2">
<form action="{{ route('impostazioni.email.destroy') }}" method="POST" onsubmit="return confirm('Eliminare definitivamente la configurazione email? Tutti i dati di connessione verranno rimossi.')">
@csrf @method('DELETE')
<button type="submit" class="btn btn-danger btn-sm">
<i class="fas fa-trash mr-1"></i> Elimina Configurazione
</button>
</form>
</div>
</div> </div>
</div> </div>
@endsection @endsection
+23 -2
View File
@@ -131,8 +131,29 @@ $currentFolder = $folders->firstWhere('type', $folder);
</table> </table>
</div> </div>
</div> </div>
<div class="card-footer"> <div class="card-footer d-flex justify-content-between align-items-center">
{{ $messages->withQueryString()->links('vendor.pagination.simple-bootstrap-4') }} <div>
<form method="GET" class="form-inline" id="perPageForm">
<label class="mr-2 small">Righe per pagina:</label>
<select name="perPage" class="form-control form-control-sm" onchange="this.form.submit()" style="width: auto;">
@foreach([10, 20, 25, 50, 100] as $p)
<option value="{{ $p }}" {{ (int) request('perPage', 20) === $p ? 'selected' : '' }}>{{ $p }}</option>
@endforeach
</select>
@foreach(request()->except(['perPage', 'page']) as $key => $value)
@if(is_array($value))
@foreach($value as $v)
<input type="hidden" name="{{ $key }}[]" value="{{ $v }}">
@endforeach
@else
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
@endif
@endforeach
</form>
</div>
<div>
{{ $messages->links('vendor.pagination.simple-bootstrap-4') }}
</div>
</div> </div>
</div> </div>
@endsection @endsection
+39 -2
View File
@@ -17,6 +17,9 @@
<a href="{{ route('eventi.create') }}" class="btn btn-success btn-sm"> <a href="{{ route('eventi.create') }}" class="btn btn-success btn-sm">
<i class="fas fa-plus mr-1"></i> Nuovo Evento <i class="fas fa-plus mr-1"></i> Nuovo Evento
</a> </a>
<button type="button" class="btn btn-info btn-sm" id="syncCalendarsBtn" onclick="syncCalendars()">
<i class="fas fa-sync mr-1"></i> Sync
</button>
<form action="{{ route('eventi.export-selected-ics') }}" method="POST" class="d-inline"> <form action="{{ route('eventi.export-selected-ics') }}" method="POST" class="d-inline">
@csrf @csrf
<button type="submit" class="btn btn-success btn-sm"> <button type="submit" class="btn btn-success btn-sm">
@@ -40,10 +43,44 @@
@section('scripts') @section('scripts')
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js"></script>
<script> <script>
function syncCalendars() {
var btn = document.getElementById('syncCalendarsBtn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-1"></i> Sync...';
fetch('{{ route('calendario-connessioni.sync-all') }}', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || '',
'Content-Type': 'application/json',
},
})
.then(function(r) { return r.json(); })
.then(function(data) {
var msg = data.connections + ' connessioni sincronizzate.';
msg += ' Importati: ' + data.total_imported + ', Esportati: ' + data.total_exported + '.';
if (data.success) {
alert('✅ Sync completato!\n' + msg);
} else {
alert('⚠️ Sync completato con errori.\n' + msg);
}
if (window.calendar) {
window.calendar.refetchEvents();
}
})
.catch(function(err) {
alert('❌ Errore sync: ' + err.message);
})
.finally(function() {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-sync mr-1"></i> Sync';
});
}
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar'); var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, { window.calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth', initialView: 'dayGridMonth',
locale: 'it', locale: 'it',
headerToolbar: { headerToolbar: {
@@ -97,7 +134,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
calendar.render(); window.calendar.render();
}); });
</script> </script>
@endsection @endsection
+55
View File
@@ -0,0 +1,55 @@
@extends('layouts.adminlte')
@section('title', 'Importa Eventi')
@section('page_title', 'Importa Eventi da ICS')
@section('content')
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-upload mr-2"></i>Carica file ICS</h3>
</div>
<div class="card-body">
<form action="{{ route('eventi.import.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="file">Seleziona file ICS *</label>
<input type="file" name="file" id="file" class="form-control" accept=".ics" required>
<small class="form-text text-muted">
Il file deve essere in formato ICS (iCalendar).
</small>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload mr-1"></i> Importa
</button>
<a href="/eventi" class="btn btn-secondary ml-2">
Annulla
</a>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-info-circle mr-2"></i>Informazioni</h3>
</div>
<div class="card-body">
<h6>Campi importati:</h6>
<ul>
<li><code>SUMMARY</code> Nome evento (obbligatorio)</li>
<li><code>DESCRIPTION</code> Descrizione</li>
<li><code>DTSTART</code> Data/Ora inizio</li>
<li><code>DTEND</code> Durata</li>
<li><code>LOCATION</code> Luogo</li>
<li><code>UID</code> ID esterno (per evitare duplicati)</li>
</ul>
<p class="text-muted small mt-2">
<i class="fas fa-info-circle mr-1"></i>
Gli eventi con UID già presente nel sistema verranno saltati automaticamente.
</p>
</div>
</div>
</div>
</div>
@endsection
+26 -2
View File
@@ -34,6 +34,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div> </div>
@endif @endif
@if($canWriteEventi) @if($canWriteEventi)
<a href="/eventi/import" class="btn btn-sm btn-warning mr-2">
<i class="fas fa-file-import mr-1"></i> Importa ICS
</a>
<a href="/eventi/create" class="btn btn-xs btn-success"> <a href="/eventi/create" class="btn btn-xs btn-success">
<i class="fas fa-plus mr-1"></i> Nuovo Evento <i class="fas fa-plus mr-1"></i> Nuovo Evento
</a> </a>
@@ -247,8 +250,29 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="card-footer"> <div class="card-footer d-flex justify-content-between align-items-center">
{{ $eventi->withQueryString()->links() }} <div>
<form method="GET" class="form-inline" id="perPageForm">
<label class="mr-2 small">Righe per pagina:</label>
<select name="perPage" class="form-control form-control-sm" onchange="this.form.submit()" style="width: auto;">
@foreach([10, 20, 25, 50, 100] as $p)
<option value="{{ $p }}" {{ (int) request('perPage', 20) === $p ? 'selected' : '' }}>{{ $p }}</option>
@endforeach
</select>
@foreach(request()->except(['perPage', 'page']) as $key => $value)
@if(is_array($value))
@foreach($value as $v)
<input type="hidden" name="{{ $key }}[]" value="{{ $v }}">
@endforeach
@else
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
@endif
@endforeach
</form>
</div>
<div>
{{ $eventi->links() }}
</div>
</div> </div>
</div> </div>
+64
View File
@@ -0,0 +1,64 @@
@extends('layouts.adminlte')
@section('title', 'Importa Gruppi')
@section('page_title', 'Importa Gruppi da CSV')
@section('content')
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-upload mr-2"></i>Carica file CSV</h3>
</div>
<div class="card-body">
<form action="{{ route('gruppi.import.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="file">Seleziona file CSV *</label>
<input type="file" name="file" id="file" class="form-control" accept=".csv,.txt" required>
<small class="form-text text-muted">
Il file deve essere in formato CSV con separatore virgola.
</small>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload mr-1"></i> Importa
</button>
<a href="{{ route('gruppi.index') }}" class="btn btn-secondary ml-2">
Annulla
</a>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-info">
<h3 class="card-title text-white"><i class="fas fa-download mr-2"></i>Template</h3>
</div>
<div class="card-body">
<p>Scarica il template CSV per l'importazione:</p>
<a href="{{ route('gruppi.template') }}" class="btn btn-success btn-block">
<i class="fas fa-file-csv mr-1"></i> Scarica Template
</a>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-info-circle mr-2"></i>Formato CSV</h3>
</div>
<div class="card-body">
<h6>Campi:</h6>
<ul>
<li><code>nome</code> (obbligatorio)</li>
<li><code>descrizione</code></li>
<li><code>parent_id</code> (ID del gruppo padre)</li>
<li><code>diocesi_id</code> (ID della diocesi)</li>
<li><code>indirizzo_incontro</code></li>
<li><code>cap_incontro</code></li>
<li><code>città_incontro</code></li>
<li><code>sigla_provincia_incontro</code> (2 lettere)</li>
</ul>
</div>
</div>
</div>
</div>
@endsection
+27
View File
@@ -55,6 +55,9 @@ $visibleColumnsJson = json_encode($visibleColumns);
<i class="fas fa-save mr-1"></i> Salva Vista <i class="fas fa-save mr-1"></i> Salva Vista
</button> </button>
@if($canWriteGruppi) @if($canWriteGruppi)
<a href="{{ route('gruppi.import') }}" class="btn btn-warning btn-sm mr-1">
<i class="fas fa-file-import mr-1"></i> Importa
</a>
<a href="{{ route('gruppi.create') }}" class="btn btn-success btn-sm"> <a href="{{ route('gruppi.create') }}" class="btn btn-success btn-sm">
<i class="fas fa-plus"></i> Nuovo Gruppo <i class="fas fa-plus"></i> Nuovo Gruppo
</a> </a>
@@ -174,6 +177,30 @@ $visibleColumnsJson = json_encode($visibleColumns);
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="card-footer d-flex justify-content-between align-items-center">
<div>
<form method="GET" class="form-inline" id="perPageForm">
<label class="mr-2 small">Righe per pagina:</label>
<select name="perPage" class="form-control form-control-sm" onchange="this.form.submit()" style="width: auto;">
@foreach([10, 20, 25, 50, 100] as $p)
<option value="{{ $p }}" {{ (int) request('perPage', 20) === $p ? 'selected' : '' }}>{{ $p }}</option>
@endforeach
</select>
@foreach(request()->except(['perPage', 'page']) as $key => $value)
@if(is_array($value))
@foreach($value as $v)
<input type="hidden" name="{{ $key }}[]" value="{{ $v }}">
@endforeach
@else
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
@endif
@endforeach
</form>
</div>
<div>
{{ $gruppi->links() }}
</div>
</div>
@else @else
<div class="card-body p-0"> <div class="card-body p-0">
<div class="tree-view" style="padding: 15px;"> <div class="tree-view" style="padding: 15px;">
+74
View File
@@ -27,6 +27,11 @@
<i class="fab fa-google-drive mr-2"></i> Google Drive <i class="fab fa-google-drive mr-2"></i> Google Drive
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="#help-calendario" data-toggle="tab">
<i class="fas fa-calendar-alt mr-2"></i> Calendario
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#help-tipologie" data-toggle="tab"> <a class="nav-link" href="#help-tipologie" data-toggle="tab">
<i class="fas fa-tags mr-2"></i> Tipologie e Dati <i class="fas fa-tags mr-2"></i> Tipologie e Dati
@@ -194,6 +199,75 @@
</div> </div>
</div> </div>
{{-- Calendario --}}
<div class="tab-pane" id="help-calendario">
<div class="card card-outline card-info">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-calendar-alt mr-2"></i> Google Calendar</h3>
</div>
<div class="card-body">
<h5>Cos'è</h5>
<p>Integrazione con Google Calendar per sincronizzare gli eventi tra l'app e il tuo calendario Google. Supporta import, export e sincronizzazione bidirezionale.</p>
<h5>Requisiti</h5>
<ol>
<li>Un account Google / Google Workspace</li>
<li>Un progetto sulla <strong>Google Cloud Console</strong> con l'API Google Calendar abilitata</li>
<li>Credenziali OAuth 2.0 (Client ID e Client Secret)</li>
</ol>
<h5>Passo 1: Creare il progetto Google Cloud</h5>
<ol>
<li>Vai su <a href="https://console.developers.google.com/" target="_blank">Google Cloud Console</a></li>
<li>Crea un nuovo progetto o selezionane uno esistente</li>
<li>Vai a <strong>API e servizi Libreria</strong></li>
<li>Cerca "<strong>Google Calendar API</strong>" e <strong>abilitala</strong></li>
<li>Vai a <strong>API e servizi Credenziali</strong></li>
<li>Clicca "<strong>Crea credenziali → ID client OAuth</strong>"</li>
<li>Tipo applicazione: "<strong>Applicazione Web</strong>"</li>
<li><strong>URI di reindirizzamento autorizzato:</strong> aggiungi <code>https://developers.google.com/oauthplayground</code></li>
<li>Completa la creazione e <strong>copia Client ID e Client Secret</strong></li>
</ol>
<h5>Passo 2: Ottenere il Refresh Token (Google OAuth Playground)</h5>
<p>A differenza di Google Drive, per Google Calendar il refresh token va ottenuto manualmente tramite il Google OAuth Playground:</p>
<ol>
<li>Vai su <a href="https://developers.google.com/oauthplayground" target="_blank">Google OAuth Playground</a></li>
<li>Clicca l'ingranaggio ⚙️ in alto a destra</li>
<li>Spunta "<strong>Use your own OAuth credentials</strong>"</li>
<li>Incolla <strong>Client ID</strong> e <strong>Client Secret</strong> creati al Passo 1</li>
<li>Nella colonna sinistra, espandi "<strong>Google Calendar API v3</strong>" e seleziona lo scope <code>https://www.googleapis.com/auth/calendar</code></li>
<li>Clicca "<strong>Authorize APIs</strong>" e autorizza con l'account Google</li>
<li>Clicca "<strong>Exchange authorization code for tokens</strong>"</li>
<li><strong>Copia il Refresh Token</strong> generato</li>
</ol>
<h5>Passo 3: Configurazione nell'app</h5>
<ol>
<li>Vai in <strong>Impostazioni → Calendario</strong></li>
<li>Clicca "<strong>Nuova Connessione</strong>"</li>
<li>Inserisci un nome descrittivo (es. "Calendario Lavoro")</li>
<li>Seleziona tipo <strong>Google Calendar</strong></li>
<li>Incolla <strong>Client ID</strong>, <strong>Client Secret</strong> e il <strong>Refresh Token</strong> ottenuto</li>
<li><strong>Calendar ID (opzionale):</strong> lascia <code>primary</code> per il calendario principale, oppure inserisci l'ID di un calendario specifico (lo trovi in Google Calendar Impostazioni calendario ID calendario)</li>
<li>Seleziona la <strong>direzione di sincronizzazione</strong> (import, export o bidirezionale)</li>
<li>Clicca "<strong>Salva</strong>"</li>
</ol>
<h5>Test connessione</h5>
<p>Dopo aver salvato, usa il pulsante <span class="badge badge-success"><i class="fas fa-plug"></i> Test</span> nella tabella delle connessioni per verificare che l'autorizzazione funzioni correttamente.</p>
<h5>Sincronizzazione</h5>
<p>Dopo aver configurato la connessione, puoi sincronizzare gli eventi manualmente cliccando <span class="badge badge-primary"><i class="fas fa-sync"></i> Sync</span>. La sincronizzazione automatica viene eseguita in base all'intervallo impostato (richiede il cron di Laravel configurato).</p>
<div class="alert alert-info">
<i class="fas fa-info-circle mr-2"></i>
Il Refresh Token è <strong>permanente</strong> finché non viene revocato dall'utente. Se la connessione smette di funzionare, potrebbe essere necessario rigenerarlo ripetendo il Passo 2.
</div>
</div>
</div>
</div>
{{-- Tipologie e Dati --}} {{-- Tipologie e Dati --}}
<div class="tab-pane" id="help-tipologie"> <div class="tab-pane" id="help-tipologie">
<div class="card card-outline card-success"> <div class="card card-outline card-success">
+75 -10
View File
@@ -62,6 +62,7 @@
<ol> <ol>
<li><a href="#webdav">WebDAV / Nextcloud</a></li> <li><a href="#webdav">WebDAV / Nextcloud</a></li>
<li><a href="#googledrive">Google Drive</a></li> <li><a href="#googledrive">Google Drive</a></li>
<li><a href="#calendario">Google Calendar</a></li>
<li><a href="#tipologie">Tipologie e Gestione Dati</a></li> <li><a href="#tipologie">Tipologie e Gestione Dati</a></li>
<li><a href="#email">Email / SMTP</a></li> <li><a href="#email">Email / SMTP</a></li>
<li><a href="#installazione">Guida all'Installazione</a></li> <li><a href="#installazione">Guida all'Installazione</a></li>
@@ -182,8 +183,72 @@
<div class="page-break"></div> <div class="page-break"></div>
{{-- 3. Tipologie e Dati --}} {{-- 3. Google Calendar --}}
<h2 id="tipologie">3. Tipologie e Gestione Dati</h2> <h2 id="calendario">3. Google Calendar</h2>
<h3>Cos'è</h3>
<p>Integrazione con Google Calendar per sincronizzare gli eventi tra l'app e il tuo calendario Google. Supporta import, export e sincronizzazione bidirezionale.</p>
<h3>Requisiti</h3>
<ol>
<li>Un account Google / Google Workspace</li>
<li>Un progetto sulla <strong>Google Cloud Console</strong> con l'API Google Calendar abilitata</li>
<li>Credenziali OAuth 2.0 (Client ID e Client Secret)</li>
</ol>
<h3>Passo 1: Creare il progetto Google Cloud</h3>
<ol>
<li>Vai su <strong>Google Cloud Console</strong> (console.developers.google.com)</li>
<li>Crea un nuovo progetto o selezionane uno esistente</li>
<li>Vai a <strong>API e servizi → Libreria</strong></li>
<li>Cerca "<strong>Google Calendar API</strong>" e <strong>abilitala</strong></li>
<li>Vai a <strong>API e servizi → Credenziali</strong></li>
<li>Clicca "<strong>Crea credenziali → ID client OAuth</strong>"</li>
<li>Tipo applicazione: "<strong>Applicazione Web</strong>"</li>
<li><strong>URI di reindirizzamento autorizzato:</strong> aggiungi <code>https://developers.google.com/oauthplayground</code></li>
<li>Completa la creazione e <strong>copia Client ID e Client Secret</strong></li>
</ol>
<h3>Passo 2: Ottenere il Refresh Token</h3>
<p>A differenza di Google Drive, per Google Calendar il refresh token va ottenuto manualmente tramite il Google OAuth Playground:</p>
<ol>
<li>Vai su <strong>Google OAuth Playground</strong> (developers.google.com/oauthplayground)</li>
<li>Clicca l'ingranaggio ⚙️ in alto a destra</li>
<li>Spunta "<strong>Use your own OAuth credentials</strong>"</li>
<li>Incolla <strong>Client ID</strong> e <strong>Client Secret</strong> creati al Passo 1</li>
<li>Nella colonna sinistra, espandi "<strong>Google Calendar API v3</strong>" e seleziona lo scope <code>https://www.googleapis.com/auth/calendar</code></li>
<li>Clicca "<strong>Authorize APIs</strong>" e autorizza con l'account Google</li>
<li>Clicca "<strong>Exchange authorization code for tokens</strong>"</li>
<li><strong>Copia il Refresh Token</strong> generato</li>
</ol>
<h3>Passo 3: Configurazione nell'app</h3>
<ol>
<li>Vai in <strong>Impostazioni Calendario</strong></li>
<li>Clicca "<strong>Nuova Connessione</strong>"</li>
<li>Inserisci un nome descrittivo (es. "Calendario Lavoro")</li>
<li>Seleziona tipo <strong>Google Calendar</strong></li>
<li>Incolla <strong>Client ID</strong>, <strong>Client Secret</strong> e il <strong>Refresh Token</strong> ottenuto</li>
<li><strong>Calendar ID (opzionale):</strong> lascia <code>primary</code> per il calendario principale, oppure inserisci l'ID di un calendario specifico</li>
<li>Seleziona la <strong>direzione di sincronizzazione</strong> (import, export o bidirezionale)</li>
<li>Clicca "<strong>Salva</strong>"</li>
</ol>
<h3>Test connessione</h3>
<p>Dopo aver salvato, usa il pulsante <strong>Test</strong> nella tabella delle connessioni per verificare che l'autorizzazione funzioni correttamente.</p>
<h3>Sincronizzazione</h3>
<p>Dopo aver configurato la connessione, puoi sincronizzare gli eventi manualmente cliccando <strong>Sync</strong>. La sincronizzazione automatica viene eseguita in base all'intervallo impostato (richiede il cron di Laravel configurato).</p>
<div class="callout callout-info">
<h4>Refresh Token permanente</h4>
<p>Il Refresh Token è permanente finché non viene revocato dall'utente. Se la connessione smette di funzionare, potrebbe essere necessario rigenerarlo ripetendo il Passo 2.</p>
</div>
<div class="page-break"></div>
{{-- 4. Tipologie e Dati --}}
<h2 id="tipologie">4. Tipologie e Gestione Dati</h2>
<h3>Tipologie Documenti</h3> <h3>Tipologie Documenti</h3>
<p>Le tipologie classificano i documenti (avatar, galleria, documento, statuto, altro).</p> <p>Le tipologie classificano i documenti (avatar, galleria, documento, statuto, altro).</p>
@@ -210,8 +275,8 @@
<div class="page-break"></div> <div class="page-break"></div>
{{-- 4. Email / SMTP --}} {{-- 5. Email / SMTP --}}
<h2 id="email">4. Email / SMTP</h2> <h2 id="email">5. Email / SMTP</h2>
<h3>Configurazione Email</h3> <h3>Configurazione Email</h3>
<p>Vai in <strong>Impostazioni Email</strong> per configurare la posta elettronica.</p> <p>Vai in <strong>Impostazioni Email</strong> per configurare la posta elettronica.</p>
@@ -264,8 +329,8 @@
<div class="page-break"></div> <div class="page-break"></div>
{{-- 5. Installazione --}} {{-- 6. Installazione --}}
<h2 id="installazione">5. Guida all'Installazione</h2> <h2 id="installazione">6. Guida all'Installazione</h2>
<h3>Panoramica</h3> <h3>Panoramica</h3>
<p>L'installazione avviene tramite l'interattivo <code>install.php</code> che guida passo-passo. Supporta due modalità:</p> <p>L'installazione avviene tramite l'interattivo <code>install.php</code> che guida passo-passo. Supporta due modalità:</p>
@@ -411,8 +476,8 @@ sudo systemctl reload apache2</pre>
<div class="page-break"></div> <div class="page-break"></div>
{{-- 6. Backup e Migrazione --}} {{-- 7. Backup e Migrazione --}}
<h2 id="backup">6. Backup e Migrazione Server</h2> <h2 id="backup">7. Backup e Migrazione Server</h2>
<h3>Cos'è</h3> <h3>Cos'è</h3>
<p>Il sistema di backup crea un archivio ZIP contenente tutto il necessario per ripristinare l'applicazione su un nuovo server. Include database, file caricati e configurazione.</p> <p>Il sistema di backup crea un archivio ZIP contenente tutto il necessario per ripristinare l'applicazione su un nuovo server. Include database, file caricati e configurazione.</p>
@@ -591,8 +656,8 @@ sudo certbot --apache -d glastree.esempio.it</pre>
<div class="page-break"></div> <div class="page-break"></div>
{{-- 7. Docker --}} {{-- 8. Docker --}}
<h2 id="docker">7. Docker</h2> <h2 id="docker">8. Docker</h2>
<h3>Creare un'immagine Docker con dati di base</h3> <h3>Creare un'immagine Docker con dati di base</h3>
<p>Puoi creare un'immagine Docker pronta all'uso che include l'applicazione e i dati di base (tipologie documenti, tipologie eventi, ruoli). Scegli la configurazione in base alle tue esigenze:</p> <p>Puoi creare un'immagine Docker pronta all'uso che include l'applicazione e i dati di base (tipologie documenti, tipologie eventi, ruoli). Scegli la configurazione in base alle tue esigenze:</p>
+19 -11
View File
@@ -583,6 +583,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" class="custom-control-input" id="is_active" <input type="checkbox" class="custom-control-input" id="is_active"
name="is_active" value="1" {{ ($emailSettings->is_active ?? false) ? 'checked' : '' }}> name="is_active" value="1" {{ ($emailSettings->is_active ?? false) ? 'checked' : '' }}>
<label class="custom-control-label" for="is_active">Account Email Attivo</label> <label class="custom-control-label" for="is_active">Account Email Attivo</label>
@@ -599,11 +600,12 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="form-group"> <div class="form-group">
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="signature_enabled" <input type="hidden" name="signature_enabled" value="0">
name="signature_enabled" value="1" {{ ($emailSettings->signature_enabled ?? true) ? 'checked' : '' }}> <input type="checkbox" class="custom-control-input" id="signature_enabled"
<label class="custom-control-label" for="signature_enabled">Attiva firma automatica</label> name="signature_enabled" value="1" {{ ($emailSettings->signature_enabled ?? true) ? 'checked' : '' }}>
</div> <label class="custom-control-label" for="signature_enabled">Attiva firma automatica</label>
</div>
<small class="text-muted">Quando attivo, la firma verrà aggiunta in fondo a ogni email inviata</small> <small class="text-muted">Quando attivo, la firma verrà aggiunta in fondo a ogni email inviata</small>
</div> </div>
<hr> <hr>
@@ -722,14 +724,17 @@
<a href="{{ route('impostazioni.email.sync') }}" class="btn btn-warning ml-2"> <a href="{{ route('impostazioni.email.sync') }}" class="btn btn-warning ml-2">
<i class="fas fa-sync mr-1"></i> Sincronizza Ora <i class="fas fa-sync mr-1"></i> Sincronizza Ora
</a> </a>
<form action="{{ route('impostazioni.email.destroy') }}" method="POST" class="ml-auto" onsubmit="return confirm('Eliminare definitivamente la configurazione email? Tutti i dati di connessione verranno rimossi.')">
@csrf @method('DELETE')
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash mr-1"></i> Elimina Configurazione
</button>
</form>
</div> </div>
</form> </form>
<div class="d-flex justify-content-end mt-2">
<form action="{{ route('impostazioni.email.destroy') }}" method="POST" onsubmit="return confirm('Eliminare definitivamente la configurazione email? Tutti i dati di connessione verranno rimossi.')">
@csrf @method('DELETE')
<button type="submit" class="btn btn-danger btn-sm">
<i class="fas fa-trash mr-1"></i> Elimina Configurazione
</button>
</form>
</div>
</div> </div>
{{-- === CALENDARIO === --}} {{-- === CALENDARIO === --}}
@@ -1626,6 +1631,9 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
sortableCalendario.refresh(); sortableCalendario.refresh();
} }
} }
if (target === '#email-firma') {
setTimeout(initSignatureEditor, 100);
}
}); });
// ===== MITTENTI AGGIUNTIVI ===== // ===== MITTENTI AGGIUNTIVI =====
+23 -2
View File
@@ -220,8 +220,29 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@endif @endif
</div> </div>
@if($individui->count() > 0) @if($individui->count() > 0)
<div class="card-footer"> <div class="card-footer d-flex justify-content-between align-items-center">
{{ $individui->links() }} <div>
<form method="GET" class="form-inline" id="perPageForm">
<label class="mr-2 small">Righe per pagina:</label>
<select name="perPage" class="form-control form-control-sm" onchange="this.form.submit()" style="width: auto;">
@foreach([10, 20, 25, 50, 100] as $p)
<option value="{{ $p }}" {{ (int) request('perPage', 20) === $p ? 'selected' : '' }}>{{ $p }}</option>
@endforeach
</select>
@foreach(request()->except(['perPage', 'page']) as $key => $value)
@if(is_array($value))
@foreach($value as $v)
<input type="hidden" name="{{ $key }}[]" value="{{ $v }}">
@endforeach
@else
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
@endif
@endforeach
</form>
</div>
<div>
{{ $individui->links() }}
</div>
</div> </div>
@endif @endif
</div> </div>
+6
View File
@@ -87,6 +87,9 @@ Route::patch('gruppi/{gruppo}/membri/{individuo}', [GruppoMembroController::clas
Route::delete('gruppi/{gruppo}/membri/{individuo}', [GruppoMembroController::class, 'destroy'])->middleware('auth'); Route::delete('gruppi/{gruppo}/membri/{individuo}', [GruppoMembroController::class, 'destroy'])->middleware('auth');
// Gruppi specific routes (MUST be before resource route) // Gruppi specific routes (MUST be before resource route)
Route::get('gruppi/import', [GruppoController::class, 'import'])->middleware('auth')->name('gruppi.import');
Route::post('gruppi/import', [GruppoController::class, 'importStore'])->middleware('auth')->name('gruppi.import.store');
Route::get('gruppi/template', [GruppoController::class, 'downloadTemplate'])->middleware('auth')->name('gruppi.template');
Route::get('gruppi/all', [GruppoController::class, 'allGruppi'])->middleware('auth'); Route::get('gruppi/all', [GruppoController::class, 'allGruppi'])->middleware('auth');
Route::get('gruppi/{gruppo}/individui-email', [GruppoController::class, 'individuiEmail'])->middleware('auth'); Route::get('gruppi/{gruppo}/individui-email', [GruppoController::class, 'individuiEmail'])->middleware('auth');
@@ -97,6 +100,8 @@ Route::delete('gruppi/{gruppo}/avatar', [\App\Http\Controllers\GruppoAvatarContr
Route::resource('gruppi', GruppoController::class)->middleware('auth'); Route::resource('gruppi', GruppoController::class)->middleware('auth');
Route::get('eventi/calendar', [EventoController::class, 'calendar'])->name('eventi.calendar')->middleware('auth'); Route::get('eventi/calendar', [EventoController::class, 'calendar'])->name('eventi.calendar')->middleware('auth');
Route::get('eventi/calendar/events', [EventoController::class, 'calendarEvents'])->name('eventi.calendar.events')->middleware('auth'); Route::get('eventi/calendar/events', [EventoController::class, 'calendarEvents'])->name('eventi.calendar.events')->middleware('auth');
Route::get('eventi/import', [EventoController::class, 'importIcs'])->middleware('auth')->name('eventi.import');
Route::post('eventi/import', [EventoController::class, 'importIcsStore'])->middleware('auth')->name('eventi.import.store');
Route::get('eventi/{evento}/export-ics', [EventoController::class, 'exportIcs'])->name('eventi.export-ics')->middleware('auth'); Route::get('eventi/{evento}/export-ics', [EventoController::class, 'exportIcs'])->name('eventi.export-ics')->middleware('auth');
Route::post('eventi/export-ics', [EventoController::class, 'exportSelectedIcs'])->name('eventi.export-selected-ics')->middleware('auth'); Route::post('eventi/export-ics', [EventoController::class, 'exportSelectedIcs'])->name('eventi.export-selected-ics')->middleware('auth');
Route::post('eventi/mass-elimina', [EventoController::class, 'massElimina'])->middleware('auth'); Route::post('eventi/mass-elimina', [EventoController::class, 'massElimina'])->middleware('auth');
@@ -198,6 +203,7 @@ Route::put('/calendario-connessioni/{id}', [App\Http\Controllers\CalendarioConne
Route::delete('/calendario-connessioni/{id}', [App\Http\Controllers\CalendarioConnessioneController::class, 'destroy'])->middleware('auth')->name('calendario-connessioni.destroy'); Route::delete('/calendario-connessioni/{id}', [App\Http\Controllers\CalendarioConnessioneController::class, 'destroy'])->middleware('auth')->name('calendario-connessioni.destroy');
Route::post('/calendario-connessioni/{id}/test', [App\Http\Controllers\CalendarioConnessioneController::class, 'test'])->middleware('auth')->name('calendario-connessioni.test'); Route::post('/calendario-connessioni/{id}/test', [App\Http\Controllers\CalendarioConnessioneController::class, 'test'])->middleware('auth')->name('calendario-connessioni.test');
Route::post('/calendario-connessioni/{id}/sync', [App\Http\Controllers\CalendarioConnessioneController::class, 'sync'])->middleware('auth')->name('calendario-connessioni.sync'); Route::post('/calendario-connessioni/{id}/sync', [App\Http\Controllers\CalendarioConnessioneController::class, 'sync'])->middleware('auth')->name('calendario-connessioni.sync');
Route::post('/calendario-connessioni/sync-all', [App\Http\Controllers\CalendarioConnessioneController::class, 'syncAll'])->middleware('auth')->name('calendario-connessioni.sync-all');
Route::post('/calendario-connessioni/reorder', [App\Http\Controllers\CalendarioConnessioneController::class, 'reorder'])->middleware('auth')->name('calendario-connessioni.reorder'); Route::post('/calendario-connessioni/reorder', [App\Http\Controllers\CalendarioConnessioneController::class, 'reorder'])->middleware('auth')->name('calendario-connessioni.reorder');
// Storage Repositories (Opzione B) // Storage Repositories (Opzione B)
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
@@ -1,5 +0,0 @@
<?php $__env->startSection('title', __('Not Found')); ?>
<?php $__env->startSection('code', '404'); ?>
<?php $__env->startSection('message', __('Not Found')); ?>
<?php echo $__env->make('errors::minimal', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php ENDPATH**/ ?>
@@ -1,350 +0,0 @@
<?php $__env->startSection('title', 'Email - ' . ucfirst($folder)); ?>
<?php $__env->startSection('page_title', 'Email'); ?>
<?php
$currentFolder = $folders->firstWhere('type', $folder);
?>
<?php $__env->startSection('content'); ?>
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-envelope mr-2"></i>Email</h3>
<div class="card-tools">
<div class="btn-group mr-2">
<?php $__currentLoopData = $folders; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $f): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<a href="<?php echo e(route('email.index', $f->type)); ?>"
class="btn btn-xs <?php echo e($folder === $f->type ? 'btn-primary' : 'btn-default'); ?>">
<i class="fas <?php echo e($f->icon); ?>"></i>
<?php echo e($f->name); ?>
<?php if($f->type === 'inbox' && $messages->where('is_read', false)->count() > 0): ?>
<span class="badge badge-light"><?php echo e($messages->where('is_read', false)->count()); ?></span>
<?php endif; ?>
</a>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</div>
<a href="<?php echo e(route('email.compose')); ?>" class="btn btn-success btn-xs">
<i class="fas fa-plus"></i> Nuova Email
</a>
<button class="btn btn-primary btn-xs" onclick="syncEmails()" id="syncBtn">
<i class="fas fa-sync"></i> Ricevi/Invia
</button>
</div>
</div>
<div class="card-body p-0">
<div class="card-header pb-0">
<div class="row align-items-center">
<div class="col-md-6">
<div class="form-inline">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="selectAll" onchange="toggleAll(this)">
<label class="custom-control-label" for="selectAll">Seleziona tutto</label>
</div>
<span class="ml-3 text-muted" id="selectedCount">0 selezionati</span>
</div>
</div>
<div class="col-md-6">
<div class="input-group input-group-sm float-right" style="width: 220px;">
<select class="form-control" id="bulkAction">
<option value="">Azioni di massa...</option>
<?php if($folder !== 'trash'): ?>
<option value="mark_read">Marca come letto</option>
<option value="mark_unread">Marca come non letto</option>
<option value="star">Aggiungi a preferiti</option>
<option value="unstar">Rimuovi da preferiti</option>
<option value="move_trash">Sposta nel cestino</option>
<?php else: ?>
<option value="empty_trash">Svuota cestino</option>
<?php endif; ?>
</select>
<div class="input-group-append">
<button class="btn btn-secondary" type="button" onclick="executeBulkAction()">Applica</button>
</div>
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th style="width: 30px;"></th>
<th style="width: 30px;"></th>
<th style="width: 150px;">
<?php $sort = request('sort'); $dir = request('direction'); ?>
<?php if($folder === 'sent' || $folder === 'inviate'): ?>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'to_email', 'direction' => $sort === 'to_email' && $dir === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark text-decoration-none">
Destinatario <?php echo $sort === 'to_email' ? ($dir === 'asc' ? '▲' : '▼') : ''; ?>
</a>
<?php else: ?>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'from_email', 'direction' => $sort === 'from_email' && $dir === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark text-decoration-none">
Mittente <?php echo $sort === 'from_email' ? ($dir === 'asc' ? '▲' : '▼') : ''; ?>
</a>
<?php endif; ?>
</th>
<th>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'subject', 'direction' => $sort === 'subject' && $dir === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark text-decoration-none">
Oggetto <?php echo $sort === 'subject' ? ($dir === 'asc' ? '▲' : '▼') : ''; ?>
</a>
</th>
<th style="width: 120px;">
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'received_at', 'direction' => $sort === 'received_at' && $dir === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark text-decoration-none">
Data <?php echo $sort === 'received_at' ? ($dir === 'asc' ? '▲' : '▼') : ''; ?>
</a>
</th>
</tr>
</thead>
<tbody>
<?php $__empty_1 = true; $__currentLoopData = $messages; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $message): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<tr class="<?php echo e($message->is_read ? '' : 'font-weight-bold'); ?>">
<td style="width: 30px;" onclick="event.stopPropagation()">
<input type="checkbox" class="email-checkbox" value="<?php echo e($message->id); ?>" onchange="updateCount()">
</td>
<td style="width: 30px;" onclick="event.stopPropagation(); window.location='<?php echo e(route('email.show', [$folder, $message->id])); ?>'">
<?php if($message->is_starred): ?>
<i class="fas fa-star text-warning"></i>
<?php endif; ?>
</td>
<td onclick="window.location='<?php echo e(route('email.show', [$folder, $message->id])); ?>'">
<?php echo e($message->is_sent ? 'A: ' . $message->to_email : ($message->from_name ?: $message->from_email)); ?>
</td>
<td onclick="window.location='<?php echo e(route('email.show', [$folder, $message->id])); ?>'">
<?php echo e($message->subject ?? '(senza oggetto)'); ?>
</td>
<td>
<?php if($message->received_at): ?>
<?php echo e($message->received_at->format('d/m/Y')); ?>
<?php elseif($message->sent_at): ?>
<?php echo e($message->sent_at->format('d/m/Y')); ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<tr>
<td colspan="5" class="text-center text-muted py-4">
<i class="fas fa-inbox fa-2x mb-2"></i>
<p>Nessuna email</p>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<?php echo e($messages->withQueryString()->links('vendor.pagination.simple-bootstrap-4')); ?>
</div>
</div>
<?php $__env->stopSection(); ?>
<?php $__env->startPush('styles'); ?>
<style>
/* Bootstrap 4 Pagination override */
.pagination {
display: flex !important;
padding-left: 0;
list-style: none;
border-radius: 0.25rem;
margin: 0;
justify-content: center;
}
.page-item {
display: inline-block;
}
.page-link {
position: relative;
display: block;
padding: 0.5rem 0.75rem;
margin-left: -1px;
line-height: 1.25;
color: #007bff;
background-color: #fff;
border: 1px solid #dee2e6;
text-decoration: none;
font-size: 0.875rem;
}
.page-item.active .page-link {
z-index: 3;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.page-item.disabled .page-link {
color: #6c757d;
pointer-events: none;
background-color: #fff;
border-color: #dee2e6;
}
.page-link:hover {
color: #0056b3;
background-color: #e9ecef;
border-color: #dee2e6;
}
/* Icons as text characters */
.pagination .page-link::before,
.pagination .page-link::after {
display: none;
}
</style>
<?php $__env->stopPush(); ?>
<?php if($syncInterval > 0 && $folder === 'inbox'): ?>
<?php $__env->startPush('scripts'); ?>
<script>
let syncInterval = <?php echo e($syncInterval * 60 * 1000); ?>;
let syncTimeout;
function autoSync() {
fetch('<?php echo e(url('email/sync/quick')); ?>', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>',
'Content-Type': 'application/json'
}
}).then(() => {
console.log('Auto sync completed');
}).catch(() => {
console.log('Auto sync failed');
});
}
function startAutoSync() {
if (syncInterval > 0) {
syncTimeout = setInterval(autoSync, syncInterval);
}
}
function stopAutoSync() {
if (syncTimeout) {
clearInterval(syncTimeout);
}
}
document.addEventListener('DOMContentLoaded', function() {
startAutoSync();
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
stopAutoSync();
} else {
startAutoSync();
}
});
});
</script>
<?php $__env->stopPush(); ?>
<?php endif; ?>
<?php $__env->startSection('scripts'); ?>
<script>
function toggleAll(checkbox) {
const checkboxes = document.querySelectorAll('.email-checkbox');
checkboxes.forEach(function(cb) {
cb.checked = checkbox.checked;
});
updateCount();
}
function updateCount() {
const checkboxes = document.querySelectorAll('.email-checkbox:checked');
document.getElementById('selectedCount').textContent = checkboxes.length + ' selezionati';
}
function executeBulkAction() {
const select = document.getElementById('bulkAction');
const action = select.value;
if (!action) {
alert('Seleziona un\'azione');
return;
}
if (action === 'empty_trash') {
if (!confirm('Sei sicuro di voler svuotare il cestino? Questa azione non è reversibile.')) {
return;
}
}
const checkboxes = document.querySelectorAll('.email-checkbox:checked');
if (action !== 'empty_trash' && checkboxes.length === 0) {
alert('Seleziona almeno una email');
return;
}
let ids = [];
if (action !== 'empty_trash') {
ids = Array.from(checkboxes).map(function(cb) {
return cb.value;
});
}
fetch('<?php echo e(route('email.bulk')); ?>', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>',
'Content-Type': 'application/json'
},
body: JSON.stringify({ ids: ids, action: action })
}).then(function(response) {
return response.json();
}).then(function(data) {
if (data.success) {
// Reset checkboxes before reload to avoid ghost selection
document.querySelectorAll('.email-checkbox').forEach(function(cb) {
cb.checked = false;
});
document.getElementById('selectAll').checked = false;
updateCount();
window.location.href = window.location.href;
} else {
alert(data.message || 'Errore');
}
}).catch(function() {
alert('Errore');
});
}
function syncEmails() {
const btn = document.getElementById('syncBtn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sincronizzazione...';
fetch('<?php echo e(url('email/sync/quick')); ?>', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>',
'Content-Type': 'application/json'
}
}).then(function(response) {
return response.json();
}).then(function(data) {
if (data.success) {
location.reload();
} else {
alert(data.message || 'Errore durante la sincronizzazione');
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-sync"></i> Ricevi/Invia';
}
}).catch(function(error) {
alert('Errore di connessione');
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-sync"></i> Ricevi/Invia';
});
}
</script>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/email/index.blade.php ENDPATH**/ ?>
@@ -1,123 +0,0 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo $__env->yieldContent('title', 'Admin'); ?> - Glastree</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<?php echo $__env->yieldContent('styles'); ?>
</head>
<body class="hold-transition sidebar-mini layout-fixed">
<div class="wrapper">
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#"><i class="fas fa-bars"></i></a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="<?php echo e(route('dashboard')); ?>">
<i class="fas fa-home"></i> Torna al Sito
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#">
<i class="fas fa-user"></i> <?php echo e(Auth::user()->name); ?>
</a>
<div class="dropdown-menu dropdown-menu-right">
<form method="POST" action="<?php echo e(route('logout')); ?>">
<?php echo csrf_field(); ?>
<button type="submit" class="dropdown-item">
<i class="fas fa-sign-out-alt mr-2"></i> Esci
</button>
</form>
</div>
</li>
</ul>
</nav>
<aside class="main-sidebar sidebar-dark-primary elevation-4">
<a href="<?php echo e(route('dashboard')); ?>" class="brand-link">
<i class="fas fa-shield-alt brand-image ml-3 mr-2"></i>
<span class="brand-text font-weight-light">Admin</span>
</a>
<div class="sidebar">
<nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column">
<li class="nav-item">
<a href="/admin/utenti" class="nav-link <?php echo e(request()->is('admin/utenti*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-users"></i>
<p>Utenti</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/ruoli" class="nav-link <?php echo e(request()->is('admin/ruoli*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-user-tag"></i>
<p>Ruoli</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/backup" class="nav-link <?php echo e(request()->is('admin/backup*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-hdd"></i>
<p>Backup</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/activity-logs" class="nav-link <?php echo e(request()->is('admin/activity-logs*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-history"></i>
<p>Log Attività</p>
</a>
</li>
</ul>
</nav>
</div>
</aside>
<div class="content-wrapper">
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0"><?php echo $__env->yieldContent('page_title', 'Admin'); ?></h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="<?php echo e(route('dashboard')); ?>">Dashboard</a></li>
<?php echo $__env->yieldContent('breadcrumbs'); ?>
</ol>
</div>
</div>
</div>
</div>
<div class="content">
<div class="container-fluid">
<?php if(session('success')): ?>
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
<?php endif; ?>
<?php if(session('error')): ?>
<div class="alert alert-danger"><?php echo e(session('error')); ?></div>
<?php endif; ?>
<?php echo $__env->yieldContent('content'); ?>
</div>
</div>
</div>
<footer class="main-footer">
<strong><?php echo e(\App\Models\AppSetting::getFooterText()); ?></strong>
<?php if(\App\Models\AppSetting::shouldShowVersion() && \App\Models\AppSetting::getAppVersion()): ?>
<span class="ml-2">v<?php echo e(\App\Models\AppSetting::getAppVersion()); ?></span>
<?php endif; ?>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
<?php echo $__env->yieldContent('scripts'); ?>
</body>
</html><?php /**PATH /var/www/html/glastree/resources/views/admin/layout.blade.php ENDPATH**/ ?>
@@ -1,44 +0,0 @@
<?php if($paginator->hasPages()): ?>
<ul class="pagination justify-content-center" role="navigation">
<?php if($paginator->onFirstPage()): ?>
<li class="page-item disabled" aria-disabled="true" aria-label="<?php echo app('translator')->get('pagination.previous'); ?>">
<span class="page-link" aria-hidden="true">&lsaquo;</span>
</li>
<?php else: ?>
<li class="page-item">
<a class="page-link" href="<?php echo e($paginator->previousPageUrl()); ?>" rel="prev" aria-label="<?php echo app('translator')->get('pagination.previous'); ?>">&lsaquo;</a>
</li>
<?php endif; ?>
<?php $__currentLoopData = $elements; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $element): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<?php if(is_string($element)): ?>
<li class="page-item disabled" aria-disabled="true"><span class="page-link"><?php echo e($element); ?></span></li>
<?php endif; ?>
<?php if(is_array($element)): ?>
<?php $__currentLoopData = $element; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $page => $url): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<?php if($page == $paginator->currentPage()): ?>
<li class="page-item active" aria-current="page"><span class="page-link"><?php echo e($page); ?></span></li>
<?php elseif($page >= $paginator->currentPage() - 2 && $page <= $paginator->currentPage() + 2): ?>
<li class="page-item"><a class="page-link" href="<?php echo e($url); ?>"><?php echo e($page); ?></a></li>
<?php endif; ?>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
<?php endif; ?>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
<?php if($paginator->hasMorePages()): ?>
<li class="page-item">
<a class="page-link" href="<?php echo e($paginator->nextPageUrl()); ?>" rel="next" aria-label="<?php echo app('translator')->get('pagination.next'); ?>">&rsaquo;</a>
</li>
<?php else: ?>
<li class="page-item disabled" aria-disabled="true" aria-label="<?php echo app('translator')->get('pagination.next'); ?>">
<span class="page-link" aria-hidden="true">&rsaquo;</span>
</li>
<?php endif; ?>
</ul>
<?php endif; ?><?php /**PATH /var/www/html/glastree/resources/views/vendor/pagination/simple-bootstrap-4.blade.php ENDPATH**/ ?>
@@ -1,248 +0,0 @@
<?php $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree'; ?>
<?php $__env->startSection('title', 'Backup - ' . $appName); ?>
<?php $__env->startSection('page_title', 'Backup e Migrazione'); ?>
<?php $__env->startSection('breadcrumbs'); ?>
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="/admin/utenti">Admin</a></li>
<li class="breadcrumb-item active">Backup</li>
<?php $__env->stopSection(); ?>
<?php $__env->startSection('content'); ?>
<div class="row">
<div class="col-md-8">
<div class="card card-outline card-primary">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-hdd mr-2"></i>Backup Disponibili</h3>
<div class="card-tools">
<form method="POST" action="<?php echo e(route('admin.backup.run')); ?>" style="display:inline">
<?php echo csrf_field(); ?>
<button type="submit" class="btn btn-primary btn-sm">
<i class="fas fa-play mr-1"></i> Esegui Backup Ora
</button>
</form>
</div>
</div>
<div class="card-body p-0">
<?php if(count($backups) > 0): ?>
<div class="table-responsive">
<table class="table table-hover table-striped mb-0">
<thead>
<tr>
<th>File</th>
<th>Data</th>
<th>Dimensione</th>
<th>Contenuto</th>
<th class="text-center">Azioni</th>
</tr>
</thead>
<tbody>
<?php $__currentLoopData = $backups; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $backup): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<tr>
<td><code><?php echo e($backup['filename']); ?></code></td>
<td><?php echo e(date('d/m/Y H:i', $backup['last_modified'])); ?></td>
<td><?php echo e($backup['size_formatted']); ?></td>
<td>
<?php if($backup['manifest']): ?>
<span class="badge badge-info" title="PHP <?php echo e($backup['manifest']['php_version'] ?? '?'); ?>, Laravel <?php echo e($backup['manifest']['laravel_version'] ?? '?'); ?>">
<i class="fas fa-database mr-1"></i>DB
</span>
<?php if(isset($backup['manifest']['db_name'])): ?>
<small class="text-muted"><?php echo e($backup['manifest']['db_name']); ?></small>
<?php endif; ?>
<?php else: ?>
<span class="badge badge-secondary">N/A</span>
<?php endif; ?>
</td>
<td class="text-center">
<a href="<?php echo e(route('admin.backup.download', $backup['filename'])); ?>" class="btn btn-success btn-sm" title="Scarica">
<i class="fas fa-download"></i>
</a>
<form method="POST" action="<?php echo e(route('admin.backup.destroy', $backup['filename'])); ?>" style="display:inline" onsubmit="return confirm('Eliminare definitivamente questo backup?')">
<?php echo csrf_field(); ?>
<?php echo method_field('DELETE'); ?>
<button type="submit" class="btn btn-danger btn-sm" title="Elimina">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="text-center py-5">
<i class="fas fa-hdd fa-3x text-muted mb-3"></i>
<p class="text-muted mb-0">Nessun backup disponibile.</p>
<p class="text-muted">Clicca "Esegui Backup Ora" per creare il primo backup.</p>
</div>
<?php endif; ?>
</div>
</div>
<div class="card card-outline card-info">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-info-circle mr-2"></i>Cosa Include il Backup</h3>
</div>
<div class="card-body p-0">
<table class="table table-bordered mb-0">
<thead>
<tr>
<th>Componente</th>
<th>Incluso</th>
<th>Dettagli</th>
</tr>
</thead>
<tbody>
<tr>
<td><i class="fas fa-database text-primary mr-2"></i> Database (MySQL)</td>
<td><span class="badge badge-success">Sempre</span></td>
<td>Struttura, dati, stored procedure, eventi, trigger</td>
</tr>
<tr>
<td><i class="fas fa-file text-warning mr-2"></i> File caricati</td>
<td>
<?php if($config['backup_include_files']): ?>
<span class="badge badge-success">Sì</span>
<?php else: ?>
<span class="badge badge-secondary">No</span>
<?php endif; ?>
</td>
<td>Documenti, avatar, gallerie — dalla cartella storage</td>
</tr>
<tr>
<td><i class="fas fa-cog text-secondary mr-2"></i> Configurazione (.env)</td>
<td>
<?php if($config['backup_include_env']): ?>
<span class="badge badge-success">Sì</span>
<?php else: ?>
<span class="badge badge-secondary">No</span>
<?php endif; ?>
</td>
<td>APP_KEY, DB credentials, SMTP, OAuth token</td>
</tr>
<tr>
<td><i class="fas fa-tag text-info mr-2"></i> Metadati</td>
<td><span class="badge badge-success">Sempre</span></td>
<td>Manifest JSON con versione app, PHP, Laravel</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card card-outline card-secondary">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-sliders-h mr-2"></i>Configurazione Backup</h3>
</div>
<form method="POST" action="<?php echo e(route('admin.backup.config')); ?>">
<?php echo csrf_field(); ?>
<div class="card-body">
<div class="form-group">
<label for="backup_path">Percorso backup</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><code>storage/app/</code></span>
</div>
<input type="text" name="backup_path" id="backup_path" class="form-control" value="<?php echo e($config['backup_path']); ?>">
</div>
<small class="text-muted">Cartella dove salvare i backup (relativa a storage/app/)</small>
</div>
<div class="form-group">
<label for="backup_retention_days">Ritenzione (giorni)</label>
<input type="number" name="backup_retention_days" id="backup_retention_days" class="form-control" value="<?php echo e($config['backup_retention_days']); ?>" min="1" max="365">
<small class="text-muted">I backup più vecchi vengono eliminati automaticamente</small>
</div>
<div class="form-group mb-0">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="backup_include_files" name="backup_include_files" value="1" <?php echo e($config['backup_include_files'] ? 'checked' : ''); ?>>
<label class="custom-control-label" for="backup_include_files">Includi file caricati</label>
</div>
<div class="custom-control custom-switch mt-2">
<input type="checkbox" class="custom-control-input" id="backup_include_env" name="backup_include_env" value="1" <?php echo e($config['backup_include_env'] ? 'checked' : ''); ?>>
<label class="custom-control-label" for="backup_include_env">Includi .env (configurazione)</label>
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-secondary btn-block">
<i class="fas fa-save mr-1"></i> Salva Configurazione
</button>
</div>
</form>
</div>
<div class="card card-outline card-warning">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-clock mr-2"></i>Backup Automatico</h3>
<div class="card-tools">
<?php $autoEnabled = \App\Models\AppSetting::getSetting('backup_auto_enabled', false) ?>
<form method="POST" action="<?php echo e(route('admin.backup.toggle-auto')); ?>" style="display:inline">
<?php echo csrf_field(); ?>
<button type="submit" class="btn btn-sm <?php echo e($autoEnabled ? 'btn-success' : 'btn-secondary'); ?>">
<i class="fas <?php echo e($autoEnabled ? 'fa-check-circle' : 'fa-times-circle'); ?> mr-1"></i>
<?php echo e($autoEnabled ? 'Attivo' : 'Disattivo'); ?>
</button>
</form>
</div>
</div>
<form method="POST" action="<?php echo e(route('admin.backup.save-auto')); ?>">
<?php echo csrf_field(); ?>
<div class="card-body">
<div class="form-group">
<label for="backup_auto_frequency">Frequenza</label>
<select name="backup_auto_frequency" id="backup_auto_frequency" class="form-control">
<option value="daily" <?php echo e(\App\Models\AppSetting::getSetting('backup_auto_frequency', 'daily') === 'daily' ? 'selected' : ''); ?>>Giornaliero</option>
<option value="weekly" <?php echo e(\App\Models\AppSetting::getSetting('backup_auto_frequency', 'daily') === 'weekly' ? 'selected' : ''); ?>>Settimanale</option>
<option value="monthly" <?php echo e(\App\Models\AppSetting::getSetting('backup_auto_frequency', 'daily') === 'monthly' ? 'selected' : ''); ?>>Mensile</option>
</select>
</div>
<div class="form-group mb-0">
<label for="backup_auto_hour">Ora di esecuzione</label>
<select name="backup_auto_hour" id="backup_auto_hour" class="form-control">
<?php for($h = 0; $h <= 23; $h++): ?>
<option value="<?php echo e($h); ?>" <?php echo e(\App\Models\AppSetting::getSetting('backup_auto_hour', 3) == $h ? 'selected' : ''); ?>><?php echo e(sprintf('%02d:00', $h)); ?></option>
<?php endfor; ?>
</select>
<small class="text-muted">Ora del giorno (formato 24h). Consigliato: 03:00 (notte)</small>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-warning btn-block">
<i class="fas fa-save mr-1"></i> Salva Automatico
</button>
</div>
</form>
</div>
<div class="card card-outline card-danger">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-truck mr-2"></i>Migrazione Server</h3>
</div>
<div class="card-body">
<p class="text-muted">Scarica il backup, poi esegui lo script di setup sul nuovo server per una migrazione completa.</p>
<a href="<?php echo e(route('help')); ?>#help-backup" class="btn btn-danger btn-block" target="_blank">
<i class="fas fa-book mr-1"></i> Guida alla Migrazione
</a>
</div>
</div>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle mr-2"></i>
<strong>Importante:</strong> Per il backup automatico, assicurati che il cron di Laravel sia configurato sul server:
<code class="ml-2">* * * * * cd <?php echo e(base_path()); ?> && php artisan schedule:run >> /dev/null 2>&1</code>
</div>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('admin.layout', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/admin/backup/index.blade.php ENDPATH**/ ?>
@@ -1,5 +0,0 @@
<?php $__env->startSection('title', __('Page Expired')); ?>
<?php $__env->startSection('code', '419'); ?>
<?php $__env->startSection('message', __('Page Expired')); ?>
<?php echo $__env->make('errors::minimal', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/419.blade.php ENDPATH**/ ?>
File diff suppressed because it is too large Load Diff
@@ -1,723 +0,0 @@
<?php $__env->startSection('title', 'Impostazioni Email'); ?>
<?php $__env->startSection('page_title', 'Configurazione Email'); ?>
<?php $__env->startSection('breadcrumbs'); ?>
<li class="breadcrumb-item"><a href="<?php echo e(route('dashboard')); ?>">Dashboard</a></li>
<li class="breadcrumb-item"><a href="/impostazioni">Impostazioni</a></li>
<li class="breadcrumb-item active">Email Settings</li>
<?php $__env->stopSection(); ?>
<?php $__env->startSection('content'); ?>
<div class="row">
<div class="col-md-3">
<div class="card card-secondary">
<div class="card-header">
<h3 class="card-title">Sezioni</h3>
</div>
<div class="list-group list-group-flush">
<a href="#imap" class="list-group-item list-group-item-action active" data-toggle="tab">
<i class="nav-icon fas fa-server"></i> Server IMAP
</a>
<a href="#smtp" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fas fa-paper-plane"></i> Server SMTP (Invio)
</a>
<a href="#account" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fas fa-user"></i> Account
</a>
<a href="#sync" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fas fa-sync"></i> Sincronizzazione
</a>
<a href="#signature" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fas fa-signature"></i> Firma
</a>
<a href="#mittenti" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fas fa-user-plus"></i> Mittenti Aggiuntivi
</a>
</div>
</div>
</div>
<div class="col-md-9">
<form method="POST" action="<?php echo e(route('impostazioni.email.save')); ?>">
<?php echo csrf_field(); ?>
<input type="hidden" name="id" value="<?php echo e($settings->id ?? 1); ?>">
<div class="tab-content">
<div class="tab-pane active" id="imap">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Configurazione Server IMAP</h3>
</div>
<div class="card-body">
<?php if(session('success')): ?>
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
<?php endif; ?>
<?php if(session('error')): ?>
<div class="alert alert-danger"><?php echo e(session('error')); ?></div>
<?php endif; ?>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="imap_host">Host IMAP</label>
<input type="text" name="imap_host" id="imap_host" class="form-control"
value="<?php echo e($settings->imap_host ?? 'imap.gmail.com'); ?>"
placeholder="es. imap.gmail.com">
<small class="text-muted">Gmail: imap.gmail.com | Outlook: outlook.office365.com</small>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="imap_port">Porta</label>
<input type="number" name="imap_port" id="imap_port" class="form-control"
value="<?php echo e($settings->imap_port ?? 993); ?>">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="imap_encryption">Crittografia</label>
<select name="imap_encryption" id="imap_encryption" class="form-control">
<option value="ssl" <?php echo e(($settings->imap_encryption ?? 'ssl') == 'ssl' ? 'selected' : ''); ?>>SSL</option>
<option value="tls" <?php echo e(($settings->imap_encryption ?? '') == 'tls' ? 'selected' : ''); ?>>TLS</option>
<option value="none" <?php echo e(($settings->imap_encryption ?? '') == 'none' ? 'selected' : ''); ?>>Nessuna</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="smtp">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Configurazione Server SMTP (per invio email)</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>Nota:</strong> Per inviare email devi configurare il server SMTP.
Per Gmail, usa <code>smtp.gmail.com</code> sulla porta <code>587</code> con TLS
e genera una <a href="https://support.google.com/accounts/answer/185833" target="_blank">App Password</a>.
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="smtp_host">Host SMTP</label>
<input type="text" name="smtp_host" id="smtp_host" class="form-control"
value="<?php echo e($settings->smtp_host ?? 'smtp.gmail.com'); ?>"
placeholder="es. smtp.gmail.com">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="smtp_port">Porta</label>
<input type="number" name="smtp_port" id="smtp_port" class="form-control"
value="<?php echo e($settings->smtp_port ?? 587); ?>">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="smtp_encryption">Crittografia</label>
<select name="smtp_encryption" id="smtp_encryption" class="form-control">
<option value="tls" <?php echo e(($settings->smtp_encryption ?? 'tls') == 'tls' ? 'selected' : ''); ?>>TLS</option>
<option value="ssl" <?php echo e(($settings->smtp_encryption ?? '') == 'ssl' ? 'selected' : ''); ?>>SSL</option>
<option value="none" <?php echo e(($settings->smtp_encryption ?? '') == 'none' ? 'selected' : ''); ?>>Nessuna</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="smtp_username">Username SMTP</label>
<input type="text" name="smtp_username" id="smtp_username" class="form-control"
value="<?php echo e($settings->smtp_username ?? ''); ?>"
placeholder="lascia vuoto per usare l'account IMAP">
<small class="text-muted">Generalmente uguale all'account email</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="smtp_password">Password SMTP / App Password</label>
<input type="password" name="smtp_password" id="smtp_password" class="form-control"
placeholder="<?php echo e($settings->smtp_password ? '••••••••' : ''); ?>">
<small class="text-muted">Lascia vuoto per usare la password IMAP</small>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="account">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Credenziali Account</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="imap_username">Username/Email</label>
<input type="text" name="imap_username" id="imap_username" class="form-control"
value="<?php echo e($settings->imap_username ?? ''); ?>"
placeholder="es. tuo@gmail.com">
<small class="text-muted">Per Gmail usa l'indirizzo completo</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="imap_password">Password / App Password</label>
<input type="password" name="imap_password" id="imap_password" class="form-control"
placeholder="">
<small class="text-muted">
<a href="https://support.google.com/accounts/answer/185833" target="_blank">
Per Gmail: genera una App Password
</a>
</small>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="email_address">Email Indirizzo</label>
<input type="email" name="email_address" id="email_address" class="form-control"
value="<?php echo e($settings->email_address ?? ''); ?>">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="email_name">Nome Visualizzato</label>
<input type="text" name="email_name" id="email_name" class="form-control"
value="<?php echo e($settings->email_name ?? ''); ?>"
placeholder="es. Nome Cognome">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="reply_to">Email Risposta (Reply-To)</label>
<input type="email" name="reply_to" id="reply_to" class="form-control"
value="<?php echo e($settings->reply_to ?? ''); ?>">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="sync">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Sincronizzazione</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="sync_interval_minutes">Intervallo Sync (minuti)</label>
<select name="sync_interval_minutes" id="sync_interval_minutes" class="form-control">
<option value="1" <?php echo e(($settings->sync_interval_minutes ?? 5) == 1 ? 'selected' : ''); ?>>1 minuto</option>
<option value="5" <?php echo e(($settings->sync_interval_minutes ?? 5) == 5 ? 'selected' : ''); ?>>5 minuti</option>
<option value="15" <?php echo e(($settings->sync_interval_minutes ?? 5) == 15 ? 'selected' : ''); ?>>15 minuti</option>
<option value="30" <?php echo e(($settings->sync_interval_minutes ?? 5) == 30 ? 'selected' : ''); ?>>30 minuti</option>
<option value="60" <?php echo e(($settings->sync_interval_minutes ?? 5) == 60 ? 'selected' : ''); ?>>1 ora</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Stato Connessione</label>
<div class="mt-2">
<?php if($settings->last_sync_at): ?>
<span class="badge badge-success">
<i class="fas fa-check-circle"></i> Ultimo sync: <?php echo e($settings->last_sync_at->format('d/m/Y H:i')); ?>
</span>
<?php else: ?>
<span class="badge badge-secondary">
<i class="fas fa-clock"></i> Mai sincronizzato
</span>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="is_active"
name="is_active" value="1" <?php echo e(($settings->is_active ?? false) ? 'checked' : ''); ?>>
<label class="custom-control-label" for="is_active">Account Email Attivo</label>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="signature">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Firma Email</h3>
</div>
<div class="card-body">
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="signature_enabled"
name="signature_enabled" value="1" <?php echo e(($settings->signature_enabled ?? true) ? 'checked' : ''); ?>>
<label class="custom-control-label" for="signature_enabled">Attiva firma automatica</label>
</div>
<small class="text-muted">Quando attivo, la firma verrà aggiunta in fondo a ogni email inviata</small>
</div>
<hr>
<div class="form-group">
<label for="signature">Testo Firma (formato HTML)</label>
<div id="signature-editor" style="height: 200px;"></div>
<input type="hidden" name="signature" id="signature_input" value="<?php echo e($settings->signature ?? ''); ?>">
<small class="text-muted">Usa il riquadro sopra per comporre la tua firma. HTML supportato.</small>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="mittenti">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Mittenti Aggiuntivi</h3>
<div class="card-tools">
<button type="button" class="btn btn-sm btn-success" data-toggle="modal" data-target="#senderModal" onclick="resetSenderForm()">
<i class="fas fa-plus mr-1"></i> Nuovo Mittente
</button>
</div>
</div>
<div class="card-body">
<?php if(session('success')): ?>
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
<?php endif; ?>
<?php if(session('error')): ?>
<div class="alert alert-danger"><?php echo e(session('error')); ?></div>
<?php endif; ?>
<?php if($senderAccounts->count() > 0): ?>
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm">
<thead>
<tr>
<th>Email</th>
<th>Nome</th>
<th>SMTP</th>
<th>Reply-To</th>
<th>Verify</th>
<th>Stato</th>
<th style="width: 160px;">Azioni</th>
</tr>
</thead>
<tbody>
<?php $__currentLoopData = $senderAccounts; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $sender): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<tr>
<td><strong><?php echo e($sender->email_address); ?></strong></td>
<td><?php echo e($sender->email_name ?: '-'); ?></td>
<td><small><?php echo e($sender->smtp_host ?: '-'); ?>:<?php echo e($sender->smtp_port ?: '-'); ?></small></td>
<td><small class="text-muted"><?php echo e($sender->reply_to ?: '-'); ?></small></td>
<td><small class="text-muted"><?php echo e($sender->verify_email ?: '-'); ?></small></td>
<td>
<?php if($sender->is_active): ?>
<span class="badge badge-success">Attivo</span>
<?php else: ?>
<span class="badge badge-secondary">Disattivo</span>
<?php endif; ?>
</td>
<td>
<button type="button" class="btn btn-xs btn-info" onclick="editSender(<?php echo e($sender->id); ?>)" title="Modifica">
<i class="fas fa-edit"></i>
</button>
<button type="button" class="btn btn-xs btn-success" onclick="testSenderSmtp(<?php echo e($sender->id); ?>)" title="Test SMTP">
<i class="fas fa-paper-plane"></i>
</button>
<form action="<?php echo e(route('impostazioni.sender.destroy', $sender->id)); ?>" method="POST" class="d-inline" onsubmit="return confirm('Eliminare il mittente <?php echo e($sender->email_address); ?>?')">
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
<button type="submit" class="btn btn-xs btn-danger" title="Elimina">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="text-center text-muted py-4">
<i class="fas fa-user-plus fa-2x mb-2"></i>
<p class="mb-0">Nessun mittente aggiuntivo configurato.</p>
<p class="mb-0">Usa il pulsante "Nuovo Mittente" per aggiungere un account di invio dedicato (es. Newsletter).</p>
</div>
<?php endif; ?>
</div>
</div>
<?php if($senderAccounts->count() > 0): ?>
<div class="card card-secondary">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-info-circle mr-2"></i>Come funziona</h3>
</div>
<div class="card-body">
<ul class="mb-0">
<li>I mittenti aggiuntivi sono account <strong>solo invio</strong> (SMTP) senza IMAP.</li>
<li>Puoi selezionarli nel <strong>compose email</strong> o negli <strong>invii mailing</strong>.</li>
<li>Il campo <strong>Reply-To</strong> imposta l'indirizzo di risposta (es. <code>no-reply@parrocchia.it</code>).</li>
<li>Il campo <strong>Verify Email</strong> riceve un report di riepilogo dopo ogni invio massivo con l'esito (ok/falliti).</li>
<li>Se Reply-To non è impostato, viene usato Verify Email come fallback.</li>
</ul>
</div>
</div>
<?php endif; ?>
</div>
</div>
<div class="card-footer d-flex flex-wrap align-items-center gap-2">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save mr-1"></i> Salva Impostazioni
</button>
<button type="button" class="btn btn-info ml-2" onclick="testConnection()">
<i class="fas fa-plug mr-1"></i> Test Connessione IMAP
</button>
<button type="button" class="btn btn-success ml-2" onclick="testSmtp()">
<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)
</button>
<a href="<?php echo e(route('impostazioni.email.sync')); ?>" class="btn btn-warning ml-2">
<i class="fas fa-sync mr-1"></i> Sincronizza Ora
</a>
<form action="<?php echo e(route('impostazioni.email.destroy')); ?>" method="POST" class="ml-auto" onsubmit="return confirm('Eliminare definitivamente la configurazione email? Tutti i dati di connessione verranno rimossi.')">
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash mr-1"></i> Elimina Configurazione
</button>
</form>
</div>
</form>
</div>
</div>
<?php $__env->stopSection(); ?>
<div class="modal fade" id="senderModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form id="senderForm" method="POST" action="<?php echo e(route('impostazioni.sender.store')); ?>">
<?php echo csrf_field(); ?>
<input type="hidden" name="_method" id="senderMethod" value="POST">
<input type="hidden" name="id" id="senderId" value="">
<div class="modal-header bg-primary">
<h5 class="modal-title" id="senderModalTitle"><i class="fas fa-user-plus mr-2"></i>Nuovo Mittente</h5>
<button type="button" class="close text-white" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="sender_email">Email *</label>
<input type="email" name="email_address" id="sender_email" class="form-control" required placeholder="newsletter@parrocchia.it">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="sender_name">Nome visualizzato</label>
<input type="text" name="email_name" id="sender_name" class="form-control" placeholder="Newsletter Parrocchiale">
</div>
</div>
</div>
<hr>
<h6>Configurazione SMTP</h6>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="sender_smtp_host">Host SMTP</label>
<input type="text" name="smtp_host" id="sender_smtp_host" class="form-control" placeholder="smtp.gmail.com">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="sender_smtp_port">Porta</label>
<input type="number" name="smtp_port" id="sender_smtp_port" class="form-control" value="587">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="sender_smtp_encryption">Crittografia</label>
<select name="smtp_encryption" id="sender_smtp_encryption" class="form-control">
<option value="tls">TLS</option>
<option value="ssl">SSL</option>
<option value="none">Nessuna</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="sender_smtp_username">Username SMTP</label>
<input type="text" name="smtp_username" id="sender_smtp_username" class="form-control" placeholder="Lascia vuoto per usare l'email">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="sender_smtp_password">Password SMTP</label>
<input type="password" name="smtp_password" id="sender_smtp_password" class="form-control" placeholder="......">
<small class="text-muted">Lascia vuoto per non cambiare</small>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="sender_reply_to">Reply-To <small class="text-muted">(no-reply)</small></label>
<input type="email" name="reply_to" id="sender_reply_to" class="form-control" placeholder="no-reply@parrocchia.it">
<small class="text-muted">Indirizzo di risposta per le email inviate</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="sender_verify_email">Verify Email <small class="text-muted">(report)</small></label>
<input type="email" name="verify_email" id="sender_verify_email" class="form-control" placeholder="verifica@parrocchia.it">
<small class="text-muted">Riceve report riepilogo dopo invii massivi</small>
</div>
</div>
</div>
<div class="form-group">
<label for="sender_note">Nota interna</label>
<textarea name="note" id="sender_note" class="form-control" rows="2" placeholder="Es. Newsletter settimanale"></textarea>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="sender_is_active" name="is_active" value="1" checked>
<label class="custom-control-label" for="sender_is_active">Mittente attivo</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save mr-1"></i> Salva Mittente
</button>
</div>
</form>
</div>
</div>
</div>
<?php $__env->startSection('scripts'); ?>
<script>
let senderAccounts = <?php echo json_encode($senderAccounts, 15, 512) ?>;
function resetSenderForm() {
document.getElementById('senderForm').action = '<?php echo e(route('impostazioni.sender.store')); ?>';
document.getElementById('senderMethod').value = 'POST';
document.getElementById('senderModalTitle').textContent = 'Nuovo Mittente';
document.getElementById('senderForm').reset();
document.getElementById('senderId').value = '';
document.getElementById('sender_is_active').checked = true;
document.getElementById('sender_smtp_password').placeholder = '';
document.getElementById('sender_smtp_password').required = false;
}
function editSender(id) {
const sender = senderAccounts.find(s => s.id === id);
if (!sender) return;
document.getElementById('senderForm').action = '<?php echo e(route('impostazioni.sender.update', '__ID__')); ?>'.replace('__ID__', id);
document.getElementById('senderMethod').value = 'PUT';
document.getElementById('senderModalTitle').textContent = 'Modifica Mittente - ' + sender.email_address;
document.getElementById('senderId').value = sender.id;
document.getElementById('sender_email').value = sender.email_address;
document.getElementById('sender_name').value = sender.email_name || '';
document.getElementById('sender_smtp_host').value = sender.smtp_host || '';
document.getElementById('sender_smtp_port').value = sender.smtp_port || 587;
document.getElementById('sender_smtp_encryption').value = sender.smtp_encryption || 'tls';
document.getElementById('sender_smtp_username').value = sender.smtp_username || '';
document.getElementById('sender_smtp_password').value = '';
document.getElementById('sender_smtp_password').placeholder = '......';
document.getElementById('sender_smtp_password').required = false;
document.getElementById('sender_reply_to').value = sender.reply_to || '';
document.getElementById('sender_verify_email').value = sender.verify_email || '';
document.getElementById('sender_note').value = sender.note || '';
document.getElementById('sender_is_active').checked = sender.is_active;
$('#senderModal').modal('show');
}
function testSenderSmtp(id) {
const email = prompt('Inserisci l\'indirizzo email a cui inviare il test:');
if (!email || !email.includes('@')) return;
const btn = event.target;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
$.ajax({
url: '<?php echo e(route('impostazioni.sender.test', '__ID__')); ?>'.replace('__ID__', id),
method: 'POST',
headers: { 'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>' },
data: { email: email },
timeout: 30000,
success: function(response) {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-paper-plane"></i>';
alert(response.success ? '✅ ' + response.message : '❌ ' + response.message);
},
error: function(xhr) {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-paper-plane"></i>';
const msg = xhr.responseJSON?.message || 'Errore di connessione';
alert('❌ ' + msg);
}
});
}
function testConnection() {
$.ajax({
url: '<?php echo e(route('impostazioni.email.test')); ?>',
method: 'POST',
headers: { 'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>' },
success: function(response) {
if (response.success) {
alert('✅ ' + response.message);
} else {
alert('❌ ' + response.message);
}
},
error: function() {
alert('❌ Errore nella richiesta');
}
});
}
function testSmtp() {
var email = prompt('Inserisci l\'indirizzo email a cui inviare il test:');
if (!email) return;
if (!email.includes('@')) {
alert('⚠️ Indirizzo email non valido');
return;
}
var btn = event.target;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Invio...';
$.ajax({
url: '<?php echo e(route('impostazioni.email.testSmtp')); ?>',
method: 'POST',
headers: { 'X-CSRF-TOKEN': '<?php echo e(csrf_token()); ?>' },
data: { email: email },
timeout: 30000,
success: function(response) {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)';
if (response.success) {
alert('✅ ' + response.message);
} else {
alert('❌ ' + response.message);
}
},
error: function(xhr, status, error) {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-paper-plane mr-1"></i> Test Invio Email (SMTP)';
if (xhr.responseJSON && xhr.responseJSON.message) {
alert('❌ ' + xhr.responseJSON.message);
} else {
alert('❌ Errore: ' + status + ' - ' + error);
}
}
});
}
var quillEditorInstance = null;
function createEditor() {
var container = document.querySelector('#signature-editor');
if (!container || quillEditorInstance) return;
var quill = new Quill('#signature-editor', {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ 'color': [] }, { 'background': [] }],
[{ 'header': 1 }, { 'header': 2 }, { 'header': 3 }],
[{ 'align': [] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['link', 'image'],
['clean', 'code']
]
}
});
var signatureInput = document.getElementById('signature_input');
var isEditingHtml = false;
quill.getModule('toolbar').addHandler('code', function() {
if (!isEditingHtml) {
var textarea = document.createElement('textarea');
textarea.className = 'ql-editor';
textarea.style.cssText = 'font-family: monospace; min-height: 150px; width: 100%;';
textarea.value = signatureInput.value;
quill.root.innerHTML = '';
quill.root.appendChild(textarea);
textarea.focus();
isEditingHtml = true;
} else {
var textarea = quill.root.querySelector('textarea');
if (textarea) {
signatureInput.value = textarea.value;
quill.root.innerHTML = textarea.value;
}
isEditingHtml = false;
}
});
if (signatureInput.value) {
quill.root.innerHTML = signatureInput.value;
}
quill.on('text-change', function() {
if (!isEditingHtml) {
signatureInput.value = quill.root.innerHTML;
}
});
quillEditorInstance = quill;
}
function initSignatureEditor() {
if (quillEditorInstance) return;
if (typeof Quill === 'undefined') {
var script = document.createElement('script');
script.src = 'https://cdn.quilljs.com/1.3.7/quill.min.js';
script.onload = createEditor;
document.head.appendChild(script);
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdn.quilljs.com/1.3.7/quill.snow.css';
document.head.appendChild(link);
} else {
createEditor();
}
}
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
if ($(e.target).attr('href') === '#signature') {
setTimeout(initSignatureEditor, 100);
}
});
if ($('#signature').hasClass('active')) {
initSignatureEditor();
}
</script>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/admin/email-settings/index.blade.php ENDPATH**/ ?>
@@ -1,70 +0,0 @@
<?php $__env->startSection('title', 'Gestione Ruoli'); ?>
<?php $__env->startSection('page_title', 'Gestione Ruoli'); ?>
<?php $__env->startSection('breadcrumbs'); ?>
<li class="breadcrumb-item active">Admin</li>
<li class="breadcrumb-item active">Ruoli</li>
<?php $__env->stopSection(); ?>
<?php $__env->startSection('content'); ?>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Ruoli Predefiniti</h3>
<a href="/admin/ruoli/create" class="btn btn-success btn-sm float-right">
<i class="fas fa-plus"></i> Nuovo Ruolo
</a>
</div>
<div class="card-body">
<table class="table table-bordered">
<thead>
<tr>
<th>Nome</th>
<th>Descrizione</th>
<th>Utenti</th>
<th>Permessi</th>
<th style="width: 100px;">Azioni</th>
</tr>
</thead>
<tbody>
<?php $__currentLoopData = $ruoli; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $ruolo): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<tr>
<td><strong><?php echo e($ruolo->name); ?></strong></td>
<td><?php echo e($ruolo->description); ?></td>
<td><span class="badge badge-info"><?php echo e($ruolo->users_count); ?></span></td>
<td>
<?php $__currentLoopData = $ruolo->permissions; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $module => $level): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<span class="badge badge-<?php echo e($level == 2 ? 'success' : ($level == 1 ? 'info' : 'secondary')); ?>">
<?php echo e(ucfirst($module)); ?>: <?php echo e($level == 2 ? 'Full' : ($level == 1 ? 'Read' : 'None')); ?>
</span>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</td>
<td>
<a href="<?php echo e(url('/admin/ruoli/' . $ruolo->id . '/edit')); ?>" class="btn btn-xs btn-warning">
<i class="fas fa-edit"></i>
</a>
<?php if($ruolo->users_count == 0): ?>
<form method="POST" action="<?php echo e(url('/admin/ruoli/' . $ruolo->id)); ?>" style="display: inline;">
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo ruolo?')">
<i class="fas fa-trash"></i>
</button>
</form>
<?php else: ?>
<button type="button" class="btn btn-xs btn-secondary" disabled title="Utenti associati: <?php echo e($ruolo->users_count); ?>">
<i class="fas fa-trash"></i>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('admin.layout', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/admin/ruoli/index.blade.php ENDPATH**/ ?>
File diff suppressed because one or more lines are too long
@@ -1,962 +0,0 @@
<?php $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree'; ?>
<?php $__env->startSection('title', 'Guida - ' . $appName); ?>
<?php $__env->startSection('page_title', 'Guida alla Configurazione'); ?>
<?php $__env->startSection('breadcrumbs'); ?>
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item active">Guida</li>
<?php $__env->stopSection(); ?>
<?php $__env->startSection('content'); ?>
<div class="row">
<div class="col-md-3">
<div class="card card-outline card-primary">
<div class="card-header">
<h3 class="card-title">Argomenti</h3>
</div>
<div class="card-body p-0">
<ul class="nav nav-pills flex-column" id="helpTabs">
<li class="nav-item">
<a class="nav-link active" href="#help-webdav" data-toggle="tab">
<i class="fas fa-cloud mr-2"></i> WebDAV / Nextcloud
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#help-googledrive" data-toggle="tab">
<i class="fab fa-google-drive mr-2"></i> Google Drive
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#help-tipologie" data-toggle="tab">
<i class="fas fa-tags mr-2"></i> Tipologie e Dati
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#help-email" data-toggle="tab">
<i class="fas fa-envelope mr-2"></i> Email / SMTP
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#help-installazione" data-toggle="tab">
<i class="fas fa-terminal mr-2"></i> Installazione
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#help-backup" data-toggle="tab">
<i class="fas fa-hdd mr-2"></i> Backup e Migrazione
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#help-docker" data-toggle="tab">
<i class="fab fa-docker mr-2"></i> Docker
</a>
</li>
</ul>
</div>
<div class="card-footer">
<a href="<?php echo e(route('help.pdf.download')); ?>" class="btn btn-primary btn-block">
<i class="fas fa-file-pdf mr-2"></i> Scarica Guida PDF
</a>
</div>
</div>
</div>
<div class="col-md-9">
<div class="tab-content">
<div class="tab-pane active" id="help-webdav">
<div class="card card-outline card-info">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-cloud mr-2"></i> WebDAV / Nextcloud</h3>
</div>
<div class="card-body">
<h5>Cos'è</h5>
<p>WebDAV è un protocollo che permette di accedere a file su server remoti. Nextcloud lo utilizza per la sincronizzazione dei file.</p>
<h5>Configurazione</h5>
<p>Vai in <strong>Impostazioni → Repository</strong> e crea un nuovo repository di tipo WebDAV.</p>
<div class="callout callout-info">
<h6>Campi obbligatori</h6>
<dl class="row mb-0">
<dt class="col-sm-3">Nome</dt>
<dd class="col-sm-9">Un nome descrittivo (es. "Nextcloud Lavoro")</dd>
<dt class="col-sm-3">Base URI</dt>
<dd class="col-sm-9">L'URL del tuo server WebDAV.
<br><em>Nextcloud:</em> <code>https://tuodominio.it/remote.php/dav/files/utente/</code>
<br><em>Altri:</em> <code>https://server.webdav.it/</code></dd>
<dt class="col-sm-3">Root</dt>
<dd class="col-sm-9">Percorso base all'interno del server (es. <code>/Documenti/</code>). Lascia <code>/</code> per usare la root.</dd>
<dt class="col-sm-3">Username</dt>
<dd class="col-sm-9">Il tuo nome utente</dd>
<dt class="col-sm-3">Password</dt>
<dd class="col-sm-9">La password dell'account</dd>
<dt class="col-sm-3">Auth Type</dt>
<dd class="col-sm-9"><code>Basic</code> per la maggior parte dei server (Nextcloud, ownCloud).</dd>
</dl>
</div>
<h5 class="mt-4">⚠️ Nome utente con @ (chiocciola)</h5>
<p>Se il tuo nome utente contiene il carattere <code>@</code> (es. <code>mario.rossi@azienda.com</code>), segui queste regole:</p>
<ul>
<li><strong>Nel campo "Base URI":</strong> l'indirizzo email deve essere <strong>URL-encoded</strong>:
<br><code>https://server.it/remote.php/dav/files/mario.rossi%40azienda.com/</code>
<br><small>(il carattere <code>@</code> diventa <code>%40</code>)</small></li>
<li><strong>Nel campo "Username":</strong> inserisci l'indirizzo email <strong>normale</strong> con la <code>@</code> vera.</li>
</ul>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
Il sistema gestisce automaticamente la differenza tra l'URI (che richiede <code>%40</code>) e l'autenticazione (che usa la <code>@</code> reale).
</div>
<h5 class="mt-4">Test connessione</h5>
<p>Dopo aver salvato, usa il pulsante <span class="badge badge-success"><i class="fas fa-plug"></i> Test</span> nella tabella dei repository per verificare che la connessione funzioni.</p>
<h5 class="mt-4">Importare file</h5>
<p>Apri la sezione <strong>Documenti</strong>, clicca sul pulsante del repository nella toolbar per sfogliare i file remoti, poi clicca <span class="badge badge-success"><i class="fas fa-save"></i> Salva tra i documenti</span> su un file per importarlo come documento locale.</p>
</div>
</div>
</div>
<div class="tab-pane" id="help-googledrive">
<div class="card card-outline card-danger">
<div class="card-header">
<h3 class="card-title"><i class="fab fa-google-drive mr-2"></i> Google Drive</h3>
</div>
<div class="card-body">
<h5>Cos'è</h5>
<p>Integrazione con Google Drive per sfogliare e importare file direttamente dal tuo spazio Google Drive.</p>
<h5>Requisiti</h5>
<ol>
<li>Un account Google / Google Workspace</li>
<li>Un progetto sulla <strong>Google Cloud Console</strong> con l'API Google Drive abilitata</li>
<li>Credenziali OAuth 2.0 (Client ID e Client Secret)</li>
</ol>
<h5>Passo 1: Creare il progetto Google Cloud</h5>
<ol>
<li>Vai su <a href="https://console.developers.google.com/" target="_blank">Google Cloud Console</a></li>
<li>Crea un nuovo progetto o selezionane uno esistente</li>
<li>Vai a <strong>API e servizi → Libreria</strong></li>
<li>Cerca "<strong>Google Drive API</strong>" e <strong>abilitala</strong></li>
<li>Vai a <strong>API e servizi → Credenziali</strong></li>
<li>Clicca "<strong>Crea credenziali → ID client OAuth</strong>"</li>
<li>Tipo applicazione: "<strong>Applicazione Web</strong>"</li>
<li><strong>URI di reindirizzamento autorizzato:</strong> aggiungi <code><?php echo e(url('/auth/google-drive/callback')); ?></code></li>
<li>Completa la creazione e <strong>copia Client ID e Client Secret</strong></li>
</ol>
<h5>Passo 2: Configurazione nell'app</h5>
<ol>
<li>Vai in <strong>Impostazioni → Repository</strong></li>
<li>Clicca "<strong>Nuovo Repository</strong>" e seleziona tipo <strong>Google Drive</strong></li>
<li>Inserisci un nome descrittivo (es. "Drive Lavoro")</li>
<li>Incolla <strong>Client ID</strong> e <strong>Client Secret</strong></li>
<li><strong>Folder ID (opzionale):</strong> se vuoi limitare l'accesso a una cartella specifica, inserisci il suo ID (lo trovi nell'URL quando apri la cartella in Google Drive: <code>https://drive.google.com/drive/folders/<strong>ID_QUI</strong></code>)</li>
<li>Clicca "<strong>Autorizza Google Drive</strong>" — verrai reindirizzato a Google per concedere i permessi</li>
<li>Dopo l'autorizzazione, il refresh token verrà salvato automaticamente</li>
</ol>
<h5>⚠️ Google Drive API non abilitata</h5>
<p>Se vedi l'errore <em>"Google Drive API non abilitata"</em> nel test connessione, significa che l'API Drive non è stata attivata nel tuo progetto Google Cloud.</p>
<div class="callout callout-danger">
<h6>Soluzione</h6>
<p>Vai su <a href="https://console.developers.google.com/apis/api/drive.googleapis.com/overview" target="_blank">
https://console.developers.google.com/apis/api/drive.googleapis.com/overview
</a></p>
<p>Seleziona il progetto corretto dall'header in alto, clicca <strong>"Enable"</strong> e attendi qualche minuto prima di riprovare.</p>
</div>
<h5>Importare file</h5>
<p>Stessa procedura di WebDAV: apri <strong>Documenti</strong>, clicca sul repository Drive, sfoglia i file e usa <span class="badge badge-success"><i class="fas fa-save"></i> Salva tra i documenti</span>.</p>
<h5 class="mt-4">⚠️ File Google (Documenti, Fogli, Presentazioni)</h5>
<p>I file creati con Google Workspace (Documenti Google, Fogli Google, Presentazioni Google, Disegni Google) non possono essere scaricati direttamente perché non hanno un formato binario nativo.</p>
<p>Il sistema li converte automaticamente in formato Office Open XML durante l'import:</p>
<table class="table table-sm table-bordered">
<thead><tr><th>File Google</th><th>Formato convertito</th></tr></thead>
<tbody>
<tr><td>Documento Google</td><td><code>.docx</code> (Word)</td></tr>
<tr><td>Foglio Google</td><td><code>.xlsx</code> (Excel)</td></tr>
<tr><td>Presentazione Google</td><td><code>.pptx</code> (PowerPoint)</td></tr>
<tr><td>Disegno Google</td><td><code>.png</code> (Immagine)</td></tr>
<tr><td>Altri (Moduli, Script)</td><td><code>.pdf</code> (PDF)</td></tr>
</tbody>
</table>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
Il nome del file viene automaticamente esteso con l'estensione corretta (es. "Relazione" → "Relazione.docx").
</div>
</div>
</div>
</div>
<div class="tab-pane" id="help-tipologie">
<div class="card card-outline card-success">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-tags mr-2"></i> Tipologie e Gestione Dati</h3>
</div>
<div class="card-body">
<h5>Tipologie Documenti</h5>
<p>Le tipologie classificano i documenti (avatar, galleria, documento, statuto, altro).</p>
<p>Si gestiscono in <strong>Impostazioni → Tipologie Documenti</strong>. Puoi aggiungere, rinominare, riordinare ed eliminare tipologie (tranne quelle con documenti associati).</p>
<h5>Tipologie Individui</h5>
<p>Classificano gli individui (es. socio, volontario, dipendente). Si gestiscono in <strong>Impostazioni → Tipologie</strong>.</p>
<h5>Tipologie Eventi</h5>
<p>Classificano gli eventi (es. riunione, assemblea, formazione). Si gestiscono in <strong>Impostazioni → Tipologie Eventi</strong>.</p>
<h5>Ruoli Individui</h5>
<p>Definiscono il ruolo o la carica di un individuo (es. presidente, segretario, tesoriere). Si gestiscono in <strong>Impostazioni → Ruoli</strong>.</p>
<h5>Visibilità Documenti</h5>
<p>Ogni documento può avere diversi livelli di visibilità:</p>
<ul>
<li><strong>Pubblico</strong> — visibile a tutti gli utenti</li>
<li><strong>Individuo</strong> — visibile solo a uno specifico individuo</li>
<li><strong>Gruppo</strong> — visibile solo ai membri di un gruppo</li>
<li><strong>Evento</strong> — visibile solo ai partecipanti di un evento</li>
<li><strong>Mailing</strong> — visibile solo ai membri di una mailing list</li>
</ul>
</div>
</div>
</div>
<div class="tab-pane" id="help-email">
<div class="card card-outline card-warning">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-envelope mr-2"></i> Email / SMTP</h3>
</div>
<div class="card-body">
<h5>Configurazione Email</h5>
<p>Vai in <strong>Impostazioni → Email</strong> per configurare la posta elettronica.</p>
<h5>Server IMAP (Ricezione)</h5>
<p>Usato per ricevere e leggere le email direttamente nell'app.</p>
<div class="callout callout-info">
<h6>Campi consigliati per provider comuni</h6>
<table class="table table-sm table-bordered">
<thead><tr><th>Provider</th><th>Host</th><th>Porta</th><th>Crittografia</th></tr></thead>
<tbody>
<tr><td>Gmail</td><td><code>imap.gmail.com</code></td><td><code>993</code></td><td>SSL</td></tr>
<tr><td>Outlook/Office 365</td><td><code>outlook.office365.com</code></td><td><code>993</code></td><td>SSL</td></tr>
<tr><td>Libero</td><td><code>imapmail.libero.it</code></td><td><code>993</code></td><td>SSL</td></tr>
<tr><td>Aruba</td><td><code>imap.aruba.it</code></td><td><code>993</code></td><td>SSL</td></tr>
</tbody>
</table>
</div>
<h5>Server SMTP (Invio)</h5>
<p>Usato per inviare email dall'app. Supporta account multipli (mittenti).</p>
<div class="callout callout-info">
<h6>Campi consigliati per provider comuni</h6>
<table class="table table-sm table-bordered">
<thead><tr><th>Provider</th><th>Host</th><th>Porta</th><th>Crittografia</th></tr></thead>
<tbody>
<tr><td>Gmail</td><td><code>smtp.gmail.com</code></td><td><code>587</code></td><td>TLS</td></tr>
<tr><td>Outlook/Office 365</td><td><code>smtp.office365.com</code></td><td><code>587</code></td><td>TLS</td></tr>
<tr><td>Libero</td><td><code>smtp.libero.it</code></td><td><code>465</code></td><td>SSL</td></tr>
<tr><td>Aruba</td><td><code>smtp.aruba.it</code></td><td><code>465</code></td><td>SSL</td></tr>
</tbody>
</table>
</div>
<h5>Gmail / Google Workspace</h5>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
Gmail richiede una <strong>Password per l'app</strong> (non la password normale) se usi la verifica in due passaggi. Puoi generarla su
<a href="https://myaccount.google.com/apppasswords" target="_blank">https://myaccount.google.com/apppasswords</a>.
</div>
<h5>Mailing</h5>
<p>Le <strong>Mailing List</strong> permettono di inviare email a gruppi predefiniti di destinatari.</p>
<ul>
<li>Crea una mailing list in <strong>Mailing List → Nuova</strong></li>
<li>Puoi popolare la lista da individui, gruppi o manualmente</li>
<li>Invia email alla lista da <strong>Email → Nuova Email</strong>, selezionando la mailing list come destinatario</li>
</ul>
<h5>Test connessione</h5>
<p>Dopo aver configurato i server, usa il pulsante <span class="badge badge-success"><i class="fas fa-vial"></i> Test Connessione</span> per verificare che IMAP e SMTP funzionino correttamente.</p>
</div>
</div>
</div>
<div class="tab-pane" id="help-backup">
<div class="card card-outline card-danger">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-hdd mr-2"></i> Backup e Migrazione Server</h3>
</div>
<div class="card-body">
<h5>Cos'è</h5>
<p>Il sistema di backup crea un archivio ZIP contenente tutto il necessario per ripristinare l'applicazione su un nuovo server. Include database, file caricati e configurazione.</p>
<h5>Cosa include il backup</h5>
<table class="table table-sm table-bordered">
<thead><tr><th>Componente</th><th>Incluso</th><th>Dettagli</th></tr></thead>
<tbody>
<tr><td>Database (MySQL)</td><td><span class="badge badge-success">Sempre</span></td><td>Struttura, dati, stored procedure, eventi, trigger</td></tr>
<tr><td>File caricati</td><td><span class="badge badge-success">Opzionale</span></td><td>Documenti, avatar, gallerie</td></tr>
<tr><td>Configurazione .env</td><td><span class="badge badge-success">Opzionale</span></td><td>APP_KEY, credenziali DB, SMTP, token OAuth</td></tr>
<tr><td>Metadati</td><td><span class="badge badge-success">Sempre</span></td><td>Versione app, PHP, Laravel</td></tr>
</tbody>
</table>
<h5 class="mt-4">Come eseguire un backup</h5>
<div class="row">
<div class="col-md-6">
<div class="callout callout-info">
<h6>Via Interfaccia Web</h6>
<ol class="mb-0">
<li>Vai su <strong>Admin → Backup</strong></li>
<li>Verifica le impostazioni (includi files/.env)</li>
<li>Clicca <span class="badge badge-primary"><i class="fas fa-play"></i> Esegui Backup Ora</span></li>
<li>Scarica il file ZIP generato</li>
</ol>
</div>
</div>
<div class="col-md-6">
<div class="callout callout-success">
<h6>Via Comando</h6>
<pre class="mb-0"><code>php artisan backup:run</code></pre>
<p class="mb-0 mt-2 small">Il file viene creato in <code>storage/app/backups/</code></p>
</div>
</div>
</div>
<h5 class="mt-4">Backup automatico</h5>
<p>Puoi abilitare il backup automatico dalla pagina <strong>Admin → Backup</strong>:</p>
<ul>
<li><strong>Frequenza:</strong> giornaliero, settimanale o mensile</li>
<li><strong>Ora:</strong> consigliata 03:00 (notte)</li>
<li><strong>Requisito:</strong> il cron di Laravel deve essere configurato sul server</li>
</ul>
<div class="alert alert-warning">
<i class="fas fa-clock mr-2"></i>
Per il backup automatico, aggiungi questo cron:
<pre class="mt-2 mb-0"><code>* * * * * cd <?php echo e(base_path()); ?> && php artisan schedule:run >> /dev/null 2>&1</code></pre>
</div>
<hr class="my-4">
<h4><i class="fas fa-truck mr-2"></i> Procedura di Migrazione Completa</h4>
<p>Segui questa procedura per migrare l'applicazione su un nuovo server LAMP (Linux, Apache, MySQL, PHP).</p>
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle mr-2"></i>
<strong>ATTENZIONE: APP_KEY</strong>
<p class="mb-0 mt-2">L'APP_KEY cripta i dati sensibili (password email SMTP, token OAuth Google Drive, ecc.).</p>
<ul class="mt-2">
<li><strong>Se hai il .env originale dal backup:</strong> NON rigenerare APP_KEY — mantieni i dati criptati accessibili.</li>
<li><strong>Se hai perso il .env:</strong> DEVI rigenerare APP_KEY, ma perderai l'accesso ai dati criptati esistenti. Dovrai riconfigurare SMTP e OAuth.</li>
</ul>
</div>
<h5 class="mt-4">Passo 1: Preparazione del server</h5>
<pre><code>sudo apt update && sudo apt upgrade -y</code></pre>
<h5 class="mt-4">Passo 2: Installa LAMP + PHP 8.4</h5>
<pre><code>sudo apt install -y apache2 mariadb-server php8.4 php8.4-cli \
php8.4-mysql php8.4-xml php8.4-mbstring php8.4-curl \
php8.4-zip php8.4-bcmath php8.4-gd php8.4-intl \
composer git unzip
sudo a2enmod rewrite</code></pre>
<h5 class="mt-4">Passo 3: Ottieni l'applicazione</h5>
<div class="callout callout-info py-2">
<i class="fas fa-info-circle"></i>
Puoi clonare il repository con git oppure estrarre il file ZIP ricevuto nella cartella <code>/var/www/glastree</code>.
</div>
<pre><code>cd /var/www
# Opzione A — Git clone
git clone &lt;URL_REPOSITORY&gt; glastree
# Opzione B — ZIP (se non hai git)
# unzip /percorso/del/glastree.zip -d glastree
cd glastree
composer install --no-dev --optimize-autoloader
npm ci && npm run build</code></pre>
<h5 class="mt-4">Passo 4: Carica il backup</h5>
<p>Copia il file <code>backup.zip</code> sul nuovo server e estrailo:</p>
<pre><code>mkdir -p /tmp/restore
unzip backup.zip -d /tmp/restore/
ls -la /tmp/restore/
# Dovresti vedere: database.sql .env files/ manifest.json</code></pre>
<h5 class="mt-4">Passo 5: Crea database e importa</h5>
<pre><code>sudo mysql -u root &lt;&lt;EOF
CREATE DATABASE glastree CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'glastree'@'localhost' IDENTIFIED BY 'ScegliPasswordFortep4ss!';
GRANT ALL ON glastree.* TO 'glastree'@'localhost';
FLUSH PRIVILEGES;
EOF
mysql -u glastree -p glastree &lt; /tmp/restore/database.sql</code></pre>
<h5 class="mt-4">Passo 6: Configura .env</h5>
<pre><code>cp /tmp/restore/.env .env
# Modifica se necessario:
# APP_URL=https://nuovo-dominio.it
# DB_HOST=localhost
# DB_DATABASE=glastree
# DB_USERNAME=glastree
# DB_PASSWORD=ScegliPasswordFortep4ss!
# DECIDI SULL'APP_KEY (leggi avviso sopra!)
# php artisan key:generate ← SOLO se non hai il .env originale</code></pre>
<h5 class="mt-4">Passo 7: Ripristina file e permessi</h5>
<pre><code>cp -r /tmp/restore/files/* storage/app/documenti/
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 755 storage bootstrap/cache
php artisan storage:link</code></pre>
<h5 class="mt-4">Passo 8: Cache</h5>
<pre><code>php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache</code></pre>
<h5 class="mt-4">Passo 9: Configura Apache</h5>
<pre><code>sudo tee /etc/apache2/sites-available/glastree.conf &lt;&lt;APACHE
&lt;VirtualHost *:80&gt;
ServerName glastree.esempio.it
DocumentRoot /var/www/glastree/public
&lt;Directory /var/www/glastree/public&gt;
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
&lt;/Directory&gt;
ErrorLog \${APACHE_LOG_DIR}/glastree_error.log
CustomLog \${APACHE_LOG_DIR}/glastree_access.log combined
&lt;/VirtualHost&gt;
APACHE
sudo a2dissite 000-default.conf
sudo a2ensite glastree.conf
sudo systemctl reload apache2</code></pre>
<h5 class="mt-4">Passo 10: SSL Let's Encrypt</h5>
<pre><code>sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d glastree.esempio.it</code></pre>
<h5 class="mt-4">Passo 11: Cron per backup automatico</h5>
<pre><code>sudo crontab -u www-data -e
# Inserisci:
* * * * * cd /var/www/glastree && php artisan schedule:run >> /dev/null 2>&1</code></pre>
<h5 class="mt-4">Passo 12: Verifica finale</h5>
<ol>
<li>Apri https://glastree.esempio.it nel browser</li>
<li>Fai login con le credenziali esistenti</li>
<li>Verifica: Dashboard, Documenti, Impostazioni, Email</li>
<li>Se hai rigenerato APP_KEY: riconfigura SMTP e OAuth Google Drive</li>
</ol>
<hr class="my-4">
<h5>Comandi Artisan</h5>
<p>Per installazione e ripristino usa i comandi Artisan da terminale. Vedi la sezione <strong>Installazione</strong> per la guida completa.</p>
</div>
</div>
</div>
<div class="tab-pane" id="help-docker">
<div class="card card-outline card-primary">
<div class="card-header">
<h3 class="card-title"><i class="fab fa-docker mr-2"></i> Docker</h3>
</div>
<div class="card-body">
<h5>Creare un'immagine Docker con dati di base</h5>
<p>Puoi creare un'immagine Docker pronta all'uso che include l'applicazione e i dati di base (tipologie documenti, tipologie eventi, ruoli). Scegli la configurazione in base alle tue esigenze:</p>
<div class="callout callout-info">
<i class="fas fa-info-circle"></i>
<strong>Ottenere il codice:</strong> puoi clonare il repository con <code>git clone &lt;URL&gt; glastree</code> oppure, se non hai git, estrarre il file ZIP ricevuto con <code>unzip glastree.zip -d glastree</code> e posizionarti nella cartella <code>glastree</code> prima di procedere.
</div>
<div class="row mb-4">
<div class="col-md-6">
<div class="callout callout-info">
<h6><i class="fas fa-database"></i> SQLite (Leggero)</h6>
<p class="mb-0 small">Database embedded, nessun servizio aggiuntivo. Ideale per demo, test, sviluppo locale o ambienti con poca concorrenza.</p>
</div>
</div>
<div class="col-md-6">
<div class="callout callout-success">
<h6><i class="fas fa-server"></i> MySQL (Standard)</h6>
<p class="mb-0 small">Database separato in un container MySQL. Configurazione standard, ideale per produzione, staging e ambienti con piu utenti.</p>
</div>
</div>
</div>
<h5 class="mt-4">1. Dockerfile (comune a entrambe le modalita)</h5>
<p>Crea un file <code>Dockerfile</code> nella root del progetto:</p>
<pre><code>FROM php:8.4-apache
# Abilita mod_rewrite
RUN docker-php-ext-enable opcache \
&& a2enmod rewrite
# Installa estensioni PHP (sia SQLite che MySQL)
RUN docker-php-ext-install pdo_mysql pdo_sqlite bcmath gd zip
# Installa Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Configura DocumentRoot per Laravel
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
# Copia l'applicazione
COPY . /var/www/html/
# Installa dipendenze e compila asset
RUN composer install --no-dev --optimize-autoloader \
&& mkdir -p storage/app/public \
&& mkdir -p storage/framework/cache/data \
&& mkdir -p storage/framework/sessions \
&& mkdir -p storage/framework/views \
&& mkdir -p storage/logs \
&& mkdir -p bootstrap/cache \
&& chown -R www-data:www-data storage bootstrap/cache
# Entrypoint per inizializzazione
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]</code></pre>
<h5 class="mt-4">2. Entrypoint Script (comune)</h5>
<p>Crea <code>docker-entrypoint.sh</code>. Lo script rileva automaticamente la configurazione in base alle variabili d'ambiente:</p>
<pre><code>#!/bin/bash
set -e
# Se .env non esiste, lo genera dalle variabili d'ambiente
if [ ! -f .env ]; then
cp .env.example .env
if [ "$DB_CONNECTION" = "mysql" ]; then
# Configura MySQL
sed -i "s/DB_CONNECTION=mysql/DB_CONNECTION=mysql/" .env
sed -i "s/DB_HOST=.*/DB_HOST=${DB_HOST:-mysql}/" .env
sed -i "s/DB_PORT=.*/DB_PORT=${DB_PORT:-3306}/" .env
sed -i "s/DB_DATABASE=.*/DB_DATABASE=${DB_DATABASE:-glastree}/" .env
sed -i "s/DB_USERNAME=.*/DB_USERNAME=${DB_USERNAME:-glastree}/" .env
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=${DB_PASSWORD:-secret}/" .env
# Attende che MySQL sia pronto
echo "Attendo MySQL..."
until php -r "new PDO('mysql:host=${DB_HOST:-mysql};dbname=${DB_DATABASE:-glastree}', '${DB_USERNAME:-glastree}', '${DB_PASSWORD:-secret}');" 2>/dev/null; do
sleep 2
done
echo "MySQL pronto."
else
# Configura SQLite
sed -i 's/DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' .env
sed -i 's/DB_HOST=.*//' .env
sed -i 's/DB_PORT=.*//' .env
sed -i 's/DB_DATABASE=.*/DB_DATABASE=\/var\/www\/html\/database\/database.sqlite/' .env
sed -i 's/DB_USERNAME=.*//' .env
sed -i 's/DB_PASSWORD=.*//' .env
touch database/database.sqlite
chmod 664 database/database.sqlite
fi
# Genera APP_KEY
php artisan key:generate --force
fi
# Esegui migration e seed se il database e vuoto
if [ ! -f .db_initialized ]; then
php artisan migrate --force
php artisan db:seed --force
touch .db_initialized
fi
# Configura storage link
php artisan storage:link --force 2>/dev/null || true
exec "$@"</code></pre>
<pre><code>chmod +x docker-entrypoint.sh</code></pre>
<h5 class="mt-4">3. Seed Base Dati (comune)</h5>
<p>Per includere tipologie documenti, tipologie eventi e ruoli, crea un seeder dedicato:</p>
<pre><code>php artisan make:seeder DockerBaseDataSeeder</code></pre>
<p>Contenuto di <code>database/seeders/DockerBaseDataSeeder.php</code>:</p>
<pre><code>&lt;?php
declare(strict_types=1);
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DockerBaseDataSeeder extends Seeder
{
public function run(): void
{
// Tipologie Documenti
$tipologieDoc = ['documento', 'avatar', 'galleria', 'statuto', 'programma', 'locandina', 'altro'];
foreach ($tipologieDoc as $ord => $nome) {
DB::table('tipologie_documenti')->updateOrInsert(
['nome' => $nome],
['descrizione' => ucfirst($nome), 'ordine' => $ord + 1, 'attiva' => true]
);
}
// Tipologie Eventi
$tipologieEventi = [
['nome' => 'catechesi', 'descrizione' => 'Catechesi'],
['nome' => 'liturgia', 'descrizione' => 'Liturgia'],
['nome' => 'animazione', 'descrizione' => 'Animazione'],
['nome' => 'formazione', 'descrizione' => 'Formazione'],
['nome' => 'incontro', 'descrizione' => 'Incontro'],
['nome' => 'riunione', 'descrizione' => 'Riunione'],
['nome' => 'festa', 'descrizione' => 'Festa'],
['nome' => 'altro', 'descrizione' => 'Altro'],
];
foreach ($tipologieEventi as $ord => $t) {
DB::table('tipologie_eventi')->updateOrInsert(
['nome' => $t['nome']],
['descrizione' => $t['descrizione'], 'ordine' => $ord + 1, 'attiva' => true]
);
}
// Ruoli
$ruoli = ['presidente', 'vicepresidente', 'segretario', 'tesoriere', 'consigliere', 'volontario', 'socio', 'altro'];
foreach ($ruoli as $ord => $nome) {
DB::table('ruoli')->updateOrInsert(
['nome' => $nome],
['descrizione' => ucfirst($nome), 'ordine' => $ord + 1, 'attiva' => true]
);
}
}
}</code></pre>
<p>Registralo in <code>database/seeders/DatabaseSeeder.php</code>:</p>
<pre><code>$this->call([
// ... altri seeder ...
DockerBaseDataSeeder::class,
]);</code></pre>
<h5 class="mt-4">4. Docker Compose</h5>
<h6 class="text-info"><i class="fas fa-database mr-1"></i> Opzione A — SQLite (leggero)</h6>
<pre><code>services:
glastree:
build: .
ports:
- "8080:80"
volumes:
- glastree_storage:/var/www/html/storage
- glastree_db:/var/www/html/database
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_URL=http://localhost:8080
- DB_CONNECTION=sqlite
volumes:
glastree_storage:
glastree_db:</code></pre>
<h6 class="mt-3 text-success"><i class="fas fa-server mr-1"></i> Opzione B — MySQL (standard)</h6>
<pre><code>services:
glastree:
build: .
ports:
- "8080:80"
volumes:
- glastree_storage:/var/www/html/storage
depends_on:
mysql:
condition: service_healthy
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_URL=http://localhost:8080
- DB_CONNECTION=mysql
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=glastree
- DB_USERNAME=glastree
- DB_PASSWORD=secret
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
environment:
- MYSQL_DATABASE=glastree
- MYSQL_USER=glastree
- MYSQL_PASSWORD=secret
- MYSQL_ROOT_PASSWORD=root_secret
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 10
volumes:
glastree_storage:
mysql_data:</code></pre>
<h5 class="mt-4">5. Build e avvio</h5>
<p>Assicurati di essere nella cartella del progetto (ottenuta via <code>git clone</code> o estraendo il ZIP), poi:</p>
<pre><code># Costruisci l'immagine (comune)
docker build -t glastree:latest .
# Avvia con SQLite
docker compose -f docker-compose.yml up -d
# Oppure avvia con MySQL
# docker compose -f docker-compose.mysql.yml up -d
# Apri nel browser
open http://localhost:8080</code></pre>
<h5 class="mt-4">6. Primo accesso</h5>
<p>Al primo avvio, l'entrypoint esegue automaticamente migration e seed. Troverai gia popolati:</p>
<ul>
<li><strong>Tipologie Documenti:</strong> documento, avatar, galleria, statuto, programma, locandina, altro</li>
<li><strong>Tipologie Eventi:</strong> catechesi, liturgia, animazione, formazione, incontro, riunione, festa, altro</li>
<li><strong>Ruoli:</strong> presidente, vicepresidente, segretario, tesoriere, consigliere, volontario, socio, altro</li>
</ul>
<p>Dovrai creare il primo utente amministratore via CLI:</p>
<pre><code>docker exec -it glastree php artisan tinker --execute="
\$u = new \App\Models\User;
\$u->name = 'Amministratore';
\$u->email = 'admin@esempio.it';
\$u->password = bcrypt('password-forte');
\$u->is_admin = true;
\$u->permissions = ['individui' => 2, 'gruppi' => 2, 'eventi' => 2, 'documenti' => 2, 'mailing' => 2, 'viste' => 2, 'report' => 2, 'settings' => 2];
\$u->save();
echo 'Utente creato con successo!';
"</code></pre>
<div class="callout callout-warning mt-4">
<h6><i class="fas fa-exclamation-triangle"></i> Note importanti</h6>
<ul class="mb-0">
<li><strong>Persistenza:</strong> i volumi Docker preservano storage e database tra i riavvii</li>
<li><strong>APP_KEY:</strong> viene generata al primo avvio — se elimini i volumi, la perdi e i dati criptati diventano inaccessibili</li>
<li><strong>MySQL in produzione:</strong> imposta password sicure per <code>MYSQL_PASSWORD</code> e <code>MYSQL_ROOT_PASSWORD</code></li>
<li><strong>File compose separati:</strong> puoi mantenere entrambi i file (<code>docker-compose.yml</code> per SQLite, <code>docker-compose.mysql.yml</code> per MySQL) e scegliere con <code>-f</code></li>
</ul>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="help-installazione">
<div class="card card-outline card-dark">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-terminal mr-2"></i> Guida all'Installazione</h3>
</div>
<div class="card-body">
<h5>Panoramica</h5>
<p>L'installazione avviene tramite l'interattivo <code>install.php</code> che guida passo-passo. Supporta due modalita:</p>
<div class="row">
<div class="col-md-6">
<div class="callout callout-info">
<h6><i class="fas fa-rocket"></i> Fresh Install</h6>
<p class="mb-0">Installa l'applicazione da zero su un nuovo server: crea il database, esegue le migration, popola i dati di base e crea l'amministratore.</p>
</div>
</div>
<div class="col-md-6">
<div class="callout callout-success">
<h6><i class="fas fa-upload"></i> Restore da Backup</h6>
<p class="mb-0">Ripristina un'installazione esistente da un file ZIP di backup: importa il database, ripristina i documenti e la configurazione.</p>
</div>
</div>
</div>
<h5 class="mt-4">Requisiti del Server</h5>
<table class="table table-sm table-bordered">
<thead><tr><th>Componente</th><th>Versione Minima</th><th>Note</th></tr></thead>
<tbody>
<tr><td>Web Server</td><td>Apache 2.4+</td><td>Con mod_rewrite abilitato</td></tr>
<tr><td>PHP</td><td>8.2+ (consigliato 8.4)</td><td>Estensioni: pdo_mysql, mbstring, xml, curl, zip, gd, fileinfo</td></tr>
<tr><td>MySQL / MariaDB</td><td>MySQL 8.0+ / MariaDB 10.5+</td><td>Supporto utf8mb4</td></tr>
<tr><td>Composer</td><td>2.x</td><td>Per gestione dipendenze PHP</td></tr>
<tr><td>Node.js / npm</td><td>Node 20+ / npm 10+</td><td>Per compilazione asset frontend (opzionale se precompilati)</td></tr>
</tbody>
</table>
<h5 class="mt-4">Installazione Passo-Passo</h5>
<h6>Passo 1: Preparare il server LAMP</h6>
<pre><code>sudo apt update && sudo apt upgrade -y
sudo apt install -y apache2 mariadb-server php8.4 php8.4-cli \
php8.4-mysql php8.4-xml php8.4-mbstring php8.4-curl \
php8.4-zip php8.4-bcmath php8.4-gd php8.4-intl \
composer git unzip
sudo a2enmod rewrite
sudo systemctl restart apache2</code></pre>
<h6>Passo 2: Ottenere l'applicazione</h6>
<div class="callout callout-info py-2">
<i class="fas fa-info-circle"></i>
Puoi clonare il repository con git oppure estrarre il file ZIP ricevuto nella cartella <code>/var/www/glastree</code>.
</div>
<pre><code>cd /var/www
# Opzione A — Git clone
git clone &lt;URL_REPOSITORY&gt; glastree
# Opzione B — ZIP (se non hai git)
# unzip /percorso/del/glastree.zip -d glastree
cd glastree</code></pre>
<h6>Passo 3: Eseguire l'installer</h6>
<p>Lancerai l'installer interattivo che ti guidera attraverso tutte le fasi:</p>
<pre><code>php install.php</code></pre>
<p>L'installer ti chiedera:</p>
<ol>
<li><strong>Modalità</strong>: Fresh Install (Apache/Docker) o Restore da Backup</li>
<li><strong>Parametri generali</strong>: nome sito, amministratore (nome, email, password), URL pubblico</li>
<li><strong>Database</strong>: MySQL (host, porta, nome, utente, password) o SQLite</li>
</ol>
<p>L'installer esegue automaticamente:</p>
<ul>
<li>Generazione <code>.env</code> e <code>APP_KEY</code></li>
<li>Creazione del database</li>
<li><code>composer install</code> e registrazione pacchetti</li>
<li>Migration e seed dei dati di base</li>
<li>Creazione dell'utente amministratore</li>
<li>Compilazione asset frontend (se npm disponibile)</li>
<li>Impostazione permessi cartelle</li>
</ul>
<div class="callout callout-info">
<h6><i class="fas fa-info-circle"></i> Docker</h6>
<p>Se scegli la modalita Docker, l'installer builda l'immagine, avvia i container e crea l'admin all'interno del container.</p>
</div>
<div class="callout callout-success">
<h6><i class="fas fa-upload"></i> Restore da Backup</h6>
<p>Se scegli Restore, l'installer ti chiedera il percorso del file ZIP di backup, ripristinera il database, i file e la configurazione.</p>
</div>
<h6 class="mt-4">Passo 4: Configurare Apache (solo modalita Fresh Install)</h6>
<pre><code>sudo tee /etc/apache2/sites-available/glastree.conf &lt;&lt;APACHE
&lt;VirtualHost *:80&gt;
ServerName glastree.esempio.it
DocumentRoot /var/www/glastree/public
&lt;Directory /var/www/glastree/public&gt;
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
&lt;/Directory&gt;
ErrorLog \${APACHE_LOG_DIR}/glastree_error.log
CustomLog \${APACHE_LOG_DIR}/glastree_access.log combined
&lt;/VirtualHost&gt;
APACHE
sudo a2dissite 000-default.conf
sudo a2ensite glastree.conf
sudo systemctl reload apache2</code></pre>
<i class="fas fa-exclamation-triangle"></i>
<strong>Importante:</strong> Il restore da backup richiede che tu abbia un file ZIP generato dalla pagina <strong>Admin → Backup</strong> del server originale. Estrai manualmente il contenuto seguendo la struttura del progetto.
</div>
<h5 class="mt-4">Cosa succede durante l'installazione</h5>
<p>I comandi <code>php artisan migrate --seed</code> eseguono:</p>
<ol>
<li>Genera <code>APP_KEY</code> per la crittografia (gia eseguito al passo 5)</li>
<li>Esegue tutte le migration per creare le tabelle del database</li>
<li>Popola i dati di base (diocesi, comuni, ruoli, preset)</li>
<li>Crea il collegamento <code>storage &rarr; public/storage</code></li>
<li>Crea l'utente amministratore con i permessi completi</li>
</ol>
<h5 class="mt-4">Restore da Backup</h5>
<p>Il backup ZIP generato dalla pagina <strong>Admin &rarr; Backup</strong> contiene un dump SQL e i file dei documenti. Per ripristinare:</p>
<ul>
<li>Estrai il file ZIP in una cartella temporanea</li>
<li>Importa il database dal file SQL (<code>mysql -u root -p glastree &lt; backup/database.sql</code>)</li>
<li>Copia i file in <code>storage/app/</code> per ripristinare i documenti</li>
<li>Copia il file <code>.env</code> dal backup (modifica le credenziali DB per il nuovo server)</li>
<li>Esegui <code>php artisan key:generate</code> se la <code>APP_KEY</code> del backup non è utilizzabile</li>
</ul>
<h5 class="mt-4">Dopo l'Installazione</h5>
<ol>
<li><strong>Configura SSL</strong> con Let's Encrypt:
<pre><code>sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d glastree.esempio.it</code></pre>
</li>
<li><strong>Accedi</strong> con le credenziali amministratore create durante l'installazione</li>
<li><strong>Configura l'applicazione</strong> in Impostazioni:
<ul>
<li>Nome applicazione e logo</li>
<li>Email (SMTP per invio, IMAP per ricezione)</li>
<li>Repository remoti (Google Drive, Nextcloud)</li>
</ul>
</li>
<li><strong>Crea utenti</strong> dal pannello Admin &rarr; Utenti</li>
<li><strong>Configura il cron</strong> per backup automatico e sync email:
<pre><code>sudo crontab -u www-data -e
# Inserisci:
* * * * * cd /var/www/glastree && php artisan schedule:run >> /dev/null 2>&1</code></pre>
</li>
</ol>
<h5 class="mt-4">Risoluzione Problemi</h5>
<div class="callout callout-danger">
<h6>Errore "500 | Server Error" dopo l'installazione</h6>
<p>Probabilmente mancano permessi sulle cartelle <code>storage</code> o <code>bootstrap/cache</code>:</p>
<pre><code>sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache</code></pre>
</div>
<div class="callout callout-danger">
<h6>Errore "No application encryption key"</h6>
<p>APP_KEY non generata. Nella cartella dell'app:</p>
<pre><code>php artisan key:generate</code></pre>
</div>
<div class="callout callout-warning">
<h6>mod_rewrite non funziona</h6>
<p>Se le rotte restituiscono 404, verifica che Apache abbia AllowOverride:</p>
<pre><code>sudo a2enmod rewrite
# Nel VirtualHost: AllowOverride All
sudo systemctl reload apache2</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.nav-pills .nav-link { border-radius: 0; }
.nav-pills .nav-link.active { background-color: #007bff; }
.callout { border-left: 3px solid; padding: 12px 16px; margin: 16px 0; background: #f8f9fa; }
.callout-info { border-color: #17a2b8; }
.callout-danger { border-color: #dc3545; }
.callout-warning { border-color: #ffc107; }
</style>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/help/index.blade.php ENDPATH**/ ?>
@@ -1,103 +0,0 @@
<?php $__env->startSection('title', 'Calendario Eventi'); ?>
<?php $__env->startSection('page_title', 'Calendario Eventi'); ?>
<?php $__env->startSection('content'); ?>
<div class="row">
<div class="col-12">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">
<i class="fas fa-calendar-alt mr-2"></i>Calendario Eventi
</h3>
<div class="card-tools">
<a href="<?php echo e(route('eventi.index')); ?>" class="btn btn-secondary btn-sm">
<i class="fas fa-list mr-1"></i> Elenco Eventi
</a>
<a href="<?php echo e(route('eventi.create')); ?>" class="btn btn-success btn-sm">
<i class="fas fa-plus mr-1"></i> Nuovo Evento
</a>
<form action="<?php echo e(route('eventi.export-selected-ics')); ?>" method="POST" class="d-inline">
<?php echo csrf_field(); ?>
<button type="submit" class="btn btn-success btn-sm">
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
</button>
</form>
</div>
</div>
<div class="card-body">
<div id="calendar"></div>
</div>
</div>
</div>
</div>
<?php $__env->stopSection(); ?>
<?php $__env->startSection('styles'); ?>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css">
<?php $__env->stopSection(); ?>
<?php $__env->startSection('scripts'); ?>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
locale: 'it',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,listWeek'
},
buttonText: {
today: 'Oggi',
month: 'Mese',
week: 'Settimana',
list: 'Lista'
},
eventSources: [
{
url: '/eventi/calendar/events',
method: 'GET',
extraParams: function() {
return {
_token: document.querySelector('meta[name="csrf-token"]')?.content || ''
};
},
startParam: 'start',
endParam: 'end',
success: function(response) {
console.log('Eventi caricati:', response.length, response);
},
failure: function(error) {
console.error('Errore:', error);
console.log('XHR:', error.xhr);
alert('Errore nel caricamento degli eventi. Controlla la console per dettagli.');
}
}
],
eventClick: function(info) {
info.jsEvent.preventDefault();
window.location.href = info.event.url;
},
eventDisplay: 'block',
displayEventTime: true,
displayEventEnd: true,
nowIndicator: true,
height: 'auto',
expandRows: true,
stickyHeaderDates: true,
dayMaxEvents: true,
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit',
hour12: false
}
});
calendar.render();
});
</script>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/eventi/calendar.blade.php ENDPATH**/ ?>
@@ -1,75 +0,0 @@
<?php
use App\Models\AppSetting;
$appLogo = AppSetting::getLogoUrl();
$appName = AppSetting::getAppName() ?? 'Glastree';
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reset Password - <?php echo e(e($appName)); ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body class="hold-transition login-page">
<div class="login-box">
<div class="login-logo">
<a href="/">
<?php if($appLogo): ?>
<img src="<?php echo e($appLogo); ?>" alt="Logo" style="max-height: 80px; max-width: 200px;">
<?php else: ?>
<i class="fas fa-tree text-success"></i> <b><?php echo e(e($appName)); ?></b>
<?php endif; ?>
</a>
</div>
<div class="card">
<div class="card-body login-card-body">
<p class="login-box-msg">Password dimenticata? Inserisci la tua email e ti invieremo il link per reimpostarla.</p>
<?php if(session('success')): ?>
<div class="alert alert-success"><?php echo e(session('success')); ?></div>
<?php endif; ?>
<form action="<?php echo e(route('password.email')); ?>" method="POST">
<?php echo csrf_field(); ?>
<div class="input-group mb-3">
<input type="email" name="email" class="form-control <?php $__errorArgs = ['email'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?>" placeholder="Email" required value="<?php echo e(old('email')); ?>">
<div class="input-group-append">
<div class="input-group-text"><span class="fas fa-envelope"></span></div>
</div>
<?php $__errorArgs = ['email'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="invalid-feedback"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?>
</div>
<div class="row">
<div class="col-12">
<button type="submit" class="btn btn-success btn-block">Invia link reset</button>
</div>
</div>
</form>
<p class="mb-0 text-center mt-3">
<a href="<?php echo e(route('login')); ?>">Torna al login</a>
</p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<?php /**PATH /var/www/html/glastree/resources/views/auth/forgot-password.blade.php ENDPATH**/ ?>
@@ -1,165 +0,0 @@
<?php $__env->startSection('title', 'Dashboard'); ?>
<?php $__env->startSection('page_title', 'Dashboard'); ?>
<?php $__env->startSection('content'); ?>
<div class="row">
<div class="col-lg-3 col-6">
<div class="small-box bg-info">
<div class="inner">
<h3><?php echo e($stats['individui']); ?></h3>
<p>Individui</p>
</div>
<div class="icon">
<i class="fas fa-users"></i>
</div>
<a href="<?php echo e(route('individui.index')); ?>" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<div class="col-lg-3 col-6">
<div class="small-box bg-success">
<div class="inner">
<h3><?php echo e($stats['gruppi']); ?></h3>
<p>Gruppi</p>
</div>
<div class="icon">
<i class="fas fa-folder"></i>
</div>
<a href="<?php echo e(route('gruppi.index')); ?>" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<div class="col-lg-3 col-6">
<div class="small-box bg-primary">
<div class="inner">
<h3><?php echo e($stats['documenti']); ?></h3>
<p>Documenti</p>
</div>
<div class="icon">
<i class="fas fa-file"></i>
</div>
<a href="/documenti" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<div class="col-lg-3 col-6">
<div class="small-box bg-danger">
<div class="inner">
<h3><?php echo e($stats['eventi']); ?></h3>
<p>Eventi</p>
</div>
<div class="icon">
<i class="fas fa-calendar"></i>
</div>
<a href="/eventi" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-3 col-6">
<div class="small-box bg-warning">
<div class="inner">
<h3><?php echo e($stats['notifiche']); ?></h3>
<p>Notifiche</p>
</div>
<div class="icon">
<i class="fas fa-bell"></i>
</div>
<a href="#" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<div class="col-lg-3 col-6">
<div class="small-box bg-secondary">
<div class="inner">
<h3><?php echo e($stats['email_nuove']); ?></h3>
<p>Nuove Email</p>
</div>
<div class="icon">
<i class="fas fa-envelope"></i>
</div>
<a href="/email" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<div class="col-lg-3 col-6">
<div class="small-box bg-dark">
<div class="inner">
<h3><?php echo e($stats['mailing_liste']); ?></h3>
<p>Mailing List</p>
</div>
<div class="icon">
<i class="fas fa-list-alt"></i>
</div>
<a href="/mailing-liste" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<?php if(Auth::user()->canManage('settings')): ?>
<div class="col-lg-3 col-6">
<div class="small-box bg-navy">
<div class="inner">
<h3><?php echo e($stats['backups']); ?></h3>
<p>Backup</p>
</div>
<div class="icon">
<i class="fas fa-hdd"></i>
</div>
<a href="<?php echo e(route('admin.backup.index')); ?>" class="small-box-footer">Visualizza <i class="fas fa-arrow-circle-right"></i></a>
</div>
</div>
<?php endif; ?>
</div>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-bell"></i> Ultime Notifiche</h3>
</div>
<div class="card-body">
<?php if($notifiche->count() > 0): ?>
<ul class="todo-list" data-widget="todo-list">
<?php $__currentLoopData = $notifiche; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $notifica): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<li>
<span class="text"><?php echo e($notifica->titolo); ?></span>
<small class="badge badge-info"><i class="far fa-clock"></i> <?php echo e($notifica->created_at->diffForHumans()); ?></small>
</li>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</ul>
<?php else: ?>
<p class="text-muted">Nessuna notifica</p>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-info-circle"></i> Benvenuto</h3>
</div>
<div class="card-body">
<?php
$appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
$orgName = \App\Models\AppSetting::getOrgName();
$welcome = \App\Models\AppSetting::getDashboardWelcome();
?>
<?php if($welcome): ?>
<p><?php echo nl2br(e($welcome)); ?></p>
<?php else: ?>
<p>Benvenuto in <strong><?php echo e(e($appName)); ?></strong><?php echo e($orgName ? ', ' . e($orgName) : ''); ?>.</p>
<?php endif; ?>
<p>Dal menu laterale puoi navigare tra le sezioni:</p>
<ul>
<li><strong>Individui</strong>: Gestione delle persone registrate</li>
<li><strong>Gruppi</strong>: Gestione della struttura organizzativa</li>
<li><strong>Documenti</strong>: Archivio documentale</li>
<li><strong>Eventi</strong>: Calendario eventi</li>
<li><strong>Mailing</strong>: Comunicazioni email</li>
</ul>
<?php if(Auth::user()->is_admin): ?>
<div class="alert alert-success mt-3">
<i class="fas fa-crown"></i> <strong>Amministratore</strong>: Hai accesso completo al sistema.
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/home/dashboard.blade.php ENDPATH**/ ?>
File diff suppressed because it is too large Load Diff
@@ -1,349 +0,0 @@
<?php $__env->startSection('title', 'Eventi'); ?>
<?php $__env->startSection('page_title', 'Eventi'); ?>
<?php
$canWriteEventi = Auth::user()->canManage('eventi');
$canDeleteEventi = Auth::user()->canDelete('eventi');
?>
<?php $__env->startSection('content'); ?>
<?php if(session('success')): ?>
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<?php echo e(session('success')); ?>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header">
<h3 class="card-title">Tutti gli Eventi</h3>
<div class="card-tools">
<a href="/eventi/calendar" class="btn btn-sm btn-outline-primary mr-2">
<i class="fas fa-calendar-alt mr-1"></i> Calendario
</a>
<button type="button" class="btn btn-sm btn-success mr-2" onclick="exportSelectedIcs()">
<i class="fas fa-calendar-plus mr-1"></i> Esporta ICS
</button>
<?php if($canDeleteEventi): ?>
<div class="btn-group mr-2">
<button type="button" class="btn btn-sm btn-danger" onclick="deleteSelected()">
<i class="fas fa-trash mr-1"></i> Elimina Selezionati
</button>
</div>
<?php endif; ?>
<?php if($canWriteEventi): ?>
<a href="/eventi/create" class="btn btn-xs btn-success">
<i class="fas fa-plus mr-1"></i> Nuovo Evento
</a>
<?php endif; ?>
</div>
</div>
<div class="card-body">
<form method="GET" class="mb-3">
<div class="row">
<div class="col-md-3">
<input type="text" name="search" class="form-control" placeholder="Cerca evento..." value="<?php echo e(request('search')); ?>">
</div>
<div class="col-md-3">
<select name="gruppo_id" class="form-control" onchange="this.form.submit()">
<option value="">Tutti i gruppi</option>
<?php $__currentLoopData = $gruppi; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $g): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($g->id); ?>" <?php echo e(request('gruppo_id') == $g->id ? 'selected' : ''); ?>><?php echo e($g->full_path); ?></option>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</select>
</div>
<div class="col-md-2">
<select name="tipo" class="form-control" onchange="this.form.submit()">
<option value="">Tutte le ricorrenze</option>
<option value="singolo" <?php echo e(request('tipo') == 'singolo' ? 'selected' : ''); ?>>Singolo</option>
<option value="settimanale" <?php echo e(request('tipo') == 'settimanale' ? 'selected' : ''); ?>>Settimanale</option>
<option value="mensile" <?php echo e(request('tipo') == 'mensile' ? 'selected' : ''); ?>>Mensile</option>
<option value="annuale" <?php echo e(request('tipo') == 'annuale' ? 'selected' : ''); ?>>Annuale</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary"><i class="fas fa-search mr-1"></i> Cerca</button>
<a href="/eventi" class="btn btn-secondary"><i class="fas fa-times"></i></a>
</div>
</div>
</form>
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr>
<th style="width: 40px;">
<?php if($canDeleteEventi): ?>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
<label class="custom-control-label" for="select-all"></label>
</div>
<?php endif; ?>
</th>
<th>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'nome_evento', 'direction' => request('sort') === 'nome_evento' && request('direction') === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark">
Nome Evento
<?php if(request('sort') === 'nome_evento'): ?>
<i class="fas fa-sort-<?php echo e(request('direction') === 'asc' ? 'up' : 'down'); ?> ml-1"></i>
<?php else: ?>
<i class="fas fa-sort ml-1 text-muted"></i>
<?php endif; ?>
</a>
</th>
<th>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'descrizione_evento', 'direction' => request('sort') === 'descrizione_evento' && request('direction') === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark">
Descrizione
<?php if(request('sort') === 'descrizione_evento'): ?>
<i class="fas fa-sort-<?php echo e(request('direction') === 'asc' ? 'up' : 'down'); ?> ml-1"></i>
<?php else: ?>
<i class="fas fa-sort ml-1 text-muted"></i>
<?php endif; ?>
</a>
</th>
<th>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'tipo_evento', 'direction' => request('sort') === 'tipo_evento' && request('direction') === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark">
Tipologia
<?php if(request('sort') === 'tipo_evento'): ?>
<i class="fas fa-sort-<?php echo e(request('direction') === 'asc' ? 'up' : 'down'); ?> ml-1"></i>
<?php else: ?>
<i class="fas fa-sort ml-1 text-muted"></i>
<?php endif; ?>
</a>
</th>
<th>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'tipo_recorrenza', 'direction' => request('sort') === 'tipo_recorrenza' && request('direction') === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark">
Ricorrenza
<?php if(request('sort') === 'tipo_recorrenza'): ?>
<i class="fas fa-sort-<?php echo e(request('direction') === 'asc' ? 'up' : 'down'); ?> ml-1"></i>
<?php else: ?>
<i class="fas fa-sort ml-1 text-muted"></i>
<?php endif; ?>
</a>
</th>
<th>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'gruppi', 'direction' => request('sort') === 'gruppi' && request('direction') === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark">
Gruppi
<?php if(request('sort') === 'gruppi'): ?>
<i class="fas fa-sort-<?php echo e(request('direction') === 'asc' ? 'up' : 'down'); ?> ml-1"></i>
<?php else: ?>
<i class="fas fa-sort ml-1 text-muted"></i>
<?php endif; ?>
</a>
</th>
<th>
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'responsabili', 'direction' => request('sort') === 'responsabili' && request('direction') === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark">
Responsabili
<?php if(request('sort') === 'responsabili'): ?>
<i class="fas fa-sort-<?php echo e(request('direction') === 'asc' ? 'up' : 'down'); ?> ml-1"></i>
<?php else: ?>
<i class="fas fa-sort ml-1 text-muted"></i>
<?php endif; ?>
</a>
</th>
<th style="width: 120px;">
<a href="<?php echo e(request()->fullUrlWithQuery(['sort' => 'created_at', 'direction' => request('sort') === 'created_at' && request('direction') === 'asc' ? 'desc' : 'asc'])); ?>" class="text-dark">
Creato
<?php if(request('sort') === 'created_at'): ?>
<i class="fas fa-sort-<?php echo e(request('direction') === 'asc' ? 'up' : 'down'); ?> ml-1"></i>
<?php else: ?>
<i class="fas fa-sort ml-1 text-muted"></i>
<?php endif; ?>
</a>
</th>
<th style="width: 120px;">Azioni</th>
</tr>
</thead>
<tbody>
<?php $__empty_1 = true; $__currentLoopData = $eventi; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $evento): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<tr data-id="<?php echo e($evento->id); ?>">
<td>
<?php if($canDeleteEventi): ?>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input row-checkbox" id="row-checkbox-<?php echo e($evento->id); ?>" value="<?php echo e($evento->id); ?>">
<label class="custom-control-label" for="row-checkbox-<?php echo e($evento->id); ?>"></label>
</div>
<?php endif; ?>
</td>
<td>
<a href="/eventi/<?php echo e($evento->id); ?>">
<i class="fas fa-calendar mr-1 text-primary"></i>
<?php echo e($evento->nome_evento); ?>
</a>
<?php if($evento->is_incontro_gruppo): ?>
<span class="badge badge-success ml-1">Incluso</span>
<?php endif; ?>
</td>
<td><?php echo e(Str::limit($evento->descrizione_evento, 50) ?: '-'); ?></td>
<td>
<?php if($evento->tipo_evento): ?>
<?php
$tipologiaEvento = \App\Models\TipologiaEvento::where('nome', $evento->tipo_evento)->first();
?>
<?php if($tipologiaEvento): ?>
<span class="badge badge-primary"><?php echo e($tipologiaEvento->descrizione ?: $tipologiaEvento->nome); ?></span>
<?php else: ?>
<span class="badge badge-secondary"><?php echo e($evento->tipo_evento); ?></span>
<?php endif; ?>
<?php else: ?>
<span class="text-muted">-</span>
<?php endif; ?>
</td>
<td>
<?php if($evento->tipo_recorrenza && $evento->tipo_recorrenza !== 'singolo'): ?>
<span class="badge badge-info"><?php echo e($evento->periodicita_label); ?></span>
<?php else: ?>
<span class="badge badge-secondary">Singolo</span>
<?php endif; ?>
</td>
<td>
<?php $__empty_2 = true; $__currentLoopData = $evento->gruppi->take(2); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $gruppo): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_2 = false; ?>
<a href="/gruppi/<?php echo e($gruppo->id); ?>">
<span class="badge badge-warning"><?php echo e($gruppo->nome); ?></span>
</a>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_2): ?>
<span class="text-muted">-</span>
<?php endif; ?>
<?php if($evento->gruppi->count() > 2): ?>
<small class="text-muted">+<?php echo e($evento->gruppi->count() - 2); ?></small>
<?php endif; ?>
</td>
<td>
<?php $__empty_2 = true; $__currentLoopData = $evento->responsabili->take(2); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $resp): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_2 = false; ?>
<small><?php echo e($resp->nome_completo); ?></small><br>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_2): ?>
<span class="text-muted">-</span>
<?php endif; ?>
<?php if($evento->responsabili->count() > 2): ?>
<small class="text-muted">+<?php echo e($evento->responsabili->count() - 2); ?> altri</small>
<?php endif; ?>
</td>
<td><small class="text-muted"><?php echo e($evento->created_at->format('d/m/Y')); ?></small></td>
<td>
<a href="<?php echo e(url('/eventi/' . $evento->id)); ?>" class="btn btn-xs btn-info" title="Visualizza">
<i class="fas fa-eye"></i>
</a>
<?php if($canWriteEventi): ?>
<a href="<?php echo e(url('/eventi/' . $evento->id . '/edit')); ?>" class="btn btn-xs btn-warning" title="Modifica">
<i class="fas fa-edit"></i>
</a>
<form action="<?php echo e(url('/eventi/' . $evento->id)); ?>" method="POST" class="d-inline">
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Confermi l\'eliminazione?')" title="Elimina">
<i class="fas fa-trash"></i>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<tr>
<td colspan="9" class="text-center text-muted py-4">
<i class="fas fa-calendar fa-2x mb-2"></i>
<p class="mb-0">Nessun evento trovato</p>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="card-footer">
<?php echo e($eventi->withQueryString()->links()); ?>
</div>
</div>
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
<?php if($canDeleteEventi): ?>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header bg-danger">
<h5 class="modal-title text-white"><i class="fas fa-trash-alt mr-2"></i>Conferma Eliminazione</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<p>Stai per eliminare <strong id="delete-count">0</strong> eventi selezionati.</p>
<p class="text-muted small">Verranno eliminati anche i collegamenti con gruppi e responsabili.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">Elimina</button>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php $__env->stopSection(); ?>
<?php $__env->startSection('scripts'); ?>
<script>
function toggleSelectAll(source) {
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
}
function getSelectedIds() {
return Array.from(document.querySelectorAll('.row-checkbox:checked')).map(cb => cb.value);
}
function deleteSelected() {
const selectedIds = getSelectedIds();
if (selectedIds.length === 0) {
alert('Seleziona almeno un evento');
return;
}
document.getElementById('delete-count').textContent = selectedIds.length;
document.querySelector('#deleteModal .btn-danger').onclick = function() {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/eventi/mass-elimina';
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '<?php echo e(csrf_token()); ?>';
let html = `<input type="hidden" name="_token" value="${csrfToken}">`;
selectedIds.forEach(function(id) {
html += `<input type="hidden" name="ids[]" value="${id}">`;
});
form.innerHTML = html;
document.body.appendChild(form);
form.submit();
};
$('#deleteModal').modal('show');
}
function exportSelectedIcs() {
const selectedIds = getSelectedIds();
const form = document.createElement('form');
form.method = 'POST';
form.action = '/eventi/export-ics';
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '<?php echo e(csrf_token()); ?>';
let html = `<input type="hidden" name="_token" value="${csrfToken}">`;
if (selectedIds.length > 0) {
selectedIds.forEach(function(id) {
html += `<input type="hidden" name="ids[]" value="${id}">`;
});
} else {
const params = new URLSearchParams(window.location.search);
params.forEach(function(value, key) {
if (key === 'search') html += `<input type="hidden" name="search" value="${value}">`;
if (key === 'gruppo_id') html += `<input type="hidden" name="gruppo_id" value="${value}">`;
if (key === 'tipo') html += `<input type="hidden" name="tipo" value="${value}">`;
});
}
form.innerHTML = html;
document.body.appendChild(form);
form.submit();
}
</script>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.adminlte', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/eventi/index.blade.php ENDPATH**/ ?>
@@ -1,108 +0,0 @@
<?php
use App\Models\AppSetting;
$appLogo = AppSetting::getLogoUrl();
$appName = AppSetting::getAppName() ?? 'Glastree';
$appOrgName = AppSetting::getOrgName() ?? '';
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Accedi - <?php echo e(e($appName)); ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body class="hold-transition login-page">
<div class="login-box">
<div class="login-logo">
<a href="/">
<?php if($appLogo): ?>
<img src="<?php echo e($appLogo); ?>" alt="Logo" style="max-height: 80px; max-width: 200px;">
<?php else: ?>
<i class="fas fa-tree text-success"></i> <b><?php echo e(e($appName)); ?></b>
<?php endif; ?>
</a>
</div>
<?php if($appOrgName): ?>
<div class="text-center mb-3">
<small class="text-muted"><?php echo e(e($appOrgName)); ?></small>
</div>
<?php endif; ?>
<div class="card">
<div class="card-body login-card-body">
<p class="login-box-msg">Accedi per continuare</p>
<form action="<?php echo e(route('login')); ?>" method="POST">
<?php echo csrf_field(); ?>
<div class="input-group mb-3">
<input type="email" name="email" class="form-control <?php $__errorArgs = ['email'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?>" placeholder="Email" required>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-envelope"></span>
</div>
</div>
<?php $__errorArgs = ['email'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="invalid-feedback"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?>
</div>
<div class="input-group mb-3">
<input type="password" name="password" class="form-control <?php $__errorArgs = ['password'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?> is-invalid <?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?>" placeholder="Password" required>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-lock"></span>
</div>
</div>
<?php $__errorArgs = ['password'];
$__bag = $errors->getBag($__errorArgs[1] ?? 'default');
if ($__bag->has($__errorArgs[0])) :
if (isset($message)) { $__messageOriginal = $message; }
$message = $__bag->first($__errorArgs[0]); ?>
<span class="invalid-feedback"><?php echo e($message); ?></span>
<?php unset($message);
if (isset($__messageOriginal)) { $message = $__messageOriginal; }
endif;
unset($__errorArgs, $__bag); ?>
</div>
<div class="row">
<div class="col-8">
<div class="icheck-primary">
<input type="checkbox" id="remember">
<label for="remember">Ricordami</label>
</div>
</div>
<div class="col-4">
<button type="submit" class="btn btn-success btn-block">Accedi</button>
</div>
</div>
</form>
<p class="mb-0 text-center mt-3">
<a href="<?php echo e(route('password.request')); ?>" class="text-center">Password dimenticata?</a>
</p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html><?php /**PATH /var/www/html/glastree/resources/views/auth/login.blade.php ENDPATH**/ ?>
@@ -1,301 +0,0 @@
<?php
$appLogoSmall = \App\Models\AppSetting::getLogoSmallUrl();
$appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="<?php echo e(csrf_token()); ?>">
<title><?php echo $__env->yieldContent('title', e($appName)); ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600;700&display=swap">
<?php echo $__env->yieldContent('styles'); ?>
</head>
<body class="hold-transition sidebar-mini layout-fixed">
<div class="wrapper">
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="<?php echo e(route('help')); ?>" title="Guida">
<i class="fas fa-question-circle"></i>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#">
<i class="far fa-bell"></i>
<?php $unread = Auth::user()->unreadNotificheCount() ?? 0 ?>
<?php if($unread > 0): ?>
<span class="badge badge-warning navbar-badge"><?php echo e($unread); ?></span>
<?php endif; ?>
</a>
<div class="dropdown-menu dropdown-menu-right">
<span class="dropdown-header">Notifiche (<?php echo e($unread); ?>)</span>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">Visualizza tutte</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#">
<i class="fas fa-user"></i> <?php echo e(Auth::user()->name); ?>
</a>
<div class="dropdown-menu dropdown-menu-right">
<a href="<?php echo e(route('profile')); ?>" class="dropdown-item">
<i class="fas fa-user-cog mr-2"></i> Profilo
</a>
<div class="dropdown-divider"></div>
<form method="POST" action="<?php echo e(route('logout')); ?>">
<?php echo csrf_field(); ?>
<button type="submit" class="dropdown-item">
<i class="fas fa-sign-out-alt mr-2"></i> Esci
</button>
</form>
</div>
</li>
</ul>
</nav>
<?php
$appLogoSmall = \App\Models\AppSetting::getLogoSmallUrl();
$appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
?>
<aside class="main-sidebar sidebar-dark-primary elevation-4">
<a href="<?php echo e(route('dashboard')); ?>" class="brand-link">
<?php if($appLogoSmall): ?>
<img src="<?php echo e($appLogoSmall); ?>" alt="Logo" class="brand-image" style="max-height: 35px; max-width: 40px;">
<?php else: ?>
<i class="fas fa-tree brand-image ml-3 mr-2"></i>
<?php endif; ?>
<span class="brand-text font-weight-light"><?php echo e($appName); ?></span>
</a>
<div class="sidebar">
<nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu">
<li class="nav-item">
<a href="<?php echo e(route('dashboard')); ?>" class="nav-link <?php echo e(request()->routeIs('dashboard') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-tachometer-alt"></i>
<p>Dashboard</p>
</a>
</li>
<?php if(Auth::user()->canAccess('individui')): ?>
<li class="nav-item">
<a href="<?php echo e(route('individui.index')); ?>" class="nav-link">
<i class="nav-icon fas fa-users"></i>
<p>Individui</p>
</a>
</li>
<?php endif; ?>
<?php if(Auth::user()->canAccess('gruppi')): ?>
<li class="nav-item">
<a href="<?php echo e(route('gruppi.index')); ?>" class="nav-link">
<i class="nav-icon fas fa-folder"></i>
<p>Gruppi</p>
</a>
</li>
<?php endif; ?>
<?php if(Auth::user()->canAccess('documenti')): ?>
<li class="nav-item">
<a href="<?php echo e(route('documenti.index')); ?>" class="nav-link">
<i class="nav-icon fas fa-file"></i>
<p>Documenti</p>
</a>
</li>
<?php endif; ?>
<?php if(Auth::user()->canAccess('eventi')): ?>
<li class="nav-item has-treeview">
<a href="<?php echo e(route('eventi.calendar')); ?>" class="nav-link">
<i class="nav-icon fas fa-calendar"></i>
<p>Eventi <i class="right fas fa-angle-left" style="font-size: 0.8rem;"></i></p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="<?php echo e(route('eventi.calendar')); ?>" class="nav-link">
<i class="nav-icon fas fa-calendar-alt"></i>
<p>Calendario</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('eventi.index')); ?>" class="nav-link">
<i class="nav-icon fas fa-list"></i>
<p>Elenco</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('eventi.create')); ?>" class="nav-link">
<i class="nav-icon fas fa-plus"></i>
<p>Nuovo Evento</p>
</a>
</li>
</ul>
</li>
<?php endif; ?>
<?php if(Auth::user()->canAccess('email') || Auth::user()->canAccess('mailing')): ?>
<li class="nav-item has-treeview">
<a href="<?php echo e(route('email.index', 'inbox')); ?>" class="nav-link">
<i class="nav-icon fas fa-envelope"></i>
<p>Email <i class="right fas fa-angle-left" style="font-size: 0.8rem;"></i></p>
</a>
<ul class="nav nav-treeview">
<?php if(Auth::user()->canAccess('email')): ?>
<li class="nav-item">
<a href="<?php echo e(route('email.compose')); ?>" class="nav-link">
<i class="nav-icon fas fa-plus"></i>
<p>Nuova Email</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('email.index', 'inbox')); ?>" class="nav-link">
<i class="nav-icon fas fa-inbox"></i>
<p>In arrivo</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('email.index', 'starred')); ?>" class="nav-link">
<i class="nav-icon fas fa-star"></i>
<p>Preferiti</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('email.index', 'sent')); ?>" class="nav-link">
<i class="nav-icon fas fa-paper-plane"></i>
<p>Inviate</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('email.index', 'drafts')); ?>" class="nav-link">
<i class="nav-icon fas fa-edit"></i>
<p>Bozze</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('email.index', 'archive')); ?>" class="nav-link">
<i class="nav-icon fas fa-archive"></i>
<p>Archivio</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('email.index', 'trash')); ?>" class="nav-link">
<i class="nav-icon fas fa-trash"></i>
<p>Cestino</p>
</a>
</li>
<?php endif; ?>
</ul>
</li>
<?php endif; ?>
<?php if(Auth::user()->canAccess('mailing')): ?>
<li class="nav-item">
<a href="<?php echo e(route('mailing-liste.index')); ?>" class="nav-link">
<i class="nav-icon fas fa-list"></i>
<p>Mailing List</p>
</a>
</li>
<?php endif; ?>
<?php if(Auth::user()->canAccess('report')): ?>
<li class="nav-item">
<a href="<?php echo e(route('report.index')); ?>" class="nav-link <?php echo e(request()->routeIs('report.*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-chart-bar"></i>
<p>Report</p>
</a>
</li>
<?php endif; ?>
<?php if(Auth::user()->canManage('settings')): ?>
<li class="nav-item">
<a href="/impostazioni" class="nav-link <?php echo e(request()->is('impostazioni*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-cog"></i>
<p>Impostazioni</p>
</a>
</li>
<li class="nav-item has-treeview <?php echo e(request()->is('admin/*') ? 'menu-open' : ''); ?>">
<a href="#" class="nav-link <?php echo e(request()->is('admin/*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-users-cog"></i>
<p>Admin <i class="right fas fa-angle-left" style="font-size: 0.8rem;"></i></p>
</a>
<ul class="nav nav-treeview">
<li class="nav-item">
<a href="/admin/utenti" class="nav-link <?php echo e(request()->routeIs('admin.utenti.*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-user-shield"></i>
<p>Utenti</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('admin.ruoli.index')); ?>" class="nav-link <?php echo e(request()->routeIs('admin.ruoli.*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-tag"></i>
<p>Ruoli</p>
</a>
</li>
<li class="nav-item">
<a href="/admin/backup" class="nav-link <?php echo e(request()->routeIs('admin.backup.*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-hdd"></i>
<p>Backup</p>
</a>
</li>
<li class="nav-item">
<a href="<?php echo e(route('admin.activity-logs.index')); ?>" class="nav-link <?php echo e(request()->routeIs('admin.activity-logs.*') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-history"></i>
<p>Log Attività</p>
</a>
</li>
</ul>
</li>
<?php endif; ?>
<li class="nav-item">
<a href="<?php echo e(route('help')); ?>" class="nav-link <?php echo e(request()->routeIs('help') ? 'active' : ''); ?>">
<i class="nav-icon fas fa-question-circle"></i>
<p>Guida / Aiuto</p>
</a>
</li>
</ul>
</nav>
</div>
</aside>
<div class="content-wrapper" style="min-height: calc(100vh - 120px);">
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0"><?php echo $__env->yieldContent('page_title', 'Dashboard'); ?></h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<?php echo $__env->yieldContent('breadcrumbs'); ?>
</ol>
</div>
</div>
</div>
</div>
<div class="content">
<div class="container-fluid">
<?php echo $slot ?? ''; ?>
<?php echo $__env->yieldContent('content'); ?>
</div>
</div>
</div>
<footer class="main-footer">
<strong><?php echo e(\App\Models\AppSetting::getFooterText()); ?></strong>
<?php if(\App\Models\AppSetting::shouldShowVersion() && \App\Models\AppSetting::getAppVersion()): ?>
<span class="ml-2">v<?php echo e(\App\Models\AppSetting::getAppVersion()); ?></span>
<?php endif; ?>
</footer>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<?php echo $__env->yieldContent('scripts'); ?>
</body>
</html><?php /**PATH /var/www/html/glastree/resources/views/layouts/adminlte.blade.php ENDPATH**/ ?>
@@ -1,109 +0,0 @@
<?php $__env->startSection('title', 'Gestione Utenti'); ?>
<?php $__env->startSection('page_title', 'Gestione Utenti'); ?>
<?php $__env->startSection('breadcrumbs'); ?>
<li class="breadcrumb-item active">Admin</li>
<li class="breadcrumb-item active">Utenti</li>
<?php $__env->stopSection(); ?>
<?php $__env->startSection('content'); ?>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Utenti Registrati</h3>
<a href="/admin/utenti/create" class="btn btn-success btn-sm float-right">
<i class="fas fa-plus"></i> Nuovo Utente
</a>
</div>
<div class="card-body">
<form method="GET" class="mb-3">
<div class="row">
<div class="col-md-4">
<input type="text" name="search" class="form-control" placeholder="Cerca nome o email..." value="<?php echo e(request('search')); ?>">
</div>
<div class="col-md-3">
<select name="status" class="form-control">
<option value="">Tutti gli stati</option>
<option value="active" <?php echo e(request('status') == 'active' ? 'selected' : ''); ?>>Attivo</option>
<option value="suspended" <?php echo e(request('status') == 'suspended' ? 'selected' : ''); ?>>Sospeso</option>
<option value="pending" <?php echo e(request('status') == 'pending' ? 'selected' : ''); ?>>In attesa</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Filtra</button>
</div>
</div>
</form>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Nome</th>
<th>Email</th>
<th>Stato</th>
<th>Permessi</th>
<th>Creato</th>
<th style="width: 150px;">Azioni</th>
</tr>
</thead>
<tbody>
<?php $__currentLoopData = $utenti; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $utente): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<tr>
<td>
<strong><?php echo e($utente->name); ?></strong>
<?php if($utente->isSuperAdmin()): ?>
<span class="badge badge-danger ml-1">Admin</span>
<?php endif; ?>
</td>
<td><?php echo e($utente->email); ?></td>
<td>
<?php switch($utente->status):
case ('active'): ?>
<span class="badge badge-success">Attivo</span>
<?php break; ?>
<?php case ('suspended'): ?>
<span class="badge badge-secondary">Sospeso</span>
<?php break; ?>
<?php case ('pending'): ?>
<span class="badge badge-warning">In attesa</span>
<?php break; ?>
<?php endswitch; ?>
</td>
<td>
<?php $summary = $utente->getPermissionsSummary(); ?>
<small>
<?php $__currentLoopData = $summary; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $module => $level): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<?php if($level !== 'Nessuno'): ?>
<span class="text-<?php echo e($level === 'Completo' ? 'success' : 'info'); ?>"><?php echo e(ucfirst($module)); ?>: <?php echo e($level); ?></span><br>
<?php endif; ?>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</small>
</td>
<td><?php echo e($utente->created_at->format('d/m/Y')); ?></td>
<td>
<a href="<?php echo e(url('/admin/utenti/' . $utente->id . '/edit')); ?>" class="btn btn-xs btn-warning">
<i class="fas fa-edit"></i>
</a>
<?php if($utente->id !== auth()->id()): ?>
<form method="POST" action="<?php echo e(url('/admin/utenti/' . $utente->id)); ?>" style="display: inline;">
<?php echo csrf_field(); ?> <?php echo method_field('DELETE'); ?>
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo utente?')">
<i class="fas fa-trash"></i>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</tbody>
</table>
<?php echo e($utenti->links()); ?>
</div>
</div>
</div>
</div>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('admin.layout', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH /var/www/html/glastree/resources/views/admin/utenti/index.blade.php ENDPATH**/ ?>
+28
View File
@@ -0,0 +1,28 @@
<?php
use App\Models\EmailSetting;
use App\Models\User;
beforeEach(function () {
EmailSetting::where('imap_host', 'test.feature')->delete();
});
it('saves email settings via POST form', function () {
$user = User::first();
$response = $this->actingAs($user)->post(route('impostazioni.email.save'), [
'imap_host' => 'test.feature',
'imap_port' => '993',
'imap_encryption' => 'ssl',
'imap_username' => 'featureuser',
'imap_password' => 'featurepass',
'email_address' => 'feature@test.com',
'email_name' => 'Feature Test',
'is_active' => '1',
]);
$response->assertSessionHas('success');
$record = EmailSetting::where('imap_host', 'test.feature')->first();
expect($record)->not->toBeNull();
expect($record->email_address)->toBe('feature@test.com');
});
-122
View File
@@ -1,122 +0,0 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = 'phpvfscomposer://'.$this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
}
}
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
-119
View File
@@ -1,119 +0,0 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../laravel/pint/builds/pint)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/laravel/pint/builds/pint');
}
}
return include __DIR__ . '/..'.'/laravel/pint/builds/pint';
+6 -2094
View File
File diff suppressed because it is too large Load Diff
+3 -10
View File
@@ -11,11 +11,11 @@ return array(
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php', '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
@@ -27,15 +27,13 @@ return array(
'2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php', '2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php',
'606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php', '606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php', 'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php',
'1f87db08236948d07391152dccb70f04' => $vendorDir . '/google/apiclient-services/autoload.php', '1f87db08236948d07391152dccb70f04' => $vendorDir . '/google/apiclient-services/autoload.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php', 'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php', '09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
'a8d3953fd9959404dd22d3dfcd0a79f0' => $vendorDir . '/google/apiclient/src/aliases.php', 'a8d3953fd9959404dd22d3dfcd0a79f0' => $vendorDir . '/google/apiclient/src/aliases.php',
'23dd7ece5822da3d0100ef3deb0ef55f' => $vendorDir . '/laravel/agent-detector/src/functions.php',
'47e1160838b5e5a10346ac4084b58c23' => $vendorDir . '/laravel/prompts/src/helpers.php', '47e1160838b5e5a10346ac4084b58c23' => $vendorDir . '/laravel/prompts/src/helpers.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', '35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php', '801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php',
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php', 'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
'9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php', '9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php',
@@ -49,10 +47,5 @@ return array(
'91892b814db86b8442ad76273bb7aec5' => $vendorDir . '/laravel/framework/src/Illuminate/Reflection/helpers.php', '91892b814db86b8442ad76273bb7aec5' => $vendorDir . '/laravel/framework/src/Illuminate/Reflection/helpers.php',
'493c6aea52f6009bab023b26c21a386a' => $vendorDir . '/laravel/framework/src/Illuminate/Support/functions.php', '493c6aea52f6009bab023b26c21a386a' => $vendorDir . '/laravel/framework/src/Illuminate/Support/functions.php',
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php', '58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
'309cd39bb536ff667b25a3a76938cb83' => $vendorDir . '/laravel/pao/src/Autoload.php',
'c72349b1fe8d0deeedd3a52e8aa814d8' => $vendorDir . '/mockery/mockery/library/helpers.php',
'ce9671a430e4846b44e1c68c7611f9f5' => $vendorDir . '/mockery/mockery/library/Mockery.php',
'a1cfe24d14977df6878b9bf804af2d1c' => $vendorDir . '/nunomaduro/collision/src/Adapters/Phpunit/Autoload.php',
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
'377b22b161c09ed6e5152de788ca020a' => $vendorDir . '/spatie/laravel-permission/src/helpers.php', '377b22b161c09ed6e5152de788ca020a' => $vendorDir . '/spatie/laravel-permission/src/helpers.php',
); );
+3 -12
View File
@@ -10,10 +10,8 @@ return array(
'ZBateson\\StreamDecorators\\' => array($vendorDir . '/zbateson/stream-decorators/src'), 'ZBateson\\StreamDecorators\\' => array($vendorDir . '/zbateson/stream-decorators/src'),
'ZBateson\\MbWrapper\\' => array($vendorDir . '/zbateson/mb-wrapper/src'), 'ZBateson\\MbWrapper\\' => array($vendorDir . '/zbateson/mb-wrapper/src'),
'ZBateson\\MailMimeParser\\' => array($vendorDir . '/zbateson/mail-mime-parser/src'), 'ZBateson\\MailMimeParser\\' => array($vendorDir . '/zbateson/mail-mime-parser/src'),
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
'Webklex\\PHPIMAP\\' => array($vendorDir . '/webklex/php-imap/src'), 'Webklex\\PHPIMAP\\' => array($vendorDir . '/webklex/php-imap/src'),
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'), 'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
'Tests\\' => array($baseDir . '/tests'),
'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'), 'Termwind\\' => array($vendorDir . '/nunomaduro/termwind/src'),
'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'), 'Symfony\\Polyfill\\Uuid\\' => array($vendorDir . '/symfony/polyfill-uuid'),
'Symfony\\Polyfill\\Php86\\' => array($vendorDir . '/symfony/polyfill-php86'), 'Symfony\\Polyfill\\Php86\\' => array($vendorDir . '/symfony/polyfill-php86'),
@@ -66,10 +64,8 @@ return array(
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'), 'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'),
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'), 'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'Mockery\\' => array($vendorDir . '/mockery/mockery/library/Mockery'),
'League\\Uri\\' => array($vendorDir . '/league/uri', $vendorDir . '/league/uri-interfaces'), 'League\\Uri\\' => array($vendorDir . '/league/uri', $vendorDir . '/league/uri-interfaces'),
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), 'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\WebDAV\\' => array($vendorDir . '/league/flysystem-webdav'), 'League\\Flysystem\\WebDAV\\' => array($vendorDir . '/league/flysystem-webdav'),
@@ -80,9 +76,6 @@ return array(
'Laravel\\Tinker\\' => array($vendorDir . '/laravel/tinker/src'), 'Laravel\\Tinker\\' => array($vendorDir . '/laravel/tinker/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'), 'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Laravel\\Prompts\\' => array($vendorDir . '/laravel/prompts/src'), 'Laravel\\Prompts\\' => array($vendorDir . '/laravel/prompts/src'),
'Laravel\\Pao\\' => array($vendorDir . '/laravel/pao/src'),
'Laravel\\Pail\\' => array($vendorDir . '/laravel/pail/src'),
'Laravel\\AgentDetector\\' => array($vendorDir . '/laravel/agent-detector/src'),
'Invoker\\' => array($vendorDir . '/php-di/invoker/src'), 'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections', $vendorDir . '/laravel/framework/src/Illuminate/Conditionable', $vendorDir . '/laravel/framework/src/Illuminate/Reflection'), 'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections', $vendorDir . '/laravel/framework/src/Illuminate/Conditionable', $vendorDir . '/laravel/framework/src/Illuminate/Reflection'),
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'), 'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
@@ -96,7 +89,6 @@ return array(
'Google\\' => array($vendorDir . '/google/apiclient/src'), 'Google\\' => array($vendorDir . '/google/apiclient/src'),
'Fruitcake\\Cors\\' => array($vendorDir . '/fruitcake/php-cors/src'), 'Fruitcake\\Cors\\' => array($vendorDir . '/fruitcake/php-cors/src'),
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), 'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'Faker\\' => array($vendorDir . '/fakerphp/faker/src/Faker'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), 'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'), 'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/src'), 'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/src'),
@@ -104,9 +96,8 @@ return array(
'DirectoryTree\\ImapEngine\\Laravel\\' => array($vendorDir . '/directorytree/imapengine-laravel/src'), 'DirectoryTree\\ImapEngine\\Laravel\\' => array($vendorDir . '/directorytree/imapengine-laravel/src'),
'DirectoryTree\\ImapEngine\\' => array($vendorDir . '/directorytree/imapengine/src'), 'DirectoryTree\\ImapEngine\\' => array($vendorDir . '/directorytree/imapengine/src'),
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'), 'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), 'Database\\Seeders\\' => array($baseDir . '/database/seeders'),
'Database\\Seeders\\' => array($baseDir . '/database/seeders', $vendorDir . '/laravel/pint/database/seeders'), 'Database\\Factories\\' => array($baseDir . '/database/factories'),
'Database\\Factories\\' => array($baseDir . '/database/factories', $vendorDir . '/laravel/pint/database/factories'),
'DI\\' => array($vendorDir . '/php-di/php-di/src'), 'DI\\' => array($vendorDir . '/php-di/php-di/src'),
'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'), 'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'),
'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'), 'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'),
@@ -114,5 +105,5 @@ return array(
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'), 'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
'As247\\Flysystem\\GoogleDrive\\' => array($vendorDir . '/as247/flysystem-google-drive/src'), 'As247\\Flysystem\\GoogleDrive\\' => array($vendorDir . '/as247/flysystem-google-drive/src'),
'As247\\CloudStorages\\' => array($vendorDir . '/as247/cloud-storages/src'), 'As247\\CloudStorages\\' => array($vendorDir . '/as247/cloud-storages/src'),
'App\\' => array($baseDir . '/app', $vendorDir . '/laravel/pint/app'), 'App\\' => array($baseDir . '/app'),
); );
+9 -2152
View File
File diff suppressed because it is too large Load Diff
+2 -2405
View File
File diff suppressed because it is too large Load Diff
+3 -318
View File
@@ -3,11 +3,11 @@
'name' => 'laravel/laravel', 'name' => 'laravel/laravel',
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'aa4e582925ec155fa08dbeb9bf4e3a97b4751df1', 'reference' => '90093a086b35dce8edd97fce09d32d2be42853e8',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'dev' => true, 'dev' => false,
), ),
'versions' => array( 'versions' => array(
'as247/cloud-storages' => array( 'as247/cloud-storages' => array(
@@ -46,18 +46,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'cordoval/hamcrest-php' => array(
'dev_requirement' => true,
'replaced' => array(
0 => '*',
),
),
'davedevelopment/hamcrest-php' => array(
'dev_requirement' => true,
'replaced' => array(
0 => '*',
),
),
'dflydev/dot-access-data' => array( 'dflydev/dot-access-data' => array(
'pretty_version' => 'v3.0.3', 'pretty_version' => 'v3.0.3',
'version' => '3.0.3.0', 'version' => '3.0.3.0',
@@ -121,24 +109,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'fakerphp/faker' => array(
'pretty_version' => 'v1.24.1',
'version' => '1.24.1.0',
'reference' => 'e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5',
'type' => 'library',
'install_path' => __DIR__ . '/../fakerphp/faker',
'aliases' => array(),
'dev_requirement' => true,
),
'filp/whoops' => array(
'pretty_version' => '2.18.4',
'version' => '2.18.4.0',
'reference' => 'd2102955e48b9fd9ab24280a7ad12ed552752c4d',
'type' => 'library',
'install_path' => __DIR__ . '/../filp/whoops',
'aliases' => array(),
'dev_requirement' => true,
),
'firebase/php-jwt' => array( 'firebase/php-jwt' => array(
'pretty_version' => 'v7.0.5', 'pretty_version' => 'v7.0.5',
'version' => '7.0.5.0', 'version' => '7.0.5.0',
@@ -229,15 +199,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'hamcrest/hamcrest-php' => array(
'pretty_version' => 'v2.1.1',
'version' => '2.1.1.0',
'reference' => 'f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487',
'type' => 'library',
'install_path' => __DIR__ . '/../hamcrest/hamcrest-php',
'aliases' => array(),
'dev_requirement' => true,
),
'illuminate/auth' => array( 'illuminate/auth' => array(
'dev_requirement' => false, 'dev_requirement' => false,
'replaced' => array( 'replaced' => array(
@@ -454,21 +415,6 @@
0 => 'v13.8.0', 0 => 'v13.8.0',
), ),
), ),
'kodova/hamcrest-php' => array(
'dev_requirement' => true,
'replaced' => array(
0 => '*',
),
),
'laravel/agent-detector' => array(
'pretty_version' => 'v2.0.2',
'version' => '2.0.2.0',
'reference' => '90694b9256099591cf9e55d08c18ba7a00bf099f',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/agent-detector',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/framework' => array( 'laravel/framework' => array(
'pretty_version' => 'v13.8.0', 'pretty_version' => 'v13.8.0',
'version' => '13.8.0.0', 'version' => '13.8.0.0',
@@ -481,39 +427,12 @@
'laravel/laravel' => array( 'laravel/laravel' => array(
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'aa4e582925ec155fa08dbeb9bf4e3a97b4751df1', 'reference' => '90093a086b35dce8edd97fce09d32d2be42853e8',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'laravel/pail' => array(
'pretty_version' => 'v1.2.6',
'version' => '1.2.6.0',
'reference' => 'aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/pail',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/pao' => array(
'pretty_version' => 'v1.0.6',
'version' => '1.0.6.0',
'reference' => '02f62a64c2b60af44a418ee490fee193590d8269',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/pao',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/pint' => array(
'pretty_version' => 'v1.29.1',
'version' => '1.29.1.0',
'reference' => '0770e9b7fafd50d4586881d456d6eb41c9247a80',
'type' => 'project',
'install_path' => __DIR__ . '/../laravel/pint',
'aliases' => array(),
'dev_requirement' => true,
),
'laravel/prompts' => array( 'laravel/prompts' => array(
'pretty_version' => 'v0.3.17', 'pretty_version' => 'v0.3.17',
'version' => '0.3.17.0', 'version' => '0.3.17.0',
@@ -613,15 +532,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'mockery/mockery' => array(
'pretty_version' => '1.6.12',
'version' => '1.6.12.0',
'reference' => '1f4efdd7d3beafe9807b08156dfcb176d18f1699',
'type' => 'library',
'install_path' => __DIR__ . '/../mockery/mockery',
'aliases' => array(),
'dev_requirement' => true,
),
'monolog/monolog' => array( 'monolog/monolog' => array(
'pretty_version' => '3.10.0', 'pretty_version' => '3.10.0',
'version' => '3.10.0.0', 'version' => '3.10.0.0',
@@ -637,15 +547,6 @@
0 => '^1.0', 0 => '^1.0',
), ),
), ),
'myclabs/deep-copy' => array(
'pretty_version' => '1.13.4',
'version' => '1.13.4.0',
'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a',
'type' => 'library',
'install_path' => __DIR__ . '/../myclabs/deep-copy',
'aliases' => array(),
'dev_requirement' => true,
),
'nesbot/carbon' => array( 'nesbot/carbon' => array(
'pretty_version' => '3.11.4', 'pretty_version' => '3.11.4',
'version' => '3.11.4.0', 'version' => '3.11.4.0',
@@ -682,15 +583,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'nunomaduro/collision' => array(
'pretty_version' => 'v8.9.4',
'version' => '8.9.4.0',
'reference' => '716af8f95a470e9094cfca09ed897b023be191a5',
'type' => 'library',
'install_path' => __DIR__ . '/../nunomaduro/collision',
'aliases' => array(),
'dev_requirement' => true,
),
'nunomaduro/termwind' => array( 'nunomaduro/termwind' => array(
'pretty_version' => 'v2.4.0', 'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0', 'version' => '2.4.0.0',
@@ -700,24 +592,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'phar-io/manifest' => array(
'pretty_version' => '2.0.4',
'version' => '2.0.4.0',
'reference' => '54750ef60c58e43759730615a392c31c80e23176',
'type' => 'library',
'install_path' => __DIR__ . '/../phar-io/manifest',
'aliases' => array(),
'dev_requirement' => true,
),
'phar-io/version' => array(
'pretty_version' => '3.2.1',
'version' => '3.2.1.0',
'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74',
'type' => 'library',
'install_path' => __DIR__ . '/../phar-io/version',
'aliases' => array(),
'dev_requirement' => true,
),
'php-di/invoker' => array( 'php-di/invoker' => array(
'pretty_version' => '2.3.7', 'pretty_version' => '2.3.7',
'version' => '2.3.7.0', 'version' => '2.3.7.0',
@@ -745,60 +619,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'phpunit/php-code-coverage' => array(
'pretty_version' => '12.5.6',
'version' => '12.5.6.0',
'reference' => '876099a072646c7745f673d7aeab5382c4439691',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-file-iterator' => array(
'pretty_version' => '6.0.1',
'version' => '6.0.1.0',
'reference' => '3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-file-iterator',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-invoker' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => '12b54e689b07a25a9b41e57736dfab6ec9ae5406',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-invoker',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-text-template' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => 'e1367a453f0eda562eedb4f659e13aa900d66c53',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-text-template',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-timer' => array(
'pretty_version' => '8.0.0',
'version' => '8.0.0.0',
'reference' => 'f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-timer',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/phpunit' => array(
'pretty_version' => '12.5.24',
'version' => '12.5.24.0',
'reference' => 'd75dd30597caa80e72fad2ef7904601a30ef1046',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/phpunit',
'aliases' => array(),
'dev_requirement' => true,
),
'psr/cache' => array( 'psr/cache' => array(
'pretty_version' => '3.0.0', 'pretty_version' => '3.0.0',
'version' => '3.0.0.0', 'version' => '3.0.0.0',
@@ -1027,123 +847,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'sebastian/cli-parser' => array(
'pretty_version' => '4.2.0',
'version' => '4.2.0.0',
'reference' => '90f41072d220e5c40df6e8635f5dafba2d9d4d04',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/cli-parser',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/comparator' => array(
'pretty_version' => '7.1.6',
'version' => '7.1.6.0',
'reference' => 'c769009dee98f494e0edc3fd4f4087501688f11e',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/comparator',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/complexity' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => 'bad4316aba5303d0221f43f8cee37eb58d384bbb',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/complexity',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/diff' => array(
'pretty_version' => '7.0.0',
'version' => '7.0.0.0',
'reference' => '7ab1ea946c012266ca32390913653d844ecd085f',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/diff',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/environment' => array(
'pretty_version' => '8.1.0',
'version' => '8.1.0.0',
'reference' => 'b121608b28a13f721e76ffbbd386d08eff58f3f6',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/environment',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/exporter' => array(
'pretty_version' => '7.0.2',
'version' => '7.0.2.0',
'reference' => '016951ae10980765e4e7aee491eb288c64e505b7',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/exporter',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/global-state' => array(
'pretty_version' => '8.0.2',
'version' => '8.0.2.0',
'reference' => 'ef1377171613d09edd25b7816f05be8313f9115d',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/global-state',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/lines-of-code' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => '97ffee3bcfb5805568d6af7f0f893678fc076d2f',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/lines-of-code',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/object-enumerator' => array(
'pretty_version' => '7.0.0',
'version' => '7.0.0.0',
'reference' => '1effe8e9b8e068e9ae228e542d5d11b5d16db894',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-enumerator',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/object-reflector' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => '4bfa827c969c98be1e527abd576533293c634f6a',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-reflector',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/recursion-context' => array(
'pretty_version' => '7.0.1',
'version' => '7.0.1.0',
'reference' => '0b01998a7d5b1f122911a66bebcb8d46f0c82d8c',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/recursion-context',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/type' => array(
'pretty_version' => '6.0.3',
'version' => '6.0.3.0',
'reference' => 'e549163b9760b8f71f191651d22acf32d56d6d4d',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/type',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/version' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => '3e6ccf7657d4f0a59200564b08cead899313b53c',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/version',
'aliases' => array(),
'dev_requirement' => true,
),
'spatie/laravel-package-tools' => array( 'spatie/laravel-package-tools' => array(
'pretty_version' => '1.93.0', 'pretty_version' => '1.93.0',
'version' => '1.93.0.0', 'version' => '1.93.0.0',
@@ -1168,15 +871,6 @@
0 => '*', 0 => '*',
), ),
), ),
'staabm/side-effects-detector' => array(
'pretty_version' => '1.0.5',
'version' => '1.0.5.0',
'reference' => 'd8334211a140ce329c13726d4a715adbddd0a163',
'type' => 'library',
'install_path' => __DIR__ . '/../staabm/side-effects-detector',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/clock' => array( 'symfony/clock' => array(
'pretty_version' => 'v8.0.8', 'pretty_version' => 'v8.0.8',
'version' => '8.0.8.0', 'version' => '8.0.8.0',
@@ -1468,15 +1162,6 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'theseer/tokenizer' => array(
'pretty_version' => '2.0.1',
'version' => '2.0.1.0',
'reference' => '7989e43bf381af0eac72e4f0ca5bcbfa81658be4',
'type' => 'library',
'install_path' => __DIR__ . '/../theseer/tokenizer',
'aliases' => array(),
'dev_requirement' => true,
),
'tijsverkoyen/css-to-inline-styles' => array( 'tijsverkoyen/css-to-inline-styles' => array(
'pretty_version' => 'v2.4.0', 'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0', 'version' => '2.4.0.0',
-209
View File
@@ -1,209 +0,0 @@
# CHANGELOG
## [Unreleased](https://github.com/FakerPHP/Faker/compare/v1.24.0...1.24.1)
- Removed domain `gmail.com.au` from `Provider\en_AU\Internet` (#886)
## [2024-11-09, v1.24.0](https://github.com/FakerPHP/Faker/compare/v1.23.1..v1.24.0)
- Fix internal deprecations in Doctrine's populator by @gnutix in (#889)
- Fix mobile phone number pattern for France by @ker0x in (#859)
- PHP 8.4 Support by @Jubeki in (#904)
- Added support for PHP 8.4 (#904)
## [2023-09-29, v1.23.1](https://github.com/FakerPHP/Faker/compare/v1.23.0..v1.23.1)
- Fixed double `а` female lastName in `ru_RU/Person::name()` (#832)
- Fixed polish license plates (#685)
- Stopped using `static` in callables in `Provider\pt_BR\PhoneNumber` (#785)
- Fixed incorrect female name (#794)
- Stopped using the deprecated `MT_RAND_PHP` constant to seed the random generator on PHP 8.3 (#844)
## [2023-06-12, v1.23.0](https://github.com/FakerPHP/Faker/compare/v1.22.0..v1.23.0)
- Update `randomElements` to return random number of elements when no count is provided (#658)
## [2023-05-14, v1.22.0](https://github.com/FakerPHP/Faker/compare/v1.21.0..v1.22.0)
- Fixed `randomElements()` to accept empty iterator (#605)
- Added support for passing an `Enum` to `randomElement()` and `randomElements()` (#620)
- Started rejecting invalid arguments passed to `randomElement()` and `randomElements()` (#642)
## [2022-12-13, v1.21.0](https://github.com/FakerPHP/Faker/compare/v1.20.0..v1.21.0)
- Dropped support for PHP 7.1, 7.2, and 7.3 (#543)
- Added support for PHP 8.2 (#528)
## [2022-07-20, v1.20.0](https://github.com/FakerPHP/Faker/compare/v1.19.0..v1.20.0)
- Fixed typo in French phone number (#452)
- Fixed some Hungarian naming bugs (#451)
- Fixed bug where the NL-BE VAT generation was incorrect (#455)
- Improve Turkish phone numbers for E164 and added landline support (#460)
- Add Microsoft Edge User Agent (#464)
- Added option to set image formats on Faker\Provider\Image (#473)
- Added support for French color translations (#466)
- Support filtering timezones by country code (#480)
- Fixed typo in some greek names (#490)
- Marked the Faker\Provider\Image as deprecated
## [2022-02-02, v1.19.0](https://github.com/FakerPHP/Faker/compare/v1.18.0..v1.19.0)
- Added color extension to core (#442)
- Added conflict with `doctrine/persistence` below version `1.4`
- Fix for support on different Doctrine ORM versions (#414)
- Fix usage of `Doctrine\Persistence` dependency
- Fix CZ Person birthNumber docblock return type (#437)
- Fix is_IS Person docbock types (#439)
- Fix is_IS Address docbock type (#438)
- Fix regexify escape backslash in character class (#434)
- Removed UUID from Generator to be able to extend it (#441)
## [2022-01-23, v1.18.0](https://github.com/FakerPHP/Faker/compare/v1.17.0..v1.18.0)
- Deprecated UUID, use uuid3 to specify version (#427)
- Reset formatters when adding a new provider (#366)
- Helper methods to use our custom generators (#155)
- Set allow-plugins for Composer 2.2 (#405)
- Fix kk_KZ\Person::individualIdentificationNumber generation (#411)
- Allow for -> syntax to be used in parsing (#423)
- Person->name was missing string return type (#424)
- Generate a valid BE TAX number (#415)
- Added the UUID extension to Core (#427)
## [2021-12-05, v1.17.0](https://github.com/FakerPHP/Faker/compare/v1.16.0..v1.17.0)
- Partial PHP 8.1 compatibility (#373)
- Add payment provider for `ne_NP` locale (#375)
- Add Egyptian Arabic `ar_EG` locale (#377)
- Updated list of South African TLDs (#383)
- Fixed formatting of E.164 numbers (#380)
- Allow `symfony/deprecation-contracts` `^3.0` (#397)
## [2021-09-06, v1.16.0](https://github.com/FakerPHP/Faker/compare/v1.15.0..v1.16.0)
- Add Company extension
- Add Address extension
- Add Person extension
- Add PhoneNumber extension
- Add VersionExtension (#350)
- Stricter types in Extension\Container and Extension\GeneratorAwareExtension (#345)
- Fix deprecated property access in `nl_NL` (#348)
- Add support for `psr/container` >= 2.0 (#354)
- Add missing union types in Faker\Generator (#352)
## [2021-07-06, v1.15.0](https://github.com/FakerPHP/Faker/compare/v1.14.1..v1.15.0)
- Updated the generator phpdoc to help identify magic methods (#307)
- Prevent direct access and triggered deprecation warning for "word" (#302)
- Updated length on all global e164 numbers (#301)
- Updated last names from different source (#312)
- Don't generate birth number of '000' for Swedish personal identity (#306)
- Add job list for localization id_ID (#339)
## [2021-03-30, v1.14.1](https://github.com/FakerPHP/Faker/compare/v1.14.0..v1.14.1)
- Fix where randomNumber and randomFloat would return a 0 value (#291 / #292)
## [2021-03-29, v1.14.0](https://github.com/FakerPHP/Faker/compare/v1.13.0..v1.14.0)
- Fix for realText to ensure the text keeps closer to its boundaries (#152)
- Fix where regexify produces a random character instead of a literal dot (#135
- Deprecate zh_TW methods that only call base methods (#122)
- Add used extensions to composer.json as suggestion (#120)
- Moved TCNo and INN from calculator to localized providers (#108)
- Fix regex dot/backslash issue where a dot is replaced with a backslash as escape character (#206)
- Deprecate direct property access (#164)
- Added test to assert unique() behaviour (#233)
- Added RUC for the es_PE locale (#244)
- Test IBAN formats for Latin America (AR/PE/VE) (#260)
- Added VAT number for en_GB (#255)
- Added new districts for the ne_NP locale (#258)
- Fix for U.S. Area Code Generation (#261)
- Fix in numerify where a better random numeric value is guaranteed (#256)
- Fix e164PhoneNumber to only generate valid phone numbers with valid country codes (#264)
- Extract fixtures into separate classes (#234)
- Remove french domains that no longer exists (#277)
- Fix error that occurs when getting a polish title (#279)
- Use valid area codes for North America E164 phone numbers (#280)
- Adding support for extensions and PSR-11 (#154)
- Adding trait for GeneratorAwareExtension (#165)
- Added helper class for extension (#162)
- Added blood extension to core (#232)
- Added barcode extension to core (#252)
- Added number extension (#257)
- Various code style updates
- Added a note about our breaking change promise (#273)
## [2020-12-18, v1.13.0](https://github.com/FakerPHP/Faker/compare/v1.12.1..v1.13.0)
Several fixes and new additions in this release. A lot of cleanup has been done
on the codebase on both tests and consistency.
- Feature/pl pl license plate (#62)
- Fix greek phone numbers (#16)
- Move AT payment provider logic to de_AT (#72)
- Fix wiktionary links (#73)
- Fix AT person links (#74)
- Fix AT cities (#75)
- Deprecate at_AT providers (#78)
- Add Austrian `ssn()` to `Person` provider (#79)
- Fix typos in id_ID Address (#83)
- Austrian post codes (#86)
- Updated Polish data (#70)
- Improve Austrian social security number generation (#88)
- Move US phone numbers with extension to own method (#91)
- Add UK National Insurance number generator (#89)
- Fix en_SG phone number generator (#100)
- Remove usage of mt_rand (#87)
- Remove whitespace from beginning of el_GR phone numbers (#105)
- Building numbers can not be 0, 00, 000 (#107)
- Add 172.16/12 local IPv4 block (#121)
- Add JCB credit card type (#124)
- Remove json_decode from emoji generation (#123)
- Remove ro street address (#146)
## [2020-12-11, v1.12.1](https://github.com/FakerPHP/Faker/compare/v1.12.0..v1.12.1)
This is a security release that prevents a hacker to execute code on the server.
## [2020-11-23, v1.12.0](https://github.com/FakerPHP/Faker/compare/v1.11.0..v1.12.0)
- Fix ro_RO first and last day of year calculation offset (#65)
- Fix en_NG locale test namespaces that did not match PSR-4 (#57)
- Added Singapore NRIC/FIN provider (#56)
- Added provider for Lithuanian municipalities (#58)
- Added blood types provider (#61)
## [2020-11-15, v1.11.0](https://github.com/FakerPHP/Faker/compare/v1.10.1..v1.11.0)
- Added Provider for Swedish Municipalities
- Updates to person names in pt_BR
- Many code style changes
## [2020-10-28, v1.10.1](https://github.com/FakerPHP/Faker/compare/v1.10.0..v1.10.1)
- Updates the Danish addresses in dk_DK
- Removed offense company names in nl_NL
- Clarify changelog with original fork
- Standin replacement for LoremPixel to Placeholder.com (#11)
## [2020-10-27, v1.10.0](https://github.com/FakerPHP/Faker/compare/v1.9.1..v1.10.0)
- Support PHP 7.1-8.0
- Fix typo in de_DE Company Provider
- Fix dateTimeThisYear method
- Fix typo in de_DE jobTitleFormat
- Fix IBAN generation for CR
- Fix typos in greek first names
- Fix US job title typo
- Do not clear entity manager for doctrine orm populator
- Remove persian rude words
- Corrections to RU names
## 2020-10-27, v1.9.1
- Initial version. Same as `fzaninotto/Faker:v1.9.1`.
-22
View File
@@ -1,22 +0,0 @@
Copyright (c) 2011 François Zaninotto
Portions Copyright (c) 2008 Caius Durling
Portions Copyright (c) 2008 Adam Royle
Portions Copyright (c) 2008 Fiona Burrows
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-114
View File
@@ -1,114 +0,0 @@
<p style="text-align: center"><img src="https://github.com/FakerPHP/Artwork/raw/main/src/socialcard.png" alt="Social card of FakerPHP"></p>
# Faker
[![Packagist Downloads](https://img.shields.io/packagist/dm/FakerPHP/Faker)](https://packagist.org/packages/fakerphp/faker)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/FakerPHP/Faker/Tests/main)](https://github.com/FakerPHP/Faker/actions)
[![Type Coverage](https://shepherd.dev/github/FakerPHP/Faker/coverage.svg)](https://shepherd.dev/github/FakerPHP/Faker)
[![Code Coverage](https://codecov.io/gh/FakerPHP/Faker/branch/main/graph/badge.svg)](https://codecov.io/gh/FakerPHP/Faker)
Faker is a PHP library that generates fake data for you. Whether you need to bootstrap your database, create good-looking XML documents, fill-in your persistence to stress test it, or anonymize data taken from a production service, Faker is for you.
It's heavily inspired by Perl's [Data::Faker](https://metacpan.org/pod/Data::Faker), and by Ruby's [Faker](https://rubygems.org/gems/faker).
## Getting Started
### Installation
Faker requires PHP >= 7.4.
```shell
composer require fakerphp/faker
```
### Documentation
Full documentation can be found over on [fakerphp.github.io](https://fakerphp.github.io).
### Basic Usage
Use `Faker\Factory::create()` to create and initialize a Faker generator, which can generate data by accessing methods named after the type of data you want.
```php
<?php
require_once 'vendor/autoload.php';
// use the factory to create a Faker\Generator instance
$faker = Faker\Factory::create();
// generate data by calling methods
echo $faker->name();
// 'Vince Sporer'
echo $faker->email();
// 'walter.sophia@hotmail.com'
echo $faker->text();
// 'Numquam ut mollitia at consequuntur inventore dolorem.'
```
Each call to `$faker->name()` yields a different (random) result. This is because Faker uses `__call()` magic, and forwards `Faker\Generator->$method()` calls to `Faker\Generator->format($method, $attributes)`.
```php
<?php
for ($i = 0; $i < 3; $i++) {
echo $faker->name() . "\n";
}
// 'Cyrus Boyle'
// 'Alena Cummerata'
// 'Orlo Bergstrom'
```
## Automated refactoring
If you already used this library with its properties, they are now deprecated and needs to be replaced by their equivalent methods.
You can use the provided [Rector](https://github.com/rectorphp/rector) config file to automate the work.
Run
```bash
composer require --dev rector/rector
```
to install `rector/rector`.
Run
```bash
vendor/bin/rector process src/ --config vendor/fakerphp/faker/rector-migrate.php
```
to run `rector/rector`.
*Note:* do not forget to replace `src/` with the path to your source directory.
Alternatively, import the configuration in your `rector.php` file:
```php
<?php
declare(strict_types=1);
use Rector\Config;
return static function (Config\RectorConfig $rectorConfig): void {
$rectorConfig->import('vendor/fakerphp/faker/rector-migrate.php');
};
```
## License
Faker is released under the MIT License. See [`LICENSE`](LICENSE) for details.
## Backward compatibility promise
Faker is using [Semver](https://semver.org/). This means that versions are tagged
with MAJOR.MINOR.PATCH. Only a new major version will be allowed to break backward
compatibility (BC).
Classes marked as `@experimental` or `@internal` are not included in our backward compatibility promise.
You are also not guaranteed that the value returned from a method is always the
same. You are guaranteed that the data type will not change.
PHP 8 introduced [named arguments](https://wiki.php.net/rfc/named_params), which
increased the cost and reduces flexibility for package maintainers. The names of the
arguments for methods in Faker is not included in our BC promise.
-56
View File
@@ -1,56 +0,0 @@
{
"name": "fakerphp/faker",
"type": "library",
"description": "Faker is a PHP library that generates fake data for you.",
"keywords": [
"faker",
"fixtures",
"data"
],
"license": "MIT",
"authors": [
{
"name": "François Zaninotto"
}
],
"require": {
"php": "^7.4 || ^8.0",
"psr/container": "^1.0 || ^2.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"require-dev": {
"ext-intl": "*",
"bamarni/composer-bin-plugin": "^1.4.1",
"doctrine/persistence": "^1.3 || ^2.0",
"phpunit/phpunit": "^9.5.26",
"symfony/phpunit-bridge": "^5.4.16"
},
"autoload": {
"psr-4": {
"Faker\\": "src/Faker/"
}
},
"autoload-dev": {
"psr-4": {
"Faker\\Test\\": "test/Faker/",
"Faker\\Test\\Fixture\\": "test/Fixture/"
}
},
"conflict": {
"fzaninotto/faker": "*"
},
"suggest": {
"ext-curl": "Required by Faker\\Provider\\Image to download images.",
"ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
"ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
"ext-mbstring": "Required for multibyte Unicode string functionality.",
"doctrine/orm": "Required to use Faker\\ORM\\Doctrine"
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true,
"composer/package-versions-deprecated": true
},
"sort-packages": true
}
}
-161
View File
@@ -1,161 +0,0 @@
<?php
declare(strict_types=1);
use Faker\Generator;
use Rector\Config;
use Rector\Transform;
// This file configures rector/rector to replace all deprecated property usages with their equivalent functions.
return static function (Config\RectorConfig $rectorConfig): void {
$properties = [
'address',
'amPm',
'asciify',
'biasedNumberBetween',
'boolean',
'bothify',
'buildingNumber',
'century',
'chrome',
'city',
'citySuffix',
'colorName',
'company',
'companyEmail',
'companySuffix',
'country',
'countryCode',
'countryISOAlpha3',
'creditCardDetails',
'creditCardExpirationDate',
'creditCardExpirationDateString',
'creditCardNumber',
'creditCardType',
'currencyCode',
'date',
'dateTime',
'dateTimeAD',
'dateTimeBetween',
'dateTimeInInterval',
'dateTimeThisCentury',
'dateTimeThisDecade',
'dateTimeThisMonth',
'dateTimeThisYear',
'dayOfMonth',
'dayOfWeek',
'domainName',
'domainWord',
'e164PhoneNumber',
'email',
'emoji',
'file',
'firefox',
'firstName',
'firstNameFemale',
'firstNameMale',
'freeEmail',
'freeEmailDomain',
'getDefaultTimezone',
'hexColor',
'hslColor',
'hslColorAsArray',
'iban',
'image',
'imageUrl',
'imei',
'internetExplorer',
'iosMobileToken',
'ipv4',
'ipv6',
'iso8601',
'jobTitle',
'languageCode',
'lastName',
'latitude',
'lexify',
'linuxPlatformToken',
'linuxProcessor',
'localCoordinates',
'localIpv4',
'locale',
'longitude',
'macAddress',
'macPlatformToken',
'macProcessor',
'md5',
'month',
'monthName',
'msedge',
'name',
'numerify',
'opera',
'paragraph',
'paragraphs',
'passthrough',
'password',
'phoneNumber',
'postcode',
'randomAscii',
'randomDigitNotNull',
'randomElement',
'randomElements',
'randomHtml',
'randomKey',
'randomLetter',
'realText',
'realTextBetween',
'regexify',
'rgbColor',
'rgbColorAsArray',
'rgbCssColor',
'rgbaCssColor',
'safari',
'safeColorName',
'safeEmail',
'safeEmailDomain',
'safeHexColor',
'sentence',
'sentences',
'setDefaultTimezone',
'sha1',
'sha256',
'shuffle',
'shuffleArray',
'shuffleString',
'slug',
'streetAddress',
'streetName',
'streetSuffix',
'swiftBicNumber',
'text',
'time',
'timezone',
'title',
'titleFemale',
'titleMale',
'tld',
'toLower',
'toUpper',
'unixTime',
'url',
'userAgent',
'userName',
'uuid',
'windowsPlatformToken',
'word',
'words',
'year',
];
$rectorConfig->ruleWithConfiguration(
Transform\Rector\Assign\PropertyFetchToMethodCallRector::class,
array_map(static function (string $property): Transform\ValueObject\PropertyFetchToMethodCall {
return new Transform\ValueObject\PropertyFetchToMethodCall(
Generator::class,
$property,
$property,
);
}, $properties),
);
};
-50
View File
@@ -1,50 +0,0 @@
<?php
namespace Faker\Calculator;
/**
* Utility class for validating EAN-8 and EAN-13 numbers
*/
class Ean
{
/**
* @var string EAN validation pattern
*/
public const PATTERN = '/^(?:\d{8}|\d{13})$/';
/**
* Computes the checksum of an EAN number.
*
* @see https://en.wikipedia.org/wiki/International_Article_Number
*
* @return int
*/
public static function checksum(string $digits)
{
$sequence = (strlen($digits) + 1) === 8 ? [3, 1] : [1, 3];
$sums = 0;
foreach (str_split($digits) as $n => $digit) {
$sums += ((int) $digit) * $sequence[$n % 2];
}
return (10 - $sums % 10) % 10;
}
/**
* Checks whether the provided number is an EAN compliant number and that
* the checksum is correct.
*
* @param string $ean An EAN number
*
* @return bool
*/
public static function isValid(string $ean)
{
if (!preg_match(self::PATTERN, $ean)) {
return false;
}
return self::checksum(substr($ean, 0, -1)) === (int) substr($ean, -1);
}
}
-69
View File
@@ -1,69 +0,0 @@
<?php
namespace Faker\Calculator;
class Iban
{
/**
* Generates IBAN Checksum
*
* @return string Checksum (numeric string)
*/
public static function checksum(string $iban)
{
// Move first four digits to end and set checksum to '00'
$checkString = substr($iban, 4) . substr($iban, 0, 2) . '00';
// Replace all letters with their number equivalents
$checkString = preg_replace_callback(
'/[A-Z]/',
static function (array $matches): string {
return (string) self::alphaToNumber($matches[0]);
},
$checkString,
);
// Perform mod 97 and subtract from 98
$checksum = 98 - self::mod97($checkString);
return str_pad($checksum, 2, '0', STR_PAD_LEFT);
}
/**
* Converts letter to number
*
* @return int
*/
public static function alphaToNumber(string $char)
{
return ord($char) - 55;
}
/**
* Calculates mod97 on a numeric string
*
* @param string $number Numeric string
*
* @return int
*/
public static function mod97(string $number)
{
$checksum = (int) $number[0];
for ($i = 1, $size = strlen($number); $i < $size; ++$i) {
$checksum = (10 * $checksum + (int) $number[$i]) % 97;
}
return $checksum;
}
/**
* Checks whether an IBAN has a valid checksum
*
* @return bool
*/
public static function isValid(string $iban)
{
return self::checksum($iban) === substr($iban, 2, 2);
}
}
-42
View File
@@ -1,42 +0,0 @@
<?php
namespace Faker\Calculator;
/**
* @deprecated moved to ru_RU\Company, use {@link \Faker\Provider\ru_RU\Company}.
* @see \Faker\Provider\ru_RU\Company
*/
class Inn
{
/**
* Generates INN Checksum
*
* https://ru.wikipedia.org/wiki/%D0%98%D0%B4%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%BE%D0%BC%D0%B5%D1%80_%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3%D0%BE%D0%BF%D0%BB%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%89%D0%B8%D0%BA%D0%B0
*
* @param string $inn
*
* @return string Checksum (one digit)
*
* @deprecated use {@link \Faker\Provider\ru_RU\Company::inn10Checksum()} instead
* @see \Faker\Provider\ru_RU\Company::inn10Checksum()
*/
public static function checksum($inn)
{
return \Faker\Provider\ru_RU\Company::inn10Checksum($inn);
}
/**
* Checks whether an INN has a valid checksum
*
* @param string $inn
*
* @return bool
*
* @deprecated use {@link \Faker\Provider\ru_RU\Company::inn10IsValid()} instead
* @see \Faker\Provider\ru_RU\Company::inn10IsValid()
*/
public static function isValid($inn)
{
return \Faker\Provider\ru_RU\Company::inn10IsValid($inn);
}
}
-60
View File
@@ -1,60 +0,0 @@
<?php
namespace Faker\Calculator;
/**
* Utility class for validating ISBN-10
*/
class Isbn
{
/**
* @var string ISBN-10 validation pattern
*/
public const PATTERN = '/^\d{9}[0-9X]$/';
/**
* ISBN-10 check digit
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digits
*
* @param string $input ISBN without check-digit
*
* @throws \LengthException When wrong input length passed
*/
public static function checksum(string $input): string
{
// We're calculating check digit for ISBN-10
// so, the length of the input should be 9
$length = 9;
if (strlen($input) !== $length) {
throw new \LengthException(sprintf('Input length should be equal to %d', $length));
}
$digits = str_split($input);
array_walk(
$digits,
static function (&$digit, $position): void {
$digit = (10 - $position) * $digit;
},
);
$result = (11 - array_sum($digits) % 11) % 11;
// 10 is replaced by X
return ($result < 10) ? (string) $result : 'X';
}
/**
* Checks whether the provided number is a valid ISBN-10 number
*
* @param string $isbn ISBN to check
*/
public static function isValid(string $isbn): bool
{
if (!preg_match(self::PATTERN, $isbn)) {
return false;
}
return self::checksum(substr($isbn, 0, -1)) === substr($isbn, -1);
}
}
-72
View File
@@ -1,72 +0,0 @@
<?php
namespace Faker\Calculator;
/**
* Utility class for generating and validating Luhn numbers.
*
* Luhn algorithm is used to validate credit card numbers, IMEI numbers, and
* National Provider Identifier numbers.
*
* @see http://en.wikipedia.org/wiki/Luhn_algorithm
*/
class Luhn
{
/**
* @return int
*/
private static function checksum(string $number)
{
$number = (string) $number;
$length = strlen($number);
$sum = 0;
for ($i = $length - 1; $i >= 0; $i -= 2) {
$sum += $number[$i];
}
for ($i = $length - 2; $i >= 0; $i -= 2) {
$sum += array_sum(str_split($number[$i] * 2));
}
return $sum % 10;
}
/**
* @return string
*/
public static function computeCheckDigit(string $partialNumber)
{
$checkDigit = self::checksum($partialNumber . '0');
if ($checkDigit === 0) {
return '0';
}
return (string) (10 - $checkDigit);
}
/**
* Checks whether a number (partial number + check digit) is Luhn compliant
*
* @return bool
*/
public static function isValid(string $number)
{
return self::checksum($number) === 0;
}
/**
* Generate a Luhn compliant number.
*
* @return string
*/
public static function generateLuhnNumber(string $partialValue)
{
if (!preg_match('/^\d+$/', $partialValue)) {
throw new \InvalidArgumentException('Argument should be an integer.');
}
return $partialValue . Luhn::computeCheckDigit($partialValue);
}
}
-43
View File
@@ -1,43 +0,0 @@
<?php
namespace Faker\Calculator;
/**
* @deprecated moved to tr_TR\Person, use {@link \Faker\Provider\tr_TR\Person}.
* @see \Faker\Provider\tr_TR\Person
*/
class TCNo
{
/**
* Generates Turkish Identity Number Checksum
* Gets first 9 digit as prefix and calculates checksum
*
* https://en.wikipedia.org/wiki/Turkish_Identification_Number
*
* @param string $identityPrefix
*
* @return string Checksum (two digit)
*
* @deprecated use {@link \Faker\Provider\tr_TR\Person::tcNoChecksum()} instead
* @see \Faker\Provider\tr_TR\Person::tcNoChecksum()
*/
public static function checksum($identityPrefix)
{
return \Faker\Provider\tr_TR\Person::tcNoChecksum($identityPrefix);
}
/**
* Checks whether a TCNo has a valid checksum
*
* @param string $tcNo
*
* @return bool
*
* @deprecated use {@link \Faker\Provider\tr_TR\Person::tcNoIsValid()} instead
* @see \Faker\Provider\tr_TR\Person::tcNoIsValid()
*/
public static function isValid($tcNo)
{
return \Faker\Provider\tr_TR\Person::tcNoIsValid($tcNo);
}
}
-60
View File
@@ -1,60 +0,0 @@
<?php
namespace Faker;
use Faker\Extension\Extension;
/**
* This generator returns a default value for all called properties
* and methods. It works with Faker\Generator::optional().
*
* @mixin Generator
*/
class ChanceGenerator
{
private $generator;
private $weight;
protected $default;
/**
* @param Extension|Generator $generator
*/
public function __construct($generator, float $weight, $default = null)
{
$this->default = $default;
$this->generator = $generator;
$this->weight = $weight;
}
public function ext(string $id)
{
return new self($this->generator->ext($id), $this->weight, $this->default);
}
/**
* Catch and proxy all generator calls but return only valid values
*
* @param string $attribute
*
* @deprecated Use a method instead.
*/
public function __get($attribute)
{
trigger_deprecation('fakerphp/faker', '1.14', 'Accessing property "%s" is deprecated, use "%s()" instead.', $attribute, $attribute);
return $this->__call($attribute, []);
}
/**
* @param string $name
* @param array $arguments
*/
public function __call($name, $arguments)
{
if (mt_rand(1, 100) <= (100 * $this->weight)) {
return call_user_func_array([$this->generator, $name], $arguments);
}
return $this->default;
}
}
-139
View File
@@ -1,139 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Faker\Extension\Extension;
/**
* A simple implementation of a container.
*
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Container implements ContainerInterface
{
/**
* @var array<string, callable|object|string>
*/
private array $definitions;
private array $services = [];
/**
* Create a container object with a set of definitions. The array value MUST
* produce an object that implements Extension.
*
* @param array<string, callable|object|string> $definitions
*/
public function __construct(array $definitions)
{
$this->definitions = $definitions;
}
/**
* Retrieve a definition from the container.
*
* @param string $id
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
* @throws ContainerException
* @throws NotInContainerException
*/
public function get($id): Extension
{
if (!is_string($id)) {
throw new \InvalidArgumentException(sprintf(
'First argument of %s::get() must be string',
self::class,
));
}
if (array_key_exists($id, $this->services)) {
return $this->services[$id];
}
if (!$this->has($id)) {
throw new NotInContainerException(sprintf(
'There is not service with id "%s" in the container.',
$id,
));
}
$definition = $this->definitions[$id];
$service = $this->getService($id, $definition);
if (!$service instanceof Extension) {
throw new \RuntimeException(sprintf(
'Service resolved for identifier "%s" does not implement the %s" interface.',
$id,
Extension::class,
));
}
$this->services[$id] = $service;
return $service;
}
/**
* Get the service from a definition.
*
* @param callable|object|string $definition
*/
private function getService(string $id, $definition)
{
if (is_callable($definition)) {
try {
return $definition();
} catch (\Throwable $e) {
throw new ContainerException(
sprintf('Error while invoking callable for "%s"', $id),
0,
$e,
);
}
} elseif (is_object($definition)) {
return $definition;
} elseif (is_string($definition)) {
if (class_exists($definition)) {
try {
return new $definition();
} catch (\Throwable $e) {
throw new ContainerException(sprintf('Could not instantiate class "%s"', $id), 0, $e);
}
}
throw new ContainerException(sprintf(
'Could not instantiate class "%s". Class was not found.',
$id,
));
} else {
throw new ContainerException(sprintf(
'Invalid type for definition with id "%s"',
$id,
));
}
}
/**
* Check if the container contains a given identifier.
*
* @param string $id
*
* @throws \InvalidArgumentException
*/
public function has($id): bool
{
if (!is_string($id)) {
throw new \InvalidArgumentException(sprintf(
'First argument of %s::get() must be string',
self::class,
));
}
return array_key_exists($id, $this->definitions);
}
}
@@ -1,68 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class ContainerBuilder
{
/**
* @var array<string, callable|object|string>
*/
private array $definitions = [];
/**
* @param callable|object|string $definition
*
* @throws \InvalidArgumentException
*/
public function add(string $id, $definition): self
{
if (!is_string($definition) && !is_callable($definition) && !is_object($definition)) {
throw new \InvalidArgumentException(sprintf(
'First argument to "%s::add()" must be a string, callable or object.',
self::class,
));
}
$this->definitions[$id] = $definition;
return $this;
}
public function build(): ContainerInterface
{
return new Container($this->definitions);
}
private static function defaultExtensions(): array
{
return [
Extension\BarcodeExtension::class => Core\Barcode::class,
Extension\BloodExtension::class => Core\Blood::class,
Extension\ColorExtension::class => Core\Color::class,
Extension\DateTimeExtension::class => Core\DateTime::class,
Extension\FileExtension::class => Core\File::class,
Extension\NumberExtension::class => Core\Number::class,
Extension\UuidExtension::class => Core\Uuid::class,
Extension\VersionExtension::class => Core\Version::class,
];
}
public static function withDefaultExtensions(): self
{
$instance = new self();
foreach (self::defaultExtensions() as $id => $definition) {
$instance->add($id, $definition);
}
return $instance;
}
}
@@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Psr\Container\ContainerExceptionInterface;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class ContainerException extends \RuntimeException implements ContainerExceptionInterface
{
}
@@ -1,9 +0,0 @@
<?php
namespace Faker\Container;
use Psr\Container\ContainerInterface as BaseContainerInterface;
interface ContainerInterface extends BaseContainerInterface
{
}
@@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Container;
use Psr\Container\NotFoundExceptionInterface;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class NotInContainerException extends \RuntimeException implements NotFoundExceptionInterface
{
}
-52
View File
@@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Calculator;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Barcode implements Extension\BarcodeExtension
{
private Extension\NumberExtension $numberExtension;
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
private function ean(int $length = 13): string
{
$code = Extension\Helper::numerify(str_repeat('#', $length - 1));
return sprintf('%s%s', $code, Calculator\Ean::checksum($code));
}
public function ean13(): string
{
return $this->ean();
}
public function ean8(): string
{
return $this->ean(8);
}
public function isbn10(): string
{
$code = Extension\Helper::numerify(str_repeat('#', 9));
return sprintf('%s%s', $code, Calculator\Isbn::checksum($code));
}
public function isbn13(): string
{
$code = '97' . $this->numberExtension->numberBetween(8, 9) . Extension\Helper::numerify(str_repeat('#', 9));
return sprintf('%s%s', $code, Calculator\Ean::checksum($code));
}
}
-42
View File
@@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Blood implements Extension\BloodExtension
{
/**
* @var string[]
*/
private array $bloodTypes = ['A', 'AB', 'B', 'O'];
/**
* @var string[]
*/
private array $bloodRhFactors = ['+', '-'];
public function bloodType(): string
{
return Extension\Helper::randomElement($this->bloodTypes);
}
public function bloodRh(): string
{
return Extension\Helper::randomElement($this->bloodRhFactors);
}
public function bloodGroup(): string
{
return sprintf(
'%s%s',
$this->bloodType(),
$this->bloodRh(),
);
}
}
-177
View File
@@ -1,177 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
use Faker\Extension\Helper;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Color implements Extension\ColorExtension
{
private Extension\NumberExtension $numberExtension;
/**
* @var string[]
*/
private array $safeColorNames = [
'black', 'maroon', 'green', 'navy', 'olive',
'purple', 'teal', 'lime', 'blue', 'silver',
'gray', 'yellow', 'fuchsia', 'aqua', 'white',
];
/**
* @var string[]
*/
private array $allColorNames = [
'AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine',
'Azure', 'Beige', 'Bisque', 'Black', 'BlanchedAlmond',
'Blue', 'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue',
'Chartreuse', 'Chocolate', 'Coral', 'CornflowerBlue',
'Cornsilk', 'Crimson', 'Cyan', 'DarkBlue', 'DarkCyan',
'DarkGoldenRod', 'DarkGray', 'DarkGreen', 'DarkKhaki',
'DarkMagenta', 'DarkOliveGreen', 'Darkorange', 'DarkOrchid',
'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue',
'DarkSlateGray', 'DarkTurquoise', 'DarkViolet', 'DeepPink',
'DeepSkyBlue', 'DimGray', 'DimGrey', 'DodgerBlue', 'FireBrick',
'FloralWhite', 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite',
'Gold', 'GoldenRod', 'Gray', 'Green', 'GreenYellow', 'HoneyDew',
'HotPink', 'IndianRed', 'Indigo', 'Ivory', 'Khaki', 'Lavender',
'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', 'LightCoral',
'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGreen', 'LightPink',
'LightSalmon', 'LightSeaGreen', 'LightSkyBlue', 'LightSlateGray', 'LightSteelBlue',
'LightYellow', 'Lime', 'LimeGreen', 'Linen', 'Magenta', 'Maroon', 'MediumAquaMarine',
'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue',
'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue',
'MintCream', 'MistyRose', 'Moccasin', 'NavajoWhite', 'Navy', 'OldLace', 'Olive',
'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGoldenRod', 'PaleGreen',
'PaleTurquoise', 'PaleVioletRed', 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum',
'PowderBlue', 'Purple', 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon',
'SandyBrown', 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue',
'SlateGray', 'Snow', 'SpringGreen', 'SteelBlue', 'Tan', 'Teal', 'Thistle', 'Tomato',
'Turquoise', 'Violet', 'Wheat', 'White', 'WhiteSmoke', 'Yellow', 'YellowGreen',
];
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
/**
* @example '#fa3cc2'
*/
public function hexColor(): string
{
return '#' . str_pad(dechex($this->numberExtension->numberBetween(1, 16777215)), 6, '0', STR_PAD_LEFT);
}
/**
* @example '#ff0044'
*/
public function safeHexColor(): string
{
$color = str_pad(dechex($this->numberExtension->numberBetween(0, 255)), 3, '0', STR_PAD_LEFT);
return sprintf(
'#%s%s%s%s%s%s',
$color[0],
$color[0],
$color[1],
$color[1],
$color[2],
$color[2],
);
}
/**
* @example 'array(0,255,122)'
*
* @return int[]
*/
public function rgbColorAsArray(): array
{
$color = $this->hexColor();
return [
hexdec(substr($color, 1, 2)),
hexdec(substr($color, 3, 2)),
hexdec(substr($color, 5, 2)),
];
}
/**
* @example '0,255,122'
*/
public function rgbColor(): string
{
return implode(',', $this->rgbColorAsArray());
}
/**
* @example 'rgb(0,255,122)'
*/
public function rgbCssColor(): string
{
return sprintf(
'rgb(%s)',
$this->rgbColor(),
);
}
/**
* @example 'rgba(0,255,122,0.8)'
*/
public function rgbaCssColor(): string
{
return sprintf(
'rgba(%s,%s)',
$this->rgbColor(),
$this->numberExtension->randomFloat(1, 0, 1),
);
}
/**
* @example 'blue'
*/
public function safeColorName(): string
{
return Helper::randomElement($this->safeColorNames);
}
/**
* @example 'NavajoWhite'
*/
public function colorName(): string
{
return Helper::randomElement($this->allColorNames);
}
/**
* @example '340,50,20'
*/
public function hslColor(): string
{
return sprintf(
'%s,%s,%s',
$this->numberExtension->numberBetween(0, 360),
$this->numberExtension->numberBetween(0, 100),
$this->numberExtension->numberBetween(0, 100),
);
}
/**
* @example array(340, 50, 20)
*
* @return int[]
*/
public function hslColorAsArray(): array
{
return [
$this->numberExtension->numberBetween(0, 360),
$this->numberExtension->numberBetween(0, 100),
$this->numberExtension->numberBetween(0, 100),
];
}
}
-78
View File
@@ -1,78 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Coordinates implements Extension\Extension
{
private Extension\NumberExtension $numberExtension;
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
/**
* @example '77.147489'
*
* @return float Uses signed degrees format (returns a float number between -90 and 90)
*/
public function latitude(float $min = -90.0, float $max = 90.0): float
{
if ($min < -90 || $max < -90) {
throw new \LogicException('Latitude cannot be less that -90.0');
}
if ($min > 90 || $max > 90) {
throw new \LogicException('Latitude cannot be greater that 90.0');
}
return $this->randomFloat(6, $min, $max);
}
/**
* @example '86.211205'
*
* @return float Uses signed degrees format (returns a float number between -180 and 180)
*/
public function longitude(float $min = -180.0, float $max = 180.0): float
{
if ($min < -180 || $max < -180) {
throw new \LogicException('Longitude cannot be less that -180.0');
}
if ($min > 180 || $max > 180) {
throw new \LogicException('Longitude cannot be greater that 180.0');
}
return $this->randomFloat(6, $min, $max);
}
/**
* @example array('77.147489', '86.211205')
*
* @return array{latitude: float, longitude: float}
*/
public function localCoordinates(): array
{
return [
'latitude' => $this->latitude(),
'longitude' => $this->longitude(),
];
}
private function randomFloat(int $nbMaxDecimals, float $min, float $max): float
{
if ($min > $max) {
throw new \LogicException('Invalid coordinates boundaries');
}
return $this->numberExtension->randomFloat($nbMaxDecimals, $min, $max);
}
}
-217
View File
@@ -1,217 +0,0 @@
<?php
namespace Faker\Core;
use Faker\Extension\DateTimeExtension;
use Faker\Extension\GeneratorAwareExtension;
use Faker\Extension\GeneratorAwareExtensionTrait;
use Faker\Extension\Helper;
/**
* @experimental This class is experimental and does not fall under our BC promise
*
* @since 1.20.0
*/
final class DateTime implements DateTimeExtension, GeneratorAwareExtension
{
use GeneratorAwareExtensionTrait;
/**
* @var string[]
*/
private array $centuries = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX', 'XXI'];
private ?string $defaultTimezone = null;
/**
* Get the POSIX-timestamp of a DateTime, int or string.
*
* @param \DateTime|float|int|string $until
*
* @return false|int
*/
private function getTimestamp($until = 'now')
{
if (is_numeric($until)) {
return (int) $until;
}
if ($until instanceof \DateTime) {
return $until->getTimestamp();
}
return strtotime(empty($until) ? 'now' : $until);
}
/**
* Get a DateTime created based on a POSIX-timestamp.
*
* @param int $timestamp the UNIX / POSIX-compatible timestamp
*/
private function getTimestampDateTime(int $timestamp): \DateTime
{
return new \DateTime('@' . $timestamp);
}
private function resolveTimezone(?string $timezone): string
{
if ($timezone !== null) {
return $timezone;
}
return null === $this->defaultTimezone ? date_default_timezone_get() : $this->defaultTimezone;
}
/**
* Internal method to set the timezone on a DateTime object.
*/
private function setTimezone(\DateTime $dateTime, ?string $timezone): \DateTime
{
$timezone = $this->resolveTimezone($timezone);
return $dateTime->setTimezone(new \DateTimeZone($timezone));
}
public function dateTime($until = 'now', ?string $timezone = null): \DateTime
{
return $this->setTimezone(
$this->getTimestampDateTime($this->unixTime($until)),
$timezone,
);
}
public function dateTimeAD($until = 'now', ?string $timezone = null): \DateTime
{
$min = (PHP_INT_SIZE > 4) ? -62135597361 : -PHP_INT_MAX;
return $this->setTimezone(
$this->getTimestampDateTime($this->generator->numberBetween($min, $this->getTimestamp($until))),
$timezone,
);
}
public function dateTimeBetween($from = '-30 years', $until = 'now', ?string $timezone = null): \DateTime
{
$start = $this->getTimestamp($from);
$end = $this->getTimestamp($until);
if ($start > $end) {
throw new \InvalidArgumentException('"$from" must be anterior to "$until".');
}
$timestamp = $this->generator->numberBetween($start, $end);
return $this->setTimezone(
$this->getTimestampDateTime($timestamp),
$timezone,
);
}
public function dateTimeInInterval($from = '-30 years', string $interval = '+5 days', ?string $timezone = null): \DateTime
{
$intervalObject = \DateInterval::createFromDateString($interval);
$datetime = $from instanceof \DateTime ? $from : new \DateTime($from);
$other = (clone $datetime)->add($intervalObject);
$begin = min($datetime, $other);
$end = $datetime === $begin ? $other : $datetime;
return $this->dateTimeBetween($begin, $end, $timezone);
}
public function dateTimeThisWeek($until = 'sunday this week', ?string $timezone = null): \DateTime
{
return $this->dateTimeBetween('monday this week', $until, $timezone);
}
public function dateTimeThisMonth($until = 'last day of this month', ?string $timezone = null): \DateTime
{
return $this->dateTimeBetween('first day of this month', $until, $timezone);
}
public function dateTimeThisYear($until = 'last day of december', ?string $timezone = null): \DateTime
{
return $this->dateTimeBetween('first day of january', $until, $timezone);
}
public function dateTimeThisDecade($until = 'now', ?string $timezone = null): \DateTime
{
$year = floor(date('Y') / 10) * 10;
return $this->dateTimeBetween("first day of january $year", $until, $timezone);
}
public function dateTimeThisCentury($until = 'now', ?string $timezone = null): \DateTime
{
$year = floor(date('Y') / 100) * 100;
return $this->dateTimeBetween("first day of january $year", $until, $timezone);
}
public function date(string $format = 'Y-m-d', $until = 'now'): string
{
return $this->dateTime($until)->format($format);
}
public function time(string $format = 'H:i:s', $until = 'now'): string
{
return $this->date($format, $until);
}
public function unixTime($until = 'now'): int
{
return $this->generator->numberBetween(0, $this->getTimestamp($until));
}
public function iso8601($until = 'now'): string
{
return $this->date(\DateTime::ISO8601, $until);
}
public function amPm($until = 'now'): string
{
return $this->date('a', $until);
}
public function dayOfMonth($until = 'now'): string
{
return $this->date('d', $until);
}
public function dayOfWeek($until = 'now'): string
{
return $this->date('l', $until);
}
public function month($until = 'now'): string
{
return $this->date('m', $until);
}
public function monthName($until = 'now'): string
{
return $this->date('F', $until);
}
public function year($until = 'now'): string
{
return $this->date('Y', $until);
}
public function century(): string
{
return Helper::randomElement($this->centuries);
}
public function timezone(?string $countryCode = null): string
{
if ($countryCode) {
$timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY, $countryCode);
} else {
$timezones = \DateTimeZone::listIdentifiers();
}
return Helper::randomElement($timezones);
}
}
-564
View File
@@ -1,564 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class File implements Extension\FileExtension
{
/**
* MIME types from the apache.org file. Some types are truncated.
*
* @var array Map of MIME types => file extension(s)
*
* @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
*/
private array $mimeTypes = [
'application/atom+xml' => 'atom',
'application/ecmascript' => 'ecma',
'application/emma+xml' => 'emma',
'application/epub+zip' => 'epub',
'application/java-archive' => 'jar',
'application/java-vm' => 'class',
'application/javascript' => 'js',
'application/json' => 'json',
'application/jsonml+json' => 'jsonml',
'application/lost+xml' => 'lostxml',
'application/mathml+xml' => 'mathml',
'application/mets+xml' => 'mets',
'application/mods+xml' => 'mods',
'application/mp4' => 'mp4s',
'application/msword' => ['doc', 'dot'],
'application/octet-stream' => [
'bin',
'dms',
'lrf',
'mar',
'so',
'dist',
'distz',
'pkg',
'bpk',
'dump',
'elc',
'deploy',
],
'application/ogg' => 'ogx',
'application/omdoc+xml' => 'omdoc',
'application/pdf' => 'pdf',
'application/pgp-encrypted' => 'pgp',
'application/pgp-signature' => ['asc', 'sig'],
'application/pkix-pkipath' => 'pkipath',
'application/pkixcmp' => 'pki',
'application/pls+xml' => 'pls',
'application/postscript' => ['ai', 'eps', 'ps'],
'application/pskc+xml' => 'pskcxml',
'application/rdf+xml' => 'rdf',
'application/reginfo+xml' => 'rif',
'application/rss+xml' => 'rss',
'application/rtf' => 'rtf',
'application/sbml+xml' => 'sbml',
'application/vnd.adobe.air-application-installer-package+zip' => 'air',
'application/vnd.adobe.xdp+xml' => 'xdp',
'application/vnd.adobe.xfdf' => 'xfdf',
'application/vnd.ahead.space' => 'ahead',
'application/vnd.dart' => 'dart',
'application/vnd.data-vision.rdz' => 'rdz',
'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'],
'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'],
'application/vnd.dece.unspecified' => ['uvx', 'uvvx'],
'application/vnd.dece.zip' => ['uvz', 'uvvz'],
'application/vnd.denovo.fcselayout-link' => 'fe_launch',
'application/vnd.dna' => 'dna',
'application/vnd.dolby.mlp' => 'mlp',
'application/vnd.dpgraph' => 'dpg',
'application/vnd.dreamfactory' => 'dfac',
'application/vnd.ds-keypoint' => 'kpxx',
'application/vnd.dvb.ait' => 'ait',
'application/vnd.dvb.service' => 'svc',
'application/vnd.dynageo' => 'geo',
'application/vnd.ecowin.chart' => 'mag',
'application/vnd.enliven' => 'nml',
'application/vnd.epson.esf' => 'esf',
'application/vnd.epson.msf' => 'msf',
'application/vnd.epson.quickanime' => 'qam',
'application/vnd.epson.salt' => 'slt',
'application/vnd.epson.ssf' => 'ssf',
'application/vnd.ezpix-album' => 'ez2',
'application/vnd.ezpix-package' => 'ez3',
'application/vnd.fdf' => 'fdf',
'application/vnd.fdsn.mseed' => 'mseed',
'application/vnd.fdsn.seed' => ['seed', 'dataless'],
'application/vnd.flographit' => 'gph',
'application/vnd.fluxtime.clip' => 'ftc',
'application/vnd.hal+xml' => 'hal',
'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx',
'application/vnd.ibm.minipay' => 'mpy',
'application/vnd.ibm.secure-container' => 'sc',
'application/vnd.iccprofile' => ['icc', 'icm'],
'application/vnd.igloader' => 'igl',
'application/vnd.immervision-ivp' => 'ivp',
'application/vnd.kde.karbon' => 'karbon',
'application/vnd.kde.kchart' => 'chrt',
'application/vnd.kde.kformula' => 'kfo',
'application/vnd.kde.kivio' => 'flw',
'application/vnd.kde.kontour' => 'kon',
'application/vnd.kde.kpresenter' => ['kpr', 'kpt'],
'application/vnd.kde.kspread' => 'ksp',
'application/vnd.kde.kword' => ['kwd', 'kwt'],
'application/vnd.kenameaapp' => 'htke',
'application/vnd.kidspiration' => 'kia',
'application/vnd.kinar' => ['kne', 'knp'],
'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'],
'application/vnd.kodak-descriptor' => 'sse',
'application/vnd.las.las+xml' => 'lasxml',
'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
'application/vnd.lotus-1-2-3' => '123',
'application/vnd.lotus-approach' => 'apr',
'application/vnd.lotus-freelance' => 'pre',
'application/vnd.lotus-notes' => 'nsf',
'application/vnd.lotus-organizer' => 'org',
'application/vnd.lotus-screencam' => 'scm',
'application/vnd.mozilla.xul+xml' => 'xul',
'application/vnd.ms-artgalry' => 'cil',
'application/vnd.ms-cab-compressed' => 'cab',
'application/vnd.ms-excel' => [
'xls',
'xlm',
'xla',
'xlc',
'xlt',
'xlw',
],
'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam',
'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb',
'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
'application/vnd.ms-excel.template.macroenabled.12' => 'xltm',
'application/vnd.ms-fontobject' => 'eot',
'application/vnd.ms-htmlhelp' => 'chm',
'application/vnd.ms-ims' => 'ims',
'application/vnd.ms-lrm' => 'lrm',
'application/vnd.ms-officetheme' => 'thmx',
'application/vnd.ms-pki.seccat' => 'cat',
'application/vnd.ms-pki.stl' => 'stl',
'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot'],
'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam',
'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm',
'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm',
'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm',
'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm',
'application/vnd.ms-project' => ['mpp', 'mpt'],
'application/vnd.ms-word.document.macroenabled.12' => 'docm',
'application/vnd.ms-word.template.macroenabled.12' => 'dotm',
'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb'],
'application/vnd.ms-wpl' => 'wpl',
'application/vnd.ms-xpsdocument' => 'xps',
'application/vnd.mseq' => 'mseq',
'application/vnd.musician' => 'mus',
'application/vnd.oasis.opendocument.chart' => 'odc',
'application/vnd.oasis.opendocument.chart-template' => 'otc',
'application/vnd.oasis.opendocument.database' => 'odb',
'application/vnd.oasis.opendocument.formula' => 'odf',
'application/vnd.oasis.opendocument.formula-template' => 'odft',
'application/vnd.oasis.opendocument.graphics' => 'odg',
'application/vnd.oasis.opendocument.graphics-template' => 'otg',
'application/vnd.oasis.opendocument.image' => 'odi',
'application/vnd.oasis.opendocument.image-template' => 'oti',
'application/vnd.oasis.opendocument.presentation' => 'odp',
'application/vnd.oasis.opendocument.presentation-template' => 'otp',
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
'application/vnd.oasis.opendocument.text' => 'odt',
'application/vnd.oasis.opendocument.text-master' => 'odm',
'application/vnd.oasis.opendocument.text-template' => 'ott',
'application/vnd.oasis.opendocument.text-web' => 'oth',
'application/vnd.olpc-sugar' => 'xo',
'application/vnd.oma.dd2+xml' => 'dd2',
'application/vnd.openofficeorg.extension' => 'oxt',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
'application/vnd.pvi.ptid1' => 'ptid',
'application/vnd.quark.quarkxpress' => [
'qxd',
'qxt',
'qwd',
'qwt',
'qxl',
'qxb',
],
'application/vnd.realvnc.bed' => 'bed',
'application/vnd.recordare.musicxml' => 'mxl',
'application/vnd.recordare.musicxml+xml' => 'musicxml',
'application/vnd.rig.cryptonote' => 'cryptonote',
'application/vnd.rim.cod' => 'cod',
'application/vnd.rn-realmedia' => 'rm',
'application/vnd.rn-realmedia-vbr' => 'rmvb',
'application/vnd.route66.link66+xml' => 'link66',
'application/vnd.sailingtracker.track' => 'st',
'application/vnd.seemail' => 'see',
'application/vnd.sema' => 'sema',
'application/vnd.semd' => 'semd',
'application/vnd.semf' => 'semf',
'application/vnd.shana.informed.formdata' => 'ifm',
'application/vnd.shana.informed.formtemplate' => 'itp',
'application/vnd.shana.informed.interchange' => 'iif',
'application/vnd.shana.informed.package' => 'ipk',
'application/vnd.simtech-mindmapper' => ['twd', 'twds'],
'application/vnd.smaf' => 'mmf',
'application/vnd.stepmania.stepchart' => 'sm',
'application/vnd.sun.xml.calc' => 'sxc',
'application/vnd.sun.xml.calc.template' => 'stc',
'application/vnd.sun.xml.draw' => 'sxd',
'application/vnd.sun.xml.draw.template' => 'std',
'application/vnd.sun.xml.impress' => 'sxi',
'application/vnd.sun.xml.impress.template' => 'sti',
'application/vnd.sun.xml.math' => 'sxm',
'application/vnd.sun.xml.writer' => 'sxw',
'application/vnd.sun.xml.writer.global' => 'sxg',
'application/vnd.sun.xml.writer.template' => 'stw',
'application/vnd.sus-calendar' => ['sus', 'susp'],
'application/vnd.svd' => 'svd',
'application/vnd.symbian.install' => ['sis', 'sisx'],
'application/vnd.syncml+xml' => 'xsm',
'application/vnd.syncml.dm+wbxml' => 'bdm',
'application/vnd.syncml.dm+xml' => 'xdm',
'application/vnd.tao.intent-module-archive' => 'tao',
'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'],
'application/vnd.tmobile-livetv' => 'tmo',
'application/vnd.trid.tpt' => 'tpt',
'application/vnd.triscape.mxs' => 'mxs',
'application/vnd.trueapp' => 'tra',
'application/vnd.ufdl' => ['ufd', 'ufdl'],
'application/vnd.uiq.theme' => 'utz',
'application/vnd.umajin' => 'umj',
'application/vnd.unity' => 'unityweb',
'application/vnd.uoml+xml' => 'uoml',
'application/vnd.vcx' => 'vcx',
'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'],
'application/vnd.visionary' => 'vis',
'application/vnd.vsf' => 'vsf',
'application/vnd.wap.wbxml' => 'wbxml',
'application/vnd.wap.wmlc' => 'wmlc',
'application/vnd.wap.wmlscriptc' => 'wmlsc',
'application/vnd.webturbo' => 'wtb',
'application/vnd.wolfram.player' => 'nbp',
'application/vnd.wordperfect' => 'wpd',
'application/vnd.wqd' => 'wqd',
'application/vnd.wt.stf' => 'stf',
'application/vnd.xara' => 'xar',
'application/vnd.xfdl' => 'xfdl',
'application/voicexml+xml' => 'vxml',
'application/widget' => 'wgt',
'application/winhlp' => 'hlp',
'application/wsdl+xml' => 'wsdl',
'application/wspolicy+xml' => 'wspolicy',
'application/x-7z-compressed' => '7z',
'application/x-bittorrent' => 'torrent',
'application/x-blorb' => ['blb', 'blorb'],
'application/x-bzip' => 'bz',
'application/x-cdlink' => 'vcd',
'application/x-cfs-compressed' => 'cfs',
'application/x-chat' => 'chat',
'application/x-chess-pgn' => 'pgn',
'application/x-conference' => 'nsc',
'application/x-cpio' => 'cpio',
'application/x-csh' => 'csh',
'application/x-debian-package' => ['deb', 'udeb'],
'application/x-dgc-compressed' => 'dgc',
'application/x-director' => [
'dir',
'dcr',
'dxr',
'cst',
'cct',
'cxt',
'w3d',
'fgd',
'swa',
],
'application/x-font-ttf' => ['ttf', 'ttc'],
'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm'],
'application/x-font-woff' => 'woff',
'application/x-freearc' => 'arc',
'application/x-futuresplash' => 'spl',
'application/x-gca-compressed' => 'gca',
'application/x-glulx' => 'ulx',
'application/x-gnumeric' => 'gnumeric',
'application/x-gramps-xml' => 'gramps',
'application/x-gtar' => 'gtar',
'application/x-hdf' => 'hdf',
'application/x-install-instructions' => 'install',
'application/x-iso9660-image' => 'iso',
'application/x-java-jnlp-file' => 'jnlp',
'application/x-latex' => 'latex',
'application/x-lzh-compressed' => ['lzh', 'lha'],
'application/x-mie' => 'mie',
'application/x-mobipocket-ebook' => ['prc', 'mobi'],
'application/x-ms-application' => 'application',
'application/x-ms-shortcut' => 'lnk',
'application/x-ms-wmd' => 'wmd',
'application/x-ms-wmz' => 'wmz',
'application/x-ms-xbap' => 'xbap',
'application/x-msaccess' => 'mdb',
'application/x-msbinder' => 'obd',
'application/x-mscardfile' => 'crd',
'application/x-msclip' => 'clp',
'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi'],
'application/x-msmediaview' => [
'mvb',
'm13',
'm14',
],
'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'],
'application/x-rar-compressed' => 'rar',
'application/x-research-info-systems' => 'ris',
'application/x-sh' => 'sh',
'application/x-shar' => 'shar',
'application/x-shockwave-flash' => 'swf',
'application/x-silverlight-app' => 'xap',
'application/x-sql' => 'sql',
'application/x-stuffit' => 'sit',
'application/x-stuffitx' => 'sitx',
'application/x-subrip' => 'srt',
'application/x-sv4cpio' => 'sv4cpio',
'application/x-sv4crc' => 'sv4crc',
'application/x-t3vm-image' => 't3',
'application/x-tads' => 'gam',
'application/x-tar' => 'tar',
'application/x-tcl' => 'tcl',
'application/x-tex' => 'tex',
'application/x-tex-tfm' => 'tfm',
'application/x-texinfo' => ['texinfo', 'texi'],
'application/x-tgif' => 'obj',
'application/x-ustar' => 'ustar',
'application/x-wais-source' => 'src',
'application/x-x509-ca-cert' => ['der', 'crt'],
'application/x-xfig' => 'fig',
'application/x-xliff+xml' => 'xlf',
'application/x-xpinstall' => 'xpi',
'application/x-xz' => 'xz',
'application/x-zmachine' => 'z1',
'application/xaml+xml' => 'xaml',
'application/xcap-diff+xml' => 'xdf',
'application/xenc+xml' => 'xenc',
'application/xhtml+xml' => ['xhtml', 'xht'],
'application/xml' => ['xml', 'xsl'],
'application/xml-dtd' => 'dtd',
'application/xop+xml' => 'xop',
'application/xproc+xml' => 'xpl',
'application/xslt+xml' => 'xslt',
'application/xspf+xml' => 'xspf',
'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'],
'application/yang' => 'yang',
'application/yin+xml' => 'yin',
'application/zip' => 'zip',
'audio/adpcm' => 'adp',
'audio/basic' => ['au', 'snd'],
'audio/midi' => ['mid', 'midi', 'kar', 'rmi'],
'audio/mp4' => 'mp4a',
'audio/mpeg' => [
'mpga',
'mp2',
'mp2a',
'mp3',
'm2a',
'm3a',
],
'audio/ogg' => ['oga', 'ogg', 'spx'],
'audio/vnd.dece.audio' => ['uva', 'uvva'],
'audio/vnd.rip' => 'rip',
'audio/webm' => 'weba',
'audio/x-aac' => 'aac',
'audio/x-aiff' => ['aif', 'aiff', 'aifc'],
'audio/x-caf' => 'caf',
'audio/x-flac' => 'flac',
'audio/x-matroska' => 'mka',
'audio/x-mpegurl' => 'm3u',
'audio/x-ms-wax' => 'wax',
'audio/x-ms-wma' => 'wma',
'audio/x-pn-realaudio' => ['ram', 'ra'],
'audio/x-pn-realaudio-plugin' => 'rmp',
'audio/x-wav' => 'wav',
'audio/xm' => 'xm',
'image/bmp' => 'bmp',
'image/cgm' => 'cgm',
'image/g3fax' => 'g3',
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jpeg' => ['jpeg', 'jpg', 'jpe'],
'image/ktx' => 'ktx',
'image/png' => 'png',
'image/prs.btif' => 'btif',
'image/sgi' => 'sgi',
'image/svg+xml' => ['svg', 'svgz'],
'image/tiff' => ['tiff', 'tif'],
'image/vnd.adobe.photoshop' => 'psd',
'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'],
'image/vnd.dvb.subtitle' => 'sub',
'image/vnd.djvu' => ['djvu', 'djv'],
'image/vnd.dwg' => 'dwg',
'image/vnd.dxf' => 'dxf',
'image/vnd.fastbidsheet' => 'fbs',
'image/vnd.fpx' => 'fpx',
'image/vnd.fst' => 'fst',
'image/vnd.fujixerox.edmics-mmr' => 'mmr',
'image/vnd.fujixerox.edmics-rlc' => 'rlc',
'image/vnd.ms-modi' => 'mdi',
'image/vnd.ms-photo' => 'wdp',
'image/vnd.net-fpx' => 'npx',
'image/vnd.wap.wbmp' => 'wbmp',
'image/vnd.xiff' => 'xif',
'image/webp' => 'webp',
'image/x-3ds' => '3ds',
'image/x-cmu-raster' => 'ras',
'image/x-cmx' => 'cmx',
'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'],
'image/x-icon' => 'ico',
'image/x-mrsid-image' => 'sid',
'image/x-pcx' => 'pcx',
'image/x-pict' => ['pic', 'pct'],
'image/x-portable-anymap' => 'pnm',
'image/x-portable-bitmap' => 'pbm',
'image/x-portable-graymap' => 'pgm',
'image/x-portable-pixmap' => 'ppm',
'image/x-rgb' => 'rgb',
'image/x-tga' => 'tga',
'image/x-xbitmap' => 'xbm',
'image/x-xpixmap' => 'xpm',
'image/x-xwindowdump' => 'xwd',
'message/rfc822' => ['eml', 'mime'],
'model/iges' => ['igs', 'iges'],
'model/mesh' => ['msh', 'mesh', 'silo'],
'model/vnd.collada+xml' => 'dae',
'model/vnd.dwf' => 'dwf',
'model/vnd.gdl' => 'gdl',
'model/vnd.gtw' => 'gtw',
'model/vnd.mts' => 'mts',
'model/vnd.vtu' => 'vtu',
'model/vrml' => ['wrl', 'vrml'],
'model/x3d+binary' => 'x3db',
'model/x3d+vrml' => 'x3dv',
'model/x3d+xml' => 'x3d',
'text/cache-manifest' => 'appcache',
'text/calendar' => ['ics', 'ifb'],
'text/css' => 'css',
'text/csv' => 'csv',
'text/html' => ['html', 'htm'],
'text/n3' => 'n3',
'text/plain' => [
'txt',
'text',
'conf',
'def',
'list',
'log',
'in',
],
'text/prs.lines.tag' => 'dsc',
'text/richtext' => 'rtx',
'text/sgml' => ['sgml', 'sgm'],
'text/tab-separated-values' => 'tsv',
'text/troff' => [
't',
'tr',
'roff',
'man',
'me',
'ms',
],
'text/turtle' => 'ttl',
'text/uri-list' => ['uri', 'uris', 'urls'],
'text/vcard' => 'vcard',
'text/vnd.curl' => 'curl',
'text/vnd.curl.dcurl' => 'dcurl',
'text/vnd.curl.scurl' => 'scurl',
'text/vnd.curl.mcurl' => 'mcurl',
'text/vnd.dvb.subtitle' => 'sub',
'text/vnd.fly' => 'fly',
'text/vnd.fmi.flexstor' => 'flx',
'text/vnd.graphviz' => 'gv',
'text/vnd.in3d.3dml' => '3dml',
'text/vnd.in3d.spot' => 'spot',
'text/vnd.sun.j2me.app-descriptor' => 'jad',
'text/vnd.wap.wml' => 'wml',
'text/vnd.wap.wmlscript' => 'wmls',
'text/x-asm' => ['s', 'asm'],
'text/x-fortran' => ['f', 'for', 'f77', 'f90'],
'text/x-java-source' => 'java',
'text/x-opml' => 'opml',
'text/x-pascal' => ['p', 'pas'],
'text/x-nfo' => 'nfo',
'text/x-setext' => 'etx',
'text/x-sfv' => 'sfv',
'text/x-uuencode' => 'uu',
'text/x-vcalendar' => 'vcs',
'text/x-vcard' => 'vcf',
'video/3gpp' => '3gp',
'video/3gpp2' => '3g2',
'video/h261' => 'h261',
'video/h263' => 'h263',
'video/h264' => 'h264',
'video/jpeg' => 'jpgv',
'video/jpm' => ['jpm', 'jpgm'],
'video/mj2' => 'mj2',
'video/mp4' => 'mp4',
'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'],
'video/ogg' => 'ogv',
'video/quicktime' => ['qt', 'mov'],
'video/vnd.dece.hd' => ['uvh', 'uvvh'],
'video/vnd.dece.mobile' => ['uvm', 'uvvm'],
'video/vnd.dece.pd' => ['uvp', 'uvvp'],
'video/vnd.dece.sd' => ['uvs', 'uvvs'],
'video/vnd.dece.video' => ['uvv', 'uvvv'],
'video/vnd.dvb.file' => 'dvb',
'video/vnd.fvt' => 'fvt',
'video/vnd.mpegurl' => ['mxu', 'm4u'],
'video/vnd.ms-playready.media.pyv' => 'pyv',
'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'],
'video/vnd.vivo' => 'viv',
'video/webm' => 'webm',
'video/x-f4v' => 'f4v',
'video/x-fli' => 'fli',
'video/x-flv' => 'flv',
'video/x-m4v' => 'm4v',
'video/x-matroska' => ['mkv', 'mk3d', 'mks'],
'video/x-mng' => 'mng',
'video/x-ms-asf' => ['asf', 'asx'],
'video/x-ms-vob' => 'vob',
'video/x-ms-wm' => 'wm',
'video/x-ms-wmv' => 'wmv',
'video/x-ms-wmx' => 'wmx',
'video/x-ms-wvx' => 'wvx',
'video/x-msvideo' => 'avi',
'video/x-sgi-movie' => 'movie',
];
public function mimeType(): string
{
return array_rand($this->mimeTypes, 1);
}
public function extension(): string
{
$extension = $this->mimeTypes[array_rand($this->mimeTypes, 1)];
return is_array($extension) ? $extension[array_rand($extension, 1)] : $extension;
}
public function filePath(): string
{
return tempnam(sys_get_temp_dir(), 'faker');
}
}
-83
View File
@@ -1,83 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Number implements Extension\NumberExtension
{
public function numberBetween(int $min = 0, int $max = 2147483647): int
{
$int1 = min($min, $max);
$int2 = max($min, $max);
return mt_rand($int1, $int2);
}
public function randomDigit(): int
{
return $this->numberBetween(0, 9);
}
public function randomDigitNot(int $except): int
{
$result = $this->numberBetween(0, 8);
if ($result >= $except) {
++$result;
}
return $result;
}
public function randomDigitNotZero(): int
{
return $this->numberBetween(1, 9);
}
public function randomFloat(?int $nbMaxDecimals = null, float $min = 0, ?float $max = null): float
{
if (null === $nbMaxDecimals) {
$nbMaxDecimals = $this->randomDigit();
}
if (null === $max) {
$max = $this->randomNumber();
if ($min > $max) {
$max = $min;
}
}
if ($min > $max) {
$tmp = $min;
$min = $max;
$max = $tmp;
}
return round($min + $this->numberBetween() / mt_getrandmax() * ($max - $min), $nbMaxDecimals);
}
public function randomNumber(?int $nbDigits = null, bool $strict = false): int
{
if (null === $nbDigits) {
$nbDigits = $this->randomDigitNotZero();
}
$max = 10 ** $nbDigits - 1;
if ($max > mt_getrandmax()) {
throw new \InvalidArgumentException('randomNumber() can only generate numbers up to mt_getrandmax()');
}
if ($strict) {
return $this->numberBetween(10 ** ($nbDigits - 1), $max);
}
return $this->numberBetween(0, $max);
}
}
-65
View File
@@ -1,65 +0,0 @@
<?php
namespace Faker\Core;
use Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Uuid implements Extension\UuidExtension
{
private Extension\NumberExtension $numberExtension;
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
public function uuid3(): string
{
// fix for compatibility with 32bit architecture; each mt_rand call is restricted to 32bit
// two such calls will cause 64bits of randomness regardless of architecture
$seed = $this->numberExtension->numberBetween(0, 2147483647) . '#' . $this->numberExtension->numberBetween(0, 2147483647);
// Hash the seed and convert to a byte array
$val = md5($seed, true);
$byte = array_values(unpack('C16', $val));
// extract fields from byte array
$tLo = ($byte[0] << 24) | ($byte[1] << 16) | ($byte[2] << 8) | $byte[3];
$tMi = ($byte[4] << 8) | $byte[5];
$tHi = ($byte[6] << 8) | $byte[7];
$csLo = $byte[9];
$csHi = $byte[8] & 0x3f | (1 << 7);
// correct byte order for big edian architecture
if (pack('L', 0x6162797A) == pack('N', 0x6162797A)) {
$tLo = (($tLo & 0x000000ff) << 24) | (($tLo & 0x0000ff00) << 8)
| (($tLo & 0x00ff0000) >> 8) | (($tLo & 0xff000000) >> 24);
$tMi = (($tMi & 0x00ff) << 8) | (($tMi & 0xff00) >> 8);
$tHi = (($tHi & 0x00ff) << 8) | (($tHi & 0xff00) >> 8);
}
// apply version number
$tHi &= 0x0fff;
$tHi |= (3 << 12);
// cast to string
return sprintf(
'%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
$tLo,
$tMi,
$tHi,
$csHi,
$csLo,
$byte[10],
$byte[11],
$byte[12],
$byte[13],
$byte[14],
$byte[15],
);
}
}
-69
View File
@@ -1,69 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Core;
use Faker\Extension;
use Faker\Provider\DateTime;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Version implements Extension\VersionExtension
{
private Extension\NumberExtension $numberExtension;
/**
* @var string[]
*/
private array $semverCommonPreReleaseIdentifiers = ['alpha', 'beta', 'rc'];
public function __construct(?Extension\NumberExtension $numberExtension = null)
{
$this->numberExtension = $numberExtension ?: new Number();
}
/**
* Represents v2.0.0 of the semantic versioning: https://semver.org/spec/v2.0.0.html
*/
public function semver(bool $preRelease = false, bool $build = false): string
{
return sprintf(
'%d.%d.%d%s%s',
$this->numberExtension->numberBetween(0, 9),
$this->numberExtension->numberBetween(0, 99),
$this->numberExtension->numberBetween(0, 99),
$preRelease && $this->numberExtension->numberBetween(0, 1) === 1 ? '-' . $this->semverPreReleaseIdentifier() : '',
$build && $this->numberExtension->numberBetween(0, 1) === 1 ? '+' . $this->semverBuildIdentifier() : '',
);
}
/**
* Common pre-release identifier
*/
private function semverPreReleaseIdentifier(): string
{
$ident = Extension\Helper::randomElement($this->semverCommonPreReleaseIdentifiers);
if ($this->numberExtension->numberBetween(0, 1) !== 1) {
return $ident;
}
return $ident . '.' . $this->numberExtension->numberBetween(1, 99);
}
/**
* Common random build identifier
*/
private function semverBuildIdentifier(): string
{
if ($this->numberExtension->numberBetween(0, 1) === 1) {
// short git revision syntax: https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection
return substr(sha1(Extension\Helper::lexify('??????')), 0, 7);
}
// date syntax
return DateTime::date('YmdHis');
}
}
-49
View File
@@ -1,49 +0,0 @@
<?php
namespace Faker;
/**
* This generator returns a default value for all called properties
* and methods.
*
* @mixin Generator
*
* @deprecated Use ChanceGenerator instead
*/
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
trigger_deprecation('fakerphp/faker', '1.16', 'Class "%s" is deprecated, use "%s" instead.', __CLASS__, ChanceGenerator::class);
$this->default = $default;
}
public function ext()
{
return $this;
}
/**
* @param string $attribute
*
* @deprecated Use a method instead.
*/
public function __get($attribute)
{
trigger_deprecation('fakerphp/faker', '1.14', 'Accessing property "%s" is deprecated, use "%s()" instead.', $attribute, $attribute);
return $this->default;
}
/**
* @param string $method
* @param array $attributes
*/
public function __call($method, $attributes)
{
return $this->default;
}
}
-70
View File
@@ -1,70 +0,0 @@
<?php
namespace Faker;
class Documentor
{
protected $generator;
public function __construct(Generator $generator)
{
$this->generator = $generator;
}
/**
* @return array
*/
public function getFormatters()
{
$formatters = [];
$providers = array_reverse($this->generator->getProviders());
$providers[] = new Provider\Base($this->generator);
foreach ($providers as $provider) {
$providerClass = get_class($provider);
$formatters[$providerClass] = [];
$refl = new \ReflectionObject($provider);
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflmethod) {
if ($reflmethod->getDeclaringClass()->getName() == 'Faker\Provider\Base' && $providerClass != 'Faker\Provider\Base') {
continue;
}
$methodName = $reflmethod->name;
if ($reflmethod->isConstructor()) {
continue;
}
$parameters = [];
foreach ($reflmethod->getParameters() as $reflparameter) {
$parameter = '$' . $reflparameter->getName();
if ($reflparameter->isDefaultValueAvailable()) {
$parameter .= ' = ' . var_export($reflparameter->getDefaultValue(), true);
}
$parameters[] = $parameter;
}
$parameters = $parameters ? '(' . implode(', ', $parameters) . ')' : '';
try {
$example = $this->generator->format($methodName);
} catch (\InvalidArgumentException $e) {
$example = '';
}
if (is_array($example)) {
$example = "array('" . implode("', '", $example) . "')";
} elseif ($example instanceof \DateTime) {
$example = "DateTime('" . $example->format('Y-m-d H:i:s') . "')";
} elseif ($example instanceof Generator || $example instanceof UniqueGenerator) { // modifier
$example = '';
} else {
$example = var_export($example, true);
}
$formatters[$providerClass][$methodName . $parameters] = $example;
}
}
return $formatters;
}
}
@@ -1,39 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface AddressExtension extends Extension
{
/**
* @example '791 Crist Parks, Sashabury, IL 86039-9874'
*/
public function address(): string;
/**
* Randomly return a real city name.
*/
public function city(): string;
/**
* @example 86039-9874
*/
public function postcode(): string;
/**
* @example 'Crist Parks'
*/
public function streetName(): string;
/**
* @example '791 Crist Parks'
*/
public function streetAddress(): string;
/**
* Randomly return a building number.
*/
public function buildingNumber(): string;
}
@@ -1,41 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface BarcodeExtension extends Extension
{
/**
* Get a random EAN13 barcode.
*
* @example '4006381333931'
*/
public function ean13(): string;
/**
* Get a random EAN8 barcode.
*
* @example '73513537'
*/
public function ean8(): string;
/**
* Get a random ISBN-10 code
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number
*
* @example '4881416324'
*/
public function isbn10(): string;
/**
* Get a random ISBN-13 code
*
* @see http://en.wikipedia.org/wiki/International_Standard_Book_Number
*
* @example '9790404436093'
*/
public function isbn13(): string;
}
@@ -1,30 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface BloodExtension extends Extension
{
/**
* Get an actual blood type
*
* @example 'AB'
*/
public function bloodType(): string;
/**
* Get a random resis value
*
* @example '+'
*/
public function bloodRh(): string;
/**
* Get a full blood group
*
* @example 'AB+'
*/
public function bloodGroup(): string;
}
@@ -1,63 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface ColorExtension extends Extension
{
/**
* @example '#fa3cc2'
*/
public function hexColor(): string;
/**
* @example '#ff0044'
*/
public function safeHexColor(): string;
/**
* @example 'array(0,255,122)'
*
* @return int[]
*/
public function rgbColorAsArray(): array;
/**
* @example '0,255,122'
*/
public function rgbColor(): string;
/**
* @example 'rgb(0,255,122)'
*/
public function rgbCssColor(): string;
/**
* @example 'rgba(0,255,122,0.8)'
*/
public function rgbaCssColor(): string;
/**
* @example 'blue'
*/
public function safeColorName(): string;
/**
* @example 'NavajoWhite'
*/
public function colorName(): string;
/**
* @example '340,50,20'
*/
public function hslColor(): string;
/**
* @example array(340, 50, 20)
*
* @return int[]
*/
public function hslColorAsArray(): array;
}
@@ -1,21 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface CompanyExtension extends Extension
{
/**
* @example 'Acme Ltd'
*/
public function company(): string;
/**
* @example 'Ltd'
*/
public function companySuffix(): string;
public function jobTitle(): string;
}
@@ -1,14 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface CountryExtension extends Extension
{
/**
* @example 'Japan'
*/
public function country(): string;
}
@@ -1,242 +0,0 @@
<?php
namespace Faker\Extension;
/**
* FakerPHP extension for Date-related randomization.
*
* Functions accepting a date string use the `strtotime()` function internally.
*
* @experimental
*
* @since 1.20.0
*/
interface DateTimeExtension
{
/**
* Get a DateTime object between January 1, 1970, and `$until` (defaults to "now").
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*
* @example DateTime('2005-08-16 20:39:21')
*/
public function dateTime($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a DateTime object for a date between January 1, 0001, and now.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @example DateTime('1265-03-22 21:15:52')
*
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeAD($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a DateTime object a random date between `$from` and `$until`.
* Accepts date strings that can be recognized by `strtotime()`.
*
* @param \DateTime|string $from defaults to 30 years ago
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeBetween($from = '-30 years', $until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a DateTime object based on a random date between `$from` and an interval.
* Accepts date string that can be recognized by `strtotime()`.
*
* @param \DateTime|int|string $from defaults to 30 years ago
* @param string $interval defaults to 5 days after
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeInInterval($from = '-30 years', string $interval = '+5 days', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current week.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone zone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisWeek($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current month.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisMonth($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current year.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisYear($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current decade.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisDecade($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date time object somewhere inside the current century.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
* @param string|null $timezone timezone for generated date, fallback to `DateTime::$defaultTimezone` and `date_default_timezone_get()`.
*
* @see \DateTimeZone
* @see http://php.net/manual/en/timezones.php
* @see http://php.net/manual/en/function.date-default-timezone-get.php
*/
public function dateTimeThisCentury($until = 'now', ?string $timezone = null): \DateTime;
/**
* Get a date string between January 1, 1970, and `$until`.
*
* @param string $format DateTime format
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @see https://www.php.net/manual/en/datetime.format.php
*/
public function date(string $format = 'Y-m-d', $until = 'now'): string;
/**
* Get a time string (24h format by default).
*
* @param string $format DateTime format
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @see https://www.php.net/manual/en/datetime.format.php
*/
public function time(string $format = 'H:i:s', $until = 'now'): string;
/**
* Get a UNIX (POSIX-compatible) timestamp between January 1, 1970, and `$until`.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*/
public function unixTime($until = 'now'): int;
/**
* Get a date string according to the ISO-8601 standard.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*/
public function iso8601($until = 'now'): string;
/**
* Get a string containing either "am" or "pm".
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example 'am'
*/
public function amPm($until = 'now'): string;
/**
* Get a localized random day of the month.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example '16'
*/
public function dayOfMonth($until = 'now'): string;
/**
* Get a localized random day of the week.
*
* Uses internal DateTime formatting, hence PHP's internal locale will be used (change using `setlocale()`).
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example 'Tuesday'
*
* @see setlocale
* @see https://www.php.net/manual/en/function.setlocale.php Set a different output language
*/
public function dayOfWeek($until = 'now'): string;
/**
* Get a random month (numbered).
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example '7'
*/
public function month($until = 'now'): string;
/**
* Get a random month.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @see setlocale
* @see https://www.php.net/manual/en/function.setlocale.php Set a different output language
*
* @example 'September'
*/
public function monthName($until = 'now'): string;
/**
* Get a random year between 1970 and `$until`.
*
* @param \DateTime|int|string $until maximum timestamp, defaults to "now"
*
* @example '1987'
*/
public function year($until = 'now'): string;
/**
* Get a random century, formatted as Roman numerals.
*
* @example 'XVII'
*/
public function century(): string;
/**
* Get a random timezone, uses `\DateTimeZone::listIdentifiers()` internally.
*
* @param string|null $countryCode two-letter ISO 3166-1 compatible country code
*
* @example 'Europe/Rome'
*/
public function timezone(?string $countryCode = null): string;
}
-14
View File
@@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
/**
* An extension is the only way to add new functionality to Faker.
*
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface Extension
{
}
@@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
/**
* @experimental This class is experimental and does not fall under our BC promise
*/
final class ExtensionNotFound extends \LogicException
{
}
@@ -1,28 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface FileExtension extends Extension
{
/**
* Get a random MIME type
*
* @example 'video/avi'
*/
public function mimeType(): string;
/**
* Get a random file extension (without a dot)
*
* @example avi
*/
public function extension(): string;
/**
* Get a full path to a new real file on the system.
*/
public function filePath(): string;
}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
use Faker\Generator;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface GeneratorAwareExtension extends Extension
{
/**
* This method MUST be implemented in such a way as to retain the
* immutability of the extension, and MUST return an instance that has the
* new Generator.
*/
public function withGenerator(Generator $generator): Extension;
}
@@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace Faker\Extension;
use Faker\Generator;
/**
* A helper trait to be used with GeneratorAwareExtension.
*/
trait GeneratorAwareExtensionTrait
{
/**
* @var Generator|null
*/
private $generator;
/**
* @return static
*/
public function withGenerator(Generator $generator): Extension
{
$instance = clone $this;
$instance->generator = $generator;
return $instance;
}
}
-106
View File
@@ -1,106 +0,0 @@
<?php
namespace Faker\Extension;
/**
* A class with some methods that may make building extensions easier.
*
* @experimental This class is experimental and does not fall under our BC promise
*/
final class Helper
{
/**
* Returns a random element from a passed array.
*/
public static function randomElement(array $array)
{
if ($array === []) {
return null;
}
return $array[array_rand($array, 1)];
}
/**
* Replaces all hash sign ('#') occurrences with a random number
* Replaces all percentage sign ('%') occurrences with a non-zero number.
*
* @param string $string String that needs to bet parsed
*/
public static function numerify(string $string): string
{
// instead of using randomDigit() several times, which is slow,
// count the number of hashes and generate once a large number
$toReplace = [];
if (($pos = strpos($string, '#')) !== false) {
for ($i = $pos, $last = strrpos($string, '#', $pos) + 1; $i < $last; ++$i) {
if ($string[$i] === '#') {
$toReplace[] = $i;
}
}
}
if ($nbReplacements = count($toReplace)) {
$maxAtOnce = strlen((string) mt_getrandmax()) - 1;
$numbers = '';
$i = 0;
while ($i < $nbReplacements) {
$size = min($nbReplacements - $i, $maxAtOnce);
$numbers .= str_pad((string) mt_rand(0, 10 ** $size - 1), $size, '0', STR_PAD_LEFT);
$i += $size;
}
for ($i = 0; $i < $nbReplacements; ++$i) {
$string[$toReplace[$i]] = $numbers[$i];
}
}
return self::replaceWildcard($string, '%', static function () {
return mt_rand(1, 9);
});
}
/**
* Replaces all question mark ('?') occurrences with a random letter.
*
* @param string $string String that needs to bet parsed
*/
public static function lexify(string $string): string
{
return self::replaceWildcard($string, '?', static function () {
return chr(mt_rand(97, 122));
});
}
/**
* Replaces hash signs ('#') and question marks ('?') with random numbers and letters
* An asterisk ('*') is replaced with either a random number or a random letter.
*
* @param string $string String that needs to bet parsed
*/
public static function bothify(string $string): string
{
$string = self::replaceWildcard($string, '*', static function () {
return mt_rand(0, 1) === 1 ? '#' : '?';
});
return self::lexify(self::numerify($string));
}
private static function replaceWildcard(string $string, string $wildcard, callable $callback): string
{
if (($pos = strpos($string, $wildcard)) === false) {
return $string;
}
for ($i = $pos, $last = strrpos($string, $wildcard, $pos) + 1; $i < $last; ++$i) {
if ($string[$i] === $wildcard) {
$string[$i] = call_user_func($callback);
}
}
return $string;
}
}
@@ -1,53 +0,0 @@
<?php
namespace Faker\Extension;
/**
* @experimental This interface is experimental and does not fall under our BC promise
*/
interface NumberExtension extends Extension
{
/**
* Returns a random number between $int1 and $int2 (any order)
*
* @param int $min default to 0
* @param int $max defaults to 32 bit max integer, ie 2147483647
*
* @example 79907610
*/
public function numberBetween(int $min, int $max): int;
/**
* Returns a random number between 0 and 9
*/
public function randomDigit(): int;
/**
* Generates a random digit, which cannot be $except
*/
public function randomDigitNot(int $except): int;
/**
* Returns a random number between 1 and 9
*/
public function randomDigitNotZero(): int;
/**
* Return a random float number
*
* @example 48.8932
*/
public function randomFloat(?int $nbMaxDecimals, float $min, ?float $max): float;
/**
* Returns a random integer with 0 to $nbDigits digits.
*
* The maximum value returned is mt_getrandmax()
*
* @param int|null $nbDigits Defaults to a random number between 1 and 9
* @param bool $strict Whether the returned number should have exactly $nbDigits
*
* @example 79907610
*/
public function randomNumber(?int $nbDigits, bool $strict): int;
}

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