2026-05-28 09:34:28 +02:00
< ? php
declare ( strict_types = 1 );
namespace App\Http\Controllers ;
use App\Models\StorageRepository ;
use App\Services\StorageRepositoryService ;
use Illuminate\Http\JsonResponse ;
use Illuminate\Http\RedirectResponse ;
use Illuminate\Http\Request ;
2026-05-29 06:23:57 +02:00
use Illuminate\Support\Facades\Log ;
2026-05-28 13:25:47 +02:00
use Illuminate\View\View ;
2026-05-28 09:34:28 +02:00
use Symfony\Component\HttpFoundation\StreamedResponse ;
class StorageRepositoryController extends Controller
{
public function __construct (
private readonly StorageRepositoryService $repoService
) {}
public function index ( Request $request )
{
$this -> authorizeWrite ( 'settings' );
$repositories = StorageRepository :: orderBy ( 'ordine' ) -> get ();
if ( $request -> ajax ()) {
return response () -> json ( $repositories );
}
return view ( 'storage-repositories.index' , compact ( 'repositories' ));
}
public function store ( Request $request ) : RedirectResponse
{
$this -> authorizeWrite ( 'settings' );
$validated = $request -> validate ([
'nome' => 'required|string|max:255' ,
'tipo' => 'required|string|in:webdav,google_drive' ,
'config' => 'required|array' ,
'is_active' => 'boolean' ,
'ordine' => 'nullable|integer|min:0' ,
]);
$maxOrdine = StorageRepository :: max ( 'ordine' ) ? ? 0 ;
$validated [ 'ordine' ] ? ? = $maxOrdine + 1 ;
$validated [ 'config' ] = $this -> repoService -> encryptSensitiveConfig ( $validated [ 'config' ], $validated [ 'tipo' ]);
StorageRepository :: create ( $validated );
return redirect ( '/impostazioni#repository' ) -> with ( 'success' , 'Repository creato.' );
}
public function update ( Request $request , StorageRepository $storageRepository ) : RedirectResponse
{
$this -> authorizeWrite ( 'settings' );
$validated = $request -> validate ([
'nome' => 'required|string|max:255' ,
'tipo' => 'required|string|in:webdav,google_drive' ,
'config' => 'required|array' ,
'is_active' => 'boolean' ,
'ordine' => 'nullable|integer|min:0' ,
]);
2026-05-28 13:25:47 +02:00
$validated [ 'config' ] = $this -> repoService -> encryptSensitiveConfig (
$validated [ 'config' ],
$validated [ 'tipo' ],
$storageRepository -> config
);
2026-05-28 09:34:28 +02:00
$storageRepository -> update ( $validated );
return redirect ( '/impostazioni#repository' ) -> with ( 'success' , 'Repository aggiornato.' );
}
public function destroy ( StorageRepository $storageRepository ) : JsonResponse | RedirectResponse
{
$this -> authorizeDelete ( 'settings' );
$docCount = $storageRepository -> documenti () -> count ();
if ( $docCount > 0 ) {
$message = " Impossibile eliminare: { $docCount } documenti associati a questo repository. " ;
if ( request () -> expectsJson ()) {
return response () -> json ([ 'success' => false , 'message' => $message ], 409 );
}
return redirect ( '/impostazioni#repository' ) -> with ( 'error' , $message );
}
$storageRepository -> delete ();
if ( request () -> expectsJson ()) {
return response () -> json ([ 'success' => true ]);
}
return redirect ( '/impostazioni#repository' ) -> with ( 'success' , 'Repository eliminato.' );
}
public function test ( StorageRepository $storageRepository ) : JsonResponse
{
$this -> authorizeWrite ( 'settings' );
$result = $this -> repoService -> testConnection ( $storageRepository );
return response () -> json ( $result );
}
public function browse ( Request $request , StorageRepository $storageRepository ) : JsonResponse
{
$this -> authorizeRead ( 'documenti' );
$path = $request -> get ( 'path' , '/' );
2026-05-29 06:23:57 +02:00
try {
if ( ! $storageRepository -> is_active ) {
return response () -> json ([
'success' => false ,
'message' => 'Repository non attivo' ,
'contents' => []
], 400 );
}
$contents = $this -> repoService -> listContents ( $storageRepository , $path );
return response () -> json ([
'success' => true ,
'contents' => $contents ,
'path' => $path ,
'repository' => [
'id' => $storageRepository -> id ,
'nome' => $storageRepository -> nome ,
'tipo' => $storageRepository -> tipo ,
],
]);
} catch ( \Exception $e ) {
Log :: error ( " StorageRepository browse failed for repo # { $storageRepository -> id } : " . $e -> getMessage ());
return response () -> json ([
'success' => false ,
'message' => 'Errore: ' . $e -> getMessage (),
'contents' => []
], 500 );
}
2026-05-28 09:34:28 +02:00
}
public function reorder ( Request $request ) : JsonResponse
{
$this -> authorizeWrite ( 'settings' );
$order = $request -> input ( 'order' , []);
foreach ( $order as $index => $id ) {
StorageRepository :: where ( 'id' , $id ) -> update ([ 'ordine' => $index ]);
}
return response () -> json ([ 'success' => true ]);
}
public function downloadRemote ( StorageRepository $storageRepository , Request $request ) : StreamedResponse
{
$this -> authorizeRead ( 'documenti' );
$path = $request -> get ( 'path' );
if ( ! $path ) {
abort ( 400 , 'Path required' );
}
$filesystem = $this -> repoService -> buildFilesystem ( $storageRepository );
if ( ! $filesystem || ! $filesystem -> fileExists ( $path )) {
abort ( 404 );
}
$stream = $filesystem -> readStream ( $path );
if ( ! $stream ) {
abort ( 404 );
}
$mimeType = $filesystem -> mimeType ( $path ) ? ? 'application/octet-stream' ;
$basename = basename ( $path );
return response () -> stream ( function () use ( $stream ) {
fpassthru ( $stream );
fclose ( $stream );
}, 200 , [
'Content-Type' => $mimeType ,
'Content-Disposition' => 'attachment; filename="' . $basename . '"' ,
]);
}
public function previewRemote ( StorageRepository $storageRepository , Request $request ) : StreamedResponse
{
$this -> authorizeRead ( 'documenti' );
$path = $request -> get ( 'path' );
if ( ! $path ) {
abort ( 400 , 'Path required' );
}
$filesystem = $this -> repoService -> buildFilesystem ( $storageRepository );
if ( ! $filesystem || ! $filesystem -> fileExists ( $path )) {
abort ( 404 );
}
$stream = $filesystem -> readStream ( $path );
if ( ! $stream ) {
abort ( 404 );
}
$mimeType = $filesystem -> mimeType ( $path ) ? ? 'application/octet-stream' ;
$basename = basename ( $path );
return response () -> stream ( function () use ( $stream ) {
fpassthru ( $stream );
fclose ( $stream );
}, 200 , [
'Content-Type' => $mimeType ,
'Content-Disposition' => 'inline; filename="' . $basename . '"' ,
]);
}
2026-05-28 13:25:47 +02:00
2026-05-29 06:54:07 +02:00
public function importToLocal ( Request $request , StorageRepository $storageRepository ) : JsonResponse
{
$this -> authorizeWrite ( 'documenti' );
$validated = $request -> validate ([
'path' => 'required|string' ,
'basename' => 'required|string|max:255' ,
'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id' ,
'tipologia' => 'nullable|string|in:avatar,galleria,documento,statuto,altro' ,
'visibilita' => 'nullable|string|in:pubblico,individuo,gruppo,evento,mailing' ,
'visibilita_target_id' => 'nullable|integer' ,
]);
$path = $validated [ 'path' ];
$basename = $validated [ 'basename' ];
$filesystem = $this -> repoService -> buildFilesystem ( $storageRepository );
if ( ! $filesystem ) {
return response () -> json ([ 'success' => false , 'message' => 'Impossibile connettersi al repository remoto.' ], 500 );
}
$normalizedPath = $this -> repoService -> normalizePath ( $path );
2026-06-01 16:11:29 +02:00
if ( $storageRepository -> tipo === 'google_drive' ) {
try {
$fileData = $this -> repoService -> readGoogleDriveFile ( $storageRepository , $normalizedPath , $basename );
} catch ( \Exception $e ) {
Log :: error ( " StorageRepository importToLocal: readGoogleDriveFile failed for repo # { $storageRepository -> id } path ' { $path } ': " . $e -> getMessage ());
return response () -> json ([ 'success' => false , 'message' => 'Impossibile leggere il file da Google Drive: ' . $e -> getMessage ()], 500 );
}
} else {
try {
$fileData = [];
$fileData [ 'stream' ] = $filesystem -> readStream ( $normalizedPath );
$fileData [ 'mimeType' ] = null ;
try { $fileData [ 'mimeType' ] = $filesystem -> mimeType ( $normalizedPath ); } catch ( \Exception ) {}
$fileData [ 'fileSize' ] = null ;
try { $fileData [ 'fileSize' ] = $filesystem -> fileSize ( $normalizedPath ); } catch ( \Exception ) {}
$fileData [ 'basename' ] = $basename ;
} catch ( \Exception $e ) {
Log :: error ( " StorageRepository importToLocal: readStream failed for repo # { $storageRepository -> id } path ' { $path } ': " . $e -> getMessage ());
return response () -> json ([ 'success' => false , 'message' => 'File non trovato nel repository remoto o impossibile da leggere.' ], 404 );
}
2026-05-29 06:54:07 +02:00
}
2026-06-01 16:11:29 +02:00
$stream = $fileData [ 'stream' ];
2026-05-29 06:54:07 +02:00
if ( ! $stream ) {
return response () -> json ([ 'success' => false , 'message' => 'Impossibile leggere il file remoto.' ], 500 );
}
2026-06-01 16:11:29 +02:00
$basename = $fileData [ 'basename' ];
$mimeType = $fileData [ 'mimeType' ] ? ? null ;
$fileSize = $fileData [ 'fileSize' ] ? ? null ;
2026-05-29 06:54:07 +02:00
$disk = \App\Models\AppSetting :: getDocumentiStorageDisk ();
$storagePath = \App\Models\AppSetting :: getDocumentiStoragePath ();
$extension = strtolower ( pathinfo ( $basename , PATHINFO_EXTENSION ));
$nomeSenzaEstensione = pathinfo ( $basename , PATHINFO_FILENAME );
2026-06-01 16:11:29 +02:00
$uniqueName = preg_replace ( '/[^a-zA-Z0-9_\-\x80-\x{FF}]/u' , '_' , $nomeSenzaEstensione ) . '_' . time () . ( $extension ? '.' . $extension : '' );
2026-05-29 06:54:07 +02:00
$filePath = $storagePath . '/' . date ( 'Y/m/d' ) . '/' . $uniqueName ;
$stored = \Illuminate\Support\Facades\Storage :: disk ( $disk ) -> writeStream ( $filePath , $stream );
fclose ( $stream );
if ( ! $stored ) {
return response () -> json ([ 'success' => false , 'message' => 'Errore durante il salvataggio locale del file.' ], 500 );
}
$visibilita = $validated [ 'visibilita' ] ? ? 'pubblico' ;
$targetType = null ;
$targetId = null ;
if ( $visibilita === 'individuo' && ! empty ( $validated [ 'visibilita_target_id' ])) {
$targetType = \App\Models\Individuo :: class ;
$targetId = ( int ) $validated [ 'visibilita_target_id' ];
} elseif ( $visibilita === 'gruppo' && ! empty ( $validated [ 'visibilita_target_id' ])) {
$targetType = \App\Models\Gruppo :: class ;
$targetId = ( int ) $validated [ 'visibilita_target_id' ];
} elseif ( $visibilita === 'evento' && ! empty ( $validated [ 'visibilita_target_id' ])) {
$targetType = \App\Models\Evento :: class ;
$targetId = ( int ) $validated [ 'visibilita_target_id' ];
} elseif ( $visibilita === 'mailing' && ! empty ( $validated [ 'visibilita_target_id' ])) {
$targetType = \App\Models\MailingList :: class ;
$targetId = ( int ) $validated [ 'visibilita_target_id' ];
}
$documento = \App\Models\Documento :: create ([
'nome_file' => $nomeSenzaEstensione ,
'file_path' => $filePath ,
'storage_disk' => $disk ,
'tipo' => 'upload' ,
'tipologia' => $validated [ 'tipologia' ] ? ? 'documento' ,
'visibilita' => $visibilita ,
'visibilita_target_id' => $targetId ,
'visibilita_target_type' => $targetType ,
'mime_type' => $mimeType ,
'dimensione' => $fileSize ,
'user_id' => auth () -> id (),
'cartella_id' => $validated [ 'cartella_id' ] ? ? null ,
'repository_id' => null ,
]);
return response () -> json ([
'success' => true ,
'message' => 'File importato come documento locale: ' . $documento -> nome_file ,
'documento_id' => $documento -> id ,
]);
}
2026-05-28 13:25:47 +02:00
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 ());
}
}
2026-05-28 09:34:28 +02:00
}