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