Basic flood control for started conversations
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / lib / system / conversation / ConversationHandler.class.php
1 <?php
2 namespace wcf\system\conversation;
3 use wcf\system\database\util\PreparedStatementConditionBuilder;
4 use wcf\system\exception\NamedUserException;
5 use wcf\system\user\storage\UserStorageHandler;
6 use wcf\system\SingletonFactory;
7 use wcf\system\WCF;
8
9 /**
10 * Handles the number of conversations and unread conversations of the active user.
11 *
12 * @author Marcel Werk
13 * @copyright 2001-2017 WoltLab GmbH
14 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15 * @package WoltLabSuite\Core\System\Conversation
16 */
17 class ConversationHandler extends SingletonFactory {
18 /**
19 * number of unread conversations
20 * @var integer[]
21 */
22 protected $unreadConversationCount = [];
23
24 /**
25 * number of conversations
26 * @var integer[]
27 */
28 protected $conversationCount = [];
29
30 /**
31 * Returns the number of unread conversations for given user.
32 *
33 * @param integer $userID
34 * @param boolean $skipCache
35 * @return integer
36 */
37 public function getUnreadConversationCount($userID = null, $skipCache = false) {
38 if ($userID === null) $userID = WCF::getUser()->userID;
39
40 if (!isset($this->unreadConversationCount[$userID]) || $skipCache) {
41 $this->unreadConversationCount[$userID] = 0;
42
43 // load storage data
44 UserStorageHandler::getInstance()->loadStorage([$userID]);
45
46 // get ids
47 $data = UserStorageHandler::getInstance()->getStorage([$userID], 'unreadConversationCount');
48
49 // cache does not exist or is outdated
50 if ($data[$userID] === null || $skipCache) {
51 $conditionBuilder = new PreparedStatementConditionBuilder();
52 $conditionBuilder->add('conversation.conversationID = conversation_to_user.conversationID');
53 $conditionBuilder->add('conversation_to_user.participantID = ?', [$userID]);
54 $conditionBuilder->add('conversation_to_user.hideConversation = 0');
55 $conditionBuilder->add('conversation_to_user.lastVisitTime < conversation.lastPostTime');
56
57 $sql = "SELECT COUNT(*) AS count
58 FROM wcf".WCF_N."_conversation_to_user conversation_to_user,
59 wcf".WCF_N."_conversation conversation
60 ".$conditionBuilder->__toString();
61 $statement = WCF::getDB()->prepareStatement($sql);
62 $statement->execute($conditionBuilder->getParameters());
63 $row = $statement->fetchArray();
64 $this->unreadConversationCount[$userID] = $row['count'];
65
66 // update storage data
67 UserStorageHandler::getInstance()->update($userID, 'unreadConversationCount', serialize($this->unreadConversationCount[$userID]));
68 }
69 else {
70 $this->unreadConversationCount[$userID] = unserialize($data[$userID]);
71 }
72 }
73
74 return $this->unreadConversationCount[$userID];
75 }
76
77 /**
78 * Returns the number of conversations for given user.
79 *
80 * @param integer $userID
81 * @return integer
82 */
83 public function getConversationCount($userID = null) {
84 if ($userID === null) $userID = WCF::getUser()->userID;
85
86 if (!isset($this->conversationCount[$userID])) {
87 $this->conversationCount[$userID] = 0;
88
89 // load storage data
90 UserStorageHandler::getInstance()->loadStorage([$userID]);
91
92 // get ids
93 $data = UserStorageHandler::getInstance()->getStorage([$userID], 'conversationCount');
94
95 // cache does not exist or is outdated
96 if ($data[$userID] === null) {
97 $conditionBuilder1 = new PreparedStatementConditionBuilder();
98 $conditionBuilder1->add('conversation_to_user.participantID = ?', [$userID]);
99 $conditionBuilder1->add('conversation_to_user.hideConversation IN (0,1)');
100 $conditionBuilder2 = new PreparedStatementConditionBuilder();
101 $conditionBuilder2->add('conversation.userID = ?', [$userID]);
102 $conditionBuilder2->add('conversation.isDraft = 1');
103
104 $sql = "SELECT (SELECT COUNT(*)
105 FROM wcf".WCF_N."_conversation_to_user conversation_to_user
106 ".$conditionBuilder1->__toString().")
107 +
108 (SELECT COUNT(*)
109 FROM wcf".WCF_N."_conversation conversation
110 ".$conditionBuilder2->__toString().") AS count";
111 $statement = WCF::getDB()->prepareStatement($sql);
112 $statement->execute(array_merge($conditionBuilder1->getParameters(), $conditionBuilder2->getParameters()));
113 $row = $statement->fetchArray();
114 $this->conversationCount[$userID] = $row['count'];
115
116 // update storage data
117 UserStorageHandler::getInstance()->update($userID, 'conversationCount', serialize($this->conversationCount[$userID]));
118 }
119 else {
120 $this->conversationCount[$userID] = unserialize($data[$userID]);
121 }
122 }
123
124 return $this->conversationCount[$userID];
125 }
126
127 /**
128 * Enforces the flood control.
129 */
130 public function enforceFloodControl() {
131 $limit = WCF::getSession()->getPermission('user.conversation.maxStartedConversationsPer24Hours');
132 if ($limit < 1) {
133 return;
134 }
135
136 $sql = "SELECT COUNT(*) AS count, MIN(time) AS oldestDate
137 FROM wcf" . WCF_N . "_conversation
138 WHERE userID = ?
139 AND time > ?
140 GROUP BY userID";
141 $statement = WCF::getDB()->prepareStatement($sql);
142 $statement->execute([
143 WCF::getUser()->userID,
144 TIME_NOW - 86400,
145 ]);
146 $row = $statement->fetchSingleRow();
147
148 if ($row !== false && $row['count'] >= $limit) {
149 throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.conversation.error.floodControl', [
150 'limit' => $limit,
151 'notBefore' => $row['oldestDate'] + 86400,
152 ]));
153 }
154 }
155 }