. Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. namespace App\Models\Forum; use App\Casts\TimestampOrZero; use App\Models\User; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; /** * @property bool $allow_topic_covers * @property ForumCover $cover * @property int $display_on_index * @property int $enable_icons * @property bool $enable_indexing * @property int $enable_prune * @property bool $enable_sigs * @property string $forum_desc * @property string $forum_desc_bitfield * @property int $forum_desc_options * @property string $forum_desc_uid * @property int $forum_flags * @property int $forum_id * @property string $forum_image * @property int $forum_last_post_id * @property string $forum_last_post_subject * @property \Carbon\Carbon|null $forum_last_post_time * @property string $forum_last_poster_colour * @property int $forum_last_poster_id * @property string $forum_last_poster_name * @property string $forum_link * @property string $forum_name * @property mixed $forum_parents * @property string $forum_password * @property int $forum_posts * @property string $forum_rules * @property string $forum_rules_bitfield * @property string $forum_rules_link * @property int $forum_rules_options * @property string $forum_rules_uid * @property int $forum_status * @property int $forum_style * @property int $forum_topics * @property int $forum_topics_per_page * @property int $forum_topics_real * @property int $forum_type * @property Post $lastPost * @property int $left_id * @property array|null $moderator_groups * @property static $parentForum * @property int $parent_id * @property int $prune_days * @property int $prune_freq * @property int $prune_next * @property int $prune_viewed * @property int $right_id * @property \Illuminate\Database\Eloquent\Collection $subforums static * @property \Illuminate\Database\Eloquent\Collection $topics Topic */ class Forum extends Model { public $timestamps = false; protected $casts = [ 'allow_topic_covers' => 'boolean', 'enable_indexing' => 'boolean', 'enable_sigs' => 'boolean', 'forum_last_post_time' => TimestampOrZero::class, 'moderator_groups' => 'array', ]; protected $primaryKey = 'forum_id'; protected $table = 'phpbb_forums'; public static function lastTopics($forum = null) { $forumForLastTopic = static ::select('forum_id', 'parent_id', 'forum_parents', 'forum_last_post_id') ->with('lastPost.topic'); if ($forum !== null) { $forumForLastTopic->whereIn('forum_id', $forum->allSubforums()); } foreach ($forumForLastTopic->get() as $forum) { if ($forum->lastPost === null) { continue; } $topic = $forum->lastPost->topic; if ($topic === null) { continue; } $forumIds = array_keys($forum->forum_parents); $forumIds[] = $forum->getKey(); foreach ($forumIds as $forumId) { if (!isset($lastTopics[$forumId]) || $topic->topic_last_post_time > $lastTopics[$forumId]->topic_last_post_time) { $lastTopics[$forumId] = $topic; } } } return $lastTopics ?? []; } public static function markAllAsRead(User $user) { $user->update(['user_lastmark' => Carbon::now()]); ForumTrack::where('user_id', $user->getKey())->delete(); TopicTrack::where('user_id', $user->getKey())->delete(); } public function categorySlug() { return 'category-'.str_slug($this->category()); } public function allSubforums($forum_ids = null, $new_forum_ids = null) { if ($forum_ids === null) { $forum_ids = $new_forum_ids = [$this->forum_id]; } $new_forum_ids = model_pluck(static::whereIn('parent_id', $new_forum_ids), 'forum_id'); $new_forum_ids = array_map('intval', $new_forum_ids); $forum_ids = array_merge($forum_ids, $new_forum_ids); if (count($new_forum_ids) === 0) { return $forum_ids; } else { return $this->allSubforums($forum_ids, $new_forum_ids); } } public function categoryId() { if ($this->forum_parents) { return array_keys($this->forum_parents)[0]; } else { return $this->forum_id; } } public function category() { if ($this->forum_parents) { return array_values($this->forum_parents)[0][0]; } else { return $this->forum_name; } } public function topics() { return $this->hasMany(Topic::class); } public function parentForum() { return $this->belongsTo(static::class, 'parent_id'); } public function subforums() { return $this->hasMany(static::class, 'parent_id')->orderBy('left_id'); } public function lastPost() { return $this->belongsTo(Post::class, 'forum_last_post_id', 'post_id'); } public function cover() { return $this->hasOne(ForumCover::class); } public function scopeDisplayList($query) { $query->orderBy('left_id'); } public function scopeSearchable(Builder $query): Builder { return $query->where('enable_indexing', true); } public function setForumParentsAttribute($value) { $this->attributes['forum_parents'] = $value === null || count($value) === 0 ? '' : serialize($value); } /** * Returns array which keys are id of this forum's parents and values are * their names and types. Sorted from topmost parent to immediate parent. * * This method isn't intended to be directly called but through Laravel's * attribute accessor method (in this case, `$forum->forum_parents`) * * warning: don't access this attribute (forum_parents) without selecting * parent_id otherwise returned value may be wrong. * * @param string $value * @return array */ public function getForumParentsAttribute($value) { if ($this->parent_id === 0) { return []; } if (!present($value) && $this->parentForum !== null) { $parentsArray = $this->parentForum->forum_parents; $parentsArray[$this->parentForum->forum_id] = [ $this->parentForum->forum_name, $this->parentForum->forum_type, ]; $this->update(['forum_parents' => $parentsArray]); return $parentsArray; } else { return unserialize($value); } } public function getForumLastPosterColourAttribute($value) { if (present($value)) { return "#{$value}"; } } public function setForumLastPosterColourAttribute($value) { // also functions for casting null to string $this->attributes['forum_last_poster_colour'] = ltrim($value, '#'); } // feature forum shall have extra features like sorting and voting public function isFeatureForum() { $id = $GLOBALS['cfg']['osu']['forum']['feature_forum_id']; return $this->forum_id === $id || isset($this->forum_parents[$id]); } public function isHelpForum() { return $this->forum_id === $GLOBALS['cfg']['osu']['forum']['help_forum_id']; } public function topicsAdded($count) { $this->getConnection()->transaction(function () use ($count) { $this->update([ 'forum_topics' => db_unsigned_increment('forum_topics', $count), 'forum_topics_real' => db_unsigned_increment('forum_topics_real', $count), ]); }); } public function postsAdded($count) { $this->getConnection()->transaction(function () use ($count) { $this->fill([ 'forum_posts' => db_unsigned_increment('forum_posts', $count), ]); $this->setLastPostCache(); $this->save(); }); } public function refreshCache() { $this->getConnection()->transaction(function () { $this->setTopicsCountCache(); $this->setPostCountCache(); $this->setLastPostCache(); $this->saveOrExplode(); }); } public function currentDepth() { return count($this->forum_parents); } public function setTopicsCountCache() { $this->forum_topics_real = $this->topics()->count(); $this->forum_topics = $this->topics()->where('topic_approved', true)->count(); } public function setPostCountCache() { $postCount = $this->forum_topics; $postCount += $this->topics()->sum('topic_replies'); $this->forum_posts = $postCount; } public function setLastPostCache() { $lastTopic = Topic ::whereIn('forum_id', $this->allSubforums()) ->orderBy('topic_last_post_time', 'DESC') ->first(); if ($lastTopic === null) { $this->forum_last_post_id = 0; $this->forum_last_post_time = null; $this->forum_last_post_subject = ''; $this->forum_last_poster_id = 0; $this->forum_last_poster_name = ''; $this->forum_last_poster_colour = ''; } else { $this->forum_last_post_id = $lastTopic->topic_last_post_id; $this->forum_last_post_time = $lastTopic->topic_last_post_time; $this->forum_last_post_subject = $lastTopic->topic_title; $this->forum_last_poster_id = $lastTopic->topic_last_poster_id; $this->forum_last_poster_name = $lastTopic->topic_last_poster_name; $this->forum_last_poster_colour = $lastTopic->topic_last_poster_colour; } } public function isOpen() { return $this->forum_type === 1; } public function markAsRead(User $user, bool $recursive = false) { $forumIds = [$this->getKey()]; if ($recursive) { $forums = static::all(); foreach ($forums as $forum) { if (isset($forum->forum_parents[$this->getKey()])) { $forumIds[] = $forum->getKey(); } } } $this->getConnection()->transaction(function () use ($forumIds, $user) { foreach ($forumIds as $forumId) { $forumTrack = ForumTrack::firstOrNew([ 'user_id' => $user->getKey(), 'forum_id' => $forumId, ]); $forumTrack->mark_time = Carbon::now(); $forumTrack->save(); } TopicTrack::where('user_id', $user->getKey())->whereIn('forum_id', $forumIds)->delete(); }); } }