the browser-facing portion of osu!
at master 9.9 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\Models; 7 8use Carbon\Carbon; 9 10/** 11 * @property Beatmapset $beatmapset 12 * @property int $beatmapset_id 13 * @property string|null $comment 14 * @property \Carbon\Carbon|null $created_at 15 * @property int $id 16 * @property mixed|null $type 17 * @property \Carbon\Carbon|null $updated_at 18 * @property User $user 19 * @property int|null $user_id 20 */ 21class BeatmapsetEvent extends Model 22{ 23 const NOMINATE = 'nominate'; 24 const LOVE = 'love'; 25 const REMOVE_FROM_LOVED = 'remove_from_loved'; 26 const QUALIFY = 'qualify'; 27 const DISQUALIFY = 'disqualify'; 28 const APPROVE = 'approve'; 29 const RANK = 'rank'; 30 31 const KUDOSU_ALLOW = 'kudosu_allow'; 32 const KUDOSU_DENY = 'kudosu_deny'; 33 const KUDOSU_GAIN = 'kudosu_gain'; 34 const KUDOSU_LOST = 'kudosu_lost'; 35 const KUDOSU_RECALCULATE = 'kudosu_recalculate'; 36 37 const ISSUE_RESOLVE = 'issue_resolve'; 38 const ISSUE_REOPEN = 'issue_reopen'; 39 40 const DISCUSSION_LOCK = 'discussion_lock'; 41 const DISCUSSION_UNLOCK = 'discussion_unlock'; 42 43 const DISCUSSION_DELETE = 'discussion_delete'; 44 const DISCUSSION_RESTORE = 'discussion_restore'; 45 46 const DISCUSSION_POST_DELETE = 'discussion_post_delete'; 47 const DISCUSSION_POST_RESTORE = 'discussion_post_restore'; 48 49 const NOMINATION_RESET = 'nomination_reset'; 50 const NOMINATION_RESET_RECEIVED = 'nomination_reset_received'; 51 52 const GENRE_EDIT = 'genre_edit'; 53 const LANGUAGE_EDIT = 'language_edit'; 54 const NSFW_TOGGLE = 'nsfw_toggle'; 55 const OFFSET_EDIT = 'offset_edit'; 56 const TAGS_EDIT = 'tags_edit'; 57 58 const BEATMAP_OWNER_CHANGE = 'beatmap_owner_change'; 59 60 public static function log($type, $user, $object, $extraData = []) 61 { 62 if ($object instanceof BeatmapDiscussionPost) { 63 $discussionPostId = $object->getKey(); 64 $discussionId = $object->beatmap_discussion_id; 65 $beatmapsetId = $object->beatmapDiscussion->beatmapset_id; 66 } elseif ($object instanceof BeatmapDiscussion) { 67 $discussionId = $object->getKey(); 68 $beatmapsetId = $object->beatmapset_id; 69 } elseif ($object instanceof Beatmapset) { 70 $beatmapsetId = $object->getKey(); 71 } 72 73 return new static([ 74 'beatmapset_id' => $beatmapsetId, 75 'user_id' => isset($user) ? $user->getKey() : null, 76 'type' => $type, 77 'comment' => array_merge([ 78 'beatmap_discussion_id' => $discussionId ?? null, 79 'beatmap_discussion_post_id' => $discussionPostId ?? null, 80 ], $extraData), 81 ]); 82 } 83 84 public static function search($rawParams = []) 85 { 86 [$query, $params] = static::searchQueryAndParams($rawParams); 87 88 $searchByUser = present($rawParams['user'] ?? null); 89 $isModerator = $rawParams['is_moderator'] ?? false; 90 91 if ($searchByUser) { 92 $params['user'] = $rawParams['user']; 93 $findAll = $isModerator || (($rawParams['current_user_id'] ?? null) === $rawParams['user']); 94 $user = User::lookup($params['user'], null, $findAll); 95 96 if ($user === null) { 97 $query->none(); 98 } else { 99 $query->where('user_id', '=', $user->getKey()); 100 } 101 } 102 103 if (present($rawParams['beatmapset_id'] ?? null)) { 104 $params['beatmapset_id'] = $rawParams['beatmapset_id']; 105 $query->where('beatmapset_id', '=', $params['beatmapset_id']); 106 } 107 108 $sortParam = presence(get_string($rawParams['sort'] ?? null)); 109 if (isset($sortParam)) { 110 $sort = explode('_', strtolower($sortParam)); 111 112 if (in_array($sort[0] ?? null, ['id'], true)) { 113 $sortField = $sort[0]; 114 } 115 116 if (in_array($sort[1] ?? null, ['asc', 'desc'], true)) { 117 $sortOrder = $sort[1]; 118 } 119 } 120 121 $sortField ??= 'id'; 122 $sortOrder ??= 'desc'; 123 124 if ($sortField !== 'id' && $sortOrder !== 'desc') { 125 $params['sort'] = "{$sortField}_{$sortOrder}"; 126 } 127 128 $query->orderBy($sortField, $sortOrder); 129 130 $params['types'] = []; 131 132 if (get_string($rawParams['type'] ?? null) !== null) { 133 $params['types'][] = $rawParams['type']; 134 } 135 136 if (isset($rawParams['types'])) { 137 $params['types'] = array_merge($params['types'], get_arr($rawParams['types'], 'get_string') ?? []); 138 } 139 140 if ($searchByUser) { 141 $allowedTypes = static::types('public'); 142 if ($isModerator) { 143 $allowedTypes = array_merge($allowedTypes, static::types('moderation')); 144 } 145 if ($rawParams['is_kudosu_moderator'] ?? false) { 146 $allowedTypes = array_merge($allowedTypes, static::types('kudosuModeration')); 147 } 148 } else { 149 $allowedTypes = static::types('all'); 150 } 151 152 $params['types'] = array_intersect($params['types'], $allowedTypes); 153 154 if (empty($params['types'])) { 155 if ($searchByUser) { 156 $query->whereIn('type', $allowedTypes); 157 } 158 } else { 159 $query->whereIn('type', $params['types']); 160 } 161 162 if (isset($rawParams['min_date'])) { 163 $timestamp = strtotime($rawParams['min_date']); 164 165 if ($timestamp !== false) { 166 $minDate = Carbon::createFromTimestamp($timestamp)->startOfDay(); 167 $params['min_date'] = json_date($minDate); 168 $query->where('created_at', '>=', $minDate); 169 } 170 } 171 172 if (isset($rawParams['max_date'])) { 173 $timestamp = strtotime($rawParams['max_date']); 174 175 if ($timestamp !== false) { 176 $maxDate = Carbon::createFromTimestamp($timestamp)->endOfDay(); 177 $params['max_date'] = json_date($maxDate); 178 $query->where('created_at', '<=', $maxDate); 179 } 180 } 181 182 return ['query' => $query, 'params' => $params]; 183 } 184 185 /** 186 * Currently used for: 187 * - generating type filter checkboxes in events index page 188 * - searching by user should limit the allowed types 189 * - checking whether or not user id should be visible 190 * Order affects how they're displayed. 191 */ 192 public static function types($privilege) 193 { 194 static $ret; 195 196 if ($ret === null) { 197 $ret = [ 198 'public' => [ 199 static::NOMINATE, 200 static::QUALIFY, 201 static::RANK, 202 static::LOVE, 203 static::NOMINATION_RESET, 204 static::NOMINATION_RESET_RECEIVED, 205 static::DISQUALIFY, 206 static::REMOVE_FROM_LOVED, 207 208 static::KUDOSU_GAIN, 209 static::KUDOSU_LOST, 210 211 static::GENRE_EDIT, 212 static::LANGUAGE_EDIT, 213 static::NSFW_TOGGLE, 214 static::OFFSET_EDIT, 215 216 static::ISSUE_RESOLVE, 217 static::ISSUE_REOPEN, 218 219 static::BEATMAP_OWNER_CHANGE, 220 ], 221 'kudosuModeration' => [ 222 static::KUDOSU_ALLOW, 223 static::KUDOSU_DENY, 224 ], 225 'moderation' => [ 226 static::APPROVE, // not actually used 227 228 static::KUDOSU_RECALCULATE, 229 230 static::DISCUSSION_DELETE, 231 static::DISCUSSION_RESTORE, 232 233 static::DISCUSSION_POST_DELETE, 234 static::DISCUSSION_POST_RESTORE, 235 ], 236 ]; 237 } 238 239 if ($privilege === 'all' && !isset($ret['all'])) { 240 $all = []; 241 242 foreach ($ret as $_priv => $types) { 243 $all = array_merge($all, $types); 244 } 245 246 $ret['all'] = $all; 247 } 248 249 return $ret[$privilege]; 250 } 251 252 public function beatmapset() 253 { 254 // FIXME: consistency with BeatmapDiscussion which includes deleted. 255 return $this->belongsTo(Beatmapset::class, 'beatmapset_id'); 256 } 257 258 public function getBeatmapDiscussionIdAttribute() 259 { 260 return $this->comment['beatmap_discussion_id'] ?? null; 261 } 262 263 public function getNominationModesAttribute() 264 { 265 if ($this->type !== self::NOMINATE) { 266 return null; 267 } 268 269 return $this->comment['modes'] ?? []; 270 } 271 272 public function beatmapDiscussion() 273 { 274 return $this->belongsTo(BeatmapDiscussion::class, 'beatmap_discussion_id'); 275 } 276 277 public function user() 278 { 279 return $this->belongsTo(User::class, 'user_id'); 280 } 281 282 public function scopeNominations($query) 283 { 284 return $query->where('type', self::NOMINATE); 285 } 286 287 public function scopeNominationResetReceiveds($query) 288 { 289 return $query->where('type', self::NOMINATION_RESET_RECEIVED); 290 } 291 292 public function scopeNominationResets($query) 293 { 294 return $query->where('type', self::NOMINATION_RESET); 295 } 296 297 public function scopeDisqualifications($query) 298 { 299 return $query->where('type', self::DISQUALIFY); 300 } 301 302 public function scopeDisqualificationAndNominationResetEvents($query) 303 { 304 return $query->whereIn('type', [self::DISQUALIFY, self::NOMINATION_RESET]); 305 } 306 307 public function getCommentAttribute($value) 308 { 309 return json_decode($value ?? '', true) ?? $value; 310 } 311 312 public function setCommentAttribute($value) 313 { 314 $this->attributes['comment'] = is_array($value) ? json_encode($value) : $value; 315 } 316}