Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/Filament/Pages/Companies.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public function table(Table $table): Table
->label('Users')
->icon('heroicon-o-users')
->color('gray')
->visible(fn (): bool => UserResource::canViewAny())
->url(fn (array $record): string => UserResource::getUrl('index', [
'tableSearch' => $record['domain'],
])),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function table(Table $table): Table
])
->defaultSort('sort_order')
->reorderable('sort_order')
->recordUrl(fn ($record) => CourseModuleResource::getUrl('edit', ['record' => $record]))
->recordUrl(fn ($record) => CourseModuleResource::can('update', $record) ? CourseModuleResource::getUrl('edit', ['record' => $record]) : null)
->headerActions([
Actions\CreateAction::make(),
])
Expand Down
2 changes: 1 addition & 1 deletion app/Filament/Resources/OpenCollectiveDonationResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public static function infolist(Schema $schema): Schema
Infolists\Components\TextEntry::make('user.email')
->label('Claimed By')
->placeholder('Not claimed')
->url(fn ($record) => $record->user_id ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null),
->url(fn ($record) => $record->user && UserResource::can('update', $record->user) ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null),
]),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion app/Filament/Resources/PluginResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ public static function form(Schema $schema): Schema
? UserResource::getUrl('edit', ['record' => $record->user_id])
: null)
->openUrlInNewTab()
->visible(fn (?Plugin $record) => $record?->user_id !== null),
->visible(fn (?Plugin $record) => $record?->user !== null && UserResource::can('update', $record->user)),
),

Forms\Components\Placeholder::make('created_at')
Expand Down
2 changes: 1 addition & 1 deletion app/Filament/Resources/SubscriptionItemResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static function table(Table $table): Table
Tables\Columns\TextColumn::make('subscription.id')
->numeric()
->sortable()
->url(fn ($record) => SubscriptionResource::getUrl('view', ['record' => $record->subscription])),
->url(fn ($record) => $record->subscription && SubscriptionResource::can('view', $record->subscription) ? SubscriptionResource::getUrl('view', ['record' => $record->subscription]) : null),
Tables\Columns\TextColumn::make('subscription.stripe_status')
->label('Subscription Status')
->badge()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ public function infolist(Schema $schema): Schema
->schema([
Components\TextEntry::make('subscription.id')
->label('Subscription ID')
->url(fn ($record) => SubscriptionResource::getUrl('view', ['record' => $record->subscription_id])),
->url(fn ($record) => $record->subscription && SubscriptionResource::can('view', $record->subscription) ? SubscriptionResource::getUrl('view', ['record' => $record->subscription_id]) : null),
Components\TextEntry::make('subscription.user.email')
->label('User')
->url(fn ($record) => UserResource::getUrl('edit', ['record' => $record->subscription_id])),
->url(fn ($record) => $record->subscription?->user && UserResource::can('update', $record->subscription->user) ? UserResource::getUrl('edit', ['record' => $record->subscription->user_id]) : null),
Components\TextEntry::make('stripe_id')
->label('Stripe ID')
->copyable(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function infolist(Schema $schema): Schema
->schema([
Components\TextEntry::make('user.email')
->label('User')
->url(fn ($record) => UserResource::getUrl('edit', ['record' => $record->user_id])),
->url(fn ($record) => $record->user && UserResource::can('update', $record->user) ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null),
Components\TextEntry::make('type'),
Components\TextEntry::make('stripe_id')
->label('Stripe ID')
Expand Down
2 changes: 1 addition & 1 deletion app/Filament/Resources/SupportTicketResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static function infolist(Schema $schema): Schema
Infolists\Components\TextEntry::make('user.email')
->label('User')
->formatStateUsing(fn (SupportTicket $record): string => trim(($record->user->name ?? '').' ('.$record->user->email.')'))
->url(fn (SupportTicket $record): string => UserResource::getUrl('edit', ['record' => $record->user_id])),
->url(fn (SupportTicket $record): ?string => $record->user && UserResource::can('update', $record->user) ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null),
Infolists\Components\TextEntry::make('created_at')
->label('Created')
->dateTime(),
Expand Down
2 changes: 1 addition & 1 deletion app/Filament/Resources/ThirdPartySaleResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public static function table(Table $table): Table
->url(fn (PluginLicense $record): ?string => $record->payout
? PluginPayoutResource::getUrl('view', ['record' => $record->payout])
: null)
->visible(fn (PluginLicense $record): bool => $record->payout !== null),
->visible(fn (PluginLicense $record): bool => $record->payout !== null && PluginPayoutResource::can('view', $record->payout)),
])
->bulkActions([])
->defaultSort('purchased_at', 'desc');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ public function table(Table $table): Table
->sortable(),
])
->defaultSort('created_at', 'desc')
->recordUrl(fn ($record) => PluginResource::getUrl('edit', ['record' => $record]));
->recordUrl(fn ($record) => PluginResource::can('update', $record) ? PluginResource::getUrl('edit', ['record' => $record]) : null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ protected function getHeaderActions(): array
Actions\DeleteAction::make(),
Actions\Action::make('user')
->icon('heroicon-s-user')
->action(function ($record) {
return redirect(UserResource::getUrl('edit', ['record' => $record]));
}),
->visible(fn ($record): bool => $record->user !== null && UserResource::can('update', $record->user))
->url(fn ($record): string => UserResource::getUrl('edit', ['record' => $record->user_id])),
];
}
}
20 changes: 10 additions & 10 deletions app/Http/Controllers/PluginDirectoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ public function show(string $vendor, string $package): View

