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