1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Http\Controllers;
7
8use App\Exceptions\ModelNotSavedException;
9use App\Libraries\BeatmapsetDiscussion\Review;
10use App\Libraries\BeatmapsetDiscussionsBundle;
11use App\Models\BeatmapDiscussion;
12use App\Models\Beatmapset;
13use Auth;
14
15/**
16 * @group Beatmapset Discussions
17 */
18class BeatmapDiscussionsController extends Controller
19{
20 public function __construct()
21 {
22 $this->middleware('auth', ['except' => ['index', 'mediaUrl', 'show']]);
23 $this->middleware('require-scopes:public', ['only' => ['index']]);
24
25 parent::__construct();
26 }
27
28 public function allowKudosu($id)
29 {
30 $discussion = BeatmapDiscussion::findOrFail($id);
31 priv_check('BeatmapDiscussionAllowOrDenyKudosu', $discussion)->ensureCan();
32
33 try {
34 $discussion->allowKudosu(Auth::user());
35 } catch (ModelNotSavedException $e) {
36 return error_popup($e->getMessage());
37 }
38
39 return $discussion->beatmapset->defaultDiscussionJson();
40 }
41
42 public function denyKudosu($id)
43 {
44 $discussion = BeatmapDiscussion::findOrFail($id);
45 priv_check('BeatmapDiscussionAllowOrDenyKudosu', $discussion)->ensureCan();
46
47 try {
48 $discussion->denyKudosu(Auth::user());
49 } catch (ModelNotSavedException $e) {
50 return error_popup($e->getMessage());
51 }
52
53 return $discussion->beatmapset->defaultDiscussionJson();
54 }
55
56 public function destroy($id)
57 {
58 $discussion = BeatmapDiscussion::whereNull('deleted_at')->findOrFail($id);
59 priv_check('BeatmapDiscussionDestroy', $discussion)->ensureCan();
60
61 try {
62 $discussion->softDeleteOrExplode(Auth::user());
63 } catch (ModelNotSavedException $e) {
64 return error_popup($e->getMessage());
65 }
66
67 return $discussion->beatmapset->defaultDiscussionJson();
68 }
69
70 /**
71 * Get Beatmapset Discussions
72 *
73 * Returns a list of beatmapset discussions.
74 *
75 * ---
76 *
77 * ### Response Format
78 *
79 * <aside class="warning">
80 * The response of this endpoint is likely to change soon!
81 * </aside>
82 *
83 * Field | Type | Description
84 * ------------------------- | ----------------------------------------------- | -----------
85 * beatmaps | [BeatmapExtended](#beatmapextended)[] | List of beatmaps associated with the discussions returned.
86 * cursor_string | [CursorString](#cursorstring) | |
87 * discussions | [BeatmapsetDiscussion](#beatmapsetdiscussion)[] | List of discussions according to `sort` order.
88 * included_discussions | [BeatmapsetDiscussion](#beatmapsetdiscussion)[] | Additional discussions related to `discussions`.
89 * reviews_config.max_blocks | integer | Maximum number of blocks allowed in a review.
90 * users | [User](#user)[] | List of users associated with the discussions returned.
91 *
92 * @usesCursor
93 * @queryParam beatmap_id `id` of the [Beatmap](#beatmap).
94 * @queryParam beatmapset_id `id` of the [Beatmapset](#beatmapset).
95 * @queryParam beatmapset_status One of `all`, `ranked`, `qualified`, `disqualified`, `never_qualified`. Defaults to `all`. TODO: better descriptions.
96 * @queryParam limit Maximum number of results.
97 * @queryParam message_types[] `suggestion`, `problem`, `mapper_note`, `praise`, `hype`, `review`. Blank defaults to all types. TODO: better descriptions.
98 * @queryParam only_unresolved `true` to show only unresolved issues; `false`, otherwise. Defaults to `false`.
99 * @queryParam page Search result page.
100 * @queryParam sort `id_desc` for newest first; `id_asc` for oldest first. Defaults to `id_desc`.
101 * @queryParam user The `id` of the [User](#user).
102 * @queryParam with_deleted This param has no effect as api calls do not currently receive group permissions.
103 */
104 public function index()
105 {
106 $bundle = new BeatmapsetDiscussionsBundle(request()->all());
107
108 $json = $bundle->toArray();
109
110 if (is_api_request()) {
111 return $json;
112 }
113
114 $paginator = $bundle->getPaginator();
115 $search = $bundle->getSearchParams();
116
117 return ext_view('beatmap_discussions.index', compact('json', 'search', 'paginator'));
118 }
119
120 public function restore($id)
121 {
122 $discussion = BeatmapDiscussion::whereNotNull('deleted_at')->findOrFail($id);
123 priv_check('BeatmapDiscussionRestore', $discussion)->ensureCan();
124
125 $discussion->restore(Auth::user());
126
127 return $discussion->beatmapset->defaultDiscussionJson();
128 }
129
130 public function review($beatmapsetId)
131 {
132 $beatmapset = Beatmapset::findOrFail($beatmapsetId);
133
134 priv_check('BeatmapsetDiscussionReviewStore', $beatmapset)->ensureCan();
135
136 try {
137 $document = json_decode(request()->all()['document'] ?? '[]', true);
138 Review::create($beatmapset, $document, Auth::user());
139 } catch (\Exception $e) {
140 return error_popup($e->getMessage(), 422);
141 }
142
143 return $beatmapset->defaultDiscussionJson();
144 }
145
146 public function show($id)
147 {
148 $discussion = BeatmapDiscussion::findOrFail($id);
149
150 if ($discussion->beatmapset === null) {
151 abort(404);
152 }
153
154 return ujs_redirect(route('beatmapsets.discussion', $discussion->beatmapset).'#/'.$discussion->getKey());
155 }
156
157 public function vote($id)
158 {
159 $discussion = BeatmapDiscussion::findOrFail($id);
160
161 priv_check('BeatmapDiscussionVote', $discussion)->ensureCan();
162
163 $params = get_params(\Request::all(), 'beatmap_discussion_vote', ['score:int']);
164 $params['user_id'] = Auth::user()->user_id;
165
166 if ($discussion->vote($params)) {
167 return $discussion->beatmapset->defaultDiscussionJson();
168 } else {
169 return error_popup(osu_trans('beatmaps.discussion-votes.update.error'));
170 }
171 }
172}