$user = Auth::user();

$isAdmin = $user?->isAdmin() ?? false;
$isStaff = $user?->can('view', $plugin) ?? false;
$isOwner = $user && $plugin->user_id === $user->id;

abort_unless(($plugin->isApproved() && $plugin->is_active) || $isAdmin || $isOwner, 404);
abort_unless(($plugin->isApproved() && $plugin->is_active) || $isStaff || $isOwner, 404);

// For paid plugins, check if user has an accessible price (admins and owners bypass)
if (! $isAdmin && ! $isOwner && $plugin->isPaid() && ! $plugin->hasAccessiblePriceFor($user)) {
// For paid plugins, check if user has an accessible price (staff and owners bypass)
if (! $isStaff && ! $isOwner && $plugin->isPaid() && ! $plugin->hasAccessiblePriceFor($user)) {
abort(404);
}

Expand All @@ -81,7 +81,7 @@ public function show(string $vendor, string $package): View
'regularPrice' => $regularPrice,
'ultraOfferPrice' => $user?->hasActiveUltraSubscription() ? null : $plugin->getUltraPrice(),
'hasDiscount' => $bestPrice && $regularPrice && $bestPrice->id !== $regularPrice->id,
'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isAdmin || $isOwner),
'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isStaff || $isOwner),
]);
}

Expand All @@ -91,23 +91,23 @@ public function license(string $vendor, string $package): View

$user = Auth::user();

$isAdmin = $user?->isAdmin() ?? false;
$isStaff = $user?->can('view', $plugin) ?? false;
$isOwner = $user && $plugin->user_id === $user->id;

abort_unless($plugin->isPaid(), 404);
abort_unless($plugin->license_html, 404);
abort_unless(($plugin->isApproved() && $plugin->is_active) || $isAdmin || $isOwner, 404);
abort_unless(($plugin->isApproved() && $plugin->is_active) || $isStaff || $isOwner, 404);

// For paid plugins, check if user has an accessible price (admins and owners bypass)
if (! $isAdmin && ! $isOwner && ! $plugin->hasAccessiblePriceFor($user)) {
// For paid plugins, check if user has an accessible price (staff and owners bypass)
if (! $isStaff && ! $isOwner && ! $plugin->hasAccessiblePriceFor($user)) {
abort(404);
}

$this->setPluginSeo($plugin, suffix: 'License');

return view('plugin-license', [
'plugin' => $plugin,
'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isAdmin || $isOwner),
'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isStaff || $isOwner),
]);
}

Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/ShowBlogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function feed(BlogFeed $feed): Response

