Skip to content

Commit 817fe08

Browse files
simonhampclaude
andcommitted
Show license preview page for admins and plugin owners
Allow admins and plugin authors to view the license page for paid plugins before they are approved, matching the existing preview behavior on the plugin show page. Adds an amber preview banner explaining the plugin's visibility status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2f6a574 commit 817fe08

3 files changed

Lines changed: 193 additions & 5 deletions

File tree

app/Http/Controllers/PluginDirectoryController.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,23 @@ public function license(string $vendor, string $package): View
8585
{
8686
$plugin = Plugin::findByVendorPackageOrFail($vendor, $package);
8787

88-
abort_unless($plugin->isApproved(), 404);
88+
$user = Auth::user();
89+
90+
$isAdmin = $user?->isAdmin() ?? false;
91+
$isOwner = $user && $plugin->user_id === $user->id;
92+
8993
abort_unless($plugin->isPaid(), 404);
9094
abort_unless($plugin->license_html, 404);
95+
abort_unless(($plugin->isApproved() && $plugin->is_active) || $isAdmin || $isOwner, 404);
9196

92-
$user = Auth::user();
93-
94-
// For paid plugins, check if user has an accessible price
95-
if (! $plugin->hasAccessiblePriceFor($user)) {
97+
// For paid plugins, check if user has an accessible price (admins and owners bypass)
98+
if (! $isAdmin && ! $isOwner && ! $plugin->hasAccessiblePriceFor($user)) {
9699
abort(404);
97100
}
98101

99102
return view('plugin-license', [
100103
'plugin' => $plugin,
104+
'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isAdmin || $isOwner),
101105
]);
102106
}
103107
}

