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