Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / user / UserAction.class.php
CommitLineData
11ade432
AE
1<?php
2namespace wcf\data\user;
0dd6ea0c
MW
3use wcf\data\object\type\ObjectTypeCache;
4use wcf\data\user\avatar\UserAvatarAction;
11ade432 5use wcf\data\user\group\UserGroup;
6374f974 6use wcf\data\user\UserEditor;
931f6597 7use wcf\data\AbstractDatabaseObjectAction;
7918ddba 8use wcf\data\IClipboardAction;
a427a8c8 9use wcf\data\ISearchAction;
7f379ade 10use wcf\system\clipboard\ClipboardHandler;
11ade432 11use wcf\system\database\util\PreparedStatementConditionBuilder;
a79cfb56 12use wcf\system\exception\PermissionDeniedException;
3631f7bd 13use wcf\system\exception\UserInputException;
bae8dd1e 14use wcf\system\request\RequestHandler;
2bc9f31d 15use wcf\system\WCF;
2fe45e04 16use wcf\util\UserRegistrationUtil;
11ade432
AE
17
18/**
19 * Executes user-related actions.
20 *
21 * @author Alexander Ebert
ca4ba303 22 * @copyright 2001-2014 WoltLab GmbH
11ade432
AE
23 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
24 * @package com.woltlab.wcf
25 * @subpackage data.user
9f959ced 26 * @category Community Framework
11ade432 27 */
7918ddba 28class UserAction extends AbstractDatabaseObjectAction implements IClipboardAction, ISearchAction {
11ade432 29 /**
0ad90fc3 30 * @see \wcf\data\AbstractDatabaseObjectAction::$className
11ade432
AE
31 */
32 public $className = 'wcf\data\user\UserEditor';
33
8eb8876b 34 /**
0ad90fc3 35 * @see \wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
8eb8876b
MW
36 */
37 protected $allowGuestAccess = array('getSearchResultList');
38
11ade432 39 /**
0ad90fc3 40 * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsCreate
11ade432
AE
41 */
42 protected $permissionsCreate = array('admin.user.canAddUser');
43
44 /**
0ad90fc3 45 * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
11ade432
AE
46 */
47 protected $permissionsDelete = array('admin.user.canDeleteUser');
48
49 /**
0ad90fc3 50 * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
11ade432
AE
51 */
52 protected $permissionsUpdate = array('admin.user.canEditUser');
53
bae8dd1e 54 /**
0ad90fc3 55 * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
bae8dd1e 56 */
57f097e8 57 protected $requireACP = array('create', 'delete', 'disable', 'enable');
bae8dd1e 58
11ade432
AE
59 /**
60 * Validates permissions and parameters.
61 */
62 public function validateCreate() {
a54f8d8f 63 $this->readString('password', false, 'data');
11ade432
AE
64 }
65
66 /**
11cf19be
MW
67 * Validates accessible groups.
68 *
69 * @param boolean $ignoreOwnUser
11ade432 70 */
11cf19be
MW
71 protected function __validateAccessibleGroups($ignoreOwnUser = true) {
72 if ($ignoreOwnUser) {
73 if (in_array(WCF::getUser()->userID, $this->objectIDs)) {
74 unset($this->objectIDs[array_search(WCF::getUser()->userID, $this->objectIDs)]);
75 if (isset($this->objects[WCF::getUser()->userID])) {
76 unset($this->objects[WCF::getUser()->userID]);
77 }
a7fd745e 78 }
48f9369a 79 }
11ade432 80
a7fd745e 81 // list might be empty because only our own user id was given
11cf19be 82 if (empty($this->objectIDs)) {
3631f7bd 83 throw new UserInputException('objectIDs');
a7fd745e
AE
84 }
85
11ade432
AE
86 // validate groups
87 $conditions = new PreparedStatementConditionBuilder();
11cf19be 88 $conditions->add("userID IN (?)", array($this->objectIDs));
11ade432
AE
89
90 $sql = "SELECT DISTINCT groupID
91 FROM wcf".WCF_N."_user_to_group
92 ".$conditions;
93 $statement = WCF::getDB()->prepareStatement($sql);
94 $statement->execute($conditions->getParameters());
95
96 $groupIDs = array();
97 while ($row = $statement->fetchArray()) {
98 $groupIDs[] = $row['groupID'];
99 }
100
101 if (!UserGroup::isAccessibleGroup($groupIDs)) {
3631f7bd 102 throw new PermissionDeniedException();
11ade432
AE
103 }
104 }
105
11cf19be
MW
106 /**
107 * Validates permissions and parameters.
108 */
109 public function validateDelete() {
110 // read and validate user objects
111 parent::validateDelete();
112
113 $this->__validateAccessibleGroups();
114 }
115
0dd6ea0c 116 /**
0ad90fc3 117 * @see \wcf\data\IDeleteAction::delete()
0dd6ea0c
MW
118 */
119 public function delete() {
120 if (empty($this->objects)) {
121 $this->readObjects();
122 }
123
124 // delete avatars
125 $avatarIDs = array();
126 foreach ($this->objects as $user) {
127 if ($user->avatarID) $avatarIDs[] = $user->avatarID;
128 }
129 if (!empty($avatarIDs)) {
130 $action = new UserAvatarAction($avatarIDs, 'delete');
131 $action->executeAction();
132 }
133
134 // delete profile comments
135 if (!empty($this->objectIDs)) {
136 $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.comment.commentableContent', 'com.woltlab.wcf.user.profileComment');
137 $conditionBuilder = new PreparedStatementConditionBuilder();
138 $conditionBuilder->add('objectTypeID = ?', array($objectType->objectTypeID));
139 $conditionBuilder->add('objectID IN (?)', array($this->objectIDs));
140
141 $sql = "DELETE FROM wcf".WCF_N."_comment
142 ".$conditionBuilder;
143 $statement = WCF::getDB()->prepareStatement($sql);
144 $statement->execute($conditionBuilder->getParameters());
145 }
146
147 $returnValue = parent::delete();
148
149 return $returnValue;
150 }
151
11ade432
AE
152 /**
153 * Validates permissions and parameters.
11ade432
AE
154 */
155 public function validateUpdate() {
a79cfb56 156 // read objects
15fa2802 157 if (empty($this->objects)) {
a79cfb56 158 $this->readObjects();
15fa2802
MS
159
160 if (empty($this->objects)) {
3631f7bd 161 throw new UserInputException('objectIDs');
15fa2802 162 }
a79cfb56 163 }
11ade432 164
bae8dd1e
AE
165 // disallow updating of anything except for options outside of ACP
166 if (RequestHandler::getInstance()->isACPRequest() && (count($this->parameters) != 1 || !isset($this->parameters['options']))) {
167 throw new PermissionDeniedException();
168 }
169
a79cfb56
AE
170 try {
171 WCF::getSession()->checkPermissions($this->permissionsUpdate);
172 }
173 catch (PermissionDeniedException $e) {
174 // check if we're editing ourselves
175 if (count($this->objects) == 1 && ($this->objects[0]->userID == WCF::getUser()->userID)) {
67ca3261
AE
176 $count = count($this->parameters);
177 if ($count > 1 || ($count == 1 && !isset($this->parameters['options']))) {
3631f7bd 178 throw new PermissionDeniedException();
a79cfb56
AE
179 }
180 }
181
3631f7bd 182 throw new PermissionDeniedException();
a79cfb56 183 }
11ade432
AE
184 }
185
11cf19be
MW
186 /**
187 * Validates the ban action.
188 */
189 public function validateBan() {
190 WCF::getSession()->checkPermissions(array('admin.user.canBanUser'));
191
192 $this->__validateAccessibleGroups();
193 }
194
195 /**
196 * Validates the unban action.
197 */
198 public function validateUnban() {
199 $this->validateBan();
200 }
201
202 /**
203 * Bans users.
204 */
205 public function ban() {
206 $conditionBuilder = new PreparedStatementConditionBuilder();
207 $conditionBuilder->add('userID IN (?)', array($this->objectIDs));
208 $sql = "UPDATE wcf".WCF_N."_user
209 SET banned = ?,
210 banReason = ?
211 ".$conditionBuilder;
212 $statement = WCF::getDB()->prepareStatement($sql);
213 $statement->execute(
57f097e8 214 array_merge(array(1, $this->parameters['banReason']), $conditionBuilder->getParameters())
11cf19be 215 );
bbef7ed8
MW
216
217 $this->unmarkItems();
11cf19be
MW
218 }
219
220 /**
221 * Unbans users.
222 */
223 public function unban() {
224 $conditionBuilder = new PreparedStatementConditionBuilder();
225 $conditionBuilder->add('userID IN (?)', array($this->objectIDs));
226 $sql = "UPDATE wcf".WCF_N."_user
227 SET banned = 0
228 ".$conditionBuilder;
229 $statement = WCF::getDB()->prepareStatement($sql);
230 $statement->execute($conditionBuilder->getParameters());
231 }
232
11ade432
AE
233 /**
234 * Creates a new user.
235 *
236 * @return User
237 */
238 public function create() {
239 $user = parent::create();
240 $userEditor = new UserEditor($user);
241
242 // updates user options
243 if (isset($this->parameters['options'])) {
244 $userEditor->updateUserOptions($this->parameters['options']);
245 }
246
247 // insert user groups
2bb10466 248 $addDefaultGroups = (isset($this->parameters['addDefaultGroups'])) ? $this->parameters['addDefaultGroups'] : true;
11ade432 249 $groupIDs = (isset($this->parameters['groups'])) ? $this->parameters['groups'] : array();
2bb10466 250 $userEditor->addToGroups($groupIDs, false, $addDefaultGroups);
11ade432
AE
251
252 // insert visible languages
253 $languageIDs = (isset($this->parameters['languages'])) ? $this->parameters['languages'] : array();
695780d7 254 $userEditor->addToLanguages($languageIDs, false);
11ade432 255
320f4a6d
MW
256 if (PACKAGE_ID) {
257 // set default notifications
258 $sql = "INSERT INTO wcf".WCF_N."_user_notification_event_to_user
259 (userID, eventID)
695780d7
MW
260 SELECT ?, eventID
261 FROM wcf".WCF_N."_user_notification_event
262 WHERE preset = ?";
320f4a6d 263 $statement = WCF::getDB()->prepareStatement($sql);
695780d7 264 $statement->execute(array($user->userID, 1));
c9d91afc
MW
265
266 // update user rank
267 if (MODULE_USER_RANK) {
268 $action = new UserProfileAction(array($userEditor), 'updateUserRank');
269 $action->executeAction();
270 }
271 // update user online marking
272 $action = new UserProfileAction(array($userEditor), 'updateUserOnlineMarking');
273 $action->executeAction();
320f4a6d
MW
274 }
275
11ade432
AE
276 return $user;
277 }
835fa8c2
AE
278
279 /**
0ad90fc3 280 * @see \wcf\data\AbstractDatabaseObjectAction::update()
835fa8c2
AE
281 */
282 public function update() {
881246d6
AE
283 if (isset($this->parameters['data'])) {
284 parent::update();
8a3258f5
MS
285
286 if (isset($this->parameters['data']['languageID'])) {
287 foreach ($this->objects as $object) {
288 if ($object->userID == WCF::getUser()->userID) {
289 if ($this->parameters['data']['languageID'] != WCF::getUser()->languageID) {
290 WCF::setLanguage($this->parameters['data']['languageID']);
291 }
292
293 break;
294 }
295 }
296 }
881246d6
AE
297 }
298 else {
15fa2802 299 if (empty($this->objects)) {
881246d6
AE
300 $this->readObjects();
301 }
302 }
835fa8c2
AE
303
304 $groupIDs = (isset($this->parameters['groups'])) ? $this->parameters['groups'] : array();
44adccf6 305 $languageIDs = (isset($this->parameters['languageIDs'])) ? $this->parameters['languageIDs'] : array();
835fa8c2 306 $removeGroups = (isset($this->parameters['removeGroups'])) ? $this->parameters['removeGroups'] : array();
f277d540 307 $userOptions = (isset($this->parameters['options'])) ? $this->parameters['options'] : array();
835fa8c2 308
c2000c5d 309 if (!empty($groupIDs)) {
12f80a9d
MW
310 $action = new UserAction($this->objects, 'addToGroups', array(
311 'groups' => $groupIDs,
312 'addDefaultGroups' => false
313 ));
c2000c5d
MW
314 $action->executeAction();
315 }
316
835fa8c2 317 foreach ($this->objects as $userEditor) {
f277d540 318 if (!empty($removeGroups)) {
835fa8c2
AE
319 $userEditor->removeFromGroups($removeGroups);
320 }
f277d540
AE
321
322 if (!empty($userOptions)) {
323 $userEditor->updateUserOptions($userOptions);
324 }
44adccf6
AE
325
326 if (!empty($languageIDs)) {
327 $userEditor->addToLanguages($languageIDs);
328 }
835fa8c2
AE
329 }
330 }
d5cab442 331
0dd6ea0c
MW
332 /**
333 * Add users to given groups.
334 */
c2000c5d
MW
335 public function addToGroups() {
336 if (empty($this->objects)) {
337 $this->readObjects();
338 }
339
340 $groupIDs = $this->parameters['groups'];
341 $deleteOldGroups = $addDefaultGroups = true;
342 if (isset($this->parameters['deleteOldGroups'])) $deleteOldGroups = $this->parameters['deleteOldGroups'];
343 if (isset($this->parameters['addDefaultGroups'])) $addDefaultGroups = $this->parameters['addDefaultGroups'];
344
345 foreach ($this->objects as $userEditor) {
346 $userEditor->addToGroups($groupIDs, $deleteOldGroups, $addDefaultGroups);
347 }
320f4a6d 348
6374f974
JR
349 //reread objects
350 $this->objects = array();
351 UserEditor::resetCache();
352 $this->readObjects();
353
320f4a6d
MW
354 if (MODULE_USER_RANK) {
355 $action = new UserProfileAction($this->objects, 'updateUserRank');
356 $action->executeAction();
357 }
358 if (MODULE_USERS_ONLINE) {
359 $action = new UserProfileAction($this->objects, 'updateUserOnlineMarking');
360 $action->executeAction();
361 }
c2000c5d
MW
362 }
363
a7fd745e 364 /**
0ad90fc3 365 * @see \wcf\data\ISearchAction::validateGetSearchResultList()
a7fd745e 366 */
a427a8c8 367 public function validateGetSearchResultList() {
a54f8d8f
AE
368 $this->readBoolean('includeUserGroups', false, 'data');
369 $this->readString('searchString', false, 'data');
a7fd745e
AE
370
371 if (isset($this->parameters['data']['excludedSearchValues']) && !is_array($this->parameters['data']['excludedSearchValues'])) {
3631f7bd 372 throw new UserInputException('excludedSearchValues');
a7fd745e 373 }
d5cab442
AE
374 }
375
a7fd745e 376 /**
0ad90fc3 377 * @see \wcf\data\ISearchAction::getSearchResultList()
a7fd745e 378 */
a427a8c8 379 public function getSearchResultList() {
d5cab442 380 $searchString = $this->parameters['data']['searchString'];
c000b08a
MS
381 $excludedSearchValues = array();
382 if (isset($this->parameters['data']['excludedSearchValues'])) {
383 $excludedSearchValues = $this->parameters['data']['excludedSearchValues'];
384 }
d5cab442 385 $list = array();
9f959ced 386
d5cab442
AE
387 if ($this->parameters['data']['includeUserGroups']) {
388 $accessibleGroups = UserGroup::getAccessibleGroups();
389 foreach ($accessibleGroups as $group) {
18c05238 390 $groupName = $group->getName();
c000b08a 391 if (!in_array($groupName, $excludedSearchValues)) {
838e315b 392 $pos = mb_strripos($groupName, $searchString);
c000b08a
MS
393 if ($pos !== false && $pos == 0) {
394 $list[] = array(
395 'label' => $groupName,
396 'objectID' => $group->groupID,
397 'type' => 'group'
398 );
399 }
d5cab442
AE
400 }
401 }
402 }
c000b08a 403
c2d0b2d6
MS
404 // find users
405 $userProfileList = new UserProfileList();
406 $userProfileList->getConditionBuilder()->add("username LIKE ?", array($searchString.'%'));
15fa2802 407 if (!empty($excludedSearchValues)) {
c2d0b2d6 408 $userProfileList->getConditionBuilder()->add("username NOT IN (?)", array($excludedSearchValues));
c000b08a 409 }
c2d0b2d6
MS
410 $userProfileList->sqlLimit = 10;
411 $userProfileList->readObjects();
9f959ced 412
c2d0b2d6 413 foreach ($userProfileList as $userProfile) {
d5cab442 414 $list[] = array(
c2d0b2d6
MS
415 'icon' => $userProfile->getAvatar()->getImageTag(16),
416 'label' => $userProfile->username,
417 'objectID' => $userProfile->userID,
d5cab442
AE
418 'type' => 'user'
419 );
420 }
9f959ced 421
d5cab442
AE
422 return $list;
423 }
49c164a8
AE
424
425 /**
0ad90fc3 426 * @see \wcf\data\IClipboardAction::validateUnmarkAll()
49c164a8 427 */
fbb077d4
MS
428 public function validateUnmarkAll() {
429 // does nothing
430 }
49c164a8
AE
431
432 /**
0ad90fc3 433 * @see \wcf\data\IClipboardAction::unmarkAll()
49c164a8
AE
434 */
435 public function unmarkAll() {
436 ClipboardHandler::getInstance()->removeItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user'));
437 }
bbef7ed8
MW
438
439 /**
440 * Unmarks users.
59dc0db6 441 *
bbef7ed8
MW
442 * @param array<integer> $userIDs
443 */
444 protected function unmarkItems(array $userIDs = array()) {
445 if (empty($userIDs)) {
446 $userIDs = $this->objectIDs;
447 }
e3369fd2 448
bbef7ed8
MW
449 if (!empty($userIDs)) {
450 ClipboardHandler::getInstance()->unmark($userIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user'));
451 }
452 }
2fe45e04
MW
453
454 /**
455 * Validates the enable action.
456 */
457 public function validateEnable() {
458 WCF::getSession()->checkPermissions(array('admin.user.canEnableUser'));
9927f711
MS
459
460 $this->__validateAccessibleGroups();
2fe45e04
MW
461 }
462
463 /**
464 * Validates the disable action.
465 */
466 public function validateDisable() {
467 $this->validateEnable();
468 }
469
470 /**
471 * Enables users.
472 */
473 public function enable() {
474 if (empty($this->objects)) $this->readObjects();
9927f711 475
2fe45e04
MW
476 $action = new UserAction($this->objects, 'update', array(
477 'data' => array(
478 'activationCode' => 0
479 ),
2818981f 480 'removeGroups' => UserGroup::getGroupIDsByType(array(UserGroup::GUESTS))
2fe45e04
MW
481 ));
482 $action->executeAction();
2818981f
MW
483 $action = new UserAction($this->objects, 'addToGroups', array(
484 'groups' => UserGroup::getGroupIDsByType(array(UserGroup::USERS)),
485 'deleteOldGroups' => false,
9927f711 486 'addDefaultGroups' => false
2818981f 487 ));
2fe45e04 488 $action->executeAction();
00ce5cf8
AE
489
490 $this->unmarkItems();
2fe45e04
MW
491 }
492
493 /**
494 * Disables users.
495 */
496 public function disable() {
497 if (empty($this->objects)) $this->readObjects();
9927f711 498
2fe45e04
MW
499 $action = new UserAction($this->objects, 'update', array(
500 'data' => array(
501 'activationCode' => UserRegistrationUtil::getActivationCode()
502 ),
2818981f 503 'removeGroups' => UserGroup::getGroupIDsByType(array(UserGroup::USERS)),
2fe45e04
MW
504 ));
505 $action->executeAction();
2818981f
MW
506 $action = new UserAction($this->objects, 'addToGroups', array(
507 'groups' => UserGroup::getGroupIDsByType(array(UserGroup::GUESTS)),
508 'deleteOldGroups' => false,
509 'addDefaultGroups' => false
510 ));
2fe45e04 511 $action->executeAction();
00ce5cf8
AE
512
513 $this->unmarkItems();
2fe45e04 514 }
2ce24640
MW
515
516 /**
517 * @see \wcf\data\AbstractDatabaseObjectAction::readObjects()
518 */
519 protected function readObjects() {
520 if (empty($this->objectIDs)) {
521 return;
522 }
57f097e8 523
2ce24640
MW
524 // get base class
525 $baseClass = call_user_func(array($this->className, 'getBaseClass'));
57f097e8 526
2ce24640
MW
527 // get objects
528 $sql = "SELECT user_option_value.*, user_table.*
529 FROM wcf".WCF_N."_user user_table
530 LEFT JOIN wcf".WCF_N."_user_option_value user_option_value
531 ON (user_option_value.userID = user_table.userID)
532 WHERE user_table.userID IN (".str_repeat('?,', count($this->objectIDs) - 1)."?)";
533 $statement = WCF::getDB()->prepareStatement($sql);
534 $statement->execute($this->objectIDs);
535 while ($object = $statement->fetchObject($baseClass)) {
536 $this->objects[] = new $this->className($object);
537 }
538 }
57f097e8
MS
539
540 /**
541 * Validates the 'disableSignature' action.
542 */
543 public function validateDisableSignature() {
3696fe93 544 $this->validateEnableSignature();
57f097e8
MS
545
546 $this->readString('disableSignatureReason', true);
547 }
548
549 /**
550 * Disables the signature of the handled users.
551 */
552 public function disableSignature() {
553 if (empty($this->objects)) {
554 $this->readObjects();
555 }
556
557 foreach ($this->objects as $userEditor) {
558 $userEditor->update(array(
559 'disableSignature' => 1,
560 'disableSignatureReason' => $this->parameters['disableSignatureReason']
561 ));
562 }
563 }
564
565 /**
566 * Validates the 'enableSignature' action.
567 */
568 public function validateEnableSignature() {
569 WCF::getSession()->checkPermissions(array('admin.user.canDisableSignature'));
570
571 $this->__validateAccessibleGroups();
572
573 if (empty($this->objects)) {
574 $this->readObjects();
575
576 if (empty($this->objects)) {
577 throw new UserInputException('objectIDs');
578 }
579 }
580 }
581
582 /**
583 * Enables the signature of the handled users.
584 */
585 public function enableSignature() {
586 if (empty($this->objects)) {
587 $this->readObjects();
588 }
589
590 foreach ($this->objects as $userEditor) {
591 $userEditor->update(array(
592 'disableSignature' => 0
593 ));
594 }
595 }
596
597 /**
598 * Validates the 'disableAvatar' action.
599 */
600 public function validateDisableAvatar() {
3696fe93 601 $this->validateEnableAvatar();
57f097e8
MS
602
603 $this->readString('disableAvatarReason', true);
604 }
605
606 /**
607 * Disables the avatar of the handled users.
608 */
609 public function disableAvatar() {
610 if (empty($this->objects)) {
611 $this->readObjects();
612 }
613
614 foreach ($this->objects as $userEditor) {
615 $userEditor->update(array(
616 'disableAvatar' => 1,
617 'disableAvatarReason' => $this->parameters['disableAvatarReason']
618 ));
619 }
620 }
621
622 /**
623 * Validates the 'enableAvatar' action.
624 */
625 public function validateEnableAvatar() {
626 WCF::getSession()->checkPermissions(array('admin.user.canDisableAvatar'));
627
628 $this->__validateAccessibleGroups();
629
630 if (empty($this->objects)) {
631 $this->readObjects();
632
633 if (empty($this->objects)) {
634 throw new UserInputException('objectIDs');
635 }
636 }
637 }
638
639 /**
640 * Enables the avatar of the handled users.
641 */
642 public function enableAvatar() {
643 if (empty($this->objects)) {
644 $this->readObjects();
645 }
646
647 foreach ($this->objects as $userEditor) {
648 $userEditor->update(array(
649 'disableAvatar' => 0
650 ));
651 }
652 }
11ade432 653}