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