Commit | Line | Data |
---|---|---|
158bd3ca TD |
1 | <?php |
2 | namespace wcf\acp\page; | |
158bd3ca | 3 | use wcf\data\user\group\UserGroup; |
c897c9b4 | 4 | use wcf\data\user\option\ViewableUserOption; |
2bc9f31d | 5 | use wcf\data\user\User; |
f538a459 | 6 | use wcf\data\user\UserProfile; |
158bd3ca | 7 | use wcf\page\SortablePage; |
b401cd0d | 8 | use wcf\system\cache\builder\UserOptionCacheBuilder; |
d322363b | 9 | use wcf\system\clipboard\ClipboardHandler; |
158bd3ca TD |
10 | use wcf\system\database\util\PreparedStatementConditionBuilder; |
11 | use wcf\system\event\EventHandler; | |
6286572b | 12 | use wcf\system\exception\IllegalLinkException; |
e4499881 | 13 | use wcf\system\option\user\IUserOptionOutput; |
734662c9 | 14 | use wcf\system\option\IOptionHandler; |
3e0e6b2c | 15 | use wcf\system\request\LinkHandler; |
2bc9f31d | 16 | use wcf\system\WCF; |
158bd3ca TD |
17 | use wcf\util\DateUtil; |
18 | use wcf\util\StringUtil; | |
19 | ||
20 | /** | |
21 | * Shows the result of a user search. | |
22 | * | |
23 | * @author Marcel Werk | |
c839bd49 | 24 | * @copyright 2001-2018 WoltLab GmbH |
158bd3ca | 25 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 26 | * @package WoltLabSuite\Core\Acp\Page |
158bd3ca TD |
27 | */ |
28 | class UserListPage extends SortablePage { | |
21489986 | 29 | /** |
d33a31dc | 30 | * list of displayed column names |
734662c9 | 31 | * @var string[] |
21489986 | 32 | */ |
734662c9 | 33 | public $columnHeads = []; |
158bd3ca | 34 | |
21489986 | 35 | /** |
d9e1d8a4 | 36 | * list of selected columns |
734662c9 | 37 | * @var string[] |
21489986 | 38 | */ |
734662c9 | 39 | public $columns = ['registrationDate', 'lastActivityTime']; |
d9e1d8a4 MW |
40 | |
41 | /** | |
42 | * applies special CSS classes for selected columns | |
0f0590c2 | 43 | * @var array |
d9e1d8a4 | 44 | */ |
734662c9 | 45 | public $columnStyling = [ |
d9e1d8a4 MW |
46 | 'registrationDate' => 'columnDate', |
47 | 'lastActivityTime' => 'columnDate', | |
48 | 'profileHits' => 'columnDigits', | |
49 | 'activityPoints' => 'columnDigits', | |
50 | 'likesReceived' => 'columnDigits' | |
734662c9 | 51 | ]; |
d33a31dc AE |
52 | |
53 | /** | |
54 | * list of column values | |
fea72e74 | 55 | * @var string[][] |
d33a31dc | 56 | */ |
734662c9 | 57 | public $columnValues = []; |
21489986 MS |
58 | |
59 | /** | |
734662c9 | 60 | * @inheritDoc |
21489986 MS |
61 | */ |
62 | public $defaultSortField = 'username'; | |
63 | ||
e34164d2 | 64 | /** |
734662c9 | 65 | * @inheritDoc |
e34164d2 | 66 | */ |
d33a31dc AE |
67 | public $itemsPerPage = 50; |
68 | ||
69 | /** | |
70 | * list of marked user ids | |
734662c9 | 71 | * @var integer[] |
d33a31dc | 72 | */ |
734662c9 | 73 | public $markedUsers = []; |
d33a31dc AE |
74 | |
75 | /** | |
734662c9 | 76 | * @inheritDoc |
d33a31dc | 77 | */ |
734662c9 | 78 | public $neededPermissions = ['admin.user.canSearchUser']; |
d33a31dc AE |
79 | |
80 | /** | |
81 | * IOptionHandler object | |
734662c9 | 82 | * @var IOptionHandler |
d33a31dc AE |
83 | */ |
84 | protected $optionHandler = null; | |
85 | ||
86 | /** | |
87 | * list of available user option names | |
88 | * @var array | |
89 | */ | |
734662c9 | 90 | public $options = []; |
e34164d2 | 91 | |
21489986 MS |
92 | /** |
93 | * id of a user search | |
94 | * @var integer | |
95 | */ | |
158bd3ca TD |
96 | public $searchID = 0; |
97 | ||
d33a31dc AE |
98 | /** |
99 | * list of user ids | |
734662c9 | 100 | * @var integer[] |
d33a31dc | 101 | */ |
734662c9 | 102 | public $userIDs = []; |
d33a31dc AE |
103 | |
104 | /** | |
105 | * list of users | |
e4499881 | 106 | * @var UserProfile[] |
d33a31dc | 107 | */ |
734662c9 | 108 | public $users = []; |
d33a31dc AE |
109 | |
110 | /** | |
111 | * page url | |
112 | * @var string | |
113 | */ | |
158bd3ca | 114 | public $url = ''; |
158bd3ca | 115 | |
d33a31dc | 116 | /** |
734662c9 MS |
117 | * condition builder for user filtering |
118 | * @var PreparedStatementConditionBuilder | |
d33a31dc | 119 | */ |
734662c9 | 120 | public $conditions = null; |
c897c9b4 | 121 | |
158bd3ca | 122 | /** |
734662c9 MS |
123 | * @inheritDoc |
124 | */ | |
125 | public $validSortFields = ['userID', 'registrationDate', 'username', 'lastActivityTime', 'profileHits', 'activityPoints', 'likesReceived']; | |
126 | ||
127 | /** | |
128 | * @inheritDoc | |
158bd3ca TD |
129 | */ |
130 | public function readParameters() { | |
131 | parent::readParameters(); | |
132 | ||
133 | $this->conditions = new PreparedStatementConditionBuilder(); | |
134 | ||
08d76680 AE |
135 | if (!empty($_REQUEST['id'])) { |
136 | $this->searchID = intval($_REQUEST['id']); | |
158bd3ca | 137 | if ($this->searchID) $this->readSearchResult(); |
15fa2802 | 138 | if (empty($this->userIDs)) { |
158bd3ca TD |
139 | throw new IllegalLinkException(); |
140 | } | |
734662c9 | 141 | $this->conditions->add("user_table.userID IN (?)", [$this->userIDs]); |
158bd3ca TD |
142 | } |
143 | ||
144 | // get user options | |
145 | $this->readUserOptions(); | |
146 | } | |
147 | ||
148 | /** | |
734662c9 | 149 | * @inheritDoc |
158bd3ca TD |
150 | */ |
151 | public function validateSortField() { | |
e34164d2 MS |
152 | // add options to valid sort fields |
153 | $this->validSortFields = array_merge($this->validSortFields, array_keys($this->options)); | |
158bd3ca | 154 | |
654bb16b TD |
155 | // avoid leaking mail adresses by sorting |
156 | if (WCF::getSession()->getPermission('admin.user.canEditMailAddress')) { | |
157 | $this->validSortFields[] = 'email'; | |
158 | } | |
159 | ||
e34164d2 | 160 | parent::validateSortField(); |
158bd3ca TD |
161 | } |
162 | ||
163 | /** | |
734662c9 | 164 | * @inheritDoc |
158bd3ca TD |
165 | */ |
166 | public function readData() { | |
167 | parent::readData(); | |
168 | ||
654bb16b TD |
169 | // add email column for authorized users |
170 | if (WCF::getSession()->getPermission('admin.user.canEditMailAddress')) { | |
171 | array_unshift($this->columns, 'email'); | |
172 | } | |
173 | ||
158bd3ca TD |
174 | // get marked users |
175 | $this->markedUsers = WCF::getSession()->getVar('markedUsers'); | |
734662c9 | 176 | if ($this->markedUsers == null || !is_array($this->markedUsers)) $this->markedUsers = []; |
158bd3ca TD |
177 | |
178 | // get columns heads | |
179 | $this->readColumnsHeads(); | |
180 | ||
181 | // get users | |
182 | $this->readUsers(); | |
183 | ||
184 | // build page url | |
734662c9 | 185 | $this->url = LinkHandler::getInstance()->getLink('UserList', [], 'searchID='.$this->searchID.'&action='.rawurlencode($this->action).'&pageNo='.$this->pageNo.'&sortField='.$this->sortField.'&sortOrder='.$this->sortOrder); |
158bd3ca TD |
186 | } |
187 | ||
188 | /** | |
734662c9 | 189 | * @inheritDoc |
158bd3ca TD |
190 | */ |
191 | public function assignVariables() { | |
192 | parent::assignVariables(); | |
193 | ||
734662c9 | 194 | WCF::getTPL()->assign([ |
158bd3ca TD |
195 | 'users' => $this->users, |
196 | 'searchID' => $this->searchID, | |
da27d58a | 197 | 'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user')), |
158bd3ca TD |
198 | 'url' => $this->url, |
199 | 'columnHeads' => $this->columnHeads, | |
d9e1d8a4 MW |
200 | 'columnValues' => $this->columnValues, |
201 | 'columnStyling' => $this->columnStyling | |
734662c9 | 202 | ]); |
158bd3ca TD |
203 | } |
204 | ||
205 | /** | |
734662c9 | 206 | * @inheritDoc |
158bd3ca TD |
207 | */ |
208 | public function show() { | |
264c6eea | 209 | $this->activeMenuItem = 'wcf.acp.menu.link.user.'.($this->searchID ? 'search' : 'list'); |
158bd3ca TD |
210 | |
211 | parent::show(); | |
212 | } | |
213 | ||
214 | /** | |
734662c9 | 215 | * @inheritDoc |
158bd3ca TD |
216 | */ |
217 | public function countItems() { | |
218 | // call countItems event | |
219 | EventHandler::getInstance()->fireAction($this, 'countItems'); | |
9f959ced | 220 | |
5c6ddd85 | 221 | $sql = "SELECT COUNT(*) |
158bd3ca TD |
222 | FROM wcf".WCF_N."_user user_table |
223 | ".$this->conditions; | |
224 | $statement = WCF::getDB()->prepareStatement($sql); | |
225 | $statement->execute($this->conditions->getParameters()); | |
5c6ddd85 | 226 | |
49fb0d2d | 227 | return $statement->fetchSingleColumn(); |
158bd3ca TD |
228 | } |
229 | ||
230 | /** | |
ec1b3610 | 231 | * Fetches the list of results. |
158bd3ca TD |
232 | */ |
233 | protected function readUsers() { | |
234 | // get user ids | |
158bd3ca TD |
235 | $sql = "SELECT user_table.userID |
236 | FROM wcf".WCF_N."_user user_table | |
991c7b8d | 237 | ".(isset($this->options[$this->sortField]) ? "LEFT JOIN wcf".WCF_N."_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)" : '')." |
158bd3ca | 238 | ".$this->conditions." |
9068ad3c | 239 | ORDER BY ".(($this->sortField != 'email' && isset($this->options[$this->sortField])) ? 'user_option_value.userOption'.$this->options[$this->sortField]->optionID : $this->sortField)." ".$this->sortOrder; |
158bd3ca TD |
240 | $statement = WCF::getDB()->prepareStatement($sql, $this->itemsPerPage, ($this->pageNo - 1) * $this->itemsPerPage); |
241 | $statement->execute($this->conditions->getParameters()); | |
cd975610 | 242 | $userIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); |
158bd3ca TD |
243 | |
244 | // get user data | |
15fa2802 | 245 | if (!empty($userIDs)) { |
158bd3ca TD |
246 | // get group ids |
247 | $conditions = new PreparedStatementConditionBuilder(); | |
734662c9 | 248 | $conditions->add("user_table.userID IN (?)", [$userIDs]); |
158bd3ca TD |
249 | |
250 | $sql = "SELECT userID, groupID | |
c8f2b1cc | 251 | FROM wcf".WCF_N."_user_to_group user_table |
158bd3ca TD |
252 | ".$conditions; |
253 | $statement = WCF::getDB()->prepareStatement($sql); | |
254 | $statement->execute($conditions->getParameters()); | |
0557bb04 | 255 | $userToGroups = $statement->fetchMap('userID', 'groupID', false); |
158bd3ca | 256 | |
f538a459 | 257 | $sql = "SELECT user_avatar.*, option_value.*, user_table.* |
158bd3ca TD |
258 | FROM wcf".WCF_N."_user user_table |
259 | LEFT JOIN wcf".WCF_N."_user_option_value option_value | |
260 | ON (option_value.userID = user_table.userID) | |
1df32c5a | 261 | LEFT JOIN wcf".WCF_N."_user_avatar user_avatar |
f538a459 | 262 | ON (user_avatar.avatarID = user_table.avatarID) |
158bd3ca | 263 | ".$conditions." |
9068ad3c | 264 | ORDER BY ".(($this->sortField != 'email' && isset($this->options[$this->sortField])) ? 'option_value.userOption'.$this->options[$this->sortField]->optionID : 'user_table.'.$this->sortField)." ".$this->sortOrder; |
158bd3ca TD |
265 | $statement = WCF::getDB()->prepareStatement($sql); |
266 | $statement->execute($conditions->getParameters()); | |
267 | while ($row = $statement->fetchArray()) { | |
734662c9 | 268 | $groupIDs = (isset($userToGroups[$row['userID']]) ? $userToGroups[$row['userID']] : []); |
0599e896 MW |
269 | |
270 | $row['groupIDs'] = implode(',', $groupIDs); | |
271 | $accessible = (!empty($groupIDs) ? UserGroup::isAccessibleGroup($groupIDs) : true); | |
158bd3ca TD |
272 | $row['accessible'] = $accessible; |
273 | $row['deletable'] = ($accessible && WCF::getSession()->getPermission('admin.user.canDeleteUser') && $row['userID'] != WCF::getUser()->userID) ? 1 : 0; | |
274 | $row['editable'] = ($accessible && WCF::getSession()->getPermission('admin.user.canEditUser')) ? 1 : 0; | |
11cf19be | 275 | $row['bannable'] = ($accessible && WCF::getSession()->getPermission('admin.user.canBanUser') && $row['userID'] != WCF::getUser()->userID) ? 1 : 0; |
e6740117 | 276 | $row['canBeEnabled'] = ($accessible && WCF::getSession()->getPermission('admin.user.canEnableUser') && $row['userID'] != WCF::getUser()->userID) ? 1 : 0; |
158bd3ca TD |
277 | $row['isMarked'] = intval(in_array($row['userID'], $this->markedUsers)); |
278 | ||
f538a459 | 279 | $this->users[] = new UserProfile(new User(null, $row)); |
158bd3ca TD |
280 | } |
281 | ||
282 | // get special columns | |
283 | foreach ($this->users as $key => $user) { | |
284 | foreach ($this->columns as $column) { | |
c897c9b4 AE |
285 | switch ($column) { |
286 | case 'email': | |
287 | $this->columnValues[$user->userID][$column] = '<a href="mailto:'.StringUtil::encodeHTML($user->email).'">'.StringUtil::encodeHTML($user->email).'</a>'; | |
288 | break; | |
289 | ||
290 | case 'registrationDate': | |
291 | $this->columnValues[$user->userID][$column] = DateUtil::format(DateUtil::getDateTimeByTimestamp($user->{$column}), DateUtil::DATE_FORMAT); | |
292 | break; | |
293 | ||
d9e1d8a4 MW |
294 | case 'lastActivityTime': |
295 | if ($user->{$column}) { | |
53c75a38 | 296 | $this->columnValues[$user->userID][$column] = str_replace('%time%', DateUtil::format(DateUtil::getDateTimeByTimestamp($user->{$column}), DateUtil::TIME_FORMAT), str_replace('%date%', DateUtil::format(DateUtil::getDateTimeByTimestamp($user->{$column}), DateUtil::DATE_FORMAT), WCF::getLanguage()->get('wcf.date.dateTimeFormat'))); |
d9e1d8a4 MW |
297 | } |
298 | break; | |
299 | ||
300 | case 'profileHits': | |
301 | case 'activityPoints': | |
302 | case 'likesReceived': | |
303 | $this->columnValues[$user->userID][$column] = StringUtil::formatInteger($user->{$column}); | |
304 | break; | |
305 | ||
c897c9b4 AE |
306 | default: |
307 | if (isset($this->options[$column])) { | |
308 | if ($this->options[$column]->outputClass) { | |
40c9e036 | 309 | $this->options[$column]->setOptionValue($user->getDecoratedObject()); |
e4499881 MS |
310 | |
311 | /** @var IUserOptionOutput $outputObj */ | |
c897c9b4 | 312 | $outputObj = $this->options[$column]->getOutputObject(); |
40c9e036 | 313 | $this->columnValues[$user->userID][$column] = $outputObj->getOutput($user->getDecoratedObject(), $this->options[$column]->getDecoratedObject(), $user->{$column}); |
c897c9b4 AE |
314 | } |
315 | else { | |
316 | $this->columnValues[$user->userID][$column] = StringUtil::encodeHTML($user->{$column}); | |
317 | } | |
318 | } | |
319 | break; | |
158bd3ca TD |
320 | } |
321 | } | |
322 | } | |
323 | } | |
324 | } | |
325 | ||
326 | /** | |
ec1b3610 | 327 | * Fetches the result of the search with the given search id. |
158bd3ca TD |
328 | */ |
329 | protected function readSearchResult() { | |
330 | // get user search from database | |
331 | $sql = "SELECT searchData | |
332 | FROM wcf".WCF_N."_search | |
333 | WHERE searchID = ? | |
334 | AND userID = ? | |
335 | AND searchType = ?"; | |
336 | $statement = WCF::getDB()->prepareStatement($sql); | |
734662c9 | 337 | $statement->execute([ |
158bd3ca TD |
338 | $this->searchID, |
339 | WCF::getUser()->userID, | |
340 | 'users' | |
734662c9 | 341 | ]); |
158bd3ca TD |
342 | $search = $statement->fetchArray(); |
343 | if (!isset($search['searchData'])) { | |
344 | throw new IllegalLinkException(); | |
345 | } | |
346 | ||
347 | $data = unserialize($search['searchData']); | |
348 | $this->userIDs = $data['matches']; | |
349 | $this->itemsPerPage = $data['itemsPerPage']; | |
350 | $this->columns = $data['columns']; | |
351 | unset($data); | |
352 | } | |
353 | ||
354 | /** | |
ec1b3610 | 355 | * Fetches the user options from cache. |
158bd3ca TD |
356 | */ |
357 | protected function readUserOptions() { | |
734662c9 | 358 | $this->options = UserOptionCacheBuilder::getInstance()->getData([], 'options'); |
c897c9b4 AE |
359 | |
360 | foreach ($this->options as &$option) { | |
361 | $option = new ViewableUserOption($option); | |
362 | } | |
363 | unset($option); | |
158bd3ca TD |
364 | } |
365 | ||
366 | /** | |
367 | * Reads the column heads. | |
368 | */ | |
369 | protected function readColumnsHeads() { | |
370 | foreach ($this->columns as $column) { | |
d9e1d8a4 MW |
371 | if ($column == 'likesReceived') { |
372 | $this->columnHeads[$column] = 'wcf.like.likesReceived'; | |
373 | continue; | |
374 | } | |
375 | if ($column == 'activityPoints') { | |
376 | $this->columnHeads[$column] = 'wcf.user.activityPoint'; | |
377 | continue; | |
378 | } | |
379 | ||
6b60b7e2 | 380 | if (isset($this->options[$column]) && $column != 'email') { |
158bd3ca TD |
381 | $this->columnHeads[$column] = 'wcf.user.option.'.$column; |
382 | } | |
383 | else { | |
384 | $this->columnHeads[$column] = 'wcf.user.'.$column; | |
385 | } | |
386 | } | |
387 | } | |
388 | ||
158bd3ca | 389 | /** |
734662c9 | 390 | * @inheritDoc |
d726f13d | 391 | */ |
734662c9 MS |
392 | protected function initObjectList() { |
393 | // does nothing | |
394 | } | |
158bd3ca TD |
395 | |
396 | /** | |
734662c9 | 397 | * @inheritDoc |
d726f13d | 398 | */ |
734662c9 MS |
399 | protected function readObjects() { |
400 | // does nothing | |
401 | } | |
158bd3ca | 402 | } |