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