diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php
index e36654d3..718ca2d5 100644
--- a/app/Http/Controllers/AdminController.php
+++ b/app/Http/Controllers/AdminController.php
@@ -128,6 +128,12 @@ class AdminController extends Controller
$form->text('user_initial_score');
})->handle();
+ $report = Option::form('report', OptionForm::AUTO_DETECT, function ($form) {
+ $form->text('reporter_score_modification')->description();
+
+ $form->text('reporter_reward_score');
+ })->handle();
+
$sign = Option::form('sign', OptionForm::AUTO_DETECT, function ($form) {
$form->group('sign_score')
->text('sign_score_from')->addon(trans('options.sign.sign_score.addon1'))
@@ -154,7 +160,7 @@ class AdminController extends Controller
->addon(trans('general.user.score'));
})->handle();
- return view('admin.score', ['forms' => compact('rate', 'sign', 'sharing')]);
+ return view('admin.score', ['forms' => compact('rate', 'report', 'sign', 'sharing')]);
}
public function options()
@@ -215,10 +221,13 @@ class AdminController extends Controller
$form->text('texture_name_regexp')->hint()->placeholder();
+ $form->textarea('content_policy')->rows(3)->description();
+
$form->textarea('comment_script')->rows(6)->description();
})->handle(function () {
Option::set('site_name_'.config('app.locale'), request('site_name'));
Option::set('site_description_'.config('app.locale'), request('site_description'));
+ Option::set('content_policy_'.config('app.locale'), request('content_policy'));
});
$announ = Option::form('announ', OptionForm::AUTO_DETECT, function ($form) {
diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php
new file mode 100644
index 00000000..7638fafe
--- /dev/null
+++ b/app/Http/Controllers/ReportController.php
@@ -0,0 +1,137 @@
+validate($request, [
+ 'tid' => 'required|exists:textures',
+ 'reason' => 'required'
+ ]);
+ $reporter = auth()->user();
+
+ if (Report::where('reporter', $reporter->uid)->where('tid', $data['tid'])->count() > 0) {
+ return json(trans('skinlib.report.duplicate'), 1);
+ }
+
+ $score = option('reporter_score_modification', 0);
+ if ($score < 0 && $reporter->score < -$score) {
+ return json(trans('skinlib.upload.lack-score'), 1);
+ }
+ $reporter->score += $score;
+ $reporter->save();
+
+ $report = new Report;
+ $report->tid = $data['tid'];
+ $report->uploader = Texture::find($data['tid'])->uploader;
+ $report->reporter = $reporter->uid;
+ $report->reason = $data['reason'];
+ $report->status = Report::PENDING;
+ $report->save();
+
+ return json(trans('skinlib.report.success'), 0);
+ }
+
+ public function viewTrack()
+ {
+ return view('user.report', ['user' => auth()->user()]);
+ }
+
+ public function track()
+ {
+ return Report::where('reporter', auth()->id())
+ ->orderBy('report_at', 'desc')
+ ->get();
+ }
+
+ public function manage(Request $request)
+ {
+ $search = $request->input('search', '');
+ $sortField = $request->input('sortField', 'report_at');
+ $sortType = $request->input('sortType', 'desc');
+ $page = $request->input('page', 1);
+ $perPage = $request->input('perPage', 10);
+
+ $reports = Report::where('tid', 'like', '%'.$search.'%')
+ ->orWhere('reporter', 'like', '%'.$search.'%')
+ ->orWhere('reason', 'like', '%'.$search.'%')
+ ->orderBy($sortField, $sortType)
+ ->offset(($page - 1) * $perPage)
+ ->limit($perPage)
+ ->get()
+ ->makeHidden(['informer'])
+ ->map(function ($report) {
+ $uploader = User::find($report->uploader);
+ if ($uploader) {
+ $report->uploaderName = $uploader->nickname;
+ }
+ if ($report->informer) {
+ $report->reporterName = $report->informer->nickname;
+ }
+ return $report;
+ });
+
+ return [
+ 'totalRecords' => Report::count(),
+ 'data' => $reports,
+ ];
+ }
+
+ public function review(Request $request)
+ {
+ $data = $this->validate($request, [
+ 'id' => 'required|exists:reports',
+ 'action' => ['required', Rule::in(['delete', 'ban', 'reject'])]
+ ]);
+ $report = Report::find($data['id']);
+
+ if ($report->status != Report::PENDING) {
+ return json(trans('admin.report-reviewed'), 1);
+ }
+
+ if ($data['action'] == 'reject') {
+ if (
+ $report->informer &&
+ ($score = option('reporter_score_modification', 0)) > 0
+ ) {
+ $report->informer->score -= $score;
+ $report->informer->save();
+ }
+ $report->status = Report::REJECTED;
+ $report->save();
+ return json(trans('general.op-success'), 0, ['status' => Report::REJECTED]);
+ }
+
+ switch ($data['action']) {
+ case 'delete':
+ $report->texture->delete();
+ break;
+ case 'ban':
+ if (auth()->user()->permission <= $report->informer->permission) {
+ return json(trans('admin.users.operations.no-permission'), 1);
+ }
+ $report->informer->permission = User::BANNED;
+ $report->informer->save();
+ break;
+ }
+
+ $report->status = Report::RESOLVED;
+ $report->save();
+
+ if (($score = option('reporter_score_modification', 0)) < 0) {
+ $report->informer->score -= $score;
+ }
+ $report->informer->score += option('reporter_reward_score', 0);
+ $report->informer->save();
+
+ return json(trans('general.op-success'), 0, ['status' => Report::RESOLVED]);
+ }
+}
diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php
index 53f22f59..8cb55b32 100644
--- a/app/Http/Controllers/SetupController.php
+++ b/app/Http/Controllers/SetupController.php
@@ -250,7 +250,14 @@ class SetupController extends Controller
public static function checkTablesExist($tables = [], $returnExistingTables = false)
{
$existingTables = [];
- $tables = $tables ?: ['users', 'user_closet', 'players', 'textures', 'options'];
+ $tables = $tables ?: [
+ 'users',
+ 'user_closet',
+ 'players',
+ 'textures',
+ 'options',
+ 'reports',
+ ];
foreach ($tables as $tableName) {
if (Schema::hasTable($tableName)) {
diff --git a/app/Http/Controllers/SkinlibController.php b/app/Http/Controllers/SkinlibController.php
index 503c189f..40368c95 100644
--- a/app/Http/Controllers/SkinlibController.php
+++ b/app/Http/Controllers/SkinlibController.php
@@ -157,7 +157,8 @@ class SkinlibController extends Controller
'currentUid' => $user ? $user->uid : 0,
'admin' => $user && $user->isAdmin(),
'inCloset' => $user && $user->closet()->where('tid', $texture->tid)->count() > 0,
- 'nickname' => ($up = User::find($texture->uploader)) ? $up->nickname : null
+ 'nickname' => ($up = User::find($texture->uploader)) ? $up->nickname : null,
+ 'report' => intval(option('reporter_score_modification', 0)),
]);
}
@@ -185,6 +186,7 @@ class SkinlibController extends Controller
'scorePublic' => intval(option('score_per_storage')),
'scorePrivate' => intval(option('private_score_per_storage')),
'award' => intval(option('score_award_per_texture')),
+ 'contentPolicy' => app('parsedown')->text(option_localized('content_policy')),
])
->with('with_out_filter', true);
}
diff --git a/app/Models/Report.php b/app/Models/Report.php
new file mode 100644
index 00000000..2d0f9f2d
--- /dev/null
+++ b/app/Models/Report.php
@@ -0,0 +1,32 @@
+ 'integer',
+ 'uploader' => 'integer',
+ 'reporter' => 'integer',
+ 'status' => 'integer',
+ ];
+
+ public function texture()
+ {
+ return $this->belongsTo(Texture::class, 'tid', 'tid');
+ }
+
+ public function informer()
+ {
+ return $this->belongsTo(User::class, 'reporter', 'uid');
+ }
+}
diff --git a/config/menu.php b/config/menu.php
index 2d314dc7..fcc7b63b 100644
--- a/config/menu.php
+++ b/config/menu.php
@@ -12,6 +12,7 @@ $menu['user'] = [
['title' => 'general.dashboard', 'link' => 'user', 'icon' => 'fa-tachometer-alt'],
['title' => 'general.my-closet', 'link' => 'user/closet', 'icon' => 'fa-star'],
['title' => 'general.player-manage', 'link' => 'user/player', 'icon' => 'fa-users'],
+ ['title' => 'general.my-reports', 'link' => 'user/reports', 'icon' => 'fa-flag'],
['title' => 'general.profile', 'link' => 'user/profile', 'icon' => 'fa-user'],
];
@@ -19,6 +20,7 @@ $menu['admin'] = [
['title' => 'general.dashboard', 'link' => 'admin', 'icon' => 'fa-tachometer-alt'],
['title' => 'general.user-manage', 'link' => 'admin/users', 'icon' => 'fa-users'],
['title' => 'general.player-manage', 'link' => 'admin/players', 'icon' => 'fa-gamepad'],
+ ['title' => 'general.report-manage', 'link' => 'admin/reports', 'icon' => 'fa-flag'],
['title' => 'general.customize', 'link' => 'admin/customize', 'icon' => 'fa-paint-brush'],
['title' => 'general.score-options', 'link' => 'admin/score', 'icon' => 'fa-credit-card'],
['title' => 'general.options', 'link' => 'admin/options', 'icon' => 'fa-cog'],
diff --git a/config/options.php b/config/options.php
index 11debf36..a5d88fca 100644
--- a/config/options.php
+++ b/config/options.php
@@ -53,4 +53,7 @@ return [
'recaptcha_sitekey' => '',
'recaptcha_secretkey' => '',
'recaptcha_invisible' => 'false',
+ 'reporter_score_modification' => '0',
+ 'reporter_reward_score' => '0',
+ 'content_policy' => '',
];
diff --git a/database/migrations/2019_03_23_171728_create_report_table.php b/database/migrations/2019_03_23_171728_create_report_table.php
new file mode 100644
index 00000000..cfd4e404
--- /dev/null
+++ b/database/migrations/2019_03_23_171728_create_report_table.php
@@ -0,0 +1,26 @@
+increments('id');
+ $table->integer('tid');
+ $table->integer('uploader');
+ $table->integer('reporter');
+ $table->longText('reason');
+ $table->integer('status');
+ $table->dateTime('report_at');
+ });
+ }
+
+ public function down()
+ {
+ Schema::dropIfExists('reports');
+ }
+}
diff --git a/resources/assets/src/components/mixins/serverTable.ts b/resources/assets/src/components/mixins/serverTable.ts
index c6831b07..76767cf1 100644
--- a/resources/assets/src/components/mixins/serverTable.ts
+++ b/resources/assets/src/components/mixins/serverTable.ts
@@ -27,9 +27,9 @@ export default Vue.extend({
this.serverParams.perPage = params.currentPerPage
this.fetchData()
},
- onSortChange(params: { sortType: 'asc' | 'desc', columnIndex: number }) {
- this.serverParams.sortType = params.sortType
- this.serverParams.sortField = this.columns[params.columnIndex].field
+ onSortChange([params]: { type: 'asc' | 'desc', field: string }[]) {
+ this.serverParams.sortType = params.type
+ this.serverParams.sortField = params.field
this.fetchData()
},
onSearch(params: { searchTerm: string }) {
diff --git a/resources/assets/src/scripts/route.ts b/resources/assets/src/scripts/route.ts
index 42e89d2f..888d1abd 100644
--- a/resources/assets/src/scripts/route.ts
+++ b/resources/assets/src/scripts/route.ts
@@ -23,6 +23,11 @@ export default [
component: () => import('../views/user/Bind.vue'),
el: 'form',
},
+ {
+ path: 'user/reports',
+ component: () => import('../views/user/Report.vue'),
+ el: '.content',
+ },
{
path: 'user/profile',
component: () => import('../views/user/Profile.vue'),
@@ -42,6 +47,11 @@ export default [
component: () => import('../views/admin/Players.vue'),
el: '.content',
},
+ {
+ path: 'admin/reports',
+ component: () => import('../views/admin/Reports.vue'),
+ el: '.content',
+ },
{
path: 'admin/customize',
component: () => import('../views/admin/Customization.vue'),
diff --git a/resources/assets/src/views/admin/Reports.vue b/resources/assets/src/views/admin/Reports.vue
new file mode 100644
index 00000000..991e1bd9
--- /dev/null
+++ b/resources/assets/src/views/admin/Reports.vue
@@ -0,0 +1,139 @@
+
+