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