Skip to content

Commit 1262ac6

Browse files
authored
Add forum content reporting (#20)
1 parent b90d4ee commit 1262ac6

22 files changed

Lines changed: 809 additions & 5 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
All notable changes to `Filament Forum` will be documented in this file.
44

5+
## Unreleased
6+
7+
Add reusable forum content reports and moderation review workflow.
8+
59
## v2.1.3 - 2026-04-09
610

711
Flatten post view: collapsible comment form, no card nesting

config/filament-forum.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use Tapp\FilamentForum\Filament\Resources\Admin\ForumContentReportResource;
34
use Tapp\FilamentForum\Filament\Resources\ForumPosts\ForumPostResource;
45
use Tapp\FilamentForum\Filament\Resources\Forums\ForumResource;
56

@@ -13,6 +14,7 @@
1314
'admin-resources' => [
1415
'adminForumResource' => Tapp\FilamentForum\Filament\Resources\Admin\ForumResource::class,
1516
'adminForumPostResource' => Tapp\FilamentForum\Filament\Resources\Admin\ForumPostResource::class,
17+
'adminForumContentReportResource' => ForumContentReportResource::class,
1618
],
1719

1820
'user' => [
@@ -60,4 +62,14 @@
6062
],
6163
],
6264

65+
'reports' => [
66+
'reasons' => [
67+
'spam' => 'Spam',
68+
'harassment' => 'Harassment',
69+
'inappropriate' => 'Inappropriate content',
70+
'misinformation' => 'Misinformation',
71+
'other' => 'Other',
72+
],
73+
],
74+
6375
];
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('forum_content_reports', function (Blueprint $table) {
12+
$table->id();
13+
14+
if (config('filament-forum.tenancy.enabled')) {
15+
$tenantModel = config('filament-forum.tenancy.model');
16+
$table->foreignIdFor($tenantModel)
17+
->constrained()
18+
->cascadeOnDelete();
19+
}
20+
21+
$table->morphs('reportable');
22+
$table->morphs('reporter');
23+
$table->string('reason')->nullable();
24+
$table->text('details')->nullable();
25+
$table->string('status')->default('pending')->index();
26+
$userModel = config('auth.providers.users.model');
27+
$table->foreignId('reviewed_by_id')->nullable()->constrained((new $userModel)->getTable())->nullOnDelete();
28+
$table->timestamp('reviewed_at')->nullable();
29+
$table->text('resolution_note')->nullable();
30+
$table->timestamps();
31+
});
32+
}
33+
34+
public function down(): void
35+
{
36+
Schema::dropIfExists('forum_content_reports');
37+
}
38+
};

resources/lang/en/filament-forum.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,42 @@
113113
'comments.reactions-count' => '{0} No reactions|{1} :count reaction|[2,*] :count reactions',
114114
'comments.count' => '{0} No comments|{1} :count comment|[2,*] :count comments',
115115

