Add the new variable `FileProcessorFormField::$bigPreview` with getter and setter.
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / user / UserEditor.class.php
1 <?php
2
3 namespace wcf\data\user;
4
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;
14 use wcf\system\WCF;
15
16 /**
17 * Provides functions to edit users.
18 *
19 * @author Alexander Ebert
20 * @copyright 2001-2019 WoltLab GmbH
21 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22 *
23 * @method User getDecoratedObject()
24 * @mixin User
25 */
26 class UserEditor extends DatabaseObjectEditor implements IEditableCachedObject
27 {
28 /**
29 * @inheritDoc
30 */
31 protected static $baseClass = User::class;
32
33 /**
34 * list of user options default values
35 * @var array
36 */
37 protected static $userOptionDefaultValues;
38
39 /**
40 * Returns the encoded password hash + algorithm for the given password.
41 *
42 * A `null` password will result in the `Invalid` algorithm, otherwise
43 * the default algorithm will be used.
44 *
45 * @since 5.4
46 */
47 private static function getPasswordHash(
48 #[\SensitiveParameter]
49 ?string $password = null
50 ): string {
51 $manager = PasswordAlgorithmManager::getInstance();
52
53 $algorithm = $manager->getDefaultAlgorithm();
54 if ($password === null) {
55 $algorithm = new InvalidPasswordAlgorithm();
56 $password = '';
57 }
58
59 return $manager->getNameFromAlgorithm($algorithm) . ':' . $algorithm->hash($password);
60 }
61
62 /**
63 * @inheritDoc
64 * @return User
65 */
66 public static function create(array $parameters = [])
67 {
68 if ($parameters['password'] !== '') {
69 $parameters['password'] = self::getPasswordHash($parameters['password']);
70 }
71
72 // create accessToken for AbstractAuthedPage
73 $parameters['accessToken'] = Hex::encode(\random_bytes(20));
74
75 // handle registration date
76 if (!isset($parameters['registrationDate'])) {
77 $parameters['registrationDate'] = TIME_NOW;
78 }
79
80 /** @var User $user */
81 $user = parent::create($parameters);
82
83 // create default values for user options
84 self::createUserOptions($user->userID);
85
86 return $user;
87 }
88
89 /**
90 * @inheritDoc
91 */
92 public static function deleteAll(array $objectIDs = [])
93 {
94 // unmark users
95 ClipboardHandler::getInstance()->unmark(
96 $objectIDs,
97 ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user')
98 );
99
100 return parent::deleteAll($objectIDs);
101 }
102
103 /**
104 * @inheritDoc
105 */
106 public function update(array $parameters = [])
107 {
108 if (\array_key_exists('password', $parameters) && $parameters['password'] !== '') {
109 $parameters['password'] = self::getPasswordHash($parameters['password']);
110 $parameters['accessToken'] = Hex::encode(\random_bytes(20));
111 } else {
112 unset($parameters['password'], $parameters['accessToken']);
113 }
114
115 parent::update($parameters);
116 }
117
118 /**
119 * Inserts default options.
120 */
121 protected static function createUserOptions(int $userID)
122 {
123 // fetch default values
124 if (self::$userOptionDefaultValues === null) {
125 self::$userOptionDefaultValues = [];
126
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'];
134 }
135 }
136 }
137
138 // insert default values
139 $keys = $values = '';
140 $statementParameters = [$userID];
141 foreach (self::$userOptionDefaultValues as $optionID => $optionValue) {
142 $keys .= ', userOption' . $optionID;
143 $values .= ', ?';
144 $statementParameters[] = $optionValue;
145 }
146
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);
152 }
153
154 /**
155 * Updates user options.
156 *
157 * @param array $userOptions
158 */
159 public function updateUserOptions(array $userOptions = []): void
160 {
161 $updateSQL = '';
162 $statementParameters = [];
163 foreach ($userOptions as $optionID => $optionValue) {
164 if (!empty($updateSQL)) {
165 $updateSQL .= ',';
166 }
167
168 $updateSQL .= 'userOption' . $optionID . ' = ?';
169 $statementParameters[] = $optionValue;
170 }
171 $statementParameters[] = $this->userID;
172
173 if (!empty($updateSQL)) {
174 $sql = "UPDATE wcf" . WCF_N . "_user_option_value
175 SET " . $updateSQL . "
176 WHERE userID = ?";
177 $statement = WCF::getDB()->prepareStatement($sql);
178 $statement->execute($statementParameters);
179 }
180 }
181
182 /**
183 * Adds a user to the groups he should be in.
184 *
185 * @param int[] $groupIDs
186 */
187 public function addToGroups(array $groupIDs, bool $deleteOldGroups = true, bool $addDefaultGroups = true): void
188 {
189 // add default groups
190 if ($addDefaultGroups) {
191 $groupIDs = \array_merge($groupIDs, UserGroup::getGroupIDsByType([UserGroup::EVERYONE, UserGroup::USERS]));
192 $groupIDs = \array_unique($groupIDs);
193 }
194
195 // remove old groups
196 if ($deleteOldGroups) {
197 $sql = "DELETE FROM wcf" . WCF_N . "_user_to_group
198 WHERE userID = ?";
199 $statement = WCF::getDB()->prepareStatement($sql);
200 $statement->execute([$this->userID]);
201 }
202
203 // insert new groups
204 if (!empty($groupIDs)) {
205 $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_to_group
206 (userID, groupID)
207 VALUES (?, ?)";
208 $statement = WCF::getDB()->prepareStatement($sql);
209 foreach ($groupIDs as $groupID) {
210 $statement->execute([$this->userID, $groupID]);
211 }
212 }
213 }
214
215 /**
216 * Adds a user to a user group.
217 */
218 public function addToGroup(int $groupID): void
219 {
220 $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_to_group
221 (userID, groupID)
222 VALUES (?, ?)";
223 $statement = WCF::getDB()->prepareStatement($sql);
224 $statement->execute([$this->userID, $groupID]);
225 }
226
227 /**
228 * Removes a user from a user group.
229 */
230 public function removeFromGroup(int $groupID): void
231 {
232 $sql = "DELETE FROM wcf" . WCF_N . "_user_to_group
233 WHERE userID = ?
234 AND groupID = ?";
235 $statement = WCF::getDB()->prepareStatement($sql);
236 $statement->execute([$this->userID, $groupID]);
237 }
238
239 /**
240 * Removes a user from multiple user groups.
241 *
242 * @param int[] $groupIDs
243 */
244 public function removeFromGroups(array $groupIDs): void
245 {
246 $sql = "DELETE FROM wcf" . WCF_N . "_user_to_group
247 WHERE userID = ?
248 AND groupID = ?";
249 $statement = WCF::getDB()->prepareStatement($sql);
250 foreach ($groupIDs as $groupID) {
251 $statement->execute([
252 $this->userID,
253 $groupID,
254 ]);
255 }
256 }
257
258 /**
259 * Saves the visible languages of a user.
260 *
261 * @param int[] $languageIDs
262 */
263 public function addToLanguages(array $languageIDs, bool $deleteOldLanguages = true): void
264 {
265 // remove previous languages
266 if ($deleteOldLanguages) {
267 $sql = "DELETE FROM wcf" . WCF_N . "_user_to_language
268 WHERE userID = ?";
269 $statement = WCF::getDB()->prepareStatement($sql);
270 $statement->execute([$this->userID]);
271 }
272
273 // insert language ids
274 $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_to_language
275 (userID, languageID)
276 VALUES (?, ?)";
277 $statement = WCF::getDB()->prepareStatement($sql);
278
279 if (!empty($languageIDs)) {
280 WCF::getDB()->beginTransaction();
281 foreach ($languageIDs as $languageID) {
282 $statement->execute([
283 $this->userID,
284 $languageID,
285 ]);
286 }
287 WCF::getDB()->commitTransaction();
288 } else {
289 // no language id given, use default language id instead
290 $statement->execute([
291 $this->userID,
292 LanguageFactory::getInstance()->getDefaultLanguageID(),
293 ]);
294 }
295 }
296
297 /**
298 * @inheritDoc
299 */
300 public static function resetCache(): void
301 {
302 UserStorageHandler::getInstance()->resetAll('groupIDs');
303 UserStorageHandler::getInstance()->resetAll('languageIDs');
304 }
305 }