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