Commit | Line | Data |
---|---|---|
df8f8628 | 1 | <?php |
fea86294 | 2 | |
df8f8628 | 3 | namespace wcf\system\conversation; |
fea86294 | 4 | |
df8f8628 | 5 | use wcf\system\database\util\PreparedStatementConditionBuilder; |
88cde7b3 | 6 | use wcf\system\exception\NamedUserException; |
90f61ed9 | 7 | use wcf\system\exception\PermissionDeniedException; |
2ab1f24b | 8 | use wcf\system\flood\FloodControl; |
df8f8628 | 9 | use wcf\system\SingletonFactory; |
fea86294 | 10 | use wcf\system\user\storage\UserStorageHandler; |
df8f8628 MW |
11 | use wcf\system\WCF; |
12 | ||
db864366 MS |
13 | /** |
14 | * Handles the number of conversations and unread conversations of the active user. | |
fea86294 | 15 | * |
e23349a0 MW |
16 | * @author Marcel Werk |
17 | * @copyright 2001-2024 WoltLab GmbH | |
18 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
db864366 | 19 | */ |
e23349a0 | 20 | final class ConversationHandler extends SingletonFactory |
fea86294 TD |
21 | { |
22 | /** | |
23 | * number of unread conversations | |
c85e9df8 | 24 | * @var int[] |
fea86294 | 25 | */ |
e23349a0 | 26 | private array $unreadConversationCount = []; |
fea86294 TD |
27 | |
28 | /** | |
29 | * number of conversations | |
c85e9df8 | 30 | * @var int[] |
fea86294 | 31 | */ |
e23349a0 | 32 | private array $conversationCount = []; |
fea86294 TD |
33 | |
34 | /** | |
35 | * Returns the number of unread conversations for given user. | |
fea86294 | 36 | */ |
e23349a0 | 37 | public function getUnreadConversationCount(?int $userID = null, bool $skipCache = false): int |
fea86294 TD |
38 | { |
39 | if ($userID === null) { | |
40 | $userID = WCF::getUser()->userID; | |
41 | } | |
42 | ||
e23349a0 MW |
43 | if (!$userID) { |
44 | return 0; | |
45 | } | |
46 | ||
fea86294 TD |
47 | if (!isset($this->unreadConversationCount[$userID]) || $skipCache) { |
48 | $this->unreadConversationCount[$userID] = 0; | |
49 | ||
50 | // load storage data | |
51 | UserStorageHandler::getInstance()->loadStorage([$userID]); | |
52 | ||
53 | // get ids | |
54 | $data = UserStorageHandler::getInstance()->getStorage([$userID], 'unreadConversationCount'); | |
55 | ||
56 | // cache does not exist or is outdated | |
57 | if ($data[$userID] === null || $skipCache) { | |
58 | $conditionBuilder = new PreparedStatementConditionBuilder(); | |
59 | $conditionBuilder->add('conversation.conversationID = conversation_to_user.conversationID'); | |
60 | $conditionBuilder->add('conversation_to_user.participantID = ?', [$userID]); | |
61 | $conditionBuilder->add('conversation_to_user.hideConversation = 0'); | |
62 | $conditionBuilder->add('conversation_to_user.lastVisitTime < conversation.lastPostTime'); | |
63 | $conditionBuilder->add('conversation_to_user.leftAt = 0'); | |
64 | ||
8fbd8b01 MS |
65 | $sql = "SELECT COUNT(*) AS count |
66 | FROM wcf" . WCF_N . "_conversation_to_user conversation_to_user, | |
67 | wcf" . WCF_N . "_conversation conversation | |
68 | " . $conditionBuilder; | |
fea86294 TD |
69 | $statement = WCF::getDB()->prepareStatement($sql); |
70 | $statement->execute($conditionBuilder->getParameters()); | |
71 | $row = $statement->fetchArray(); | |
72 | $this->unreadConversationCount[$userID] = $row['count']; | |
73 | ||
74 | // update storage data | |
75 | UserStorageHandler::getInstance()->update( | |
76 | $userID, | |
77 | 'unreadConversationCount', | |
78 | \serialize($this->unreadConversationCount[$userID]) | |
79 | ); | |
80 | } else { | |
81 | $this->unreadConversationCount[$userID] = \unserialize($data[$userID]); | |
82 | } | |
83 | } | |
84 | ||
85 | return $this->unreadConversationCount[$userID]; | |
86 | } | |
87 | ||
88 | /** | |
89 | * Returns the number of conversations for given user. | |
fea86294 | 90 | */ |
e23349a0 | 91 | public function getConversationCount(?int $userID = null): int |
fea86294 TD |
92 | { |
93 | if ($userID === null) { | |
94 | $userID = WCF::getUser()->userID; | |
95 | } | |
96 | ||
e23349a0 MW |
97 | if (!$userID) { |
98 | return 0; | |
99 | } | |
100 | ||
fea86294 TD |
101 | if (!isset($this->conversationCount[$userID])) { |
102 | $this->conversationCount[$userID] = 0; | |
103 | ||
104 | // load storage data | |
105 | UserStorageHandler::getInstance()->loadStorage([$userID]); | |
106 | ||
107 | // get ids | |
108 | $data = UserStorageHandler::getInstance()->getStorage([$userID], 'conversationCount'); | |
109 | ||
110 | // cache does not exist or is outdated | |
111 | if ($data[$userID] === null) { | |
112 | $conditionBuilder1 = new PreparedStatementConditionBuilder(); | |
113 | $conditionBuilder1->add('conversation_to_user.participantID = ?', [$userID]); | |
114 | $conditionBuilder1->add('conversation_to_user.hideConversation IN (0,1)'); | |
115 | $conditionBuilder2 = new PreparedStatementConditionBuilder(); | |
116 | $conditionBuilder2->add('conversation.userID = ?', [$userID]); | |
117 | $conditionBuilder2->add('conversation.isDraft = 1'); | |
118 | ||
8fbd8b01 MS |
119 | $sql = "SELECT ( |
120 | SELECT COUNT(*) | |
121 | FROM wcf" . WCF_N . "_conversation_to_user conversation_to_user | |
ad5cae90 | 122 | " . $conditionBuilder1 . " |
8fbd8b01 MS |
123 | ) + ( |
124 | SELECT COUNT(*) | |
125 | FROM wcf" . WCF_N . "_conversation conversation | |
ad5cae90 | 126 | " . $conditionBuilder2 . " |
8fbd8b01 | 127 | ) AS count"; |
fea86294 TD |
128 | $statement = WCF::getDB()->prepareStatement($sql); |
129 | $statement->execute(\array_merge( | |
130 | $conditionBuilder1->getParameters(), | |
131 | $conditionBuilder2->getParameters() | |
132 | )); | |
133 | $row = $statement->fetchArray(); | |
134 | $this->conversationCount[$userID] = $row['count']; | |
135 | ||
136 | // update storage data | |
137 | UserStorageHandler::getInstance()->update( | |
138 | $userID, | |
139 | 'conversationCount', | |
140 | \serialize($this->conversationCount[$userID]) | |
141 | ); | |
142 | } else { | |
143 | $this->conversationCount[$userID] = \unserialize($data[$userID]); | |
144 | } | |
145 | } | |
146 | ||
147 | return $this->conversationCount[$userID]; | |
148 | } | |
149 | ||
150 | /** | |
151 | * Enforces the flood control. | |
152 | */ | |
7d84f6d1 TD |
153 | public function enforceFloodControl( |
154 | bool $isReply = false | |
e23349a0 | 155 | ): void { |
7d84f6d1 TD |
156 | if (!$isReply) { |
157 | // 1. Check for the maximum conversations per 24 hours. | |
158 | $limit = WCF::getSession()->getPermission('user.conversation.maxStartedConversationsPer24Hours'); | |
159 | if ($limit == 0) { | |
160 | // `0` is not a valid value, but the interface logic does not permit and exclusion | |
161 | // while also allowing the special value `-1`. Therefore, `0` behaves like the | |
162 | // 'canStartConversation' permission added in WoltLab Suite 5.2. | |
163 | throw new PermissionDeniedException(); | |
164 | } | |
165 | ||
166 | if ($limit != -1) { | |
167 | $count = FloodControl::getInstance()->countContent('com.woltlab.wcf.conversation', new \DateInterval('P1D')); | |
168 | if ($count['count'] >= $limit) { | |
169 | throw new NamedUserException(WCF::getLanguage()->getDynamicVariable( | |
170 | 'wcf.conversation.error.floodControl', | |
171 | [ | |
172 | 'limit' => $count['count'], | |
173 | 'notBefore' => $count['earliestTime'] + 86400, | |
174 | ] | |
175 | )); | |
176 | } | |
177 | } | |
fea86294 TD |
178 | } |
179 | ||
7d84f6d1 TD |
180 | // 2. Check the time between conversation messages. |
181 | $floodControlTime = WCF::getSession()->getPermission('user.conversation.floodControlTime'); | |
182 | $lastTime = FloodControl::getInstance()->getLastTime('com.woltlab.wcf.conversation.message'); | |
183 | if ($lastTime !== null && $lastTime > TIME_NOW - $floodControlTime) { | |
184 | throw new NamedUserException(WCF::getLanguage()->getDynamicVariable( | |
185 | 'wcf.conversation.message.error.floodControl', | |
186 | [ | |
187 | 'lastMessageTime' => $lastTime, | |
188 | 'waitTime' => $lastTime + $floodControlTime - TIME_NOW, | |
189 | ] | |
190 | )); | |
fea86294 TD |
191 | } |
192 | } | |
df8f8628 | 193 | } |