the browser-facing portion of osu!
at master 11 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\Forum; 7 8use App\Casts\TimestampOrZero; 9use App\Models\User; 10use Carbon\Carbon; 11use Illuminate\Database\Eloquent\Builder; 12 13/** 14 * @property bool $allow_topic_covers 15 * @property ForumCover $cover 16 * @property int $display_on_index 17 * @property int $enable_icons 18 * @property bool $enable_indexing 19 * @property int $enable_prune 20 * @property bool $enable_sigs 21 * @property string $forum_desc 22 * @property string $forum_desc_bitfield 23 * @property int $forum_desc_options 24 * @property string $forum_desc_uid 25 * @property int $forum_flags 26 * @property int $forum_id 27 * @property string $forum_image 28 * @property int $forum_last_post_id 29 * @property string $forum_last_post_subject 30 * @property \Carbon\Carbon|null $forum_last_post_time 31 * @property string $forum_last_poster_colour 32 * @property int $forum_last_poster_id 33 * @property string $forum_last_poster_name 34 * @property string $forum_link 35 * @property string $forum_name 36 * @property mixed $forum_parents 37 * @property string $forum_password 38 * @property int $forum_posts 39 * @property string $forum_rules 40 * @property string $forum_rules_bitfield 41 * @property string $forum_rules_link 42 * @property int $forum_rules_options 43 * @property string $forum_rules_uid 44 * @property int $forum_status 45 * @property int $forum_style 46 * @property int $forum_topics 47 * @property int $forum_topics_per_page 48 * @property int $forum_topics_real 49 * @property int $forum_type 50 * @property Post $lastPost 51 * @property int $left_id 52 * @property array|null $moderator_groups 53 * @property static $parentForum 54 * @property int $parent_id 55 * @property int $prune_days 56 * @property int $prune_freq 57 * @property int $prune_next 58 * @property int $prune_viewed 59 * @property int $right_id 60 * @property \Illuminate\Database\Eloquent\Collection $subforums static 61 * @property \Illuminate\Database\Eloquent\Collection $topics Topic 62 */ 63class Forum extends Model 64{ 65 public $timestamps = false; 66 67 protected $casts = [ 68 'allow_topic_covers' => 'boolean', 69 'enable_indexing' => 'boolean', 70 'enable_sigs' => 'boolean', 71 'forum_last_post_time' => TimestampOrZero::class, 72 'moderator_groups' => 'array', 73 ]; 74 protected $primaryKey = 'forum_id'; 75 protected $table = 'phpbb_forums'; 76 77 public static function lastTopics($forum = null) 78 { 79 $forumForLastTopic = static 80 ::select('forum_id', 'parent_id', 'forum_parents', 'forum_last_post_id') 81 ->with('lastPost.topic'); 82 83 if ($forum !== null) { 84 $forumForLastTopic->whereIn('forum_id', $forum->allSubforums()); 85 } 86 87 foreach ($forumForLastTopic->get() as $forum) { 88 if ($forum->lastPost === null) { 89 continue; 90 } 91 92 $topic = $forum->lastPost->topic; 93 94 if ($topic === null) { 95 continue; 96 } 97 98 $forumIds = array_keys($forum->forum_parents); 99 $forumIds[] = $forum->getKey(); 100 101 foreach ($forumIds as $forumId) { 102 if (!isset($lastTopics[$forumId]) || $topic->topic_last_post_time > $lastTopics[$forumId]->topic_last_post_time) { 103 $lastTopics[$forumId] = $topic; 104 } 105 } 106 } 107 108 return $lastTopics ?? []; 109 } 110 111 public static function markAllAsRead(User $user) 112 { 113 $user->update(['user_lastmark' => Carbon::now()]); 114 ForumTrack::where('user_id', $user->getKey())->delete(); 115 TopicTrack::where('user_id', $user->getKey())->delete(); 116 } 117 118 public function categorySlug() 119 { 120 return 'category-'.str_slug($this->category()); 121 } 122 123 public function allSubforums($forum_ids = null, $new_forum_ids = null) 124 { 125 if ($forum_ids === null) { 126 $forum_ids = $new_forum_ids = [$this->forum_id]; 127 } 128 $new_forum_ids = model_pluck(static::whereIn('parent_id', $new_forum_ids), 'forum_id'); 129 130 $new_forum_ids = array_map('intval', $new_forum_ids); 131 $forum_ids = array_merge($forum_ids, $new_forum_ids); 132 133 if (count($new_forum_ids) === 0) { 134 return $forum_ids; 135 } else { 136 return $this->allSubforums($forum_ids, $new_forum_ids); 137 } 138 } 139 140 public function categoryId() 141 { 142 if ($this->forum_parents) { 143 return array_keys($this->forum_parents)[0]; 144 } else { 145 return $this->forum_id; 146 } 147 } 148 149 public function category() 150 { 151 if ($this->forum_parents) { 152 return array_values($this->forum_parents)[0][0]; 153 } else { 154 return $this->forum_name; 155 } 156 } 157 158 public function topics() 159 { 160 return $this->hasMany(Topic::class); 161 } 162 163 public function parentForum() 164 { 165 return $this->belongsTo(static::class, 'parent_id'); 166 } 167 168 public function subforums() 169 { 170 return $this->hasMany(static::class, 'parent_id')->orderBy('left_id'); 171 } 172 173 public function lastPost() 174 { 175 return $this->belongsTo(Post::class, 'forum_last_post_id', 'post_id'); 176 } 177 178 public function cover() 179 { 180 return $this->hasOne(ForumCover::class); 181 } 182 183 public function scopeDisplayList($query) 184 { 185 $query->orderBy('left_id'); 186 } 187 188 public function scopeSearchable(Builder $query): Builder 189 { 190 return $query->where('enable_indexing', true); 191 } 192 193 public function setForumParentsAttribute($value) 194 { 195 $this->attributes['forum_parents'] = $value === null || count($value) === 0 ? '' : serialize($value); 196 } 197 198 /** 199 * Returns array which keys are id of this forum's parents and values are 200 * their names and types. Sorted from topmost parent to immediate parent. 201 * 202 * This method isn't intended to be directly called but through Laravel's 203 * attribute accessor method (in this case, `$forum->forum_parents`) 204 * 205 * warning: don't access this attribute (forum_parents) without selecting 206 * parent_id otherwise returned value may be wrong. 207 * 208 * @param string $value 209 * @return array 210 */ 211 public function getForumParentsAttribute($value) 212 { 213 if ($this->parent_id === 0) { 214 return []; 215 } 216 217 if (!present($value) && $this->parentForum !== null) { 218 $parentsArray = $this->parentForum->forum_parents; 219 $parentsArray[$this->parentForum->forum_id] = [ 220 $this->parentForum->forum_name, 221 $this->parentForum->forum_type, 222 ]; 223 224 $this->update(['forum_parents' => $parentsArray]); 225 226 return $parentsArray; 227 } else { 228 return unserialize($value); 229 } 230 } 231 232 public function getForumLastPosterColourAttribute($value) 233 { 234 if (present($value)) { 235 return "#{$value}"; 236 } 237 } 238 239 public function setForumLastPosterColourAttribute($value) 240 { 241 // also functions for casting null to string 242 $this->attributes['forum_last_poster_colour'] = ltrim($value, '#'); 243 } 244 245 // feature forum shall have extra features like sorting and voting 246 public function isFeatureForum() 247 { 248 $id = $GLOBALS['cfg']['osu']['forum']['feature_forum_id']; 249 250 return $this->forum_id === $id || isset($this->forum_parents[$id]); 251 } 252 253 public function isHelpForum() 254 { 255 return $this->forum_id === $GLOBALS['cfg']['osu']['forum']['help_forum_id']; 256 } 257 258 public function topicsAdded($count) 259 { 260 $this->getConnection()->transaction(function () use ($count) { 261 $this->update([ 262 'forum_topics' => db_unsigned_increment('forum_topics', $count), 263 'forum_topics_real' => db_unsigned_increment('forum_topics_real', $count), 264 ]); 265 }); 266 } 267 268 public function postsAdded($count) 269 { 270 $this->getConnection()->transaction(function () use ($count) { 271 $this->fill([ 272 'forum_posts' => db_unsigned_increment('forum_posts', $count), 273 ]); 274 $this->setLastPostCache(); 275 276 $this->save(); 277 }); 278 } 279 280 public function refreshCache() 281 { 282 $this->getConnection()->transaction(function () { 283 $this->setTopicsCountCache(); 284 $this->setPostCountCache(); 285 $this->setLastPostCache(); 286 287 $this->saveOrExplode(); 288 }); 289 } 290 291 public function currentDepth() 292 { 293 return count($this->forum_parents); 294 } 295 296 public function setTopicsCountCache() 297 { 298 $this->forum_topics_real = $this->topics()->count(); 299 $this->forum_topics = $this->topics()->where('topic_approved', true)->count(); 300 } 301 302 public function setPostCountCache() 303 { 304 $postCount = $this->forum_topics; 305 $postCount += $this->topics()->sum('topic_replies'); 306 307 $this->forum_posts = $postCount; 308 } 309 310 public function setLastPostCache() 311 { 312 $lastTopic = Topic 313 ::whereIn('forum_id', $this->allSubforums()) 314 ->orderBy('topic_last_post_time', 'DESC') 315 ->first(); 316 317 if ($lastTopic === null) { 318 $this->forum_last_post_id = 0; 319 $this->forum_last_post_time = null; 320 $this->forum_last_post_subject = ''; 321 $this->forum_last_poster_id = 0; 322 $this->forum_last_poster_name = ''; 323 $this->forum_last_poster_colour = ''; 324 } else { 325 $this->forum_last_post_id = $lastTopic->topic_last_post_id; 326 $this->forum_last_post_time = $lastTopic->topic_last_post_time; 327 $this->forum_last_post_subject = $lastTopic->topic_title; 328 $this->forum_last_poster_id = $lastTopic->topic_last_poster_id; 329 $this->forum_last_poster_name = $lastTopic->topic_last_poster_name; 330 $this->forum_last_poster_colour = $lastTopic->topic_last_poster_colour; 331 } 332 } 333 334 public function isOpen() 335 { 336 return $this->forum_type === 1; 337 } 338 339 public function markAsRead(User $user, bool $recursive = false) 340 { 341 $forumIds = [$this->getKey()]; 342 343 if ($recursive) { 344 $forums = static::all(); 345 346 foreach ($forums as $forum) { 347 if (isset($forum->forum_parents[$this->getKey()])) { 348 $forumIds[] = $forum->getKey(); 349 } 350 } 351 } 352 353 $this->getConnection()->transaction(function () use ($forumIds, $user) { 354 foreach ($forumIds as $forumId) { 355 $forumTrack = ForumTrack::firstOrNew([ 356 'user_id' => $user->getKey(), 357 'forum_id' => $forumId, 358 ]); 359 $forumTrack->mark_time = Carbon::now(); 360 $forumTrack->save(); 361 } 362 363 TopicTrack::where('user_id', $user->getKey())->whereIn('forum_id', $forumIds)->delete(); 364 }); 365 } 366}