Commit | Line | Data |
---|---|---|
78db8ddf | 1 | <?php |
a9229942 | 2 | |
78db8ddf | 3 | namespace wcf\system\importer; |
a9229942 | 4 | |
a96244e1 | 5 | use wcf\data\user\group\UserGroup; |
7a23a706 | 6 | use wcf\data\user\option\UserOption; |
2ec5b95d | 7 | use wcf\data\user\option\UserOptionList; |
90d1166e | 8 | use wcf\data\user\User; |
b72edcd8 | 9 | use wcf\data\user\UserEditor; |
9f4e72b4 | 10 | use wcf\system\language\LanguageFactory; |
78db8ddf MW |
11 | use 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 |
20 | class 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 | } |