public function show(Article $article)
{
abort_unless($article->isPublished() || $article->isScheduled() || auth()->user()?->isAdmin(), 404);
abort_unless($article->isPublished() || $article->isScheduled() || auth()->user()?->can('view', $article), 404);

// Set SEO metadata for the article
SEOTools::setTitle($article->title.' - Blog');
Expand Down
4 changes: 2 additions & 2 deletions app/Http/Controllers/SupportTicketAttachmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function downloadTicketAttachment(SupportTicket $supportTicket, int $inde
{
$user = auth()->user();

abort_unless($user->id === $supportTicket->user_id || $user->isAdmin(), 403);
abort_unless($user->can('view', $supportTicket), 403);

$attachments = $supportTicket->attachments ?? [];

Expand All @@ -30,7 +30,7 @@ public function downloadReplyAttachment(SupportTicket $supportTicket, Reply $rep
{
$user = auth()->user();

abort_unless($user->id === $supportTicket->user_id || $user->isAdmin(), 403);
abort_unless($user->can('view', $supportTicket), 403);
abort_unless($reply->support_ticket_id === $supportTicket->id, 404);

$attachments = $reply->attachments ?? [];
Expand Down
8 changes: 8 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ public function getFilamentName(): string
* role, whose permissions then decide which sections they can see.
*/
public function canAccessPanel(Panel $panel): bool
{
return $this->isStaff();
}

/**
* Staff are super admins plus anyone given a role in the admin panel.
*/
public function isStaff(): bool
{
return $this->isAdmin() || $this->roles->isNotEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
@forelse ($record->replies()->with('user')->orderBy('created_at', 'desc')->get() as $reply)
@php
$isAdmin = $reply->user?->isAdmin();
$isStaff = $reply->user?->isStaff();
$isNote = $reply->note;

if ($isNote) {
$bgColor = '#fefce8';
$borderColor = '#facc15';
} elseif ($isAdmin) {
} elseif ($isStaff) {
$bgColor = '#ede9fe';
$borderColor = '#c4b5fd';
} else {
Expand All @@ -139,7 +139,7 @@
@if ($reply->pinned)
<x-filament::badge color="success" size="sm">Pinned</x-filament::badge>
@endif
@elseif ($isAdmin)
@elseif ($isStaff)
<x-filament::badge color="primary" size="sm">Staff</x-filament::badge>
@endif
@endif
Expand Down
65 changes: 65 additions & 0 deletions tests/Feature/AdminPluginPreviewTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Pennant\Feature;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

class AdminPluginPreviewTest extends TestCase
Expand Down Expand Up @@ -83,4 +85,67 @@ public function test_admin_can_view_approved_plugin_without_preview_banner(): vo
->assertStatus(200)
->assertDontSee('This plugin is not publicly visible');
}

public function test_staff_with_plugin_view_permission_can_preview_pending_plugin(): void
{
$plugin = Plugin::factory()->pending()->create();

$this->actingAs($this->staffWithPermissions(['ViewAny:Plugin', 'View:Plugin']))
->get(route('plugins.show', $plugin->routeParams()))
->assertOk()
->assertSee('This plugin is not publicly visible.');
}

public function test_staff_with_plugin_view_permission_can_preview_paid_pending_plugin_and_its_license(): void
{
$plugin = Plugin::factory()->pending()->paid()->create(['license_html' => '<p>License terms</p>']);
$staff = $this->staffWithPermissions(['ViewAny:Plugin', 'View:Plugin']);

$this->actingAs($staff)
->get(route('plugins.show', $plugin->routeParams()))
->assertOk();

$this->actingAs($staff)
->get(route('plugins.license', $plugin->routeParams()))
->assertOk()
->assertSee('License terms', false);
}

public function test_staff_without_plugin_view_permission_cannot_preview_pending_plugin(): void
{
$plugin = Plugin::factory()->pending()->create();

$this->actingAs($this->staffWithPermissions(['ViewAny:Lead']))
->get(route('plugins.show', $plugin->routeParams()))
->assertNotFound();
}

public function test_super_admin_role_can_preview_pending_plugin(): void
{
$superAdmin = User::factory()->create();
$superAdmin->assignRole(Role::findOrCreate(config('filament-shield.super_admin.name')));

$plugin = Plugin::factory()->pending()->create();

$this->actingAs($superAdmin)
->get(route('plugins.show', $plugin->routeParams()))
->assertOk();
}

/**
* @param list<string> $permissions
*/
private function staffWithPermissions(array $permissions): User
{
$role = Role::findOrCreate('test_role');

foreach ($permissions as $permission) {
$role->givePermissionTo(Permission::findOrCreate($permission));
}

$user = User::factory()->create();
$user->assignRole($role);

return $user;
}
}
Loading