resources/views/plugin-license.blade.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
class="mx-auto mt-10 w-full max-w-3xl px-5 md:mt-14"
44
aria-labelledby="license-title"
55
>
6+
@if ($isAdminPreview ?? false)
7+
<div class="mb-6 rounded-xl border border-amber-300 bg-amber-50 p-4 text-center dark:border-amber-600 dark:bg-amber-950/50">
8+
<p class="text-sm font-medium text-amber-800 dark:text-amber-200">
9+
Preview &mdash; This plugin is not publicly visible.
10+
@if ($plugin->isApproved() && ! $plugin->is_active)
11+
It has been de-listed.
12+
@else
13+
Status: {{ $plugin->status->label() }}
14+
@endif
15+
</p>
16+
</div>
17+
@endif
18+
619
<header class="relative">
720
{{-- Back button --}}
821
<div
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use App\Features\ShowPlugins;
6+
use App\Models\Plugin;
7+
use App\Models\PluginPrice;
8+
use App\Models\User;
9+
use Illuminate\Foundation\Testing\RefreshDatabase;
10+
use Laravel\Pennant\Feature;
11+
use Tests\TestCase;
12+
13+
class PluginLicensePreviewTest extends TestCase
14+
{
15+
use RefreshDatabase;
16+
17+
protected function setUp(): void
18+
{
19+
parent::setUp();
20+
21+
Feature::define(ShowPlugins::class, true);
22+
}
23+
24+
private function createPaidPluginWithLicense(array $pluginAttributes = []): Plugin
25+
{
26+
$plugin = Plugin::factory()
27+
->paid()
28+
->create(array_merge([
29+
'license_html' => '<p>License agreement content</p>',
30+
], $pluginAttributes));
31+
32+
PluginPrice::factory()->regular()->create([
33+
'plugin_id' => $plugin->id,
34+
'amount' => 2999,
35+
]);
36+
37+
return $plugin;
38+
}
39+
40+
public function test_guest_cannot_view_pending_paid_plugin_license(): void
41+
{
42+
$plugin = $this->createPaidPluginWithLicense();
43+
44+
$this->get(route('plugins.license', $plugin->routeParams()))
45+
->assertStatus(404);
46+
}
47+
48+
public function test_regular_user_cannot_view_pending_paid_plugin_license(): void
49+
{
50+
$user = User::factory()->create();
51+
$plugin = $this->createPaidPluginWithLicense();
52+
53+
$this->actingAs($user)
54+
->get(route('plugins.license', $plugin->routeParams()))
55+
->assertStatus(404);
56+
}
57+
58+
public function test_admin_can_view_pending_paid_plugin_license(): void
59+
{
60+
$admin = User::factory()->create(['email' => 'admin@test.com']);
61+
config(['filament.users' => ['admin@test.com']]);
62+
63+
$plugin = $this->createPaidPluginWithLicense();
64+
65+
$this->actingAs($admin)
66+
->get(route('plugins.license', $plugin->routeParams()))
67+
->assertStatus(200);
68+
}
69+
70+
public function test_owner_can_view_pending_paid_plugin_license(): void
71+
{
72+
$owner = User::factory()->create();
73+
$plugin = $this->createPaidPluginWithLicense(['user_id' => $owner->id]);
74+
75+
$this->actingAs($owner)
76+
->get(route('plugins.license', $plugin->routeParams()))
77+
->assertStatus(200);
78+
}
79+
80+
public function test_admin_sees_preview_banner_on_pending_paid_plugin_license(): void
81+
{
82+
$admin = User::factory()->create(['email' => 'admin@test.com']);
83+
config(['filament.users' => ['admin@test.com']]);
84+
85+
$plugin = $this->createPaidPluginWithLicense();
86+
87+
$this->actingAs($admin)
88+
->get(route('plugins.license', $plugin->routeParams()))
89+
->assertSee('Preview')
90+
->assertSee('This plugin is not publicly visible')
91+
->assertSee('Pending Review');
92+
}
93+
94+
public function test_owner_sees_preview_banner_on_pending_paid_plugin_license(): void
95+
{
96+
$owner = User::factory()->create();
97+
$plugin = $this->createPaidPluginWithLicense(['user_id' => $owner->id]);
98+
99+
$this->actingAs($owner)
100+
->get(route('plugins.license', $plugin->routeParams()))
101+
->assertSee('Preview')
102+
->assertSee('This plugin is not publicly visible')
103+
->assertSee('Pending Review');
104+
}
105+
106+
public function test_approved_paid_plugin_license_does_not_show_preview_banner(): void
107+
{
108+
$plugin = $this->createPaidPluginWithLicense(['status' => 'approved', 'approved_at' => now()]);
109+
110+
$this->get(route('plugins.license', $plugin->routeParams()))
111+
->assertStatus(200)
112+
->assertDontSee('This plugin is not publicly visible');
113+
}
114+
115+
public function test_admin_sees_preview_banner_on_delisted_paid_plugin_license(): void
116+
{
117+
$admin = User::factory()->create(['email' => 'admin@test.com']);
118+
config(['filament.users' => ['admin@test.com']]);
119+
120+
$plugin = $this->createPaidPluginWithLicense([
121+
'status' => 'approved',
122+
'approved_at' => now(),
123+
'is_active' => false,
124+
]);
125+
126+
$this->actingAs($admin)
127+
->get(route('plugins.license', $plugin->routeParams()))
128+
->assertSee('Preview')
129+
->assertSee('It has been de-listed');
130+
}
131+
132+
public function test_guest_cannot_view_delisted_paid_plugin_license(): void
133+
{
134+
$plugin = $this->createPaidPluginWithLicense([
135+
'status' => 'approved',
136+
'approved_at' => now(),
137+
'is_active' => false,
138+
]);
139+
140+
$this->get(route('plugins.license', $plugin->routeParams()))
141+
->assertStatus(404);
142+
}
143+
144+
public function test_free_plugin_license_returns_404(): void
145+
{
146+
$plugin = Plugin::factory()->approved()->free()->create([
147+
'license_html' => '<p>License content</p>',
148+
]);
149+
150+
$this->get(route('plugins.license', $plugin->routeParams()))
151+
->assertStatus(404);
152+
}
153+
154+
public function test_paid_plugin_without_license_html_returns_404(): void
155+
{
156+
$admin = User::factory()->create(['email' => 'admin@test.com']);
157+
config(['filament.users' => ['admin@test.com']]);
158+
159+
$plugin = Plugin::factory()->pending()->paid()->create([
160+
'license_html' => null,
161+
]);
162+
163+
PluginPrice::factory()->regular()->create([
164+
'plugin_id' => $plugin->id,
165+
]);
166+
167+
$this->actingAs($admin)
168+
->get(route('plugins.license', $plugin->routeParams()))
169+
->assertStatus(404);
170+
}
171+
}

0 commit comments

Comments
 (0)