the browser-facing portion of osu!
at master 10 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 App\Models\Solo\Score; 9use Illuminate\Database\Eloquent\Casts\AsArrayObject; 10 11/** 12 * @property \Carbon\Carbon $created_at 13 * @property string|null $extras_order 14 * @property int $id 15 * @property \Carbon\Carbon $updated_at 16 * @property int|null $user_id 17 */ 18class UserProfileCustomization extends Model 19{ 20 const DEFAULTS = [ 21 'audio_autoplay' => false, 22 'audio_muted' => false, 23 'audio_volume' => 0.45, 24 'beatmapset_card_size' => self::BEATMAPSET_CARD_SIZES[0], 25 'beatmapset_download' => self::BEATMAPSET_DOWNLOAD[0], 26 'beatmapset_show_nsfw' => false, 27 'beatmapset_title_show_original' => false, 28 'comments_show_deleted' => false, 29 'comments_sort' => Comment::DEFAULT_SORT, 30 'extras_order' => self::SECTIONS, 31 'forum_posts_show_deleted' => true, 32 'legacy_score_only' => false, 33 'profile_cover_expanded' => true, 34 'scoring_mode' => self::SCORING_MODES[0], 35 'user_list_filter' => self::USER_LIST['filters']['default'], 36 'user_list_sort' => self::USER_LIST['sorts']['default'], 37 'user_list_view' => self::USER_LIST['views']['default'], 38 ]; 39 40 /** 41 * An array of all possible profile sections, also in their default order. 42 */ 43 const SECTIONS = [ 44 'me', 45 'recent_activity', 46 'top_ranks', 47 'medals', 48 'historical', 49 'beatmaps', 50 'kudosu', 51 ]; 52 53 const BEATMAPSET_CARD_SIZES = ['normal', 'extra']; 54 55 const BEATMAPSET_DOWNLOAD = ['all', 'no_video', 'direct']; 56 57 public const array SCORING_MODES = ['standardised', 'classic']; 58 59 const USER_LIST = [ 60 'filters' => ['all' => ['all', 'online', 'offline'], 'default' => 'all'], 61 'sorts' => ['all' => ['last_visit', 'rank', 'username'], 'default' => 'last_visit'], 62 'views' => ['all' => ['card', 'list', 'brick'], 'default' => 'card'], 63 ]; 64 65 public $incrementing = false; 66 67 protected $casts = [ 68 'options' => AsArrayObject::class, 69 ]; 70 protected $primaryKey = 'user_id'; 71 72 public static function forUser(?User $user): array|static 73 { 74 if ($user === null) { 75 return static::DEFAULTS; 76 } 77 78 $ret = $user->userProfileCustomization; 79 80 if ($ret === null) { 81 $ret = new static(['user_id' => $user->getKey()]); 82 $user->setRelation('userProfileCustomization', $ret); 83 } 84 85 return $ret; 86 } 87 88 public static function repairExtrasOrder(array $value): array 89 { 90 // read from inside out 91 return array_values( 92 // remove duplicate sections from previous merge 93 array_unique( 94 // ensure all sections are included 95 array_merge( 96 // remove invalid sections 97 array_intersect($value, static::SECTIONS), 98 static::SECTIONS 99 ) 100 ) 101 ); 102 } 103 104 public function getAudioAutoplayAttribute() 105 { 106 return $this->options['audio_autoplay'] ?? static::DEFAULTS['audio_autoplay']; 107 } 108 109 public function setAudioAutoplayAttribute($value) 110 { 111 $this->setOption('audio_autoplay', get_bool($value)); 112 } 113 114 public function getAudioMutedAttribute() 115 { 116 return $this->options['audio_muted'] ?? static::DEFAULTS['audio_muted']; 117 } 118 119 public function setAudioMutedAttribute($value) 120 { 121 $this->setOption('audio_muted', get_bool($value)); 122 } 123 124 public function getAudioVolumeAttribute() 125 { 126 return $this->options['audio_volume'] ?? static::DEFAULTS['audio_volume']; 127 } 128 129 public function setAudioVolumeAttribute($value) 130 { 131 $this->setOption('audio_volume', get_float($value)); 132 } 133 134 public function getBeatmapsetCardSizeAttribute() 135 { 136 return $this->options['beatmapset_card_size'] ?? static::DEFAULTS['beatmapset_card_size']; 137 } 138 139 public function setBeatmapsetCardSizeAttribute($value) 140 { 141 if ($value !== null && !in_array($value, static::BEATMAPSET_CARD_SIZES, true)) { 142 $value = null; 143 } 144 145 $this->setOption('beatmapset_card_size', $value); 146 } 147 148 public function getBeatmapsetDownloadAttribute() 149 { 150 return $this->options['beatmapset_download'] ?? static::DEFAULTS['beatmapset_download']; 151 } 152 153 public function setBeatmapsetDownloadAttribute($value) 154 { 155 if ($value !== null && !in_array($value, static::BEATMAPSET_DOWNLOAD, true)) { 156 $value = null; 157 } 158 159 $this->setOption('beatmapset_download', $value); 160 } 161 162 public function getBeatmapsetShowNsfwAttribute() 163 { 164 return $this->options['beatmapset_show_nsfw'] ?? static::DEFAULTS['beatmapset_show_nsfw']; 165 } 166 167 public function setBeatmapsetShowNsfwAttribute($value) 168 { 169 $this->setOption('beatmapset_show_nsfw', get_bool($value)); 170 } 171 172 public function getBeatmapsetTitleShowOriginalAttribute() 173 { 174 return $this->options['beatmapset_title_show_original'] ?? static::DEFAULTS['beatmapset_title_show_original']; 175 } 176 177 public function setBeatmapsetTitleShowOriginalAttribute($value) 178 { 179 $this->setOption('beatmapset_title_show_original', get_bool($value)); 180 } 181 182 public function getCommentsShowDeletedAttribute() 183 { 184 return $this->options['comments_show_deleted'] ?? static::DEFAULTS['comments_show_deleted']; 185 } 186 187 public function setCommentsShowDeletedAttribute($value) 188 { 189 $this->setOption('comments_show_deleted', get_bool($value)); 190 } 191 192 public function getCommentsSortAttribute() 193 { 194 return $this->options['comments_sort'] ?? static::DEFAULTS['comments_sort']; 195 } 196 197 public function setCommentsSortAttribute($value) 198 { 199 if ($value !== null && !array_key_exists($value, Comment::SORTS)) { 200 $value = null; 201 } 202 203 $this->setOption('comments_sort', $value); 204 } 205 206 public function getForumPostsShowDeletedAttribute() 207 { 208 return $this->options['forum_posts_show_deleted'] ?? static::DEFAULTS['forum_posts_show_deleted']; 209 } 210 211 public function setForumPostsShowDeletedAttribute($value) 212 { 213 $this->setOption('forum_posts_show_deleted', get_bool($value)); 214 } 215 216 public function getLegacyScoreOnlyAttribute(): bool 217 { 218 $option = $this->options['legacy_score_only'] ?? null; 219 if ($option === null) { 220 $lastScore = Score::where('user_id', $this->getKey())->last(); 221 if ($lastScore === null) { 222 $option = static::DEFAULTS['legacy_score_only']; 223 } else { 224 $option = $lastScore->isLegacy(); 225 $this->setOption('legacy_score_only', $option); 226 227 try { 228 $this->save(); 229 } catch (\Throwable $e) { 230 if (!is_sql_unique_exception($e)) { 231 throw $e; 232 } 233 } 234 } 235 } 236 237 return $option; 238 } 239 240 public function setLegacyScoreOnlyAttribute($value): void 241 { 242 $this->setOption('legacy_score_only', get_bool($value)); 243 } 244 245 public function getScoringModeAttribute(): string 246 { 247 return $this->options['scoring_mode'] ?? static::DEFAULTS['scoring_mode']; 248 } 249 250 public function setScoringModeAttribute($value): void 251 { 252 if ($value !== null && !in_array($value, static::SCORING_MODES, true)) { 253 $value = null; 254 } 255 256 $this->setOption('scoring_mode', $value); 257 } 258 259 public function getUserListFilterAttribute() 260 { 261 return $this->options['user_list_filter'] ?? static::DEFAULTS['user_list_filter']; 262 } 263 264 public function setUserListFilterAttribute($value) 265 { 266 if ($value !== null && !in_array($value, static::USER_LIST['filters']['all'], true)) { 267 $value = null; 268 } 269 270 $this->setOption('user_list_filter', $value); 271 } 272 273 public function getUserListSortAttribute() 274 { 275 return $this->options['user_list_sort'] ?? static::DEFAULTS['user_list_sort']; 276 } 277 278 public function setUserListSortAttribute($value) 279 { 280 if ($value !== null && !in_array($value, static::USER_LIST['sorts']['all'], true)) { 281 $value = null; 282 } 283 284 $this->setOption('user_list_sort', $value); 285 } 286 287 public function getUserListViewAttribute() 288 { 289 return $this->options['user_list_view'] ?? static::DEFAULTS['user_list_view']; 290 } 291 292 public function setUserListViewAttribute($value) 293 { 294 if ($value !== null && !in_array($value, static::USER_LIST['views']['all'], true)) { 295 $value = null; 296 } 297 298 $this->setOption('user_list_view', $value); 299 } 300 301 public function getExtrasOrderAttribute($value) 302 { 303 $newValue = $this->options['extras_order'] ?? null; 304 305 if ($newValue === null && $value !== null) { 306 $newValue = json_decode($value, true); 307 } 308 309 if ($newValue === null) { 310 return static::DEFAULTS['extras_order']; 311 } 312 313 return static::repairExtrasOrder($newValue); 314 } 315 316 public function setExtrasOrderAttribute($value) 317 { 318 $this->attributes['extras_order'] = null; 319 $this->setOption( 320 'extras_order', 321 $value === null ? null : static::repairExtrasOrder($value), 322 ); 323 } 324 325 public function getProfileCoverExpandedAttribute() 326 { 327 return $this->options['profile_cover_expanded'] ?? static::DEFAULTS['profile_cover_expanded']; 328 } 329 330 public function setProfileCoverExpandedAttribute($value) 331 { 332 $this->setOption('profile_cover_expanded', get_bool($value)); 333 } 334 335 private function setOption($key, $value) 336 { 337 $this->options ??= []; 338 $this->options[$key] = $value; 339 } 340}