Commit | Line | Data |
---|---|---|
9544b6b4 | 1 | <?php |
fea86294 | 2 | |
9544b6b4 | 3 | namespace wcf\page; |
fea86294 TD |
4 | |
5 | use wcf\data\conversation\Conversation; | |
6 | use wcf\data\conversation\ConversationAction; | |
7 | use wcf\data\conversation\ConversationParticipantList; | |
2e0bc870 | 8 | use wcf\data\conversation\label\ConversationLabel; |
9d25a4e3 | 9 | use wcf\data\conversation\label\ConversationLabelList; |
9544b6b4 | 10 | use wcf\data\conversation\message\ConversationMessage; |
65f1cc0b | 11 | use wcf\data\conversation\message\ViewableConversationMessageList; |
2e0bc870 | 12 | use wcf\data\conversation\ViewableConversation; |
83e23d22 | 13 | use wcf\data\modification\log\ConversationLogModificationLogList; |
b2de9161 | 14 | use wcf\data\smiley\SmileyCache; |
195a4249 | 15 | use wcf\data\user\UserProfile; |
41c23899 | 16 | use wcf\system\attachment\AttachmentHandler; |
a21c8732 | 17 | use wcf\system\bbcode\BBCodeHandler; |
9544b6b4 MW |
18 | use wcf\system\exception\IllegalLinkException; |
19 | use wcf\system\exception\PermissionDeniedException; | |
2d181735 | 20 | use wcf\system\message\quote\MessageQuoteManager; |
e298db3c | 21 | use wcf\system\page\PageLocationManager; |
2163ec8f | 22 | use wcf\system\page\ParentPageLocation; |
9544b6b4 | 23 | use wcf\system\request\LinkHandler; |
a3273089 | 24 | use wcf\system\user\signature\SignatureCache; |
9544b6b4 MW |
25 | use wcf\system\WCF; |
26 | use 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 |
37 | class 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 | } |