3 namespace wcf\acp\form
;
5 use wcf\data\style\Style
;
6 use wcf\data\user\avatar\UserAvatar
;
7 use wcf\data\user\avatar\UserAvatarAction
;
8 use wcf\data\user\cover\photo\UserCoverPhoto
;
9 use wcf\data\user\group\UserGroup
;
10 use wcf\data\user\User
;
11 use wcf\data\user\UserAction
;
12 use wcf\data\user\UserEditor
;
13 use wcf\data\user\UserProfileAction
;
14 use wcf\form\AbstractForm
;
15 use wcf\system\cache\runtime\UserProfileRuntimeCache
;
16 use wcf\system\exception\IllegalLinkException
;
17 use wcf\system\exception\PermissionDeniedException
;
18 use wcf\system\exception\UserInputException
;
19 use wcf\system\message\embedded\
object\MessageEmbeddedObjectManager
;
20 use wcf\system\moderation\queue\ModerationQueueManager
;
21 use wcf\system\option\user\UserOptionHandler
;
22 use wcf\system\style\StyleHandler
;
23 use wcf\system\user\command\SetColorScheme
;
24 use wcf\system\user\multifactor\Setup
;
26 use wcf\util\StringUtil
;
29 * Shows the user edit form.
32 * @copyright 2001-2020 WoltLab GmbH
33 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
35 class UserEditForm
extends UserAddForm
40 public $activeMenuItem = 'wcf.acp.menu.link.user.list';
45 public $neededPermissions = ['admin.user.canEditUser'];
69 public $banReason = '';
72 * date when the ban expires
75 public $banExpires = 0;
87 public $avatarType = 'none';
90 * true to disable this avatar
93 public $disableAvatar = 0;
99 public $disableAvatarReason = '';
102 * date when the avatar will be enabled again
105 public $disableAvatarExpires = 0;
108 * user cover photo object
109 * @var UserCoverPhoto
111 public $userCoverPhoto;
114 * true to disable this cover photo
117 public $disableCoverPhoto = 0;
123 public $disableCoverPhotoReason = '';
126 * date when the cover photo will be enabled again
129 public $disableCoverPhotoExpires = 0;
132 * true to delete the current cover photo
135 public $deleteCoverPhoto = 0;
138 * true to delete the current auth data
141 public $disconnect3rdParty = 0;
144 * true to disable multifactor authentication
147 public $multifactorDisable = 0;
150 * list of available styles for the edited user
154 public $availableStyles = [];
157 * id of the used style
166 public string $colorScheme = 'system';
171 public function readParameters()
173 if (isset($_REQUEST['id'])) {
174 $this->userID
= \
intval($_REQUEST['id']);
176 $user = new User($this->userID
);
177 if (!$user->userID
) {
178 throw new IllegalLinkException();
181 $this->user
= new UserEditor($user);
182 if (!UserGroup
::isAccessibleGroup($this->user
->getGroupIDs())) {
183 throw new PermissionDeniedException();
185 $this->attachmentObjectID
= $this->user
->userID
;
187 parent
::readParameters();
193 protected function initOptionHandler()
195 \assert
($this->optionHandler
instanceof UserOptionHandler
);
196 $this->optionHandler
->setUser($this->user
->getDecoratedObject());
202 public function readFormParameters()
204 parent
::readFormParameters();
206 if (!WCF
::getSession()->getPermission('admin.user.canEditPassword') ||
!empty($this->user
->authData
)) {
207 $this->password
= '';
209 if (!WCF
::getSession()->getPermission('admin.user.canEditMailAddress')) {
210 $this->email
= $this->user
->email
;
213 if (!empty($_POST['banned'])) {
216 if (isset($_POST['banReason'])) {
217 $this->banReason
= StringUtil
::trim($_POST['banReason']);
219 if ($this->banned
&& !isset($_POST['banNeverExpires'])) {
220 if (isset($_POST['banExpires'])) {
221 $this->banExpires
= @\
intval(\
strtotime(StringUtil
::trim($_POST['banExpires'])));
225 if (isset($_POST['avatarType'])) {
226 $this->avatarType
= $_POST['avatarType'];
228 if (isset($_POST['styleID'])) {
229 $this->styleID
= \
intval($_POST['styleID']);
231 if (isset($_POST['colorScheme'])) {
232 $this->colorScheme
= $_POST['colorScheme'];
235 if (WCF
::getSession()->getPermission('admin.user.canDisableAvatar')) {
236 if (!empty($_POST['disableAvatar'])) {
237 $this->disableAvatar
= 1;
239 if (isset($_POST['disableAvatarReason'])) {
240 $this->disableAvatarReason
= StringUtil
::trim($_POST['disableAvatarReason']);
242 if ($this->disableAvatar
&& !isset($_POST['disableAvatarNeverExpires'])) {
243 if (isset($_POST['disableAvatarExpires'])) {
244 $this->disableAvatarExpires
= @\
intval(@\
strtotime(StringUtil
::trim($_POST['disableAvatarExpires'])));
249 if (WCF
::getSession()->getPermission('admin.user.canDisableCoverPhoto')) {
250 if (isset($_POST['deleteCoverPhoto'])) {
251 $this->deleteCoverPhoto
= 1;
253 if (!empty($_POST['disableCoverPhoto'])) {
254 $this->disableCoverPhoto
= 1;
256 if (isset($_POST['disableCoverPhotoReason'])) {
257 $this->disableCoverPhotoReason
= StringUtil
::trim($_POST['disableCoverPhotoReason']);
259 if ($this->disableCoverPhoto
&& !isset($_POST['disableCoverPhotoNeverExpires'])) {
260 if (isset($_POST['disableCoverPhotoExpires'])) {
261 $this->disableCoverPhotoExpires
= @\
intval(@\
strtotime(StringUtil
::trim($_POST['disableCoverPhotoExpires'])));
266 if (WCF
::getSession()->getPermission('admin.user.canEditPassword') && isset($_POST['disconnect3rdParty'])) {
267 $this->disconnect3rdParty
= 1;
269 if (WCF
::getSession()->getPermission('admin.user.canEditPassword') && isset($_POST['multifactorDisable'])) {
270 $this->multifactorDisable
= 1;
277 public function readData()
280 // get visible languages
281 $this->readVisibleLanguages();
284 $this->readDefaultValues();
287 $userProfile = UserProfileRuntimeCache
::getInstance()->getObject($this->userID
);
288 foreach (StyleHandler
::getInstance()->getStyles() as $style) {
289 if (!$style->isDisabled ||
$userProfile->getPermission('admin.style.canUseDisabledStyle')) {
290 $this->availableStyles
[$style->styleID
] = $style;
296 // get the avatar object
297 if ($this->avatarType
== 'custom' && $this->user
->avatarID
) {
298 $this->userAvatar
= new UserAvatar($this->user
->avatarID
);
301 // get the user cover photo object
302 if ($this->user
->coverPhotoHash
) {
303 // If the editing user lacks the permissions to view the cover photo, the system
304 // will try to load the default cover photo. However, the default cover photo depends
305 // on the style, eventually triggering a change to the template group which will
306 // fail in the admin panel.
307 if ($userProfile->canSeeCoverPhoto()) {
308 $this->userCoverPhoto
= UserProfileRuntimeCache
::getInstance()
309 ->getObject($this->userID
)
310 ->getCoverPhoto(true);
316 * Sets the selected languages.
318 protected function readVisibleLanguages()
320 $this->visibleLanguages
= $this->user
->getLanguageIDs();
324 * Sets the default values.
326 protected function readDefaultValues()
328 $this->username
= $this->user
->username
;
329 $this->email
= $this->user
->email
;
330 $this->groupIDs
= $this->user
->getGroupIDs(true);
331 $this->languageID
= $this->user
->languageID
;
332 $this->banned
= $this->user
->banned
;
333 $this->banReason
= $this->user
->banReason
;
334 $this->banExpires
= $this->user
->banExpires
;
335 $this->userTitle
= $this->user
->userTitle
;
336 $this->styleID
= $this->user
->styleID
;
338 $this->signature
= $this->user
->signature
;
339 $this->disableSignature
= $this->user
->disableSignature
;
340 $this->disableSignatureReason
= $this->user
->disableSignatureReason
;
341 $this->disableSignatureExpires
= $this->user
->disableSignatureExpires
;
343 $this->disableAvatar
= $this->user
->disableAvatar
;
344 $this->disableAvatarReason
= $this->user
->disableAvatarReason
;
345 $this->disableAvatarExpires
= $this->user
->disableAvatarExpires
;
347 $this->disableCoverPhoto
= $this->user
->disableCoverPhoto
;
348 $this->disableCoverPhotoReason
= $this->user
->disableCoverPhotoReason
;
349 $this->disableCoverPhotoExpires
= $this->user
->disableCoverPhotoExpires
;
351 if ($this->user
->avatarID
) {
352 $this->avatarType
= 'custom';
355 $this->colorScheme
= $this->user
->getUserOption('colorScheme');
361 public function assignVariables()
363 parent
::assignVariables();
365 WCF
::getTPL()->assign([
366 'userID' => $this->user
->userID
,
370 'user' => $this->user
,
371 'banned' => $this->banned
,
372 'banReason' => $this->banReason
,
373 'avatarType' => $this->avatarType
,
374 'disableAvatar' => $this->disableAvatar
,
375 'disableAvatarReason' => $this->disableAvatarReason
,
376 'disableAvatarExpires' => $this->disableAvatarExpires
,
377 'userAvatar' => $this->userAvatar
,
378 'banExpires' => $this->banExpires
,
379 'userCoverPhoto' => $this->userCoverPhoto
,
380 'disableCoverPhoto' => $this->disableCoverPhoto
,
381 'disableCoverPhotoReason' => $this->disableCoverPhotoReason
,
382 'disableCoverPhotoExpires' => $this->disableCoverPhotoExpires
,
383 'deleteCoverPhoto' => $this->deleteCoverPhoto
,
384 'ownerGroupID' => UserGroup
::getOwnerGroupID(),
385 'availableStyles' => $this->availableStyles
,
386 'styleID' => $this->styleID
,
387 'colorScheme' => $this->colorScheme
,
394 public function save()
396 AbstractForm
::save();
397 $this->htmlInputProcessor
->setObjectID($this->userID
);
398 MessageEmbeddedObjectManager
::getInstance()->registerObjects($this->htmlInputProcessor
);
401 if ($this->avatarType
!= 'custom') {
402 // delete custom avatar
403 if ($this->user
->avatarID
) {
404 $action = new UserAvatarAction([$this->user
->avatarID
], 'delete');
405 $action->executeAction();
410 if ($this->avatarType
=== 'none') {
416 $this->additionalFields
= \array_merge
($this->additionalFields
, $avatarData);
418 if ($this->disconnect3rdParty
) {
419 $this->additionalFields
['authData'] = '';
422 // add default groups
423 $defaultGroups = UserGroup
::getAccessibleGroups([UserGroup
::GUESTS
, UserGroup
::EVERYONE
, UserGroup
::USERS
]);
424 $oldGroupIDs = $this->user
->getGroupIDs(true);
425 foreach ($oldGroupIDs as $oldGroupID) {
426 if (isset($defaultGroups[$oldGroupID])) {
427 $this->groupIDs
[] = $oldGroupID;
430 $this->groupIDs
= \array_unique
($this->groupIDs
);
433 $saveOptions = $this->optionHandler
->save();
436 'data' => \array_merge
($this->additionalFields
, [
437 'username' => $this->username
,
438 'email' => $this->email
,
439 'password' => $this->password
,
440 'languageID' => $this->languageID
,
441 'userTitle' => $this->userTitle
,
442 'signature' => $this->htmlInputProcessor
->getHtml(),
443 'signatureEnableHtml' => 1,
444 'styleID' => $this->styleID
,
446 'groups' => $this->groupIDs
,
447 'languageIDs' => $this->visibleLanguages
,
448 'options' => $saveOptions,
449 'signatureAttachmentHandler' => $this->attachmentHandler
,
451 // handle changed username
452 if (\
mb_strtolower($this->username
) != \
mb_strtolower($this->user
->username
)) {
453 $data['data']['lastUsernameChange'] = TIME_NOW
;
454 $data['data']['oldUsername'] = $this->user
->username
;
458 if (WCF
::getSession()->getPermission('admin.user.canBanUser')) {
459 $data['data']['banned'] = $this->banned
;
460 $data['data']['banReason'] = $this->banReason
;
461 $data['data']['banExpires'] = $this->banExpires
;
464 // handle disabled signature
465 if (WCF
::getSession()->getPermission('admin.user.canDisableSignature')) {
466 $data['data']['disableSignature'] = $this->disableSignature
;
467 $data['data']['disableSignatureReason'] = $this->disableSignatureReason
;
468 $data['data']['disableSignatureExpires'] = $this->disableSignatureExpires
;
471 // handle disabled avatar
472 if (WCF
::getSession()->getPermission('admin.user.canDisableAvatar')) {
473 $data['data']['disableAvatar'] = $this->disableAvatar
;
474 $data['data']['disableAvatarReason'] = $this->disableAvatarReason
;
475 $data['data']['disableAvatarExpires'] = $this->disableAvatarExpires
;
478 // handle disabled cover photo
479 if (WCF
::getSession()->getPermission('admin.user.canDisableCoverPhoto')) {
480 $data['data']['disableCoverPhoto'] = $this->disableCoverPhoto
;
481 $data['data']['disableCoverPhotoReason'] = $this->disableCoverPhotoReason
;
482 $data['data']['disableCoverPhotoExpires'] = $this->disableCoverPhotoExpires
;
484 if ($this->deleteCoverPhoto
) {
485 UserProfileRuntimeCache
::getInstance()->getObject($this->userID
)->getCoverPhoto()->delete();
487 $data['data']['coverPhotoHash'] = null;
488 $data['data']['coverPhotoExtension'] = '';
490 UserProfileRuntimeCache
::getInstance()->removeObject($this->userID
);
494 $this->objectAction
= new UserAction([$this->userID
], 'update', $data);
495 $this->objectAction
->executeAction();
497 // disable multifactor authentication
498 if (WCF
::getSession()->getPermission('admin.user.canEditPassword') && $this->multifactorDisable
) {
499 WCF
::getDB()->beginTransaction();
500 $setups = Setup
::getAllForUser($this->user
->getDecoratedObject());
501 foreach ($setups as $setup) {
505 $this->user
->update([
506 'multifactorActive' => 0,
508 WCF
::getDB()->commitTransaction();
511 if ($this->user
->getUserOption('colorScheme') !== $this->colorScheme
) {
512 $command = new SetColorScheme($this->user
->getDecoratedObject(), $this->colorScheme
);
517 $this->user
= new UserEditor(new User($this->userID
));
520 if (MODULE_USER_RANK
) {
521 $action = new UserProfileAction([$this->user
], 'updateUserRank');
522 $action->executeAction();
524 if (MODULE_USERS_ONLINE
) {
525 $action = new UserProfileAction([$this->user
], 'updateUserOnlineMarking');
526 $action->executeAction();
529 // remove assignments
530 $sql = "DELETE FROM wcf" . WCF_N
. "_moderation_queue_to_user
532 $statement = WCF
::getDB()->prepareStatement($sql);
533 $statement->execute([$this->user
->userID
]);
535 // reset moderation count
536 ModerationQueueManager
::getInstance()->resetModerationCount($this->user
->userID
);
540 $this->password
= '';
543 $this->user
= new UserEditor(new User($this->userID
));
545 // show success message
546 WCF
::getTPL()->assign('success', true);
552 protected function validateUsername($username)
555 if (\
mb_strtolower($this->user
->username
) != \
mb_strtolower($username)) {
556 parent
::validateUsername($username);
558 } catch (UserInputException
$e) {
559 if ($e->getField() === 'username' && $e->getType() === 'notUnique') {
560 $user2 = User
::getUserByUsername($username);
561 if ($user2->userID
!= $this->user
->userID
) {
571 protected function validateEmail(string $email): void
573 if (\
mb_strtolower($this->user
->email
) != \
mb_strtolower($email)) {
574 parent
::validateEmail($email);
579 protected function validatePassword(
580 #[\SensitiveParameter]
583 if (!empty($password)) {
584 parent
::validatePassword($password);
589 * Validates the user avatar.
591 protected function validateAvatar()
593 if ($this->avatarType
!= 'custom') {
594 $this->avatarType
= 'none';
598 switch ($this->avatarType
) {
600 if (!$this->user
->avatarID
) {
601 throw new UserInputException('customAvatar');
605 } catch (UserInputException
$e) {
606 $this->errorType
[$e->getField()] = $e->getType();
613 public function validate()
615 if ($this->user
->userID
== WCF
::getUser()->userID
&& WCF
::getUser()->hasOwnerAccess()) {
616 $ownerGroupID = UserGroup
::getOwnerGroupID();
617 if ($ownerGroupID && !\
in_array($ownerGroupID, $this->groupIDs
)) {
618 // Members of the owner group cannot remove themselves.
619 throw new PermissionDeniedException();
623 $this->validateAvatar();
627 if (!isset($this->availableStyles
[$this->styleID
])) {
631 if ($this->colorScheme
!== 'light' && $this->colorScheme
!== 'dark') {
632 $this->colorScheme
= 'system';