Merge pull request #195 from WoltLab/conversation-handler
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / lib / system / conversation / ConversationHandler.class.php
CommitLineData
df8f8628 1<?php
fea86294 2
df8f8628 3namespace wcf\system\conversation;
fea86294 4
df8f8628 5use wcf\system\database\util\PreparedStatementConditionBuilder;
88cde7b3 6use wcf\system\exception\NamedUserException;
90f61ed9 7use wcf\system\exception\PermissionDeniedException;
2ab1f24b 8use wcf\system\flood\FloodControl;
df8f8628 9use wcf\system\SingletonFactory;
fea86294 10use wcf\system\user\storage\UserStorageHandler;
df8f8628
MW
11use 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 20final 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}