From f81f44a00423eca16db965336f82286a1e41538e Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 25 Sep 2026 20:55:44 +0200 Subject: [PATCH] Upstream pelican/panel state of the touched files before 4334d5141e --- .../Application/Plugins/PluginController.php | 191 ++++++++++++++++++ app/Services/Helpers/PluginService.php | 2 +- .../Services/PluginServiceTest.php | 58 ------ 3 files changed, 192 insertions(+), 59 deletions(-) create mode 100644 app/Http/Controllers/Api/Application/Plugins/PluginController.php diff --git a/app/Http/Controllers/Api/Application/Plugins/PluginController.php b/app/Http/Controllers/Api/Application/Plugins/PluginController.php new file mode 100644 index 0000000..22828e2 --- /dev/null +++ b/app/Http/Controllers/Api/Application/Plugins/PluginController.php @@ -0,0 +1,191 @@ + + */ + public function index(ReadPluginRequest $request): array + { + $plugins = QueryBuilder::for(Plugin::class) + ->allowedFilters('id', 'name', 'author', 'category') + ->allowedSorts('id', 'name', 'author', 'category') + ->paginate($request->query('per_page') ?? 10); + + return $this->response->collection($plugins) + ->transformWith(PluginData::class) + ->toArray(); + } + + /** + * View plugin + * + * Return a single plugin. + * + * @return array + */ + public function view(ReadPluginRequest $request, Plugin $plugin): array + { + return $this->response->item($plugin) + ->transformWith(PluginData::class) + ->toArray(); + } + + /** + * Import plugin (file) + * + * Imports a new plugin file. + * + * @throws Exception + */ + public function importFile(WritePluginRequest $request): Response + { + throw_unless($request->hasFile('plugin'), new PanelException("No 'plugin' file in request")); + + $this->pluginService->downloadPluginFromFile($request->file('plugin')); + + return new Response('', Response::HTTP_CREATED); + } + + /** + * Import plugin (url) + * + * Imports a new plugin from an url. + * + * @throws Exception + */ + public function importUrl(ImportFilePluginRequest $request): Response + { + $this->pluginService->downloadPluginFromUrl($request->input('url')); + + return new Response('', Response::HTTP_CREATED); + } + + /** + * Install plugin + * + * Installs and enables a plugin. + * + * @return array + * + * @throws Exception + */ + public function install(WritePluginRequest $request, Plugin $plugin): array + { + throw_if($plugin->status !== PluginStatus::NotInstalled, new PanelException('Plugin is already installed')); + + $this->pluginService->installPlugin($plugin); + + return $this->response->item($plugin) + ->transformWith(PluginData::class) + ->toArray(); + } + + /** + * Update plugin + * + * Downloads and installs an update for a plugin. Will throw if no update is available. + * + * @return array + * + * @throws Exception + */ + public function update(WritePluginRequest $request, Plugin $plugin): array + { + throw_unless($plugin->isUpdateAvailable(), new PanelException("Plugin doesn't need updating")); + + $this->pluginService->updatePlugin($plugin); + + return $this->response->item($plugin) + ->transformWith(PluginData::class) + ->toArray(); + } + + /** + * Uninstall plugin + * + * Uninstalls a plugin. Optionally it will delete the plugin folder too. + * + * @return array + * + * @throws Exception + */ + public function uninstall(UninstallPluginRequest $request, Plugin $plugin): array + { + throw_if($plugin->status === PluginStatus::NotInstalled, new PanelException('Plugin is not installed')); + + $this->pluginService->uninstallPlugin($plugin, $request->boolean('delete')); + + return $this->response->item($plugin) + ->transformWith(PluginData::class) + ->toArray(); + } + + /** + * Enable plugin + * + * Enables a plugin. + * + * @return array + * + * @throws Exception + */ + public function enable(WritePluginRequest $request, Plugin $plugin): array + { + throw_unless($plugin->canEnable(), new PanelException("Plugin can't be enabled")); + + $this->pluginService->enablePlugin($plugin); + + return $this->response->item($plugin) + ->transformWith(PluginData::class) + ->toArray(); + } + + /** + * Disable plugin + * + * Disables a plugin. + * + * @return array + * + * @throws Exception + */ + public function disable(WritePluginRequest $request, Plugin $plugin): array + { + throw_unless($plugin->canDisable(), new PanelException("Plugin can't be disabled")); + + $this->pluginService->disablePlugin($plugin); + + return $this->response->item($plugin) + ->transformWith(PluginData::class) + ->toArray(); + } +} diff --git a/app/Services/Helpers/PluginService.php b/app/Services/Helpers/PluginService.php index fb90ba7..f794155 100644 --- a/app/Services/Helpers/PluginService.php +++ b/app/Services/Helpers/PluginService.php @@ -321,7 +321,7 @@ class PluginService $this->downloadPluginFromUrl($downloadUrl, $plugin->id); Plugin::refreshRows(); - $plugin = Plugin::findOrFail($plugin->id); + $plugin = $plugin->refresh(); $this->installPlugin($plugin, false); diff --git a/tests/Integration/Services/PluginServiceTest.php b/tests/Integration/Services/PluginServiceTest.php index 66d3805..3d0c38f 100644 --- a/tests/Integration/Services/PluginServiceTest.php +++ b/tests/Integration/Services/PluginServiceTest.php @@ -2,8 +2,6 @@ namespace App\Tests\Integration\Services; -use App\Enums\PluginStatus; -use App\Models\Plugin; use App\Services\Helpers\PluginService; use App\Tests\Integration\IntegrationTestCase; use Exception; @@ -11,7 +9,6 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; -use Illuminate\Support\Facades\Process; use PHPUnit\Framework\Attributes\DataProvider; use Spatie\TemporaryDirectory\TemporaryDirectory; use ZipArchive; @@ -46,9 +43,6 @@ class PluginServiceTest extends IntegrationTestCase File::deleteDirectory($leftover); } - // Don't leak the in-memory Sushi rows of the plugins removed above into later tests. - Plugin::refreshRows(); - // TemporaryDirectory::make() does not clean up on its own; remove the upload fixtures. foreach ($this->uploadDirs as $dir) { $dir->delete(); @@ -394,44 +388,6 @@ class PluginServiceTest extends IntegrationTestCase $this->assertDirectoryDoesNotExist(plugin_path('.test-clean-plugin.bak')); } - public function test_update_downloads_and_reinstalls_the_new_version(): void - { - $this->importedPlugins[] = 'test-update-plugin'; - - // Updates are resolved against the panel version, which is null on canary. - config()->set('app.version', '1.0.0'); - Process::fake(); - - $updateUrl = 'https://example.test/test-update-plugin/update.json'; - $downloadUrl = 'https://example.test/test-update-plugin/2.0.0.zip'; - - $this->service->downloadPluginFromFile($this->makeUpload('test-update-plugin.zip', [ - 'test-update-plugin/plugin.json' => $this->updatableManifest('test-update-plugin', '1.0.0', $updateUrl), - ])); - - $update = file_get_contents($this->makeUpload('2.0.0.zip', [ - 'test-update-plugin/plugin.json' => $this->updatableManifest('test-update-plugin', '2.0.0', $updateUrl), - ])->getPathname()); - - Http::fake([ - $updateUrl => Http::response(['*' => ['version' => '2.0.0', 'download_url' => $downloadUrl]]), - $downloadUrl => Http::response($update), - ]); - - Plugin::refreshRows(); - $plugin = Plugin::findOrFail('test-update-plugin'); - - $this->service->updatePlugin($plugin); - - $this->assertSame('2.0.0', $this->installedVersion('test-update-plugin')); - - // installPlugin() ran against the reloaded row and recorded the plugin as installed but disabled. - $this->assertSame(PluginStatus::Disabled->value, File::json(plugin_path('test-update-plugin', 'plugin.json'))['meta']['status']); - - Plugin::refreshRows(); - $this->assertSame('2.0.0', Plugin::findOrFail('test-update-plugin')->version); - } - private function installedVersion(string $id): string { return File::json(plugin_path($id, 'plugin.json'))['version']; @@ -442,20 +398,6 @@ class PluginServiceTest extends IntegrationTestCase return json_encode(['id' => $id, 'version' => $version]); } - private function updatableManifest(string $id, string $version, string $updateUrl): string - { - return json_encode([ - 'id' => $id, - 'name' => 'Test Update Plugin', - 'author' => 'Pelican', - 'version' => $version, - 'category' => 'plugin', - 'update_url' => $updateUrl, - 'namespace' => 'TestUpdatePlugin', - 'class' => 'TestUpdatePlugin', - ]); - } - /** * Build a zip on disk from the given [path => contents] map and wrap it in an UploadedFile. *