From ef2bae409da31cb3e8bc21c1aa0e8444a91ff1f2 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sat, 2 Aug 2025 01:35:56 +0200 Subject: [PATCH] add simple plugin downloader --- app/Facades/Plugins.php | 3 + .../Admin/Resources/PluginResource.php | 58 ++++++++++++++++++- app/Services/Helpers/PluginService.php | 35 +++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/app/Facades/Plugins.php b/app/Facades/Plugins.php index fecd72dc7..673a5fad7 100644 --- a/app/Facades/Plugins.php +++ b/app/Facades/Plugins.php @@ -3,6 +3,7 @@ namespace App\Facades; use App\Models\Plugin; +use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Facade; use App\Services\Helpers\PluginService; use Filament\Panel; @@ -13,6 +14,8 @@ use Filament\Panel; * @method static void requireComposerPackages(Plugin $plugin) * @method static void runPluginMigrations(Plugin $plugin) * @method static void installPlugin(Plugin $plugin) + * @method static void downloadPluginFromFile(UploadedFile $file) + * @method static void downloadPluginFromUrl(string $url) * @method static void enablePlugin(string|Plugin $plugin) * @method static void disablePlugin(string|Plugin $plugin) * @method static void updateLoadOrder(array $order) diff --git a/app/Filament/Admin/Resources/PluginResource.php b/app/Filament/Admin/Resources/PluginResource.php index b867eb6da..bd70339a3 100644 --- a/app/Filament/Admin/Resources/PluginResource.php +++ b/app/Filament/Admin/Resources/PluginResource.php @@ -5,11 +5,17 @@ namespace App\Filament\Admin\Resources; use App\Facades\Plugins; use App\Filament\Admin\Resources\PluginResource\Pages\ListPlugins; use App\Models\Plugin; +use Exception; +use Filament\Forms\Components\FileUpload; +use Filament\Forms\Components\Tabs; +use Filament\Forms\Components\Tabs\Tab; +use Filament\Forms\Components\TextInput; use Filament\Notifications\Notification; use Filament\Resources\Resource; use Filament\Tables\Actions\Action; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; +use Illuminate\Http\UploadedFile; class PluginResource extends Resource { @@ -111,7 +117,57 @@ class PluginResource extends Resource }), ]) ->headerActions([ - // TODO: "import" button + Action::make('download') + ->authorize(fn (Plugin $plugin) => auth()->user()->can('create', $plugin)) + ->icon('tabler-download') + ->form([ + Tabs::make('Tabs') + ->contained(false) + ->tabs([ + Tab::make('From File') + ->icon('tabler-file-upload') + ->schema([ + FileUpload::make('file') + ->acceptedFileTypes(['application/zip', 'application/zip-compressed', 'application/x-zip-compressed']) + ->preserveFilenames() + ->previewable(false) + ->storeFiles(false), + ]), + Tab::make('From URL') + ->icon('tabler-world-upload') + ->schema([ + TextInput::make('url') + ->url() + ->endsWith('.zip'), + ]), + ]), + ]) + ->action(function ($data) { + try { + if ($data['file'] instanceof UploadedFile) { + Plugins::downloadPluginFromFile($data['file']); + } + + if (is_string($data['url'])) { + Plugins::downloadPluginFromUrl($data['url']); + } + + redirect(ListPlugins::getUrl()); + + Notification::make() + ->success() + ->title('Plugin downloaded') + ->send(); + } catch (Exception $exception) { + report($exception); + + Notification::make() + ->danger() + ->title('Could not download plugin.') + ->body($exception->getMessage()) + ->send(); + } + }), ]) ->emptyStateIcon('tabler-packages') ->emptyStateDescription('') diff --git a/app/Services/Helpers/PluginService.php b/app/Services/Helpers/PluginService.php index 9e792e9fd..1e9456a0e 100644 --- a/app/Services/Helpers/PluginService.php +++ b/app/Services/Helpers/PluginService.php @@ -3,6 +3,7 @@ namespace App\Services\Helpers; use App\Enums\PluginStatus; +use App\Exceptions\Service\InvalidFileUploadException; use App\Models\Plugin; use Composer\Autoload\ClassLoader; use Exception; @@ -10,10 +11,13 @@ use Filament\Panel; use Illuminate\Console\Application as ConsoleApplication; use Illuminate\Console\Command; use Illuminate\Foundation\Application; +use Illuminate\Http\UploadedFile; use Illuminate\Support\Composer; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\File; use Illuminate\Support\ServiceProvider; +use Spatie\TemporaryDirectory\TemporaryDirectory; +use ZipArchive; class PluginService { @@ -172,6 +176,37 @@ class PluginService } } + public function downloadPluginFromFile(UploadedFile $file): void + { + $zip = new ZipArchive(); + + if (!$zip->open($file->getPathname())) { + throw new Exception('Could not open zip file.'); + } + + $pluginName = str($file->getClientOriginalName())->before('.zip')->toString(); + $extractPath = $zip->locateName($pluginName) ? base_path('plugins') : plugin_path($pluginName); + + if (!$zip->extractTo($extractPath)) { + throw new Exception('Could not extract zip file.'); + } + + $zip->close(); + } + + public function downloadPluginFromUrl(string $url): void + { + $info = pathinfo($url); + $tmpDir = TemporaryDirectory::make()->deleteWhenDestroyed(); + $tmpPath = $tmpDir->path($info['basename']); + + if (!file_put_contents($tmpPath, file_get_contents($url))) { + throw new InvalidFileUploadException('Could not write temporary file.'); + } + + $this->downloadPluginFromFile(new UploadedFile($tmpPath, $info['basename'], 'application/zip')); + } + public function enablePlugin(string|Plugin $plugin): void { $this->setStatus($plugin, PluginStatus::Enabled);