diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 50b78146..f66d8cce 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7,"Tests\\Feature\\StorageRepositoryTest::test_can_create_webdav_repository":8,"Tests\\Feature\\StorageRepositoryTest::test_can_create_google_drive_repository":8,"Tests\\Feature\\StorageRepositoryTest::test_requires_nome_and_tipo":8,"Tests\\Feature\\StorageRepositoryTest::test_can_list_repositories_in_settings":8,"Tests\\Feature\\StorageRepositoryTest::test_can_delete_repository_with_no_documents":7,"Tests\\Feature\\StorageRepositoryTest::test_encrypts_sensitive_config_fields":8,"Tests\\Feature\\StorageRepositoryTest::test_tipo_labels":8,"Tests\\Feature\\StorageRepositoryTest::test_tipo_icons":8,"Tests\\Feature\\StorageRepositoryTest::test_can_reorder_repositories":8,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_webdav_password":8,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_google_drive_secrets":8,"Tests\\Unit\\StorageRepositoryServiceTest::test_builds_null_filesystem_for_unknown_tipo":8},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.022,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.002,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.015,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.002,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.002,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.004,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_webdav_password":0.021,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_google_drive_secrets":0.001,"Tests\\Unit\\StorageRepositoryServiceTest::test_builds_null_filesystem_for_unknown_tipo":0.015,"Tests\\Feature\\StorageRepositoryTest::test_can_create_webdav_repository":0.012,"Tests\\Feature\\StorageRepositoryTest::test_can_create_google_drive_repository":0.005,"Tests\\Feature\\StorageRepositoryTest::test_requires_nome_and_tipo":0.007,"Tests\\Feature\\StorageRepositoryTest::test_can_delete_repository_with_no_documents":0.008,"Tests\\Feature\\StorageRepositoryTest::test_tipo_labels":0.001,"Tests\\Feature\\StorageRepositoryTest::test_tipo_icons":0.002,"Tests\\Feature\\StorageRepositoryTest::test_can_reorder_repositories":0.008}} \ No newline at end of file +{"version":2,"defects":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":7,"Tests\\Feature\\StorageRepositoryTest::test_can_create_webdav_repository":8,"Tests\\Feature\\StorageRepositoryTest::test_can_create_google_drive_repository":8,"Tests\\Feature\\StorageRepositoryTest::test_requires_nome_and_tipo":8,"Tests\\Feature\\StorageRepositoryTest::test_can_list_repositories_in_settings":8,"Tests\\Feature\\StorageRepositoryTest::test_can_delete_repository_with_no_documents":7,"Tests\\Feature\\StorageRepositoryTest::test_encrypts_sensitive_config_fields":8,"Tests\\Feature\\StorageRepositoryTest::test_tipo_labels":8,"Tests\\Feature\\StorageRepositoryTest::test_tipo_icons":8,"Tests\\Feature\\StorageRepositoryTest::test_can_reorder_repositories":8,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_webdav_password":8,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_google_drive_secrets":8,"Tests\\Unit\\StorageRepositoryServiceTest::test_builds_null_filesystem_for_unknown_tipo":8},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.029,"Tests\\Feature\\CalendarEventsTest::test_find_first_sunday_of_june_2026":0.003,"Tests\\Feature\\CalendarEventsTest::test_find_second_saturday_of_june_2026":0.001,"Tests\\Feature\\CalendarEventsTest::test_find_fifth_sunday_returns_null_when_not_in_month":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_for_all_months":0.014,"Tests\\Feature\\CalendarEventsTest::test_generate_mensile_events_respects_selected_months":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_annuale_events_produces_correct_dates":0.001,"Tests\\Feature\\CalendarEventsTest::test_generate_settimanale_events_produces_weekly_occurrences":0.005,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_webdav_password":0.016,"Tests\\Unit\\StorageRepositoryServiceTest::test_encrypts_google_drive_secrets":0.001,"Tests\\Unit\\StorageRepositoryServiceTest::test_builds_null_filesystem_for_unknown_tipo":0.015,"Tests\\Feature\\StorageRepositoryTest::test_can_create_webdav_repository":0.026,"Tests\\Feature\\StorageRepositoryTest::test_can_create_google_drive_repository":0.005,"Tests\\Feature\\StorageRepositoryTest::test_requires_nome_and_tipo":0.006,"Tests\\Feature\\StorageRepositoryTest::test_can_delete_repository_with_no_documents":0.009,"Tests\\Feature\\StorageRepositoryTest::test_tipo_labels":0.001,"Tests\\Feature\\StorageRepositoryTest::test_tipo_icons":0.001,"Tests\\Feature\\StorageRepositoryTest::test_can_reorder_repositories":0.007}} \ No newline at end of file diff --git a/MEMORY.md b/MEMORY.md index 8a21ca76..e1c3762d 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -5,6 +5,14 @@ Laravel 13 / PHP 8.4 project for parish management (Glastree). Full codebase exi --- +## Issues Open +1. **Drive-Segreteria-ADIM (#3)**: Root ha 0 elementi. Verificare root_folder_id nelle impostazioni repository. +2. **Nextcloud_MB (#4)**: Connessione "Unauthorized" dopo fix authType — credenziali da verificare. + +--- + + + ## System Status ### ✅ FUNZIONANTE (Stable) @@ -895,3 +903,82 @@ php artisan route:list --name=email - `resources/views/documenti/index.blade.php`: 10 onclick con `json_encode` invece di `addslashes` - `resources/views/documenti/_folder_tree.blade.php`: 3 onclick con `json_encode` invece di `addslashes` - **Stato**: ✅ 18/19 test pass. `php -l` OK. `php artisan view:cache` OK. JS braces 182/182. + +### 28 Maggio 2026 - Fix Google Drive Repository: credenziali mancanti + wipe su update + +- **Problema**: Drive-Segreteria (repo #1) mostrava 0 file nonostante l'account Google Drive avesse 2 cartelle e 1 file. Il test connessione diceva "Connessione riuscita. 0 elementi trovati nella root." — falso positivo. +- **Cause** (2 bug): + 1. **Credenziali OAuth mancanti**: `client_secret` e `refresh_token` erano entrambi `null` nel DB. La Google API falliva silenziosamente dentro `fetchDirectory()` (catch vuoto → 0 risultati). + 2. **Update wipe credenziali**: `StorageRepositoryController@update()` chiamava `encryptSensitiveConfig()` che criptava solo campi NON vuoti. Quando il form edit veniva salvato con `client_secret=''` e `refresh_token=''` (placeholder), i vecchi valori criptati venivano sovrascritti con stringhe vuote. +- **Fix**: + 1. `StorageRepositoryService::encryptSensitiveConfig()`: aggiunto parametro `$existingConfig`. Se un campo sensibile è vuoto nel nuovo config ma presente in `$existingConfig`, lo preserva. + 2. `StorageRepositoryController@update()`: passa `$storageRepository->config` come existing config a `encryptSensitiveConfig()`. + 3. `StorageRepositoryService::buildGoogleDrive()`: validazione esplicita — lancia `InvalidArgumentException` se mancano `client_id`, `client_secret` o `refresh_token`. + 4. `testConnection()` e `listContents()`: check preventivo credenziali Google Drive complete prima di chiamare `buildFilesystem()`, con messaggio chiaro. +- **Files modificati**: + - `app/Services/StorageRepositoryService.php`: encryptSensitiveConfig() con existingConfig, buildGoogleDrive() validation, testConnection() credential check, listContents() credential check + - `app/Http/Controllers/StorageRepositoryController.php`: update() passa existing config +- **Per risolvere**: l'utente deve reinserire `client_secret` e `refresh_token` nella modale di modifica del repository (Impostazioni → Repository Remoti → Drive-Segreteria → Modifica). Dopo il fix, questi campi non verranno più persi al prossimo salvataggio. +- **Stato**: ✅ 18/19 test pass (solo ExampleTest pre-esistente). `php -l` OK. `php artisan view:cache` OK. + +### 28 Maggio 2026 - OAuth Google Drive integrato nell'app + +- **Implementazione**: Flusso OAuth 2.0 completo senza passare dall'OAuth Playground +- **Route nuove**: + - `GET /auth/google-drive/redirect` → reindirizza a Google OAuth + - `GET /auth/google-drive/callback` → riceve il token, lo salva nel repository +- **Controller**: `StorageRepositoryController@oauthRedirect()` e `oauthCallback()` +- **Flusso UX**: + 1. Utente seleziona Google Drive → compila Client ID, Client Secret, Nome + 2. Click "Autorizza con Google" → reindirizzato a Google + 3. Login con account Google e autorizzazione + 4. Callback salva automaticamente refresh_token + campi form nella sessione + 5. Redirect a `/impostazioni#repository` → modale si riapre con tutti i campi preservati +- **Fix preservazione campi**: `oauthRedirect()` salva in sessione anche `nome`, `is_active`, `root_folder_id`. Il callback li restituisce nel flash `google_drive_new_token`. JS ripristina tutti i campi nel modal (non chiama più `resetRepoForm()` che svuotava tutto). +- **Status visivo**: ✅ badge verde "Autorizzato" se refresh_token presente, ❌ "Non autorizzato" altrimenti +- **Files modificati**: + - `routes/web.php`: 2 nuove route OAuth + - `app/Http/Controllers/StorageRepositoryController.php`: oauthRedirect(), oauthCallback() + - `app/Http/Controllers/ImpostazioniController.php`: passa `$googleDriveNewToken` alla view + - `resources/views/impostazioni/index.blade.php`: pulsante + status + JS auto-ripristino campi +- **Config necessaria una tantum**: aggiungere `{APP_URL}/auth/google-drive/callback` come Authorized Redirect URI in Google Cloud Console +- **Stato**: ✅ 18/19 test pass. `php -l` OK. `php artisan view:cache` OK. JS braces 176/176. + +### 28 Maggio 2026 - Fix Google Drive: normalizePath() root path + +- **Problema**: Google Drive non visualizzava file e cartelle (repository remoto mostrava 0 elementi). +- **Causa**: `normalizePath('/')` in `StorageRepositoryService.php` restituiva `'/'` invece di `''`. L'adapter `as247/flysystem-google-drive` non accetta `'/'` come root path, causando fallimento silenzioso di `listContents()` → `[]`. +- **Contraddizione**: MEMORY.md del 28 Maggio documentava `normalizePath()` come "converte `/` → `''`" ma l'implementazione effettiva a riga 122 faceva `return '/'`. +- **Fix**: Cambiato `return '/'` in `return ''` in `normalizePath()`. +- **Files modificati**: + - `app/Services/StorageRepositoryService.php`: riga 122, `'/'` → `''` +- **Stato**: ✅ 10/10 StorageRepository test pass. `php -l` OK. + +### 28 Maggio 2026 - Fix browseRepo ReferenceError: getRepoIcon inesistente + +- **Problema**: Cliccando "Drive-Segreteria" nella sidebar documenti non succedeva nulla (contenuto remoto non caricato). +- **Causa**: `updateRemoteBreadcrumb()` chiamava `getRepoIcon(repoIconFromId(...))` ma `getRepoIcon()` non è mai stata definita. Il `ReferenceError` bloccava l'esecuzione di `browseRepo()` prima che `loadRemoteContents()` venisse chiamato — nessuna fetch AJAX, nessun contenuto caricato. +- **Fix**: Rimossa chiamata a `getRepoIcon()`, `repoIconFromId()` già restituisce il `className` completo (es. `"fab fa-google-drive text-danger"`). Rimosso anche `fab` hardcoded dal template (duplicato). +- **Files modificati**: + - `resources/views/documenti/index.blade.php`: riga 1383, `getRepoIcon(...)` → diretto +- **Stato**: ✅ JS braces 206/206 OK. `php -l` OK. `php artisan view:cache` OK. + +### 28 Maggio 2026 - CAUSA RADICE: SweetAlert2 mancante in layout + +- **Problema**: Tutte le icone azioni cartelle (Rinomina, Sposta, Elimina) non funzionavano — click senza effetto. Anche il pulsante "+ Nuova cartella" nel modal upload non funzionava. +- **Causa**: SweetAlert2 (`Swal`) **non era incluso nel layout** `adminlte.blade.php`. Le funzioni `renameFolder()`, `moveFolder()`, `deleteFolder()` e il listener `massMoveNewFolderBtn` chiamano `Swal.fire()`, che lanciava `ReferenceError: Swal is not defined`. +- **SweetAlert2 assente**: Non presente né via CDN, né via NPM (`package.json` non ha sweetalert2), né via Vite. L'unica dipendenza JS era jQuery + Bootstrap + AdminLTE via CDN. +- **Perché i fix precedenti sembravano non funzionare**: Anche dopo le correzioni a `getRepoIcon()` e `normalizePath()`, la view cache conteneva ancora il vecchio codice JS. SweetAlert2 mancante bloccava indipendentemente le azioni cartelle. +- **Fix**: Aggiunto CDN SweetAlert2 11 nel layout `adminlte.blade.php` subito dopo AdminLTE JS, prima di `@yield('scripts')`. +- **Files modificati**: + - `resources/views/layouts/adminlte.blade.php`: riga 259, aggiunto `` +- **Stato**: ✅ view cache pulita (`php artisan view:clear`), tutti i file lint OK. `php artisan view:cache` OK. + +### 28 Maggio 2026 - Fix authType WebDAV (Nextcloud) +- **Problema**: Test connessione Nextcloud dava "Unsupported operand types: string & int" +- **Causa**: `buildWebDAV()` passava `'authType' => 'basic'` (stringa) a `Sabre\DAV\Client`, che esegue bitwise AND (`'basic' & AUTH_BASIC`) — incompatibile +- **Soluzione**: Convertito stringa → int tramite `match`: `'basic' → WebDAVClient::AUTH_BASIC (1)`, `'digest' → AUTH_DIGEST (2)`, `'ntlm' → AUTH_NTLM (4)` +- **Stato test Nextcloud**: AuthType non dà più errore. Ora fallisce con "Unauthorized" — le credenziali Nextcloud effettive non sono corrette. +- **Drive-Segreteria-ADIM**: Credenziali OK, connessione OK, ma 0 elementi nella root. Verificare `root_folder_id`. + +(Last updated: 28 Maggio 2026 - Fix authType WebDAV + verify Drive state) diff --git a/app/Http/Controllers/ImpostazioniController.php b/app/Http/Controllers/ImpostazioniController.php index 9dbcec7b..c62d27e1 100644 --- a/app/Http/Controllers/ImpostazioniController.php +++ b/app/Http/Controllers/ImpostazioniController.php @@ -3,14 +3,15 @@ namespace App\Http\Controllers; use App\Models\AppSetting; -use App\Models\Documento; use App\Models\EmailSetting; use App\Models\Ruolo; use App\Models\SenderAccount; use App\Models\StorageRepository; use App\Models\TipologiaDocumento; use App\Models\TipologiaEvento; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; @@ -27,8 +28,9 @@ class ImpostazioniController extends Controller $emailSettings = EmailSetting::first() ?? new EmailSetting(); $senderAccounts = SenderAccount::orderBy('email_address')->get(); $repositories = StorageRepository::orderBy('ordine')->get(); + $googleDriveNewToken = session('google_drive_new_token'); - return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings', 'emailSettings', 'senderAccounts', 'repositories')); + return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings', 'emailSettings', 'senderAccounts', 'repositories', 'googleDriveNewToken')); } public function saveAppSettings(Request $request) diff --git a/app/Http/Controllers/StorageRepositoryController.php b/app/Http/Controllers/StorageRepositoryController.php index 439da82d..3e0e83f4 100644 --- a/app/Http/Controllers/StorageRepositoryController.php +++ b/app/Http/Controllers/StorageRepositoryController.php @@ -9,6 +9,7 @@ use App\Services\StorageRepositoryService; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\View\View; use Symfony\Component\HttpFoundation\StreamedResponse; class StorageRepositoryController extends Controller @@ -64,7 +65,11 @@ class StorageRepositoryController extends Controller 'ordine' => 'nullable|integer|min:0', ]); - $validated['config'] = $this->repoService->encryptSensitiveConfig($validated['config'], $validated['tipo']); + $validated['config'] = $this->repoService->encryptSensitiveConfig( + $validated['config'], + $validated['tipo'], + $storageRepository->config + ); $storageRepository->update($validated); @@ -194,4 +199,110 @@ class StorageRepositoryController extends Controller 'Content-Disposition' => 'inline; filename="' . $basename . '"', ]); } + + public function oauthRedirect(Request $request): RedirectResponse + { + $this->authorizeWrite('settings'); + + $clientId = $request->get('client_id'); + $clientSecret = $request->get('client_secret'); + $repoId = (int) $request->get('repo_id', 0); + + if (empty($clientId) || empty($clientSecret)) { + return redirect('/impostazioni#repository')->with('error', 'Inserisci Client ID e Client Secret prima di autorizzare.'); + } + + session(['google_drive_oauth' => [ + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + 'repo_id' => $repoId, + 'nome' => $request->get('nome', ''), + 'is_active' => $request->get('is_active', '1'), + 'root_folder_id' => $request->get('root_folder_id', 'root'), + ]]); + + $client = new \Google_Client(); + $client->setClientId($clientId); + $client->setClientSecret($clientSecret); + $client->setRedirectUri(url('/auth/google-drive/callback')); + $client->addScope(\Google_Service_Drive::DRIVE); + $client->setAccessType('offline'); + $client->setPrompt('consent'); + + $authUrl = $client->createAuthUrl(); + + return redirect()->away($authUrl); + } + + public function oauthCallback(Request $request): RedirectResponse + { + $this->authorizeWrite('settings'); + + if ($request->get('error')) { + return redirect('/impostazioni#repository')->with('error', 'Autorizzazione Google Drive annullata o fallita.'); + } + + $code = $request->get('code'); + if (!$code) { + return redirect('/impostazioni#repository')->with('error', 'Codice di autorizzazione mancante.'); + } + + $oauthData = session('google_drive_oauth'); + if (!$oauthData) { + return redirect('/impostazioni#repository')->with('error', 'Sessione scaduta. Riprova l\'autorizzazione.'); + } + + try { + $client = new \Google_Client(); + $client->setClientId($oauthData['client_id']); + $client->setClientSecret($oauthData['client_secret']); + $client->setRedirectUri(url('/auth/google-drive/callback')); + $client->addScope(\Google_Service_Drive::DRIVE); + + $token = $client->fetchAccessTokenWithAuthCode($code); + + if (isset($token['error'])) { + throw new \Exception($token['error_description'] ?? $token['error']); + } + + $refreshToken = $client->getRefreshToken(); + if (!$refreshToken) { + throw new \Exception('Refresh token non ricevuto. Assicurati che l\'app OAuth sia configurata con access_type=offline e prompt=consent nella Google Cloud Console.'); + } + + $repoId = $oauthData['repo_id']; + session()->forget('google_drive_oauth'); + + if ($repoId > 0) { + $repo = StorageRepository::find($repoId); + if ($repo) { + $config = $repo->config; + $config['client_id'] = $oauthData['client_id']; + $config['client_secret'] = $oauthData['client_secret']; + $config['refresh_token'] = $refreshToken; + + $config = $this->repoService->encryptSensitiveConfig($config, 'google_drive'); + + $repo->update(['config' => $config]); + + return redirect('/impostazioni#repository')->with('success', 'Google Drive autorizzato con successo! Refresh token salvato.'); + } + + return redirect('/impostazioni#repository')->with('error', 'Repository non trovato (ID: ' . $repoId . ').'); + } + + session()->flash('google_drive_new_token', [ + 'refresh_token' => $refreshToken, + 'client_id' => $oauthData['client_id'], + 'client_secret' => $oauthData['client_secret'], + 'nome' => $oauthData['nome'] ?? '', + 'is_active' => $oauthData['is_active'] ?? '1', + 'root_folder_id' => $oauthData['root_folder_id'] ?? 'root', + ]); + + return redirect('/impostazioni#repository')->with('success', 'Autorizzazione Google Drive completata! Ora completa la creazione del repository e salva.'); + } catch (\Exception $e) { + return redirect('/impostazioni#repository')->with('error', 'Errore autorizzazione Google Drive: ' . $e->getMessage()); + } + } } diff --git a/app/Services/StorageRepositoryService.php b/app/Services/StorageRepositoryService.php index 5933d90b..eaaf998d 100644 --- a/app/Services/StorageRepositoryService.php +++ b/app/Services/StorageRepositoryService.php @@ -33,6 +33,14 @@ class StorageRepositoryService public function testConnection(StorageRepository $repo): array { try { + $config = $repo->getDecryptedConfig(); + + if ($repo->tipo === 'google_drive') { + if (empty($config['client_id']) || empty($config['client_secret']) || empty($config['refresh_token'])) { + return ['success' => false, 'message' => 'Configurazione incompleta: client_id, client_secret e refresh_token sono necessari per Google Drive.']; + } + } + $filesystem = $this->buildFilesystem($repo); if (!$filesystem) { return ['success' => false, 'message' => 'Impossibile costruire il filesystem per questo tipo di repository.']; @@ -49,12 +57,21 @@ class StorageRepositoryService public function listContents(StorageRepository $repo, string $path = '/'): array { - $filesystem = $this->buildFilesystem($repo); - if (!$filesystem) { - return []; - } - try { + $config = $repo->getDecryptedConfig(); + + if ($repo->tipo === 'google_drive') { + if (empty($config['client_id']) || empty($config['client_secret']) || empty($config['refresh_token'])) { + Log::warning("StorageRepository: listContents failed for repo #{$repo->id} ({$repo->nome}): credenziali Google Drive mancanti"); + return []; + } + } + + $filesystem = $this->buildFilesystem($repo); + if (!$filesystem) { + return []; + } + $normalizedPath = $this->normalizePath($path); $items = $filesystem->listContents($normalizedPath, false)->toArray(); $result = []; @@ -76,7 +93,7 @@ class StorageRepositoryService } } - public function encryptSensitiveConfig(array $config, string $tipo): array + public function encryptSensitiveConfig(array $config, string $tipo, array $existingConfig = []): array { $sensitiveFields = match ($tipo) { 'webdav' => ['password'], @@ -87,6 +104,8 @@ class StorageRepositoryService foreach ($sensitiveFields as $field) { if (!empty($config[$field]) && !$this->isEncrypted($config[$field])) { $config[$field] = Crypt::encryptString($config[$field]); + } elseif (empty($config[$field]) && !empty($existingConfig[$field])) { + $config[$field] = $existingConfig[$field]; } } @@ -100,7 +119,7 @@ class StorageRepositoryService return ''; } if ($path === '/') { - return '/'; + return ''; } return ltrim($path, '/\\'); } @@ -112,11 +131,18 @@ class StorageRepositoryService private function buildWebDAV(array $config): ?Filesystem { + $authType = match ($config['auth_type'] ?? 'basic') { + 'basic' => WebDAVClient::AUTH_BASIC, + 'digest' => WebDAVClient::AUTH_DIGEST, + 'ntlm' => WebDAVClient::AUTH_NTLM, + default => WebDAVClient::AUTH_BASIC, + }; + $client = new WebDAVClient([ 'baseUri' => rtrim($config['base_uri'] ?? '', '/') . '/', 'userName' => $config['username'] ?? '', 'password' => $config['password'] ?? '', - 'authType' => $config['auth_type'] ?? 'basic', + 'authType' => $authType, ]); $root = ltrim($config['root'] ?? '/', '/'); @@ -127,10 +153,14 @@ class StorageRepositoryService private function buildGoogleDrive(array $config): ?Filesystem { + if (empty($config['client_id']) || empty($config['client_secret']) || empty($config['refresh_token'])) { + throw new \InvalidArgumentException('Google Drive richiede client_id, client_secret e refresh_token per l\'autenticazione.'); + } + $client = new \Google_Client(); - $client->setClientId($config['client_id'] ?? ''); - $client->setClientSecret($config['client_secret'] ?? ''); - $client->refreshToken($config['refresh_token'] ?? ''); + $client->setClientId($config['client_id']); + $client->setClientSecret($config['client_secret']); + $client->refreshToken($config['refresh_token']); $client->addScope(\Google_Service_Drive::DRIVE); $service = new \Google_Service_Drive($client); diff --git a/resources/views/documenti/index.blade.php b/resources/views/documenti/index.blade.php index cbd29215..e2a85bbd 100644 --- a/resources/views/documenti/index.blade.php +++ b/resources/views/documenti/index.blade.php @@ -1380,7 +1380,7 @@ function browseRepo(repoId, repoNome, path) { function updateRemoteBreadcrumb(nome, path) { const bc = document.getElementById('remoteBreadcrumb'); const parts = path.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean); - let html = ' ' + escHtml(nome); + let html = ' ' + escHtml(nome); if (parts.length > 0 && parts[0] !== '') { html += ' / ' + parts.map(p => escHtml(p)).join(' / '); } diff --git a/resources/views/impostazioni/index.blade.php b/resources/views/impostazioni/index.blade.php index 3216adcf..9f5b70a7 100644 --- a/resources/views/impostazioni/index.blade.php +++ b/resources/views/impostazioni/index.blade.php @@ -1625,6 +1625,7 @@ if (window.location.hash === '#email') { // ===== REPOSITORY REMOTI ===== let repositories = @json($repositories); +let newToken = @json($googleDriveNewToken); function resetRepoForm() { document.getElementById('repoForm').action = '{{ route('storage-repositories.store') }}'; @@ -1633,6 +1634,7 @@ function resetRepoForm() { document.getElementById('repoForm').reset(); document.getElementById('repoId').value = ''; document.getElementById('repo_is_active').checked = true; + setGdAuthStatus(false, ''); toggleRepoConfigFields(); } @@ -1640,6 +1642,59 @@ function toggleRepoConfigFields() { const tipo = document.getElementById('repo_tipo').value; document.getElementById('webdavFields').style.display = tipo === 'webdav' ? '' : 'none'; document.getElementById('googleDriveFields').style.display = tipo === 'google_drive' ? '' : 'none'; + if (tipo === 'google_drive') { + updateGdAuthUrl(); + } +} + +function setGdAuthStatus(authorized, clientId) { + const statusEl = document.getElementById('gdAuthStatus'); + const btn = document.getElementById('gdAuthBtn'); + if (authorized) { + statusEl.innerHTML = ' Autorizzato'; + btn.innerHTML = ' Ri-autorizza con Google'; + } else { + statusEl.innerHTML = ' Non autorizzato'; + btn.innerHTML = ' Autorizza con Google'; + } +} + +function updateGdAuthUrl() { + const clientId = document.getElementById('gd_client_id').value.trim(); + const clientSecret = document.getElementById('gd_client_secret').value.trim(); + const repoId = document.getElementById('repoId').value; + const nome = document.getElementById('repo_nome').value.trim(); + const isActive = document.getElementById('repo_is_active').checked ? '1' : '0'; + const rootFolderId = document.getElementById('gd_root_folder_id').value.trim(); + const btn = document.getElementById('gdAuthBtn'); + if (clientId && clientSecret) { + const params = new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + repo_id: repoId || '0', + nome: nome, + is_active: isActive, + root_folder_id: rootFolderId || 'root', + }); + btn.href = '{{ route('google-drive.redirect') }}?' + params.toString(); + btn.classList.remove('disabled'); + } else { + btn.href = '#'; + btn.classList.add('disabled'); + } +} + +function startGdAuth() { + const clientId = document.getElementById('gd_client_id').value.trim(); + const clientSecret = document.getElementById('gd_client_secret').value.trim(); + if (!clientId || !clientSecret) { + alert('Inserisci Client ID e Client Secret prima di autorizzare.'); + return false; + } + // Open in same window (full redirect flow) + updateGdAuthUrl(); + window.location.href = document.getElementById('gdAuthBtn').href; + return false; } function editRepo(id) { @@ -1664,10 +1719,11 @@ function editRepo(id) { document.getElementById('gd_client_id').value = config.client_id || ''; document.getElementById('gd_client_secret').value = ''; document.getElementById('gd_client_secret').placeholder = config.client_secret ? '......' : ''; - document.getElementById('gd_refresh_token').value = ''; - document.getElementById('gd_refresh_token').placeholder = config.refresh_token ? '......' : ''; + document.getElementById('gd_refresh_token').value = config.refresh_token || ''; document.getElementById('gd_root_folder_id').value = config.root_folder_id || 'root'; + const hasRefreshToken = !!(config.refresh_token && config.refresh_token !== ''); + setGdAuthStatus(hasRefreshToken, config.client_id || ''); toggleRepoConfigFields(); $('#repoModal').modal('show'); } @@ -1736,6 +1792,25 @@ $(document).ready(function() { } }); } + + if (newToken && newToken.refresh_token) { + // Auto-open modal after OAuth callback — preserve nome and other fields + document.getElementById('repoForm').action = '{{ route('storage-repositories.store') }}'; + document.getElementById('repoMethod').value = 'POST'; + document.getElementById('repoModalTitle').textContent = 'Nuovo Repository Remoto'; + document.getElementById('repoForm').reset(); + document.getElementById('repoId').value = ''; + document.getElementById('repo_nome').value = newToken.nome || ''; + document.getElementById('repo_tipo').value = 'google_drive'; + document.getElementById('repo_is_active').checked = newToken.is_active !== '0'; + document.getElementById('gd_client_id').value = newToken.client_id || ''; + document.getElementById('gd_client_secret').value = newToken.client_secret || ''; + document.getElementById('gd_refresh_token').value = newToken.refresh_token; + document.getElementById('gd_root_folder_id').value = newToken.root_folder_id || 'root'; + setGdAuthStatus(true, newToken.client_id || ''); + toggleRepoConfigFields(); + $('#repoModal').modal('show'); + } }); @@ -1828,35 +1903,46 @@ $(document).ready(function() {
{{ url('/auth/google-drive/callback') }} come Authorized Redirect URI.