Propagate additional error info about XML parse failures
[moodle.git] / admin / uploaduser.php
CommitLineData
066bfbfe 1<?php
0a6150ca 2
3/// Bulk user registration script from a comma separated file
4/// Returns list of users with their user ids
5
6b09974b 6require_once('../config.php');
cc891abe 7require_once($CFG->libdir.'/adminlib.php');
066bfbfe 8require_once($CFG->libdir.'/textlib.class.php');
0a5dffcc 9require_once('uploaduser_form.php');
066bfbfe 10define('LINE_MAX_SIZE', 1024);
1ae083e4 11
066bfbfe 12//Note: commas within a field should be encoded as &#44 (for comma separated csv files)
13//Note: semicolon within a field should be encoded as &#59 (for semicolon separated csv files)
14$csv_delimiter = isset($CFG->CSV_DELIMITER) ? $CFG->CSV_DELIMITER : ',';
15$csv_encode = '&#' . (isset($CFG->CSV_ENCODE) ? $CFG->CSV_ENCODE : ord($csv_delimiter));
16
17@set_time_limit(0);
18@raise_memory_limit('192M');
0a6150ca 19
066bfbfe 20admin_externalpage_setup('uploadusers');
1ae083e4 21require_capability('moodle/site:uploadusers', get_context_instance(CONTEXT_SYSTEM));
0a6150ca 22
6b09974b 23if (! $site = get_site()) {
066bfbfe 24 error('Could not find site-level course');
6b09974b 25}
0a6150ca 26
1c0ccfd6 27$textlib = textlib_get_instance();
ca23a9c9 28
066bfbfe 29$struserrenamed = get_string('userrenamed', 'admin');
30$strusernotrenamedexists = get_string('usernotrenamedexists', 'error');
31$strusernotrenamedmissing = get_string('usernotrenamedmissing', 'error');
6b09974b 32
066bfbfe 33$struserupdated = get_string('useraccountupdated', 'admin');
34$strusernotupdated = get_string('usernotupdatederror', 'error');
6b09974b 35
066bfbfe 36$struseradded = get_string('newuser');
a7db355a 37$strusernotadded = get_string('usernotaddedregistered', 'error');
066bfbfe 38$strusernotaddederror = get_string('usernotaddederror', 'error');
a7db355a 39
40$struserdeleted = get_string('userdeleted', 'admin');
41$strusernotdeletederror = get_string('usernotdeletederror', 'error');
42$strusernotdeletedmissing = get_string('usernotdeletedmissing', 'error');
43
066bfbfe 44$strcannotassignrole = get_string('cannotassignrole', 'error');
45$strduplicateusername = get_string('duplicateusername', 'error');
46$strindent = '-->';
a5702569 47
0a5dffcc 48$mform = new admin_uploaduser_form();
a5702569 49
066bfbfe 50// Print the header
0a6150ca 51
066bfbfe 52admin_externalpage_print_header();
0a6150ca 53
066bfbfe 54// If a file has been uploaded, then process it
55if ( $formdata = $mform->get_data() ) {
0a5dffcc 56 $createpassword = $formdata->createpassword;
57 $updateaccounts = $formdata->updateaccounts;
58 $allowrenames = $formdata->allowrenames;
066bfbfe 59 $skipduplicates = $formdata->duplicatehandling;
ed22a01b 60
61 // make arrays of valid fields for error checking
066bfbfe 62 // the value associated to each field is: 0 = optional field, 1 = field required either in default values or in data file
63 $fields = array(
64 'firstname' => 1,
65 'lastname' => 1,
66 'username' => 1,
67 'email' => 1,
68 'city' => 1,
69 'country' => 1,
70 'lang' => 1,
71 'auth' => 1,
72 'timezone' => 1,
73 'mailformat' => 1,
74 'maildisplay' => 1,
ed22a01b 75 'htmleditor' => 0,
76 'ajax' => 0,
066bfbfe 77 'autosubscribe' => 1,
ed22a01b 78 'mnethostid' => 0,
066bfbfe 79 'institution' => 0,
80 'department' => 0,
81 'idnumber' => 0,
82 'icq' => 0,
83 'phone1' => 0,
84 'phone2' => 0,
85 'address' => 0,
86 'url' => 0,
87 'description' => 0,
88 'icq' => 0,
89 'oldusername' => 0,
90 'emailstop' => 1,
a7db355a 91 'deleted' => 0,
066bfbfe 92 'password' => !$createpassword,
93 );
94
ed22a01b 95 $text = $mform->get_file_content('userfile');
96 // convert to utf-8 encoding
97 $text = $textlib->convert($text, $formdata->encoding, 'utf-8');
98 // remove Unicode BOM from first line
99 $text = $textlib->trim_utf8_bom($text);
100 // Fix mac/dos newlines
101 $text = preg_replace('!\r\n?!', "\n", $text);
102
103 // find header row
066bfbfe 104 $headers = array();
ed22a01b 105 $linenum = 0;
106 $line = strtok($text, "\n");
107 while ($line !== false) {
108 $linenum++;
109 $line = trim($line);
110 if ($line == '') {
111 //ignore empty lines
112 $line = strtok("\n");
113 continue;
066bfbfe 114 }
ed22a01b 115 $line = explode($csv_delimiter, $line);
116 // check for valid field names
117 foreach ($line as $key => $value) {
118 $value = trim($value); // remove whitespace
119 if (!in_array($value, $fields) && // if not a standard field and not an enrolment field, then we have an error
120 !preg_match('/^course\d+$/', $value) && !preg_match('/^group\d+$/', $value) &&
121 !preg_match('/^type\d+$/', $value) && !preg_match('/^role\d+$/', $value)) {
122 error(get_string('invalidfieldname', 'error', $value), 'uploaduser.php?sesskey='.$USER->sesskey);
123 }
124 $headers[$key] = $value;
125 }
126 $line = false; // found header line
6b09974b 127 }
a2ce7344 128
a7db355a 129 // check that required fields are present or a default value for them exists
130 $headersOk = true;
131 // disable the check if we also have deleting information (ie. deleted column)
132 if (!in_array('deleted', $headers)) {
133 foreach ($fields as $key => $required) {
ed22a01b 134 if($required && !in_array($key, $headers) && (!isset($formdata->$key) || $formdata->$key==='')) {
a7db355a 135 notify(get_string('missingfield', 'error', $key));
136 $headersOk = false;
137 }
138 }
066bfbfe 139 }
ed22a01b 140 if ($headersOk) {
a7db355a 141 $usersnew = 0;
142 $usersupdated = 0;
143 $userserrors = 0;
144 $usersdeleted = 0;
145 $renames = 0;
146 $renameerrors = 0;
147 $deleteerrors = 0;
148 $newusernames = array();
149 // We'll need courses a lot, so fetch it early and keep it in memory, indexed by their shortname
150 $tmp =& get_courses('all','','id,shortname,visible');
151 $courses = array();
152 foreach ($tmp as $c) {
153 $courses[$c->shortname] = $c;
154 }
155 unset($tmp);
066bfbfe 156
a7db355a 157 echo '<p id="results">';
ed22a01b 158 $line = strtok("\n");
159 while ($line !== false) {
160 $linenum++;
161 $line = trim($line);
162 if ($line == '') {
163 //empty line??
164 $line = strtok("\n");
165 continue;
166 }
167 $line = explode($csv_delimiter, $line);
a7db355a 168 $errors = '';
169 $user = new object();
170 // by default, use the local mnet id (this may be changed in the file)
171 $user->mnethostid = $CFG->mnet_localhost_id;
a7db355a 172 // add fields to user object
173 foreach ($line as $key => $value) {
174 if($value !== '') {
175 $key = $headers[$key];
176 //decode encoded commas
177 $value = str_replace($csv_encode,$csv_delimiter,trim($value));
178 // special fields: password and username
179 if ($key == 'password' && !empty($value)) {
180 $user->$key = hash_internal_user_password($value);
181 } else if($key == 'username') {
182 $value = $textlib->strtolower(addslashes($value));
183 if(empty($CFG->extendedusernamechars)) {
184 $value = eregi_replace('[^(-\.[:alnum:])]', '', $value);
185 }
186 @$newusernames[$value]++;
187 $user->$key = $value;
188 } else {
189 $user->$key = addslashes($value);
190 }
6b09974b 191 }
16a1fed4 192 }
ed22a01b 193
a7db355a 194 // add default values for remaining fields
195 foreach ($fields as $key => $required) {
196 if(isset($user->$key)) {
197 continue;
0063abee 198 }
a7db355a 199 if(!isset($formdata->$key) || $formdata->$key==='') { // no default value was submited
200 // if the field is required, give an error only if we are adding the user or deleting a user with unkown username
ed22a01b 201 if($required && (empty($user->deleted) || $key == 'username')) {
a7db355a 202 $errors .= get_string('missingfield', 'error', $key) . ' ';
6b09974b 203 }
a7db355a 204 continue;
205 }
206 // process templates
207 $template = $formdata->$key;
208 $templatelen = strlen($template);
209 $value = '';
210 for ($i = 0 ; $i < $templatelen; ++$i) {
211 if($template[$i] == '%') {
212 $case = 0; // 1=lowercase, 2=uppercase
213 $len = 0; // number of characters to keep
214 $info = null; // data to process
215 for($j = $i + 1; is_null($info) && $j < $templatelen; ++$j) {
216 $car = $template[$j];
217 if ($car >= '0' && $car <= '9') {
218 $len = $len * 10 + (int)$car;
219 } else if($car == '-') {
220 $case = 1;
ed22a01b 221 } else if($car == '+') {
a7db355a 222 $case = 2;
223 } else if($car == 'f') { // first name
224 $info = @$user->firstname;
225 } else if($car == 'l') { // last name
226 $info = @$user->lastname;
227 } else if($car == 'u') { // username
228 $info = @$user->username;
229 } else if($car == '%' && $j == $i+1) {
230 $info = '%';
231 } else { // invalid character
232 $info = '';
233 }
234 }
235 if($info==='' || is_null($info)) { // invalid template
236 continue;
237 }
238 $i = $j - 1;
239 // change case
ed22a01b 240 if($case == 1) {
a7db355a 241 $info = $textlib->strtolower($info);
242 } else if($case == 2) {
243 $info = $textlib->strtoupper($info);
244 }
245 if($len) { // truncate data
246 $info = $textlib->substr($info, 0, $len);
247 }
248 $value .= $info;
249 } else {
250 $value .= $template[$i];
6b09974b 251 }
a7db355a 252 }
ed22a01b 253
a7db355a 254 if($key == 'username') {
255 $value = $textlib->strtolower($value);
256 if(empty($CFG->extendedusernamechars)) {
257 $value = eregi_replace('[^(-\.[:alnum:])]', '', $value);
6740dd64 258 }
a7db355a 259 @$newusernames[$value]++;
260 // check for new username duplicates
261 if($newusernames[$value] > 1) {
262 if($skipduplicates) {
263 $errors .= $strduplicateusername . ' (' . stripslashes($value) . '). ';
264 continue;
265 } else {
266 $value .= $newusernames[$value];
267 }
066bfbfe 268 }
066bfbfe 269 }
a7db355a 270 $user->$key = $value;
066bfbfe 271 }
a7db355a 272 if($errors) {
273 notify(get_string('erroronline', 'error', $linenum). ': ' . $errors);
274 ++$userserrors;
275 continue;
276 }
277
ed22a01b 278 // delete user
a7db355a 279 if(@$user->deleted) {
280 $info = ': ' . stripslashes($user->username) . '. ';
281 if($user =& get_record('user', 'username', $user->username, 'mnethostid', $user->mnethostid)) {
282 $user->timemodified = time();
283 $user->username = addslashes($user->email . $user->timemodified); // Remember it just in case
284 $user->deleted = 1;
285 $user->email = ''; // Clear this field to free it up
286 $user->idnumber = ''; // Clear this field to free it up
287 if (update_record('user', $user)) {
288 // not sure if this is needed. unenrol_student($user->id); // From all courses
289 delete_records('role_assignments', 'userid', $user->id); // unassign all roles
290 // remove all context assigned on this user?
291 echo $struserdeleted . $info . '<br />';
292 ++$usersdeleted;
066bfbfe 293 } else {
a7db355a 294 notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotdeletederror . $info);
295 ++$deleteerrors;
a2ce7344 296 }
a7db355a 297 } else {
298 notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotdeletedmissing . $info);
299 ++$deleteerrors;
ed22a01b 300 }
a7db355a 301 continue;
066bfbfe 302 }
ed22a01b 303
a7db355a 304 // save the user to the database
305 $user->confirmed = 1;
306 $user->timemodified = time();
066bfbfe 307
a7db355a 308 // before insert/update, check whether we should be updating an old record instead
309 if ($allowrenames && !empty($user->oldusername) ) {
310 $user->oldusername = $textlib->strtolower($user->oldusername);
311 $info = ': ' . stripslashes($user->oldusername) . '-->' . stripslashes($user->username) . '. ';
312 if ($olduser =& get_record('user', 'username', $user->oldusername, 'mnethostid', $user->mnethostid)) {
313 if (set_field('user', 'username', $user->username, 'id', $olduser->id)) {
314 echo $struserrenamed . $info;
315 $renames++;
316 } else {
317 notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotrenamedexists . $info);
318 $renameerrors++;
319 continue;
320 }
066bfbfe 321 } else {
a7db355a 322 notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotrenamedmissing . $info);
066bfbfe 323 $renameerrors++;
324 continue;
325 }
066bfbfe 326 }
a2ce7344 327
a7db355a 328 // save the information
329 if ($olduser =& get_record('user', 'username', $user->username, 'mnethostid', $user->mnethostid)) {
330 $user->id = $olduser->id;
331 $info = ': ' . stripslashes($user->username) .' (ID = ' . $user->id . ')';
332 if ($updateaccounts) {
333 // Record is being updated
334 if (update_record('user', $user)) {
335 echo $struserupdated . $info . '<br />';
336 $usersupdated++;
337 } else {
338 notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotupdated . $info);
339 $userserrors++;
340 continue;
341 }
066bfbfe 342 } else {
a7db355a 343 //Record not added - user is already registered
344 //In this case, output userid from previous registration
345 //This can be used to obtain a list of userids for existing users
346 echo $strusernotadded . $info . '<br />';
066bfbfe 347 $userserrors++;
066bfbfe 348 }
a7db355a 349 } else { // new user
350 if ($user->id = insert_record('user', $user)) {
351 $info = ': ' . stripslashes($user->username) .' (ID = ' . $user->id . ')';
352 echo $struseradded . $info . '<br />';
353 $usersnew++;
354 if (empty($user->password) && $createpassword) {
355 // passwords will be created and sent out on cron
ed22a01b 356 set_user_preference('create_password', 1, $user->id);
357 set_user_preference('auth_forcepasswordchange', 1, $user->id);
a7db355a 358 }
359 } else {
360 // Record not added -- possibly some other error
361 notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotaddederror . ': ' . stripslashes($user->username));
362 $userserrors++;
363 continue;
066bfbfe 364 }
066bfbfe 365 }
6b09974b 366
a7db355a 367 // find course enrolments, groups and roles/types
368 for($ncourses = 1; $addcourse = @$user->{'course' . $ncourses}; ++$ncourses) {
369 // find course
370 if(!$course = @$courses[$addcourse]) {
371 notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('unknowncourse', 'error', $addcourse));
372 continue;
066bfbfe 373 }
a7db355a 374 // find role
375 if ($addrole = @$user->{'role' . $ncourses}) {
376 $coursecontext =& get_context_instance(CONTEXT_COURSE, $course->id);
377 if (!$ok = role_assign($addrole, $user->id, 0, $coursecontext->id)) {
378 echo $strindent . $strcannotassignrole . '<br >';
379 }
380 } else {
381 // if no role, then find "old" enrolment type
382 switch ($addtype = @$user->{'type' . $ncourses}) {
383 case 2: // teacher
384 $ok = add_teacher($user->id, $course->id, 1);
385 break;
386 case 3: // non-editing teacher
387 $ok = add_teacher($user->id, $course->id, 0);
388 break;
389 case 1: // student
ed22a01b 390 default:
a7db355a 391 $ok = enrol_student($user->id, $course->id);
392 break;
393 }
394 }
395 if ($ok) { // OK
396 echo $strindent . get_string('enrolledincourse', '', $addcourse) . '<br />';
397 } else {
398 notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('enrolledincoursenot', '', $addcourse));
066bfbfe 399 }
6b09974b 400
a7db355a 401 // find group to add to
402 if ($addgroup = @$user->{'group' . $ncourses}) {
403 if ($gid =& groups_get_group_by_name($course->id, $addgroup)) {
404 $coursecontext =& get_context_instance(CONTEXT_COURSE, $course->id);
405 if (count(get_user_roles($coursecontext, $user->id))) {
406 if (groups_add_member($gid, $user->id)) {
407 echo $strindent . get_string('addedtogroup','',$addgroup) . '<br />';
408 } else {
409 notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('addedtogroupnot','',$addgroup));
410 }
066bfbfe 411 } else {
a7db355a 412 notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('addedtogroupnotenrolled','',$addgroup));
a5702569 413 }
066bfbfe 414 } else {
a7db355a 415 notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('groupunknown','error',$addgroup));
0063abee 416 }
6b09974b 417 }
a5702569 418 }
ed22a01b 419 //read next line
420 $line = strtok("\n");
066bfbfe 421 }
a7db355a 422 echo '</p>';
423 notify(get_string('userscreated', 'admin') . ': ' . $usersnew);
424 notify(get_string('usersupdated', 'admin') . ': ' . $usersupdated);
425 notify(get_string('usersdeleted', 'admin') . ': ' . $usersdeleted);
426 notify(get_string('deleteerrors', 'admin') . ': ' . $deleteerrors);
427 if ($allowrenames) {
428 notify(get_string('usersrenamed', 'admin') . ': ' . $renames);
429 notify(get_string('renameerrors', 'admin') . ': ' . $renameerrors);
430 }
431 notify(get_string('errors', 'admin') . ': ' . $userserrors);
066bfbfe 432 }
066bfbfe 433 echo '<hr />';
6b09974b 434}
0a6150ca 435
a5702569 436/// Print the form
d526725b 437print_heading_with_help(get_string('uploadusers'), 'uploadusers2');
0a5dffcc 438$mform->display();
1ae083e4 439admin_externalpage_print_footer();
0a6150ca 440?>