116+
/*
117+
|--------------------------------------------------------------------------
118+
| Reports
119+
|--------------------------------------------------------------------------
120+
*/
121+
122+
'reports.navigation-label' => 'Content Reports',
123+
'reports.label' => 'Content Report',
124+
'reports.plural-label' => 'Content Reports',
125+
'reports.report' => 'Report',
126+
'reports.actions' => 'Report actions',
127+
'reports.resolve' => 'Resolve',
128+
'reports.dismiss' => 'Dismiss',
129+
'reports.reason' => 'Reason',
130+
'reports.details' => 'Details',
131+
'reports.status' => 'Status',
132+
'reports.content' => 'Reported content',
133+
'reports.review' => 'Review',
134+
'reports.reviewer' => 'Reviewer',
135+
'reports.resolution-note' => 'Resolution note',
136+
'reports.no-details' => 'No details provided',
137+
'reports.no-resolution-note' => 'No resolution note',
138+
'reports.not-reviewed' => 'Not reviewed yet',
139+
'reports.login-required' => 'You must be logged in to report content',
140+
'reports.already-reported' => 'You already reported this content',
141+
'reports.submitted' => 'Report submitted',
142+
'reports.modal.heading' => 'Report content',
143+
'reports.modal.description' => 'Send this to moderators for review.',
144+
'reports.modal.submit' => 'Submit report',
145+
'reports.status.pending' => 'Pending',
146+
'reports.status.resolved' => 'Resolved',
147+
'reports.status.dismissed' => 'Dismissed',
148+
'reports.table.content-type' => 'Content type',
149+
'reports.table.reporter' => 'Reporter',
150+
'reports.table.reported-at' => 'Reported',
151+
116152
/*
117153
|--------------------------------------------------------------------------
118154
| Reactions

resources/views/livewire/forum-comments.blade.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,14 @@ class="w-8 h-8 rounded-full flex-shrink-0"
113113
@endif
114114
</div>
115115

116-
{{-- Edit/Delete Actions --}}
116+
{{-- Report/Edit/Delete Actions --}}
117117
@auth
118-
@if($comment->isAuthor(Auth::user()))
119-
<div class="flex items-center space-x-1">
118+
<div class="flex items-center space-x-1">
119+
@unless($comment->isAuthor(Auth::user()))
120+
{{ ($this->reportCommentAction)(['commentId' => $comment->id]) }}
121+
@endunless
122+
123+
@if($comment->isAuthor(Auth::user()))
120124
@if($editingCommentId !== $comment->id)
121125
<button
122126
wire:click="editComment({{ $comment->id }})"
@@ -128,8 +132,8 @@ class="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition
128132

129133
{{ ($this->deleteCommentAction)(['commentId' => $comment->id]) }}
130134
@endif
131-
</div>
132-
@endif
135+
@endif
136+
</div>
133137
@endauth
134138
</div>
135139
</div>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Tapp\FilamentForum\Events;
4+
5+
use Illuminate\Foundation\Events\Dispatchable;
6+
use Illuminate\Queue\SerializesModels;
7+
use Tapp\FilamentForum\Models\ForumContentReport;
8+
9+
class ForumContentReportDismissed
10+
{
11+
use Dispatchable;
12+
use SerializesModels;
13+
14+
public function __construct(
15+
public ForumContentReport $report,
16+
) {}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Tapp\FilamentForum\Events;
4+
5+
use Illuminate\Foundation\Events\Dispatchable;
6+
use Illuminate\Queue\SerializesModels;
7+
use Tapp\FilamentForum\Models\ForumContentReport;
8+
9+
class ForumContentReportResolved
10+
{
11+
use Dispatchable;
12+
use SerializesModels;
13+
14+
public function __construct(
15+
public ForumContentReport $report,
16+
) {}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Tapp\FilamentForum\Events;
4+
5+
use Illuminate\Foundation\Events\Dispatchable;
6+
use Illuminate\Queue\SerializesModels;
7+
use Tapp\FilamentForum\Models\ForumContentReport;
8+
9+
class ForumContentReported
10+
{
11+
use Dispatchable;
12+
use SerializesModels;
13+
14+
public function __construct(
15+
public ForumContentReport $report,
16+
) {}
17+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Tapp\FilamentForum\Filament\Actions;
4+
5+
use Filament\Actions\Action;
6+
use Filament\Forms\Components\Select;
7+
use Filament\Forms\Components\Textarea;
8+
use Filament\Notifications\Notification;
9+
use Illuminate\Support\Facades\Auth;
10+
11+
class ReportForumContentAction
12+
{
13+
public static function make(string $name = 'report'): Action
14+
{
15+
return Action::make($name)
16+
->label(__('filament-forum::filament-forum.reports.report'))
17+
->icon('heroicon-o-flag')
18+
->iconButton()
19+
->color('gray')
20+
->tooltip(__('filament-forum::filament-forum.reports.report'))
21+
->modalHeading(__('filament-forum::filament-forum.reports.modal.heading'))
22+
->modalDescription(__('filament-forum::filament-forum.reports.modal.description'))
23+
->modalSubmitActionLabel(__('filament-forum::filament-forum.reports.modal.submit'))
24+
->schema([
25+
Select::make('reason')
26+
->label(__('filament-forum::filament-forum.reports.reason'))
27+
->options(config('filament-forum.reports.reasons', []))
28+
->native(false)
29+
->required(),
30+
Textarea::make('details')
31+
->label(__('filament-forum::filament-forum.reports.details'))
32+
->rows(4)
33+
->maxLength(2000),
34+
])
35+
->visible(fn ($record = null): bool => Auth::check() && ($record === null || method_exists($record, 'reportContent')))
36+
->disabled(fn ($record = null): bool => Auth::check() && $record !== null && $record->hasPendingReportBy(Auth::user()))
37+
->action(function ($record, array $data): void {
38+
if (! Auth::check()) {
39+
Notification::make()
40+
->title(__('filament-forum::filament-forum.reports.login-required'))
41+
->danger()
42+
->send();
43+
44+
return;
45+
}
46+
47+
if ($record->hasPendingReportBy(Auth::user())) {
48+
Notification::make()
49+
->title(__('filament-forum::filament-forum.reports.already-reported'))
50+
->warning()
51+
->send();
52+
53+
return;
54+
}
55+
56+
$record->reportContent(
57+
reporter: Auth::user(),
58+
reason: $data['reason'] ?? null,
59+
details: $data['details'] ?? null,
60+
);
61+
62+
Notification::make()
63+
->title(__('filament-forum::filament-forum.reports.submitted'))
64+
->success()
65+
->send();
66+
});
67+
}
68+
}

0 commit comments

Comments
 (0)