3 namespace wcf\data\user
;
5 use ParagonIE\ConstantTime\Hex
;
6 use wcf\data\DatabaseObjectEditor
;
7 use wcf\data\IEditableCachedObject
;
8 use wcf\data\user\group\UserGroup
;
9 use wcf\system\clipboard\ClipboardHandler
;
10 use wcf\system\language\LanguageFactory
;
11 use wcf\system\user\authentication\password\algorithm\Invalid
as InvalidPasswordAlgorithm
;
12 use wcf\system\user\authentication\password\PasswordAlgorithmManager
;
13 use wcf\system\user\storage\UserStorageHandler
;
17 * Provides functions to edit users.
19 * @author Alexander Ebert
20 * @copyright 2001-2019 WoltLab GmbH
21 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
23 * @method User getDecoratedObject()
26 class UserEditor
extends DatabaseObjectEditor
implements IEditableCachedObject
31 protected static $baseClass = User
::class;
34 * list of user options default values
37 protected static $userOptionDefaultValues;
40 * Returns the encoded password hash + algorithm for the given password.
42 * A `null` password will result in the `Invalid` algorithm, otherwise
43 * the default algorithm will be used.
47 private static function getPasswordHash(
48 #[\SensitiveParameter]
49 ?
string $password = null
51 $manager = PasswordAlgorithmManager
::getInstance();
53 $algorithm = $manager->getDefaultAlgorithm();
54 if ($password === null) {
55 $algorithm = new InvalidPasswordAlgorithm();
59 return $manager->getNameFromAlgorithm($algorithm) . ':' . $algorithm->hash($password);
66 public static function create(array $parameters = [])
68 if ($parameters['password'] !== '') {
69 $parameters['password'] = self
::getPasswordHash($parameters['password']);
72 // create accessToken for AbstractAuthedPage
73 $parameters['accessToken'] = Hex
::encode(\random_bytes
(20));
75 // handle registration date
76 if (!isset($parameters['registrationDate'])) {
77 $parameters['registrationDate'] = TIME_NOW
;
80 /** @var User $user */
81 $user = parent
::create($parameters);
83 // create default values for user options
84 self
::createUserOptions($user->userID
);
92 public static function deleteAll(array $objectIDs = [])
95 ClipboardHandler
::getInstance()->unmark(
97 ClipboardHandler
::getInstance()->getObjectTypeID('com.woltlab.wcf.user')
100 return parent
::deleteAll($objectIDs);
106 public function update(array $parameters = [])
108 if (\array_key_exists
('password', $parameters) && $parameters['password'] !== '') {
109 $parameters['password'] = self
::getPasswordHash($parameters['password']);
110 $parameters['accessToken'] = Hex
::encode(\random_bytes
(20));
112 unset($parameters['password'], $parameters['accessToken']);
115 parent
::update($parameters);
119 * Inserts default options.
121 protected static function createUserOptions(int $userID)
123 // fetch default values
124 if (self
::$userOptionDefaultValues === null) {
125 self
::$userOptionDefaultValues = [];
127 $sql = "SELECT optionID, defaultValue
128 FROM wcf" . WCF_N
. "_user_option";
129 $statement = WCF
::getDB()->prepareStatement($sql);
130 $statement->execute();
131 while ($row = $statement->fetchArray()) {
132 if (!empty($row['defaultValue'])) {
133 self
::$userOptionDefaultValues[$row['optionID']] = $row['defaultValue'];
138 // insert default values
139 $keys = $values = '';
140 $statementParameters = [$userID];
141 foreach (self
::$userOptionDefaultValues as $optionID => $optionValue) {
142 $keys .= ', userOption' . $optionID;
144 $statementParameters[] = $optionValue;
147 $sql = "INSERT INTO wcf" . WCF_N
. "_user_option_value
148 (userID" . $keys . ")
149 VALUES (?" . $values . ")";
150 $statement = WCF
::getDB()->prepareStatement($sql);
151 $statement->execute($statementParameters);
155 * Updates user options.
157 * @param array $userOptions
159 public function updateUserOptions(array $userOptions = []): void
162 $statementParameters = [];
163 foreach ($userOptions as $optionID => $optionValue) {
164 if (!empty($updateSQL)) {
168 $updateSQL .= 'userOption' . $optionID . ' = ?';
169 $statementParameters[] = $optionValue;
171 $statementParameters[] = $this->userID
;
173 if (!empty($updateSQL)) {
174 $sql = "UPDATE wcf" . WCF_N
. "_user_option_value
175 SET " . $updateSQL . "
177 $statement = WCF
::getDB()->prepareStatement($sql);
178 $statement->execute($statementParameters);
183 * Adds a user to the groups he should be in.
185 * @param int[] $groupIDs
187 public function addToGroups(array $groupIDs, bool $deleteOldGroups = true, bool $addDefaultGroups = true): void
189 // add default groups
190 if ($addDefaultGroups) {
191 $groupIDs = \array_merge
($groupIDs, UserGroup
::getGroupIDsByType([UserGroup
::EVERYONE
, UserGroup
::USERS
]));
192 $groupIDs = \array_unique
($groupIDs);
196 if ($deleteOldGroups) {
197 $sql = "DELETE FROM wcf" . WCF_N
. "_user_to_group
199 $statement = WCF
::getDB()->prepareStatement($sql);
200 $statement->execute([$this->userID
]);
204 if (!empty($groupIDs)) {
205 $sql = "INSERT IGNORE INTO wcf" . WCF_N
. "_user_to_group
208 $statement = WCF
::getDB()->prepareStatement($sql);
209 foreach ($groupIDs as $groupID) {
210 $statement->execute([$this->userID
, $groupID]);
216 * Adds a user to a user group.
218 public function addToGroup(int $groupID): void
220 $sql = "INSERT IGNORE INTO wcf" . WCF_N
. "_user_to_group
223 $statement = WCF
::getDB()->prepareStatement($sql);
224 $statement->execute([$this->userID
, $groupID]);
228 * Removes a user from a user group.
230 public function removeFromGroup(int $groupID): void
232 $sql = "DELETE FROM wcf" . WCF_N
. "_user_to_group
235 $statement = WCF
::getDB()->prepareStatement($sql);
236 $statement->execute([$this->userID
, $groupID]);
240 * Removes a user from multiple user groups.
242 * @param int[] $groupIDs
244 public function removeFromGroups(array $groupIDs): void
246 $sql = "DELETE FROM wcf" . WCF_N
. "_user_to_group
249 $statement = WCF
::getDB()->prepareStatement($sql);
250 foreach ($groupIDs as $groupID) {
251 $statement->execute([
259 * Saves the visible languages of a user.
261 * @param int[] $languageIDs
263 public function addToLanguages(array $languageIDs, bool $deleteOldLanguages = true): void
265 // remove previous languages
266 if ($deleteOldLanguages) {
267 $sql = "DELETE FROM wcf" . WCF_N
. "_user_to_language
269 $statement = WCF
::getDB()->prepareStatement($sql);
270 $statement->execute([$this->userID
]);
273 // insert language ids
274 $sql = "INSERT IGNORE INTO wcf" . WCF_N
. "_user_to_language
277 $statement = WCF
::getDB()->prepareStatement($sql);
279 if (!empty($languageIDs)) {
280 WCF
::getDB()->beginTransaction();
281 foreach ($languageIDs as $languageID) {
282 $statement->execute([
287 WCF
::getDB()->commitTransaction();
289 // no language id given, use default language id instead
290 $statement->execute([
292 LanguageFactory
::getInstance()->getDefaultLanguageID(),
300 public static function resetCache(): void
302 UserStorageHandler
::getInstance()->resetAll('groupIDs');
303 UserStorageHandler
::getInstance()->resetAll('languageIDs');