Merge pull request #6006 from WoltLab/file-processor-can-adopt
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / importer / UserImporter.class.php
CommitLineData
78db8ddf 1<?php
a9229942 2
78db8ddf 3namespace wcf\system\importer;
a9229942 4
a96244e1 5use wcf\data\user\group\UserGroup;
7a23a706 6use wcf\data\user\option\UserOption;
2ec5b95d 7use wcf\data\user\option\UserOptionList;
90d1166e 8use wcf\data\user\User;
b72edcd8 9use wcf\data\user\UserEditor;
9f4e72b4 10use wcf\system\language\LanguageFactory;
78db8ddf
MW
11use wcf\system\WCF;
12
13/**
14 * Imports users.
a9229942
TD
15 *
16 * @author Marcel Werk
17 * @copyright 2001-2019 WoltLab GmbH
18 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
78db8ddf 19 */
a9229942
TD
20class UserImporter extends AbstractImporter
21{
22 /**
23 * @inheritDoc
24 */
25 protected $className = User::class;
26
27 /**
28 * ids of default notification events
29 * @var int[]
30 */
31 protected $eventIDs = [];
32
72820037
TD
33 /**
34 * @since 5.4
35 * @var array
36 */
37 protected $defaultGroups = [
38 'notActivated' => [],
39 'activated' => [],
40 ];
41
a9229942
TD
42 /**
43 * list of user options
44 * @var UserOption[]
45 */
46 protected $userOptions = [];
47
48 const MERGE_MODE_EMAIL = 4;
49
50 const MERGE_MODE_USERNAME_OR_EMAIL = 5;
51
52 /**
53 * Creates a new UserImporter object.
54 */
55 public function __construct()
56 {
57 // get default notification events
58 $sql = "SELECT eventID
59 FROM wcf" . WCF_N . "_user_notification_event
60 WHERE preset = ?";
61 $statement = WCF::getDB()->prepareStatement($sql);
62 $statement->execute([1]);
63 $this->eventIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
64
65 $userOptionList = new UserOptionList();
66 $userOptionList->readObjects();
67 $this->userOptions = $userOptionList->getObjects();
72820037
TD
68
69 $this->defaultGroups = [
70 'notActivated' => UserGroup::getGroupIDsByType([UserGroup::EVERYONE, UserGroup::GUESTS]),
71 'activated' => UserGroup::getGroupIDsByType([UserGroup::EVERYONE, UserGroup::USERS]),
72 ];
a9229942
TD
73 }
74
75 /**
76 * @inheritDoc
77 */
78 public function import($oldID, array $data, array $additionalData = [])
79 {
80 $targetUser = null;
81
82 // whether to perform a merge
83 $performMerge = false;
84
85 // fetch user with same username
86 $conflictingUser = User::getUserByUsername($data['username']);
87 switch (ImportHandler::getInstance()->getUserMergeMode()) {
88 /** @noinspection PhpMissingBreakStatementInspection */
89 case self::MERGE_MODE_USERNAME_OR_EMAIL:
90 // merge target will be the conflicting user
91 $targetUser = $conflictingUser;
92
93 // check whether user exists
94 if ($targetUser->userID) {
95 $performMerge = true;
96 break;
97 }
05e0a089 98 // no break
a9229942
TD
99 case self::MERGE_MODE_EMAIL:
100 // fetch merge target
101 $targetUser = User::getUserByEmail($data['email']);
102 // if it exists: perform a merge
103 if ($targetUser->userID) {
104 $performMerge = true;
105 }
106 break;
107 }
108
109 // merge should be performed
110 if ($performMerge) {
111 ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.user', $oldID, $targetUser->userID);
112
113 return 0;
114 }
115
116 // a conflict arose, but no merge was performed, resolve
117 if ($conflictingUser->userID) {
118 // rename user
119 $data['username'] = self::resolveDuplicate($data['username']);
120 }
121
122 // check existing user id
123 if (\ctype_digit((string)$oldID)) {
124 $user = new User($oldID);
125 if (!$user->userID) {
126 $data['userID'] = $oldID;
127 }
128 }
129
130 // handle user options
131 $userOptions = [];
132 if (isset($additionalData['options'])) {
133 foreach ($additionalData['options'] as $optionName => $optionValue) {
134 if (\is_int($optionName)) {
135 $optionID = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user.option', $optionName);
136 } else {
137 $optionID = User::getUserOptionID($optionName);
138 }
139
140 if ($optionID) {
141 $userOptions[$optionID] = $optionValue;
142 }
143 }
144
145 // fix option values
146 foreach ($userOptions as $optionID => &$optionValue) {
147 switch ($this->userOptions[$optionID]->optionType) {
148 case 'boolean':
149 if ($optionValue) {
150 $optionValue = 1;
151 } else {
152 $optionValue = 0;
153 }
154 break;
155
156 case 'integer':
157 $optionValue = \intval($optionValue);
158 if ($optionValue > 2147483647) {
159 $optionValue = 2147483647;
160 }
161 break;
162
163 case 'float':
164 $optionValue = \floatval($optionValue);
165 break;
166
167 case 'textarea':
168 if (\strlen($optionValue) > 16777215) {
169 $optionValue = \substr($optionValue, 0, 16777215);
170 }
171 break;
172
173 case 'birthday':
174 case 'date':
175 if (!\preg_match('/^\d{4}\-\d{2}\-\d{2}$/', $optionValue)) {
176 $optionValue = '0000-00-00';
177 }
178 break;
179
180 default:
181 if (\strlen($optionValue) > 65535) {
182 $optionValue = \substr($optionValue, 0, 65535);
183 }
184 }
185 }
186 }
187
188 $languageIDs = [];
189 if (isset($additionalData['languages'])) {
190 foreach ($additionalData['languages'] as $languageCode) {
191 $language = LanguageFactory::getInstance()->getLanguageByCode($languageCode);
192 if ($language !== null) {
193 $languageIDs[] = $language->languageID;
194 }
195 }
196 }
197 if (empty($languageIDs)) {
198 $languageIDs[] = LanguageFactory::getInstance()->getDefaultLanguageID();
199 }
200
201 // assign an interface language
202 $data['languageID'] = \reset($languageIDs);
203
204 // create user
205 $user = UserEditor::create($data);
206 $userEditor = new UserEditor($user);
207
208 // updates user options
209 $userEditor->updateUserOptions($userOptions);
210
211 // save user groups
212 $groupIDs = [];
213 if (isset($additionalData['groupIDs'])) {
214 foreach ($additionalData['groupIDs'] as $oldGroupID) {
215 $newGroupID = ImportHandler::getInstance()->getNewID('com.woltlab.wcf.user.group', $oldGroupID);
216 if ($newGroupID) {
217 $groupIDs[] = $newGroupID;
218 }
219 }
220 }
221
72820037
TD
222 $groupIDs = \array_merge(
223 $groupIDs,
224 $this->defaultGroups[$user->pendingActivation() ? 'notActivated' : 'activated']
225 );
d7ccddb0
TD
226 $placeholders = '(?,?)' . \str_repeat(',(?,?)', \count($groupIDs) - 1);
227 $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_to_group
228 (userID, groupID)
229 VALUES {$placeholders}";
230 $statement = WCF::getDB()->prepareStatement($sql);
231 $parameters = [];
232 foreach ($groupIDs as $groupID) {
233 $parameters[] = $user->userID;
234 $parameters[] = $groupID;
a9229942 235 }
d7ccddb0 236 $statement->execute($parameters);
a9229942
TD
237
238 // save languages
239 $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_to_language
240 (userID, languageID)
241 VALUES (?, ?)";
242 $statement = WCF::getDB()->prepareStatement($sql);
243 foreach ($languageIDs as $languageID) {
244 $statement->execute([
245 $user->userID,
246 $languageID,
247 ]);
248 }
249
72820037
TD
250 if (!empty($this->eventIDs)) {
251 // save default user events
252 $placeholders = '(?,?)' . \str_repeat(',(?,?)', \count($this->eventIDs) - 1);
253 $sql = "INSERT IGNORE INTO wcf" . WCF_N . "_user_notification_event_to_user
254 (userID, eventID)
255 VALUES {$placeholders}";
256 $statement = WCF::getDB()->prepareStatement($sql);
257 $parameters = [];
258 foreach ($this->eventIDs as $eventID) {
259 $parameters[] = $user->userID;
260 $parameters[] = $eventID;
261 }
262 $statement->execute($parameters);
a9229942 263 }
a9229942
TD
264
265 // save mapping
266 ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.user', $oldID, $user->userID);
267
268 return $user->userID;
269 }
270
271 /**
272 * Revolves duplicate user names and returns the new user name.
273 *
274 * @param string $username
275 * @return string
276 */
277 private static function resolveDuplicate($username)
278 {
279 $i = 0;
280 do {
281 $i++;
282 $newUsername = 'Duplicate' . ($i > 1 ? $i : '') . ' ' . $username;
283 // try username
284 $sql = "SELECT userID
285 FROM wcf" . WCF_N . "_user
286 WHERE username = ?";
287 $statement = WCF::getDB()->prepareStatement($sql);
288 $statement->execute([$newUsername]);
289 $row = $statement->fetchArray();
290 if (empty($row['userID'])) {
291 break;
292 }
293 } while (true);
294
295 return $newUsername;
296 }
78db8ddf 297}