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 @@ + + + diff --git a/resources/assets/src/views/skinlib/Show.vue b/resources/assets/src/views/skinlib/Show.vue index b2ae2c61..0b9cfd24 100644 --- a/resources/assets/src/views/skinlib/Show.vue +++ b/resources/assets/src/views/skinlib/Show.vue @@ -52,6 +52,14 @@ :href="`${baseUrl}/raw/${tid}.png`" :download="`${name}`.png" /> + + {{ $t('skinlib.report.title') }} +
{ + if (this.reportScore > 0) { + return this.$t('skinlib.report.positive', { score: this.reportScore }) + } else if (this.reportScore < 0) { + return this.$t('skinlib.report.negative', { score: -this.reportScore }) + } + return '' + })() + let reason + try { + ({ value: reason } = await this.$prompt(message, { + title: this.$t('skinlib.report.title'), + inputPlaceholder: this.$t('skinlib.report.reason'), + })) + } catch { + return + } + + const { errno, msg } = await this.$http.post( + '/skinlib/report', + { tid: this.tid, reason } + ) + if (errno === 0) { + this.$message.success(msg) + } else { + this.$message.warning(msg) + } + }, }, } diff --git a/resources/assets/src/views/skinlib/Upload.vue b/resources/assets/src/views/skinlib/Upload.vue index 94345cef..369aa24e 100644 --- a/resources/assets/src/views/skinlib/Upload.vue +++ b/resources/assets/src/views/skinlib/Upload.vue @@ -46,7 +46,11 @@ {{ $t('skinlib.upload.remove') }}
- + + + {{ $t('skinlib.showContentPolicy') }} + +