Merge branch '3.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / acp / page / UserListPage.class.php
CommitLineData
158bd3ca
TD
1<?php
2namespace wcf\acp\page;
158bd3ca 3use wcf\data\user\group\UserGroup;
c897c9b4 4use wcf\data\user\option\ViewableUserOption;
2bc9f31d 5use wcf\data\user\User;
f538a459 6use wcf\data\user\UserProfile;
158bd3ca 7use wcf\page\SortablePage;
b401cd0d 8use wcf\system\cache\builder\UserOptionCacheBuilder;
d322363b 9use wcf\system\clipboard\ClipboardHandler;
158bd3ca
TD
10use wcf\system\database\util\PreparedStatementConditionBuilder;
11use wcf\system\event\EventHandler;
6286572b 12use wcf\system\exception\IllegalLinkException;
e4499881 13use wcf\system\option\user\IUserOptionOutput;
734662c9 14use wcf\system\option\IOptionHandler;
3e0e6b2c 15use wcf\system\request\LinkHandler;
2bc9f31d 16use wcf\system\WCF;
158bd3ca
TD
17use wcf\util\DateUtil;
18use 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 */
28class 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}