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