Commit | Line | Data |
---|---|---|
9544b6b4 | 1 | <?php |
fea86294 | 2 | |
9544b6b4 | 3 | namespace wcf\page; |
fea86294 | 4 | |
5e279c42 | 5 | use wcf\data\conversation\label\ConversationLabel; |
65f1cc0b | 6 | use wcf\data\conversation\label\ConversationLabelList; |
9544b6b4 | 7 | use wcf\data\conversation\UserConversationList; |
18ec67a4 | 8 | use wcf\system\clipboard\ClipboardHandler; |
4d951026 | 9 | use wcf\system\database\util\PreparedStatementConditionBuilder; |
5e279c42 | 10 | use wcf\system\exception\IllegalLinkException; |
e298db3c | 11 | use wcf\system\page\PageLocationManager; |
9544b6b4 | 12 | use wcf\system\WCF; |
3d6dd2ed | 13 | use wcf\util\ArrayUtil; |
9544b6b4 MW |
14 | |
15 | /** | |
16 | * Shows a list of conversations. | |
fea86294 TD |
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 | |
9544b6b4 | 24 | */ |
fea86294 TD |
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 | ||
fea86294 TD |
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 | |
8fbd8b01 MS |
168 | FROM wcf" . WCF_N . "_user |
169 | " . $conditions; | |
fea86294 TD |
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(' | |
8fbd8b01 MS |
188 | ( |
189 | ( | |
190 | conversation.userID = ? | |
191 | AND conversation.conversationID IN ( | |
192 | SELECT conversationID | |
193 | FROM wcf' . WCF_N . '_conversation_to_user | |
194 | WHERE participantID IN (?) | |
195 | GROUP BY conversationID | |
196 | HAVING COUNT(conversationID) = ? | |
197 | ) | |
198 | ) | |
199 | OR | |
200 | ( | |
201 | conversation.userID <> ? | |
202 | AND conversation.conversationID IN ( | |
203 | SELECT conversationID | |
204 | FROM wcf' . WCF_N . '_conversation_to_user | |
205 | WHERE participantID IN (?) | |
206 | AND isInvisible = ? | |
207 | GROUP BY conversationID | |
208 | HAVING COUNT(conversationID) = ? | |
209 | ) | |
210 | ) | |
211 | )', [ | |
fea86294 TD |
212 | // Parameters for the first condition. |
213 | WCF::getUser()->userID, | |
214 | $userIDs, | |
215 | \count($userIDs), | |
216 | ||
217 | // Parameters for the second condition. | |
218 | WCF::getUser()->userID, | |
219 | $userIDs, | |
220 | 0, | |
221 | \count($userIDs), | |
222 | ]); | |
223 | } | |
224 | } | |
225 | } | |
226 | ||
227 | /** | |
228 | * @inheritDoc | |
229 | */ | |
230 | public function readData() | |
231 | { | |
232 | // if sort field is `username`, `conversation.` has to prepended because `username` | |
233 | // alone is ambiguous | |
234 | if ($this->sortField === 'username') { | |
235 | $this->sortField = 'conversation.username'; | |
236 | } | |
237 | ||
238 | parent::readData(); | |
239 | ||
240 | // change back to old value | |
241 | if ($this->sortField === 'conversation.username') { | |
242 | $this->sortField = 'username'; | |
243 | } | |
244 | ||
245 | if ($this->filter != '') { | |
246 | // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList` | |
247 | PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList', -1); | |
248 | } | |
249 | ||
250 | // read stats | |
251 | if (!$this->labelID && empty($this->participants)) { | |
252 | switch ($this->filter) { | |
253 | case '': | |
254 | $this->conversationCount = $this->items; | |
255 | break; | |
256 | ||
257 | case 'draft': | |
258 | $this->draftCount = $this->items; | |
259 | break; | |
260 | ||
261 | case 'hidden': | |
262 | $this->hiddenCount = $this->items; | |
263 | break; | |
264 | ||
265 | case 'outbox': | |
266 | $this->outboxCount = $this->items; | |
267 | break; | |
268 | } | |
269 | } | |
270 | ||
271 | if ($this->filter != '' || $this->labelID || !empty($this->participants)) { | |
272 | $conversationList = new UserConversationList(WCF::getUser()->userID, ''); | |
273 | $this->conversationCount = $conversationList->countObjects(); | |
274 | } | |
275 | if ($this->filter != 'draft' || $this->labelID || !empty($this->participants)) { | |
276 | $conversationList = new UserConversationList(WCF::getUser()->userID, 'draft'); | |
277 | $this->draftCount = $conversationList->countObjects(); | |
278 | } | |
279 | if ($this->filter != 'hidden' || $this->labelID || !empty($this->participants)) { | |
280 | $conversationList = new UserConversationList(WCF::getUser()->userID, 'hidden'); | |
281 | $this->hiddenCount = $conversationList->countObjects(); | |
282 | } | |
283 | if ($this->filter != 'outbox' || $this->labelID || !empty($this->participants)) { | |
284 | $conversationList = new UserConversationList(WCF::getUser()->userID, 'outbox'); | |
285 | $this->outboxCount = $conversationList->countObjects(); | |
286 | } | |
287 | } | |
288 | ||
289 | /** | |
290 | * @inheritDoc | |
291 | */ | |
292 | public function assignVariables() | |
293 | { | |
294 | parent::assignVariables(); | |
295 | ||
296 | WCF::getTPL()->assign([ | |
297 | 'filter' => $this->filter, | |
298 | 'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems( | |
299 | ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation') | |
300 | ), | |
301 | 'labelID' => $this->labelID, | |
302 | 'labelList' => $this->labelList, | |
303 | 'conversationCount' => $this->conversationCount, | |
304 | 'draftCount' => $this->draftCount, | |
305 | 'hiddenCount' => $this->hiddenCount, | |
306 | 'outboxCount' => $this->outboxCount, | |
307 | 'participants' => $this->participants, | |
308 | 'validSortFields' => $this->validSortFields, | |
309 | ]); | |
310 | } | |
9544b6b4 | 311 | } |