the browser-facing portion of osu!
at master 514 lines 14 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\Transformers; 7 8use App\Libraries\MorphMap; 9use App\Libraries\Search\ScoreSearchParams; 10use App\Models\Beatmap; 11use App\Models\User; 12use App\Models\UserProfileCustomization; 13use Illuminate\Support\Arr; 14use League\Fractal\Resource\ResourceInterface; 15 16class UserCompactTransformer extends TransformerAbstract 17{ 18 const CARD_INCLUDES = [ 19 'country', 20 'cover', 21 'groups', 22 'team', 23 ]; 24 25 const CARD_INCLUDES_PRELOAD = [ 26 'team', 27 'userGroups', 28 ]; 29 30 // Paired with static::listIncludesPreload 31 const LIST_INCLUDES = [ 32 ...self::CARD_INCLUDES, 33 'statistics', 34 'support_level', 35 ]; 36 37 const PROFILE_HEADER_INCLUDES = [ 38 'active_tournament_banner', 39 'active_tournament_banners', 40 'badges', 41 'comments_count', 42 'follower_count', 43 'groups', 44 'mapping_follower_count', 45 'previous_usernames', 46 'support_level', 47 ]; 48 49 protected string $mode; 50 51 protected array $availableIncludes = [ 52 'account_history', 53 'active_tournament_banner', // deprecated 54 'active_tournament_banners', 55 'badges', 56 'beatmap_playcounts_count', 57 'blocks', 58 'comments_count', 59 'country', 60 'cover', 61 'daily_challenge_user_stats', 62 'favourite_beatmapset_count', 63 'follow_user_mapping', 64 'follower_count', 65 'friends', 66 'graveyard_beatmapset_count', 67 'groups', 68 'guest_beatmapset_count', 69 'is_admin', 70 'is_bng', 71 'is_full_bn', 72 'is_gmt', 73 'is_limited_bn', 74 'is_moderator', 75 'is_nat', 76 'is_restricted', 77 'is_silenced', 78 'kudosu', 79 'loved_beatmapset_count', 80 'mapping_follower_count', 81 'monthly_playcounts', 82 'nominated_beatmapset_count', 83 'page', 84 'pending_beatmapset_count', 85 'previous_usernames', 86 'rank_highest', 87 'ranked_beatmapset_count', 88 'replays_watched_counts', 89 'scores_best_count', 90 'scores_first_count', 91 'scores_pinned_count', 92 'scores_recent_count', 93 'session_verified', 94 'statistics', 95 'statistics_rulesets', 96 'support_level', 97 'team', 98 'unread_pm_count', 99 'user_achievements', 100 'user_preferences', 101 102 // TODO: should be alphabetically ordered but lazer relies on being after statistics. 103 'rank_history', 104 ]; 105 106 protected $permissions = [ 107 'friends' => 'IsNotOAuth', 108 'is_admin' => 'IsNotOAuth', 109 'is_bng' => 'IsNotOAuth', 110 'is_full_bn' => 'IsNotOAuth', 111 'is_gmt' => 'IsNotOAuth', 112 'is_limited_bn' => 'IsNotOAuth', 113 'is_moderator' => 'IsNotOAuth', 114 'is_nat' => 'IsNotOAuth', 115 'is_restricted' => 'UserShowRestrictedStatus', 116 'is_silenced' => 'IsNotOAuth', 117 ]; 118 119 public static function listIncludesPreload(string $rulesetName): array 120 { 121 return [ 122 ...static::CARD_INCLUDES_PRELOAD, 123 User::statisticsRelationName($rulesetName), 124 'supporterTagPurchases', 125 ]; 126 } 127 128 public function transform(User $user) 129 { 130 return [ 131 'avatar_url' => $user->user_avatar, 132 'country_code' => $user->country_acronym, 133 'default_group' => $user->defaultGroup()->identifier, 134 'id' => $user->user_id, 135 'is_active' => $user->isActive(), 136 'is_bot' => $user->isBot(), 137 'is_deleted' => $user->trashed(), 138 'is_online' => $user->isOnline(), 139 'is_supporter' => $user->isSupporter(), 140 'last_visit' => json_time($user->displayed_last_visit), 141 'pm_friends_only' => $user->pm_friends_only, 142 'profile_colour' => $user->user_colour, 143 'username' => $user->username, 144 ]; 145 } 146 147 public function includeAccountHistory(User $user) 148 { 149 $histories = $user->accountHistories()->recent(); 150 151 if (!priv_check('UserSilenceShowExtendedInfo')->can()) { 152 $histories->default(); 153 } else { 154 $histories->with('actor'); 155 } 156 157 return $this->collection( 158 $histories->get(), 159 new UserAccountHistoryTransformer() 160 ); 161 } 162 163 public function includeActiveTournamentBanner(User $user) 164 { 165 $banner = $user->profileBannersActive->last(); 166 167 return $banner === null 168 ? $this->primitive(null) 169 : $this->item($banner, new ProfileBannerTransformer()); 170 } 171 172 public function includeActiveTournamentBanners(User $user) 173 { 174 return $this->collection( 175 $user->profileBannersActive, 176 new ProfileBannerTransformer(), 177 ); 178 } 179 180 public function includeBadges(User $user) 181 { 182 return $this->collection( 183 $user->badges()->orderBy('awarded', 'DESC')->get(), 184 new UserBadgeTransformer() 185 ); 186 } 187 188 public function includeBeatmapPlaycountsCount(User $user) 189 { 190 return $this->primitive($user->beatmapPlaycounts()->count()); 191 } 192 193 public function includeBlocks(User $user) 194 { 195 return $this->collection( 196 $user->relations()->blocks()->get(), 197 new UserRelationTransformer() 198 ); 199 } 200 201 public function includeCommentsCount(User $user) 202 { 203 return $this->primitive($user->comments()->withoutTrashed()->count()); 204 } 205 206 public function includeCountry(User $user) 207 { 208 $countryAcronym = $user->country_acronym; 209 $country = $countryAcronym === null 210 ? null 211 : app('countries')->byCode($countryAcronym); 212 213 return $country === null 214 ? $this->primitive(null) 215 : $this->item($country, new CountryTransformer()); 216 } 217 218 public function includeCover(User $user) 219 { 220 $cover = $user->cover(); 221 222 return $this->primitive([ 223 'custom_url' => $cover->customUrl(), 224 'url' => $cover->url(), 225 // cast to string for backward compatibility 226 'id' => get_string($user->cover_preset_id), 227 ]); 228 } 229 230 public function includeDailyChallengeUserStats(User $user) 231 { 232 return $this->item( 233 $user->dailyChallengeUserStats ?? $user->dailyChallengeUserStats()->make(), 234 new DailyChallengeUserStatsTransformer(), 235 ); 236 } 237 238 public function includeFavouriteBeatmapsetCount(User $user) 239 { 240 return $this->primitive($user->profileBeatmapsetsFavourite()->count()); 241 } 242 243 public function includeFollowUserMapping(User $user) 244 { 245 return $this->primitive( 246 $user->follows()->where([ 247 'notifiable_type' => MorphMap::getType($user), 248 'subtype' => 'mapping', 249 ])->pluck('notifiable_id') 250 ); 251 } 252 253 public function includeFollowerCount(User $user) 254 { 255 return $this->primitive($user->followerCount()); 256 } 257 258 public function includeFriends(User $user) 259 { 260 return $this->collection( 261 $user->relationFriends, 262 new UserRelationTransformer() 263 ); 264 } 265 266 public function includeGraveyardBeatmapsetCount(User $user) 267 { 268 return $this->primitive($user->profileBeatmapsetCountByGroupedStatus('graveyard')); 269 } 270 271 public function includeGroups(User $user) 272 { 273 return $this->collection($user->userGroupsForBadges(), new UserGroupTransformer()); 274 } 275 276 public function includeGuestBeatmapsetCount(User $user) 277 { 278 return $this->primitive($user->profileBeatmapsetsGuest()->count()); 279 } 280 281 public function includeIsAdmin(User $user) 282 { 283 return $this->primitive($user->isAdmin()); 284 } 285 286 public function includeIsBng(User $user) 287 { 288 return $this->primitive($user->isBNG()); 289 } 290 291 public function includeIsFullBn(User $user) 292 { 293 return $this->primitive($user->isFullBN()); 294 } 295 296 public function includeIsGmt(User $user) 297 { 298 return $this->primitive($user->isGMT()); 299 } 300 301 public function includeIsLimitedBn(User $user) 302 { 303 return $this->primitive($user->isLimitedBN()); 304 } 305 306 public function includeIsModerator(User $user) 307 { 308 return $this->primitive($user->isModerator()); 309 } 310 311 public function includeIsNat(User $user) 312 { 313 return $this->primitive($user->isNAT()); 314 } 315 316 public function includeIsRestricted(User $user) 317 { 318 return $this->primitive($user->isRestricted()); 319 } 320 321 public function includeIsSilenced(User $user) 322 { 323 return $this->primitive($user->isSilenced()); 324 } 325 326 public function includeKudosu(User $user): ResourceInterface 327 { 328 return $this->primitive([ 329 'available' => $user->osu_kudosavailable, 330 'total' => $user->osu_kudostotal, 331 ]); 332 } 333 334 public function includeLovedBeatmapsetCount(User $user) 335 { 336 return $this->primitive($user->profileBeatmapsetCountByGroupedStatus('loved')); 337 } 338 339 public function includeMappingFollowerCount(User $user) 340 { 341 return $this->primitive($user->mappingFollowerCount()); 342 } 343 344 public function includeMonthlyPlaycounts(User $user) 345 { 346 return $this->collection( 347 $user->monthlyPlaycounts, 348 new UserMonthlyPlaycountTransformer() 349 ); 350 } 351 352 public function includeNominatedBeatmapsetCount(User $user) 353 { 354 return $this->primitive($user->profileBeatmapsetsNominated()->count()); 355 } 356 357 public function includePage(User $user) 358 { 359 return $this->primitive( 360 $user->userPage === null 361 ? ['html' => '', 'raw' => ''] 362 : [ 363 'html' => $user->userPage->bodyHTML(['modifiers' => ['profile-page']]), 364 'raw' => $user->userPage->bodyRaw, 365 ] 366 ); 367 } 368 369 public function includePendingBeatmapsetCount(User $user) 370 { 371 return $this->primitive($user->profileBeatmapsetCountByGroupedStatus('pending')); 372 } 373 374 public function includePreviousUsernames(User $user) 375 { 376 return $this->primitive($user->previousUsernames()->unique()->values()->toArray()); 377 } 378 379 public function includeRankHighest(User $user): ResourceInterface 380 { 381 $rankHighest = $user->rankHighests() 382 ->where('mode', Beatmap::modeInt($this->mode)) 383 ->first(); 384 385 return $rankHighest === null 386 ? $this->null() 387 : $this->item($rankHighest, new RankHighestTransformer()); 388 } 389 390 public function includeRankHistory(User $user) 391 { 392 $rankHistoryData = $user->rankHistories() 393 ->where('mode', Beatmap::modeInt($this->mode)) 394 ->first() 395 ?->setRelation('user', $user); 396 397 return $rankHistoryData === null 398 ? $this->primitive(null) 399 : $this->item($rankHistoryData, new RankHistoryTransformer()); 400 } 401 402 public function includeRankedBeatmapsetCount(User $user) 403 { 404 return $this->primitive($user->profileBeatmapsetCountByGroupedStatus('ranked')); 405 } 406 407 public function includeReplaysWatchedCounts(User $user) 408 { 409 return $this->collection( 410 $user->replaysWatchedCounts, 411 new UserReplaysWatchedCountTransformer() 412 ); 413 } 414 415 public function includeScoresBestCount(User $user) 416 { 417 return $this->primitive(count($user->beatmapBestScoreIds( 418 $this->mode, 419 ScoreSearchParams::showLegacyForUser(\Auth::user()), 420 ))); 421 } 422 423 public function includeScoresFirstCount(User $user) 424 { 425 return $this->primitive($user->scoresFirst($this->mode, true)->count()); 426 } 427 428 public function includeScoresPinnedCount(User $user) 429 { 430 return $this->primitive($user->scorePins()->forRuleset($this->mode)->withVisibleScore()->count()); 431 } 432 433 public function includeScoresRecentCount(User $user) 434 { 435 return $this->primitive($user->soloScores()->recent($this->mode, false)->count()); 436 } 437 438 public function includeSessionVerified(User $user) 439 { 440 return $this->primitive($user->token()?->isVerified() ?? false); 441 } 442 443 public function includeStatistics(User $user) 444 { 445 $stats = $user->statistics($this->mode); 446 447 return $this->item($stats, new UserStatisticsTransformer()); 448 } 449 450 public function includeStatisticsRulesets(User $user) 451 { 452 return $this->item($user, new UserStatisticsRulesetsTransformer()); 453 } 454 455 public function includeSupportLevel(User $user) 456 { 457 return $this->primitive($user->supportLevel()); 458 } 459 460 public function includeTeam(User $user) 461 { 462 return ($team = $user->team) === null 463 ? $this->null() 464 : $this->item($team, new TeamTransformer()); 465 } 466 467 public function includeUnreadPmCount(User $user) 468 { 469 // legacy pm has been turned off 470 return $this->primitive(0); 471 } 472 473 public function includeUserAchievements(User $user) 474 { 475 return $this->collection( 476 $user->userAchievements()->orderBy('date', 'desc')->get(), 477 new UserAchievementTransformer() 478 ); 479 } 480 481 public function includeUserPreferences(User $user) 482 { 483 static $fields = [ 484 'audio_autoplay', 485 'audio_muted', 486 'audio_volume', 487 'beatmapset_card_size', 488 'beatmapset_download', 489 'beatmapset_show_nsfw', 490 'beatmapset_title_show_original', 491 'comments_show_deleted', 492 'forum_posts_show_deleted', 493 'legacy_score_only', 494 'profile_cover_expanded', 495 'scoring_mode', 496 'user_list_filter', 497 'user_list_sort', 498 'user_list_view', 499 ]; 500 501 $customization = $user->userProfileCustomization; 502 503 return $this->primitive($customization === null 504 ? Arr::only(UserProfileCustomization::DEFAULTS, $fields) 505 : $customization->only($fields)); 506 } 507 508 public function setMode(string $mode) 509 { 510 $this->mode = $mode; 511 512 return $this; 513 } 514}