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\Models\User;
9
10/**
11 * @property bool $mail
12 * @property bool $notify_status
13 * @property Topic $topic
14 * @property int $topic_id
15 * @property User $user
16 * @property int $user_id
17 */
18class TopicWatch extends Model
19{
20 public $incrementing = false;
21 public $timestamps = false;
22
23 protected $casts = [
24 'notify_status' => 'boolean',
25 'mail' => 'boolean',
26 ];
27 protected $table = 'phpbb_topics_watch';
28 protected $primaryKey = ':composite';
29 protected $primaryKeys = ['topic_id', 'user_id'];
30
31 public static function unreadCount($user)
32 {
33 if ($user === null) {
34 return 0;
35 }
36
37 $watch = new static();
38 $topic = new Topic();
39 $track = new TopicTrack();
40
41 return static
42 ::join($topic->getTable(), $topic->qualifyColumn('topic_id'), '=', $watch->qualifyColumn('topic_id'))
43 ->leftJoin($track->getTable(), function ($join) use ($track, $watch) {
44 $join
45 ->on($track->qualifyColumn('topic_id'), '=', $watch->qualifyColumn('topic_id'))
46 ->on($track->qualifyColumn('user_id'), '=', $watch->qualifyColumn('user_id'));
47 })
48 ->where($watch->qualifyColumn('user_id'), '=', $user->user_id)
49 ->where(function ($query) use ($topic, $track) {
50 $query
51 ->whereRaw("{$topic->qualifyColumn('topic_last_post_time')} > {$track->qualifyColumn('mark_time')}")
52 ->orWhereNull($track->qualifyColumn('mark_time'));
53 })
54 ->count();
55 }
56
57 public static function watchStatus($user, $topics)
58 {
59 return static::where('user_id', '=', $user->getKey())
60 ->whereIn('topic_id', $topics->pluck('topic_id'))
61 ->get()
62 ->keyBy('topic_id');
63 }
64
65 public static function lookup($topic, $user)
66 {
67 if ($user === null) {
68 return new static(['topic_id' => $topic->getKey()]);
69 } else {
70 return static::lookupQuery($topic, $user)->first() ?? new static([
71 'topic_id' => $topic->getKey(),
72 'user_id' => $user->getKey(),
73 ]);
74 }
75 }
76
77 public static function setState($topic, $user, $state)
78 {
79 $tries = 0;
80
81 while (true) {
82 $watch = static::lookup($topic, $user);
83
84 try {
85 if ($state === 'not_watching') {
86 $watch->delete();
87 } else {
88 $watch->fill(['mail' => $state === 'watching_mail'])->saveOrExplode();
89 }
90
91 return $watch;
92 } catch (\Throwable $e) {
93 if (is_sql_unique_exception($e) && $tries < 2) {
94 $tries++;
95 } else {
96 throw $e;
97 }
98 }
99 }
100 }
101
102 public function topic()
103 {
104 return $this->belongsTo(Topic::class, 'topic_id');
105 }
106
107 public function user()
108 {
109 return $this->belongsTo(User::class, 'user_id');
110 }
111
112 public function scopeLookupQuery($query, $topic, $user)
113 {
114 if ($user instanceof User) {
115 $userId = $user->getKey();
116 } elseif (is_string($user) || is_int($user)) {
117 $userId = (int) $user;
118 } else {
119 return $query->none();
120 }
121
122 if ($topic instanceof Topic) {
123 $topicId = $topic->getKey();
124 } elseif (is_string($topic) || is_int($topic)) {
125 $topicId = (int) $topic;
126 } else {
127 return $query->none();
128 }
129
130 return $query->where([
131 'topic_id' => $topicId,
132 'user_id' => $userId,
133 ]);
134 }
135
136 public function stateText()
137 {
138 if ($this->exists) {
139 return $this->mail ? 'watching_mail' : 'watching';
140 } else {
141 return 'not_watching';
142 }
143 }
144}