437 lines
17 KiB
PHP
437 lines
17 KiB
PHP
<?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;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\View\View;
|
|
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',
|
|
]);
|
|
|
|
$validated['config'] = $this->repoService->encryptSensitiveConfig(
|
|
$validated['config'],
|
|
$validated['tipo'],
|
|
$storageRepository->config
|
|
);
|
|
|
|
$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', '/');
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 . '"',
|
|
]);
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
$stream = $fileData['stream'];
|
|
if (!$stream) {
|
|
return response()->json(['success' => false, 'message' => 'Impossibile leggere il file remoto.'], 500);
|
|
}
|
|
|
|
$basename = $fileData['basename'];
|
|
$mimeType = $fileData['mimeType'] ?? null;
|
|
$fileSize = $fileData['fileSize'] ?? null;
|
|
|
|
$disk = \App\Models\AppSetting::getDocumentiStorageDisk();
|
|
$storagePath = \App\Models\AppSetting::getDocumentiStoragePath();
|
|
|
|
$extension = strtolower(pathinfo($basename, PATHINFO_EXTENSION));
|
|
$nomeSenzaEstensione = pathinfo($basename, PATHINFO_FILENAME);
|
|
$uniqueName = preg_replace('/[^a-zA-Z0-9_\-\x80-\x{FF}]/u', '_', $nomeSenzaEstensione) . '_' . time() . ($extension ? '.' . $extension : '');
|
|
$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,
|
|
]);
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|