Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 557bf3fcdf | |||
| f2ea5e160b | |||
| def8d22544 | |||
| c5127da750 | |||
| baea181159 | |||
| d2084b9d07 | |||
| 3b2ad1a0d5 | |||
| 92ba47e99f | |||
| 9d649180a6 | |||
| 6909e21b67 | |||
| c333bbce4e | |||
| 6001c2e3b8 | |||
| 69f4d6a602 | |||
| 7973a94498 | |||
| 43a2c81312 | |||
| 0dd567cf7a | |||
| d9421c9159 | |||
| 7999c663fe | |||
| 06387568cb | |||
| d82b123bfd | |||
| 6f106e3130 | |||
| ce48481bc8 |
@@ -2,10 +2,18 @@ APP_NAME=Glastree
|
|||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=base64:aWz7908H9c7s+it9uMTwb6pyUrpddyMclcuN9Kzv7Ao=
|
APP_KEY=base64:aWz7908H9c7s+it9uMTwb6pyUrpddyMclcuN9Kzv7Ao=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=
|
APP_URL=http://glastree.soon.it
|
||||||
APP_PROTOCOL=https
|
APP_PROTOCOL=http
|
||||||
FORCE_HTTPS=true
|
FORCE_HTTPS=false
|
||||||
TRUSTED_PROXIES=*
|
TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1"
|
||||||
|
|
||||||
|
#GOOGLE_CLIENT_ID=980774223097-o5h7a6kepvg69te34fof2otn7ibi9uha.apps.googleusercontent.com
|
||||||
|
#GOOGLE_CLIENT_SECRET=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
|
||||||
|
|
||||||
|
GOOGLE_CLIENT_ID=416164569291-mss5favrg1qdmbk343plaj1898g3200f.apps.googleusercontent.com
|
||||||
|
GOOGLE_CLIENT_SECRET=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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
APP_LOCALE=it
|
APP_LOCALE=it
|
||||||
APP_FALLBACK_LOCALE=it
|
APP_FALLBACK_LOCALE=it
|
||||||
@@ -30,9 +38,11 @@ DB_DATABASE=glastree
|
|||||||
DB_USERNAME=glastree
|
DB_USERNAME=glastree
|
||||||
DB_PASSWORD=glastree
|
DB_PASSWORD=glastree
|
||||||
|
|
||||||
SESSION_DRIVER=file
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
SESSION_ENCRYPT=false
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_SECURE_COOKIE=false
|
||||||
|
SESSION_SAME_SITE=lax
|
||||||
SESSION_PATH=/
|
SESSION_PATH=/
|
||||||
SESSION_DOMAIN=null
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
|||||||
+9
-3
@@ -1,8 +1,12 @@
|
|||||||
APP_NAME="Glastree"
|
APP_NAME="Glastree"
|
||||||
APP_ENV=local
|
APP_ENV=production
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=false
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
APP_PROTOCOL=https
|
||||||
|
APP_SUBFOLDER=
|
||||||
|
FORCE_HTTPS=false
|
||||||
|
TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1"
|
||||||
|
|
||||||
APP_LOCALE=it
|
APP_LOCALE=it
|
||||||
APP_FALLBACK_LOCALE=en
|
APP_FALLBACK_LOCALE=en
|
||||||
@@ -24,9 +28,11 @@ DB_DATABASE=glastree
|
|||||||
DB_USERNAME=glastree
|
DB_USERNAME=glastree
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
|
||||||
SESSION_DRIVER=file
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
SESSION_ENCRYPT=false
|
SESSION_ENCRYPT=false
|
||||||
|
SESSION_SECURE_COOKIE=false
|
||||||
|
SESSION_SAME_SITE=lax
|
||||||
SESSION_PATH=/
|
SESSION_PATH=/
|
||||||
SESSION_DOMAIN=null
|
SESSION_DOMAIN=null
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":2,"defects":{"Tests\\Feature\\DocumentoLinkTest::test_admin_can_create_link_document":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_requires_url":8,"Tests\\Feature\\DocumentoLinkTest::test_create_link_validates_url_format":8,"Tests\\Feature\\DocumentoLinkTest::test_create_link_does_not_require_file":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_sets_tipo_to_link":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_individuo":7,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_gruppo":7,"Tests\\Feature\\DocumentoLinkTest::test_download_link_redirects_to_url":8,"Tests\\Feature\\DocumentoLinkTest::test_preview_link_returns_fallback_view":8,"Tests\\Feature\\DocumentoLinkTest::test_update_link_url":8,"Tests\\Feature\\DocumentoLinkTest::test_update_link_preserves_url_when_not_sent":8,"Tests\\Feature\\DocumentoLinkTest::test_update_link_validates_url_format":8,"Tests\\Feature\\DocumentoLinkTest::test_destroy_link_does_not_call_storage":8,"Tests\\Feature\\DocumentoLinkTest::test_mass_download_skips_links":7},"times":{"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_true_when_tipo_is_link":0.001,"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_false_when_tipo_is_upload":0.001,"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_false_when_tipo_is_allegato":0.001,"Tests\\Unit\\DocumentoLinkTest::test_is_link_returns_false_when_tipo_is_null":0.002,"Tests\\Unit\\DocumentoLinkTest::test_url_is_fillable":0.001,"Tests\\Unit\\DocumentoLinkTest::test_link_doc_has_no_file_fields":0.001,"Tests\\Feature\\DocumentoLinkTest::test_admin_can_create_link_document":0.064,"Tests\\Feature\\DocumentoLinkTest::test_create_link_requires_url":0.008,"Tests\\Feature\\DocumentoLinkTest::test_create_link_validates_url_format":0.007,"Tests\\Feature\\DocumentoLinkTest::test_create_link_does_not_require_file":0.006,"Tests\\Feature\\DocumentoLinkTest::test_create_link_sets_tipo_to_link":0.014,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_individuo":0.008,"Tests\\Feature\\DocumentoLinkTest::test_create_link_with_visibility_gruppo":0.008,"Tests\\Feature\\DocumentoLinkTest::test_download_link_redirects_to_url":0.005,"Tests\\Feature\\DocumentoLinkTest::test_preview_link_returns_fallback_view":0.009,"Tests\\Feature\\DocumentoLinkTest::test_update_link_url":0.008,"Tests\\Feature\\DocumentoLinkTest::test_update_link_preserves_url_when_not_sent":0.006,"Tests\\Feature\\DocumentoLinkTest::test_update_link_validates_url_format":0.006,"Tests\\Feature\\DocumentoLinkTest::test_destroy_link_does_not_call_storage":0.004,"Tests\\Feature\\DocumentoLinkTest::test_mass_download_skips_links":0.004}}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Fix: tabella `google_o_auth_connections` mancante
|
||||||
|
|
||||||
|
## Problema
|
||||||
|
|
||||||
|
Migration `2026_06_15_000001_create_google_oauth_connections_table.php` segnata come "Ran" (batch 39) ma tabella fisica inesistente sul database MySQL/MariaDB. Causa 500 su GET `/impostazioni`.
|
||||||
|
|
||||||
|
## Comandi (eseguire sul server `192.168.222.177`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Creare la tabella mancante con la stessa definizione della migration
|
||||||
|
php artisan tinker --execute="
|
||||||
|
Schema::create('google_o_auth_connections', function (\Illuminate\Database\Schema\Blueprint \$table) {
|
||||||
|
\$table->id();
|
||||||
|
\$table->string('service', 20)->unique();
|
||||||
|
\$table->string('client_id', 255);
|
||||||
|
\$table->text('client_secret');
|
||||||
|
\$table->text('refresh_token');
|
||||||
|
\$table->timestamps();
|
||||||
|
});
|
||||||
|
"
|
||||||
|
|
||||||
|
# 2. Pulire la cache delle viste (stale compiled view)
|
||||||
|
php artisan view:clear
|
||||||
|
|
||||||
|
# 3. Verificare che la tabella esista
|
||||||
|
php artisan tinker --execute="echo Schema::hasTable('google_o_auth_connections') ? 'OK' : 'ERRORE';"
|
||||||
|
```
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Proxy Configuration — Glastree
|
||||||
|
|
||||||
|
## Nginx Proxy Manager (Custom Nginx Configuration)
|
||||||
|
|
||||||
|
Nella scheda **Advanced** del tuo proxy host:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `.env` (App Laravel)
|
||||||
|
|
||||||
|
```env
|
||||||
|
APP_URL=https://glastree.soon.it
|
||||||
|
APP_PROTOCOL=https
|
||||||
|
FORCE_HTTPS=true
|
||||||
|
TRUSTED_PROXIES=*
|
||||||
|
```
|
||||||
|
|
||||||
|
Dopo qualsiasi modifica:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan config:clear
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Google OAuth — Authorized Redirect URIs
|
||||||
|
|
||||||
|
```
|
||||||
|
https://glastree.soon.it/auth/google/callback
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `bootstrap/app.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
$middleware->trustProxies(at: '*');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schema richieste
|
||||||
|
|
||||||
|
```
|
||||||
|
Cliente (HTTPS)
|
||||||
|
↓
|
||||||
|
Nginx Proxy Manager (termina TLS, inoltra HTTP + X-Forwarded-Proto: https)
|
||||||
|
↓
|
||||||
|
App Laravel (vede richieste come HTTPS grazie a trustProxies(at:"*"))
|
||||||
|
```
|
||||||
@@ -28,16 +28,17 @@ class BackupRunCommand extends Command
|
|||||||
{
|
{
|
||||||
$this->info('Avvio backup...');
|
$this->info('Avvio backup...');
|
||||||
|
|
||||||
|
$options = [];
|
||||||
if ($this->option('no-files')) {
|
if ($this->option('no-files')) {
|
||||||
$this->backupService->saveConfig(['backup_include_files' => false]);
|
$options['include_files'] = false;
|
||||||
$this->warn('Files esclusi dal backup.');
|
$this->warn('Files esclusi dal backup.');
|
||||||
}
|
}
|
||||||
if ($this->option('no-env')) {
|
if ($this->option('no-env')) {
|
||||||
$this->backupService->saveConfig(['backup_include_env' => false]);
|
$options['include_env'] = false;
|
||||||
$this->warn('.env escluso dal backup.');
|
$this->warn('.env escluso dal backup.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->backupService->run();
|
$result = $this->backupService->run($options);
|
||||||
|
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
$this->info(' Backup completato con successo!');
|
$this->info(' Backup completato con successo!');
|
||||||
@@ -45,6 +46,13 @@ class BackupRunCommand extends Command
|
|||||||
$this->line('Dimensione: ' . $result['size_formatted']);
|
$this->line('Dimensione: ' . $result['size_formatted']);
|
||||||
$this->line('Percorso: ' . storage_path('app/backups/' . $result['filename']));
|
$this->line('Percorso: ' . storage_path('app/backups/' . $result['filename']));
|
||||||
|
|
||||||
|
if (!empty($result['steps'])) {
|
||||||
|
$this->line('Contenuto:');
|
||||||
|
foreach ($result['steps'] as $step) {
|
||||||
|
$this->line(' - ' . $step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum GoogleService: string
|
||||||
|
{
|
||||||
|
case Email = 'email';
|
||||||
|
case Drive = 'drive';
|
||||||
|
case Calendar = 'calendar';
|
||||||
|
|
||||||
|
public function scopes(): array
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Email => ['https://mail.google.com/'],
|
||||||
|
self::Drive => ['https://www.googleapis.com/auth/drive'],
|
||||||
|
self::Calendar => ['https://www.googleapis.com/auth/calendar'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Email => 'Gmail',
|
||||||
|
self::Drive => 'Google Drive',
|
||||||
|
self::Calendar => 'Google Calendar',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function icon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Email => 'fas fa-envelope',
|
||||||
|
self::Drive => 'fab fa-google-drive',
|
||||||
|
self::Calendar => 'fas fa-calendar-alt',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Email => 'Invia e ricevi email tramite Gmail API (alternativa a SMTP/IMAP con password)',
|
||||||
|
self::Drive => 'Accedi ai file su Google Drive per documenti e allegati',
|
||||||
|
self::Calendar => 'Sincronizza eventi con Google Calendar',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -132,6 +132,7 @@ class ReportColumnRegistry
|
|||||||
'contatti.telefono' => ['label' => 'Telefono (da contatti)', 'type' => 'relation'],
|
'contatti.telefono' => ['label' => 'Telefono (da contatti)', 'type' => 'relation'],
|
||||||
'contatti.cellulare' => ['label' => 'Cellulare (da contatti)', 'type' => 'relation'],
|
'contatti.cellulare' => ['label' => 'Cellulare (da contatti)', 'type' => 'relation'],
|
||||||
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'gruppi' => [
|
'gruppi' => [
|
||||||
@@ -151,6 +152,7 @@ class ReportColumnRegistry
|
|||||||
'parent.nome' => ['label' => 'Gruppo Padre', 'type' => 'relation'],
|
'parent.nome' => ['label' => 'Gruppo Padre', 'type' => 'relation'],
|
||||||
'diocesi.nome' => ['label' => 'Diocesi', 'type' => 'relation'],
|
'diocesi.nome' => ['label' => 'Diocesi', 'type' => 'relation'],
|
||||||
'individui_count' => ['label' => 'Numero Membri', 'type' => 'relation'],
|
'individui_count' => ['label' => 'Numero Membri', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'eventi' => [
|
'eventi' => [
|
||||||
@@ -171,6 +173,7 @@ class ReportColumnRegistry
|
|||||||
'info_ricorrenza' => ['label' => 'Info Ricorrenza', 'type' => 'accessor'],
|
'info_ricorrenza' => ['label' => 'Info Ricorrenza', 'type' => 'accessor'],
|
||||||
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
||||||
'responsabili.nome_completo' => ['label' => 'Responsabili', 'type' => 'relation'],
|
'responsabili.nome_completo' => ['label' => 'Responsabili', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'documenti' => [
|
'documenti' => [
|
||||||
@@ -183,6 +186,7 @@ class ReportColumnRegistry
|
|||||||
'note' => ['label' => 'Note', 'type' => 'direct'],
|
'note' => ['label' => 'Note', 'type' => 'direct'],
|
||||||
'created_at' => ['label' => 'Data Caricamento', 'type' => 'direct'],
|
'created_at' => ['label' => 'Data Caricamento', 'type' => 'direct'],
|
||||||
'user.name' => ['label' => 'Caricato Da', 'type' => 'relation'],
|
'user.name' => ['label' => 'Caricato Da', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'contatti' => [
|
'contatti' => [
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ class BackupController extends Controller
|
|||||||
$result = $this->backupService->run();
|
$result = $this->backupService->run();
|
||||||
|
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
|
$steps = !empty($result['steps']) ? ' — ' . implode(', ', $result['steps']) : '';
|
||||||
return redirect()->route('admin.backup.index')
|
return redirect()->route('admin.backup.index')
|
||||||
->with('success', 'Backup completato: ' . ($result['filename'] ?? '') . ' (' . ($result['size_formatted'] ?? '') . ')');
|
->with('success', 'Backup completato: ' . ($result['filename'] ?? '') . ' (' . ($result['size_formatted'] ?? '') . ')' . $steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('admin.backup.index')
|
return redirect()->route('admin.backup.index')
|
||||||
|
|||||||
@@ -2,15 +2,20 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Enums\GoogleService;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\EmailAttachment;
|
use App\Models\EmailAttachment;
|
||||||
use App\Models\EmailFolder;
|
use App\Models\EmailFolder;
|
||||||
use App\Models\EmailSetting;
|
use App\Models\EmailSetting;
|
||||||
|
use App\Models\Firma;
|
||||||
use App\Models\SenderAccount;
|
use App\Models\SenderAccount;
|
||||||
|
use App\Services\GoogleOAuthService;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\Mime\Email;
|
||||||
|
|
||||||
class EmailSettingsController extends Controller
|
class EmailSettingsController extends Controller
|
||||||
{
|
{
|
||||||
@@ -19,7 +24,9 @@ class EmailSettingsController extends Controller
|
|||||||
$this->authorizeWrite('settings');
|
$this->authorizeWrite('settings');
|
||||||
$settings = EmailSetting::first() ?? new EmailSetting();
|
$settings = EmailSetting::first() ?? new EmailSetting();
|
||||||
$senderAccounts = SenderAccount::orderBy('email_address')->get();
|
$senderAccounts = SenderAccount::orderBy('email_address')->get();
|
||||||
return view('admin.email-settings.index', compact('settings', 'senderAccounts'));
|
$googleStatus = app(GoogleOAuthService::class)->getConnectionStatus();
|
||||||
|
$firme = $settings->exists ? $settings->firme : collect();
|
||||||
|
return view('admin.email-settings.index', compact('settings', 'senderAccounts', 'googleStatus', 'firme'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(Request $request)
|
public function save(Request $request)
|
||||||
@@ -38,11 +45,15 @@ class EmailSettingsController extends Controller
|
|||||||
'smtp_password' => 'nullable|string',
|
'smtp_password' => 'nullable|string',
|
||||||
'email_address' => 'required|email',
|
'email_address' => 'required|email',
|
||||||
'email_name' => 'nullable|string|max:255',
|
'email_name' => 'nullable|string|max:255',
|
||||||
|
'from_email' => 'nullable|email|max:255',
|
||||||
|
'from_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' => 'nullable|string',
|
||||||
'signature_enabled' => 'boolean',
|
'signature_enabled' => 'boolean',
|
||||||
|
'auth_method' => 'nullable|in:password,oauth',
|
||||||
|
'google_oauth_connection_id' => 'nullable|integer|exists:google_oauth_connections,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!empty($validated['imap_password'])) {
|
if (!empty($validated['imap_password'])) {
|
||||||
@@ -61,29 +72,17 @@ class EmailSettingsController extends Controller
|
|||||||
$validated
|
$validated
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verifica che i dati siano stati salvati
|
|
||||||
$verify = EmailSetting::find($saved->id);
|
$verify = EmailSetting::find($saved->id);
|
||||||
if ($verify &&
|
|
||||||
|
$allGood = $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'];
|
$allGood = $allGood &&
|
||||||
$signatureEnabledMatch = !array_key_exists('signature_enabled', $validated) || (bool)$verify->signature_enabled === (bool)$validated['signature_enabled'];
|
(!array_key_exists('signature', $validated) || $verify->signature === $validated['signature']) &&
|
||||||
|
(!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,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if ($allGood) {
|
||||||
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.');
|
||||||
@@ -99,7 +98,6 @@ class EmailSettingsController extends Controller
|
|||||||
return response()->json(['success' => false, 'message' => 'Configurazione non trovata.']);
|
return response()->json(['success' => false, 'message' => 'Configurazione non trovata.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifica che i dati siano salvati
|
|
||||||
if (empty($settings->imap_host) || empty($settings->email_address)) {
|
if (empty($settings->imap_host) || empty($settings->email_address)) {
|
||||||
return response()->json(['success' => false, 'message' => 'Prima salva le impostazioni.']);
|
return response()->json(['success' => false, 'message' => 'Prima salva le impostazioni.']);
|
||||||
}
|
}
|
||||||
@@ -129,60 +127,27 @@ class EmailSettingsController extends Controller
|
|||||||
return response()->json(['success' => false, 'message' => 'Configurazione non trovata.']);
|
return response()->json(['success' => false, 'message' => 'Configurazione non trovata.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($settings->smtp_host)) {
|
|
||||||
$settings->smtp_host = 'smtp.gmail.com';
|
|
||||||
$settings->smtp_port = 587;
|
|
||||||
$settings->smtp_encryption = 'tls';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$smtpUsername = $settings->smtp_username;
|
$mailer = $settings->getSmtpMailer();
|
||||||
$smtpPassword = $settings->smtp_password;
|
|
||||||
|
|
||||||
if (empty($smtpUsername)) {
|
if ($mailer === null) {
|
||||||
$smtpUsername = $settings->email_address;
|
return response()->json(['success' => false, 'message' => 'Impossibile creare il mailer SMTP. Verifica la configurazione.']);
|
||||||
}
|
|
||||||
if (empty($smtpPassword)) {
|
|
||||||
$smtpPassword = $settings->getDecryptedPassword();
|
|
||||||
} else {
|
|
||||||
$smtpPassword = Crypt::decryptString($smtpPassword);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$encryption = $settings->smtp_encryption ?? 'tls';
|
$smtpUser = $settings->smtp_username ?: $settings->email_address;
|
||||||
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
$fromName = $settings->getEffectiveFromName();
|
||||||
|
$replyToEmail = $settings->from_email ?: $smtpUser;
|
||||||
|
|
||||||
$dsn = sprintf(
|
$email = (new Email())
|
||||||
'%s://%s:%s@%s:%d?encryption=%s',
|
->from(Address::create($smtpUser, $fromName))
|
||||||
$scheme,
|
|
||||||
rawurlencode($smtpUsername),
|
|
||||||
rawurlencode($smtpPassword),
|
|
||||||
$settings->smtp_host,
|
|
||||||
$settings->smtp_port ?? 587,
|
|
||||||
$encryption
|
|
||||||
);
|
|
||||||
|
|
||||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
|
||||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
|
||||||
|
|
||||||
$fromName = $settings->email_name ?? '';
|
|
||||||
if (filter_var($fromName, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
$fromName = 'Glastree';
|
|
||||||
} else {
|
|
||||||
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
|
||||||
$fromName = trim($fromName);
|
|
||||||
if (empty($fromName) || strlen($fromName) > 50) {
|
|
||||||
$fromName = 'Glastree';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
|
|
||||||
|
|
||||||
$email = (new \Symfony\Component\Mime\Email())
|
|
||||||
->from($fromAddress)
|
|
||||||
->to($request->email)
|
->to($request->email)
|
||||||
->subject('Test Configurazione SMTP - Glastree')
|
->subject('Test Configurazione SMTP - Glastree')
|
||||||
->text('Test email da Glastree - Configurazione SMTP funziona!');
|
->text('Test email da Glastree - Configurazione SMTP funziona!');
|
||||||
|
|
||||||
|
if ($replyToEmail !== $smtpUser) {
|
||||||
|
$email->replyTo(Address::create($replyToEmail, $fromName));
|
||||||
|
}
|
||||||
|
|
||||||
$mailer->send($email);
|
$mailer->send($email);
|
||||||
|
|
||||||
\Illuminate\Support\Facades\Log::info('Test SMTP ok', ['to' => $request->email, 'from' => $settings->email_address]);
|
\Illuminate\Support\Facades\Log::info('Test SMTP ok', ['to' => $request->email, 'from' => $settings->email_address]);
|
||||||
@@ -227,6 +192,8 @@ class EmailSettingsController extends Controller
|
|||||||
'verify_email' => 'nullable|email|max:255',
|
'verify_email' => 'nullable|email|max:255',
|
||||||
'note' => 'nullable|string|max:500',
|
'note' => 'nullable|string|max:500',
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
|
'auth_method' => 'nullable|in:password,oauth',
|
||||||
|
'google_oauth_connection_id' => 'nullable|integer|exists:google_oauth_connections,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!empty($data['smtp_password'])) {
|
if (!empty($data['smtp_password'])) {
|
||||||
@@ -257,6 +224,8 @@ class EmailSettingsController extends Controller
|
|||||||
'verify_email' => 'nullable|email|max:255',
|
'verify_email' => 'nullable|email|max:255',
|
||||||
'note' => 'nullable|string|max:500',
|
'note' => 'nullable|string|max:500',
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
|
'auth_method' => 'nullable|in:password,oauth',
|
||||||
|
'google_oauth_connection_id' => 'nullable|integer|exists:google_oauth_connections,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!empty($data['smtp_password'])) {
|
if (!empty($data['smtp_password'])) {
|
||||||
@@ -326,4 +295,66 @@ class EmailSettingsController extends Controller
|
|||||||
return response()->json(['success' => false, 'message' => 'Errore invio: ' . $e->getMessage()]);
|
return response()->json(['success' => false, 'message' => 'Errore invio: ' . $e->getMessage()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function firmaStore(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
$data = $request->validate([
|
||||||
|
'nome' => 'required|string|max:255',
|
||||||
|
'contenuto_html' => 'nullable|string',
|
||||||
|
'is_default' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$settings = EmailSetting::getActive();
|
||||||
|
if (!$settings) {
|
||||||
|
return redirect('/impostazioni/email')->with('error', 'Account email non configurato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$firma = $settings->firme()->create($data);
|
||||||
|
|
||||||
|
if ($data['is_default'] ?? false) {
|
||||||
|
$settings->firme()->where('id', '!=', $firma->id)->update(['is_default' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $firma->nome . '" creata.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function firmaUpdate(Request $request, int $id)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
$data = $request->validate([
|
||||||
|
'nome' => 'required|string|max:255',
|
||||||
|
'contenuto_html' => 'nullable|string',
|
||||||
|
'is_default' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$firma = Firma::findOrFail($id);
|
||||||
|
$firma->update($data);
|
||||||
|
|
||||||
|
if ($data['is_default'] ?? false) {
|
||||||
|
Firma::where('email_setting_id', $firma->email_setting_id)
|
||||||
|
->where('id', '!=', $firma->id)
|
||||||
|
->update(['is_default' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $firma->nome . '" aggiornata.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function firmaSetDefault(int $id)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
$firma = Firma::findOrFail($id);
|
||||||
|
Firma::where('email_setting_id', $firma->email_setting_id)->update(['is_default' => false]);
|
||||||
|
$firma->update(['is_default' => true]);
|
||||||
|
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $firma->nome . '" impostata come predefinita.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function firmaDestroy(int $id)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
$firma = Firma::findOrFail($id);
|
||||||
|
$nome = $firma->nome;
|
||||||
|
$firma->delete();
|
||||||
|
return redirect('/impostazioni/email#firme')->with('success', 'Firma "' . $nome . '" eliminata.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ class CalendarioConnessioneController extends Controller
|
|||||||
$connessione->is_active = $request->boolean('is_active', true);
|
$connessione->is_active = $request->boolean('is_active', true);
|
||||||
|
|
||||||
if (!empty($data['config'])) {
|
if (!empty($data['config'])) {
|
||||||
$connessione->encryptAndSetConfig($data['config']);
|
$connessione->encryptAndSetConfig($data['config'], $connessione->config ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
$connessione->save();
|
$connessione->save();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\Individuo;
|
|||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Evento;
|
use App\Models\Evento;
|
||||||
use App\Models\StorageRepository;
|
use App\Models\StorageRepository;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\TipologiaDocumento;
|
use App\Models\TipologiaDocumento;
|
||||||
use App\Services\StorageRepositoryService;
|
use App\Services\StorageRepositoryService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@@ -42,18 +43,24 @@ class DocumentoController extends Controller
|
|||||||
$folderId = $request->get('folder_id');
|
$folderId = $request->get('folder_id');
|
||||||
$repoId = $request->get('repository_id');
|
$repoId = $request->get('repository_id');
|
||||||
|
|
||||||
$query = Documento::with(['user', 'target', 'cartella', 'repository'])
|
$query = Documento::with(['user', 'target', 'cartella', 'repository', 'tags'])
|
||||||
->orderByDesc('created_at');
|
->orderByDesc('created_at');
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
if ($repoId) {
|
if ($repoId) {
|
||||||
$query->where('repository_id', $repoId);
|
$query->where('repository_id', $repoId);
|
||||||
} elseif ($folderId) {
|
} elseif ($folderId) {
|
||||||
$query->where('cartella_id', $folderId);
|
$query->where('cartella_id', $folderId);
|
||||||
} else {
|
} else {
|
||||||
$query->whereNull('repository_id');
|
$query->whereNull('repository_id')->whereNull('cartella_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
$documenti = $query->paginate(20);
|
$documenti = $query->paginate(20);
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
$cartelle = DocumentoCartella::with('children')
|
$cartelle = DocumentoCartella::with('children')
|
||||||
->whereNull('parent_id')
|
->whereNull('parent_id')
|
||||||
->orderBy('nome')
|
->orderBy('nome')
|
||||||
@@ -85,11 +92,45 @@ class DocumentoController extends Controller
|
|||||||
'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]),
|
'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'documenti')
|
||||||
|
->where('is_default', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'documenti')
|
||||||
|
->orderBy('is_default', 'desc')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$allColumnDefs = [
|
||||||
|
['key' => 'nome_file', 'label' => 'Nome'],
|
||||||
|
['key' => 'tipologia', 'label' => 'Tipologia'],
|
||||||
|
['key' => 'dimensione', 'label' => 'Dimensione'],
|
||||||
|
['key' => 'visibilita', 'label' => 'Contesto'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'created_at', 'label' => 'Data'],
|
||||||
|
];
|
||||||
|
$defaultVisible = ['nome_file', 'tipologia', 'dimensione', 'visibilita', 'tag', 'created_at'];
|
||||||
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
|
? $vista->colonne_visibili
|
||||||
|
: $defaultVisible;
|
||||||
|
|
||||||
|
$tableColumns = array_map(fn($col) => [
|
||||||
|
'key' => $col['key'],
|
||||||
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumnDefs);
|
||||||
|
$entityType = 'documenti';
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
return view('documenti.index', compact(
|
return view('documenti.index', compact(
|
||||||
'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
|
'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
|
||||||
'currentRepo', 'repositories',
|
'currentRepo', 'repositories',
|
||||||
'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie',
|
'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie',
|
||||||
'cartelleMoveOptions'
|
'cartelleMoveOptions', 'allTags',
|
||||||
|
'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths',
|
||||||
|
'userVistas'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +147,15 @@ class DocumentoController extends Controller
|
|||||||
public function edit($documento)
|
public function edit($documento)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('documenti');
|
$this->authorizeWrite('documenti');
|
||||||
$documento = Documento::with('target')->findOrFail($documento);
|
$documento = Documento::with(['target', 'tags'])->findOrFail($documento);
|
||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$gruppi = Gruppo::orderBy('nome')->get();
|
$gruppi = Gruppo::orderBy('nome')->get();
|
||||||
$eventi = Evento::orderBy('nome_evento')->get();
|
$eventi = Evento::orderBy('nome_evento')->get();
|
||||||
$mailingLists = \App\Models\MailingList::orderBy('nome')->get();
|
$mailingLists = \App\Models\MailingList::orderBy('nome')->get();
|
||||||
$tipologie = TipologiaDocumento::attive();
|
$tipologie = TipologiaDocumento::attive();
|
||||||
return view('documenti.edit', compact('documento', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $documento->tags->pluck('id')->toArray();
|
||||||
|
return view('documenti.edit', compact('documento', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function options(Request $request)
|
public function options(Request $request)
|
||||||
@@ -158,50 +201,115 @@ class DocumentoController extends Controller
|
|||||||
$tipologieValidi = TipologiaDocumento::opzioni();
|
$tipologieValidi = TipologiaDocumento::opzioni();
|
||||||
$tipologieRule = 'in:' . implode(',', $tipologieValidi);
|
$tipologieRule = 'in:' . implode(',', $tipologieValidi);
|
||||||
|
|
||||||
$data = $request->validate([
|
$rules = [
|
||||||
'nome_file' => 'required|string|max:255',
|
'nome_file' => 'required|string|max:255',
|
||||||
'tipologia' => 'required|' . $tipologieRule,
|
'tipologia' => 'required|' . $tipologieRule,
|
||||||
'visibilita' => 'nullable|string',
|
'contesto_tipo' => 'nullable|in:individuo,gruppo,evento,mailing',
|
||||||
'visibilita_target_id' => 'nullable|integer',
|
'visibilita_target_id' => 'nullable|integer',
|
||||||
'visibilita_target_type' => 'nullable|string',
|
|
||||||
'note' => 'nullable|string',
|
'note' => 'nullable|string',
|
||||||
]);
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
];
|
||||||
|
|
||||||
$data['visibilita'] = $request->contesto_tipo ?: 'pubblico';
|
if ($documento->isLink()) {
|
||||||
|
$rules['url'] = 'nullable|url|max:2048';
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->contesto_tipo) {
|
$data = $request->validate($rules);
|
||||||
|
|
||||||
|
$contestoTipo = $data['contesto_tipo'] ?? null;
|
||||||
|
$data['visibilita'] = $contestoTipo ?: 'pubblico';
|
||||||
|
|
||||||
|
if ($contestoTipo) {
|
||||||
$typeMap = [
|
$typeMap = [
|
||||||
'individuo' => Individuo::class,
|
'individuo' => Individuo::class,
|
||||||
'gruppo' => Gruppo::class,
|
'gruppo' => Gruppo::class,
|
||||||
'evento' => Evento::class,
|
'evento' => Evento::class,
|
||||||
'mailing' => \App\Models\MailingList::class,
|
'mailing' => \App\Models\MailingList::class,
|
||||||
];
|
];
|
||||||
$data['visibilita_target_type'] = $typeMap[$request->contesto_tipo] ?? null;
|
$data['visibilita_target_type'] = $typeMap[$contestoTipo] ?? null;
|
||||||
} else {
|
} else {
|
||||||
$data['visibilita'] = 'pubblico';
|
$data['visibilita'] = 'pubblico';
|
||||||
$data['visibilita_target_id'] = null;
|
$data['visibilita_target_id'] = null;
|
||||||
$data['visibilita_target_type'] = null;
|
$data['visibilita_target_type'] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($documento->isLink()) {
|
||||||
|
$data['url'] = $data['url'] ?? $documento->url;
|
||||||
|
}
|
||||||
|
|
||||||
$documento->update($data);
|
$documento->update($data);
|
||||||
|
|
||||||
return redirect('/documenti')->with('success', 'Documento aggiornato.');
|
if ($request->has('tags')) {
|
||||||
|
$documento->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$documento->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirect = $request->input('folder_id') ? '/documenti?folder_id=' . $request->input('folder_id') : '/documenti';
|
||||||
|
return redirect($redirect)->with('success', 'Documento aggiornato.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('documenti');
|
$this->authorizeWrite('documenti');
|
||||||
$data = $request->validate([
|
$tipologieValidi = TipologiaDocumento::opzioni();
|
||||||
|
$tipologieRule = 'in:' . implode(',', $tipologieValidi);
|
||||||
|
|
||||||
|
$isLink = $request->input('tipo_caricamento') === 'link';
|
||||||
|
|
||||||
|
$rules = [
|
||||||
'nome_file' => 'required|string|max:255',
|
'nome_file' => 'required|string|max:255',
|
||||||
'tipologia' => 'required|in:avatar,galleria,documento,statuto,altro',
|
'tipologia' => 'required|' . $tipologieRule,
|
||||||
'file' => 'required|file|max:10240',
|
|
||||||
'visibilita' => 'required|in:pubblico,individuo,gruppo',
|
'visibilita' => 'required|in:pubblico,individuo,gruppo',
|
||||||
'visibilita_target_id' => 'nullable|integer',
|
'visibilita_target_id' => 'nullable|integer',
|
||||||
'visibilita_target_type' => 'nullable|string',
|
'visibilita_target_type' => 'nullable|string',
|
||||||
'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
|
'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
|
||||||
'repository_id' => 'nullable|integer|exists:storage_repositories,id',
|
'repository_id' => 'nullable|integer|exists:storage_repositories,id',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'tipo_caricamento' => 'required|in:file,link',
|
||||||
|
'url' => 'required_if:tipo_caricamento,link|nullable|url|max:2048',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$isLink) {
|
||||||
|
$rules['file'] = 'required|file|max:10240';
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate($rules);
|
||||||
|
|
||||||
|
if ($isLink) {
|
||||||
|
$linkVisibilitaTargetType = null;
|
||||||
|
if ($data['visibilita'] === 'individuo' && $data['visibilita_target_id']) {
|
||||||
|
$linkVisibilitaTargetType = Individuo::class;
|
||||||
|
} elseif ($data['visibilita'] === 'gruppo' && $data['visibilita_target_id']) {
|
||||||
|
$linkVisibilitaTargetType = Gruppo::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
$doc = Documento::create([
|
||||||
|
'nome_file' => $data['nome_file'],
|
||||||
|
'url' => $data['url'],
|
||||||
|
'tipo' => 'link',
|
||||||
|
'tipologia' => $data['tipologia'],
|
||||||
|
'visibilita' => $data['visibilita'],
|
||||||
|
'visibilita_target_id' => $data['visibilita_target_id'] ?? null,
|
||||||
|
'visibilita_target_type' => $linkVisibilitaTargetType,
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'cartella_id' => $data['cartella_id'] ?? null,
|
||||||
|
'repository_id' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($doc && $request->has('tags')) {
|
||||||
|
$doc->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirect = $request->_redirect ?? url()->previous();
|
||||||
|
if ($request->filled('folder_id')) {
|
||||||
|
$redirect = '/documenti?folder_id=' . $request->input('folder_id');
|
||||||
|
}
|
||||||
|
return redirect($redirect)->with('success', 'Documento (link) creato.');
|
||||||
|
}
|
||||||
|
|
||||||
$repositoryId = $data['repository_id'] ?? null;
|
$repositoryId = $data['repository_id'] ?? null;
|
||||||
|
|
||||||
if ($repositoryId) {
|
if ($repositoryId) {
|
||||||
@@ -233,7 +341,7 @@ class DocumentoController extends Controller
|
|||||||
$visibilitaTargetType = Gruppo::class;
|
$visibilitaTargetType = Gruppo::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
Documento::create([
|
$doc = Documento::create([
|
||||||
'nome_file' => $data['nome_file'],
|
'nome_file' => $data['nome_file'],
|
||||||
'file_path' => $filePath,
|
'file_path' => $filePath,
|
||||||
'storage_disk' => $disk ?? null,
|
'storage_disk' => $disk ?? null,
|
||||||
@@ -249,7 +357,13 @@ class DocumentoController extends Controller
|
|||||||
'repository_id' => $repositoryId,
|
'repository_id' => $repositoryId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($doc && $request->has('tags')) {
|
||||||
|
$doc->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
$redirect = $request->_redirect ?? url()->previous();
|
$redirect = $request->_redirect ?? url()->previous();
|
||||||
|
if ($request->filled('folder_id')) {
|
||||||
|
$redirect = '/documenti?folder_id=' . $request->input('folder_id');
|
||||||
|
}
|
||||||
return redirect($redirect)->with('success', 'Documento caricato.');
|
return redirect($redirect)->with('success', 'Documento caricato.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +372,10 @@ class DocumentoController extends Controller
|
|||||||
$this->authorizeRead('documenti');
|
$this->authorizeRead('documenti');
|
||||||
$documento = Documento::findOrFail($documento);
|
$documento = Documento::findOrFail($documento);
|
||||||
|
|
||||||
|
if ($documento->isLink()) {
|
||||||
|
return redirect()->away($documento->url);
|
||||||
|
}
|
||||||
|
|
||||||
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
|
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
|
||||||
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
|
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
|
||||||
|
|
||||||
@@ -303,6 +421,12 @@ class DocumentoController extends Controller
|
|||||||
$this->authorizeRead('documenti');
|
$this->authorizeRead('documenti');
|
||||||
$documento = Documento::findOrFail($documento);
|
$documento = Documento::findOrFail($documento);
|
||||||
|
|
||||||
|
if ($documento->isLink()) {
|
||||||
|
return response()->view('documenti.preview-fallback', [
|
||||||
|
'documento' => $documento,
|
||||||
|
])->header('X-Preview-Fallback', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
$previewableMimeTypes = [
|
$previewableMimeTypes = [
|
||||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
|
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
|
||||||
'application/pdf',
|
'application/pdf',
|
||||||
@@ -353,6 +477,7 @@ class DocumentoController extends Controller
|
|||||||
$this->authorizeDelete('documenti');
|
$this->authorizeDelete('documenti');
|
||||||
$documento = Documento::findOrFail($documento);
|
$documento = Documento::findOrFail($documento);
|
||||||
|
|
||||||
|
if (!$documento->isLink()) {
|
||||||
if ($documento->repository_id) {
|
if ($documento->repository_id) {
|
||||||
$repo = StorageRepository::find($documento->repository_id);
|
$repo = StorageRepository::find($documento->repository_id);
|
||||||
if ($repo) {
|
if ($repo) {
|
||||||
@@ -367,6 +492,7 @@ class DocumentoController extends Controller
|
|||||||
Storage::disk($disk)->delete($documento->file_path);
|
Storage::disk($disk)->delete($documento->file_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$documento->delete();
|
$documento->delete();
|
||||||
|
|
||||||
@@ -443,9 +569,10 @@ class DocumentoController extends Controller
|
|||||||
{
|
{
|
||||||
$idsInput = $request->input('ids', '');
|
$idsInput = $request->input('ids', '');
|
||||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
$ids = array_values(array_filter($ids, fn($v) => is_numeric($v)));
|
||||||
|
|
||||||
if (empty($ids)) {
|
if (empty($ids)) {
|
||||||
return response()->json(['linked_count' => 0, 'links' => []]);
|
return back()->with('error', 'Nessun documento selezionato.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$documenti = Documento::whereIn('id', $ids)->get();
|
$documenti = Documento::whereIn('id', $ids)->get();
|
||||||
@@ -521,6 +648,7 @@ class DocumentoController extends Controller
|
|||||||
$this->authorizeRead('documenti');
|
$this->authorizeRead('documenti');
|
||||||
$idsInput = $request->input('ids', '');
|
$idsInput = $request->input('ids', '');
|
||||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
$ids = array_values(array_filter($ids, fn($v) => is_numeric($v)));
|
||||||
|
|
||||||
if (empty($ids)) {
|
if (empty($ids)) {
|
||||||
return back()->with('error', 'Nessun documento selezionato.');
|
return back()->with('error', 'Nessun documento selezionato.');
|
||||||
@@ -539,8 +667,14 @@ class DocumentoController extends Controller
|
|||||||
return back()->with('error', 'Impossibile creare l\'archivio ZIP.');
|
return back()->with('error', 'Impossibile creare l\'archivio ZIP.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$skippedLinks = 0;
|
||||||
$addedCount = 0;
|
$addedCount = 0;
|
||||||
foreach ($documenti as $documento) {
|
foreach ($documenti as $documento) {
|
||||||
|
if ($documento->isLink()) {
|
||||||
|
$skippedLinks++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
|
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
|
||||||
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
|
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
|
||||||
|
|
||||||
@@ -573,11 +707,23 @@ class DocumentoController extends Controller
|
|||||||
$zip->close();
|
$zip->close();
|
||||||
|
|
||||||
if ($addedCount === 0) {
|
if ($addedCount === 0) {
|
||||||
unlink($zipPath);
|
if (file_exists($zipPath)) {
|
||||||
return back()->with('error', 'Nessun file disponibile per il download.');
|
@unlink($zipPath);
|
||||||
|
}
|
||||||
|
$message = 'Nessun file disponibile per il download.';
|
||||||
|
if ($skippedLinks > 0) {
|
||||||
|
$message .= " ({$skippedLinks} link esterni saltati)";
|
||||||
|
}
|
||||||
|
return back()->with('error', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->download($zipPath, 'documenti.zip')->deleteFileAfterSend(true);
|
$message = $addedCount . ' file scaricati.';
|
||||||
|
if ($skippedLinks > 0) {
|
||||||
|
$message .= " {$skippedLinks} link esterni saltati.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->download($zipPath, 'documenti.zip')->deleteFileAfterSend(true)
|
||||||
|
->with('success', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function massDestroy(Request $request)
|
public function massDestroy(Request $request)
|
||||||
@@ -585,6 +731,7 @@ class DocumentoController extends Controller
|
|||||||
$this->authorizeDelete('documenti');
|
$this->authorizeDelete('documenti');
|
||||||
$idsInput = $request->input('ids', '');
|
$idsInput = $request->input('ids', '');
|
||||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
$ids = array_values(array_filter($ids, fn($v) => is_numeric($v)));
|
||||||
|
|
||||||
if (empty($ids)) {
|
if (empty($ids)) {
|
||||||
return back()->with('error', 'Nessun documento selezionato.');
|
return back()->with('error', 'Nessun documento selezionato.');
|
||||||
@@ -593,6 +740,7 @@ class DocumentoController extends Controller
|
|||||||
$documenti = Documento::whereIn('id', $ids)->get();
|
$documenti = Documento::whereIn('id', $ids)->get();
|
||||||
|
|
||||||
foreach ($documenti as $documento) {
|
foreach ($documenti as $documento) {
|
||||||
|
if (!$documento->isLink()) {
|
||||||
if ($documento->repository_id) {
|
if ($documento->repository_id) {
|
||||||
$repo = StorageRepository::find($documento->repository_id);
|
$repo = StorageRepository::find($documento->repository_id);
|
||||||
if ($repo) {
|
if ($repo) {
|
||||||
@@ -607,6 +755,7 @@ class DocumentoController extends Controller
|
|||||||
Storage::disk($disk)->delete($documento->file_path);
|
Storage::disk($disk)->delete($documento->file_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
$documento->delete();
|
$documento->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,6 +829,7 @@ class DocumentoController extends Controller
|
|||||||
$this->authorizeWrite('documenti');
|
$this->authorizeWrite('documenti');
|
||||||
$idsInput = $request->input('ids', '');
|
$idsInput = $request->input('ids', '');
|
||||||
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
$ids = array_values(array_filter($ids, fn($v) => is_numeric($v)));
|
||||||
|
|
||||||
if (empty($ids)) {
|
if (empty($ids)) {
|
||||||
return back()->with('error', 'Nessun documento selezionato.');
|
return back()->with('error', 'Nessun documento selezionato.');
|
||||||
@@ -706,6 +856,42 @@ class DocumentoController extends Controller
|
|||||||
return back()->with('success', count($ids) . ' documenti associati.');
|
return back()->with('success', count($ids) . ' documenti associati.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('documenti');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
$ids = array_values(array_filter($ids, fn($v) => is_numeric($v)));
|
||||||
|
|
||||||
|
if (empty($ids)) {
|
||||||
|
return back()->with('error', 'Nessun documento selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
Documento::whereIn('id', $ids)->chunk(100, function ($documenti) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($documenti as $documento) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$documento->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$documento->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return back()->with('success', "Tag $actionLabel per $count documenti.");
|
||||||
|
}
|
||||||
|
|
||||||
public function massMove(Request $request): RedirectResponse
|
public function massMove(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('documenti');
|
$this->authorizeWrite('documenti');
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\MailingList;
|
|||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
|
use App\Models\Firma;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@@ -94,6 +95,11 @@ class EmailController extends Controller
|
|||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$senderAccounts = SenderAccount::active()->get();
|
$senderAccounts = SenderAccount::active()->get();
|
||||||
|
|
||||||
|
$documenti = Documento::orderBy('nome_file')->get();
|
||||||
|
|
||||||
|
$settings = EmailSetting::getActive();
|
||||||
|
$firme = $settings?->firme ?? collect();
|
||||||
|
|
||||||
$prefill = [];
|
$prefill = [];
|
||||||
if ($request->reply_to) {
|
if ($request->reply_to) {
|
||||||
$original = EmailMessage::find($request->reply_to);
|
$original = EmailMessage::find($request->reply_to);
|
||||||
@@ -111,7 +117,7 @@ class EmailController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'prefill', 'senderAccounts'));
|
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'documenti', 'prefill', 'senderAccounts', 'firme'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function send(Request $request)
|
public function send(Request $request)
|
||||||
@@ -126,6 +132,7 @@ class EmailController extends Controller
|
|||||||
$rules['to'] = 'required';
|
$rules['to'] = 'required';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$rules['firma_id'] = 'nullable|integer|exists:firme,id';
|
||||||
$validated = $request->validate($rules);
|
$validated = $request->validate($rules);
|
||||||
|
|
||||||
$recipients = $this->resolveRecipients($request);
|
$recipients = $this->resolveRecipients($request);
|
||||||
@@ -155,12 +162,18 @@ class EmailController extends Controller
|
|||||||
|
|
||||||
$imapSuccess = false;
|
$imapSuccess = false;
|
||||||
$imapError = null;
|
$imapError = null;
|
||||||
$attachments = $this->processAttachments($request);
|
$attachmentResult = $this->processAttachments($request);
|
||||||
|
$attachmentPaths = $attachmentResult['paths'];
|
||||||
|
$linkDocs = $attachmentResult['links'];
|
||||||
|
|
||||||
|
$firmaId = $request->firma_id;
|
||||||
|
|
||||||
|
$body = $this->appendLinksToBody($validated['body'], $linkDocs);
|
||||||
|
|
||||||
foreach ($recipients as $recipient) {
|
foreach ($recipients as $recipient) {
|
||||||
try {
|
try {
|
||||||
$bodyWithSig = $this->appendSignature($validated['body'], $settings->getSignature(), $settings->signature_enabled ?? false);
|
$bodyWithSig = $this->appendSignature($body, $firmaId, $settings);
|
||||||
$this->sendViaImap($settings, $recipient, $validated['subject'], $bodyWithSig, $request->cc, $attachments);
|
$this->sendViaImap($settings, $recipient, $validated['subject'], $bodyWithSig, $request->cc, $attachmentPaths);
|
||||||
$imapSuccess = true;
|
$imapSuccess = true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$imapError = $e->getMessage();
|
$imapError = $e->getMessage();
|
||||||
@@ -169,7 +182,7 @@ class EmailController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$bodyWithSig = $this->appendSignature($validated['body'], $settings->getSignature(), $settings->signature_enabled ?? false);
|
$bodyWithSig = $this->appendSignature($body, $firmaId, $settings);
|
||||||
$this->storeSentMessage($settings, $recipients, $validated['subject'], $bodyWithSig, $request->cc);
|
$this->storeSentMessage($settings, $recipients, $validated['subject'], $bodyWithSig, $request->cc);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Illuminate\Support\Facades\Log::error('Store sent message error', ['error' => $e->getMessage()]);
|
\Illuminate\Support\Facades\Log::error('Store sent message error', ['error' => $e->getMessage()]);
|
||||||
@@ -186,13 +199,20 @@ class EmailController extends Controller
|
|||||||
private function sendWithSender(Request $request, array $validated, array $recipients)
|
private function sendWithSender(Request $request, array $validated, array $recipients)
|
||||||
{
|
{
|
||||||
$sender = SenderAccount::findOrFail($request->mittente_id);
|
$sender = SenderAccount::findOrFail($request->mittente_id);
|
||||||
$attachmentPaths = $this->resolveAttachmentPaths($request);
|
$attachmentResult = $this->resolveAttachmentPaths($request);
|
||||||
|
$attachmentPaths = $attachmentResult['paths'];
|
||||||
|
$linkDocs = $attachmentResult['links'];
|
||||||
|
|
||||||
|
$settings = EmailSetting::getActive();
|
||||||
|
$body = $this->appendLinksToBody($validated['body'], $linkDocs);
|
||||||
|
$body = $this->appendSignature($body, $request->firma_id, $settings);
|
||||||
|
|
||||||
$ok = 0;
|
$ok = 0;
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
foreach ($recipients as $recipient) {
|
foreach ($recipients as $recipient) {
|
||||||
try {
|
try {
|
||||||
$sender->sendEmail($recipient, $validated['subject'], $validated['body'], $attachmentPaths);
|
$sender->sendEmail($recipient, $validated['subject'], $body, $attachmentPaths);
|
||||||
$ok++;
|
$ok++;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$errors[] = $recipient . ': ' . $e->getMessage();
|
$errors[] = $recipient . ': ' . $e->getMessage();
|
||||||
@@ -235,8 +255,8 @@ class EmailController extends Controller
|
|||||||
'body_text' => $request->body,
|
'body_text' => $request->body,
|
||||||
'to_email' => $request->to,
|
'to_email' => $request->to,
|
||||||
'is_draft' => true,
|
'is_draft' => true,
|
||||||
'from_email' => $settings->email_address ?? '',
|
'from_email' => $settings->getEffectiveFromAddress(),
|
||||||
'from_name' => $settings->email_name ?? '',
|
'from_name' => $settings->getEffectiveFromName(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json(['success' => true, 'draft_id' => $message->id]);
|
return response()->json(['success' => true, 'draft_id' => $message->id]);
|
||||||
@@ -341,7 +361,7 @@ class EmailController extends Controller
|
|||||||
$message->delete();
|
$message->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->with('success', 'Email spostata nel cestino.');
|
return redirect()->route('email.index', ['folder' => 'inbox'])->with('success', 'Email spostata nel cestino.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadAttachment($id)
|
public function downloadAttachment($id)
|
||||||
@@ -494,12 +514,20 @@ class EmailController extends Controller
|
|||||||
return array_filter(array_unique($recipients));
|
return array_filter(array_unique($recipients));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function appendSignature(string $body, ?string $signature, bool $enabled): string
|
private function appendSignature(string $body, int|string|null $firmaId = null, ?EmailSetting $settings = null): string
|
||||||
{
|
{
|
||||||
if (empty($signature) || !$enabled) {
|
if ($firmaId) {
|
||||||
return $body;
|
$firma = Firma::find($firmaId);
|
||||||
|
if ($firma && $firma->contenuto_html) {
|
||||||
|
return $body . "\n\n---\n" . $firma->contenuto_html;
|
||||||
}
|
}
|
||||||
return $body . "\n\n---\n" . $signature;
|
}
|
||||||
|
|
||||||
|
if ($settings && $settings->signature && ($settings->signature_enabled ?? false)) {
|
||||||
|
return $body . "\n\n---\n" . $settings->signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $body;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendViaImap($settings, $to, $subject, $body, $cc = null, array $attachmentPaths = [])
|
private function sendViaImap($settings, $to, $subject, $body, $cc = null, array $attachmentPaths = [])
|
||||||
@@ -525,18 +553,10 @@ class EmailController extends Controller
|
|||||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
||||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||||
|
|
||||||
$fromName = $settings->email_name ?? '';
|
$fromAddress = \Symfony\Component\Mime\Address::create(
|
||||||
if (filter_var($fromName, FILTER_VALIDATE_EMAIL)) {
|
$settings->getEffectiveFromAddress(),
|
||||||
$fromName = 'Glastree';
|
$settings->getEffectiveFromName()
|
||||||
} else {
|
);
|
||||||
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
|
||||||
$fromName = trim($fromName);
|
|
||||||
if (empty($fromName)) {
|
|
||||||
$fromName = 'Glastree';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
|
|
||||||
|
|
||||||
$email = (new \Symfony\Component\Mime\Email())
|
$email = (new \Symfony\Component\Mime\Email())
|
||||||
->from($fromAddress)
|
->from($fromAddress)
|
||||||
@@ -544,6 +564,11 @@ class EmailController extends Controller
|
|||||||
->subject($subject)
|
->subject($subject)
|
||||||
->text($body);
|
->text($body);
|
||||||
|
|
||||||
|
$replyTo = $settings->getEffectiveReplyTo();
|
||||||
|
if ($replyTo) {
|
||||||
|
$email->replyTo($replyTo);
|
||||||
|
}
|
||||||
|
|
||||||
if ($cc) {
|
if ($cc) {
|
||||||
$email->cc($cc);
|
$email->cc($cc);
|
||||||
}
|
}
|
||||||
@@ -565,19 +590,7 @@ class EmailController extends Controller
|
|||||||
private function processAttachments(Request $request): array
|
private function processAttachments(Request $request): array
|
||||||
{
|
{
|
||||||
$paths = [];
|
$paths = [];
|
||||||
if ($request->hasFile('allegati')) {
|
$links = [];
|
||||||
foreach ($request->file('allegati') as $file) {
|
|
||||||
if ($file->isValid()) {
|
|
||||||
$paths[] = $file->getRealPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolveAttachmentPaths(Request $request): array
|
|
||||||
{
|
|
||||||
$paths = [];
|
|
||||||
|
|
||||||
if ($request->hasFile('allegati')) {
|
if ($request->hasFile('allegati')) {
|
||||||
foreach ($request->file('allegati') as $file) {
|
foreach ($request->file('allegati') as $file) {
|
||||||
@@ -592,26 +605,71 @@ class EmailController extends Controller
|
|||||||
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
|
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
|
||||||
$docs = Documento::whereIn('id', $ids)->get();
|
$docs = Documento::whereIn('id', $ids)->get();
|
||||||
foreach ($docs as $doc) {
|
foreach ($docs as $doc) {
|
||||||
if ($doc->file_path && \Illuminate\Support\Facades\Storage::exists($doc->file_path)) {
|
if ($doc->isLink()) {
|
||||||
|
$links[] = ['nome' => $doc->nome_file, 'url' => $doc->url];
|
||||||
|
} elseif ($doc->file_path && Storage::exists($doc->file_path)) {
|
||||||
|
$paths[] = Storage::path($doc->file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['paths' => $paths, 'links' => $links];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveAttachmentPaths(Request $request): array
|
||||||
|
{
|
||||||
|
$paths = [];
|
||||||
|
$links = [];
|
||||||
|
|
||||||
|
if ($request->hasFile('allegati')) {
|
||||||
|
foreach ($request->file('allegati') as $file) {
|
||||||
|
if ($file->isValid()) {
|
||||||
|
$paths[] = $file->getRealPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$documentIds = $request->input('documenti_selezionati');
|
||||||
|
if (!empty($documentIds)) {
|
||||||
|
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
|
||||||
|
$docs = Documento::whereIn('id', $ids)->get();
|
||||||
|
foreach ($docs as $doc) {
|
||||||
|
if ($doc->isLink()) {
|
||||||
|
$links[] = ['nome' => $doc->nome_file, 'url' => $doc->url];
|
||||||
|
} elseif ($doc->file_path && \Illuminate\Support\Facades\Storage::exists($doc->file_path)) {
|
||||||
$paths[] = \Illuminate\Support\Facades\Storage::path($doc->file_path);
|
$paths[] = \Illuminate\Support\Facades\Storage::path($doc->file_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $paths;
|
return ['paths' => $paths, 'links' => $links];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function appendLinksToBody(string $body, array $linkDocs): string
|
||||||
|
{
|
||||||
|
if (empty($linkDocs)) {
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
$linksText = "\n\n---\nDocumenti collegati:\n";
|
||||||
|
foreach ($linkDocs as $link) {
|
||||||
|
$linksText .= "- {$link['nome']}: {$link['url']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $body . $linksText;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function storeSentMessage($settings, $recipients, $subject, $body, $cc = null)
|
private function storeSentMessage($settings, $recipients, $subject, $body, $cc = null)
|
||||||
{
|
{
|
||||||
$sentFolder = EmailFolder::where('type', 'sent')->first();
|
$sentFolder = EmailFolder::where('type', 'sent')->first();
|
||||||
$bodyWithSignature = $this->appendSignature($body, $settings->getSignature(), $settings->signature_enabled ?? false);
|
$bodyWithSignature = $body;
|
||||||
|
|
||||||
$message = EmailMessage::create([
|
$message = EmailMessage::create([
|
||||||
'email_folder_id' => $sentFolder->id,
|
'email_folder_id' => $sentFolder->id,
|
||||||
'subject' => $subject,
|
'subject' => $subject,
|
||||||
'body_text' => $bodyWithSignature,
|
'body_text' => $bodyWithSignature,
|
||||||
'from_email' => $settings->email_address,
|
'from_email' => $settings->getEffectiveFromAddress(),
|
||||||
'from_name' => $settings->email_name,
|
'from_name' => $settings->getEffectiveFromName(),
|
||||||
'to_email' => implode(', ', $recipients),
|
'to_email' => implode(', ', $recipients),
|
||||||
'cc' => $cc,
|
'cc' => $cc,
|
||||||
'is_sent' => true,
|
'is_sent' => true,
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Evento;
|
use App\Models\Evento;
|
||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\TipologiaEvento;
|
use App\Models\TipologiaEvento;
|
||||||
use App\Services\IcsExportService;
|
use App\Services\IcsExportService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -16,7 +19,12 @@ class EventoController extends Controller
|
|||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('eventi');
|
$this->authorizeRead('eventi');
|
||||||
$query = Evento::with(['gruppi', 'responsabili']);
|
$query = Evento::with(['gruppi', 'responsabili', 'tags']);
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$query->where('nome_evento', 'like', '%' . $request->search . '%');
|
$query->where('nome_evento', 'like', '%' . $request->search . '%');
|
||||||
@@ -47,8 +55,44 @@ class EventoController extends Controller
|
|||||||
$perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
|
$perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
|
||||||
$eventi = $query->paginate($perPage);
|
$eventi = $query->paginate($perPage);
|
||||||
$gruppi = Gruppo::orderBy('nome')->get();
|
$gruppi = Gruppo::orderBy('nome')->get();
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('eventi.index', compact('eventi', 'gruppi'));
|
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'eventi')
|
||||||
|
->where('is_default', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'eventi')
|
||||||
|
->orderBy('is_default', 'desc')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$allColumnDefs = [
|
||||||
|
['key' => 'nome_evento', 'label' => 'Nome Evento'],
|
||||||
|
['key' => 'descrizione_evento', 'label' => 'Descrizione'],
|
||||||
|
['key' => 'tipo_evento', 'label' => 'Tipologia'],
|
||||||
|
['key' => 'tipo_recorrenza', 'label' => 'Ricorrenza'],
|
||||||
|
['key' => 'gruppi', 'label' => 'Gruppi'],
|
||||||
|
['key' => 'responsabili', 'label' => 'Responsabili'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'data_specifica', 'label' => 'Data'],
|
||||||
|
['key' => 'created_at', 'label' => 'Creato'],
|
||||||
|
];
|
||||||
|
$defaultVisible = ['nome_evento', 'descrizione_evento', 'tipo_evento', 'tipo_recorrenza', 'gruppi', 'responsabili', 'tag', 'created_at'];
|
||||||
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
|
? $vista->colonne_visibili
|
||||||
|
: $defaultVisible;
|
||||||
|
|
||||||
|
$tableColumns = array_map(fn($col) => [
|
||||||
|
'key' => $col['key'],
|
||||||
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumnDefs);
|
||||||
|
$entityType = 'eventi';
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
|
return view('eventi.index', compact('eventi', 'gruppi', 'allTags', 'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths', 'userVistas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(Request $request)
|
public function create(Request $request)
|
||||||
@@ -58,8 +102,9 @@ class EventoController extends Controller
|
|||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$selectedGruppo = $request->query('gruppo_id') ? Gruppo::find($request->query('gruppo_id')) : null;
|
$selectedGruppo = $request->query('gruppo_id') ? Gruppo::find($request->query('gruppo_id')) : null;
|
||||||
$tipologieEventi = TipologiaEvento::attive();
|
$tipologieEventi = TipologiaEvento::attive();
|
||||||
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('eventi.create', compact('gruppi', 'individui', 'selectedGruppo', 'tipologieEventi'));
|
return view('eventi.create', compact('gruppi', 'individui', 'selectedGruppo', 'tipologieEventi', 'tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -88,6 +133,8 @@ class EventoController extends Controller
|
|||||||
'responsabili.*' => 'exists:individui,id',
|
'responsabili.*' => 'exists:individui,id',
|
||||||
'luogo_indirizzo' => 'nullable|string|max:500',
|
'luogo_indirizzo' => 'nullable|string|max:500',
|
||||||
'luogo_url_maps' => 'nullable|string',
|
'luogo_url_maps' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$evento = Evento::create([
|
$evento = Evento::create([
|
||||||
@@ -110,6 +157,10 @@ class EventoController extends Controller
|
|||||||
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!empty($data['tags'])) {
|
||||||
|
$evento->tags()->attach($data['tags']);
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($data['gruppi'])) {
|
if (!empty($data['gruppi'])) {
|
||||||
$evento->gruppi()->attach($data['gruppi']);
|
$evento->gruppi()->attach($data['gruppi']);
|
||||||
}
|
}
|
||||||
@@ -124,19 +175,21 @@ class EventoController extends Controller
|
|||||||
public function show($evento)
|
public function show($evento)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('eventi');
|
$this->authorizeRead('eventi');
|
||||||
$evento = Evento::with(['gruppi', 'responsabili.contatti', 'documenti'])->findOrFail($evento);
|
$evento = Evento::with(['gruppi', 'responsabili.contatti', 'documenti', 'tags'])->findOrFail($evento);
|
||||||
return view('eventi.show', compact('evento'));
|
return view('eventi.show', compact('evento'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function edit($evento)
|
public function edit($evento)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('eventi');
|
$this->authorizeWrite('eventi');
|
||||||
$evento = Evento::with(['gruppi', 'responsabili', 'documenti'])->findOrFail($evento);
|
$evento = Evento::with(['gruppi', 'responsabili', 'documenti', 'tags'])->findOrFail($evento);
|
||||||
$gruppi = Gruppo::orderBy('nome')->get();
|
$gruppi = Gruppo::orderBy('nome')->get();
|
||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$tipologieEventi = TipologiaEvento::attive();
|
$tipologieEventi = TipologiaEvento::attive();
|
||||||
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $evento->tags->pluck('id')->toArray();
|
||||||
|
|
||||||
return view('eventi.edit', compact('evento', 'gruppi', 'individui', 'tipologieEventi'));
|
return view('eventi.edit', compact('evento', 'gruppi', 'individui', 'tipologieEventi', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $evento)
|
public function update(Request $request, $evento)
|
||||||
@@ -167,6 +220,8 @@ class EventoController extends Controller
|
|||||||
'responsabili.*' => 'exists:individui,id',
|
'responsabili.*' => 'exists:individui,id',
|
||||||
'luogo_indirizzo' => 'nullable|string|max:500',
|
'luogo_indirizzo' => 'nullable|string|max:500',
|
||||||
'luogo_url_maps' => 'nullable|string',
|
'luogo_url_maps' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$evento->update([
|
$evento->update([
|
||||||
@@ -189,6 +244,7 @@ class EventoController extends Controller
|
|||||||
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$evento->tags()->sync($data['tags'] ?? []);
|
||||||
$evento->gruppi()->sync($data['gruppi'] ?? []);
|
$evento->gruppi()->sync($data['gruppi'] ?? []);
|
||||||
$evento->responsabili()->sync($data['responsabili'] ?? []);
|
$evento->responsabili()->sync($data['responsabili'] ?? []);
|
||||||
|
|
||||||
@@ -576,4 +632,39 @@ class EventoController extends Controller
|
|||||||
|
|
||||||
return redirect('/eventi')->with('success', $message);
|
return redirect('/eventi')->with('success', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('eventi');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
|
||||||
|
if (empty($ids)) {
|
||||||
|
return back()->with('error', 'Nessun evento selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
Evento::whereIn('id', $ids)->chunk(100, function ($eventi) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($eventi as $evento) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$evento->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$evento->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return back()->with('success', "Tag $actionLabel per $count eventi.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Evento;
|
use App\Models\Evento;
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Enums\GoogleService;
|
||||||
|
use App\Models\GoogleOAuthConnection;
|
||||||
|
use App\Services\GoogleOAuthService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class GoogleOAuthController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly GoogleOAuthService $googleOAuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function redirect(string $service): RedirectResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$serviceEnum = GoogleService::from($service);
|
||||||
|
} catch (\ValueError $e) {
|
||||||
|
return redirect()->back()->withErrors(['error' => 'Servizio Google non valido: ' . $service]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$authUrl = $this->googleOAuthService->getAuthUrl($service);
|
||||||
|
|
||||||
|
return redirect()->away($authUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callback(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$error = $request->input('error');
|
||||||
|
if ($error) {
|
||||||
|
$message = match ($error) {
|
||||||
|
'access_denied' => 'Accesso negato. Autorizzazione non concessa.',
|
||||||
|
default => 'Errore Google: ' . $error,
|
||||||
|
};
|
||||||
|
return redirect()->route('impostazioni.index', ['tab' => 'google'])
|
||||||
|
->withErrors(['error' => $message]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = $request->input('code');
|
||||||
|
if (!$code) {
|
||||||
|
return redirect()->route('impostazioni.index', ['tab' => 'google'])
|
||||||
|
->withErrors(['error' => 'Nessun codice di autorizzazione ricevuto']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = $request->input('state', 'email');
|
||||||
|
try {
|
||||||
|
$service = GoogleService::from($state);
|
||||||
|
} catch (\ValueError $e) {
|
||||||
|
$service = GoogleService::Email;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$connection = $this->googleOAuthService->handleCallback($code, $service);
|
||||||
|
|
||||||
|
$message = match ($service) {
|
||||||
|
GoogleService::Email => 'Account email Gmail connesso con successo!',
|
||||||
|
GoogleService::Drive => 'Account Google Drive connesso con successo!',
|
||||||
|
GoogleService::Calendar => 'Account Google Calendar connesso con successo!',
|
||||||
|
};
|
||||||
|
|
||||||
|
return redirect()->route('impostazioni.index', ['tab' => 'google'])
|
||||||
|
->with('success', $message);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Google OAuth callback error', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'service' => $service->value,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('impostazioni.index', ['tab' => 'google'])
|
||||||
|
->withErrors(['error' => 'Errore durante la connessione: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revoke(GoogleOAuthConnection $connection): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($connection->user_id !== auth()->id()) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$service = GoogleService::from($connection->service);
|
||||||
|
|
||||||
|
$message = match ($service) {
|
||||||
|
GoogleService::Email => 'Account email Gmail disconnesso.',
|
||||||
|
GoogleService::Drive => 'Account Google Drive disconnesso.',
|
||||||
|
GoogleService::Calendar => 'Account Google Calendar disconnesso.',
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->googleOAuthService->revoke($connection);
|
||||||
|
|
||||||
|
return redirect()->route('impostazioni.index', ['tab' => 'google'])
|
||||||
|
->with('success', $message);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Google OAuth revoke error', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'connection_id' => $connection->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('impostazioni.index', ['tab' => 'google'])
|
||||||
|
->withErrors(['error' => 'Errore durante la disconnessione: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status(): \Illuminate\Http\JsonResponse
|
||||||
|
{
|
||||||
|
$status = $this->googleOAuthService->getConnectionStatus();
|
||||||
|
|
||||||
|
return response()->json($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,11 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
use App\Models\Diocesi;
|
use App\Models\Diocesi;
|
||||||
|
use App\Models\Tag;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Pagination\Paginator;
|
use Illuminate\Pagination\Paginator;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class GruppoController extends Controller
|
class GruppoController extends Controller
|
||||||
@@ -27,8 +29,29 @@ class GruppoController extends Controller
|
|||||||
->where('is_default', true)
|
->where('is_default', true)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
$allColumns = ['nome', 'descrizione', 'diocesi', 'livello', 'padre', 'membri', 'responsabili', 'indirizzo', 'citta', 'telefono', 'email'];
|
$userVistas = \App\Models\VistaReport::where('user_id', $user->id)
|
||||||
$defaultVisible = ['nome', 'livello', 'padre', 'membri', 'responsabili'];
|
->where('tipo', 'gruppi')
|
||||||
|
->orderBy('is_default', 'desc')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$allColumnDefs = [
|
||||||
|
['key' => 'nome', 'label' => 'Nome'],
|
||||||
|
['key' => 'anno_fondazione', 'label' => 'Anno Fondazione'],
|
||||||
|
['key' => 'descrizione', 'label' => 'Descrizione'],
|
||||||
|
['key' => 'diocesi', 'label' => 'Diocesi'],
|
||||||
|
['key' => 'livello', 'label' => 'Livello'],
|
||||||
|
['key' => 'padre', 'label' => 'Gruppo Padre'],
|
||||||
|
['key' => 'membri', 'label' => 'Membri'],
|
||||||
|
['key' => 'responsabili', 'label' => 'Responsabili'],
|
||||||
|
['key' => 'indirizzo', 'label' => 'Indirizzo'],
|
||||||
|
['key' => 'citta', 'label' => 'Città'],
|
||||||
|
['key' => 'telefono', 'label' => 'Telefono'],
|
||||||
|
['key' => 'email', 'label' => 'Email'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
];
|
||||||
|
$allColumns = array_column($allColumnDefs, 'key');
|
||||||
|
$defaultVisible = ['nome', 'livello', 'padre', 'membri', 'responsabili', 'tag'];
|
||||||
|
|
||||||
if ($vista && !empty($vista->colonne_visibili)) {
|
if ($vista && !empty($vista->colonne_visibili)) {
|
||||||
$visibleColumns = $vista->colonne_visibili;
|
$visibleColumns = $vista->colonne_visibili;
|
||||||
@@ -36,9 +59,22 @@ class GruppoController extends Controller
|
|||||||
$visibleColumns = $defaultVisible;
|
$visibleColumns = $defaultVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
$allGruppi = Gruppo::with(['diocesi', 'parent', 'children', 'individui', 'avatar'])
|
$tableColumns = array_map(fn($col) => [
|
||||||
->orderBy('nome')
|
'key' => $col['key'],
|
||||||
->get()
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumnDefs);
|
||||||
|
$entityType = 'gruppi';
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
|
$gruppiQuery = Gruppo::with(['diocesi', 'gruppoContatti', 'parent', 'children', 'individui', 'avatar', 'tags']);
|
||||||
|
|
||||||
|
if (request()->filled('tag')) {
|
||||||
|
$tagSlugs = (array) request()->input('tag');
|
||||||
|
$gruppiQuery->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allGruppi = $gruppiQuery->orderBy('nome')->get()
|
||||||
->map(function ($gruppo) {
|
->map(function ($gruppo) {
|
||||||
$depth = 0;
|
$depth = 0;
|
||||||
$parent = $gruppo->parent;
|
$parent = $gruppo->parent;
|
||||||
@@ -65,12 +101,19 @@ class GruppoController extends Controller
|
|||||||
$gruppi = $allGruppi;
|
$gruppi = $allGruppi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('gruppi.index', compact(
|
return view('gruppi.index', compact(
|
||||||
'gruppi',
|
'gruppi',
|
||||||
'allColumns',
|
'allColumns',
|
||||||
'visibleColumns',
|
'visibleColumns',
|
||||||
'viewMode',
|
'viewMode',
|
||||||
'vista'
|
'vista',
|
||||||
|
'allTags',
|
||||||
|
'tableColumns',
|
||||||
|
'entityType',
|
||||||
|
'columnWidths',
|
||||||
|
'userVistas'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +143,12 @@ class GruppoController extends Controller
|
|||||||
public function create(Request $request)
|
public function create(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('gruppi');
|
$this->authorizeWrite('gruppi');
|
||||||
$diocesi = Diocesi::orderBy('nome')->get();
|
$diocesi = Diocesi::orderBy('id')->get();
|
||||||
$allGruppi = Gruppo::orderBy('nome')->get();
|
$allGruppi = Gruppo::orderBy('nome')->get();
|
||||||
$selectedParent = $request->query('parent_id') ? Gruppo::find($request->query('parent_id')) : null;
|
$selectedParent = $request->query('parent_id') ? Gruppo::find($request->query('parent_id')) : null;
|
||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
return view('gruppi.create', compact('diocesi', 'allGruppi', 'selectedParent', 'individui'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
return view('gruppi.create', compact('diocesi', 'allGruppi', 'selectedParent', 'individui', 'tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -112,9 +156,11 @@ class GruppoController extends Controller
|
|||||||
$this->authorizeWrite('gruppi');
|
$this->authorizeWrite('gruppi');
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'nome' => 'required|string|max:255',
|
'nome' => 'required|string|max:255',
|
||||||
|
'anno_fondazione' => 'nullable|integer|min:1800|max:' . (date('Y') + 1),
|
||||||
'descrizione' => 'nullable|string',
|
'descrizione' => 'nullable|string',
|
||||||
'parent_id' => 'nullable|exists:gruppi,id',
|
'parent_id' => 'nullable|exists:gruppi,id',
|
||||||
'diocesi_id' => 'nullable|exists:diocesi,id',
|
'diocesi_ids' => 'nullable|array',
|
||||||
|
'diocesi_ids.*' => 'exists:diocesi,id',
|
||||||
'responsabile_ids' => 'nullable|array',
|
'responsabile_ids' => 'nullable|array',
|
||||||
'responsabile_ids.*' => 'exists:individui,id',
|
'responsabile_ids.*' => 'exists:individui,id',
|
||||||
'indirizzo_incontro' => 'nullable|string|max:500',
|
'indirizzo_incontro' => 'nullable|string|max:500',
|
||||||
@@ -122,6 +168,13 @@ class GruppoController extends Controller
|
|||||||
'città_incontro' => 'nullable|string|max:255',
|
'città_incontro' => 'nullable|string|max:255',
|
||||||
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
||||||
'mappa_posizione' => 'nullable|string',
|
'mappa_posizione' => 'nullable|string',
|
||||||
|
'contatti' => 'nullable|array',
|
||||||
|
'contatti.*.tipo' => 'required|string|in:email,telefono,cellulare',
|
||||||
|
'contatti.*.valore' => 'required|string|max:255',
|
||||||
|
'contatti.*.etichetta' => 'nullable|string|max:255',
|
||||||
|
'contatti.*.is_primary' => 'nullable|boolean',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!empty($data['responsabile_ids'])) {
|
if (!empty($data['responsabile_ids'])) {
|
||||||
@@ -130,6 +183,25 @@ class GruppoController extends Controller
|
|||||||
|
|
||||||
$gruppo = Gruppo::create($data);
|
$gruppo = Gruppo::create($data);
|
||||||
|
|
||||||
|
if (!empty($data['diocesi_ids'])) {
|
||||||
|
$gruppo->diocesi()->sync($data['diocesi_ids']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['contatti'])) {
|
||||||
|
foreach ($data['contatti'] as $contattoData) {
|
||||||
|
$gruppo->gruppoContatti()->create([
|
||||||
|
'tipo' => $contattoData['tipo'],
|
||||||
|
'valore' => $contattoData['valore'],
|
||||||
|
'etichetta' => $contattoData['etichetta'] ?? null,
|
||||||
|
'is_primary' => $contattoData['is_primary'] ?? false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$gruppo->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('individui')) {
|
if ($request->has('individui')) {
|
||||||
foreach ($request->individui as $individuoData) {
|
foreach ($request->individui as $individuoData) {
|
||||||
if (!empty($individuoData['individuo_id'])) {
|
if (!empty($individuoData['individuo_id'])) {
|
||||||
@@ -147,9 +219,9 @@ class GruppoController extends Controller
|
|||||||
public function show($id)
|
public function show($id)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('gruppi');
|
$this->authorizeRead('gruppi');
|
||||||
$gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) {
|
$gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'gruppoContatti', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) {
|
||||||
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
||||||
}, 'avatar', 'eventi' => function ($q) {
|
}, 'avatar', 'tags', 'eventi' => function ($q) {
|
||||||
$q->where('is_incontro_gruppo', true);
|
$q->where('is_incontro_gruppo', true);
|
||||||
}, 'eventi.responsabili.contatti'])->findOrFail($id);
|
}, 'eventi.responsabili.contatti'])->findOrFail($id);
|
||||||
return view('gruppi.show', compact('gruppo'));
|
return view('gruppi.show', compact('gruppo'));
|
||||||
@@ -158,11 +230,14 @@ class GruppoController extends Controller
|
|||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('gruppi');
|
$this->authorizeWrite('gruppi');
|
||||||
$gruppo = Gruppo::with(['individui.contatti', 'individui.documenti', 'avatar'])->findOrFail($id);
|
$gruppo = Gruppo::with(['individui.contatti', 'individui.documenti', 'gruppoContatti', 'avatar', 'tags'])->findOrFail($id);
|
||||||
$diocesi = Diocesi::orderBy('nome')->get();
|
$diocesi = Diocesi::orderBy('id')->get();
|
||||||
$gruppi = Gruppo::where('id', '!=', $gruppo->id)->orderBy('nome')->get();
|
$gruppi = Gruppo::where('id', '!=', $gruppo->id)->orderBy('nome')->get();
|
||||||
$membri = $gruppo->individui()->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')->orderBy('cognome')->orderBy('nome')->get();
|
$membri = $gruppo->individui()->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')->orderBy('cognome')->orderBy('nome')->get();
|
||||||
return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $gruppo->tags->pluck('id')->toArray();
|
||||||
|
$selectedDiocesiIds = $gruppo->diocesi()->pluck('diocesi.id')->toArray();
|
||||||
|
return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri', 'tags', 'selectedTags', 'selectedDiocesiIds'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
@@ -171,9 +246,11 @@ class GruppoController extends Controller
|
|||||||
$gruppo = Gruppo::findOrFail($id);
|
$gruppo = Gruppo::findOrFail($id);
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'nome' => 'required|string|max:255',
|
'nome' => 'required|string|max:255',
|
||||||
|
'anno_fondazione' => 'nullable|integer|min:1800|max:' . (date('Y') + 1),
|
||||||
'descrizione' => 'nullable|string',
|
'descrizione' => 'nullable|string',
|
||||||
'parent_id' => 'nullable|exists:gruppi,id',
|
'parent_id' => 'nullable|exists:gruppi,id',
|
||||||
'diocesi_id' => 'nullable|exists:diocesi,id',
|
'diocesi_ids' => 'nullable|array',
|
||||||
|
'diocesi_ids.*' => 'exists:diocesi,id',
|
||||||
'responsabile_ids' => 'nullable|array',
|
'responsabile_ids' => 'nullable|array',
|
||||||
'responsabile_ids.*' => 'exists:individui,id',
|
'responsabile_ids.*' => 'exists:individui,id',
|
||||||
'indirizzo_incontro' => 'nullable|string|max:500',
|
'indirizzo_incontro' => 'nullable|string|max:500',
|
||||||
@@ -181,6 +258,13 @@ class GruppoController extends Controller
|
|||||||
'città_incontro' => 'nullable|string|max:255',
|
'città_incontro' => 'nullable|string|max:255',
|
||||||
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
||||||
'mappa_posizione' => 'nullable|string',
|
'mappa_posizione' => 'nullable|string',
|
||||||
|
'contatti' => 'nullable|array',
|
||||||
|
'contatti.*.tipo' => 'required|string|in:email,telefono,cellulare',
|
||||||
|
'contatti.*.valore' => 'required|string|max:255',
|
||||||
|
'contatti.*.etichetta' => 'nullable|string|max:255',
|
||||||
|
'contatti.*.is_primary' => 'nullable|boolean',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!empty($data['responsabile_ids'])) {
|
if (!empty($data['responsabile_ids'])) {
|
||||||
@@ -191,6 +275,30 @@ class GruppoController extends Controller
|
|||||||
|
|
||||||
$gruppo->update($data);
|
$gruppo->update($data);
|
||||||
|
|
||||||
|
if ($request->has('diocesi_ids')) {
|
||||||
|
$gruppo->diocesi()->sync($data['diocesi_ids'] ?? []);
|
||||||
|
} else {
|
||||||
|
$gruppo->diocesi()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('contatti')) {
|
||||||
|
$gruppo->gruppoContatti()->delete();
|
||||||
|
foreach ($data['contatti'] as $contattoData) {
|
||||||
|
$gruppo->gruppoContatti()->create([
|
||||||
|
'tipo' => $contattoData['tipo'],
|
||||||
|
'valore' => $contattoData['valore'],
|
||||||
|
'etichetta' => $contattoData['etichetta'] ?? null,
|
||||||
|
'is_primary' => $contattoData['is_primary'] ?? false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$gruppo->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$gruppo->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('individui')) {
|
if ($request->has('individui')) {
|
||||||
$gruppo->individui()->detach();
|
$gruppo->individui()->detach();
|
||||||
foreach ($request->individui as $individuoData) {
|
foreach ($request->individui as $individuoData) {
|
||||||
@@ -327,9 +435,12 @@ class GruppoController extends Controller
|
|||||||
'file' => 'required|file|mimes:csv,txt',
|
'file' => 'required|file|mimes:csv,txt',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
session_write_close();
|
||||||
|
|
||||||
$file = $request->file('file');
|
$file = $request->file('file');
|
||||||
$handle = fopen($file->path(), 'r');
|
$handle = fopen($file->path(), 'r');
|
||||||
$header = fgetcsv($handle, 1000, ',');
|
$header = fgetcsv($handle, 0, ',');
|
||||||
|
|
||||||
if ($header === false) {
|
if ($header === false) {
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
@@ -342,7 +453,9 @@ class GruppoController extends Controller
|
|||||||
$errors = [];
|
$errors = [];
|
||||||
$rowNumber = 1;
|
$rowNumber = 1;
|
||||||
|
|
||||||
while (($row = fgetcsv($handle, 1000, ',')) !== false) {
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
while (($row = fgetcsv($handle, 0, ',')) !== false) {
|
||||||
$rowNumber++;
|
$rowNumber++;
|
||||||
|
|
||||||
if (count($row) < count($header)) {
|
if (count($row) < count($header)) {
|
||||||
@@ -368,6 +481,7 @@ class GruppoController extends Controller
|
|||||||
try {
|
try {
|
||||||
Gruppo::create([
|
Gruppo::create([
|
||||||
'nome' => $nome,
|
'nome' => $nome,
|
||||||
|
'anno_fondazione' => !empty($data['anno_fondazione']) ? (int) $data['anno_fondazione'] : null,
|
||||||
'descrizione' => $data['descrizione'] ?? null,
|
'descrizione' => $data['descrizione'] ?? null,
|
||||||
'parent_id' => !empty($data['parent_id']) ? (int) $data['parent_id'] : null,
|
'parent_id' => !empty($data['parent_id']) ? (int) $data['parent_id'] : null,
|
||||||
'diocesi_id' => !empty($data['diocesi_id']) ? (int) $data['diocesi_id'] : null,
|
'diocesi_id' => !empty($data['diocesi_id']) ? (int) $data['diocesi_id'] : null,
|
||||||
@@ -386,6 +500,8 @@ class GruppoController extends Controller
|
|||||||
|
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
$message = 'Importati ' . $imported . ' gruppi.';
|
$message = 'Importati ' . $imported . ' gruppi.';
|
||||||
if ($skipped > 0) {
|
if ($skipped > 0) {
|
||||||
$message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).';
|
$message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).';
|
||||||
@@ -400,8 +516,8 @@ class GruppoController extends Controller
|
|||||||
public function downloadTemplate()
|
public function downloadTemplate()
|
||||||
{
|
{
|
||||||
$this->authorizeRead('gruppi');
|
$this->authorizeRead('gruppi');
|
||||||
$headers = ['nome', 'descrizione', 'parent_id', 'diocesi_id', 'indirizzo_incontro', 'cap_incontro', 'città_incontro', 'sigla_provincia_incontro'];
|
$headers = ['nome', 'anno_fondazione', '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'];
|
$example = ['Gruppo Giovani', '2020', 'Giovani dai 18 ai 30 anni', '', '', 'Via Roma 10', '00100', 'Roma', 'RM'];
|
||||||
|
|
||||||
$csv = implode(',', $headers) . "\n" . implode(',', $example);
|
$csv = implode(',', $headers) . "\n" . implode(',', $example);
|
||||||
|
|
||||||
@@ -410,4 +526,59 @@ class GruppoController extends Controller
|
|||||||
'Content-Disposition' => 'attachment; filename="template_gruppi.csv"',
|
'Content-Disposition' => 'attachment; filename="template_gruppi.csv"',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('gruppi');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
|
||||||
|
if (empty($ids)) {
|
||||||
|
return redirect()->route('gruppi.index')->with('error', 'Nessun gruppo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
Gruppo::whereIn('id', $ids)->chunk(100, function ($gruppi) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($gruppi as $gruppo) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$gruppo->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$gruppo->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return redirect()->route('gruppi.index')->with('success', "Tag {$actionLabel} per {$count} gruppi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function massElimina(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeDelete('gruppi');
|
||||||
|
$ids = $request->input('ids', []);
|
||||||
|
if (!is_array($ids) || empty($ids)) {
|
||||||
|
return redirect()->route('gruppi.index')->with('error', 'Nessun gruppo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$gruppi = Gruppo::whereIn('id', $ids)->get();
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($gruppi as $gruppo) {
|
||||||
|
$gruppo->individui()->detach();
|
||||||
|
$gruppo->delete();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('gruppi.index')->with('success', "{$count} gruppi eliminati con successo.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ use App\Models\EmailSetting;
|
|||||||
use App\Models\Ruolo;
|
use App\Models\Ruolo;
|
||||||
use App\Models\SenderAccount;
|
use App\Models\SenderAccount;
|
||||||
use App\Models\StorageRepository;
|
use App\Models\StorageRepository;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use App\Services\GoogleOAuthService;
|
||||||
use App\Models\TipologiaDocumento;
|
use App\Models\TipologiaDocumento;
|
||||||
use App\Models\TipologiaEvento;
|
use App\Models\TipologiaEvento;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@@ -26,14 +28,16 @@ class ImpostazioniController extends Controller
|
|||||||
$tipologie = TipologiaDocumento::orderBy('ordine')->get();
|
$tipologie = TipologiaDocumento::orderBy('ordine')->get();
|
||||||
$tipologieEventi = TipologiaEvento::orderBy('ordine')->get();
|
$tipologieEventi = TipologiaEvento::orderBy('ordine')->get();
|
||||||
$ruoli = Ruolo::orderBy('ordine')->get();
|
$ruoli = Ruolo::orderBy('ordine')->get();
|
||||||
|
$tags = Tag::orderBy('order_column')->get();
|
||||||
$appSettings = AppSetting::first();
|
$appSettings = AppSetting::first();
|
||||||
$emailSettings = EmailSetting::first() ?? new EmailSetting();
|
$emailSettings = EmailSetting::first() ?? new EmailSetting();
|
||||||
$senderAccounts = SenderAccount::orderBy('email_address')->get();
|
$senderAccounts = SenderAccount::orderBy('email_address')->get();
|
||||||
$repositories = StorageRepository::orderBy('ordine')->get();
|
$repositories = StorageRepository::orderBy('ordine')->get();
|
||||||
$googleDriveNewToken = session('google_drive_new_token');
|
$googleDriveNewToken = session('google_drive_new_token');
|
||||||
$calendarioConnessioni = CalendarioConnessione::orderBy('ordine')->get();
|
$calendarioConnessioni = CalendarioConnessione::orderBy('ordine')->get();
|
||||||
|
$googleStatus = app(GoogleOAuthService::class)->getConnectionStatus();
|
||||||
|
|
||||||
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings', 'emailSettings', 'senderAccounts', 'repositories', 'googleDriveNewToken', 'calendarioConnessioni'));
|
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'tags', 'appSettings', 'emailSettings', 'senderAccounts', 'repositories', 'googleDriveNewToken', 'calendarioConnessioni', 'googleStatus'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveAppSettings(Request $request)
|
public function saveAppSettings(Request $request)
|
||||||
@@ -371,6 +375,70 @@ class ImpostazioniController extends Controller
|
|||||||
return response()->json(['success' => true]);
|
return response()->json(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tagStore(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100|unique:tags,name',
|
||||||
|
'color' => 'nullable|string|max:7',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$maxOrdine = Tag::max('order_column') ?? 0;
|
||||||
|
|
||||||
|
Tag::create([
|
||||||
|
'name' => $validated['name'],
|
||||||
|
'slug' => str()->slug($validated['name']),
|
||||||
|
'color' => $validated['color'] ?? null,
|
||||||
|
'order_column' => $maxOrdine + 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect('/impostazioni#tag')->with('success', 'Tag aggiunto.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tagUpdate(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
|
||||||
|
$tag = Tag::findOrFail($id);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100|unique:tags,name,' . $id,
|
||||||
|
'color' => 'nullable|string|max:7',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tag->update([
|
||||||
|
'name' => $validated['name'],
|
||||||
|
'slug' => str()->slug($validated['name']),
|
||||||
|
'color' => $validated['color'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect('/impostazioni#tag')->with('success', 'Tag aggiornato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tagDestroy($id)
|
||||||
|
{
|
||||||
|
$this->authorizeDelete('settings');
|
||||||
|
|
||||||
|
$tag = Tag::findOrFail($id);
|
||||||
|
$tag->delete();
|
||||||
|
|
||||||
|
return redirect('/impostazioni#tag')->with('success', 'Tag eliminato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tagReorder(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
|
||||||
|
$order = $request->input('order', []);
|
||||||
|
|
||||||
|
foreach ($order as $index => $id) {
|
||||||
|
Tag::where('id', $id)->update(['order_column' => $index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
public function uploadLogo(Request $request)
|
public function uploadLogo(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('settings');
|
$this->authorizeWrite('settings');
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
use App\Models\Contatto;
|
use App\Models\Contatto;
|
||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
|
use App\Models\Gruppo;
|
||||||
use App\Models\Comune;
|
use App\Models\Comune;
|
||||||
use App\Models\Diocesi;
|
use App\Models\Diocesi;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use App\Models\VistaReport;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
@@ -18,7 +25,16 @@ class IndividuoController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorizeRead('individui');
|
$this->authorizeRead('individui');
|
||||||
$perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 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);
|
|
||||||
|
$query = Individuo::with('contatti', 'tags');
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$individui = $query->orderBy('cognome')->orderBy('nome')->paginate($perPage);
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
$vista = null;
|
$vista = null;
|
||||||
|
|
||||||
@@ -39,16 +55,43 @@ class IndividuoController extends Controller
|
|||||||
['key' => 'codice', 'label' => 'Codice'],
|
['key' => 'codice', 'label' => 'Codice'],
|
||||||
['key' => 'cognome', 'label' => 'Cognome'],
|
['key' => 'cognome', 'label' => 'Cognome'],
|
||||||
['key' => 'nome', 'label' => 'Nome'],
|
['key' => 'nome', 'label' => 'Nome'],
|
||||||
|
['key' => 'data_nascita', 'label' => 'Data Nascita'],
|
||||||
|
['key' => 'genere', 'label' => 'Genere'],
|
||||||
|
['key' => 'indirizzo', 'label' => 'Indirizzo'],
|
||||||
|
['key' => 'cap', 'label' => 'CAP'],
|
||||||
|
['key' => 'citta', 'label' => 'Città'],
|
||||||
|
['key' => 'provincia', 'label' => 'Provincia'],
|
||||||
|
['key' => 'tipo_documento', 'label' => 'Tipo Documento'],
|
||||||
|
['key' => 'numero_documento', 'label' => 'Numero Documento'],
|
||||||
|
['key' => 'scadenza_documento', 'label' => 'Scadenza Documento'],
|
||||||
['key' => 'email', 'label' => 'Email'],
|
['key' => 'email', 'label' => 'Email'],
|
||||||
['key' => 'telefono', 'label' => 'Telefono'],
|
['key' => 'telefono', 'label' => 'Telefono'],
|
||||||
|
['key' => 'note', 'label' => 'Note'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'created_at', 'label' => 'Creato il'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono'];
|
$defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono', 'tag'];
|
||||||
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
? $vista->colonne_visibili
|
? $vista->colonne_visibili
|
||||||
: $defaultVisible;
|
: $defaultVisible;
|
||||||
|
|
||||||
return view('individui.index', compact('individui', 'vista', 'allColumns', 'visibleColumns'));
|
$entityType = 'individui';
|
||||||
|
$tableColumns = array_map(fn($col) => [
|
||||||
|
'key' => $col['key'],
|
||||||
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumns);
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
|
$userVistas = VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'individui')
|
||||||
|
->orderBy('is_default', 'desc')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$gruppi = Gruppo::orderBy('nome')->get(['id', 'nome']);
|
||||||
|
return view('individui.index', compact('individui', 'vista', 'allColumns', 'visibleColumns', 'allTags', 'gruppi', 'entityType', 'tableColumns', 'columnWidths', 'userVistas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportCSV(Request $request)
|
public function exportCSV(Request $request)
|
||||||
@@ -109,8 +152,9 @@ public function create()
|
|||||||
{
|
{
|
||||||
$this->authorizeWrite('individui');
|
$this->authorizeWrite('individui');
|
||||||
$comuni = Comune::orderBy('nome')->get();
|
$comuni = Comune::orderBy('nome')->get();
|
||||||
$diocesi = Diocesi::orderBy('nome')->get();
|
$diocesi = Diocesi::orderBy('id')->get();
|
||||||
return view('individui.create', compact('comuni', 'diocesi'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
return view('individui.create', compact('comuni', 'diocesi', 'tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -129,10 +173,16 @@ public function create()
|
|||||||
'numero_documento' => 'nullable|string|max:50',
|
'numero_documento' => 'nullable|string|max:50',
|
||||||
'scadenza_documento' => 'nullable|date',
|
'scadenza_documento' => 'nullable|date',
|
||||||
'note' => 'nullable|string',
|
'note' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$individuo = Individuo::create($data);
|
$individuo = Individuo::create($data);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$individuo->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('contatti')) {
|
if ($request->has('contatti')) {
|
||||||
foreach ($request->contatti as $contatto) {
|
foreach ($request->contatti as $contatto) {
|
||||||
if (!empty($contatto['tipo']) && !empty($contatto['valore'])) {
|
if (!empty($contatto['tipo']) && !empty($contatto['valore'])) {
|
||||||
@@ -154,7 +204,7 @@ public function create()
|
|||||||
$this->authorizeRead('individui');
|
$this->authorizeRead('individui');
|
||||||
$individuo = Individuo::with(['contatti', 'gruppi' => function ($q) {
|
$individuo = Individuo::with(['contatti', 'gruppi' => function ($q) {
|
||||||
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
||||||
}, 'avatar'])->findOrFail($individuo);
|
}, 'avatar', 'tags'])->findOrFail($individuo);
|
||||||
return view('individui.show', compact('individuo'));
|
return view('individui.show', compact('individuo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,8 +230,10 @@ public function create()
|
|||||||
public function edit($individuo)
|
public function edit($individuo)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('individui');
|
$this->authorizeWrite('individui');
|
||||||
$individuo = Individuo::with(['contatti', 'gruppi', 'avatar'])->findOrFail($individuo);
|
$individuo = Individuo::with(['contatti', 'gruppi', 'avatar', 'tags'])->findOrFail($individuo);
|
||||||
return view('individui.edit', compact('individuo'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $individuo->tags->pluck('id')->toArray();
|
||||||
|
return view('individui.edit', compact('individuo', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $individuo)
|
public function update(Request $request, $individuo)
|
||||||
@@ -205,10 +257,18 @@ public function create()
|
|||||||
'numero_documento' => 'nullable|string|max:50',
|
'numero_documento' => 'nullable|string|max:50',
|
||||||
'scadenza_documento' => 'nullable|date',
|
'scadenza_documento' => 'nullable|date',
|
||||||
'note' => 'nullable|string',
|
'note' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$individuo->update($data);
|
$individuo->update($data);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$individuo->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$individuo->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
\Log::info('Individuo dati salvati', ['id' => $individuo->id]);
|
\Log::info('Individuo dati salvati', ['id' => $individuo->id]);
|
||||||
|
|
||||||
if ($request->has('contatti')) {
|
if ($request->has('contatti')) {
|
||||||
@@ -308,6 +368,75 @@ public function create()
|
|||||||
return redirect()->route('individui.index')->with('success', count($individui) . ' individui eliminati con successo.');
|
return redirect()->route('individui.index')->with('success', count($individui) . ' individui eliminati con successo.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massGruppo(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('individui');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
if (empty($ids)) {
|
||||||
|
return redirect()->route('individui.index')->with('error', 'Nessun individuo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'gruppi' => 'required|array',
|
||||||
|
'gruppi.*' => 'exists:gruppi,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$gruppiIds = $data['gruppi'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$individuo = Individuo::find($id);
|
||||||
|
if (!$individuo) continue;
|
||||||
|
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$individuo->gruppi()->syncWithoutDetaching($gruppiIds);
|
||||||
|
} else {
|
||||||
|
$individuo->gruppi()->detach($gruppiIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return redirect()->route('individui.index')->with('success', "Gruppi {$actionLabel} per {$count} individui.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('individui');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
if (empty($ids)) {
|
||||||
|
return redirect()->route('individui.index')->with('error', 'Nessun individuo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
Individuo::whereIn('id', $ids)->chunk(100, function ($individui) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($individui as $individuo) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$individuo->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$individuo->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return redirect()->route('individui.index')->with('success', "Tag {$actionLabel} per {$count} individui.");
|
||||||
|
}
|
||||||
|
|
||||||
public function elementiCollegati($individuo)
|
public function elementiCollegati($individuo)
|
||||||
{
|
{
|
||||||
$individuo = Individuo::findOrFail($individuo);
|
$individuo = Individuo::findOrFail($individuo);
|
||||||
@@ -372,16 +501,27 @@ public function emailList(Request $request)
|
|||||||
'file' => 'required|file|mimes:csv,txt',
|
'file' => 'required|file|mimes:csv,txt',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
session_write_close();
|
||||||
|
|
||||||
$file = $request->file('file');
|
$file = $request->file('file');
|
||||||
$handle = fopen($file->path(), 'r');
|
$handle = fopen($file->path(), 'r');
|
||||||
$header = fgetcsv($handle, 1000, ',');
|
$header = fgetcsv($handle, 0, ',');
|
||||||
|
|
||||||
|
if ($header === false) {
|
||||||
|
fclose($handle);
|
||||||
|
return back()->with('error', 'Il file CSV non è valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = array_map('trim', $header);
|
||||||
$imported = 0;
|
$imported = 0;
|
||||||
$skipped = 0;
|
$skipped = 0;
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$rowNumber = 1;
|
$rowNumber = 1;
|
||||||
|
|
||||||
while (($row = fgetcsv($handle, 1000, ',')) !== false) {
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
while (($row = fgetcsv($handle, 0, ',')) !== false) {
|
||||||
$rowNumber++;
|
$rowNumber++;
|
||||||
|
|
||||||
if (count($row) < count($header)) {
|
if (count($row) < count($header)) {
|
||||||
@@ -440,12 +580,15 @@ public function emailList(Request $request)
|
|||||||
|
|
||||||
$imported++;
|
$imported++;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Errore import individuo riga ' . $rowNumber . ': ' . $e->getMessage());
|
||||||
$errors[] = 'Errore riga ' . $rowNumber . ': ' . $e->getMessage();
|
$errors[] = 'Errore riga ' . $rowNumber . ': ' . $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
$message = 'Importati ' . $imported . ' individui.';
|
$message = 'Importati ' . $imported . ' individui.';
|
||||||
if ($skipped > 0) {
|
if ($skipped > 0) {
|
||||||
$message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).';
|
$message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).';
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use App\Models\MailingMessaggio;
|
|||||||
use App\Models\SenderAccount;
|
use App\Models\SenderAccount;
|
||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
|
use App\Models\EmailSetting;
|
||||||
|
use App\Models\Firma;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@@ -30,6 +32,8 @@ class MailingController extends Controller
|
|||||||
$liste = MailingList::all();
|
$liste = MailingList::all();
|
||||||
$documenti = Documento::orderBy('nome_file')->get();
|
$documenti = Documento::orderBy('nome_file')->get();
|
||||||
$senderAccounts = SenderAccount::active()->get();
|
$senderAccounts = SenderAccount::active()->get();
|
||||||
|
$settings = EmailSetting::getActive();
|
||||||
|
$firme = $settings?->firme ?? collect();
|
||||||
|
|
||||||
$documentiSelezionati = collect();
|
$documentiSelezionati = collect();
|
||||||
if ($request->has('documenti_selezionati')) {
|
if ($request->has('documenti_selezionati')) {
|
||||||
@@ -37,7 +41,7 @@ class MailingController extends Controller
|
|||||||
$documentiSelezionati = Documento::whereIn('id', $docIds)->get();
|
$documentiSelezionati = Documento::whereIn('id', $docIds)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati', 'senderAccounts'));
|
return view('mailing.nuovo', compact('individui', 'liste', 'documenti', 'documentiSelezionati', 'senderAccounts', 'firme'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invia(Request $request)
|
public function invia(Request $request)
|
||||||
@@ -46,16 +50,22 @@ class MailingController extends Controller
|
|||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'oggetto' => 'required|string|max:255',
|
'oggetto' => 'required|string|max:255',
|
||||||
'corpo' => 'required',
|
'corpo' => 'required',
|
||||||
'lista_id' => 'nullable|exists:mailing_liste,id',
|
'lista_id' => 'nullable|exists:mailing_lists,id',
|
||||||
'destinatari_ids' => 'nullable|string',
|
'destinatari_ids' => 'nullable|string',
|
||||||
'documenti_selezionati' => 'nullable|string',
|
'documenti_selezionati' => 'nullable|string',
|
||||||
'allegato' => 'nullable|file|max:10240',
|
'allegato' => 'nullable|file|max:10240',
|
||||||
'mittente_id' => 'nullable|integer|exists:sender_accounts,id',
|
'mittente_id' => 'nullable|integer|exists:sender_accounts,id',
|
||||||
|
'firma_id' => 'nullable|integer|exists:firme,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$sender = null;
|
$sender = null;
|
||||||
if (!empty($data['mittente_id'])) {
|
if (!empty($data['mittente_id'])) {
|
||||||
$sender = SenderAccount::findOrFail($data['mittente_id']);
|
$sender = SenderAccount::findOrFail($data['mittente_id']);
|
||||||
|
} elseif (!empty($data['lista_id'])) {
|
||||||
|
$lista = MailingList::with('senderAccount')->find($data['lista_id']);
|
||||||
|
if ($lista && $lista->senderAccount) {
|
||||||
|
$sender = $lista->senderAccount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$documentiAllegati = [];
|
$documentiAllegati = [];
|
||||||
@@ -95,14 +105,38 @@ class MailingController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$emails = $destinatari->flatMap(fn($ind) => $ind->contatti->where('tipo', 'email')->pluck('valore'))
|
$totaleDestinatari = $destinatari->count();
|
||||||
|
$emails = $destinatari->map(fn($ind) => $ind->contatti->where('tipo', 'email')->first()?->valore)
|
||||||
->filter()->unique()->values();
|
->filter()->unique()->values();
|
||||||
|
|
||||||
|
$skipped = $totaleDestinatari - $emails->count();
|
||||||
|
if ($skipped > 0) {
|
||||||
|
Log::info("Mailing invia: {$skipped} contatti saltati perché senza email", [
|
||||||
|
'lista_id' => $data['lista_id'] ?? null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($emails->isEmpty()) {
|
if ($emails->isEmpty()) {
|
||||||
return back()->with('error', 'Nessun destinatario email valido trovato.')->withInput();
|
return back()->with('error', 'Nessun destinatario email valido trovato.')->withInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
$attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati);
|
$attachmentResult = $this->resolveMailingAttachmentPaths($documentiAllegati);
|
||||||
|
$attachmentPaths = $attachmentResult['paths'];
|
||||||
|
$linkDocs = $attachmentResult['links'];
|
||||||
|
|
||||||
|
$firmaId = $data['firma_id'] ?? null;
|
||||||
|
if (!$firmaId && !empty($data['lista_id'])) {
|
||||||
|
$lista = MailingList::with('firma')->find($data['lista_id']);
|
||||||
|
$firmaId = $lista?->firma?->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$corpoConFirma = $this->appendMailingLinksToBody($data['corpo'], $linkDocs);
|
||||||
|
if ($firmaId) {
|
||||||
|
$firma = Firma::find($firmaId);
|
||||||
|
if ($firma && $firma->contenuto_html) {
|
||||||
|
$corpoConFirma .= "\n\n---\n" . $firma->contenuto_html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$messaggio = MailingMessaggio::create([
|
$messaggio = MailingMessaggio::create([
|
||||||
'mailing_list_id' => $data['lista_id'] ?? null,
|
'mailing_list_id' => $data['lista_id'] ?? null,
|
||||||
@@ -113,6 +147,7 @@ class MailingController extends Controller
|
|||||||
'totale_destinatari' => $emails->count(),
|
'totale_destinatari' => $emails->count(),
|
||||||
'mittente_nome' => $sender?->email_name,
|
'mittente_nome' => $sender?->email_name,
|
||||||
'mittente_email' => $sender?->email_address,
|
'mittente_email' => $sender?->email_address,
|
||||||
|
'firma_id' => $firmaId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$ok = 0;
|
$ok = 0;
|
||||||
@@ -122,9 +157,9 @@ class MailingController extends Controller
|
|||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
try {
|
try {
|
||||||
if ($sender) {
|
if ($sender) {
|
||||||
$sender->sendEmail($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
$sender->sendEmail($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
|
||||||
} else {
|
} else {
|
||||||
$this->sendViaSystem($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
$this->sendViaSystem($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
|
||||||
}
|
}
|
||||||
$ok++;
|
$ok++;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -173,11 +208,13 @@ class MailingController extends Controller
|
|||||||
public function invio(Request $request)
|
public function invio(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('mailing');
|
$this->authorizeWrite('mailing');
|
||||||
$liste = MailingList::where('attiva', true)->orderBy('nome')->get();
|
$liste = MailingList::with('firma')->where('attiva', true)->orderBy('nome')->get();
|
||||||
$documenti = Documento::orderBy('nome_file')->get();
|
$documenti = Documento::orderBy('nome_file')->get();
|
||||||
$senderAccounts = SenderAccount::active()->get();
|
$senderAccounts = SenderAccount::active()->get();
|
||||||
|
$settings = EmailSetting::getActive();
|
||||||
|
$firme = $settings?->firme ?? collect();
|
||||||
|
|
||||||
return view('mailing.invio', compact('liste', 'documenti', 'senderAccounts'));
|
return view('mailing.invio', compact('liste', 'documenti', 'senderAccounts', 'firme'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invioElabora(Request $request)
|
public function invioElabora(Request $request)
|
||||||
@@ -185,18 +222,23 @@ class MailingController extends Controller
|
|||||||
$this->authorizeWrite('mailing');
|
$this->authorizeWrite('mailing');
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'liste' => 'required|array',
|
'liste' => 'required|array',
|
||||||
'liste.*' => 'exists:mailing_liste,id',
|
'liste.*' => 'exists:mailing_lists,id',
|
||||||
'oggetto' => 'required|string|max:255',
|
'oggetto' => 'required|string|max:255',
|
||||||
'corpo' => 'required',
|
'corpo' => 'required',
|
||||||
'documenti_selezionati' => 'nullable|array',
|
'documenti_selezionati' => 'nullable|array',
|
||||||
'documenti_selezionati.*' => 'exists:documenti,id',
|
'documenti_selezionati.*' => 'exists:documenti,id',
|
||||||
'allegato' => 'nullable|file|max:10240',
|
'allegato' => 'nullable|file|max:10240',
|
||||||
'mittente_id' => 'nullable|integer|exists:sender_accounts,id',
|
'mittente_id' => 'nullable|integer|exists:sender_accounts,id',
|
||||||
|
'firma_id' => 'nullable|integer|exists:firme,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$listeModels = MailingList::with('firma', 'senderAccount')->whereIn('id', $data['liste'])->get();
|
||||||
|
|
||||||
$sender = null;
|
$sender = null;
|
||||||
if (!empty($data['mittente_id'])) {
|
if (!empty($data['mittente_id'])) {
|
||||||
$sender = SenderAccount::findOrFail($data['mittente_id']);
|
$sender = SenderAccount::findOrFail($data['mittente_id']);
|
||||||
|
} elseif ($listeModels->count() === 1) {
|
||||||
|
$sender = $listeModels->first()->senderAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
$documentiAllegati = [];
|
$documentiAllegati = [];
|
||||||
@@ -229,16 +271,39 @@ class MailingController extends Controller
|
|||||||
->with('individuo.contatti')
|
->with('individuo.contatti')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$totaleContatti = $contatti->count();
|
||||||
$emails = $contatti->map(fn($c) => $c->individuo?->contatti->where('tipo', 'email')->first()?->valore)
|
$emails = $contatti->map(fn($c) => $c->individuo?->contatti->where('tipo', 'email')->first()?->valore)
|
||||||
->filter()->unique()->values();
|
->filter()->unique()->values();
|
||||||
|
|
||||||
|
$skipped = $totaleContatti - $emails->count();
|
||||||
|
if ($skipped > 0) {
|
||||||
|
Log::info("Mailing invio elabora: {$skipped} contatti saltati perché senza email", [
|
||||||
|
'liste_ids' => $data['liste'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($emails->isEmpty()) {
|
if ($emails->isEmpty()) {
|
||||||
return back()->with('error', 'Nessun destinatario email valido trovato nelle liste selezionate.');
|
return back()->with('error', 'Nessun destinatario email valido trovato nelle liste selezionate.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$attachmentPaths = $this->resolveMailingAttachmentPaths($documentiAllegati);
|
$attachmentResult = $this->resolveMailingAttachmentPaths($documentiAllegati);
|
||||||
|
$attachmentPaths = $attachmentResult['paths'];
|
||||||
|
$linkDocs = $attachmentResult['links'];
|
||||||
|
|
||||||
$listeNomi = MailingList::whereIn('id', $data['liste'])->pluck('nome')->implode(', ');
|
$firmaId = $data['firma_id'] ?? null;
|
||||||
|
if (!$firmaId && $listeModels->count() === 1) {
|
||||||
|
$firmaId = $listeModels->first()->firma_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$corpoConFirma = $this->appendMailingLinksToBody($data['corpo'], $linkDocs);
|
||||||
|
if ($firmaId) {
|
||||||
|
$firma = Firma::find($firmaId);
|
||||||
|
if ($firma && $firma->contenuto_html) {
|
||||||
|
$corpoConFirma .= "\n\n---\n" . $firma->contenuto_html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$listeNomi = $listeModels->pluck('nome')->implode(', ');
|
||||||
|
|
||||||
$messaggio = MailingMessaggio::create([
|
$messaggio = MailingMessaggio::create([
|
||||||
'mailing_list_id' => null,
|
'mailing_list_id' => null,
|
||||||
@@ -249,6 +314,7 @@ class MailingController extends Controller
|
|||||||
'totale_destinatari' => $emails->count(),
|
'totale_destinatari' => $emails->count(),
|
||||||
'mittente_nome' => $sender?->email_name,
|
'mittente_nome' => $sender?->email_name,
|
||||||
'mittente_email' => $sender?->email_address,
|
'mittente_email' => $sender?->email_address,
|
||||||
|
'firma_id' => $firmaId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$ok = 0;
|
$ok = 0;
|
||||||
@@ -258,9 +324,9 @@ class MailingController extends Controller
|
|||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
try {
|
try {
|
||||||
if ($sender) {
|
if ($sender) {
|
||||||
$sender->sendEmail($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
$sender->sendEmail($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
|
||||||
} else {
|
} else {
|
||||||
$this->sendViaSystem($email, $data['oggetto'], $data['corpo'], $attachmentPaths);
|
$this->sendViaSystem($email, $data['oggetto'], $corpoConFirma, $attachmentPaths);
|
||||||
}
|
}
|
||||||
$ok++;
|
$ok++;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -331,18 +397,10 @@ class MailingController extends Controller
|
|||||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
||||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||||
|
|
||||||
$fromName = $settings->email_name ?? '';
|
$fromAddress = \Symfony\Component\Mime\Address::create(
|
||||||
if (filter_var($fromName, FILTER_VALIDATE_EMAIL)) {
|
$settings->getEffectiveFromAddress(),
|
||||||
$fromName = 'Glastree';
|
$settings->getEffectiveFromName()
|
||||||
} else {
|
);
|
||||||
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
|
||||||
$fromName = trim($fromName);
|
|
||||||
if (empty($fromName) || strlen($fromName) > 50) {
|
|
||||||
$fromName = 'Glastree';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$fromAddress = \Symfony\Component\Mime\Address::create($settings->email_address, $fromName);
|
|
||||||
|
|
||||||
$email = (new \Symfony\Component\Mime\Email())
|
$email = (new \Symfony\Component\Mime\Email())
|
||||||
->from($fromAddress)
|
->from($fromAddress)
|
||||||
@@ -350,6 +408,11 @@ class MailingController extends Controller
|
|||||||
->subject($subject)
|
->subject($subject)
|
||||||
->text($body);
|
->text($body);
|
||||||
|
|
||||||
|
$replyTo = $settings->getEffectiveReplyTo();
|
||||||
|
if ($replyTo) {
|
||||||
|
$email->replyTo($replyTo);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($attachmentPaths as $path) {
|
foreach ($attachmentPaths as $path) {
|
||||||
$email->attachFromPath($path);
|
$email->attachFromPath($path);
|
||||||
}
|
}
|
||||||
@@ -360,17 +423,34 @@ class MailingController extends Controller
|
|||||||
private function resolveMailingAttachmentPaths(array $documentIds): array
|
private function resolveMailingAttachmentPaths(array $documentIds): array
|
||||||
{
|
{
|
||||||
$paths = [];
|
$paths = [];
|
||||||
|
$links = [];
|
||||||
if (empty($documentIds)) {
|
if (empty($documentIds)) {
|
||||||
return $paths;
|
return ['paths' => $paths, 'links' => $links];
|
||||||
}
|
}
|
||||||
|
|
||||||
$docs = Documento::whereIn('id', $documentIds)->get();
|
$docs = Documento::whereIn('id', $documentIds)->get();
|
||||||
foreach ($docs as $doc) {
|
foreach ($docs as $doc) {
|
||||||
if ($doc->file_path && Storage::exists($doc->file_path)) {
|
if ($doc->isLink()) {
|
||||||
|
$links[] = ['nome' => $doc->nome_file, 'url' => $doc->url];
|
||||||
|
} elseif ($doc->file_path && Storage::exists($doc->file_path)) {
|
||||||
$paths[] = Storage::path($doc->file_path);
|
$paths[] = Storage::path($doc->file_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $paths;
|
return ['paths' => $paths, 'links' => $links];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function appendMailingLinksToBody(string $body, array $linkDocs): string
|
||||||
|
{
|
||||||
|
if (empty($linkDocs)) {
|
||||||
|
return $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
$linksText = "\n\n---\nDocumenti collegati:\n";
|
||||||
|
foreach ($linkDocs as $link) {
|
||||||
|
$linksText .= "- {$link['nome']}: {$link['url']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $body . $linksText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,68 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\MailingList;
|
use App\Models\MailingList;
|
||||||
|
use App\Models\SenderAccount;
|
||||||
|
use App\Models\Tag;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class MailingListController extends Controller
|
class MailingListController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('mailing');
|
$this->authorizeRead('mailing');
|
||||||
$mailingLists = MailingList::with(['user', 'contatti'])
|
$query = MailingList::with(['user', 'contatti', 'tags']);
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailingLists = $query->orderBy('nome')->get();
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
|
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'mailing-liste')
|
||||||
|
->where('is_default', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'mailing-liste')
|
||||||
|
->orderBy('is_default', 'desc')
|
||||||
->orderBy('nome')
|
->orderBy('nome')
|
||||||
->get();
|
->get();
|
||||||
return view('mailing-liste.index', compact('mailingLists'));
|
|
||||||
|
$allColumnDefs = [
|
||||||
|
['key' => 'nome', 'label' => 'Nome'],
|
||||||
|
['key' => 'descrizione', 'label' => 'Descrizione'],
|
||||||
|
['key' => 'contatti_count', 'label' => 'Contatti'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'attiva', 'label' => 'Stato'],
|
||||||
|
['key' => 'created_at', 'label' => 'Creata il'],
|
||||||
|
];
|
||||||
|
$defaultVisible = ['nome', 'descrizione', 'contatti_count', 'tag', 'attiva', 'created_at'];
|
||||||
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
|
? $vista->colonne_visibili
|
||||||
|
: $defaultVisible;
|
||||||
|
|
||||||
|
$tableColumns = array_map(fn($col) => [
|
||||||
|
'key' => $col['key'],
|
||||||
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumnDefs);
|
||||||
|
$entityType = 'mailing-liste';
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
|
return view('mailing-liste.index', compact('mailingLists', 'allTags', 'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths', 'userVistas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('mailing');
|
$this->authorizeWrite('mailing');
|
||||||
return view('mailing-liste.create');
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$settings = \App\Models\EmailSetting::getActive();
|
||||||
|
$firme = $settings?->firme ?? collect();
|
||||||
|
$senderAccounts = SenderAccount::active()->get();
|
||||||
|
return view('mailing-liste.create', compact('tags', 'firme', 'senderAccounts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -30,6 +75,10 @@ class MailingListController extends Controller
|
|||||||
'descrizione' => 'nullable|string',
|
'descrizione' => 'nullable|string',
|
||||||
'attiva' => 'boolean',
|
'attiva' => 'boolean',
|
||||||
'contatti_json' => 'nullable|string',
|
'contatti_json' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'firma_id' => 'nullable|integer|exists:firme,id',
|
||||||
|
'sender_account_id' => 'nullable|integer|exists:sender_accounts,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data['user_id'] = auth()->id();
|
$data['user_id'] = auth()->id();
|
||||||
@@ -40,13 +89,19 @@ class MailingListController extends Controller
|
|||||||
'descrizione' => $data['descrizione'] ?? null,
|
'descrizione' => $data['descrizione'] ?? null,
|
||||||
'attiva' => $data['attiva'],
|
'attiva' => $data['attiva'],
|
||||||
'user_id' => $data['user_id'],
|
'user_id' => $data['user_id'],
|
||||||
|
'firma_id' => $data['firma_id'] ?? null,
|
||||||
|
'sender_account_id' => $data['sender_account_id'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$lista->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($data['contatti_json'])) {
|
if (!empty($data['contatti_json'])) {
|
||||||
$contatti = json_decode($data['contatti_json'], true);
|
$contatti = json_decode($data['contatti_json'], true);
|
||||||
if (is_array($contatti)) {
|
if (is_array($contatti)) {
|
||||||
foreach ($contatti as $contatto) {
|
foreach ($contatti as $contatto) {
|
||||||
if (!empty($contatto['individuo_id']) && !empty($contatto['email'])) {
|
if (!empty($contatto['individuo_id'])) {
|
||||||
$lista->contatti()->create([
|
$lista->contatti()->create([
|
||||||
'individuo_id' => $contatto['individuo_id'],
|
'individuo_id' => $contatto['individuo_id'],
|
||||||
'opt_in' => true,
|
'opt_in' => true,
|
||||||
@@ -63,15 +118,20 @@ class MailingListController extends Controller
|
|||||||
public function show($mailing_liste)
|
public function show($mailing_liste)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('mailing');
|
$this->authorizeRead('mailing');
|
||||||
$mailingList = MailingList::with(['contatti.individuo.contatti'])->findOrFail($mailing_liste);
|
$mailingList = MailingList::with(['contatti.individuo.contatti', 'tags'])->findOrFail($mailing_liste);
|
||||||
return view('mailing-liste.show', compact('mailingList'));
|
return view('mailing-liste.show', compact('mailingList'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function edit($mailingList)
|
public function edit($mailingList)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('mailing');
|
$this->authorizeWrite('mailing');
|
||||||
$mailingList = MailingList::with('contatti.individuo.contatti')->findOrFail($mailingList);
|
$mailingList = MailingList::with(['contatti.individuo.contatti', 'tags', 'firma', 'senderAccount'])->findOrFail($mailingList);
|
||||||
return view('mailing-liste.edit', compact('mailingList'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $mailingList->tags->pluck('id')->toArray();
|
||||||
|
$settings = \App\Models\EmailSetting::getActive();
|
||||||
|
$firme = $settings?->firme ?? collect();
|
||||||
|
$senderAccounts = SenderAccount::active()->get();
|
||||||
|
return view('mailing-liste.edit', compact('mailingList', 'tags', 'selectedTags', 'firme', 'senderAccounts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $mailingList)
|
public function update(Request $request, $mailingList)
|
||||||
@@ -84,6 +144,10 @@ class MailingListController extends Controller
|
|||||||
'descrizione' => 'nullable|string',
|
'descrizione' => 'nullable|string',
|
||||||
'attiva' => 'boolean',
|
'attiva' => 'boolean',
|
||||||
'contatti_json' => 'nullable|string',
|
'contatti_json' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'firma_id' => 'nullable|integer|exists:firme,id',
|
||||||
|
'sender_account_id' => 'nullable|integer|exists:sender_accounts,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data['attiva'] = $data['attiva'] ?? false;
|
$data['attiva'] = $data['attiva'] ?? false;
|
||||||
@@ -92,8 +156,16 @@ class MailingListController extends Controller
|
|||||||
'nome' => $data['nome'],
|
'nome' => $data['nome'],
|
||||||
'descrizione' => $data['descrizione'] ?? null,
|
'descrizione' => $data['descrizione'] ?? null,
|
||||||
'attiva' => $data['attiva'],
|
'attiva' => $data['attiva'],
|
||||||
|
'firma_id' => $data['firma_id'] ?? null,
|
||||||
|
'sender_account_id' => $data['sender_account_id'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$mailingList->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$mailingList->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($data['contatti_json'])) {
|
if (!empty($data['contatti_json'])) {
|
||||||
$newContatti = json_decode($data['contatti_json'], true);
|
$newContatti = json_decode($data['contatti_json'], true);
|
||||||
if (is_array($newContatti)) {
|
if (is_array($newContatti)) {
|
||||||
@@ -107,7 +179,7 @@ class MailingListController extends Controller
|
|||||||
|
|
||||||
$toAdd = array_diff($newIds, $existingIds);
|
$toAdd = array_diff($newIds, $existingIds);
|
||||||
foreach ($newContatti as $contatto) {
|
foreach ($newContatti as $contatto) {
|
||||||
if (in_array($contatto['individuo_id'], $toAdd) && !empty($contatto['email'])) {
|
if (in_array($contatto['individuo_id'], $toAdd)) {
|
||||||
$mailingList->contatti()->create([
|
$mailingList->contatti()->create([
|
||||||
'individuo_id' => $contatto['individuo_id'],
|
'individuo_id' => $contatto['individuo_id'],
|
||||||
'opt_in' => true,
|
'opt_in' => true,
|
||||||
@@ -129,6 +201,41 @@ class MailingListController extends Controller
|
|||||||
return redirect()->route('mailing-liste.index')->with('success', 'Lista eliminata.');
|
return redirect()->route('mailing-liste.index')->with('success', 'Lista eliminata.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('mailing');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
|
||||||
|
if (empty($ids)) {
|
||||||
|
return back()->with('error', 'Nessuna mailing list selezionata.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
MailingList::whereIn('id', $ids)->chunk(100, function ($lists) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($lists as $list) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$list->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$list->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return back()->with('success', "Tag $actionLabel per $count mailing list.");
|
||||||
|
}
|
||||||
|
|
||||||
public function massElimina(Request $request)
|
public function massElimina(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeDelete('mailing');
|
$this->authorizeDelete('mailing');
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\Evento;
|
|||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
use App\Models\MailingList;
|
use App\Models\MailingList;
|
||||||
use App\Models\Contatto;
|
use App\Models\Contatto;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\TipologiaDocumento;
|
use App\Models\TipologiaDocumento;
|
||||||
use App\Models\ReportCustom;
|
use App\Models\ReportCustom;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -47,12 +48,15 @@ class ReportController extends Controller
|
|||||||
$columnOptions[$type] = ReportColumnRegistry::getColumns($type);
|
$columnOptions[$type] = ReportColumnRegistry::getColumns($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('report.index', compact(
|
return view('report.index', compact(
|
||||||
'stats',
|
'stats',
|
||||||
'prebuiltReports',
|
'prebuiltReports',
|
||||||
'customReports',
|
'customReports',
|
||||||
'tipologieDocumento',
|
'tipologieDocumento',
|
||||||
'columnOptions'
|
'columnOptions',
|
||||||
|
'allTags'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +140,14 @@ class ReportController extends Controller
|
|||||||
$config['search_columns'] = $request->input('search_columns');
|
$config['search_columns'] = $request->input('search_columns');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->has('tag_filter')) {
|
||||||
|
$config['tag_filter'] = $request->input('tag_filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('tag_filter_mode')) {
|
||||||
|
$config['tag_filter_mode'] = $request->input('tag_filter_mode');
|
||||||
|
}
|
||||||
|
|
||||||
ReportCustom::create([
|
ReportCustom::create([
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'nome' => $data['nome'],
|
'nome' => $data['nome'],
|
||||||
@@ -252,6 +264,9 @@ class ReportController extends Controller
|
|||||||
'documenti_completo' => $this->reportDocumentiCompleto($tipologiaFilter),
|
'documenti_completo' => $this->reportDocumentiCompleto($tipologiaFilter),
|
||||||
'contatti_completo' => $this->reportContattiCompleto(),
|
'contatti_completo' => $this->reportContattiCompleto(),
|
||||||
'scadenze_documenti' => $this->reportScadenzeDocumenti($tipologiaFilter),
|
'scadenze_documenti' => $this->reportScadenzeDocumenti($tipologiaFilter),
|
||||||
|
'tag_statistiche' => $this->reportTagStatistiche(),
|
||||||
|
'individui_per_tag' => $this->reportIndividuiPerTag(),
|
||||||
|
'documenti_per_tag' => $this->reportDocumentiPerTag(),
|
||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -403,6 +418,30 @@ class ReportController extends Controller
|
|||||||
'colore' => 'teal',
|
'colore' => 'teal',
|
||||||
'has_tipologia_filter' => false,
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'id' => 'tag_statistiche',
|
||||||
|
'nome' => 'Statistiche Tag',
|
||||||
|
'descrizione' => 'Distribuzione degli elementi per tag (individui, gruppi, eventi, documenti, mailing list)',
|
||||||
|
'icona' => 'fa-tags',
|
||||||
|
'colore' => 'indigo',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'individui_per_tag',
|
||||||
|
'nome' => 'Individui per Tag',
|
||||||
|
'descrizione' => 'Elenco completo degli individui raggruppati per tag assegnato',
|
||||||
|
'icona' => 'fa-user-tag',
|
||||||
|
'colore' => 'info',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'documenti_per_tag',
|
||||||
|
'nome' => 'Documenti per Tag',
|
||||||
|
'descrizione' => 'Elenco completo dei documenti raggruppati per tag assegnato',
|
||||||
|
'icona' => 'fa-tags',
|
||||||
|
'colore' => 'secondary',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +515,7 @@ class ReportController extends Controller
|
|||||||
$rows[] = [
|
$rows[] = [
|
||||||
'livello' => 0,
|
'livello' => 0,
|
||||||
'nome' => $gruppo->nome,
|
'nome' => $gruppo->nome,
|
||||||
'diocesi' => $gruppo->diocesi?->nome ?? '-',
|
'diocesi' => $gruppo->diocesi->count() > 0 ? $gruppo->diocesi->pluck('nome')->implode(', ') : '-',
|
||||||
'membri' => $gruppo->individui()->count(),
|
'membri' => $gruppo->individui()->count(),
|
||||||
'padre' => '-',
|
'padre' => '-',
|
||||||
];
|
];
|
||||||
@@ -499,7 +538,7 @@ class ReportController extends Controller
|
|||||||
$rows[] = [
|
$rows[] = [
|
||||||
'livello' => $level,
|
'livello' => $level,
|
||||||
'nome' => str_repeat(' ', $level) . $child->nome,
|
'nome' => str_repeat(' ', $level) . $child->nome,
|
||||||
'diocesi' => $child->diocesi?->nome ?? '-',
|
'diocesi' => $child->diocesi->count() > 0 ? $child->diocesi->pluck('nome')->implode(', ') : '-',
|
||||||
'membri' => $child->individui()->count(),
|
'membri' => $child->individui()->count(),
|
||||||
'padre' => $parent?->nome ?? '-',
|
'padre' => $parent?->nome ?? '-',
|
||||||
];
|
];
|
||||||
@@ -515,7 +554,7 @@ class ReportController extends Controller
|
|||||||
$parent = $g->parent;
|
$parent = $g->parent;
|
||||||
return [
|
return [
|
||||||
'nome' => $g->full_path,
|
'nome' => $g->full_path,
|
||||||
'diocesi' => $g->diocesi?->nome ?? '-',
|
'diocesi' => $g->diocesi->count() > 0 ? $g->diocesi->pluck('nome')->implode(', ') : '-',
|
||||||
'membri' => $g->individui_count,
|
'membri' => $g->individui_count,
|
||||||
'padre' => $parent?->nome ?? '-',
|
'padre' => $parent?->nome ?? '-',
|
||||||
'responsabili' => $g->getResponsabili()->pluck('cognome')->implode(', ') ?: '-',
|
'responsabili' => $g->getResponsabili()->pluck('cognome')->implode(', ') ?: '-',
|
||||||
@@ -856,7 +895,7 @@ class ReportController extends Controller
|
|||||||
return [
|
return [
|
||||||
'nome' => $g->nome,
|
'nome' => $g->nome,
|
||||||
'descrizione' => $g->descrizione ?? '-',
|
'descrizione' => $g->descrizione ?? '-',
|
||||||
'diocesi' => $g->diocesi?->nome ?? '-',
|
'diocesi' => $g->diocesi->count() > 0 ? $g->diocesi->pluck('nome')->implode(', ') : '-',
|
||||||
'livello' => $g->parent_id ? (count($g->getAncestors()) + 1) : 0,
|
'livello' => $g->parent_id ? (count($g->getAncestors()) + 1) : 0,
|
||||||
'membri' => $g->individui_count,
|
'membri' => $g->individui_count,
|
||||||
'padre' => $parent?->nome ?? '-',
|
'padre' => $parent?->nome ?? '-',
|
||||||
@@ -927,6 +966,102 @@ class ReportController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function reportTagStatistiche(): array
|
||||||
|
{
|
||||||
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
|
$rows = $tags->map(function ($tag) {
|
||||||
|
return [
|
||||||
|
'name' => $tag->name,
|
||||||
|
'color' => $tag->color ?? '#6c757d',
|
||||||
|
'individui' => $tag->individui()->count(),
|
||||||
|
'gruppi' => $tag->gruppi()->count(),
|
||||||
|
'eventi' => $tag->eventi()->count(),
|
||||||
|
'documenti' => $tag->documenti()->count(),
|
||||||
|
'mailing' => $tag->mailingLists()->count(),
|
||||||
|
'totale' => $tag->count,
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$totalIndividui = array_sum(array_column($rows, 'individui'));
|
||||||
|
$totalGruppi = array_sum(array_column($rows, 'gruppi'));
|
||||||
|
$totalEventi = array_sum(array_column($rows, 'eventi'));
|
||||||
|
$totalDocumenti = array_sum(array_column($rows, 'documenti'));
|
||||||
|
$totalMailing = array_sum(array_column($rows, 'mailing'));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Statistiche Tag',
|
||||||
|
'headers' => ['Tag', 'Individui', 'Gruppi', 'Eventi', 'Documenti', 'Mailing List', 'Totale Elementi'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['name'], $r['individui'], $r['gruppi'], $r['eventi'], $r['documenti'], $r['mailing'], $r['totale']], $rows),
|
||||||
|
'summary' => 'Tag: ' . count($tags) . ' | Individui: ' . $totalIndividui . ' | Gruppi: ' . $totalGruppi . ' | Eventi: ' . $totalEventi . ' | Documenti: ' . $totalDocumenti . ' | Mailing List: ' . $totalMailing,
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportIndividuiPerTag(): array
|
||||||
|
{
|
||||||
|
$individui = Individuo::with('tags')
|
||||||
|
->whereHas('tags')
|
||||||
|
->orderBy('cognome')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $individui->flatMap(function ($ind) {
|
||||||
|
if ($ind->tags->isEmpty()) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
return $ind->tags->map(function ($tag) use ($ind) {
|
||||||
|
return [
|
||||||
|
'tag' => $tag->name,
|
||||||
|
'tag_color' => $tag->color ?? '#6c757d',
|
||||||
|
'codice' => $ind->codice_id,
|
||||||
|
'cognome' => $ind->cognome,
|
||||||
|
'nome' => $ind->nome,
|
||||||
|
'email' => $ind->email_primaria ?? '-',
|
||||||
|
];
|
||||||
|
});
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Individui per Tag',
|
||||||
|
'headers' => ['Tag', 'Codice', 'Cognome', 'Nome', 'Email'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['tag'], $r['codice'], $r['cognome'], $r['nome'], $r['email']], $rows),
|
||||||
|
'summary' => 'Totale righe (individuo × tag): ' . count($rows),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportDocumentiPerTag(): array
|
||||||
|
{
|
||||||
|
$documenti = Documento::with('tags')
|
||||||
|
->whereHas('tags')
|
||||||
|
->orderBy('nome_file')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $documenti->flatMap(function ($doc) {
|
||||||
|
if ($doc->tags->isEmpty()) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
return $doc->tags->map(function ($tag) use ($doc) {
|
||||||
|
return [
|
||||||
|
'tag' => $tag->name,
|
||||||
|
'tag_color' => $tag->color ?? '#6c757d',
|
||||||
|
'nome_file' => $doc->nome_file,
|
||||||
|
'tipologia' => ucfirst((string) $doc->tipologia),
|
||||||
|
'data' => $doc->created_at->format('d/m/Y'),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Documenti per Tag',
|
||||||
|
'headers' => ['Tag', 'Nome File', 'Tipologia', 'Data Caricamento'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['tag'], $r['nome_file'], $r['tipologia'], $r['data']], $rows),
|
||||||
|
'summary' => 'Totale righe (documento × tag): ' . count($rows),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected function runCustomReport(ReportCustom $report): array
|
protected function runCustomReport(ReportCustom $report): array
|
||||||
{
|
{
|
||||||
$tipo = $report->tipo_report;
|
$tipo = $report->tipo_report;
|
||||||
@@ -954,13 +1089,34 @@ class ReportController extends Controller
|
|||||||
$columns = $config['columns'] ?? [];
|
$columns = $config['columns'] ?? [];
|
||||||
$hasRelations = false;
|
$hasRelations = false;
|
||||||
|
|
||||||
|
// Always load tags if tags column or tag filter is present
|
||||||
|
$needsTags = in_array('tags.nome', $columns) || !empty($config['tag_filter']);
|
||||||
|
if ($needsTags) {
|
||||||
|
$query->with('tags');
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($columns as $col) {
|
foreach ($columns as $col) {
|
||||||
if (str_contains($col, '.')) {
|
if (str_contains($col, '.')) {
|
||||||
$hasRelations = true;
|
$hasRelations = true;
|
||||||
$relation = explode('.', $col)[0];
|
$relation = explode('.', $col)[0];
|
||||||
|
if ($relation !== 'tags') {
|
||||||
$query->with($relation);
|
$query->with($relation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply tag filter
|
||||||
|
if (!empty($config['tag_filter'])) {
|
||||||
|
$tagSlugs = Tag::whereIn('id', (array) $config['tag_filter'])->pluck('slug')->toArray();
|
||||||
|
if (!empty($tagSlugs)) {
|
||||||
|
$mode = $config['tag_filter_mode'] ?? 'any';
|
||||||
|
if ($mode === 'all') {
|
||||||
|
$query->withAllTags($tagSlugs);
|
||||||
|
} else {
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($config['search'])) {
|
if (!empty($config['search'])) {
|
||||||
$search = $config['search'];
|
$search = $config['search'];
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use App\Models\Individuo;
|
||||||
|
use App\Models\Gruppo;
|
||||||
|
use App\Models\Evento;
|
||||||
|
use App\Models\Documento;
|
||||||
|
use App\Models\MailingList;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class RicercaController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeRead('individui');
|
||||||
|
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
|
$tagSlugs = (array) $request->input('tag', []);
|
||||||
|
|
||||||
|
if (empty($tagSlugs)) {
|
||||||
|
return view('ricerca.index', compact('allTags'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$tagSlugs = array_filter($tagSlugs);
|
||||||
|
$selectedTags = Tag::whereIn('slug', $tagSlugs)->get();
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
$totals = [];
|
||||||
|
|
||||||
|
if (count($tagSlugs) === 1) {
|
||||||
|
$tag = $selectedTags->first();
|
||||||
|
$results['individui'] = $tag->individui()->orderBy('cognome')->orderBy('nome')->get();
|
||||||
|
$results['gruppi'] = $tag->gruppi()->orderBy('nome')->get();
|
||||||
|
$results['eventi'] = $tag->eventi()->orderBy('data_specifica')->orderBy('nome_evento')->get();
|
||||||
|
$results['documenti'] = $tag->documenti()->orderBy('nome_file')->get();
|
||||||
|
$results['mailingLists'] = $tag->mailingLists()->orderBy('nome')->get();
|
||||||
|
} else {
|
||||||
|
$results['individui'] = Individuo::withAllTags($tagSlugs)->orderBy('cognome')->orderBy('nome')->get();
|
||||||
|
$results['gruppi'] = Gruppo::withAllTags($tagSlugs)->orderBy('nome')->get();
|
||||||
|
$results['eventi'] = Evento::withAllTags($tagSlugs)->orderBy('data_specifica')->orderBy('nome_evento')->get();
|
||||||
|
$results['documenti'] = Documento::withAllTags($tagSlugs)->orderBy('nome_file')->get();
|
||||||
|
$results['mailingLists'] = MailingList::withAllTags($tagSlugs)->orderBy('nome')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = [
|
||||||
|
'individui' => $results['individui']->count(),
|
||||||
|
'gruppi' => $results['gruppi']->count(),
|
||||||
|
'eventi' => $results['eventi']->count(),
|
||||||
|
'documenti' => $results['documenti']->count(),
|
||||||
|
'mailingLists' => $results['mailingLists']->count(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('ricerca.index', compact('selectedTags', 'tagSlugs', 'results', 'totals', 'allTags'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,9 +33,11 @@ class VistaReportController extends Controller
|
|||||||
$this->authorizeWrite('viste');
|
$this->authorizeWrite('viste');
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'nome' => 'required|string|max:255',
|
'nome' => 'required|string|max:255',
|
||||||
'tipo' => 'required|in:individui,gruppi,documenti,eventi',
|
'tipo' => 'required|in:individui,gruppi,documenti,eventi,mailing-liste',
|
||||||
'colonne_visibili' => 'nullable|array',
|
'colonne_visibili' => 'nullable|array',
|
||||||
'colonne_ordinamento' => 'nullable|array',
|
'colonne_ordinamento' => 'nullable|array',
|
||||||
|
'colonne_larghezze' => 'nullable|array',
|
||||||
|
'colonne_ordine' => 'nullable|array',
|
||||||
'filtri' => 'nullable|array',
|
'filtri' => 'nullable|array',
|
||||||
'ricerca' => 'nullable|string',
|
'ricerca' => 'nullable|string',
|
||||||
'is_default' => 'nullable|boolean',
|
'is_default' => 'nullable|boolean',
|
||||||
@@ -55,6 +57,8 @@ class VistaReportController extends Controller
|
|||||||
'tipo' => $data['tipo'],
|
'tipo' => $data['tipo'],
|
||||||
'colonne_visibili' => $data['colonne_visibili'] ?? null,
|
'colonne_visibili' => $data['colonne_visibili'] ?? null,
|
||||||
'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null,
|
'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null,
|
||||||
|
'colonne_larghezze' => $data['colonne_larghezze'] ?? null,
|
||||||
|
'colonne_ordine' => $data['colonne_ordine'] ?? null,
|
||||||
'filtri' => $data['filtri'] ?? null,
|
'filtri' => $data['filtri'] ?? null,
|
||||||
'ricerca' => $data['ricerca'] ?? null,
|
'ricerca' => $data['ricerca'] ?? null,
|
||||||
'is_default' => $isDefault,
|
'is_default' => $isDefault,
|
||||||
@@ -78,6 +82,8 @@ class VistaReportController extends Controller
|
|||||||
'nome' => 'required|string|max:255',
|
'nome' => 'required|string|max:255',
|
||||||
'colonne_visibili' => 'nullable|array',
|
'colonne_visibili' => 'nullable|array',
|
||||||
'colonne_ordinamento' => 'nullable|array',
|
'colonne_ordinamento' => 'nullable|array',
|
||||||
|
'colonne_larghezze' => 'nullable|array',
|
||||||
|
'colonne_ordine' => 'nullable|array',
|
||||||
'filtri' => 'nullable|array',
|
'filtri' => 'nullable|array',
|
||||||
'ricerca' => 'nullable|string',
|
'ricerca' => 'nullable|string',
|
||||||
'is_default' => 'nullable|boolean',
|
'is_default' => 'nullable|boolean',
|
||||||
@@ -92,6 +98,10 @@ class VistaReportController extends Controller
|
|||||||
|
|
||||||
$vistaReport->update($data);
|
$vistaReport->update($data);
|
||||||
|
|
||||||
|
if ($request->expectsJson()) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'Vista aggiornata.']);
|
||||||
|
}
|
||||||
|
|
||||||
return back()->with('success', 'Vista aggiornata.');
|
return back()->with('success', 'Vista aggiornata.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,13 @@ class CalendarioConnessione extends Model
|
|||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function encryptAndSetConfig(array $config): void
|
public function encryptAndSetConfig(array $config, array $existingConfig = []): void
|
||||||
{
|
{
|
||||||
foreach (['username', 'password', 'client_secret', 'refresh_token', 'api_key'] as $field) {
|
foreach (['username', 'password', 'client_secret', 'refresh_token', 'api_key'] as $field) {
|
||||||
if (isset($config[$field]) && $config[$field] !== '' && !str_starts_with($config[$field], 'eyJpdiI')) {
|
if (!empty($config[$field]) && !str_starts_with($config[$field], 'eyJpdiI')) {
|
||||||
$config[$field] = Crypt::encryptString($config[$field]);
|
$config[$field] = Crypt::encryptString($config[$field]);
|
||||||
|
} elseif (empty($config[$field]) && !empty($existingConfig[$field])) {
|
||||||
|
$config[$field] = $existingConfig[$field];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
class Diocesi extends Model
|
class Diocesi extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'diocesi';
|
protected $table = 'diocesi';
|
||||||
protected $fillable = ['nome', 'regione'];
|
protected $fillable = ['nome', 'regione'];
|
||||||
|
|
||||||
public function gruppi(): HasMany
|
public function gruppi(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Gruppo::class);
|
return $this->belongsToMany(Gruppo::class, 'diocesi_gruppo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
@@ -9,14 +12,20 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|||||||
|
|
||||||
class Documento extends Model
|
class Documento extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'documenti';
|
protected $table = 'documenti';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'user_id', 'cartella_id', 'repository_id', 'storage_disk',
|
'tenant_id', 'user_id', 'cartella_id', 'repository_id', 'storage_disk',
|
||||||
'nome_file', 'file_path', 'tipo', 'tipologia',
|
'nome_file', 'file_path', 'tipo', 'tipologia',
|
||||||
'visibilita', 'visibilita_target_id', 'visibilita_target_type',
|
'visibilita', 'visibilita_target_id', 'visibilita_target_type',
|
||||||
'mime_type', 'dimensione', 'note'
|
'mime_type', 'dimensione', 'note', 'url'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function isLink(): bool
|
||||||
|
{
|
||||||
|
return $this->tipo === 'link';
|
||||||
|
}
|
||||||
|
|
||||||
public function tenant(): BelongsTo
|
public function tenant(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Tenant::class);
|
return $this->belongsTo(Tenant::class);
|
||||||
|
|||||||
+152
-4
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Services\GoogleOAuthService;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
|
||||||
@@ -13,14 +15,17 @@ class EmailSetting extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'imap_host', 'imap_port', 'imap_encryption', 'imap_username', 'imap_password',
|
'imap_host', 'imap_port', 'imap_encryption', 'imap_username', 'imap_password',
|
||||||
'smtp_host', 'smtp_port', 'smtp_encryption', 'smtp_username', 'smtp_password',
|
'smtp_host', 'smtp_port', 'smtp_encryption', 'smtp_username', 'smtp_password',
|
||||||
'email_address', 'email_name', 'reply_to', 'sync_interval_minutes',
|
'email_address', 'email_name', 'from_email', 'from_name', 'reply_to',
|
||||||
'last_sync_at', 'is_active', 'signature', 'signature_enabled'
|
'sync_interval_minutes',
|
||||||
|
'last_sync_at', 'is_active', 'signature', 'signature_enabled',
|
||||||
|
'auth_method', 'google_oauth_connection_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
'signature_enabled' => 'boolean',
|
'signature_enabled' => 'boolean',
|
||||||
'last_sync_at' => 'datetime',
|
'last_sync_at' => 'datetime',
|
||||||
|
'google_oauth_connection_id' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function folders(): HasMany
|
public function folders(): HasMany
|
||||||
@@ -28,8 +33,25 @@ class EmailSetting extends Model
|
|||||||
return $this->hasMany(EmailFolder::class)->orderBy('sort_order');
|
return $this->hasMany(EmailFolder::class)->orderBy('sort_order');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function googleOAuthConnection(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(GoogleOAuthConnection::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function getImapConfig(): array
|
public function getImapConfig(): array
|
||||||
{
|
{
|
||||||
|
if ($this->auth_method === 'oauth') {
|
||||||
|
$token = $this->resolveOAuthToken();
|
||||||
|
return [
|
||||||
|
'host' => $this->imap_host,
|
||||||
|
'port' => $this->imap_port,
|
||||||
|
'encryption' => $this->imap_encryption,
|
||||||
|
'username' => $this->imap_username,
|
||||||
|
'authentication' => 'oauth',
|
||||||
|
'password' => $token,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'host' => $this->imap_host,
|
'host' => $this->imap_host,
|
||||||
'port' => $this->imap_port,
|
'port' => $this->imap_port,
|
||||||
@@ -41,6 +63,10 @@ class EmailSetting extends Model
|
|||||||
|
|
||||||
public function getDecryptedPassword(): string
|
public function getDecryptedPassword(): string
|
||||||
{
|
{
|
||||||
|
if ($this->auth_method === 'oauth') {
|
||||||
|
return $this->resolveOAuthToken();
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($this->imap_password)) {
|
if (empty($this->imap_password)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -53,6 +79,10 @@ class EmailSetting extends Model
|
|||||||
|
|
||||||
public function getDecryptedSmtpPassword(): string
|
public function getDecryptedSmtpPassword(): string
|
||||||
{
|
{
|
||||||
|
if ($this->auth_method === 'oauth') {
|
||||||
|
return $this->resolveOAuthToken();
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($this->smtp_password)) {
|
if (empty($this->smtp_password)) {
|
||||||
return $this->getDecryptedPassword();
|
return $this->getDecryptedPassword();
|
||||||
}
|
}
|
||||||
@@ -63,6 +93,22 @@ class EmailSetting extends Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function resolveOAuthToken(): string
|
||||||
|
{
|
||||||
|
if (!$this->relationLoaded('googleOAuthConnection')) {
|
||||||
|
$this->load('googleOAuthConnection');
|
||||||
|
}
|
||||||
|
$connection = $this->googleOAuthConnection;
|
||||||
|
if (!$connection) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return app(GoogleOAuthService::class)->getAccessToken($connection);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getSmtpConfig(): array
|
public function getSmtpConfig(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -87,19 +133,87 @@ class EmailSetting extends Model
|
|||||||
default => 'none',
|
default => 'none',
|
||||||
};
|
};
|
||||||
|
|
||||||
return new \DirectoryTree\ImapEngine\Mailbox([
|
$config = [
|
||||||
'host' => $this->imap_host,
|
'host' => $this->imap_host,
|
||||||
'port' => $this->imap_port ?? 993,
|
'port' => $this->imap_port ?? 993,
|
||||||
'encryption' => $encryption,
|
'encryption' => $encryption,
|
||||||
'validate_cert' => false,
|
'validate_cert' => false,
|
||||||
'username' => $this->imap_username,
|
'username' => $this->imap_username,
|
||||||
'password' => $this->getDecryptedPassword(),
|
'password' => $this->getDecryptedPassword(),
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if ($this->auth_method === 'oauth') {
|
||||||
|
$config['authentication'] = 'oauth';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new \DirectoryTree\ImapEngine\Mailbox($config);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSmtpMailer(): ?\Symfony\Component\Mailer\Mailer
|
||||||
|
{
|
||||||
|
if ($this->auth_method === 'oauth') {
|
||||||
|
return $this->buildOAuthSmtpMailer();
|
||||||
|
}
|
||||||
|
|
||||||
|
$dsn = $this->buildSmtpDsn();
|
||||||
|
if (!$dsn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
|
||||||
|
return new \Symfony\Component\Mailer\Mailer($transport);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildOAuthSmtpMailer(): ?\Symfony\Component\Mailer\Mailer
|
||||||
|
{
|
||||||
|
$token = $this->resolveOAuthToken();
|
||||||
|
if (empty($token)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $this->smtp_username ?? $this->email_address;
|
||||||
|
$host = $this->smtp_host ?? 'smtp.gmail.com';
|
||||||
|
$port = $this->smtp_port ?? 587;
|
||||||
|
|
||||||
|
$transport = new \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport($host, $port, true);
|
||||||
|
$transport->setUsername($username);
|
||||||
|
$transport->setPassword($token);
|
||||||
|
$transport->setAuthenticators([
|
||||||
|
new \Symfony\Component\Mailer\Transport\Smtp\Auth\XOAuth2Authenticator(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new \Symfony\Component\Mailer\Mailer($transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildSmtpDsn(): ?string
|
||||||
|
{
|
||||||
|
$password = $this->getDecryptedSmtpPassword();
|
||||||
|
if (empty($password)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $this->smtp_username ?? $this->email_address;
|
||||||
|
$encryption = $this->smtp_encryption ?? 'tls';
|
||||||
|
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'%s://%s:%s@%s:%d?encryption=%s',
|
||||||
|
$scheme,
|
||||||
|
rawurlencode($username),
|
||||||
|
rawurlencode($password),
|
||||||
|
$this->smtp_host ?? 'smtp.gmail.com',
|
||||||
|
$this->smtp_port ?? 587,
|
||||||
|
$encryption
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testConnection(): array
|
public function testConnection(): array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -134,4 +248,38 @@ class EmailSetting extends Model
|
|||||||
{
|
{
|
||||||
return $this->signature;
|
return $this->signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEffectiveFromAddress(): string
|
||||||
|
{
|
||||||
|
return $this->from_email ?: $this->email_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEffectiveFromName(): string
|
||||||
|
{
|
||||||
|
$name = $this->from_name ?: $this->email_name;
|
||||||
|
if (filter_var($name, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
return 'Glastree';
|
||||||
|
}
|
||||||
|
$name = preg_replace('/[^\p{L}\p{N}\s]/u', '', $name);
|
||||||
|
$name = trim($name);
|
||||||
|
return empty($name) ? 'Glastree' : $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEffectiveReplyTo(): ?string
|
||||||
|
{
|
||||||
|
if ($this->from_email) {
|
||||||
|
return $this->from_email;
|
||||||
|
}
|
||||||
|
return $this->reply_to ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function firme(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Firma::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function firmaPredefinita(): ?Firma
|
||||||
|
{
|
||||||
|
return $this->firme()->where('is_default', true)->first() ?? $this->firme()->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
@@ -9,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
|
|
||||||
class Evento extends Model
|
class Evento extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'eventi';
|
protected $table = 'eventi';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Firma extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'firme';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'email_setting_id',
|
||||||
|
'nome',
|
||||||
|
'contenuto_html',
|
||||||
|
'is_default',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_default' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function emailSetting(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(EmailSetting::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Services\GoogleOAuthService;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class GoogleOAuthConnection extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'google_oauth_connections';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'service',
|
||||||
|
'email',
|
||||||
|
'access_token',
|
||||||
|
'refresh_token',
|
||||||
|
'expires_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'expires_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExpired(): bool
|
||||||
|
{
|
||||||
|
return $this->expires_at && $this->expires_at->isPast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refresh(): void
|
||||||
|
{
|
||||||
|
if (!$this->refresh_token) {
|
||||||
|
throw new \RuntimeException('Nessun refresh token disponibile per ' . $this->email);
|
||||||
|
}
|
||||||
|
app(GoogleOAuthService::class)->refreshToken($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValidAccessToken(): string
|
||||||
|
{
|
||||||
|
if ($this->isExpired() && $this->refresh_token) {
|
||||||
|
$this->refresh();
|
||||||
|
}
|
||||||
|
return $this->access_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-3
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@@ -10,18 +11,22 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|||||||
|
|
||||||
class Gruppo extends Model
|
class Gruppo extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'gruppi';
|
protected $table = 'gruppi';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'parent_id', 'diocesi_id', 'responsabile_ids',
|
'tenant_id', 'parent_id', 'diocesi_id', 'responsabile_ids',
|
||||||
'nome', 'descrizione', 'indirizzo_incontro', 'cap_incontro',
|
'nome', 'anno_fondazione', 'descrizione', 'indirizzo_incontro', 'cap_incontro',
|
||||||
'città_incontro', 'sigla_provincia_incontro', 'mappa_posizione', 'metadata'
|
'città_incontro', 'sigla_provincia_incontro', 'mappa_posizione', 'metadata'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'metadata' => 'array',
|
'metadata' => 'array',
|
||||||
'responsabile_ids' => 'array',
|
'responsabile_ids' => 'array',
|
||||||
|
'anno_fondazione' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $appends = ['email_primaria', 'telefono_primario'];
|
||||||
|
|
||||||
public function tenant(): BelongsTo
|
public function tenant(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Tenant::class);
|
return $this->belongsTo(Tenant::class);
|
||||||
@@ -37,9 +42,14 @@ class Gruppo extends Model
|
|||||||
return $this->hasMany(Gruppo::class, 'parent_id');
|
return $this->hasMany(Gruppo::class, 'parent_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function diocesi(): BelongsTo
|
public function diocesi(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Diocesi::class);
|
return $this->belongsToMany(Diocesi::class, 'diocesi_gruppo');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gruppoContatti(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(GruppoContatto::class, 'gruppo_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function individui(): BelongsToMany
|
public function individui(): BelongsToMany
|
||||||
@@ -64,6 +74,18 @@ class Gruppo extends Model
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEmailPrimariaAttribute(): ?string
|
||||||
|
{
|
||||||
|
return $this->gruppoContatti()->where('tipo', 'email')->where('is_primary', true)->first()?->valore
|
||||||
|
?? $this->gruppoContatti()->where('tipo', 'email')->first()?->valore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTelefonoPrimarioAttribute(): ?string
|
||||||
|
{
|
||||||
|
return $this->gruppoContatti()->whereIn('tipo', ['telefono', 'cellulare'])->where('is_primary', true)->first()?->valore
|
||||||
|
?? $this->gruppoContatti()->whereIn('tipo', ['telefono', 'cellulare'])->first()?->valore;
|
||||||
|
}
|
||||||
|
|
||||||
public function getResponsabili()
|
public function getResponsabili()
|
||||||
{
|
{
|
||||||
if (empty($this->responsabile_ids)) {
|
if (empty($this->responsabile_ids)) {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class GruppoContatto extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'gruppo_contatti';
|
||||||
|
protected $fillable = ['gruppo_id', 'tipo', 'valore', 'etichetta', 'is_primary'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_primary' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function gruppo(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Gruppo::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
|||||||
|
|
||||||
class Individuo extends Model
|
class Individuo extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'individui';
|
protected $table = 'individui';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'codice_id', 'cognome', 'nome', 'data_nascita',
|
'tenant_id', 'codice_id', 'cognome', 'nome', 'data_nascita',
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
|
|
||||||
class MailingList extends Model
|
class MailingList extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
|
|
||||||
protected $table = 'mailing_lists';
|
protected $table = 'mailing_lists';
|
||||||
protected $fillable = ['tenant_id', 'user_id', 'nome', 'descrizione', 'attiva'];
|
protected $fillable = ['tenant_id', 'user_id', 'nome', 'descrizione', 'attiva', 'firma_id', 'sender_account_id'];
|
||||||
|
|
||||||
protected $casts = ['attiva' => 'boolean'];
|
protected $casts = ['attiva' => 'boolean'];
|
||||||
|
|
||||||
@@ -33,6 +36,16 @@ class MailingList extends Model
|
|||||||
return $this->hasMany(MailingMessaggio::class);
|
return $this->hasMany(MailingMessaggio::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function firma(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Firma::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function senderAccount(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SenderAccount::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function getIndividui()
|
public function getIndividui()
|
||||||
{
|
{
|
||||||
return Individuo::whereHas('mailingContacts', function ($q) {
|
return Individuo::whereHas('mailingContacts', function ($q) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class MailingMessaggio extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'mailing_list_id', 'user_id', 'oggetto', 'corpo',
|
'tenant_id', 'mailing_list_id', 'user_id', 'oggetto', 'corpo',
|
||||||
'canale', 'stato', 'inviato_al', 'totale_destinatari', 'invii_ok', 'invii_falliti',
|
'canale', 'stato', 'inviato_al', 'totale_destinatari', 'invii_ok', 'invii_falliti',
|
||||||
'log_falliti', 'mittente_nome', 'mittente_email',
|
'log_falliti', 'mittente_nome', 'mittente_email', 'firma_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Services\GoogleOAuthService;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
|
||||||
class SenderAccount extends Model
|
class SenderAccount extends Model
|
||||||
@@ -24,11 +26,14 @@ class SenderAccount extends Model
|
|||||||
'verify_email',
|
'verify_email',
|
||||||
'note',
|
'note',
|
||||||
'is_active',
|
'is_active',
|
||||||
|
'auth_method',
|
||||||
|
'google_oauth_connection_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'is_active' => 'boolean',
|
'is_active' => 'boolean',
|
||||||
'smtp_port' => 'integer',
|
'smtp_port' => 'integer',
|
||||||
|
'google_oauth_connection_id' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function scopeActive(Builder $query): Builder
|
public function scopeActive(Builder $query): Builder
|
||||||
@@ -36,8 +41,33 @@ class SenderAccount extends Model
|
|||||||
return $query->where('is_active', true);
|
return $query->where('is_active', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function googleOAuthConnection(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(GoogleOAuthConnection::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveOAuthToken(): string
|
||||||
|
{
|
||||||
|
if (!$this->relationLoaded('googleOAuthConnection')) {
|
||||||
|
$this->load('googleOAuthConnection');
|
||||||
|
}
|
||||||
|
$connection = $this->googleOAuthConnection;
|
||||||
|
if (!$connection) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return app(GoogleOAuthService::class)->getAccessToken($connection);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getDecryptedPassword(): string
|
public function getDecryptedPassword(): string
|
||||||
{
|
{
|
||||||
|
if ($this->auth_method === 'oauth') {
|
||||||
|
return $this->resolveOAuthToken();
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($this->smtp_password)) {
|
if (empty($this->smtp_password)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -121,11 +151,33 @@ class SenderAccount extends Model
|
|||||||
$email->attachFromPath($path);
|
$email->attachFromPath($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->auth_method === 'oauth') {
|
||||||
|
$mailer = $this->buildOAuthMailer();
|
||||||
|
} else {
|
||||||
$transport = \Symfony\Component\Mailer\Transport::fromDsn($this->buildDsn());
|
$transport = \Symfony\Component\Mailer\Transport::fromDsn($this->buildDsn());
|
||||||
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
|
||||||
|
}
|
||||||
|
|
||||||
$mailer->send($email);
|
$mailer->send($email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function buildOAuthMailer(): \Symfony\Component\Mailer\Mailer
|
||||||
|
{
|
||||||
|
$token = $this->resolveOAuthToken();
|
||||||
|
$username = $this->smtp_username ?? $this->email_address;
|
||||||
|
$host = $this->smtp_host ?? 'smtp.gmail.com';
|
||||||
|
$port = $this->smtp_port ?? 587;
|
||||||
|
|
||||||
|
$transport = new \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport($host, $port, true);
|
||||||
|
$transport->setUsername($username);
|
||||||
|
$transport->setPassword($token);
|
||||||
|
$transport->setAuthenticators([
|
||||||
|
new \Symfony\Component\Mailer\Transport\Smtp\Auth\XOAuth2Authenticator(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new \Symfony\Component\Mailer\Mailer($transport);
|
||||||
|
}
|
||||||
|
|
||||||
public function sendReport(string $subject, string $body): bool
|
public function sendReport(string $subject, string $body): bool
|
||||||
{
|
{
|
||||||
if (empty($this->verify_email)) {
|
if (empty($this->verify_email)) {
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
|
|
||||||
|
class Tag extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'tags';
|
||||||
|
|
||||||
|
protected $fillable = ['name', 'slug', 'color', 'order_column'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'order_column' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::creating(function (self $tag) {
|
||||||
|
if (empty($tag->slug)) {
|
||||||
|
$tag->slug = str()->slug($tag->name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function individui(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Individuo::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gruppi(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Gruppo::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eventi(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Evento::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documenti(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Documento::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mailingLists(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(MailingList::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCountAttribute(): int
|
||||||
|
{
|
||||||
|
return $this->individui()->count()
|
||||||
|
+ $this->gruppi()->count()
|
||||||
|
+ $this->eventi()->count()
|
||||||
|
+ $this->documenti()->count()
|
||||||
|
+ $this->mailingLists()->count();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|||||||
+5
-4
@@ -197,12 +197,13 @@ class User extends Authenticatable implements CanResetPasswordContract
|
|||||||
|
|
||||||
$resetUrl = url('/password/reset/' . $token);
|
$resetUrl = url('/password/reset/' . $token);
|
||||||
|
|
||||||
$fromName = $settings->email_name ?? config('app.name');
|
$fromAddress = new Address(
|
||||||
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
$settings->getEffectiveFromAddress(),
|
||||||
$fromName = trim($fromName) ?: config('app.name');
|
$settings->getEffectiveFromName()
|
||||||
|
);
|
||||||
|
|
||||||
$email = (new Email())
|
$email = (new Email())
|
||||||
->from(new Address($settings->email_address, $fromName))
|
->from($fromAddress)
|
||||||
->to($this->email)
|
->to($this->email)
|
||||||
->subject('Reset della password - ' . config('app.name'))
|
->subject('Reset della password - ' . config('app.name'))
|
||||||
->html(view('auth.emails.reset-password', [
|
->html(view('auth.emails.reset-password', [
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ 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', 'is_default'
|
'colonne_ordinamento', 'filtri', 'ricerca', 'is_default',
|
||||||
|
'colonne_larghezze', 'colonne_ordine',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'colonne_visibili' => 'array',
|
'colonne_visibili' => 'array',
|
||||||
'colonne_ordinamento' => 'array',
|
'colonne_ordinamento' => 'array',
|
||||||
'filtri' => 'array',
|
'filtri' => 'array',
|
||||||
|
'colonne_larghezze' => 'array',
|
||||||
|
'colonne_ordine' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class BackupService
|
|||||||
return $backups;
|
return $backups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(): array
|
public function run(array $options = []): array
|
||||||
{
|
{
|
||||||
$result = ['success' => true, 'message' => '', 'filename' => ''];
|
$result = ['success' => true, 'message' => '', 'filename' => ''];
|
||||||
|
|
||||||
@@ -74,12 +74,28 @@ class BackupService
|
|||||||
$filename = $appName . '_' . $timestamp . '.zip';
|
$filename = $appName . '_' . $timestamp . '.zip';
|
||||||
$filepath = $this->backupDir . '/' . $filename;
|
$filepath = $this->backupDir . '/' . $filename;
|
||||||
|
|
||||||
|
$includeFiles = $options['include_files'] ?? (bool) AppSetting::getSetting('backup_include_files', true);
|
||||||
|
$includeEnv = $options['include_env'] ?? (bool) AppSetting::getSetting('backup_include_env', true);
|
||||||
|
|
||||||
$steps = [];
|
$steps = [];
|
||||||
|
$includedComponents = [];
|
||||||
|
|
||||||
$steps[] = $this->backupDatabase();
|
$steps[] = $this->backupDatabase();
|
||||||
$steps[] = $this->backupEnv();
|
$includedComponents[] = 'database';
|
||||||
$steps[] = $this->backupFiles();
|
|
||||||
$this->writeManifest($timestamp);
|
$stepEnv = $this->backupEnv($includeEnv);
|
||||||
|
$steps[] = $stepEnv;
|
||||||
|
if ($includeEnv) {
|
||||||
|
$includedComponents[] = 'env';
|
||||||
|
}
|
||||||
|
|
||||||
|
$stepFiles = $this->backupFiles($includeFiles);
|
||||||
|
$steps[] = $stepFiles;
|
||||||
|
if ($includeFiles) {
|
||||||
|
$includedComponents[] = 'files';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeManifest($timestamp, $includedComponents);
|
||||||
|
|
||||||
$zipResult = $this->createZip($filepath);
|
$zipResult = $this->createZip($filepath);
|
||||||
if (!$zipResult) {
|
if (!$zipResult) {
|
||||||
@@ -90,7 +106,7 @@ class BackupService
|
|||||||
|
|
||||||
$size = file_exists($filepath) ? filesize($filepath) : 0;
|
$size = file_exists($filepath) ? filesize($filepath) : 0;
|
||||||
|
|
||||||
Log::info("Backup completato: {$filename} (" . $this->formatBytes($size) . ")");
|
Log::info("Backup completato: {$filename} (" . $this->formatBytes($size) . ')');
|
||||||
|
|
||||||
$this->cleanupOldBackups();
|
$this->cleanupOldBackups();
|
||||||
|
|
||||||
@@ -100,6 +116,7 @@ class BackupService
|
|||||||
'filename' => $filename,
|
'filename' => $filename,
|
||||||
'size' => $size,
|
'size' => $size,
|
||||||
'size_formatted' => $this->formatBytes($size),
|
'size_formatted' => $this->formatBytes($size),
|
||||||
|
'steps' => $steps,
|
||||||
];
|
];
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->cleanTemp();
|
$this->cleanTemp();
|
||||||
@@ -109,6 +126,7 @@ class BackupService
|
|||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Backup fallito: ' . $e->getMessage(),
|
'message' => 'Backup fallito: ' . $e->getMessage(),
|
||||||
'filename' => '',
|
'filename' => '',
|
||||||
|
'steps' => [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,9 +199,9 @@ class BackupService
|
|||||||
return 'database.sql (' . $this->formatBytes(filesize($sqlFile)) . ')';
|
return 'database.sql (' . $this->formatBytes(filesize($sqlFile)) . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function backupEnv(): string
|
private function backupEnv(bool $includeEnv = true): string
|
||||||
{
|
{
|
||||||
if (!AppSetting::getSetting('backup_include_env', true)) {
|
if (!$includeEnv) {
|
||||||
return '.env (escluso)';
|
return '.env (escluso)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,9 +218,9 @@ class BackupService
|
|||||||
return '.env copiato';
|
return '.env copiato';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function backupFiles(): string
|
private function backupFiles(bool $includeFiles = true): string
|
||||||
{
|
{
|
||||||
if (!AppSetting::getSetting('backup_include_files', true)) {
|
if (!$includeFiles) {
|
||||||
return 'files (esclusi)';
|
return 'files (esclusi)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +238,7 @@ class BackupService
|
|||||||
return 'files copiati';
|
return 'files copiati';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function writeManifest(string $timestamp): void
|
private function writeManifest(string $timestamp, array $includedComponents = ['database']): void
|
||||||
{
|
{
|
||||||
$manifest = [
|
$manifest = [
|
||||||
'created_at' => $timestamp,
|
'created_at' => $timestamp,
|
||||||
@@ -230,6 +248,7 @@ class BackupService
|
|||||||
'db_name' => config('database.connections.mysql.database'),
|
'db_name' => config('database.connections.mysql.database'),
|
||||||
'php_version' => PHP_VERSION,
|
'php_version' => PHP_VERSION,
|
||||||
'laravel_version' => app()->version(),
|
'laravel_version' => app()->version(),
|
||||||
|
'included_components' => $includedComponents,
|
||||||
];
|
];
|
||||||
|
|
||||||
File::put($this->tempDir . '/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT));
|
File::put($this->tempDir . '/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT));
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\GoogleOAuthConnection;
|
||||||
|
use App\Enums\GoogleService;
|
||||||
|
use Google_Client;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class GoogleOAuthService
|
||||||
|
{
|
||||||
|
private const TOKEN_CACHE_TTL = 300;
|
||||||
|
private const MAX_RETRIES = 2;
|
||||||
|
|
||||||
|
public function buildClient(): Google_Client
|
||||||
|
{
|
||||||
|
$client = new Google_Client();
|
||||||
|
$client->setClientId(config('services.google.client_id'));
|
||||||
|
$client->setClientSecret(config('services.google.client_secret'));
|
||||||
|
$client->setRedirectUri(route('google-oauth.callback'));
|
||||||
|
$client->setAccessType('offline');
|
||||||
|
$client->setPrompt('consent');
|
||||||
|
$client->setIncludeGrantedScopes(true);
|
||||||
|
|
||||||
|
return $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAuthUrl(string $service): string
|
||||||
|
{
|
||||||
|
$serviceEnum = GoogleService::from($service);
|
||||||
|
$client = $this->buildClient();
|
||||||
|
$client->setScopes($serviceEnum->scopes());
|
||||||
|
$client->setState($service);
|
||||||
|
|
||||||
|
return $client->createAuthUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleCallback(string $authorizationCode, GoogleService $service): GoogleOAuthConnection
|
||||||
|
{
|
||||||
|
$client = $this->buildClient();
|
||||||
|
$client->setScopes($service->scopes());
|
||||||
|
$client->fetchAccessTokenWithAuthCode($authorizationCode);
|
||||||
|
|
||||||
|
$tokenData = $client->getAccessToken();
|
||||||
|
|
||||||
|
if (isset($tokenData['error'])) {
|
||||||
|
throw new \RuntimeException('Google OAuth error: ' . ($tokenData['error_description'] ?? $tokenData['error']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $tokenData['access_token'];
|
||||||
|
$refreshToken = $tokenData['refresh_token'] ?? null;
|
||||||
|
$expiresAt = now()->addSeconds($tokenData['expires_in']);
|
||||||
|
|
||||||
|
$email = $this->getEmailFromToken($token);
|
||||||
|
|
||||||
|
$connection = GoogleOAuthConnection::updateOrCreate([
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'service' => $service->value,
|
||||||
|
'email' => $email,
|
||||||
|
], [
|
||||||
|
'access_token' => $token,
|
||||||
|
'refresh_token' => $refreshToken,
|
||||||
|
'expires_at' => $expiresAt,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessToken(GoogleOAuthConnection $connection): string
|
||||||
|
{
|
||||||
|
$cacheKey = "google_oauth_token_{$connection->id}";
|
||||||
|
|
||||||
|
return Cache::remember($cacheKey, self::TOKEN_CACHE_TTL, function () use ($connection) {
|
||||||
|
if ($connection->isExpired()) {
|
||||||
|
$this->refreshToken($connection);
|
||||||
|
}
|
||||||
|
return $connection->access_token;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshToken(GoogleOAuthConnection $connection): void
|
||||||
|
{
|
||||||
|
if (!$connection->refresh_token) {
|
||||||
|
throw new \RuntimeException('Impossibile rinnovare: nessun refresh token');
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = $this->buildClient();
|
||||||
|
$client->fetchAccessTokenWithRefreshToken($connection->refresh_token);
|
||||||
|
|
||||||
|
$tokenData = $client->getAccessToken();
|
||||||
|
|
||||||
|
if (isset($tokenData['error'])) {
|
||||||
|
$connection->delete();
|
||||||
|
Cache::forget("google_oauth_token_{$connection->id}");
|
||||||
|
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'Refresh token scaduto o revocato per ' . $connection->email
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$connection->access_token = $tokenData['access_token'];
|
||||||
|
$connection->expires_at = now()->addSeconds($tokenData['expires_in']);
|
||||||
|
|
||||||
|
if (isset($tokenData['refresh_token'])) {
|
||||||
|
$connection->refresh_token = $tokenData['refresh_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$connection->save();
|
||||||
|
Cache::forget("google_oauth_token_{$connection->id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revoke(GoogleOAuthConnection $connection): void
|
||||||
|
{
|
||||||
|
$client = $this->buildClient();
|
||||||
|
$client->revokeToken($connection->access_token);
|
||||||
|
|
||||||
|
$connection->delete();
|
||||||
|
Cache::forget("google_oauth_token_{$connection->id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createXoAuth2SmtpTransport(
|
||||||
|
string $email,
|
||||||
|
string $accessToken
|
||||||
|
): \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport {
|
||||||
|
$transport = new \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport(
|
||||||
|
'smtp.gmail.com',
|
||||||
|
587,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
$transport->setUsername($email);
|
||||||
|
$transport->setPassword($accessToken);
|
||||||
|
$transport->setAuthenticators([
|
||||||
|
new \Symfony\Component\Mailer\Transport\Smtp\Auth\XOAuth2Authenticator(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getEmailFromToken(string $accessToken): string
|
||||||
|
{
|
||||||
|
$client = $this->buildClient();
|
||||||
|
$payload = $client->verifyIdToken($accessToken);
|
||||||
|
|
||||||
|
if ($payload && isset($payload['email'])) {
|
||||||
|
return $payload['email'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$http = new \GuzzleHttp\Client();
|
||||||
|
$response = $http->get('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', [
|
||||||
|
'headers' => ['Authorization' => "Bearer $accessToken"],
|
||||||
|
]);
|
||||||
|
$data = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
return $data['email'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnectionStatus(): array
|
||||||
|
{
|
||||||
|
$connections = GoogleOAuthConnection::where('user_id', auth()->id())->get();
|
||||||
|
$status = [];
|
||||||
|
|
||||||
|
foreach (GoogleService::cases() as $service) {
|
||||||
|
$connection = $connections->firstWhere('service', $service->value);
|
||||||
|
$status[$service->value] = [
|
||||||
|
'connected' => $connection !== null,
|
||||||
|
'connection' => $connection,
|
||||||
|
'email' => $connection?->email,
|
||||||
|
'expired' => $connection?->isExpired() ?? false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
|
|
||||||
|
trait HasTagsLight
|
||||||
|
{
|
||||||
|
public function tags(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeWithAllTags($query, array $tags)
|
||||||
|
{
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$query->whereHas('tags', fn ($q) => $q->where('slug', $tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeWithAnyTags($query, array $tags)
|
||||||
|
{
|
||||||
|
$query->whereHas('tags', fn ($q) => $q->whereIn('slug', $tags));
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-6
@@ -19,12 +19,9 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
'permission' => CheckPermission::class,
|
'permission' => CheckPermission::class,
|
||||||
]);
|
]);
|
||||||
$middleware->trustProxies(at: '*');
|
$middleware->trustProxies(at: '*');
|
||||||
$middleware->web(append: [
|
// Web middleware defaults are already applied by the framework.
|
||||||
\Illuminate\Cookie\Middleware\EncryptCookies::class,
|
// Do NOT append EncryptCookies, StartSession, etc. here — they
|
||||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
// would run twice and corrupt session cookies, causing 419 errors.
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
+227
-21
@@ -20,6 +20,223 @@ echo "==> Pulizia cache..."
|
|||||||
rm -rf bootstrap/cache/*.php storage/framework/cache/data/* storage/framework/sessions/* storage/framework/views/* storage/logs/* 2>/dev/null || true
|
rm -rf bootstrap/cache/*.php storage/framework/cache/data/* storage/framework/sessions/* storage/framework/views/* storage/logs/* 2>/dev/null || true
|
||||||
echo " OK"
|
echo " OK"
|
||||||
|
|
||||||
|
echo "==> Generazione post-deploy.sh..."
|
||||||
|
cat > post-deploy.sh << 'POSTDEPLOY'
|
||||||
|
#!/bin/bash
|
||||||
|
# post-deploy.sh — Generated by build-dist.sh
|
||||||
|
# Usage: bash post-deploy.sh {check|upgrade|install} [--yes]
|
||||||
|
set -u
|
||||||
|
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
ARTISAN="php ${APP_DIR}/artisan"
|
||||||
|
DIAGNOSE="php ${APP_DIR}/diagnose.php"
|
||||||
|
RED='\033[31m'; GREEN='\033[32m'; YELLOW='\033[33m'
|
||||||
|
CYAN='\033[36m'; BOLD='\033[1m'; NC='\033[0m'
|
||||||
|
|
||||||
|
MODE="${1:-check}"
|
||||||
|
BATCH=false; [ "${2:-}" = "--yes" ] && BATCH=true
|
||||||
|
|
||||||
|
echo -e "${CYAN}${BOLD}============================================${NC}"
|
||||||
|
echo -e "${CYAN}${BOLD} Glastree Post-Deploy — ${MODE}${NC}"
|
||||||
|
echo -e "${CYAN}${BOLD}============================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
IS_FRESH=false; [ ! -f "${APP_DIR}/.env" ] && IS_FRESH=true
|
||||||
|
|
||||||
|
log() { echo -e " ${BOLD}$1${NC}"; }
|
||||||
|
ok() { echo -e " ${GREEN}✅ $1${NC}"; }
|
||||||
|
warn() { echo -e " ${YELLOW}⚠ $1${NC}"; }
|
||||||
|
fail() { echo -e " ${RED}❌ $1${NC}"; }
|
||||||
|
|
||||||
|
check_artisan() {
|
||||||
|
if [ ! -f "${APP_DIR}/artisan" ]; then
|
||||||
|
fail "artisan non trovato. Esegui da root dell'applicazione."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_step() {
|
||||||
|
local step="$1" desc="$2"; shift 2
|
||||||
|
log "${step} ${desc}"
|
||||||
|
if "$@" 2>&1 | sed 's/^/ /'; then
|
||||||
|
ok "${desc}"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
local rc=$?
|
||||||
|
fail "${desc} — exit code ${rc}"
|
||||||
|
return ${rc}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_diagnose() {
|
||||||
|
if ${DIAGNOSE} 2>&1 | tail -5; then
|
||||||
|
ok "Diagnostica OK"
|
||||||
|
else
|
||||||
|
warn "Diagnostica ha rilevato problemi (non bloccante)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_check() {
|
||||||
|
echo -e "${YELLOW}═══════════ CONTROLLO ═══════════${NC}" ; echo ""
|
||||||
|
|
||||||
|
echo -e "${BOLD}[0] Diagnostica sistema${NC}"
|
||||||
|
run_diagnose ; echo ""
|
||||||
|
|
||||||
|
echo -e "${BOLD}[1] Ambiente${NC}"
|
||||||
|
if $IS_FRESH; then warn ".env non trovato — fresh install necessario"
|
||||||
|
else ok ".env presente"
|
||||||
|
fi ; echo ""
|
||||||
|
|
||||||
|
echo -e "${BOLD}[2] Migration pendenti${NC}"
|
||||||
|
if [ -f "${APP_DIR}/artisan" ]; then
|
||||||
|
$ARTISAN migrate:status 2>&1 | grep -E '(Pending|Ran)' | head -5 || true
|
||||||
|
PENDING=$(php -r "echo trim(shell_exec('${ARTISAN} migrate:status 2>&1 | grep -c Pending || true'));" 2>/dev/null || echo "0")
|
||||||
|
if [ "${PENDING:-0}" -gt 0 ] 2>/dev/null; then
|
||||||
|
warn "${PENDING} migration da eseguire"
|
||||||
|
else
|
||||||
|
ok "Nessuna migration pendente"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "artisan non disponibile — skip migration check"
|
||||||
|
fi ; echo ""
|
||||||
|
|
||||||
|
echo -e "${BOLD}[3] Storage & Permessi${NC}"
|
||||||
|
for d in storage/framework/cache/data storage/framework/sessions \
|
||||||
|
storage/framework/views storage/logs bootstrap/cache \
|
||||||
|
storage/app/public storage/app/backups; do
|
||||||
|
p="${APP_DIR}/${d}"
|
||||||
|
if [ -d "$p" ]; then
|
||||||
|
[ -w "$p" ] && ok "${d}" || warn "${d} — non scrivibile"
|
||||||
|
else
|
||||||
|
warn "${d} — mancante"
|
||||||
|
fi
|
||||||
|
done ; echo ""
|
||||||
|
|
||||||
|
echo -e "${BOLD}[4] Symlink storage${NC}"
|
||||||
|
if [ -L "${APP_DIR}/public/storage" ]; then
|
||||||
|
ok "public/storage → $(readlink "${APP_DIR}/public/storage")"
|
||||||
|
else
|
||||||
|
warn "public/storage non presente"
|
||||||
|
fi ; echo ""
|
||||||
|
|
||||||
|
echo -e "${BOLD}[5] Cache${NC}"
|
||||||
|
for d in bootstrap/cache storage/framework/cache/data \
|
||||||
|
storage/framework/sessions storage/framework/views; do
|
||||||
|
c=$(find "${APP_DIR}/${d}" -type f 2>/dev/null | wc -l)
|
||||||
|
[ "$c" -gt 0 ] && warn "${d}: ${c} file" || ok "${d}: vuoto"
|
||||||
|
done
|
||||||
|
echo "" ; echo -e "${YELLOW}═══════════ FINE CONTROLLO ═══════════${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_upgrade() {
|
||||||
|
echo -e "${YELLOW}═══════════ UPGRADE ═══════════${NC}" ; check_artisan
|
||||||
|
|
||||||
|
run_step "[1/9]" "Directory storage" mkdir -p \
|
||||||
|
"${APP_DIR}/storage/framework/cache/data" \
|
||||||
|
"${APP_DIR}/storage/framework/sessions" \
|
||||||
|
"${APP_DIR}/storage/framework/views" \
|
||||||
|
"${APP_DIR}/storage/logs" "${APP_DIR}/bootstrap/cache" \
|
||||||
|
"${APP_DIR}/storage/app/public" \
|
||||||
|
"${APP_DIR}/storage/app/public/logos" \
|
||||||
|
"${APP_DIR}/storage/app/public/documenti/eventi" \
|
||||||
|
"${APP_DIR}/storage/app/backups" \
|
||||||
|
"${APP_DIR}/storage/app/documenti" \
|
||||||
|
"${APP_DIR}/storage/app/private/avatars/gruppi" || true
|
||||||
|
|
||||||
|
run_step "[2/9]" ".gitignore storage" bash -c \
|
||||||
|
"echo '*'>'${APP_DIR}/storage/app/public/.gitignore'; echo '!.gitignore'>>'${APP_DIR}/storage/app/public/.gitignore'" || true
|
||||||
|
|
||||||
|
run_step "[3/9]" "Permessi" chmod -R 775 \
|
||||||
|
"${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
|
||||||
|
|
||||||
|
run_step "[4/9]" "Symlink storage" bash -c \
|
||||||
|
"rm -f '${APP_DIR}/public/storage'; ln -sf '../storage/app/public' '${APP_DIR}/public/storage'" || true
|
||||||
|
|
||||||
|
run_step "[5/10]" "Migrazioni DB" $ARTISAN migrate --force || true
|
||||||
|
|
||||||
|
run_step "[6/10]" "Seed diocesi" $ARTISAN db:seed --class=DiocesiSeeder --force || true
|
||||||
|
|
||||||
|
run_step "[7/10]" "Dump autoload" composer dump-autoload --no-dev --optimize || true
|
||||||
|
|
||||||
|
run_step "[8/10]" "Key generate" $ARTISAN key:generate --force || true
|
||||||
|
|
||||||
|
run_step "[9/10]" "Pulizia cache" bash -c \
|
||||||
|
"$ARTISAN config:clear && $ARTISAN route:clear && $ARTISAN view:clear" || true
|
||||||
|
|
||||||
|
run_step "[10/10]" "Verifica finale" $DIAGNOSE || true
|
||||||
|
|
||||||
|
echo "" ; echo -e "${GREEN}${BOLD}✅ Upgrade completato.${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_install() {
|
||||||
|
echo -e "${YELLOW}═══════════ INSTALL (fresh) ═══════════${NC}" ; check_artisan
|
||||||
|
|
||||||
|
run_step "[1/10]" "Directory storage" mkdir -p \
|
||||||
|
"${APP_DIR}/storage/framework/cache/data" \
|
||||||
|
"${APP_DIR}/storage/framework/sessions" \
|
||||||
|
"${APP_DIR}/storage/framework/views" \
|
||||||
|
"${APP_DIR}/storage/logs" "${APP_DIR}/bootstrap/cache" \
|
||||||
|
"${APP_DIR}/storage/app/public" \
|
||||||
|
"${APP_DIR}/storage/app/public/logos" \
|
||||||
|
"${APP_DIR}/storage/app/public/documenti/eventi" \
|
||||||
|
"${APP_DIR}/storage/app/backups" \
|
||||||
|
"${APP_DIR}/storage/app/documenti" \
|
||||||
|
"${APP_DIR}/storage/app/private/avatars/gruppi" || true
|
||||||
|
|
||||||
|
run_step "[2/10]" ".gitignore storage" bash -c \
|
||||||
|
"echo '*'>'${APP_DIR}/storage/app/public/.gitignore'; echo '!.gitignore'>>'${APP_DIR}/storage/app/public/.gitignore'" || true
|
||||||
|
|
||||||
|
run_step "[3/10]" "Permessi" chmod -R 775 \
|
||||||
|
"${APP_DIR}/storage" "${APP_DIR}/bootstrap/cache" || true
|
||||||
|
|
||||||
|
run_step "[4/10]" "File .env" cp "${APP_DIR}/.env.example" "${APP_DIR}/.env" || true
|
||||||
|
warn "MODIFICA .env con i tuoi dati DB/URL prima di proseguire"
|
||||||
|
if ! $BATCH; then read -p " Fatto? (Invio per continuare) " _; fi
|
||||||
|
|
||||||
|
run_step "[5/10]" "Symlink storage" bash -c \
|
||||||
|
"rm -f '${APP_DIR}/public/storage'; ln -sf '../storage/app/public' '${APP_DIR}/public/storage'" || true
|
||||||
|
|
||||||
|
run_step "[6/10]" "Dump autoload" composer dump-autoload --no-dev --optimize || true
|
||||||
|
|
||||||
|
run_step "[7/10]" "Key generate" $ARTISAN key:generate --force || true
|
||||||
|
|
||||||
|
run_step "[8/11]" "Migrazioni DB" $ARTISAN migrate --force || true
|
||||||
|
|
||||||
|
run_step "[9/11]" "Seed diocesi" $ARTISAN db:seed --class=DiocesiSeeder --force || true
|
||||||
|
|
||||||
|
run_step "[10/11]" "Pulizia cache" bash -c \
|
||||||
|
"$ARTISAN config:clear && $ARTISAN route:clear && $ARTISAN view:clear" || true
|
||||||
|
|
||||||
|
run_step "[11/11]" "Verifica finale" $DIAGNOSE || true
|
||||||
|
|
||||||
|
echo "" ; echo -e "${GREEN}${BOLD}✅ Installazione completata.${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
check) run_check ;;
|
||||||
|
upgrade)
|
||||||
|
run_check ; echo ""
|
||||||
|
if $BATCH; then run_upgrade
|
||||||
|
else
|
||||||
|
read -p "Procedere con l'upgrade? (s/N) " c
|
||||||
|
[ "$c" = "s" ] || [ "$c" = "S" ] && run_upgrade || echo "Annullato."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
install) run_install ;;
|
||||||
|
*)
|
||||||
|
echo "Uso: bash post-deploy.sh {check|upgrade|install} [--yes]"
|
||||||
|
echo " check — diagnostica (nessuna modifica)"
|
||||||
|
echo " upgrade — aggiorna installazione esistente"
|
||||||
|
echo " install — fresh install"
|
||||||
|
echo " --yes — batch (nessuna conferma)"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
POSTDEPLOY
|
||||||
|
|
||||||
|
chmod +x post-deploy.sh
|
||||||
|
|
||||||
|
echo " OK"
|
||||||
|
|
||||||
echo "==> Creazione archivio: ${ARCHIVE}"
|
echo "==> Creazione archivio: ${ARCHIVE}"
|
||||||
|
|
||||||
tar czf "${ARCHIVE}" \
|
tar czf "${ARCHIVE}" \
|
||||||
@@ -60,6 +277,8 @@ tar czf "${ARCHIVE}" \
|
|||||||
--warning=no-file-changed \
|
--warning=no-file-changed \
|
||||||
.
|
.
|
||||||
|
|
||||||
|
rm -f post-deploy.sh
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo " Archivio creato: ${ARCHIVE}"
|
echo " Archivio creato: ${ARCHIVE}"
|
||||||
@@ -70,27 +289,14 @@ echo "Per estrarre sul server di destinazione:"
|
|||||||
echo " tar xzf ${ARCHIVE}"
|
echo " tar xzf ${ARCHIVE}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Poi esegui:"
|
echo "Poi esegui:"
|
||||||
echo " # 1. Crea directory necessarie (se non esistono già)"
|
echo " # Upgrade di un'installazione esistente:"
|
||||||
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 " bash post-deploy.sh upgrade"
|
||||||
echo ""
|
echo ""
|
||||||
echo " # 2. Crea file .gitignore in storage/app/public (serve a Laravel)"
|
echo " # Fresh install:"
|
||||||
echo ' echo "*" > storage/app/public/.gitignore'
|
echo " bash post-deploy.sh install"
|
||||||
echo ' echo "!.gitignore" >> storage/app/public/.gitignore'
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " # 3. Permessi (web server = www-data)"
|
echo " # Solo controllo (nessuna modifica):"
|
||||||
echo ' chmod -R 775 storage bootstrap/cache'
|
echo " bash post-deploy.sh check"
|
||||||
echo ' chown -R www-data:www-data storage bootstrap/cache'
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " # 4. Configura ambiente"
|
echo " # Batch mode (nessuna conferma):"
|
||||||
echo " cp .env.example .env # modifica DB, APP_URL, etc."
|
echo " bash post-deploy.sh upgrade --yes"
|
||||||
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"
|
|
||||||
|
|||||||
@@ -35,4 +35,10 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'google' => [
|
||||||
|
'client_id' => env('GOOGLE_CLIENT_ID'),
|
||||||
|
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
|
||||||
|
'redirect' => env('GOOGLE_REDIRECT_URI', config('app.url') . '/auth/google/callback'),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (Schema::hasTable('tenants')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('tenants', function (Blueprint $table) {
|
Schema::create('tenants', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome');
|
$table->string('nome');
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('diocesi')) {
|
||||||
Schema::create('diocesi', function (Blueprint $table) {
|
Schema::create('diocesi', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome');
|
$table->string('nome');
|
||||||
$table->string('regione')->nullable();
|
$table->string('regione')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('comuni')) {
|
||||||
Schema::create('comuni', function (Blueprint $table) {
|
Schema::create('comuni', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome');
|
$table->string('nome');
|
||||||
@@ -29,6 +32,7 @@ return new class extends Migration
|
|||||||
$table->index('sigla_provincia');
|
$table->index('sigla_provincia');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('individui')) {
|
||||||
Schema::create('individui', function (Blueprint $table) {
|
Schema::create('individui', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
@@ -30,6 +31,7 @@ return new class extends Migration
|
|||||||
$table->index('cognome');
|
$table->index('cognome');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('contatti')) {
|
||||||
Schema::create('contatti', function (Blueprint $table) {
|
Schema::create('contatti', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
||||||
@@ -19,6 +20,7 @@ return new class extends Migration
|
|||||||
$table->index('individuo_id');
|
$table->index('individuo_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('gruppi')) {
|
||||||
Schema::create('gruppi', function (Blueprint $table) {
|
Schema::create('gruppi', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
@@ -27,6 +28,7 @@ return new class extends Migration
|
|||||||
$table->index('parent_id');
|
$table->index('parent_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('gruppo_individuo')) {
|
||||||
Schema::create('gruppo_individuo', function (Blueprint $table) {
|
Schema::create('gruppo_individuo', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
||||||
@@ -18,6 +19,7 @@ return new class extends Migration
|
|||||||
$table->unique(['gruppo_id', 'individuo_id']);
|
$table->unique(['gruppo_id', 'individuo_id']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('eventi')) {
|
||||||
Schema::create('eventi', function (Blueprint $table) {
|
Schema::create('eventi', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
@@ -23,14 +24,18 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->index('tenant_id');
|
$table->index('tenant_id');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('eventi_gruppi')) {
|
||||||
Schema::create('eventi_gruppi', function (Blueprint $table) {
|
Schema::create('eventi_gruppi', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
||||||
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('eventi_responsabili')) {
|
||||||
Schema::create('eventi_responsabili', function (Blueprint $table) {
|
Schema::create('eventi_responsabili', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
||||||
@@ -38,6 +43,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('documenti')) {
|
||||||
Schema::create('documenti', function (Blueprint $table) {
|
Schema::create('documenti', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->string('nome_file');
|
$table->string('nome_file');
|
||||||
$table->string('file_path');
|
$table->string('file_path')->nullable();
|
||||||
$table->string('tipo')->nullable();
|
$table->string('tipo')->nullable();
|
||||||
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
|
$table->string('tipologia', 50)->default('documento');
|
||||||
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
|
$table->string('visibilita', 50)->default('gruppo');
|
||||||
$table->unsignedBigInteger('visibilita_target_id')->nullable();
|
$table->unsignedBigInteger('visibilita_target_id')->nullable();
|
||||||
$table->string('visibilita_target_type')->nullable();
|
$table->string('visibilita_target_type')->nullable();
|
||||||
$table->string('mime_type')->nullable();
|
$table->string('mime_type')->nullable();
|
||||||
@@ -25,6 +26,7 @@ return new class extends Migration
|
|||||||
$table->index('tenant_id');
|
$table->index('tenant_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('mailing_lists')) {
|
||||||
Schema::create('mailing_lists', function (Blueprint $table) {
|
Schema::create('mailing_lists', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
@@ -17,7 +18,9 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->index('tenant_id');
|
$table->index('tenant_id');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('mailing_contacts')) {
|
||||||
Schema::create('mailing_contacts', function (Blueprint $table) {
|
Schema::create('mailing_contacts', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
|
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
|
||||||
@@ -28,7 +31,9 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->unique(['mailing_list_id', 'individuo_id']);
|
$table->unique(['mailing_list_id', 'individuo_id']);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('mailing_messaggi')) {
|
||||||
Schema::create('mailing_messaggi', function (Blueprint $table) {
|
Schema::create('mailing_messaggi', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
@@ -46,6 +51,7 @@ return new class extends Migration
|
|||||||
$table->index('tenant_id');
|
$table->index('tenant_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('notifiche')) {
|
||||||
Schema::create('notifiche', function (Blueprint $table) {
|
Schema::create('notifiche', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->unsignedBigInteger('user_id')->nullable();
|
$table->unsignedBigInteger('user_id')->nullable();
|
||||||
@@ -21,6 +22,7 @@ return new class extends Migration
|
|||||||
$table->index('user_id');
|
$table->index('user_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('users')) {
|
||||||
Schema::create('users', function (Blueprint $table) {
|
Schema::create('users', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
@@ -21,13 +22,17 @@ return new class extends Migration
|
|||||||
$table->index('tenant_id');
|
$table->index('tenant_id');
|
||||||
$table->index('email');
|
$table->index('email');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('password_reset_tokens')) {
|
||||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||||
$table->string('email')->primary();
|
$table->string('email')->primary();
|
||||||
$table->string('token');
|
$table->string('token');
|
||||||
$table->timestamp('created_at')->nullable();
|
$table->timestamp('created_at')->nullable();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('sessions')) {
|
||||||
Schema::create('sessions', function (Blueprint $table) {
|
Schema::create('sessions', function (Blueprint $table) {
|
||||||
$table->string('id')->primary();
|
$table->string('id')->primary();
|
||||||
$table->foreignId('user_id')->nullable()->index();
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
@@ -37,6 +42,7 @@ return new class extends Migration
|
|||||||
$table->integer('last_activity')->index();
|
$table->integer('last_activity')->index();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,18 +8,23 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('cache')) {
|
||||||
Schema::create('cache', function (Blueprint $table) {
|
Schema::create('cache', function (Blueprint $table) {
|
||||||
$table->string('key')->primary();
|
$table->string('key')->primary();
|
||||||
$table->mediumText('value');
|
$table->mediumText('value');
|
||||||
$table->integer('expiration');
|
$table->integer('expiration');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('cache_locks')) {
|
||||||
Schema::create('cache_locks', function (Blueprint $table) {
|
Schema::create('cache_locks', function (Blueprint $table) {
|
||||||
$table->string('key')->primary();
|
$table->string('key')->primary();
|
||||||
$table->string('owner');
|
$table->string('owner');
|
||||||
$table->integer('expiration');
|
$table->integer('expiration');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('jobs')) {
|
||||||
Schema::create('jobs', function (Blueprint $table) {
|
Schema::create('jobs', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('queue')->index();
|
$table->string('queue')->index();
|
||||||
@@ -29,7 +34,9 @@ return new class extends Migration
|
|||||||
$table->unsignedInteger('available_at');
|
$table->unsignedInteger('available_at');
|
||||||
$table->unsignedInteger('created_at');
|
$table->unsignedInteger('created_at');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('job_batches')) {
|
||||||
Schema::create('job_batches', function (Blueprint $table) {
|
Schema::create('job_batches', function (Blueprint $table) {
|
||||||
$table->string('id')->primary();
|
$table->string('id')->primary();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
@@ -42,7 +49,9 @@ return new class extends Migration
|
|||||||
$table->integer('created_at');
|
$table->integer('created_at');
|
||||||
$table->integer('finished_at')->nullable();
|
$table->integer('finished_at')->nullable();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('failed_jobs')) {
|
||||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('uuid')->unique();
|
$table->string('uuid')->unique();
|
||||||
@@ -53,6 +62,7 @@ return new class extends Migration
|
|||||||
$table->timestamp('failed_at');
|
$table->timestamp('failed_at');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('individui', 'numero_documento')) {
|
||||||
Schema::table('individui', function (Blueprint $table) {
|
Schema::table('individui', function (Blueprint $table) {
|
||||||
$table->string('numero_documento', 50)->nullable()->after('tipo_documento');
|
$table->string('numero_documento', 50)->nullable()->after('tipo_documento');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('eventi', 'giorno_mese')) {
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
$table->string('giorno_mese')->nullable()->after('giorno_settimana');
|
$table->string('giorno_mese')->nullable()->after('giorno_settimana');
|
||||||
$table->string('mesi_recorrenza')->nullable()->after('giorno_mese');
|
$table->string('mesi_recorrenza')->nullable()->after('giorno_mese');
|
||||||
$table->string('mese_annuale')->nullable()->after('mesi_recorrenza');
|
$table->string('mese_annuale')->nullable()->after('mesi_recorrenza');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('eventi', 'occorrenza_mese')) {
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
$table->unsignedTinyInteger('occorrenza_mese')->nullable()->after('giorno_mese');
|
$table->unsignedTinyInteger('occorrenza_mese')->nullable()->after('giorno_mese');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('eventi_documenti')) {
|
||||||
Schema::create('eventi_documenti', function (Blueprint $table) {
|
Schema::create('eventi_documenti', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
||||||
@@ -15,6 +16,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('documenti', 'user_id')) {
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('viste_report')) {
|
||||||
Schema::create('viste_report', function (Blueprint $table) {
|
Schema::create('viste_report', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
@@ -22,6 +23,7 @@ return new class extends Migration
|
|||||||
$table->index('user_id');
|
$table->index('user_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('viste_report', 'is_default')) {
|
||||||
Schema::table('viste_report', function (Blueprint $table) {
|
Schema::table('viste_report', function (Blueprint $table) {
|
||||||
$table->boolean('is_default')->default(false)->after('nome');
|
$table->boolean('is_default')->default(false)->after('nome');
|
||||||
$table->index('is_default');
|
$table->index('is_default');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('mailing_lists', 'user_id')) {
|
||||||
Schema::table('mailing_lists', function (Blueprint $table) {
|
Schema::table('mailing_lists', function (Blueprint $table) {
|
||||||
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
||||||
$table->index('user_id');
|
$table->index('user_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('tipologie_documenti')) {
|
||||||
Schema::create('tipologie_documenti', function (Blueprint $table) {
|
Schema::create('tipologie_documenti', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome', 50)->unique();
|
$table->string('nome', 50)->unique();
|
||||||
@@ -29,6 +30,7 @@ return new class extends Migration
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('users', 'permissions')) {
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
$table->json('permissions')->nullable()->after('is_admin');
|
$table->json('permissions')->nullable()->after('is_admin');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('activity_logs')) {
|
||||||
Schema::create('activity_logs', function (Blueprint $table) {
|
Schema::create('activity_logs', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||||||
@@ -26,7 +29,9 @@ return new class extends Migration
|
|||||||
$table->index(['created_at']);
|
$table->index(['created_at']);
|
||||||
$table->index(['action']);
|
$table->index(['action']);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('role_presets')) {
|
||||||
Schema::create('role_presets', function (Blueprint $table) {
|
Schema::create('role_presets', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('name', 50)->unique();
|
$table->string('name', 50)->unique();
|
||||||
@@ -35,6 +40,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ return new class extends Migration
|
|||||||
/**
|
/**
|
||||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||||
*/
|
*/
|
||||||
|
if (!Schema::hasTable($tableNames['permissions'])) {
|
||||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||||
$table->id(); // permission id
|
$table->id(); // permission id
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
@@ -31,10 +32,12 @@ return new class extends Migration
|
|||||||
|
|
||||||
$table->unique(['name', 'guard_name']);
|
$table->unique(['name', 'guard_name']);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||||
*/
|
*/
|
||||||
|
if (!Schema::hasTable($tableNames['roles'])) {
|
||||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||||
$table->id(); // role id
|
$table->id(); // role id
|
||||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||||
@@ -50,7 +53,9 @@ return new class extends Migration
|
|||||||
$table->unique(['name', 'guard_name']);
|
$table->unique(['name', 'guard_name']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable($tableNames['model_has_permissions'])) {
|
||||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||||
$table->unsignedBigInteger($pivotPermission);
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
|
||||||
@@ -73,7 +78,9 @@ return new class extends Migration
|
|||||||
'model_has_permissions_permission_model_type_primary');
|
'model_has_permissions_permission_model_type_primary');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable($tableNames['model_has_roles'])) {
|
||||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||||
$table->unsignedBigInteger($pivotRole);
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
@@ -96,7 +103,9 @@ return new class extends Migration
|
|||||||
'model_has_roles_role_model_type_primary');
|
'model_has_roles_role_model_type_primary');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable($tableNames['role_has_permissions'])) {
|
||||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||||
$table->unsignedBigInteger($pivotPermission);
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
$table->unsignedBigInteger($pivotRole);
|
$table->unsignedBigInteger($pivotRole);
|
||||||
@@ -113,6 +122,7 @@ return new class extends Migration
|
|||||||
|
|
||||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
app('cache')
|
app('cache')
|
||||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('email_settings')) {
|
||||||
Schema::create('email_settings', function (Blueprint $table) {
|
Schema::create('email_settings', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('imap_host')->nullable();
|
$table->string('imap_host')->nullable();
|
||||||
@@ -29,6 +30,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('email_folders')) {
|
||||||
Schema::create('email_folders', function (Blueprint $table) {
|
Schema::create('email_folders', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
@@ -20,6 +21,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('email_messages')) {
|
||||||
Schema::create('email_messages', function (Blueprint $table) {
|
Schema::create('email_messages', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
|
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
|
||||||
@@ -38,6 +39,7 @@ return new class extends Migration
|
|||||||
$table->index('received_at');
|
$table->index('received_at');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
// Column type changed to string in 2024_01_01_000008_create_documenti_table.php.
|
||||||
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro', 'email_attachment'])->default('documento')->change();
|
// Application-level validation handles allowed values.
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
// No-op: column is string, not enum.
|
||||||
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento')->change();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -7,14 +7,15 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
DB::table('tipologie_documenti')->insert([
|
DB::table('tipologie_documenti')->updateOrInsert(
|
||||||
'nome' => 'email_attachment',
|
['nome' => 'email_attachment'],
|
||||||
|
[
|
||||||
'descrizione' => 'Allegato email',
|
'descrizione' => 'Allegato email',
|
||||||
'ordine' => 100,
|
'ordine' => 100,
|
||||||
'attiva' => true,
|
'attiva' => true,
|
||||||
'created_at' => now(),
|
|
||||||
'updated_at' => now(),
|
'updated_at' => now(),
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('email_attachments')) {
|
||||||
Schema::create('email_attachments', function (Blueprint $table) {
|
Schema::create('email_attachments', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
|
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
|
||||||
@@ -20,6 +21,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('ruoli')) {
|
||||||
Schema::create('ruoli', function (Blueprint $table) {
|
Schema::create('ruoli', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome', 50)->unique();
|
$table->string('nome', 50)->unique();
|
||||||
@@ -37,7 +38,9 @@ return new class extends Migration
|
|||||||
'updated_at' => now(),
|
'updated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_id')) {
|
||||||
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
$table->unsignedBigInteger('ruolo_id')->nullable()->after('ruolo_nel_gruppo');
|
$table->unsignedBigInteger('ruolo_id')->nullable()->after('ruolo_nel_gruppo');
|
||||||
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
||||||
@@ -60,6 +63,7 @@ return new class extends Migration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,15 +8,20 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (Schema::hasColumn('gruppo_individuo', 'ruolo_id')) {
|
||||||
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
|
$table->dropIndex(['ruolo_id']);
|
||||||
$table->dropForeign(['ruolo_id']);
|
$table->dropForeign(['ruolo_id']);
|
||||||
$table->dropColumn('ruolo_id');
|
$table->dropColumn('ruolo_id');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_ids')) {
|
||||||
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
$table->json('ruolo_ids')->nullable()->after('individuo_id');
|
$table->json('ruolo_ids')->nullable()->after('individuo_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
@@ -24,9 +29,11 @@ return new class extends Migration
|
|||||||
$table->dropColumn('ruolo_ids');
|
$table->dropColumn('ruolo_ids');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_id')) {
|
||||||
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
$table->unsignedBigInteger('ruolo_id')->nullable()->after('individuo_id');
|
$table->unsignedBigInteger('ruolo_id')->nullable()->after('individuo_id');
|
||||||
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -13,10 +13,12 @@ return new class extends Migration
|
|||||||
$table->dropColumn('responsabile_id');
|
$table->dropColumn('responsabile_id');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!Schema::hasColumn('gruppi', 'responsabile_ids')) {
|
||||||
Schema::table('gruppi', function (Blueprint $table) {
|
Schema::table('gruppi', function (Blueprint $table) {
|
||||||
$table->json('responsabile_ids')->nullable()->after('diocesi_id');
|
$table->json('responsabile_ids')->nullable()->after('diocesi_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
@@ -24,9 +26,11 @@ return new class extends Migration
|
|||||||
$table->dropColumn('responsabile_ids');
|
$table->dropColumn('responsabile_ids');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!Schema::hasColumn('gruppi', 'responsabile_id')) {
|
||||||
Schema::table('gruppi', function (Blueprint $table) {
|
Schema::table('gruppi', function (Blueprint $table) {
|
||||||
$table->unsignedBigInteger('responsabile_id')->nullable()->after('diocesi_id');
|
$table->unsignedBigInteger('responsabile_id')->nullable()->after('diocesi_id');
|
||||||
$table->foreign('responsabile_id')->references('id')->on('individui')->onDelete('set null');
|
$table->foreign('responsabile_id')->references('id')->on('individui')->onDelete('set null');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -8,11 +8,13 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('eventi', 'luogo_indirizzo')) {
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
$table->string('luogo_indirizzo', 500)->nullable()->after('note');
|
$table->string('luogo_indirizzo', 500)->nullable()->after('note');
|
||||||
$table->text('luogo_url_maps')->nullable()->after('luogo_indirizzo');
|
$table->text('luogo_url_maps')->nullable()->after('luogo_indirizzo');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
DB::statement("ALTER TABLE documenti MODIFY COLUMN tipologia ENUM('avatar', 'galleria', 'documento', 'statuto', 'altro', 'programma', 'locandina') DEFAULT 'documento'");
|
// Column type changed to string in 2024_01_01_000008_create_documenti_table.php.
|
||||||
|
// Application-level validation handles allowed values.
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
DB::statement("ALTER TABLE documenti MODIFY COLUMN tipologia ENUM('avatar', 'galleria', 'documento', 'statuto', 'altro') DEFAULT 'documento'");
|
// No-op: column is string, not enum.
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('email_settings', 'signature')) {
|
||||||
Schema::table('email_settings', function (Blueprint $table) {
|
Schema::table('email_settings', function (Blueprint $table) {
|
||||||
$table->longText('signature')->nullable()->after('reply_to');
|
$table->longText('signature')->nullable()->after('reply_to');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('email_settings', 'signature_enabled')) {
|
||||||
Schema::table('email_settings', function (Blueprint $table) {
|
Schema::table('email_settings', function (Blueprint $table) {
|
||||||
$table->boolean('signature_enabled')->default(true)->after('signature');
|
$table->boolean('signature_enabled')->default(true)->after('signature');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('report_custom')) {
|
||||||
Schema::create('report_custom', function (Blueprint $table) {
|
Schema::create('report_custom', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
@@ -18,6 +19,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('tipologie_eventi')) {
|
||||||
Schema::create('tipologie_eventi', function (Blueprint $table) {
|
Schema::create('tipologie_eventi', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome', 50)->unique();
|
$table->string('nome', 50)->unique();
|
||||||
@@ -30,6 +31,7 @@ return new class extends Migration
|
|||||||
['nome' => 'altro', 'descrizione' => 'Altro', 'ordine' => 7, 'attiva' => true],
|
['nome' => 'altro', 'descrizione' => 'Altro', 'ordine' => 7, 'attiva' => true],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('sender_accounts')) {
|
||||||
Schema::create('sender_accounts', function (Blueprint $table) {
|
Schema::create('sender_accounts', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('email_address')->unique();
|
$table->string('email_address')->unique();
|
||||||
@@ -26,6 +27,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('mailing_messaggi', 'log_falliti')) {
|
||||||
Schema::table('mailing_messaggi', function (Blueprint $table) {
|
Schema::table('mailing_messaggi', function (Blueprint $table) {
|
||||||
$table->json('log_falliti')->nullable()->after('invii_falliti');
|
$table->json('log_falliti')->nullable()->after('invii_falliti');
|
||||||
$table->string('mittente_nome')->nullable()->after('log_falliti');
|
$table->string('mittente_nome')->nullable()->after('log_falliti');
|
||||||
$table->string('mittente_email')->nullable()->after('mittente_nome');
|
$table->string('mittente_email')->nullable()->after('mittente_nome');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('documenti_cartelle')) {
|
||||||
Schema::create('documenti_cartelle', function (Blueprint $table) {
|
Schema::create('documenti_cartelle', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
|
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
|
||||||
@@ -15,6 +16,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('documenti', 'cartella_id')) {
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
$table->foreignId('cartella_id')->nullable()->constrained('documenti_cartelle')->onDelete('set null');
|
$table->foreignId('cartella_id')->nullable()->constrained('documenti_cartelle')->onDelete('set null');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('storage_repositories')) {
|
||||||
Schema::create('storage_repositories', function (Blueprint $table) {
|
Schema::create('storage_repositories', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome');
|
$table->string('nome');
|
||||||
@@ -18,6 +19,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('documenti', 'repository_id')) {
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
$table->foreignId('repository_id')
|
$table->foreignId('repository_id')
|
||||||
->nullable()
|
->nullable()
|
||||||
@@ -16,6 +17,7 @@ return new class extends Migration
|
|||||||
->after('cartella_id');
|
->after('cartella_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('documenti', 'storage_disk')) {
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
$table->string('storage_disk', 50)->nullable()->after('repository_id');
|
$table->string('storage_disk', 50)->nullable()->after('repository_id');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasTable('calendario_connessioni')) {
|
||||||
Schema::create('calendario_connessioni', function (Blueprint $table) {
|
Schema::create('calendario_connessioni', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome');
|
$table->string('nome');
|
||||||
@@ -26,6 +27,7 @@ return new class extends Migration
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (!Schema::hasColumn('eventi', 'uid_esterno')) {
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
$table->string('uid_esterno', 500)->nullable()->after('luogo_url_maps');
|
$table->string('uid_esterno', 500)->nullable()->after('luogo_url_maps');
|
||||||
$table->index('uid_esterno');
|
$table->index('uid_esterno');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
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('gruppo_individuo', 'ruolo_nel_gruppo')) {
|
||||||
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
|
$table->string('ruolo_nel_gruppo')->nullable()->after('ruolo_ids');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
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::hasTable('tags')) {
|
||||||
|
Schema::create('tags', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name', 100);
|
||||||
|
$table->string('slug', 100)->unique();
|
||||||
|
$table->string('color', 7)->nullable();
|
||||||
|
$table->integer('order_column')->default(0);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('taggables')) {
|
||||||
|
Schema::create('taggables', function (Blueprint $table) {
|
||||||
|
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->morphs('taggable');
|
||||||
|
$table->primary(['tag_id', 'taggable_id', 'taggable_type']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('taggables');
|
||||||
|
Schema::dropIfExists('tags');
|
||||||
|
}
|
||||||
|
};
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
Schema::table('viste_report', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('viste_report', 'colonne_larghezze')) {
|
||||||
|
$table->json('colonne_larghezze')->nullable()->after('colonne_visibili');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('viste_report', 'colonne_ordine')) {
|
||||||
|
$table->json('colonne_ordine')->nullable()->after('colonne_larghezze');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('viste_report', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('viste_report', 'colonne_larghezze')) {
|
||||||
|
$table->dropColumn('colonne_larghezze');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('viste_report', 'colonne_ordine')) {
|
||||||
|
$table->dropColumn('colonne_ordine');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
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::hasTable('google_oauth_connections')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::create('google_oauth_connections', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->string('service'); // email, drive, calendar
|
||||||
|
$table->string('email');
|
||||||
|
$table->text('access_token');
|
||||||
|
$table->text('refresh_token')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['user_id', 'service', 'email']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('google_oauth_connections');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
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('email_settings', 'auth_method')) {
|
||||||
|
Schema::table('email_settings', function (Blueprint $table) {
|
||||||
|
$table->string('auth_method', 20)->default('password');
|
||||||
|
$table->foreignId('google_oauth_connection_id')
|
||||||
|
->nullable()
|
||||||
|
->constrained('google_oauth_connections')
|
||||||
|
->nullOnDelete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasColumn('sender_accounts', 'auth_method')) {
|
||||||
|
Schema::table('sender_accounts', function (Blueprint $table) {
|
||||||
|
$table->string('auth_method', 20)->default('password');
|
||||||
|
$table->foreignId('google_oauth_connection_id')
|
||||||
|
->nullable()
|
||||||
|
->constrained('google_oauth_connections')
|
||||||
|
->nullOnDelete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('email_settings', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('email_settings', 'google_oauth_connection_id')) {
|
||||||
|
$table->dropForeign(['google_oauth_connection_id']);
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('email_settings', 'auth_method')) {
|
||||||
|
$table->dropColumn(['auth_method', 'google_oauth_connection_id']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('sender_accounts', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('sender_accounts', 'google_oauth_connection_id')) {
|
||||||
|
$table->dropForeign(['google_oauth_connection_id']);
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('sender_accounts', 'auth_method')) {
|
||||||
|
$table->dropColumn(['auth_method', 'google_oauth_connection_id']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user