Merge branch 'master' into next
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / lib / page / ConversationPage.class.php
1 <?php
2 namespace wcf\page;
3 use wcf\data\conversation\label\ConversationLabel;
4 use wcf\data\conversation\label\ConversationLabelList;
5 use wcf\data\conversation\message\ConversationMessage;
6 use wcf\data\conversation\message\ViewableConversationMessageList;
7 use wcf\data\conversation\Conversation;
8 use wcf\data\conversation\ConversationAction;
9 use wcf\data\conversation\ConversationParticipantList;
10 use wcf\data\conversation\ViewableConversation;
11 use wcf\data\modification\log\ConversationLogModificationLogList;
12 use wcf\data\smiley\SmileyCache;
13 use wcf\data\user\UserProfile;
14 use wcf\system\attachment\AttachmentHandler;
15 use wcf\system\bbcode\BBCodeHandler;
16 use wcf\system\exception\IllegalLinkException;
17 use wcf\system\exception\PermissionDeniedException;
18 use wcf\system\message\quote\MessageQuoteManager;
19 use wcf\system\page\PageLocationManager;
20 use wcf\system\page\ParentPageLocation;
21 use wcf\system\request\LinkHandler;
22 use wcf\system\user\signature\SignatureCache;
23 use wcf\system\WCF;
24 use wcf\util\HeaderUtil;
25 use wcf\util\StringUtil;
26
27 /**
28 * Shows a conversation.
29 *
30 * @author Marcel Werk
31 * @copyright 2001-2019 WoltLab GmbH
32 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
33 * @package WoltLabSuite\Core\Page
34 *
35 * @property ViewableConversationMessageList $objectList
36 */
37 class ConversationPage extends MultipleLinkPage {
38 /**
39 * @inheritDoc
40 */
41 public $itemsPerPage = CONVERSATION_MESSAGES_PER_PAGE;
42
43 /**
44 * @inheritDoc
45 */
46 public $sortOrder = 'ASC';
47
48 /**
49 * @inheritDoc
50 */
51 public $objectListClassName = ViewableConversationMessageList::class;
52
53 /**
54 * @inheritDoc
55 */
56 public $loginRequired = true;
57
58 /**
59 * @inheritDoc
60 */
61 public $neededModules = ['MODULE_CONVERSATION'];
62
63 /**
64 * @inheritDoc
65 */
66 public $neededPermissions = ['user.conversation.canUseConversation'];
67
68 /**
69 * conversation id
70 * @var integer
71 */
72 public $conversationID = 0;
73
74 /**
75 * viewable conversation object
76 * @var ViewableConversation
77 */
78 public $conversation;
79
80 /**
81 * conversation label list
82 * @var ConversationLabelList
83 */
84 public $labelList;
85
86 /**
87 * message id
88 * @var integer
89 */
90 public $messageID = 0;
91
92 /**
93 * conversation message object
94 * @var ConversationMessage
95 */
96 public $message;
97
98 /**
99 * modification log list object
100 * @var ConversationLogModificationLogList
101 */
102 public $modificationLogList;
103
104 /**
105 * list of participants
106 * @var ConversationParticipantList
107 */
108 public $participantList;
109
110 /**
111 * @inheritDoc
112 */
113 public function readParameters() {
114 parent::readParameters();
115
116 if (isset($_REQUEST['id'])) $this->conversationID = intval($_REQUEST['id']);
117 if (isset($_REQUEST['messageID'])) $this->messageID = intval($_REQUEST['messageID']);
118 if ($this->messageID) {
119 $this->message = new ConversationMessage($this->messageID);
120 if (!$this->message->messageID) {
121 throw new IllegalLinkException();
122 }
123 $this->conversationID = $this->message->conversationID;
124 }
125
126 $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
127 if ($this->conversation === null) {
128 throw new IllegalLinkException();
129 }
130 if (!$this->conversation->canRead()) {
131 throw new PermissionDeniedException();
132 }
133
134 // load labels
135 $this->labelList = ConversationLabel::getLabelsByUser();
136 $this->conversation = ViewableConversation::getViewableConversation($this->conversation, $this->labelList);
137
138 // messages per page
139 /** @noinspection PhpUndefinedFieldInspection */
140 if (WCF::getUser()->conversationMessagesPerPage) {
141 /** @noinspection PhpUndefinedFieldInspection */
142 $this->itemsPerPage = WCF::getUser()->conversationMessagesPerPage;
143 }
144
145 $this->canonicalURL = LinkHandler::getInstance()->getLink('Conversation', [
146 'object' => $this->conversation
147 ], ($this->pageNo ? 'pageNo=' . $this->pageNo : ''));
148 }
149
150 /**
151 * @inheritDoc
152 */
153 protected function initObjectList() {
154 parent::initObjectList();
155
156 $this->objectList->getConditionBuilder()->add('conversation_message.conversationID = ?', [$this->conversation->conversationID]);
157 $this->objectList->setConversation($this->conversation->getDecoratedObject());
158
159 // handle visibility filter
160 if ($this->conversation->joinedAt > 0) $this->objectList->getConditionBuilder()->add('conversation_message.time >= ?', [$this->conversation->joinedAt]);
161 if ($this->conversation->leftAt > 0) $this->objectList->getConditionBuilder()->add('conversation_message.time <= ?', [$this->conversation->leftAt]);
162
163 // handle jump to
164 if ($this->action == 'lastPost') $this->goToLastPost();
165 if ($this->action == 'firstNew') $this->goToFirstNewPost();
166 if ($this->messageID) $this->goToPost();
167 }
168
169 /**
170 * @inheritDoc
171 */
172 public function readData() {
173 parent::readData();
174
175 // add breadcrumbs
176 if ($this->conversation->isDraft) {
177 // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
178 PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList', -1, new ParentPageLocation(
179 WCF::getLanguage()->get('wcf.conversation.folder.draft'),
180 LinkHandler::getInstance()->getLink('ConversationList', ['filter' => 'draft'])
181 ));
182 }
183 PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
184
185 // update last visit time count
186 if ($this->conversation->isNew() && $this->objectList->getMaxPostTime() > $this->conversation->lastVisitTime) {
187 $visitTime = $this->objectList->getMaxPostTime();
188 if ($visitTime == $this->conversation->lastPostTime) $visitTime = TIME_NOW;
189 $conversationAction = new ConversationAction([$this->conversation->getDecoratedObject()], 'markAsRead', ['visitTime' => $visitTime]);
190 $conversationAction->executeAction();
191 }
192
193 // get participants
194 $this->participantList = new ConversationParticipantList($this->conversationID, WCF::getUser()->userID, $this->conversation->userID == WCF::getUser()->userID);
195 $this->participantList->readObjects();
196
197 // init quote objects
198 $messageIDs = [];
199 foreach ($this->objectList as $message) {
200 $messageIDs[] = $message->messageID;
201 }
202 MessageQuoteManager::getInstance()->initObjects('com.woltlab.wcf.conversation.message', $messageIDs);
203
204 $userIDs = [];
205 foreach ($this->objectList as $message) {
206 if ($message->userID) {
207 $userIDs[] = $message->userID;
208 }
209 }
210
211 // fetch special trophies
212 if (MODULE_TROPHY) {
213 if (!empty($userIDs)) {
214 UserProfile::prepareSpecialTrophies(array_unique($userIDs));
215 }
216 }
217
218 if (MODULE_USER_SIGNATURE) {
219 if (!empty($userIDs)) {
220 SignatureCache::getInstance()->cacheUserSignature($userIDs);
221 }
222 }
223
224 // set attachment permissions
225 if ($this->objectList->getAttachmentList() !== null) {
226 $this->objectList->getAttachmentList()->setPermissions([
227 'canDownload' => true,
228 'canViewPreview' => true
229 ]);
230 }
231
232 // get timeframe for modifications
233 $this->objectList->rewind();
234 $startTime = ($this->conversation->joinedAt ?: $this->objectList->current()->time);
235 $endTime = ($this->conversation->leftAt ?: TIME_NOW);
236
237 $count = count($this->objectList);
238 if ($count > 1) {
239 $this->objectList->seek($count - 1);
240 if ($this->objectList->current()->time < $this->conversation->lastPostTime) {
241 $sql = "SELECT time
242 FROM wcf".WCF_N."_conversation_message
243 WHERE conversationID = ?
244 AND time > ?
245 ORDER BY time";
246 $statement = WCF::getDB()->prepareStatement($sql, 1);
247 $statement->execute([$this->conversationID, $this->objectList->current()->time]);
248 $endTime = $statement->fetchSingleColumn() - 1;
249 }
250 }
251 $this->objectList->rewind();
252
253 // get invisible participants
254 $invisibleParticipantIDs = [];
255 if (WCF::getUser()->userID != $this->conversation->userID) {
256 foreach ($this->participantList as $participant) {
257 if ($participant->isInvisible) {
258 $invisibleParticipantIDs[] = $participant->userID;
259 }
260 }
261 }
262
263 // load modification log entries
264 $this->modificationLogList = new ConversationLogModificationLogList($this->conversation->conversationID);
265 $this->modificationLogList->getConditionBuilder()->add("modification_log.time BETWEEN ? AND ?", [$startTime, $endTime]);
266
267 if (!empty($invisibleParticipantIDs)) {
268 $this->modificationLogList->getConditionBuilder()->add("(modification_log.action <> ? OR modification_log.userID NOT IN (?))", ['leave', $invisibleParticipantIDs]);
269 }
270
271 $this->modificationLogList->readObjects();
272 }
273
274 /**
275 * @inheritDoc
276 */
277 public function assignVariables() {
278 parent::assignVariables();
279
280 MessageQuoteManager::getInstance()->assignVariables();
281
282 $tmpHash = StringUtil::getRandomID();
283 $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $tmpHash, 0);
284
285 WCF::getTPL()->assign([
286 'attachmentHandler' => $attachmentHandler,
287 'attachmentObjectID' => 0,
288 'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
289 'attachmentParentObjectID' => 0,
290 'tmpHash' => $tmpHash,
291 'attachmentList' => $this->objectList->getAttachmentList(),
292 'labelList' => $this->labelList,
293 'modificationLogList' => $this->modificationLogList,
294 'sortOrder' => $this->sortOrder,
295 'conversation' => $this->conversation,
296 'conversationID' => $this->conversationID,
297 'participants' => $this->participantList->getObjects(),
298 'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies()
299 ]);
300
301 BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.message.disallowedBBCodes')));
302 }
303
304 /**
305 * Calculates the position of a specific post in this conversation.
306 */
307 protected function goToPost() {
308 $conditionBuilder = clone $this->objectList->getConditionBuilder();
309 $conditionBuilder->add('time '.($this->sortOrder == 'ASC' ? '<=' : '>=').' ?', [$this->message->time]);
310
311 $sql = "SELECT COUNT(*) AS messages
312 FROM wcf".WCF_N."_conversation_message conversation_message
313 ".$conditionBuilder;
314 $statement = WCF::getDB()->prepareStatement($sql);
315 $statement->execute($conditionBuilder->getParameters());
316 $row = $statement->fetchArray();
317 $this->pageNo = intval(ceil($row['messages'] / $this->itemsPerPage));
318 }
319
320 /**
321 * Gets the id of the last post in this conversation and forwards the user to this post.
322 */
323 protected function goToLastPost() {
324 $sql = "SELECT conversation_message.messageID
325 FROM wcf".WCF_N."_conversation_message conversation_message
326 ".$this->objectList->getConditionBuilder()."
327 ORDER BY time ".($this->sortOrder == 'ASC' ? 'DESC' : 'ASC');
328 $statement = WCF::getDB()->prepareStatement($sql, 1);
329 $statement->execute($this->objectList->getConditionBuilder()->getParameters());
330 $row = $statement->fetchArray();
331 HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Conversation', [
332 'encodeTitle' => true,
333 'object' => $this->conversation,
334 'messageID' => $row['messageID']
335 ]).'#message'.$row['messageID']);
336 exit;
337 }
338
339 /**
340 * Forwards the user to the first new message in this conversation.
341 */
342 protected function goToFirstNewPost() {
343 $conditionBuilder = clone $this->objectList->getConditionBuilder();
344 $conditionBuilder->add('time > ?', [$this->conversation->lastVisitTime]);
345
346 $sql = "SELECT conversation_message.messageID
347 FROM wcf".WCF_N."_conversation_message conversation_message
348 ".$conditionBuilder."
349 ORDER BY time ASC";
350 $statement = WCF::getDB()->prepareStatement($sql, 1);
351 $statement->execute($conditionBuilder->getParameters());
352 $row = $statement->fetchArray();
353 if ($row !== false) {
354 HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Conversation', [
355 'encodeTitle' => true,
356 'object' => $this->conversation,
357 'messageID' => $row['messageID']
358 ]).'#message'.$row['messageID']);
359 exit;
360 }
361 else {
362 $this->goToLastPost();
363 }
364 }
365 }