the browser-facing portion of osu!
at master 9.3 kB view raw
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\Jobs\Notifications\CommentNew; 10use App\Libraries\CommentBundle; 11use App\Libraries\MorphMap; 12use App\Models\Comment; 13use App\Models\Log; 14use App\Models\User; 15use Carbon\Carbon; 16use Exception; 17use Illuminate\Pagination\LengthAwarePaginator; 18 19/** 20 * @group Comments 21 */ 22class CommentsController extends Controller 23{ 24 public function __construct() 25 { 26 parent::__construct(); 27 28 $this->middleware('auth', ['except' => ['index', 'show']]); 29 } 30 31 /** 32 * Delete Comment 33 * 34 * Deletes the specified comment. 35 * 36 * --- 37 * 38 * ### Response Format 39 * 40 * Returns [CommentBundle](#commentbundle) 41 */ 42 public function destroy($id) 43 { 44 $comment = Comment::findOrFail($id); 45 46 priv_check('CommentDestroy', $comment)->ensureCan(); 47 48 $comment->softDelete(auth()->user()); 49 50 if ($comment->user_id !== auth()->user()->getKey()) { 51 $this->logModerate('LOG_COMMENT_DELETE', $comment); 52 } 53 54 return CommentBundle::forComment($comment)->toArray(); 55 } 56 57 /** 58 * Get Comments 59 * 60 * Returns a list comments and their replies up to 2 levels deep. 61 * 62 * --- 63 * 64 * ### Response Format 65 * 66 * Returns [CommentBundle](#commentbundle). 67 * 68 * `pinned_comments` is only included when `commentable_type` and `commentable_id` are specified. 69 * 70 * @queryParam after Return comments which come after the specified comment id as per sort option. No-example 71 * @queryParam commentable_type The type of resource to get comments for. Example: beatmapset 72 * @queryParam commentable_id The id of the resource to get comments for. Example: 1 73 * @queryParam cursor Pagination option. See [CommentSort](#commentsort) for detail. The format follows [Cursor](#cursor) except it's not currently included in the response. No-example 74 * @queryParam parent_id Limit to comments which are reply to the specified id. Specify 0 to get top level comments. Example: 1 75 * @queryParam sort Sort option as defined in [CommentSort](#commentsort). Defaults to `new` for guests and user-specified default when authenticated. Example: new 76 */ 77 public function index() 78 { 79 $params = request()->all(); 80 81 $userId = $params['user_id'] ?? null; 82 83 if ($userId !== null) { 84 $user = User::lookup($userId, 'id', true); 85 86 if ($user === null || !priv_check('UserShow', $user)->can()) { 87 abort(404); 88 } 89 } 90 91 $id = $params['commentable_id'] ?? null; 92 $type = $params['commentable_type'] ?? null; 93 94 if (isset($type) && isset($id)) { 95 if (!Comment::isValidType($type)) { 96 abort(422); 97 } 98 99 $class = MorphMap::getClass($type); 100 $commentable = $class::findOrFail($id); 101 } 102 103 $params['sort'] = $params['sort'] ?? Comment::DEFAULT_SORT; 104 $commentBundle = new CommentBundle( 105 $commentable ?? null, 106 ['params' => $params] 107 ); 108 109 if (is_json_request()) { 110 return $commentBundle->toArray(); 111 } else { 112 $commentBundle->depth = 0; 113 $commentBundle->includePinned = false; 114 115 $commentPagination = new LengthAwarePaginator( 116 [], 117 $commentBundle->countForPaginator(), 118 $commentBundle->params->limit, 119 $commentBundle->params->page, 120 [ 121 'path' => LengthAwarePaginator::resolveCurrentPath(), 122 'query' => $commentBundle->params->forUrl(), 123 ] 124 ); 125 126 return ext_view('comments.index', compact('commentBundle', 'commentPagination')); 127 } 128 } 129 130 public function restore($id) 131 { 132 $comment = Comment::findOrFail($id); 133 134 priv_check('CommentRestore', $comment)->ensureCan(); 135 136 $comment->restore(); 137 138 $this->logModerate('LOG_COMMENT_RESTORE', $comment); 139 140 return CommentBundle::forComment($comment)->toArray(); 141 } 142 143 /** 144 * Get a Comment 145 * 146 * Gets a comment and its replies up to 2 levels deep. 147 * 148 * --- 149 * 150 * ### Response Format 151 * 152 * Returns [CommentBundle](#commentbundle) 153 */ 154 public function show($id) 155 { 156 $comment = Comment::findOrFail($id); 157 158 $commentBundle = CommentBundle::forComment($comment, true); 159 160 if (is_json_request()) { 161 return $commentBundle->toArray(); 162 } 163 164 set_opengraph($comment); 165 166 return ext_view('comments.show', compact('commentBundle')); 167 } 168 169 /** 170 * Post a new comment 171 * 172 * Posts a new comment to a comment thread. 173 * 174 * --- 175 * 176 * ### Response Format 177 * 178 * Returns [CommentBundle](#commentbundle) 179 * 180 * @queryParam comment.commentable_id Resource ID the comment thread is attached to 181 * @queryParam comment.commentable_type Resource type the comment thread is attached to 182 * @queryParam comment.message Text of the comment 183 * @queryParam comment.parent_id The id of the comment to reply to, null if not a reply 184 */ 185 public function store() 186 { 187 $user = auth()->user(); 188 189 $params = get_params(request()->all(), 'comment', [ 190 'commentable_id:int', 191 'commentable_type', 192 'message', 193 'parent_id:int', 194 ]); 195 $params['user_id'] = optional($user)->getKey(); 196 197 $comment = new Comment($params); 198 $comment->setCommentable(); 199 200 if ($comment->commentable === null) { 201 abort(422, 'invalid commentable specified'); 202 } 203 204 priv_check('CommentStore', $comment->commentable)->ensureCan(); 205 206 try { 207 $comment->saveOrExplode(); 208 } catch (ModelNotSavedException $e) { 209 return error_popup($e->getMessage()); 210 } 211 212 (new CommentNew($comment, $user))->dispatch(); 213 214 return CommentBundle::forComment($comment)->toArray(); 215 } 216 217 /** 218 * Edit Comment 219 * 220 * Edit an existing comment. 221 * 222 * --- 223 * 224 * ### Response Format 225 * 226 * Returns [CommentBundle](#commentbundle) 227 * 228 * @queryParam comment.message New text of the comment 229 */ 230 public function update($id) 231 { 232 $comment = Comment::findOrFail($id); 233 234 priv_check('CommentUpdate', $comment)->ensureCan(); 235 236 $params = get_params(request()->all(), 'comment', ['message']); 237 $params['edited_by_id'] = auth()->user()->getKey(); 238 $params['edited_at'] = Carbon::now(); 239 $comment->update($params); 240 241 if ($comment->user_id !== auth()->user()->getKey()) { 242 $this->logModerate('LOG_COMMENT_UPDATE', $comment); 243 } 244 245 return CommentBundle::forComment($comment)->toArray(); 246 } 247 248 public function pinDestroy($id) 249 { 250 $comment = Comment::findOrFail($id); 251 252 priv_check('CommentPin', $comment)->ensureCan(); 253 254 $comment->fill(['pinned' => false])->saveOrExplode(); 255 256 return CommentBundle::forComment($comment)->toArray(); 257 } 258 259 public function pinStore($id) 260 { 261 $comment = Comment::findOrFail($id); 262 263 priv_check('CommentPin', $comment)->ensureCan(); 264 265 $comment->fill(['pinned' => true])->saveOrExplode(); 266 267 return CommentBundle::forComment($comment)->toArray(); 268 } 269 270 /** 271 * Remove Comment vote 272 * 273 * Un-upvotes a comment. 274 * 275 * --- 276 * 277 * ### Response Format 278 * 279 * Returns [CommentBundle](#commentbundle) 280 */ 281 public function voteDestroy($id) 282 { 283 $comment = Comment::findOrFail($id); 284 285 priv_check('CommentVote', $comment)->ensureCan(); 286 287 $vote = $comment->votes()->where([ 288 'user_id' => auth()->user()->getKey(), 289 ])->first(); 290 291 optional($vote)->delete(); 292 293 return CommentBundle::forComment($comment->fresh(), false)->toArray(); 294 } 295 296 /** 297 * Add Comment vote 298 * 299 * Upvotes a comment. 300 * 301 * --- 302 * 303 * ### Response Format 304 * 305 * Returns [CommentBundle](#commentbundle) 306 */ 307 public function voteStore($id) 308 { 309 $comment = Comment::findOrFail($id); 310 311 priv_check('CommentVote', $comment)->ensureCan(); 312 313 try { 314 $comment->votes()->create([ 315 'user_id' => auth()->user()->getKey(), 316 ]); 317 } catch (Exception $e) { 318 if (!is_sql_unique_exception($e)) { 319 throw $e; 320 } 321 } 322 323 return CommentBundle::forComment($comment->fresh(), false)->toArray(); 324 } 325 326 private function logModerate($operation, $comment) 327 { 328 $this->log([ 329 'log_type' => Log::LOG_COMMENT_MOD, 330 'log_operation' => $operation, 331 'log_data' => [ 332 'commentable_type' => $comment->commentable_type, 333 'commentable_id' => $comment->commentable_id, 334 'id' => $comment->getKey(), 335 ], 336 ]); 337 } 338}