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