Merge branch '5.5'
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / lib / page / ConversationListPage.class.php
CommitLineData
9544b6b4 1<?php
fea86294 2
9544b6b4 3namespace wcf\page;
fea86294 4
5e279c42 5use wcf\data\conversation\label\ConversationLabel;
65f1cc0b 6use wcf\data\conversation\label\ConversationLabelList;
9544b6b4 7use wcf\data\conversation\UserConversationList;
18ec67a4 8use wcf\system\clipboard\ClipboardHandler;
4d951026 9use wcf\system\database\util\PreparedStatementConditionBuilder;
5e279c42 10use wcf\system\exception\IllegalLinkException;
e298db3c 11use wcf\system\page\PageLocationManager;
85876c0f 12use wcf\system\request\LinkHandler;
9544b6b4 13use wcf\system\WCF;
3d6dd2ed 14use wcf\util\ArrayUtil;
85876c0f 15use wcf\util\HeaderUtil;
9544b6b4
MW
16
17/**
18 * Shows a list of conversations.
fea86294
TD
19 *
20 * @author Marcel Werk
21 * @copyright 2001-2019 WoltLab GmbH
22 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
fea86294
TD
23 *
24 * @property UserConversationList $objectList
9544b6b4 25 */
fea86294
TD
26class ConversationListPage extends SortablePage
27{
28 /**
29 * @inheritDoc
30 */
31 public $defaultSortField = CONVERSATION_LIST_DEFAULT_SORT_FIELD;
32
33 /**
34 * @inheritDoc
35 */
36 public $defaultSortOrder = CONVERSATION_LIST_DEFAULT_SORT_ORDER;
37
38 /**
39 * @inheritDoc
40 */
41 public $validSortFields = ['subject', 'time', 'username', 'lastPostTime', 'replies', 'participants'];
42
43 /**
44 * @inheritDoc
45 */
46 public $itemsPerPage = CONVERSATIONS_PER_PAGE;
47
48 /**
49 * @inheritDoc
50 */
51 public $loginRequired = true;
52
53 /**
54 * @inheritDoc
55 */
56 public $neededModules = ['MODULE_CONVERSATION'];
57
58 /**
59 * @inheritDoc
60 */
61 public $neededPermissions = ['user.conversation.canUseConversation'];
62
63 /**
64 * list filter
65 * @var string
66 */
67 public $filter = '';
68
69 /**
70 * label id
c85e9df8 71 * @var int
fea86294
TD
72 */
73 public $labelID = 0;
74
75 /**
76 * label list object
77 * @var ConversationLabelList
78 */
79 public $labelList;
80
81 /**
82 * number of conversations (no filter)
c85e9df8 83 * @var int
fea86294
TD
84 */
85 public $conversationCount = 0;
86
87 /**
88 * number of drafts
c85e9df8 89 * @var int
fea86294
TD
90 */
91 public $draftCount = 0;
92
93 /**
94 * number of hidden conversations
c85e9df8 95 * @var int
fea86294
TD
96 */
97 public $hiddenCount = 0;
98
99 /**
100 * number of sent conversations
c85e9df8 101 * @var int
fea86294
TD
102 */
103 public $outboxCount = 0;
104
105 /**
106 * participant that
107 * @var string[]
108 */
109 public $participants = [];
110
111 /**
112 * @inheritDoc
113 */
114 public function readParameters()
115 {
116 parent::readParameters();
117
118 if (isset($_REQUEST['filter'])) {
119 $this->filter = $_REQUEST['filter'];
120 }
121 if (!\in_array($this->filter, UserConversationList::$availableFilters)) {
122 $this->filter = '';
123 }
124
125 // user settings
126 /** @noinspection PhpUndefinedFieldInspection */
127 if (WCF::getUser()->conversationsPerPage) {
128 /** @noinspection PhpUndefinedFieldInspection */
129 $this->itemsPerPage = WCF::getUser()->conversationsPerPage;
130 }
131
132 // labels
133 $this->labelList = ConversationLabel::getLabelsByUser();
8f92b6c2 134 if (!empty($_REQUEST['labelID'])) {
fea86294
TD
135 $this->labelID = \intval($_REQUEST['labelID']);
136
137 $validLabel = false;
138 foreach ($this->labelList as $label) {
139 if ($label->labelID == $this->labelID) {
140 $validLabel = true;
141 break;
142 }
143 }
144
145 if (!$validLabel) {
146 throw new IllegalLinkException();
147 }
148 }
149
150 if (isset($_REQUEST['participants'])) {
151 $this->participants = \array_slice(ArrayUtil::trim(\explode(',', $_REQUEST['participants'])), 0, 20);
152 }
8f92b6c2
MS
153
154 if (!empty($_POST)) {
155 $participantsParameter = '';
156 foreach ($this->participants as $participant) {
157 if (!empty($participantsParameter)) {
158 $participantsParameter .= ',';
159 }
160 $participantsParameter .= \rawurlencode($participant);
161 }
162 if (!empty($participantsParameter)) {
163 $participantsParameter = '&participants=' . $participantsParameter;
164 }
165
166 HeaderUtil::redirect(
167 LinkHandler::getInstance()->getLink(
168 'ConversationList',
169 [],
170 'sortField=' . $this->sortField . '&sortOrder=' . $this->sortOrder . '&filter=' . $this->filter . '&labelID=' . $this->labelID . '&pageNo=' . $this->pageNo . $participantsParameter
171 )
172 );
173
174 exit;
175 }
fea86294
TD
176 }
177
fea86294
TD
178 /**
179 * @inheritDoc
180 */
181 protected function initObjectList()
182 {
183 $this->objectList = new UserConversationList(WCF::getUser()->userID, $this->filter, $this->labelID);
184 $this->objectList->setLabelList($this->labelList);
185
186 if (!empty($this->participants)) {
187 // The column `conversation_to_user.username` has no index, causing full table scans when
188 // trying to filter by it, therefore we'll read the user ids in advance.
189 $conditions = new PreparedStatementConditionBuilder();
190 $conditions->add('username IN (?)', [$this->participants]);
191 $sql = "SELECT userID
8fbd8b01
MS
192 FROM wcf" . WCF_N . "_user
193 " . $conditions;
fea86294
TD
194 $statement = WCF::getDB()->prepareStatement($sql);
195 $statement->execute($conditions->getParameters());
196 $userIDs = [];
197 while ($userID = $statement->fetchColumn()) {
198 $userIDs[] = $userID;
199 }
200
201 if (!empty($userIDs)) {
202 // The condition is split into two branches in order to account for invisible participants.
203 // Invisible participants are only visible to the conversation starter and remain invisible
204 // until the write their first message.
205 //
206 // We need to protect these users from being exposed as participants by including them for
207 // any conversation that the current user has started. For all other conversations, users
208 // flagged with `isInvisible = 0` must be excluded.
209 //
210 // See https://github.com/WoltLab/com.woltlab.wcf.conversation/issues/131
211 $this->objectList->getConditionBuilder()->add('
8fbd8b01
MS
212 (
213 (
214 conversation.userID = ?
215 AND conversation.conversationID IN (
216 SELECT conversationID
217 FROM wcf' . WCF_N . '_conversation_to_user
218 WHERE participantID IN (?)
219 GROUP BY conversationID
220 HAVING COUNT(conversationID) = ?
221 )
222 )
223 OR
224 (
225 conversation.userID <> ?
226 AND conversation.conversationID IN (
227 SELECT conversationID
228 FROM wcf' . WCF_N . '_conversation_to_user
229 WHERE participantID IN (?)
230 AND isInvisible = ?
231 GROUP BY conversationID
232 HAVING COUNT(conversationID) = ?
233 )
234 )
235 )', [
fea86294
TD
236 // Parameters for the first condition.
237 WCF::getUser()->userID,
238 $userIDs,
239 \count($userIDs),
240
241 // Parameters for the second condition.
242 WCF::getUser()->userID,
243 $userIDs,
244 0,
245 \count($userIDs),
246 ]);
247 }
248 }
249 }
250
251 /**
252 * @inheritDoc
253 */
254 public function readData()
255 {
256 // if sort field is `username`, `conversation.` has to prepended because `username`
257 // alone is ambiguous
258 if ($this->sortField === 'username') {
259 $this->sortField = 'conversation.username';
260 }
261
262 parent::readData();
263
264 // change back to old value
265 if ($this->sortField === 'conversation.username') {
266 $this->sortField = 'username';
267 }
268
269 if ($this->filter != '') {
270 // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
271 PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList', -1);
272 }
273
274 // read stats
275 if (!$this->labelID && empty($this->participants)) {
276 switch ($this->filter) {
277 case '':
278 $this->conversationCount = $this->items;
279 break;
280
281 case 'draft':
282 $this->draftCount = $this->items;
283 break;
284
285 case 'hidden':
286 $this->hiddenCount = $this->items;
287 break;
288
289 case 'outbox':
290 $this->outboxCount = $this->items;
291 break;
292 }
293 }
294
295 if ($this->filter != '' || $this->labelID || !empty($this->participants)) {
296 $conversationList = new UserConversationList(WCF::getUser()->userID, '');
297 $this->conversationCount = $conversationList->countObjects();
298 }
299 if ($this->filter != 'draft' || $this->labelID || !empty($this->participants)) {
300 $conversationList = new UserConversationList(WCF::getUser()->userID, 'draft');
301 $this->draftCount = $conversationList->countObjects();
302 }
303 if ($this->filter != 'hidden' || $this->labelID || !empty($this->participants)) {
304 $conversationList = new UserConversationList(WCF::getUser()->userID, 'hidden');
305 $this->hiddenCount = $conversationList->countObjects();
306 }
307 if ($this->filter != 'outbox' || $this->labelID || !empty($this->participants)) {
308 $conversationList = new UserConversationList(WCF::getUser()->userID, 'outbox');
309 $this->outboxCount = $conversationList->countObjects();
310 }
311 }
312
313 /**
314 * @inheritDoc
315 */
316 public function assignVariables()
317 {
318 parent::assignVariables();
319
320 WCF::getTPL()->assign([
321 'filter' => $this->filter,
322 'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(
323 ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation')
324 ),
325 'labelID' => $this->labelID,
326 'labelList' => $this->labelList,
327 'conversationCount' => $this->conversationCount,
328 'draftCount' => $this->draftCount,
329 'hiddenCount' => $this->hiddenCount,
330 'outboxCount' => $this->outboxCount,
331 'participants' => $this->participants,
332 'validSortFields' => $this->validSortFields,
333 ]);
334 }
9544b6b4 335}