final stage
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\BackupService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class BackupRunCommand extends Command
|
||||
{
|
||||
protected $signature = 'backup:run
|
||||
{--filename= : Custom filename prefix for the backup file}
|
||||
{--no-files : Exclude uploaded files from backup}
|
||||
{--no-env : Exclude .env from backup}';
|
||||
|
||||
protected $description = 'Esegue un backup completo del database, file e configurazione';
|
||||
|
||||
private BackupService $backupService;
|
||||
|
||||
public function __construct(BackupService $backupService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->backupService = $backupService;
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('Avvio backup...');
|
||||
|
||||
if ($this->option('no-files')) {
|
||||
$this->backupService->saveConfig(['backup_include_files' => false]);
|
||||
$this->warn('Files esclusi dal backup.');
|
||||
}
|
||||
if ($this->option('no-env')) {
|
||||
$this->backupService->saveConfig(['backup_include_env' => false]);
|
||||
$this->warn('.env escluso dal backup.');
|
||||
}
|
||||
|
||||
$result = $this->backupService->run();
|
||||
|
||||
if ($result['success']) {
|
||||
$this->info(' Backup completato con successo!');
|
||||
$this->line('File: ' . $result['filename']);
|
||||
$this->line('Dimensione: ' . $result['size_formatted']);
|
||||
$this->line('Percorso: ' . storage_path('app/backups/' . $result['filename']));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->error(' Backup fallito: ' . ($result['message'] ?? 'Errore sconosciuto'));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AppSetting;
|
||||
use App\Services\BackupService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class BackupController extends Controller
|
||||
{
|
||||
private BackupService $backupService;
|
||||
|
||||
public function __construct(BackupService $backupService)
|
||||
{
|
||||
$this->backupService = $backupService;
|
||||
}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$backups = $this->backupService->list();
|
||||
$config = $this->backupService->getConfig();
|
||||
|
||||
return view('admin.backup.index', compact('backups', 'config'));
|
||||
}
|
||||
|
||||
public function run(): RedirectResponse
|
||||
{
|
||||
$result = $this->backupService->run();
|
||||
|
||||
if ($result['success']) {
|
||||
return redirect()->route('admin.backup.index')
|
||||
->with('success', 'Backup completato: ' . ($result['filename'] ?? '') . ' (' . ($result['size_formatted'] ?? '') . ')');
|
||||
}
|
||||
|
||||
return redirect()->route('admin.backup.index')
|
||||
->with('error', $result['message'] ?? 'Errore durante il backup.');
|
||||
}
|
||||
|
||||
public function download(string $filename)
|
||||
{
|
||||
$filepath = $this->backupService->download($filename);
|
||||
|
||||
if (!$filepath) {
|
||||
return redirect()->route('admin.backup.index')
|
||||
->with('error', 'File di backup non trovato.');
|
||||
}
|
||||
|
||||
return response()->download($filepath)->deleteFileAfterSend(false);
|
||||
}
|
||||
|
||||
public function destroy(string $filename): RedirectResponse
|
||||
{
|
||||
if ($this->backupService->delete($filename)) {
|
||||
return redirect()->route('admin.backup.index')
|
||||
->with('success', 'Backup "' . $filename . '" eliminato.');
|
||||
}
|
||||
|
||||
return redirect()->route('admin.backup.index')
|
||||
->with('error', 'Impossibile eliminare il backup.');
|
||||
}
|
||||
|
||||
public function updateConfig(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'backup_path' => 'nullable|string|max:255',
|
||||
'backup_retention_days' => 'nullable|integer|min:1|max:365',
|
||||
'backup_include_files' => 'nullable|boolean',
|
||||
'backup_include_env' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
$this->backupService->saveConfig($validated);
|
||||
|
||||
return redirect()->route('admin.backup.index')
|
||||
->with('success', 'Configurazione backup salvata.');
|
||||
}
|
||||
|
||||
public function toggleAuto(): RedirectResponse
|
||||
{
|
||||
$enabled = !AppSetting::getSetting('backup_auto_enabled', false);
|
||||
$setting = AppSetting::first() ?? new AppSetting();
|
||||
$setting->backup_auto_enabled = $enabled;
|
||||
$setting->save();
|
||||
|
||||
$msg = $enabled ? 'Backup automatico abilitato.' : 'Backup automatico disabilitato.';
|
||||
return redirect()->route('admin.backup.index')->with('success', $msg);
|
||||
}
|
||||
|
||||
public function saveAutoConfig(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'backup_auto_enabled' => 'nullable|boolean',
|
||||
'backup_auto_frequency' => 'nullable|in:daily,weekly,monthly',
|
||||
'backup_auto_hour' => 'nullable|integer|min:0|max:23',
|
||||
]);
|
||||
|
||||
$setting = AppSetting::first() ?? new AppSetting();
|
||||
$setting->backup_auto_enabled = !empty($validated['backup_auto_enabled']);
|
||||
$setting->backup_auto_frequency = $validated['backup_auto_frequency'] ?? 'daily';
|
||||
$setting->backup_auto_hour = $validated['backup_auto_hour'] ?? 3;
|
||||
$setting->save();
|
||||
|
||||
return redirect()->route('admin.backup.index')
|
||||
->with('success', 'Configurazione backup automatico salvata.');
|
||||
}
|
||||
|
||||
public function downloadMigrationScript(): JsonResponse
|
||||
{
|
||||
$script = view('admin.backup.migration-script')->render();
|
||||
|
||||
return response()->json(['script' => $script]);
|
||||
}
|
||||
}
|
||||
@@ -242,23 +242,43 @@ class StorageRepositoryController extends Controller
|
||||
|
||||
$normalizedPath = $this->repoService->normalizePath($path);
|
||||
|
||||
try {
|
||||
$stream = $filesystem->readStream($normalizedPath);
|
||||
} 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);
|
||||
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_\-\x{80}-\x{FF}]/u', '_', $nomeSenzaEstensione) . '_' . time() . ($extension ? '.' . $extension : '');
|
||||
$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);
|
||||
@@ -268,11 +288,6 @@ class StorageRepositoryController extends Controller
|
||||
return response()->json(['success' => false, 'message' => 'Errore durante il salvataggio locale del file.'], 500);
|
||||
}
|
||||
|
||||
$mimeType = null;
|
||||
try { $mimeType = $filesystem->mimeType($path); } catch (\Exception) {}
|
||||
$fileSize = null;
|
||||
try { $fileSize = $filesystem->fileSize($path); } catch (\Exception) {}
|
||||
|
||||
$visibilita = $validated['visibilita'] ?? 'pubblico';
|
||||
$targetType = null;
|
||||
$targetId = null;
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AppSetting;
|
||||
use App\Models\Notifica;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use RuntimeException;
|
||||
use ZipArchive;
|
||||
|
||||
class BackupService
|
||||
{
|
||||
private string $backupDir;
|
||||
private string $tempDir;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$basePath = AppSetting::getSetting('backup_path', 'backups');
|
||||
$this->backupDir = storage_path('app/' . ltrim($basePath, '/'));
|
||||
$this->tempDir = storage_path('app/backup-temp');
|
||||
}
|
||||
|
||||
public function getBackupDir(): string
|
||||
{
|
||||
return $this->backupDir;
|
||||
}
|
||||
|
||||
public function list(): array
|
||||
{
|
||||
if (!is_dir($this->backupDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = File::files($this->backupDir);
|
||||
$backups = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file->getExtension() !== 'zip') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$manifest = $this->readManifest($file->getPathname());
|
||||
$backups[] = [
|
||||
'filename' => $file->getFilename(),
|
||||
'pathname' => $file->getPathname(),
|
||||
'size' => $file->getSize(),
|
||||
'size_formatted' => $this->formatBytes($file->getSize()),
|
||||
'last_modified' => $file->getMTime(),
|
||||
'manifest' => $manifest,
|
||||
];
|
||||
}
|
||||
|
||||
usort($backups, fn(array $a, array $b) => $b['last_modified'] - $a['last_modified']);
|
||||
|
||||
return $backups;
|
||||
}
|
||||
|
||||
public function run(): array
|
||||
{
|
||||
$result = ['success' => true, 'message' => '', 'filename' => ''];
|
||||
|
||||
try {
|
||||
File::ensureDirectoryExists($this->tempDir, 0755, true);
|
||||
File::ensureDirectoryExists($this->backupDir, 0755, true);
|
||||
|
||||
$this->cleanTemp();
|
||||
|
||||
$timestamp = now()->format('Y-m-d_H-i-s');
|
||||
$appName = str_replace(' ', '_', AppSetting::getAppName() ?? 'app');
|
||||
$filename = $appName . '_' . $timestamp . '.zip';
|
||||
$filepath = $this->backupDir . '/' . $filename;
|
||||
|
||||
$steps = [];
|
||||
|
||||
$steps[] = $this->backupDatabase();
|
||||
$steps[] = $this->backupEnv();
|
||||
$steps[] = $this->backupFiles();
|
||||
$this->writeManifest($timestamp);
|
||||
|
||||
$zipResult = $this->createZip($filepath);
|
||||
if (!$zipResult) {
|
||||
throw new RuntimeException('Errore durante la creazione del file ZIP.');
|
||||
}
|
||||
|
||||
$this->cleanTemp();
|
||||
|
||||
$size = file_exists($filepath) ? filesize($filepath) : 0;
|
||||
|
||||
Log::info("Backup completato: {$filename} (" . $this->formatBytes($size) . ")");
|
||||
|
||||
$this->cleanupOldBackups();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Backup completato con successo.',
|
||||
'filename' => $filename,
|
||||
'size' => $size,
|
||||
'size_formatted' => $this->formatBytes($size),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
$this->cleanTemp();
|
||||
Log::error('Backup fallito: ' . $e->getMessage());
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Backup fallito: ' . $e->getMessage(),
|
||||
'filename' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function download(string $filename): ?string
|
||||
{
|
||||
$filepath = $this->backupDir . '/' . basename($filename);
|
||||
return file_exists($filepath) ? $filepath : null;
|
||||
}
|
||||
|
||||
public function delete(string $filename): bool
|
||||
{
|
||||
$filepath = $this->backupDir . '/' . basename($filename);
|
||||
if (file_exists($filepath)) {
|
||||
unlink($filepath);
|
||||
Log::info("Backup eliminato: {$filename}");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return [
|
||||
'backup_path' => AppSetting::getSetting('backup_path', 'backups'),
|
||||
'backup_retention_days' => (int) AppSetting::getSetting('backup_retention_days', 30),
|
||||
'backup_include_files' => (bool) AppSetting::getSetting('backup_include_files', true),
|
||||
'backup_include_env' => (bool) AppSetting::getSetting('backup_include_env', true),
|
||||
];
|
||||
}
|
||||
|
||||
public function saveConfig(array $data): void
|
||||
{
|
||||
$settings = AppSetting::first() ?? new AppSetting();
|
||||
$settings->backup_path = trim($data['backup_path'] ?? 'backups', '/');
|
||||
$settings->backup_retention_days = max(1, (int) ($data['backup_retention_days'] ?? 30));
|
||||
$settings->backup_include_files = !empty($data['backup_include_files']);
|
||||
$settings->backup_include_env = !empty($data['backup_include_env']);
|
||||
$settings->save();
|
||||
}
|
||||
|
||||
private function backupDatabase(): string
|
||||
{
|
||||
$dbName = config('database.connections.mysql.database');
|
||||
$dbUser = config('database.connections.mysql.username');
|
||||
$dbPass = config('database.connections.mysql.password');
|
||||
$dbHost = config('database.connections.mysql.host');
|
||||
$dbPort = config('database.connections.mysql.port');
|
||||
|
||||
$sqlFile = $this->tempDir . '/database.sql';
|
||||
|
||||
$command = sprintf(
|
||||
'mysqldump --host=%s --port=%s --user=%s --password=%s --routines --events --single-transaction --quick %s > %s 2>&1',
|
||||
escapeshellarg($dbHost),
|
||||
escapeshellarg($dbPort),
|
||||
escapeshellarg($dbUser),
|
||||
escapeshellarg($dbPass),
|
||||
escapeshellarg($dbName),
|
||||
escapeshellarg($sqlFile)
|
||||
);
|
||||
|
||||
$output = null;
|
||||
$exitCode = null;
|
||||
exec($command, $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0 || !file_exists($sqlFile) || filesize($sqlFile) === 0) {
|
||||
throw new RuntimeException('mysqldump fallito: ' . implode("\n", (array) $output));
|
||||
}
|
||||
|
||||
return 'database.sql (' . $this->formatBytes(filesize($sqlFile)) . ')';
|
||||
}
|
||||
|
||||
private function backupEnv(): string
|
||||
{
|
||||
if (!AppSetting::getSetting('backup_include_env', true)) {
|
||||
return '.env (escluso)';
|
||||
}
|
||||
|
||||
$envPath = base_path('.env');
|
||||
$envKeyPath = base_path('.env.key');
|
||||
|
||||
if (file_exists($envPath)) {
|
||||
copy($envPath, $this->tempDir . '/.env');
|
||||
}
|
||||
if (file_exists($envKeyPath)) {
|
||||
copy($envKeyPath, $this->tempDir . '/.env.key');
|
||||
}
|
||||
|
||||
return '.env copiato';
|
||||
}
|
||||
|
||||
private function backupFiles(): string
|
||||
{
|
||||
if (!AppSetting::getSetting('backup_include_files', true)) {
|
||||
return 'files (esclusi)';
|
||||
}
|
||||
|
||||
$filesDir = AppSetting::getDocumentiStorageAbsolutePath();
|
||||
$targetDir = $this->tempDir . '/files';
|
||||
|
||||
if (!is_dir($filesDir)) {
|
||||
return 'files (cartella non trovata)';
|
||||
}
|
||||
|
||||
File::ensureDirectoryExists($targetDir, 0755, true);
|
||||
|
||||
File::copyDirectory($filesDir, $targetDir);
|
||||
|
||||
return 'files copiati';
|
||||
}
|
||||
|
||||
private function writeManifest(string $timestamp): void
|
||||
{
|
||||
$manifest = [
|
||||
'created_at' => $timestamp,
|
||||
'app_version' => AppSetting::getAppVersion() ?? 'unknown',
|
||||
'app_name' => AppSetting::getAppName() ?? 'Glastree',
|
||||
'db_driver' => config('database.default'),
|
||||
'db_name' => config('database.connections.mysql.database'),
|
||||
'php_version' => PHP_VERSION,
|
||||
'laravel_version' => app()->version(),
|
||||
];
|
||||
|
||||
File::put($this->tempDir . '/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private function readManifest(string $zipPath): ?array
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zipPath) === true) {
|
||||
$manifestContent = $zip->getFromName('manifest.json');
|
||||
$zip->close();
|
||||
if ($manifestContent !== false) {
|
||||
return json_decode($manifestContent, true);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function createZip(string $filepath): bool
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($filepath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$files = File::allFiles($this->tempDir);
|
||||
foreach ($files as $file) {
|
||||
$relativePath = substr($file->getPathname(), strlen($this->tempDir) + 1);
|
||||
$zip->addFile($file->getPathname(), $relativePath);
|
||||
}
|
||||
|
||||
return $zip->close();
|
||||
}
|
||||
|
||||
private function cleanTemp(): void
|
||||
{
|
||||
if (is_dir($this->tempDir)) {
|
||||
File::deleteDirectory($this->tempDir);
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupOldBackups(): void
|
||||
{
|
||||
$retentionDays = (int) AppSetting::getSetting('backup_retention_days', 30);
|
||||
$cutoff = now()->subDays($retentionDays)->timestamp;
|
||||
|
||||
foreach (File::files($this->backupDir) as $file) {
|
||||
if ($file->getExtension() === 'zip' && $file->getMTime() < $cutoff) {
|
||||
unlink($file->getPathname());
|
||||
Log::info("Backup eliminato per retention: " . $file->getFilename());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function formatBytes(int $bytes): string
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$i = 0;
|
||||
while ($bytes >= 1024 && $i < count($units) - 1) {
|
||||
$bytes /= 1024;
|
||||
$i++;
|
||||
}
|
||||
return round($bytes, 2) . ' ' . $units[$i];
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,11 @@ class StorageRepositoryService
|
||||
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.'];
|
||||
}
|
||||
|
||||
$apiError = $this->checkGoogleDriveApiStatus($config);
|
||||
if ($apiError) {
|
||||
return ['success' => false, 'message' => $apiError];
|
||||
}
|
||||
}
|
||||
|
||||
$filesystem = $this->buildFilesystem($repo);
|
||||
@@ -61,6 +66,42 @@ class StorageRepositoryService
|
||||
}
|
||||
}
|
||||
|
||||
private function checkGoogleDriveApiStatus(array $config): ?string
|
||||
{
|
||||
try {
|
||||
$client = new \Google_Client();
|
||||
$client->setClientId($config['client_id']);
|
||||
$client->setClientSecret($config['client_secret']);
|
||||
$client->addScope(\Google_Service_Drive::DRIVE);
|
||||
$token = $client->refreshToken($config['refresh_token']);
|
||||
$client->setAccessToken($token);
|
||||
|
||||
$service = new \Google_Service_Drive($client);
|
||||
|
||||
$rootId = $config['root_folder_id'] ?? 'root';
|
||||
$service->files->get($rootId, ['fields' => 'id']);
|
||||
|
||||
return null;
|
||||
} catch (\Google\Service\Exception $e) {
|
||||
$errors = $e->getErrors();
|
||||
$reason = $errors[0]['reason'] ?? '';
|
||||
$message = $errors[0]['message'] ?? $e->getMessage();
|
||||
|
||||
if ($reason === 'accessNotConfigured' || str_contains($message, 'Drive API has not been used')) {
|
||||
$projectNumber = explode('-', $config['client_id'] ?? '')[0] ?? '';
|
||||
return 'Google Drive API non abilitata. Vai su https://console.developers.google.com/apis/api/drive.googleapis.com/overview?project=' . $projectNumber . ' e clicca "Enable".';
|
||||
}
|
||||
|
||||
if ($e->getCode() === 404) {
|
||||
return 'Cartella root non trovata (ID: ' . ($config['root_folder_id'] ?? 'root') . '). Verifica che il folder ID sia corretto.';
|
||||
}
|
||||
|
||||
return 'Errore API Google Drive: ' . $message;
|
||||
} catch (\Exception $e) {
|
||||
return 'Errore di connessione Google Drive: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function listContents(StorageRepository $repo, string $path = '/'): array
|
||||
{
|
||||
try {
|
||||
@@ -198,4 +239,126 @@ class StorageRepositoryService
|
||||
|
||||
return new Filesystem($adapter);
|
||||
}
|
||||
|
||||
public function readGoogleDriveFile(StorageRepository $repo, string $normalizedPath, string $basename): array
|
||||
{
|
||||
$config = $repo->getDecryptedConfig();
|
||||
|
||||
$client = new \Google_Client();
|
||||
$client->setClientId($config['client_id']);
|
||||
$client->setClientSecret($config['client_secret']);
|
||||
$client->addScope(\Google_Service_Drive::DRIVE);
|
||||
$token = $client->refreshToken($config['refresh_token']);
|
||||
$client->setAccessToken($token);
|
||||
|
||||
$service = new \Google_Service_Drive($client);
|
||||
$rootFolderId = $config['root_folder_id'] ?? 'root';
|
||||
|
||||
$fileId = $this->findGoogleDriveFileId($service, $rootFolderId, $normalizedPath);
|
||||
$file = $service->files->get($fileId, ['fields' => 'id,name,mimeType,size']);
|
||||
|
||||
$mimeType = $file->getMimeType();
|
||||
|
||||
if ($mimeType && str_starts_with($mimeType, 'application/vnd.google-apps.')) {
|
||||
return $this->exportGoogleDriveDoc($service, $fileId, $mimeType, $basename);
|
||||
}
|
||||
|
||||
$response = $service->files->get($fileId, ['alt' => 'media']);
|
||||
$body = $response->getBody();
|
||||
$stream = $body instanceof \Psr\Http\Message\StreamInterface ? $body->detach() : null;
|
||||
|
||||
if (!$stream) {
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
fwrite($stream, (string) $body);
|
||||
rewind($stream);
|
||||
}
|
||||
|
||||
$fileSize = $file->getSize();
|
||||
|
||||
return [
|
||||
'stream' => $stream,
|
||||
'mimeType' => $mimeType,
|
||||
'basename' => $basename,
|
||||
'fileSize' => $fileSize !== null ? (int) $fileSize : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function exportGoogleDriveDoc(\Google_Service_Drive $service, string $fileId, string $googleMimeType, string $originalBasename): array
|
||||
{
|
||||
$exportMap = [
|
||||
'application/vnd.google-apps.document' => [
|
||||
'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'ext' => 'docx',
|
||||
],
|
||||
'application/vnd.google-apps.spreadsheet' => [
|
||||
'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'ext' => 'xlsx',
|
||||
],
|
||||
'application/vnd.google-apps.presentation' => [
|
||||
'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'ext' => 'pptx',
|
||||
],
|
||||
'application/vnd.google-apps.drawing' => [
|
||||
'mime' => 'image/png',
|
||||
'ext' => 'png',
|
||||
],
|
||||
];
|
||||
|
||||
$format = $exportMap[$googleMimeType] ?? ['mime' => 'application/pdf', 'ext' => 'pdf'];
|
||||
|
||||
$response = $service->files->export($fileId, $format['mime'], ['alt' => 'media']);
|
||||
$body = $response->getBody();
|
||||
$stream = $body instanceof \Psr\Http\Message\StreamInterface ? $body->detach() : null;
|
||||
|
||||
if (!$stream) {
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
fwrite($stream, (string) $body);
|
||||
rewind($stream);
|
||||
}
|
||||
|
||||
$nameWithoutExt = pathinfo($originalBasename, PATHINFO_FILENAME);
|
||||
$newBasename = $nameWithoutExt . '.' . $format['ext'];
|
||||
|
||||
Log::info("StorageRepository: exported Google Drive file {$fileId} from {$googleMimeType} to {$format['mime']} as {$newBasename}");
|
||||
|
||||
return [
|
||||
'stream' => $stream,
|
||||
'mimeType' => $format['mime'],
|
||||
'basename' => $newBasename,
|
||||
'fileSize' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function findGoogleDriveFileId(\Google_Service_Drive $service, string $rootFolderId, string $path): string
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
if ($path === '') {
|
||||
return $rootFolderId;
|
||||
}
|
||||
|
||||
$parts = explode('/', $path);
|
||||
$currentParentId = $rootFolderId;
|
||||
|
||||
foreach ($parts as $name) {
|
||||
$optParams = [
|
||||
'q' => sprintf("trashed = false and '%s' in parents and name = '%s'",
|
||||
str_replace("'", "\\'", $currentParentId),
|
||||
str_replace("'", "\\'", $name)
|
||||
),
|
||||
'fields' => 'files(id, name, mimeType)',
|
||||
'pageSize' => 1,
|
||||
];
|
||||
|
||||
$results = $service->files->listFiles($optParams);
|
||||
$files = $results->getFiles();
|
||||
|
||||
if (empty($files)) {
|
||||
throw new \RuntimeException("Elemento non trovato su Google Drive: {$name} (path: {$path})");
|
||||
}
|
||||
|
||||
$currentParentId = $files[0]->getId();
|
||||
}
|
||||
|
||||
return $currentParentId;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user