]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-11996 bulk user upload - improvements, bugfixes and cleanup;
authorskodak <skodak>
Mon, 5 Nov 2007 00:43:37 +0000 (00:43 +0000)
committerskodak <skodak>
Mon, 5 Nov 2007 00:43:37 +0000 (00:43 +0000)
new csv import library included ;-)

admin/uploaduser.php
admin/uploaduser_form.php
lang/en_utf8/admin.php
lang/en_utf8/error.php
lang/en_utf8/help/uploadusers2.html
lang/en_utf8/moodle.php
lib/csvlib.class.php [new file with mode: 0644]
theme/standard/styles_color.css
theme/standard/styles_fonts.css
theme/standard/styles_layout.css

index 246951b318cb630599280fd9a0dfeda51bd94968..3be938c3a4d7973a3a8d143353ed60273c98c169 100755 (executable)
@@ -3,23 +3,26 @@
 /// Bulk user registration script from a comma separated file
 /// Returns list of users with their user ids
 
-require_once('../config.php');
+require('../config.php');
 require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/csvlib.class.php');
+require_once($CFG->dirroot.'/user/profile/lib.php');
 require_once('uploaduser_form.php');
 
-$uplid       = optional_param('uplid', '', PARAM_FILE);
+$iid         = optional_param('iid', '', PARAM_INT);
 $previewrows = optional_param('previewrows', 10, PARAM_INT);
-$separator   = optional_param('separator', 'comma', PARAM_ALPHA);
+$readcount   = optional_param('readcount', 0, PARAM_INT);
 
-if (!defined('UP_LINE_MAX_SIZE')) {
-    define('UP_LINE_MAX_SIZE', 4096);
-}
+define('UU_ADDNEW', 0);
+define('UU_ADDINC', 1);
+define('UU_ADD_UPDATE', 2);
+define('UU_UPDATE', 3);
 
 @set_time_limit(3600); // 1 hour should be enough
 @raise_memory_limit('256M');
 if (function_exists('apache_child_terminate')) {
-    // if we are running from Apache, give httpd a hint that 
-    // it can recycle the process after it's done. Apache's 
+    // if we are running from Apache, give httpd a hint that
+    // it can recycle the process after it's done. Apache's
     // memory management is truly awful but we can help it.
     @apache_child_terminate();
 }
@@ -28,126 +31,72 @@ admin_externalpage_setup('uploadusers');
 require_capability('moodle/site:uploadusers', get_context_instance(CONTEXT_SYSTEM));
 
 $textlib = textlib_get_instance();
+$systemcontext = get_context_instance(CONTEXT_SYSTEM);
+
+$struserrenamed             = get_string('userrenamed', 'admin');
+$strusernotrenamedexists    = get_string('usernotrenamedexists', 'error');
+$strusernotrenamedmissing   = get_string('usernotrenamedmissing', 'error');
+$strusernotrenamedoff       = get_string('usernotrenamedoff', 'error');
+$strusernotrenamedadmin     = get_string('usernotrenamedadmin', 'error');
+
+$struserupdated             = get_string('useraccountupdated', 'admin');
+$strusernotupdated          = get_string('usernotupdatederror', 'error');
+$strusernotupdatednotexists = get_string('usernotupdatednotexists', 'error');
+$strusernotupdatedadmin     = get_string('usernotupdatedadmin', 'error');
+
+$struseradded               = get_string('newuser');
+$strusernotadded            = get_string('usernotaddedregistered', 'error');
+$strusernotaddederror       = get_string('usernotaddederror', 'error');
+
+$struserdeleted             = get_string('userdeleted', 'admin');
+$strusernotdeletederror     = get_string('usernotdeletederror', 'error');
+$strusernotdeletedmissing   = get_string('usernotdeletedmissing', 'error');
+$strusernotdeletedoff       = get_string('usernotdeletedoff', 'error');
+$strusernotdeletedadmin     = get_string('usernotdeletedadmin', 'error');
+
+$strcannotassignrole        = get_string('cannotassignrole', 'error');
+$strduplicateusername       = get_string('duplicateusername', 'error');
+
+$struserauthunsupported     = get_string('userauthunsupported', 'error');
+
+
+$errorstr                   = get_string('error');
 
-$struserrenamed = get_string('userrenamed', 'admin');
-$strusernotrenamedexists = get_string('usernotrenamedexists', 'error');
-$strusernotrenamedmissing = get_string('usernotrenamedmissing', 'error');
-
-$struserupdated = get_string('useraccountupdated', 'admin');
-$strusernotupdated = get_string('usernotupdatederror', 'error');
-
-$struseradded = get_string('newuser');
-$strusernotadded = get_string('usernotaddedregistered', 'error');
-$strusernotaddederror = get_string('usernotaddederror', 'error');
-
-$struserdeleted = get_string('userdeleted', 'admin');
-$strusernotdeletederror = get_string('usernotdeletederror', 'error');
-$strusernotdeletedmissing = get_string('usernotdeletedmissing', 'error');
-
-$strcannotassignrole = get_string('cannotassignrole', 'error');
-$strduplicateusername = get_string('duplicateusername', 'error');
-$strindent = '-->';
-
-$return = $CFG->wwwroot.'/'.$CFG->admin.'/uploaduser.php';
-
-// make arrays of valid fields for error checking
-// the value associated to each field is: 0 = optional field, 1 = field required either in default values or in data file
-$fields = array(
-    'firstname' => 1,
-    'lastname' => 1,
-    'username' => 1,
-    'email' => 1,
-    'city' => 1,
-    'country' => 1,
-    'lang' => 1,
-    'auth' => 1,
-    'timezone' => 1,
-    'mailformat' => 1,
-    'maildisplay' => 1,
-    'htmleditor' => 0,
-    'ajax' => 0,
-    'autosubscribe' => 1,
-    'mnethostid' => 0,
-    'institution' => 0,
-    'department' => 0,
-    'idnumber' => 0,
-    'icq' => 0,
-    'phone1' => 0,
-    'phone2' => 0,
-    'address' => 0,
-    'url' => 0,
-    'description' => 0,
-    'icq' => 0,
-    'oldusername' => 0,
-    'emailstop' => 1,
-    'deleted' => 0,
-    'password' => 0, // changed later
-);
-
-if (empty($uplid)) {
+$returnurl = $CFG->wwwroot.'/'.$CFG->admin.'/uploaduser.php';
+$bulknurl  = $CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk.php';
+
+// array of all valid fields for validation
+$STD_FIELDS = array('firstname', 'lastname', 'username', 'email', 'city', 'country', 'lang', 'auth', 'timezone', 'mailformat', 'maildisplay', 'htmleditor',
+                    'ajax', 'autosubscribe', 'mnethostid', 'institution', 'department', 'idnumber', 'icq', 'phone1', 'phone2', 'address', 'url', 'description',
+                    'icq', 'oldusername', 'emailstop', 'deleted',  'password');
+
+$PRF_FIELDS = array();
+
+if ($prof_fields = $fields = get_records_select('user_info_field')) {
+    foreach ($prof_fields as $prof_field) {
+        $PRF_FIELDS[] = 'profile_field_'.$prof_field->shortname;
+    }
+    unset($prof_fields);
+}
+
+if (empty($iid)) {
     $mform = new admin_uploaduser_form1();
 
     if ($formdata = $mform->get_data()) {
-        if (!$filename = make_upload_directory('temp/uploaduser/'.$USER->id, true)) {
-            error('Can not create temporary upload directory!', $return);
-        }
-        // use current (non-conflicting) time stamp
-        $uplid = time();
-        while (file_exists($filename.'/'.$uplid)) {
-            $uplid--;
-        }
-        $filename = $filename.'/'.$uplid;
-
-        $text = $mform->get_file_content('userfile');
-        // convert to utf-8 encoding
-        $text = $textlib->convert($text, $formdata->encoding, 'utf-8');
-        // remove Unicode BOM from first line
-        $text = $textlib->trim_utf8_bom($text);
-        // Fix mac/dos newlines
-        $text = preg_replace('!\r\n?!', "\n", $text);
-        //remove empty lines at the beginning and end
-        $text = trim($text);
-
-        // verify each line has the same number of separators - this detects major breakage in files
-        $line = strtok($text, "\n");
-        if ($line === false) {
-            error('Empty file', $return); //TODO: localize
-        }
-
-        // test headers
-        $csv_delimiter = get_upload_csv_delimiter($separator);
-        $col_count = substr_count($line, $csv_delimiter);
-        if ($col_count < 2) {
-            error('Not enough columns, please verify the separator setting!', $return); //TODO: localize
-        }
+        $iid = csv_import_reader::get_new_iid('uploaduser');
+        $cir = new csv_import_reader($iid, 'uploaduser');
 
-        $line = explode($csv_delimiter, $line);
-        foreach ($line as $key => $value) {
-            $value = trim($value); // remove whitespace
-            if (!array_key_exists($value, $fields) && // if not a standard field and not an enrolment field, then we have an error
-                !preg_match('/^course\d+$/', $value) && !preg_match('/^group\d+$/', $value) &&
-                !preg_match('/^type\d+$/', $value) && !preg_match('/^role\d+$/', $value)) {
-                error(get_string('invalidfieldname', 'error', $value), $return);
-            }
-        }
+        $content = $mform->get_file_content('userfile');
 
-        $line = strtok("\n");
-        if ($line === false) {
-            error('Only one row present, can not continue!', $return); //TODO: localize
-        }
+        $readcount = $cir->load_csv_content($content, $formdata->encoding, $formdata->delimiter_name, 'validate_user_upload_columns');
+        unset($content);
 
-        while ($line !== false) {
-            if (substr_count($line, $csv_delimiter) !== $col_count) {
-                error('Incorrect file format - number of columns is not constant!', $return); //TODO: localize
-            }
-            $line = strtok("\n");
+        if ($readcount === false) {
+            error($cir->get_error(), $returnurl);
+        } else if ($readcount == 0) {
+            error(get_string('csvemptyfile', 'error'), $returnurl);
         }
-
-        // store file
-        $fp = fopen($filename, "w");
-        fwrite($fp,$text);
-        fclose($fp);
-        // continue to second form
+        // continue to form2
 
     } else {
         admin_externalpage_print_header();
@@ -156,351 +105,566 @@ if (empty($uplid)) {
         admin_externalpage_print_footer();
         die;
     }
+} else {
+    $cir = new csv_import_reader($iid, 'uploaduser');
 }
 
-$mform = new admin_uploaduser_form2();
-// set initial date from form1
-$mform->set_data(array('separator'=>$separator, 'uplid'=>$uplid, 'previewrows'=>$previewrows));
+if (!$columns = $cir->get_columns()) {
+    error('Error reading temporary file', $returnurl);
+}
+$mform = new admin_uploaduser_form2(null, $columns);
+// get initial date from form1
+$mform->set_data(array('iid'=>$iid, 'previewrows'=>$previewrows, 'readcount'=>$readcount));
 
 // If a file has been uploaded, then process it
 if ($formdata = $mform->is_cancelled()) {
-    user_upload_cleanup($uplid);
-    redirect($return);
+    $cir->cleanup(true);
+    redirect($returnurl);
 
-} else if ($formdata = $mform->get_data()) {
+} else if ($formdata = $mform->get_data(false)) { // no magic quotes here!!!
     // Print the header
     admin_externalpage_print_header();
-    print_heading(get_string('uploadusers'));
+    print_heading(get_string('uploadusersresult', 'admin'));
+
+    $optype = $formdata->uutype;
+
+    $createpasswords = (!empty($formdata->uupasswordnew) and $optype != UU_UPDATE);
+    $updatepasswords = (!empty($formdata->uupasswordold)  and $optype != UU_ADDNEW and $optype != UU_ADDINC);
+    $allowrenames    = (!empty($formdata->uuallowrenames) and $optype != UU_ADDNEW and $optype != UU_ADDINC);
+    $allowdeletes    = (!empty($formdata->uuallowdeletes) and $optype != UU_ADDNEW and $optype != UU_ADDINC);
+    $updatetype      = isset($formdata->uuupdatetype) ? $formdata->uuupdatetype : 0;
+    $bulk            = $formdata->uubulk;
+
+    // verification moved to two places: after upload and into form2
+    $usersnew     = 0;
+    $usersupdated = 0;
+    $userserrors  = 0;
+    $deletes      = 0;
+    $deleteerrors = 0;
+    $renames      = 0;
+    $renameerrors = 0;
+    $usersskipped = 0;
+
+    // caches
+    $ccache    = array(); // course cache - do not fetch all courses here, we  will not probably use them all anyway!
+    $rolecache = array(); // roles lookup cache
+
+    $allowedauths   = uu_allowed_auths();
+    $allowedauths   = array_keys($allowedauths);
+    $availableauths = get_list_of_plugins('auth');
+
+    $allowedroles = uu_allowed_roles(true);
+    foreach ($allowedroles as $rid=>$rname) {
+        $rolecache[$rid] = new object();
+        $rolecache[$rid]->id = $rid;
+        $rolecache[$rid]->name = $rname;
+        if (!is_numeric($rname)) { // only non-numeric shornames are supported!!!
+            $rolecache[$rname] = new object();
+            $rolecache[$rname]->id = $rid;
+            $rolecache[$rname]->name = $rname;
+        }
+    }
+    unset($allowedroles);
 
-    $createpassword = $formdata->createpassword;
-    $updateaccounts = $formdata->updateaccounts;
-    $allowrenames   = $formdata->allowrenames;
-    $skipduplicates = $formdata->duplicatehandling;
+    // clear bilk selection
+    if ($bulk) {
+        $SESSION->bulk_susers = array();
+    }
 
-    $fields['password'] = !$createpassword;
+    // init csv import helper
+    $cir->init();
+    $linenum = 1; //column header is first line
 
-    $filename = $CFG->dataroot.'/temp/uploaduser/'.$USER->id.'/'.$uplid;
-    if (!file_exists($filename)) {
-        user_upload_cleanup($uplid);
-        error('Error reading temporary file!', $return); //TODO: localize
-    }
-    if (!$fp = fopen($filename, "r")) {
-        user_upload_cleanup($uplid);
-        error('Error reading temporary file!', $return); //TODO: localize
-    }
-    
-    $csv_delimiter = get_upload_csv_delimiter($separator);
-    $csv_encode    = get_upload_csv_encode($csv_delimiter);
-
-    // find header row
-    $headers = array();
-    $linenum = 1;
-    $line = explode($csv_delimiter, fgets($fp, UP_LINE_MAX_SIZE));
-
-    // prepare headers
-    foreach ($line as $key => $value) {
-        $headers[$key] = trim($value);
-    }
+    // init upload progress tracker
+    $upt = new uu_progress_tracker();
+    $upt->init(); // start table
 
-    // check that required fields are present or a default value for them exists
-    $headersOk = true;
-    // disable the check if we also have deleting information (ie. deleted column)
-    if (!in_array('deleted', $headers)) {
-        foreach ($fields as $key => $required) {
-            if($required && !in_array($key, $headers) && (!isset($formdata->$key) || $formdata->$key==='')) {
-                notify(get_string('missingfield', 'error', $key));
-                $headersOk = false;
+    while ($line = $cir->next()) {
+        $upt->flush();
+        $linenum++;
+
+        $upt->track('line', $linenum);
+
+        $user = new object();
+        // by default, use the local mnet id (this may be changed in the file)
+        $user->mnethostid = $CFG->mnet_localhost_id;
+        // add fields to user object
+        foreach ($line as $key => $value) {
+            if ($value !== '') {
+                $key = $columns[$key];
+                // password is special field
+                if ($key == 'password') {
+                    if ($value !== '') {
+                        $user->password = hash_internal_user_password($value);
+                    }
+                } else {
+                    $user->$key = $value;
+                    if (in_array($key, $upt->columns)) {
+                        $upt->track($key, $value);
+                    }
+                }
             }
         }
-    }
-    if ($headersOk) {
-        $usersnew     = 0;
-        $usersupdated = 0;
-        $userserrors  = 0;
-        $usersdeleted = 0;
-        $renames      = 0;
-        $renameerrors = 0;
-        $deleteerrors = 0;
-        $newusernames = array();
-        // We'll need courses a lot, so fetch it early and keep it in memory, indexed by their shortname
-        $tmp =& get_courses('all','','id,shortname,visible');
-        $courses = array();
-        foreach ($tmp as $c) {
-            $courses[$c->shortname] = $c;
+
+        // get username, first/last name now - we need them in templates!!
+        if ($optype == UU_UPDATE) {
+            // when updating only username is required
+            if (!isset($user->username)) {
+                $upt->track('status', get_string('missingfield', 'error', 'username'), 'error');
+                $upt->track('username', $errorstr, 'error');
+                $userserrors++;
+                continue;
+            }
+
+        } else {
+            $error = false;
+            // when all other ops need firstname and lastname
+            if (!isset($user->firstname) or $user->firstname === '') {
+                $upt->track('status', get_string('missingfield', 'error', 'firstname'), 'error');
+                $upt->track('firstname', $errorstr, 'error');
+                $error = true;
+            }
+            if (!isset($user->lastname) or $user->lastname === '') {
+                $upt->track('status', get_string('missingfield', 'error', 'lastname'), 'error');
+                $upt->track('lastname', $errorstr, 'error');
+                $error = true;
+            }
+            if ($error) {
+                $userserrors++;
+                continue;
+            }
+            // we require username too - we might use template for it though
+            if (!isset($user->username)) {
+                if (!isset($formdata->username) or $formdata->username === '') {
+                    $upt->track('status', get_string('missingfield', 'error', 'username'), 'error');
+                    $upt->track('username', $errorstr, 'error');
+                    $userserrors++;
+                    continue;
+                } else {
+                    $user->username = process_template($formdata->username, $user);
+                    $upt->track('username', $user->username);
+                }
+            }
         }
-        unset($tmp);
-
-        echo '<p id="results">';
-        while (!feof($fp)) {
-            $linenum++;
-            $line = explode($csv_delimiter, fgets($fp, UP_LINE_MAX_SIZE));
-            $errors = '';
-            $user = new object();
-            // by default, use the local mnet id (this may be changed in the file)
-            $user->mnethostid = $CFG->mnet_localhost_id;
-            // add fields to user object
-            foreach ($line as $key => $value) {
-                if($value !== '') {
-                    $key = $headers[$key];
-                    //decode encoded commas
-                    $value = str_replace($csv_encode,$csv_delimiter,trim($value));
-                    // special fields: password and username
-                    if ($key == 'password' && !empty($value)) {
-                        $user->$key = hash_internal_user_password($value);
-                    } else if($key == 'username') {
-                        $value = $textlib->strtolower(addslashes($value));
-                        if(empty($CFG->extendedusernamechars)) {
-                            $value = eregi_replace('[^(-\.[:alnum:])]', '', $value);
-                        }
-                        @$newusernames[$value]++;
-                        $user->$key = $value;
-                    } else {
-                        $user->$key = addslashes($value);
-                    }
+
+        // normalize username
+        $user->username = $textlib->strtolower($user->username);
+        if (empty($CFG->extendedusernamechars)) {
+            $user->username = eregi_replace('[^(-\.[:alnum:])]', '', $user->username);
+        }
+        if (empty($user->username)) {
+            $upt->track('status', get_string('missingfield', 'error', 'username'), 'error');
+            $upt->track('username', $errorstr, 'error');
+            $userserrors++;
+            continue;
+        }
+
+        if ($existinguser = get_record('user', 'username', addslashes($user->username), 'mnethostid', $user->mnethostid)) {
+            $upt->track('id', $existinguser->id, 'normal', false);
+        }
+
+        // find out in username incrementing required
+        if ($existinguser and $optype == UU_ADDINC) {
+            $oldusername = $user->username;
+            $user->username = increment_username($user->username, $user->mnethostid);
+            $upt->track('username', '', 'normal', false); // clear previous
+            $upt->track('username', $oldusername.'-->'.$user->username, 'info');
+            $existinguser = false;
+        }
+
+        // add default values for remaining fields
+        foreach ($STD_FIELDS as $field) {
+            if (isset($user->$field)) {
+                continue;
+            }
+            // all validation moved to form2
+            if (isset($formdata->$field)) {
+                // process templates
+                $user->$field = process_template($formdata->$field, $user);
+            }
+        }
+        foreach ($PRF_FIELDS as $field) {
+            if (isset($user->$field)) {
+                continue;
+            }
+            if (isset($formdata->$field)) {
+                // process templates
+                $user->$field = process_template($formdata->$field, $user);
+            }
+        }
+
+        // delete user
+        if (!empty($user->deleted)) {
+            if (!$allowdeletes) {
+                $usersskipped++;
+                $upt->track('status', $strusernotdeletedoff, 'warning');
+                continue;
+            }
+            if ($existinguser) {
+                if (has_capability('moodle/site:doanything', $systemcontext, $existinguser->id)) {
+                    $upt->track('status', $strusernotdeletedadmin, 'error');
+                    $deleteerrors++;
+                    continue;
+                }
+                if (delete_user($existinguser)) {
+                    $upt->track('status', $struserdeleted);
+                    $deletes++;
+                } else {
+                    $upt->track('status', $strusernotdeletederror, 'error');
+                    $deleteerrors++;
                 }
+            } else {
+                $upt->track('status', $strusernotdeletedmissing, 'error');
+                $deleteerrors++;
+            }
+            continue;
+        }
+        // we do not need the deleted flag anymore
+        unset($user->deleted);
+
+        // renaming requested?
+        if (!empty($user->oldusername) ) {
+            $oldusername = $textlib->strtolower($user->oldusername);
+            if (!$allowrenames) {
+                $usersskipped++;
+                $upt->track('status', $strusernotrenamedoff, 'warning');
+                continue;
+            }
+
+            if ($existinguser) {
+                $upt->track('status', $strusernotrenamedexists, 'error');
+                $renameerrors++;
+                continue;
             }
 
-            // add default values for remaining fields
-            foreach ($fields as $key => $required) {
-                if(isset($user->$key)) {
+            if ($olduser = get_record('user', 'username', addslashes($oldusername), 'mnethostid', $user->mnethostid)) {
+                $upt->track('id', $olduser->id, 'normal', false);
+                if (has_capability('moodle/site:doanything', $systemcontext, $olduser->id)) {
+                    $upt->track('status', $strusernotrenamedadmin, 'error');
+                    $renameerrors++;
                     continue;
                 }
-                if(!isset($formdata->$key) || $formdata->$key==='') { // no default value was submited
-                    // if the field is required, give an error only if we are adding the user or deleting a user with unkown username
-                    if($required && (empty($user->deleted) || $key == 'username')) {
-                        $errors .= get_string('missingfield', 'error', $key) . ' ';
-                    }
+                if (set_field('user', 'username', addslashes($user->username), 'id', $olduser->id)) {
+                    $upt->track('username', '', 'normal', false); // clear previous
+                    $upt->track('username', $oldusername.'-->'.$user->username, 'info');
+                    $upt->track('status', $struserrenamed);
+                    $renames++;
+                } else {
+                    $upt->track('status', $strusernotrenamedexists, 'error');
+                    $renameerrors++;
                     continue;
                 }
-                // process templates
-                $template = $formdata->$key;
-                $templatelen = strlen($template);
-                $value = '';
-                for ($i = 0 ; $i < $templatelen; ++$i) {
-                    if($template[$i] == '%') {
-                        $case = 0; // 1=lowercase, 2=uppercase
-                        $len = 0; // number of characters to keep
-                        $info = null; // data to process
-                        for($j = $i + 1; is_null($info) && $j < $templatelen; ++$j) {
-                            $car = $template[$j];
-                            if ($car >= '0' && $car <= '9') {
-                                $len = $len * 10 + (int)$car;
-                            } else if($car == '-') {
-                                $case = 1;
-                            } else if($car == '+') {
-                                $case = 2;
-                            } else if($car == 'f') { // first name
-                                $info = @$user->firstname;
-                            } else if($car == 'l') { // last name
-                                $info = @$user->lastname;
-                            } else if($car == 'u') { // username
-                                $info = @$user->username;
-                            } else if($car == '%' && $j == $i+1) {
-                                $info = '%';
-                            } else { // invalid character
-                                $info = '';
-                            }
-                        }
-                        if($info==='' || is_null($info)) { // invalid template
+            } else {
+                $upt->track('status', $strusernotrenamedmissing, 'error');
+                $renameerrors++;
+                continue;
+            }
+            $existinguser = $olduser;
+            $existinguser->username = $user->username;
+        }
+
+        // can we process with update or insert?
+        $skip = false;
+        switch ($optype) {
+            case UU_ADDNEW:
+                if ($existinguser) {
+                    $usersskipped++;
+                    $upt->track('status', $strusernotadded, 'warning');
+                    $skip = true;;
+                }
+                break;
+
+            case UU_ADDINC:
+                if ($existinguser) {
+                    //this should not happen!
+                    $upt->track('status', $strusernotaddederror, 'error');
+                    $userserrors++;
+                    continue;
+                }
+                break;
+
+            case UU_ADD_UPDATE:
+                break;
+
+            case UU_UPDATE:
+                if (!$existinguser) {
+                    $usersskipped++;
+                    $upt->track('status', $strusernotupdatednotexists, 'warning');
+                    $skip = true;
+                }
+                break;
+        }
+
+        if ($skip) {
+            continue;
+        }
+
+        if ($existinguser) {
+            $user->id = $existinguser->id;
+
+            if (has_capability('moodle/site:doanything', $systemcontext, $user->id)) {
+                $upt->track('status', $strusernotupdatedadmin, 'error');
+                $userserrors++;
+                continue;
+            }
+
+            if (!$updatetype) {
+                // no updates of existing data at all
+            } else {
+                $existinguser->timemodified = time();
+                //load existing profile data
+                profile_load_data($existinguser);
+
+                $allowed = array();
+                if ($updatetype == 1) {
+                    $allowed = $columns;
+                } else if ($updatetype == 2 or $updatetype == 3) {
+                    $allowed = array_merge($STD_FIELDS, $PRF_FIELDS);
+                }
+                foreach ($allowed as $column) {
+                    if ($column == 'username') {
+                        continue;
+                    }
+                    if ($column == 'password') {
+                        if (!$updatepasswords or $updatetype == 3) {
                             continue;
+                        } else if (!empty($user->password)) {
+                            $upt->track('password', get_string('updated'));
                         }
-                        $i = $j - 1;
-                        // change case
-                        if($case == 1) {
-                            $info = $textlib->strtolower($info);
-                        } else if($case == 2) {
-                            $info = $textlib->strtoupper($info);
+                    }
+                    if ((array_key_exists($column, $existinguser) and array_key_exists($column, $user)) or in_array($column, $PRF_FIELDS)) {
+                        if ($updatetype == 3 and $existinguser->$column !== '') {
+                            //missing == non-empty only
+                            continue;
                         }
-                        if($len) { // truncate data
-                            $info = $textlib->substr($info, 0, $len);
+                        if ($existinguser->$column !== $user->$column) {
+                            if ($column != 'password' and in_array($column, $upt->columns)) {
+                                $upt->track($column, '', 'normal', false); // clear previous
+                                $upt->track($column, $existinguser->$column.'-->'.$user->$column, 'info');
+                            }
+                            $existinguser->$column = $user->$column;
                         }
-                        $value .= $info;
-                    } else {
-                        $value .= $template[$i];
                     }
                 }
 
-                if($key == 'username') {
-                    $value = $textlib->strtolower($value);
-                    if(empty($CFG->extendedusernamechars)) {
-                        $value = eregi_replace('[^(-\.[:alnum:])]', '', $value);
-                    }
-                    @$newusernames[$value]++;
-                    // check for new username duplicates
-                    if($newusernames[$value] > 1) {
-                        if($skipduplicates) {
-                            $errors .= $strduplicateusername . ' (' . stripslashes($value) . '). ';
-                            continue;
-                        } else {
-                            $value .= $newusernames[$value];
-                        }
-                    }
+                // do not update record if new auth plguin does not exist!
+                if (!in_array($existinguser->auth, $availableauths)) {
+                    $upt->track('auth', get_string('userautherror', 'error', $existinguser->auth), 'error');
+                    $upt->track('status', $strusernotupdated, 'error');
+                    $userserrors++;
+                    continue;
+                } else if (!in_array($existinguser->auth, $allowedauths)) {
+                    $upt->track('auth', $struserauthunsupported, 'warning');
                 }
-                $user->$key = $value;
-            }
-            if($errors) {
-                notify(get_string('erroronline', 'error', $linenum). ': ' . $errors);
-                ++$userserrors;
-                continue;
-            }
 
-            // delete user
-            if(@$user->deleted) {
-                $info = ': ' . stripslashes($user->username) . '. ';
-                if($user =& get_record('user', 'username', $user->username, 'mnethostid', $user->mnethostid)) {
-                    $user->timemodified = time();
-                    $user->username     = addslashes($user->email . $user->timemodified);  // Remember it just in case
-                    $user->deleted      = 1;
-                    $user->email        = '';    // Clear this field to free it up
-                    $user->idnumber     = '';    // Clear this field to free it up
-                    if (update_record('user', $user)) {
-                        // not sure if this is needed. unenrol_student($user->id);  // From all courses
-                        delete_records('role_assignments', 'userid', $user->id); // unassign all roles
-                        // remove all context assigned on this user?
-                        echo $struserdeleted . $info . '<br />';
-                        ++$usersdeleted;
-                    } else {
-                        notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotdeletederror . $info);
-                        ++$deleteerrors;
-                    }
+                if (update_record('user', addslashes_recursive($existinguser))) {
+                    $upt->track('status', $struserupdated);
+                    $usersupdated++;
                 } else {
-                    notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotdeletedmissing . $info);
-                    ++$deleteerrors;
+                    $upt->track('status', $strusernotupdated, 'error');
+                    $userserrors++;
+                    continue;
+                }
+                // save custom profile fields data from csv file
+                profile_save_data(addslashes_recursive($existinguser));
+            }
+
+            if ($bulk == 2 or $bulk == 3) {
+                if (!in_array($user->id, $SESSION->bulk_susers)) {
+                    $SESSION->bulk_susers[] = $user->id;
                 }
-                continue;
             }
 
+        } else {
             // save the user to the database
             $user->confirmed = 1;
             $user->timemodified = time();
 
-            // before insert/update, check whether we should be updating an old record instead
-            if ($allowrenames && !empty($user->oldusername) ) {
-                $user->oldusername = $textlib->strtolower($user->oldusername);
-                $info = ': ' . stripslashes($user->oldusername) . '-->' . stripslashes($user->username) . '. ';
-                if ($olduser =& get_record('user', 'username', $user->oldusername, 'mnethostid', $user->mnethostid)) {
-                    if (set_field('user', 'username', $user->username, 'id', $olduser->id)) {
-                        echo $struserrenamed . $info;
-                        $renames++;
-                    } else {
-                        notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotrenamedexists . $info);
-                        $renameerrors++;
-                        continue;
-                    }
-                } else {
-                    notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotrenamedmissing . $info);
-                    $renameerrors++;
+            if (!$createpasswords and empty($user->password)) {
+                $upt->track('password', get_string('missingfield', 'error', 'password'), 'error');
+                $upt->track('status', $strusernotaddederror, 'error');
+                $userserrors++;
+                continue;
+            }
+
+            // do not insert record if new auth plguin does not exist!
+            if (isset($user->auth)) {
+                if (!in_array($user->auth, $availableauths)) {
+                    $upt->track('auth', get_string('userautherror', 'error', $user->auth), 'error');
+                    $upt->track('status', $strusernotaddederror, 'error');
+                    $userserrors++;
                     continue;
+                } else if (!in_array($user->auth, $allowedauths)) {
+                    $upt->track('auth', $struserauthunsupported, 'warning');
                 }
             }
 
-            // save the information
-            if ($olduser =& get_record('user', 'username', $user->username, 'mnethostid', $user->mnethostid)) {
-                $user->id = $olduser->id;
-                $info = ': ' . stripslashes($user->username) .' (ID = ' . $user->id . ')';
-                if ($updateaccounts) {
-                    // Record is being updated
-                    if (update_record('user', $user)) {
-                        echo $struserupdated . $info . '<br />';
-                        $usersupdated++;
-                    } else {
-                        notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotupdated . $info);
-                        $userserrors++;
-                        continue;
-                    }
-                } else {
-                    //Record not added - user is already registered
-                    //In this case, output userid from previous registration
-                    //This can be used to obtain a list of userids for existing users
-                    echo $strusernotadded . $info . '<br />';
-                    $userserrors++;
+            if ($user->id = insert_record('user', addslashes_recursive($user))) {
+                $info = ': ' . $user->username .' (ID = ' . $user->id . ')';
+                $upt->track('status', $struseradded);
+                $upt->track('id', $user->id, 'normal', false);
+                $usersnew++;
+                if ($createpasswords and empty($user->password)) {
+                    // passwords will be created and sent out on cron
+                    set_user_preference('create_password', 1, $user->id);
+                    set_user_preference('auth_forcepasswordchange', 1, $user->id);
+                    $upt->track('password', get_string('new'));
                 }
-            } else { // new user
-                if ($user->id = insert_record('user', $user)) {
-                    $info = ': ' . stripslashes($user->username) .' (ID = ' . $user->id . ')';
-                    echo $struseradded . $info . '<br />';
-                    $usersnew++;
-                    if (empty($user->password) && $createpassword) {
-                        // passwords will be created and sent out on cron
-                        set_user_preference('create_password', 1, $user->id);
-                        set_user_preference('auth_forcepasswordchange', 1, $user->id);
-                    }
-                } else {
-                    // Record not added -- possibly some other error
-                    notify(get_string('erroronline', 'error', $linenum). ': ' . $strusernotaddederror . ': ' . stripslashes($user->username));
-                    $userserrors++;
-                    continue;
+            } else {
+                // Record not added -- possibly some other error
+                $upt->track('status', $strusernotaddederror, 'error');
+                $userserrors++;
+                continue;
+            }
+            // save custom profile fields data
+            profile_save_data($user);
+
+            if ($bulk == 1 or $bulk == 3) {
+                if (!in_array($user->id, $SESSION->bulk_susers)) {
+                    $SESSION->bulk_susers[] = $user->id;
                 }
             }
+        }
+
+        // find course enrolments, groups and roles/types
+        foreach ($columns as $column) {
+            if (!preg_match('/^course\d+$/', $column)) {
+                continue;
+            }
+            $i = substr($column, 6);
 
-            // find course enrolments, groups and roles/types
-            for($ncourses = 1; $addcourse = @$user->{'course' . $ncourses}; ++$ncourses) {
-                // find course
-                if(!$course = @$courses[$addcourse]) {
-                    notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('unknowncourse', 'error', $addcourse));
+            $shortname = $user->{'course'.$i};
+            if (!array_key_exists($shortname, $ccache)) {
+                if (!$course = get_record('course', 'shortname', $shortname, '', '', '', '', 'id, shortname, defaultrole')) {
+                    $upt->track('enrolments', get_string('unknowncourse', 'error', $shortname), 'error');
                     continue;
                 }
-                // find role
-                if ($addrole = @$user->{'role' . $ncourses}) {
-                    $coursecontext =& get_context_instance(CONTEXT_COURSE, $course->id);
-                    if (!$ok = role_assign($addrole, $user->id, 0, $coursecontext->id)) {
-                        echo $strindent . $strcannotassignrole . '<br >';
-                    }
+                $ccache[$shortname] = $course;
+                $ccache[$shortname]->groups = null;
+            }
+            $courseid      = $ccache[$shortname]->id;
+            $coursecontext = get_context_instance(CONTEXT_COURSE, $courseid);
+
+            // find role
+            $rid = false;
+            if (!empty($user->{'role'.$i})) {
+                $addrole = $user->{'role'.$i};
+                if (array_key_exists($addrole, $rolecache)) {
+                    $rid = $rolecache[$addrole]->id;
                 } else {
-                    // if no role, then find "old" enrolment type
-                    switch ($addtype = @$user->{'type' . $ncourses}) {
-                        case 2:   // teacher
-                            $ok = add_teacher($user->id, $course->id, 1);
-                            break;
-                        case 3:   // non-editing teacher
-                            $ok = add_teacher($user->id, $course->id, 0);
-                            break;
-                        case 1:   // student
-                        default:
-                            $ok = enrol_student($user->id, $course->id);
-                            break;
+                    $upt->track('enrolments', get_string('unknownrole', 'error', $addrole), 'error');
+                    continue;
+                }
+
+            } else if (!empty($user->{'type'.$i})) {
+                // if no role, then find "old" enrolment type
+                $addtype = $user->{'type'.$i};
+                if ($addtype < 1 or $addtype > 3) {
+                    $upt->track('enrolments', $strerror.': typeN = 1|2|3', 'error');
+                    continue;
+                } else if ($addtype == 1 and empty($formdata->uulegacy1)) {
+                    if (empty($ccache[$shortname]->defaultrole)) {
+                        $rid = $CFG->defaultcourseroleid;
+                    } else {
+                        $rid = $ccache[$shortname]->defaultrole;
                     }
+                } else {
+                    $rid = $formdata->{'uulegacy'.$addtype};
+                }
+
+            } else {
+                // no role specified, use the default
+                if (empty($ccache[$shortname]->defaultrole)) {
+                    $rid = $CFG->defaultcourseroleid;
+                } else {
+                    $rid = $ccache[$shortname]->defaultrole;
                 }
-                if ($ok) {   // OK
-                    echo $strindent . get_string('enrolledincourse', '', $addcourse) . '<br />';
+            }
+            if ($rid) {
+                $a = new object();
+                $a->course = $shortname;
+                $a->role   = $rolecache[$rid]->name;
+                if (role_assign($rid, $user->id, 0, $coursecontext->id)) {
+                    $upt->track('enrolments', get_string('enrolledincourserole', '', $a));
                 } else {
-                    notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('enrolledincoursenot', '', $addcourse));
+                    $upt->track('enrolments', get_string('enrolledincoursenotrole', '', $a), 'error');
                 }
+            }
 
-                // find group to add to
-                if ($addgroup = @$user->{'group' . $ncourses}) {
-                    if ($gid =& groups_get_group_by_name($course->id, $addgroup)) {
-                        $coursecontext =& get_context_instance(CONTEXT_COURSE, $course->id);
-                        if (count(get_user_roles($coursecontext, $user->id))) {
-                            if (groups_add_member($gid, $user->id)) {
-                                echo $strindent . get_string('addedtogroup','',$addgroup) . '<br />';
-                            } else {
-                                notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('addedtogroupnot','',$addgroup));
+            // find group to add to
+            if (!empty($user->{'group'.$i})) {
+                // make sure user is enrolled into course before adding into groups
+                if (!has_capability('moodle/course:view', $coursecontext, $user->id, false)) {
+                    $upt->track('enrolments', get_string('addedtogroupnotenrolled', '', $gname), 'error');
+                    continue;
+                }
+                //build group cache
+                if (is_null($ccache[$shortname]->groups)) {
+                    $ccache[$shortname]->groups = array();
+                    if ($groups = get_groups($courseid)) {
+                        foreach ($groups as $gid=>$group) {
+                            $ccache[$shortname]->groups[$gid] = new object();
+                            $ccache[$shortname]->groups[$gid]->id   = $gid;
+                            $ccache[$shortname]->groups[$gid]->name = $group->name;
+                            if (!is_numeric($group->name)) { // only non-numeric names are supported!!!
+                            $ccache[$shortname]->groups[$group->name] = new object();
+                            $ccache[$shortname]->groups[$group->name]->id   = $gid;
+                            $ccache[$shortname]->groups[$group->name]->name = $group->name;
                             }
-                        } else {
-                            notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('addedtogroupnotenrolled','',$addgroup));
                         }
-                    } else {
-                        notify(get_string('erroronline', 'error', $linenum). ': ' . get_string('groupunknown','error',$addgroup));
                     }
                 }
+                // group exists?
+                $addgroup = $user->{'group'.$i};
+                if (!array_key_exists($addgroup, $ccache[$shortname]->groups)) {
+                    $upt->track('enrolments', get_string('unknowgroup', 'error', $addgroup), 'error');
+                    continue;
+                }
+                $gid   = $ccache[$shortname]->groups[$addgroup]->id;
+                $gname = $ccache[$shortname]->groups[$addgroup]->name;
+
+                if (groups_add_member($gid, $user->id)) {
+                    $upt->track('enrolments', get_string('addedtogroup', '', $gname));
+                } else {
+                    $upt->track('enrolments', get_string('addedtogroupnot', '', $gname), 'error');
+                    continue;
+                }
             }
         }
-        echo '</p>';
-        notify(get_string('userscreated', 'admin') . ': ' . $usersnew);
-        notify(get_string('usersupdated', 'admin') . ': ' . $usersupdated);
-        notify(get_string('usersdeleted', 'admin') . ': ' . $usersdeleted);
-        notify(get_string('deleteerrors', 'admin') . ': ' . $deleteerrors);
-        if ($allowrenames) {
-            notify(get_string('usersrenamed', 'admin') . ': ' . $renames);
-            notify(get_string('renameerrors', 'admin') . ': ' . $renameerrors);
-        }
-        notify(get_string('errors', 'admin') . ': ' . $userserrors);
     }
-    fclose($fp);
-    user_upload_cleanup($uplid);
-    echo '<hr />';
-    print_continue($return);
+    $upt->flush();
+    $upt->close(); // close table
+
+    $cir->close();
+    $cir->cleanup(true);
+
+    print_box_start('boxwidthnarrow boxaligncenter generalbox', 'uploadresults');
+    echo '<p>';
+    if ($optype != UU_UPDATE) {
+        echo get_string('userscreated', 'admin').': '.$usersnew.'<br />';
+    }
+    if ($optype == UU_UPDATE or $optype == UU_ADD_UPDATE) {
+        echo get_string('usersupdated', 'admin').': '.$usersupdated.'<br />';
+    }
+    if ($allowdeletes) {
+        echo get_string('usersdeleted', 'admin').': '.$deletes.'<br />';
+        echo get_string('deleteerrors', 'admin').': '.$deleteerrors.'<br />';
+    }
+    if ($allowrenames) {
+        echo get_string('usersrenamed', 'admin').': '.$renames.'<br />';
+        echo get_string('renameerrors', 'admin').': '.$renameerrors.'<br />';
+    }
+    if ($usersskipped) {
+        echo get_string('usersskipped', 'admin').': '.$usersskipped.'<br />';
+    }
+    echo get_string('errors', 'admin').': '.$userserrors.'</p>';
+    print_box_end();
+
+    if ($bulk) {
+        print_continue($bulknurl);
+    } else {
+        print_continue($returnurl);
+    }
     admin_externalpage_print_footer();
     die;
 }
@@ -509,107 +673,271 @@ if ($formdata = $mform->is_cancelled()) {
 admin_externalpage_print_header();
 
 /// Print the form
-print_heading_with_help(get_string('uploadusers'), 'uploadusers2');
-
-/// Print csv file preview
-$filename = $CFG->dataroot.'/temp/uploaduser/'.$USER->id.'/'.$uplid;
-if (!file_exists($filename)) {
-    error('Error reading temporary file!', $return); //TODO: localize
-}
-if (!$fp = fopen($filename, "r")) {
-    error('Error reading temporary file!', $return); //TODO: localize
-}
+print_heading_with_help(get_string('uploaduserspreview', 'admin'), 'uploadusers2');
 
-$csv_delimiter = get_upload_csv_delimiter($separator);
-$csv_encode    = get_upload_csv_encode($csv_delimiter);
+$ci = 0;
+$ri = 0;
 
-$header = explode($csv_delimiter, fgets($fp, UP_LINE_MAX_SIZE));
-
-$width = count($header);
-$columncount = 0;
-$rowcount = 0;
-echo '<table class="flexible boxaligncenter generaltable">';
-echo '<tr class="heading r'.$rowcount++.'">';
-foreach ($header as $h) {
-    echo '<th class="header c'.$columncount++.'">'.trim($h).'</th>';
+echo '<table id="uupreview" class="generaltable boxaligncenter" summary="'.get_string('uploaduserspreview', 'admin').'">';
+echo '<tr class="heading r'.$ri++.'">';
+foreach ($columns as $col) {
+    echo '<th class="header c'.$ci++.'" scope="col">'.s($col).'</th>';
 }
 echo '</tr>';
 
-while (!feof($fp) and $rowcount <= $previewrows+1) {
-    $columncount = 0;
-    $fields = explode($csv_delimiter, fgets($fp, UP_LINE_MAX_SIZE));
-    echo '<tr class="r'.$rowcount++.'">';
-    foreach ($fields as $field) {
-        echo '<td class=" c'.$columncount++.'">'.trim(str_replace($csv_encode, $csv_delimiter, $field)).'</td>';;
+$cir->init();
+while ($fields = $cir->next()) {
+    if ($ri > $previewrows) {
+        echo '<tr class="r'.$ri++.'">';
+        foreach ($fields as $field) {
+            echo '<td class="cell c'.$ci++.'">...</td>';;
+        }
+        break;
     }
-    echo '</tr>';
-}
-if ($rowcount > $previewrows+1) {
-    echo '<tr class="r'.$rowcount++.'">';
+    $ci = 0;
+    echo '<tr class="r'.$ri++.'">';
     foreach ($fields as $field) {
-        echo '<td class=" c'.$columncount++.'">...</td>';;
+        echo '<td class="cell c'.$ci++.'">'.s($field).'</td>';;
     }
+    echo '</tr>';
 }
-echo '</table>';
-fclose($fp);
+$cir->close();
 
+echo '</table>';
+echo '<div class="centerpara">'.get_string('uupreprocessedcount', 'admin', $readcount).'</div>';
 $mform->display();
 admin_externalpage_print_footer();
 die;
 
-/////////////////////////
-/// Utility functions ///
-/////////////////////////
+/////////////////////////////////////
+/// Utility functions and classes ///
+/////////////////////////////////////
+
+class uu_progress_tracker {
+    var $_row;
+    var $columns = array('status', 'line', 'id', 'username', 'firstname', 'lastname', 'email', 'password', 'auth', 'enrolments', 'deleted');
+
+    function uu_progress_tracker() {
+    }
+
+    function init() {
+        $ci = 0;
+        echo '<table id="uuresults" class="generaltable boxaligncenter" summary="'.get_string('uploadusersresult', 'admin').'">';
+        echo '<tr class="heading r0">';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('status').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('uucsvline', 'admin').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">ID</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('username').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('firstname').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('lastname').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('email').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('password').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('authentication').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('enrolments').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('delete').'</th>';
+        echo '</tr>';
+        $this->_row = null;
+    }
+
+    function flush() {
+        if (empty($this->_row) or empty($this->_row['line']['normal'])) {
+            $this->_row = array();
+            foreach ($this->columns as $col) {
+                $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>'');
+            }
+            return;
+        }
+        $ci = 0;
+        $ri = 1;
+        echo '<tr class="r'.$ri++.'">';
+        foreach ($this->_row as $field) {
+            foreach ($field as $type=>$content) {
+                if ($field[$type] !== '') {
+                    $field[$type] = '<span class="uu'.$type.'">'.$field[$type].'</span>';
+                } else {
+                    unset($field[$type]);
+                }
+            }
+            echo '<td class="cell c'.$ci++.'">';
+            if (!empty($field)) {
+                echo implode('<br />', $field);
+            } else {
+                echo '&nbsp;';
+            }
+            echo '</td>';
+        }
+        echo '</tr>';
+        foreach ($this->columns as $col) {
+            $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>'');
+        }
+    }
+
+    function track($col, $msg, $level='normal', $merge=true) {
+        if (empty($this->_row)) {
+            $this->flush(); //init arrays
+        }
+        if (!in_array($col, $this->columns)) {
+            debugging('Incorrect column:'.$col);
+            return;
+        }
+        if ($merge) {
+            if ($this->_row[$col][$level] != '') {
+                $this->_row[$col][$level] .='<br />';
+            }
+            $this->_row[$col][$level] .= s($msg);
+        } else {
+            $this->_row[$col][$level] = s($msg);
+        }
+    }
+
+    function close() {
+        echo '</table>';
+    }
+}
 
-function user_upload_cleanup($uplid) {
-    global $USER, $CFG;
-    if (empty($uplid)) {
-        return;
+/**
+ * Validation callback function - verified the column line of csv file.
+ * Converts column names to lowercase too.
+ */
+function validate_user_upload_columns(&$columns) {
+    global $STD_FIELDS, $PRF_FIELDS;
+
+    if (count($columns) < 2) {
+        return get_string('csvfewcolumns', 'error');
     }
-    $filename = $CFG->dataroot.'/temp/uploaduser/'.$USER->id.'/'.$uplid;
-    if (file_exists($filename)) {
-        @unlink($filename);
+
+    // test columns
+    $processed = array();
+    foreach ($columns as $key=>$unused) {
+        $columns[$key] = strtolower($columns[$key]); // no unicode expected here, ignore case
+        $field = $columns[$key];
+        if (!in_array($field, $STD_FIELDS) && !in_array($field, $PRF_FIELDS) &&// if not a standard field and not an enrolment field, then we have an error
+            !preg_match('/^course\d+$/', $field) && !preg_match('/^group\d+$/', $field) &&
+            !preg_match('/^type\d+$/', $field) && !preg_match('/^role\d+$/', $field)) {
+            return get_string('invalidfieldname', 'error', $field);
+        }
+        if (in_array($field, $processed)) {
+            return get_string('csvcolumnduplicates', 'error');
+        }
+        $processed[] = $field;
     }
+    return true;
 }
 
-function get_uf_headers($uplid, $separator) {
-    global $USER, $CFG;
+/**
+ * Increments username - increments trailing number or adds it if not present.
+ * Varifies that the new username does not exist yet
+ * @param string $username
+ * @return incremented username which does not exist yet
+ */
+function increment_username($username, $mnethostid) {
+    if (!preg_match_all('/(.*?)([0-9]+)$/', $username, $matches)) {
+        $username = $username.'2';
+    } else {
+        $username = $matches[1][0].($matches[2][0]+1);
+    }
+
+    if (record_exists('user', 'username', addslashes($username), 'mnethostid', $mnethostid)) {
+        return increment_username($username, $mnethostid);
+    } else {
+        return $username;
+    }
+}
 
-    $filename = $CFG->dataroot.'/temp/uploaduser/'.$USER->id.'/'.$uplid;
-    if (!file_exists($filename)) {
-        return false;
+/**
+ * Check if default field contains templates and apply them.
+ * @param string template - potential tempalte string
+ * @param object user object- we need username, firstname and lastname
+ * @return string field value
+ */
+function process_template($template, $user) {
+    if (strpos($template, '%') === false) {
+        return $template;
     }
-    $fp = fopen($filename, "r");
-    $line = fgets($fp, 2048);
-    fclose($fp);
-    if ($line === false) {
-        return false;
+
+    // very very ugly hack!
+    global $template_globals;
+    $template_globals = new object();
+    $template_globals->username  = isset($user->username)  ? $user->username  : '';
+    $template_globals->firstname = isset($user->firstname) ? $user->firstname : '';
+    $template_globals->lastname  = isset($user->lastname)  ? $user->lastname  : '';
+
+    $result = preg_replace_callback('/(?<!%)%([+-~])?(\d)*([flu])/', 'process_template_callback', $template);
+
+    $template_globals = null;
+
+    if (is_null($result)) {
+        return $template; //error during regex processing??
+    } else {
+        return $result;
     }
+}
 
-    $csv_delimiter = get_upload_csv_delimiter($separator);
-    $headers = explode($csv_delimiter, $line);
-    foreach($headers as $key=>$val) {
-        $headers[$key] = trim($val);
+/**
+ * Internal callback function.
+ */
+function process_template_callback($block) {
+    global $template_globals;
+    $textlib = textlib_get_instance();
+    $repl = $block[0];
+
+    switch ($block[3]) {
+        case 'u': $repl = $template_globals->username; break;
+        case 'f': $repl = $template_globals->firstname; break;
+        case 'l': $repl = $template_globals->lastname; break;
+    }
+    switch ($block[1]) {
+        case '+': $repl = $textlib->strtoupper($repl); break;
+        case '-': $repl = $textlib->strtolower($repl); break;
+        case '~': $repl = $textlib->strtotitle($repl); break;
+    }
+    if (!empty($block[2])) {
+        $repl = $textlib->substr($repl, 0 , $block[2]);
     }
-    return $headers;
+
+    return $repl;
 }
 
-function get_upload_csv_delimiter($separator) {
+/**
+ * Returns list of auth plugins that are enabled and known to work.
+ */
+function uu_allowed_auths() {
     global $CFG;
 
-    switch ($separator) {
-        case 'semicolon' : return ';';
-        case 'colon'     : return ':';
-        case 'tab'       : return "\t";
-        case 'cfg'       : return isset($CFG->CSV_DELIMITER) ? $CFG->CSV_DELIMITER : ',';
-        default          : return ',';
+    // only following plugins are guaranteed to work properly
+    // TODO: add support for more plguins in 2.0
+    $whitelist = array('manual', 'nologin', 'none', 'email');
+    $plugins = get_enabled_auth_plugins();
+    $choices = array();
+    foreach ($plugins as $plugin) {
+        $choices[$plugin] = get_string('auth_'.$plugin.'title', 'auth');
     }
+
+    return $choices;
 }
 
-function get_upload_csv_encode($delimiter) {
-//Note: commas within a field should be encoded as &#44 (for comma separated csv files)
-//Note: semicolon within a field should be encoded as &#59 (for semicolon separated csv files)
+/**
+ * Returns list of non administrator roles
+ */
+function uu_allowed_roles($shortname=false) {
     global $CFG;
-    return '&#' . (isset($CFG->CSV_ENCODE) ? $CFG->CSV_ENCODE : ord($delimiter));
+
+    $roles = get_all_roles();
+    $choices = array();
+    foreach($roles as $role) {
+        if ($shortname) {
+            $choices[$role->id] = $role->shortname;
+        } else {
+            $choices[$role->id] = format_string($role->name);
+        }
+    }
+    // get rid of all admin roles
+    if ($adminroles = get_roles_with_capability('moodle/site:doanything', CAP_ALLOW)) {
+        foreach($adminroles as $adminrole) {
+            unset($choices[$adminrole->id]);
+        }
+    }
+
+    return $choices;
 }
 ?>
index 140a17685b9599b7691dee741fa1c4c785eb8372..4eefd7bee436a2fe7ddeb9432240bcb9b66afb1f 100644 (file)
@@ -5,26 +5,23 @@ class admin_uploaduser_form1 extends moodleform {
     function definition (){
         global $CFG, $USER;
 
-        $this->set_upload_manager(new upload_manager('userfile', false, false, null, false, 0, true, true, false));
-
         $mform =& $this->_form;
 
+        $this->set_upload_manager(new upload_manager('userfile', false, false, null, false, 0, true, true, false));
+
         $mform->addElement('header', 'settingsheader', get_string('upload'));
 
-        $mform->addElement('file', 'userfile', get_string('file'));
+        $mform->addElement('file', 'userfile', get_string('file'), 'size="40"');
         $mform->addRule('userfile', null, 'required');
 
-        $choices = array('comma'=>',', 'semicolon'=>';', 'colon'=>':', 'tab'=>'\\t');
-        if (isset($CFG->CSV_DELIMITER) and !in_array($CFG->CSV_DELIMITER, $choices)) {
-            $choices['cfg'] = $CFG->CSV_DELIMITER; 
-        }
-        $mform->addElement('select', 'separator', get_string('csvseparator', 'admin'), $choices);
+        $choices = csv_import_reader::get_delimiter_list();
+        $mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'admin'), $choices);
         if (array_key_exists('cfg', $choices)) {
-            $mform->setDefault('separator', 'cfg');
+            $mform->setDefault('delimiter_name', 'cfg');
         } else if (get_string('listsep') == ';') {
-            $mform->setDefault('separator', 'semicolon');
+            $mform->setDefault('delimiter_name', 'semicolon');
         } else {
-            $mform->setDefault('separator', 'comma');
+            $mform->setDefault('delimiter_name', 'comma');
         }
 
         $textlib = textlib_get_instance();
@@ -44,35 +41,103 @@ class admin_uploaduser_form2 extends moodleform {
     function definition (){
         global $CFG, $USER;
 
-        $mform =& $this->_form;
+        //no editors here - we need proper empty fields
+        $CFG->htmleditor = null;
+
+        $mform   =& $this->_form;
+        $columns =& $this->_customdata;
 
-        // I am the tamplate user
+        // I am the template user, why should it be the administrator? we have roles now, other ppl may use this script ;-)
         $templateuser = $USER;
 
 // upload settings and file
         $mform->addElement('header', 'settingsheader', get_string('settings'));
 
-        $choices = array(0 => get_string('infilefield', 'auth'), 1 => get_string('createpasswordifneeded', 'auth'));
-        $mform->addElement('select', 'createpassword', get_string('passwordhandling', 'auth'), $choices);
+        $choices = array(UU_ADDNEW    => get_string('uuoptype_addnew', 'admin'),
+                         UU_ADDINC    => get_string('uuoptype_addinc', 'admin'),
+                         UU_ADD_UPDATE => get_string('uuoptype_addupdate', 'admin'),
+                         UU_UPDATE     => get_string('uuoptype_update', 'admin'));
+        $mform->addElement('select', 'uutype', get_string('uuoptype', 'admin'), $choices);
 
-        $mform->addElement('selectyesno', 'updateaccounts', get_string('updateaccounts', 'admin'));
-        $mform->addElement('selectyesno', 'allowrenames', get_string('allowrenames', 'admin'));
+        $choices = array(0 => get_string('infilefield', 'auth'), 1 => get_string('createpasswordifneeded', 'auth'));
+        $mform->addElement('select', 'uupasswordnew', get_string('uupasswordnew', 'admin'), $choices);
+        $mform->disabledIf('uupasswordnew', 'uutype', 'eq', UU_UPDATE);
+
+        $choices = array(0 => get_string('nochanges', 'admin'),
+                         1 => get_string('uuupdatefromfile', 'admin'),
+                         2 => get_string('uuupdateall', 'admin'),
+                         3 => get_string('uuupdatemissing', 'admin'));
+        $mform->addElement('select', 'uuupdatetype', get_string('uuupdatetype', 'admin'), $choices);
+        $mform->disabledIf('uuupdatetype', 'uutype', 'eq', UU_ADDNEW);
+        $mform->disabledIf('uuupdatetype', 'uutype', 'eq', UU_ADDINC);
+
+        $choices = array(0 => get_string('nochanges', 'admin'), 1 => get_string('update'));
+        $mform->addElement('select', 'uupasswordold', get_string('uupasswordold', 'admin'), $choices);
+        $mform->disabledIf('uupasswordold', 'uutype', 'eq', UU_ADDNEW);
+        $mform->disabledIf('uupasswordold', 'uutype', 'eq', UU_ADDINC);
+        $mform->disabledIf('uupasswordold', 'uuupdatetype', 'eq', 0);
+        $mform->disabledIf('uupasswordold', 'uuupdatetype', 'eq', 3);
+
+        $mform->addElement('selectyesno', 'uuallowrenames', get_string('allowrenames', 'admin'));
+        $mform->disabledIf('uuallowrenames', 'uutype', 'eq', UU_ADDNEW);
+        $mform->disabledIf('uuallowrenames', 'uutype', 'eq', UU_ADDINC);
+
+        $mform->addElement('selectyesno', 'uuallowdeletes', get_string('allowdeletes', 'admin'));
+        $mform->disabledIf('uuallowdeletes', 'uutype', 'eq', UU_ADDNEW);
+        $mform->disabledIf('uuallowdeletes', 'uutype', 'eq', UU_ADDINC);
+
+        $choices = array(0 => get_string('no'),
+                         1 => get_string('uubulknew', 'admin'),
+                         2 => get_string('uubulkupdated', 'admin'),
+                         3 => get_string('uubulkall', 'admin'));
+        $mform->addElement('select', 'uubulk', get_string('uubulk', 'admin'), $choices);
+
+// roles selection
+        $showroles = false;
+        foreach ($columns as $column) {
+            if (preg_match('/^type\d+$/', $column)) {
+                $showroles = true;
+                break;
+            }
+        }
+        if ($showroles) {
+            $mform->addElement('header', 'rolesheader', get_string('roles'));
+
+            $choices = uu_allowed_roles(true);
+
+            $choices[0] = get_string('uucoursedefaultrole', 'admin');
+            $mform->addElement('select', 'uulegacy1', get_string('uulegacy1role', 'admin'), $choices);
+            $mform->setDefault('uulegacy1', 0);
+            unset($choices[0]);
+
+            $mform->addElement('select', 'uulegacy2', get_string('uulegacy2role', 'admin'), $choices);
+            if ($editteacherroles = get_roles_with_capability('moodle/legacy:editingteacher', CAP_ALLOW)) {
+                $editteacherrole = array_shift($editteacherroles);   /// Take the first one
+                $mform->setDefault('uulegacy2', $editteacherrole->id);
+                unset($editteacherroles);
+            } else {
+                $mform->setDefault('uulegacy2', $CFG->defaultcourseroleid);
+            }
 
-        $choices = array(0 => get_string('addcounter', 'admin'), 1 => get_string('skipuser', 'admin'));
-        $mform->addElement('select', 'duplicatehandling', get_string('newusernamehandling', 'admin'), $choices);
-        $mform->setDefault('duplicatehandling', 1); // better skip, bc and safer
+            $mform->addElement('select', 'uulegacy3', get_string('uulegacy3role', 'admin'), $choices);
+            if ($teacherroles = get_roles_with_capability('moodle/legacy:teacher', CAP_ALLOW)) {
+                $teacherrole = array_shift($teacherroles);   /// Take the first one
+                $mform->setDefault('uulegacy3', $teacherrole->id);
+                unset($teacherroles);
+            } else {
+                $mform->setDefault('uulegacy3', $CFG->defaultcourseroleid);
+            }
+        }
 
 // default values
         $mform->addElement('header', 'defaultheader', get_string('defaultvalues', 'admin'));
+
         $mform->addElement('text', 'username', get_string('username'), 'size="20"');
+        $mform->addRule('username', get_string('requiredtemplate', 'admin'), 'required', null, 'client');
 
-        // only enabled plugins
-        $aplugins = get_enabled_auth_plugins();
-        $auth_options = array();
-        foreach ($aplugins as $module) {
-            $auth_options[$module] = get_string('auth_'.$module.'title', 'auth');
-        }
-        $mform->addElement('select', 'auth', get_string('chooseauthmethod','auth'), $auth_options);
+        // only enabled and known to work plugins
+        $choices = uu_allowed_auths();
+        $mform->addElement('select', 'auth', get_string('chooseauthmethod','auth'), $choices);
         $mform->setDefault('auth', 'manual'); // manual is a sensible backwards compatible default
         $mform->setHelpButton('auth', array('authchange', get_string('chooseauthmethod','auth')));
         $mform->setAdvanced('auth');
@@ -162,31 +227,99 @@ class admin_uploaduser_form2 extends moodleform {
         $mform->setType('address', PARAM_MULTILANG);
         $mform->setAdvanced('address');
 
-// hidden fields
-        $mform->addElement('hidden', 'uplid');
-        $mform->setType('uplid', PARAM_FILE);
+        /// Next the profile defaults
+        profile_definition($mform);
 
-        $mform->addElement('hidden', 'separator');
-        $mform->setType('separator', PARAM_ALPHA);
+// hidden fields
+        $mform->addElement('hidden', 'iid');
+        $mform->setType('iid', PARAM_INT);
 
         $mform->addElement('hidden', 'previewrows');
-        $mform->setType('previewrows', PARAM_ALPHA);
+        $mform->setType('previewrows', PARAM_INT);
+
+        $mform->addElement('hidden', 'readcount');
+        $mform->setType('readcount', PARAM_INT);
 
         $this->add_action_buttons(true, get_string('uploadusers'));
     }
 
+    /**
+     * Form tweaks that depend on current data.
+     */
     function definition_after_data() {
-        $mform =& $this->_form;
+        $mform   =& $this->_form;
+        $columns =& $this->_customdata;
+
+        foreach ($columns as $column) {
+            if ($mform->elementExists($column)) {
+                $mform->removeElement($column);
+            }
+        }
+    }
+
+    /**
+     * Server side validation.
+     */
+    function validation($data) {
+        $errors  = array();
+        $columns =& $this->_customdata;
+        $optype  = $data['uutype'];
+
+        // detect if password column needed in file
+        if (!in_array('password', $columns)) {
+            switch ($optype) {
+                case UU_UPDATE:
+                    if (!empty($data['uupasswordold'])) {
+                        $errors['uupasswordold'] = get_string('missingfield', 'error', 'password');
+                    }
+                    break;
+
+                case UU_ADD_UPDATE:
+                    if (empty($data['uupasswordnew'])) {
+                        $errors['uupasswordnew'] = get_string('missingfield', 'error', 'password');
+                    }
+                    if  (!empty($data['uupasswordold'])) {
+                        $errors['uupasswordold'] = get_string('missingfield', 'error', 'password');
+                    }
+                    break;
+
+                case UU_ADDNEW:
+                case UU_ADDINC:
+                    if (empty($data['uupasswordnew'])) {
+                        $errors['uupasswordnew'] = get_string('missingfield', 'error', 'password');
+                    }
+                    break;
+             }
+        }
 
-        $separator = $mform->getElementValue('separator');
-        $uplid     = $mform->getElementValue('uplid');
-        
-        if ($headers = get_uf_headers($uplid, $separator)) {
-            foreach ($headers as $header) {
-                if ($mform->elementExists($header)) {
-                    $mform->removeElement($header);
+        // look for other required data
+        if ($optype != UU_UPDATE) {
+            if (!in_array('firstname', $columns)) {
+                $errors['uutype'] = get_string('missingfield', 'error', 'firstname');
+            }
+
+            if (!in_array('lastname', $columns)) {
+                if (isset($errors['uutype'])) {
+                    $errors['uutype'] = '';
+                } else {
+                    $errors['uutype'] = ' ';
                 }
+                $errors['uutype'] .= get_string('missingfield', 'error', 'lastname');
             }
+
+            if (!in_array('email', $columns) and empty($data['email'])) {
+                $errors['email'] = get_string('requiredtemplate', 'admin');
+            }
+
+            if (!in_array('city', $columns) and empty($data['city'])) {
+                $errors['city'] = get_string('required');
+            }
+        }
+
+        if (0 == count($errors)){
+            return true;
+        } else {
+            return $errors;
         }
     }
 }
index 1635f73cf89b5fcc3040346f386cca6b7e649bbd..417e95d24ef01e170927615a3f068f29316215c9 100644 (file)
@@ -1,12 +1,12 @@
 <?php
 $string['accessdenied'] = 'Access denied';
 $string['accounts'] = 'Accounts';
-$string['addcounter'] = 'Append counter';
 $string['adminseesall'] = 'Admins See All';
 $string['adminseesallevents'] = 'Administrators see all events';
 $string['adminseesownevents'] = 'Administrators are just like other users';
 $string['allowcategorythemes'] = 'Allow category themes';
 $string['allowcoursethemes'] = 'Allow course themes';
+$string['allowdeletes'] = 'Allow deletes';
 $string['allowemailaddresses'] = 'Allowed email domains';
 $string['allowobjectembed'] = 'Allow EMBED and OBJECT tags';
 $string['allowrenames'] = 'Allow renames';
@@ -231,7 +231,7 @@ $string['cronerrorclionly'] = 'Sorry, internet access to this page has been disa
 $string['cronerrorpassword'] = 'Sorry, you have not provided a valid password to access this page';
 $string['cronremotepassword'] = 'Cron password for remote access';
 $string['cronwarning'] = 'The <a href=\"cron.php\">cron.php maintenance script</a> has not been run for at least 24 hours.';
-$string['csvseparator'] = 'CSV separator';
+$string['csvdelimiter'] = 'CSV delimiter';
 $string['curlrecommended'] = 'Installing the optional Curl library is highly recommended in order to enable Moodle Networking functionality.';
 $string['customcheck'] = 'Other Checks';
 $string['datarootsecuritywarning'] = 'Your site configuration might not be secure. Please make sure that your dataroot directory ($a) is not directly accessible via web.';
@@ -434,8 +434,8 @@ $string['mymoodle'] = 'My Moodle';
 $string['mymoodleredirect'] = 'Force users to use My Moodle';
 $string['mysql416bypassed'] = 'However, if your site is using iso-8859-1 (latin) languages ONLY, you may continue using your currently installed MySQL 4.1.12 (or higher).';
 $string['mysql416required'] = 'MySQL 4.1.16 is the minimum version required for Moodle 1.6 in order to guarantee that all data can be converted to UTF-8 in the future.';
-$string['newusernamehandling'] = 'New username duplicate handling';
 $string['nobookmarksforuser'] = 'You do not have any bookmarks.';
+$string['nochanges'] = 'No changes';
 $string['nodefaultuserrolelists'] = 'Don\'t return all default role users';
 $string['nolangupdateneeded'] = 'All your language packs are up to date, no update is needed';
 $string['nomissingstrings'] = 'No missing strings';
@@ -536,6 +536,7 @@ $string['rcachettl'] = 'Record cache TTL';
 $string['releasenoteslink'] = 'For information about this version of Moodle, please see the online <a target=\"_blank\" href=\"$a\">Release Notes</a>';
 $string['remotelangnotavailable'] = 'Because Moodle can not connect to download.moodle.org, we are unable to do language pack installation automatically. Please download the appropriate zip file(s) from the list below, copy them to your $a directory and unzip them manually.';
 $string['renameerrors'] = 'Rename errors';
+$string['requiredtemplate'] = 'Required. You may use template syntax here (%%l = lastname, %%f = firstname, %%u = username). See help for details and examples.';
 $string['restrictbydefault'] = 'Restrict modules by default';
 $string['restrictmodulesfor'] = 'Restrict modules for';
 $string['riskconfig'] = 'Users could change site configuration and behaviour';
@@ -576,7 +577,6 @@ $string['sitemaintenancewarning'] = 'Your site is currently in maintenance mode
 $string['sitepolicies'] = 'Site policies';
 $string['sitepolicy'] = 'Site policy URL';
 $string['sitesectionhelp'] = 'If selected, a topic section will be displayed on the site\'s front page.';
-$string['skipuser'] = 'Skip user';
 $string['slasharguments'] = 'Use slash arguments';
 $string['smartpix'] ='Smart pix search';
 $string['smtphosts'] = 'SMTP hosts';
@@ -633,6 +633,8 @@ Please note that this process can take a long time. <br /><br />
 Are you sure you want to upgrade this server to this version?';
 $string['upgradingdata'] = 'Upgrading data';
 $string['upgradinglogs'] = 'Upgrading logs';
+$string['uploaduserspreview'] = 'Upload users preview';
+$string['uploadusersresult'] = 'Upload users results';
 $string['upwards'] = 'upwards';
 $string['usehtmleditor'] = 'Use HTML editor';
 $string['useraccountupdated'] = 'User updated';
@@ -643,10 +645,32 @@ $string['userpolicies'] = 'User policies';
 $string['userrenamed'] = 'User renamed';
 $string['users'] = 'Users';
 $string['userscreated'] = 'Users created';
-$string['usersrenamed'] = 'Users renamed';
 $string['usersdeleted'] = 'Users deleted';
+$string['usersrenamed'] = 'Users renamed';
+$string['usersskipped'] = 'Users skipped';
 $string['usersupdated'] = 'Users updated';
 $string['usetags'] = 'Enable tags functionality';
+$string['uubulk'] = 'Select for bulk operations';
+$string['uubulkall'] = 'All users';
+$string['uubulknew'] = 'New users';
+$string['uubulkupdated'] = 'Updated users';
+$string['uucsvline'] = 'CSV line';
+$string['uucoursedefaultrole'] = 'Default course role';
+$string['uulegacy1role'] = '(Original Student) typeN=1';
+$string['uulegacy2role'] = '(Original Teacher) typeN=2';
+$string['uulegacy3role'] = '(Original Non-editing teacher) typeN=3';
+$string['uuoptype_addinc'] = 'Add all, append counter to usernames if needed';
+$string['uuoptype_addnew'] = 'Add new only, skip existing users';
+$string['uuoptype_addupdate'] = 'Add new and update existing users';
+$string['uuoptype_update'] = 'Update existing users only';
+$string['uuoptype'] = 'Upload type';
+$string['uupasswordnew'] = 'New user password';
+$string['uupasswordold'] = 'Existing user password';
+$string['uupreprocessedcount'] = 'Number of preprocessed records: $a';
+$string['uuupdateall'] = 'Override with file and defaults';
+$string['uuupdatefromfile'] = 'Override with file';
+$string['uuupdatemissing'] = 'Fill in missing from file and defaults';
+$string['uuupdatetype'] = 'Existing user details';
 $string['validateerror'] = 'This value was not valid:';
 $string['xmlstrictheaders'] = 'XML strict headers';
 
index 312cb4bbc410a7c9ab434875386e0eae28aa012d..fda552b9259a69850b89a3f1d770092fbe46773a 100644 (file)
@@ -22,6 +22,10 @@ $string['componentisuptodate'] = 'Component is up to date.';
 $string['confirmsesskeybad'] = 'Sorry, but your session key could not be confirmed to carry out this action.  This security feature prevents against accidental or malicious execution of important functions in your name.  Please make sure you really wanted to execute this function.';
 $string['couldnotassignrole'] = 'A serious but unspecified error occurred while trying to assign a role to you';
 $string['coursegroupunknown'] = 'Course corresponding to group $a not specified';
+$string['csvemptyfile'] = 'The CSV file is empty.';
+$string['csvcolumnduplicates'] = 'Duplicate columns detected.';
+$string['csvfewcolumns'] = 'Not enough columns, please verify the delimiter setting.';
+$string['csvweirdcolumns'] = 'Invalid CSV file format - number of columns is not constant!';
 $string['downloadedfilecheckfailed'] = 'Downloaded file check failed.';
 $string['duplicateusername'] = 'Duplicate username - skiping record';
 $string['errorcleaningdirectory'] = 'Error cleaning directory \"$a\"';
@@ -88,15 +92,25 @@ $string['statscatchupmode'] = 'Statistics is currently in catchup mode. So far $
 $string['unicodeupgradeerror'] = 'Sorry, but your database is not already in Unicode, and this version of Moodle is not able to migrate your database to Unicode.  Please upgrade to Moodle 1.7.x first and perform the Unicode migration from the Admin page.  After that is done you should be able to migrate to Moodle $a';
 $string['unknowncourse'] = 'Unknown course named \"$a\"';
 $string['unknowncourseidnumber'] = 'Unknown Course ID \"$a\"';
+$string['unknowngroup'] = 'Unknown group \"$a\"';
+$string['unknownrole'] = 'Unknown role \"$a\"';
 $string['unknownuseraction'] = 'Sorry, I do not understand this user action.';
-$string['usernotaddederror'] = 'User not added - unknown error';
-$string['usernotaddedregistered'] = 'User not added - already registered';
-$string['usernotdeletederror'] = 'User not deleted - unknown error';
+$string['userautherror'] = 'Unknown auth plugin.';
+$string['userauthunsupported'] = 'Auth plugin not supported here.';
+$string['usernotaddedadmin'] = 'Can not delete admin accounts.';
+$string['usernotaddederror'] = 'User not added - error.';
+$string['usernotaddedregistered'] = 'User not added - already registered.';
+$string['usernotdeletederror'] = 'User not deleted - error.';
 $string['usernotdeletedmissing'] = 'User not deleted - could not find the username.';
+$string['usernotdeletedoff'] = 'User not deleted - deleting not allowed.';
 $string['usernotavailable'] = 'The details of this user are not available to you.';
-$string['usernotrenamedexists'] = 'User not renamed -- the new username is already in use.';
-$string['usernotrenamedmissing'] = 'User not renamed -- could not find the old username.';
-$string['usernotupdatederror'] = 'User not updated - unknown error';
+$string['usernotrenamedadmin'] = 'Can not rename admin accounts.';
+$string['usernotrenamedexists'] = 'User not renamed - the new username is already in use.';
+$string['usernotrenamedmissing'] = 'User not renamed - could not find the old username.';
+$string['usernotrenamedoff'] = 'User not renamed - renaming not allowed.';
+$string['usernotupdatedadmin'] = 'Can not update admin accounts.';
+$string['usernotupdatederror'] = 'User not updated - error.';
+$string['usernotupdatednotexists'] = 'User not updated - does not exist.';
 $string['wrongdestpath'] = 'Wrong destination path.';
 $string['wrongsourcebase'] = 'Wrong source URL base.';
 $string['wrongzipfilename'] = 'Wrong ZIP filename.';
index 4d091abadf00eee10a095d3e178bdb2ffbb090aa..837ab9b798ec44c62d6f2dccd5591b1dd69ecfc3 100755 (executable)
@@ -1,38 +1,38 @@
 <h1>Upload users</h1>
 
-<p>Firstly, note that <strong>it is  usually not necessary to import users in bulk</strong> - to keep your own maintenance work down you should first explore forms of authentication that do not require manual maintenance, such as connecting to existing external databases or letting the users create their own accounts. See the Authentication section in the admin menus.</p>
+<p>Firstly, note that <strong>it is  usually not necessary to import users in bulk</strong> - to keep your own maintenance work down
+you should first explore forms of authentication that do not require manual maintenance, such as connecting to existing external
+databases or letting the users create their own accounts. See the Authentication section in the admin menus.</p>
 <p>If you are sure you want to import multiple user accounts from a text file, then you need to format your text file as follows:</p>
 
 <ul>
   <li>Each line of the file contains one record</li>
-  <li>Each record is a series of data separated by commas</li>
+  <li>Each record is a series of data separated by commas (or other delimiters)</li>
   <li>The first record of the file is special, and contains a list of fieldnames. This defines the format of the rest of the file.
     <blockquote>
       <p><strong>Required fieldnames:</strong> these fields must be included in the first record, and defined for each user</p>
-      <p><code>firstname, lastname</code></p>
-      <p><strong>Optional fieldnames: </strong>all of these are completely optional. If a values is present for the field in the file, then that value is used; else, the default value for that field is used.</p>
-      <p> <code>institution, department, city, country, lang, auth, timezone, idnumber, icq, phone1, phone2, address, url, description, mailformat, maildisplay, htmleditor, autosubscribe, emailstop, deleted</code></p>
-      <p><strong>Enrolment fieldnames (optional): </strong>The  course names are the &quot;shortnames&quot; of the courses - if present then the user will be enrolled in those courses. For groups use group name; for roles use id. Group names must be associated to the corresponding courses, i.e. group1 to course1, etc.</p>
-      <p><code>course1, group1, type1, role1, course2, group2, type2, role2, etc.</code></p>
+      <p><code>firstname, lastname</code> when inserting or <code>username</code> when updating</p>
+      <p><strong>Optional fieldnames:</strong> all of these are completely optional. If a values is present for the field in the file, then that value is used; else, the default value for that field is used.</p>
+      <p><code>institution, department, city, country, lang, auth, ajax, timezone, idnumber, icq, phone1, phone2, address, url, description, mailformat, maildisplay, htmleditor, autosubscribe, emailstop</code></p>
+      <p><strong>Special fieldnames:</strong> used for changing of usernames and deleting of users, see bellow</p>
+      <p><code>deleted, oldusername</code></p>
+      <p><strong>Enrolment fieldnames (optional):</strong> The course names are the &quot;shortnames&quot; of the courses - if present
+         then the user will be enrolled in those courses. &quot;Type&quot; means type of role to be used for associated course enrolment.
+         Value 1 is default course role, 2 is legacy Teacher role and 3 is legacy Non-editing Teacher. You can use role field instead
+         to specify roles directly - use either role short name or id (numeric names of roles are not supported). Users may be also
+         assigned to groups in course (group1 in course1, group2 in course2, etc.). Groups are again identified by its names or ids
+         (numeric names of groups are not supported).</p>
+      <p><code>course1, type1, role1, group1, course2, type2, role2, group2, etc.</code></p>
     </blockquote>
     </li>
-  <li>Commas within the data should be encoded as &amp;#44 - the script will automatically decode these back to commas. </li>
+  <li>Commas within the data should be encoded as &amp;#44 - the script will automatically decode these back to commas.</li>
   <li>For Boolean fields, use 0 for false and 1 for true. </li>
-  <li>Types are used to tell Moodle whether the user is a student or a teacher if a corresponding course exists (e.g. type2 corresponds to course2). 1 = Student, 2 = Editing Teacher, and 3 = Non-editing Teacher. If type is left blank, or if no course is specified, the user is default to student. </li>
-  <li>Note: If a user is already registered in the Moodle user database, this script will return the 
-      userid number (database index) for that user, and will enrol the user as a student in any of the
-      specified courses WITHOUT updating the other specified data.</li>
 </ul>
 <p>Here is an example of a valid import file:</p>
 <p><code>username, password, firstname, lastname, email, lang, idnumber, maildisplay, course1, group1, type1<br />
 jonest, verysecret, Tom, Jones, jonest@someplace.edu, en, 3663737, 1, Intro101, Section 1, 1<br />
 reznort, somesecret, Trent, Reznor, reznort@someplace.edu, en_us, 6736733, 0, Advanced202, Section 3, 3
 </code></p>
-<p>The CSV file may contain full informations for some users and use default values for others (use extra commas to corectly associate data to headers). For example, the following file will use default values for username, city and country for user Trent Reznor:</p>
-<p><code>username, password, firstname, lastname, country, city<br />
-jonest, verysecret, Tom, Jones, RO, Constanta<br />
-, somesecret, Trent, Reznor, ,
-</code></p>
 
 <h2>Templates</h2>
 <p>The default values are processed as templates in which the following codes are allowed:</p>
@@ -44,8 +44,9 @@ jonest, verysecret, Tom, Jones, RO, Constanta<br />
 </ul>
 <p>Between the percent sign (%) and any code letter (l, f or u) the following modifiers are allowed:</p>
 <ul>
-<li>minus sign (-) - the information specified by the code letter will be converted to lowercase</li>
-<li>plus sign (+) - the information specified by the code letter will be converted to uppercase</li>
+<li>(-) minus sign - the information specified by the code letter will be converted to lowercase</li>
+<li>(+) plus sign - the information specified by the code letter will be converted to UPPERCASE</li>
+<li>(~) tilde sign - the information specified by the code letter will be converted to Title Case</li>
 <li>a decimal number - the information specified by the code letter will be truncated to that many characters</li>
 </ul>
 
@@ -58,7 +59,7 @@ jonest, verysecret, Tom, Jones, RO, Constanta<br />
 <li>http://www.example.com/~%u/ = http://www.example.com/~jdoe/ (if the username is jdoe or %-1f%-l)</li>
 </ul>
 <p>Template processing is done only on default values, and not on the values retrieved from the CSV file.</p>
-<p>In order to create corect Moodle usernames, the username is always converted to lowercase. Moreover, if the &quot;Allow extended characters in usernames&quot; option in the Site policies page is off, characters different to letters, digits, dash (-) and dot (.) are removed. 
+<p>In order to create correct Moodle usernames, the username is always converted to lowercase. Moreover, if the &quot;Allow extended characters in usernames&quot; option in the Site policies page is off, characters different to letters, digits, dash (-) and dot (.) are removed. 
 For example if the firstname is John Jr. and the lastname is Doe, the username %-f_%-l will produce john jr._doe when Allow extended characters in usernames is on, and johnjr.doe when off.</p>
 <p>When the &quot;New username duplicate handling&quot; setting is set to Append counter, an auto-increment counter will be append to duplicate usernames produced by the template.
 For example, if the CSV file contains the users named John Doe, Jane Doe and Jenny Doe without explicit usernames, the default username is %-1f%-l and New username duplicate handling is set to Append counter, then the usernames produced will be jdoe, jdoe2 and jdoe3.
@@ -66,14 +67,14 @@ For example, if the CSV file contains the users named John Doe, Jane Doe and Jen
 
 <h2>Updating existing accounts</h2>
 
-<p>By default Moodle assumes that you will be creating new user accounts, and skips records where the username matches an existing account. However, if you set "Update existing accounts" to <b>Yes</b>, the existing user account will be updated. </p>
+<p>By default Moodle assumes that you will be creating new user accounts, and skips records where the username matches an existing account. However, if you allow updating, the existing user account will be updated. </p>
 
 <p>When updating existing accounts you can change usernames as well. Set "Allow renames" to <b>Yes</b> and include in your file a field called <code>oldusername</code>.</p>
 
 <p><b>Warning:</b> any errors updating existing accounts can affect your users badly. Be careful when using the options to update.</p>
 
 <h2>Deleting accounts</h2>
-<p>If the <code>deleted</code> field is present, users with value 1 for it will be deleted. In this case, all the fields may be omitted, except for <code>username</code> (which should be present in the CSV file, or a default value for it should be available).</p>
+<p>If the <code>deleted</code> field is present, users with value 1 for it will be deleted. In this case, all the fields may be omitted, except for <code>username</code>.</p>
 <p>Deleting and uploading accounts could be done with a single CSV file. For example, the following file will add the user Tom Jones and delete the user reznort:</p>
 <p><code>username, firstname, lastname, deleted<br />
 jonest, Tom, Jones, 0<br />
index dc9c29473cc107599e6e9744b23f206af09f6cb3..dc66d531d5dd4b967bbc1347786148b8ef0b604f 100644 (file)
@@ -23,9 +23,9 @@ $string['addcreator'] = 'Add course creator';
 $string['added'] = 'Added $a';
 $string['addedrecip'] = 'Added $a new recipient';
 $string['addedrecips'] = 'Added $a new recipients';
-$string['addedtogroup'] = 'Added to group $a';
-$string['addedtogroupnot'] = 'Not added to group $a';
-$string['addedtogroupnotenrolled'] = 'Not added to group $a, because not enrolled in course';
+$string['addedtogroup'] = 'Added to group \"$a\"';
+$string['addedtogroupnot'] = 'Not added to group \"$a\"';
+$string['addedtogroupnotenrolled'] = 'Not added to group \"$a\", because not enrolled in course';
 $string['addinganew'] = 'Adding a new $a';
 $string['addinganewto'] = 'Adding a new $a->what to $a->to';
 $string['addingdatatoexisting'] = 'Adding data to existing';
@@ -539,7 +539,9 @@ $string['enrolenddate'] = 'End date';
 $string['enrolenddaterror'] = 'Enrolment end date cannot be earlier than start date';
 $string['enrollable'] = 'Course enrollable';
 $string['enrolledincourse'] = 'Enrolled in course \"$a\"';
+$string['enrolledincourserole'] = 'Enrolled in \"$a->course\" as \"$a->role\"';
 $string['enrolledincoursenot'] = 'Not enrolled in course \"$a\"';
+$string['enrolledincoursenotrole'] = 'Error enrolling into \"$a->course\" as \"$a->role\"';
 $string['enrollfirst'] = 'You have to enrol in one of the courses before you can use the site activities';
 $string['enrolme'] = 'Enrol me in this course';
 $string['enrolmentconfirmation'] = 'You are about to enrol yourself as a member of this course.<br />Are you sure you wish to do this?';
diff --git a/lib/csvlib.class.php b/lib/csvlib.class.php
new file mode 100644 (file)
index 0000000..662ceef
--- /dev/null
@@ -0,0 +1,268 @@
+<?php
+/*
+ * General csv import library.
+ * @author Petr Skoda
+ * @version $Id$
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package moodlecore
+ */
+
+/**
+ * Utitily class for importing of CSV files.
+ */
+class csv_import_reader {
+    var $_iid;
+    var $_type;
+    var $_error;
+    var $_columns; //cached columns
+    var $_fp;
+
+    /**
+     * Contructor
+     * @param int $iid import identifier
+     * @param string $type which script imports?
+     */
+    function csv_import_reader($iid, $type) {
+        $this->_iid  = $iid;
+        $this->_type = $type;
+    }
+
+    /**
+     * Parse this content
+     * @param string &$content passed by ref for memory reasons, unset after return
+     * @param string $encoding content encoding
+     * @param string $delimiter_name separator (comma, semicolon, colon, cfg)
+     * @param string $column_validation name of function for columns validation, must have one param $columns
+     * @return false if error, count of data lines if ok; use get_error() to get error string
+     */
+    function load_csv_content(&$content, $encoding, $delimiter_name, $column_validation=null) {
+        global $USER, $CFG;
+
+        $this->close();
+        $this->_error = null;
+
+        $textlib = textlib_get_instance();
+
+        $content = $textlib->convert($content, $encoding, 'utf-8');
+        // remove Unicode BOM from first line
+        $content = $textlib->trim_utf8_bom($content);
+        // Fix mac/dos newlines
+        $content = preg_replace('!\r\n?!', "\n", $content);
+        // is there anyting in file?
+        $columns = strtok($content, "\n");
+        if ($columns === false) {
+            $this->_error = get_string('csvemptyfile', 'error');
+            return false;
+        }
+        $csv_delimiter = csv_import_reader::get_delimiter($delimiter_name);
+        $csv_encode    = csv_import_reader::get_encoded_delimiter($delimiter_name);
+
+        // process header - list of columns
+        $columns   = explode($csv_delimiter, $columns);
+        $col_count = count($columns);
+        if ($col_count === 0) {
+            $this->_error = get_string('csvemptyfile', 'error');
+            return false;
+        }
+
+        foreach ($columns as $key=>$value) {
+            $columns[$key] = str_replace($csv_encode, $csv_delimiter, trim($value));
+        }
+        if ($column_validation) {
+            $result = $column_validation($columns);
+            if ($result !== true) {
+                $this->_error = $result;
+                return false;
+            }
+        }
+        $this->_columns = $columns; // cached columns
+
+        // open file for writing
+        $filename = $CFG->dataroot.'/temp/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
+        $fp = fopen($filename, "w");
+        fwrite($fp, serialize($columns)."\n");
+
+        // again - do we have any data for processing?
+        $line = strtok("\n");
+        $data_count = 0;
+        while ($line !== false) {
+            $line = explode($csv_delimiter, $line);
+            foreach ($line as $key=>$value) {
+                $line[$key] = str_replace($csv_encode, $csv_delimiter, trim($value));
+            }
+            if (count($line) !== $col_count) {
+                // this is critical!!
+                $this->_error = get_string('csvweirdcolumns', 'error');
+                fclose($fp);
+                $this->cleanup();
+                return false;
+            }
+            fwrite($fp, serialize($line)."\n");
+            $data_count++;
+            $line = strtok("\n");
+        }
+
+        fclose($fp);
+        return $data_count;
+    }
+
+    /**
+     * Returns list of columns
+     * @return array
+     */
+    function get_columns() {
+        if (isset($this->_columns)) {
+            return $this->_columns;
+        }
+
+        global $USER, $CFG;
+
+        $filename = $CFG->dataroot.'/temp/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
+        if (!file_exists($filename)) {
+            return false;
+        }
+        $fp = fopen($filename, "r");
+        $line = fgets($fp);
+        fclose($fp);
+        if ($line === false) {
+            return false;
+        }
+        $this->_columns = unserialize($line);
+        return $this->_columns;
+    }
+
+    /**
+     * Init iterator.
+     */
+    function init() {
+        global $CFG, $USER;
+
+        if (!empty($this->_fp)) {
+            $this->close();
+        }
+        $filename = $CFG->dataroot.'/temp/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
+        if (!file_exists($filename)) {
+            return false;
+        }
+        if (!$this->_fp = fopen($filename, "r")) {
+            return false;
+        }
+        //skip header
+        return (fgets($this->_fp) !== false);
+    }
+
+    /**
+     * Get next line
+     * @return array of values
+     */
+    function next() {
+        if (empty($this->_fp) or feof($this->_fp)) {
+            return false;
+        }
+        if ($ser = fgets($this->_fp)) {
+            return unserialize($ser);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Release iteration related resources
+     */
+    function close() {
+        if (!empty($this->_fp)) {
+            fclose($this->_fp);
+            $this->_fp = null;
+        }
+    }
+
+    /**
+     * Get last error
+     * @return string error text of null if none
+     */
+    function get_error() {
+        return $this->_error;
+    }
+
+    /**
+     * Cleanup temporary data
+     * @static if $full=true
+     * @param boolean true menasdo a full cleanup - all sessions for current user, false only the active iid
+     */
+    function cleanup($full=false) {
+        global $USER, $CFG;
+
+        if ($full) {
+            @remove_dir($CFG->dataroot.'/temp/csvimport/'.$this->_type.'/'.$USER->id);
+        } else {
+            @unlink($CFG->dataroot.'/temp/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid);
+        }
+    }
+
+    /**
+     * Get list of cvs delimiters
+     * @static
+     * @return array suitable for selection box
+     */
+    function get_delimiter_list() {
+        $delimiters = array('comma'=>',', 'semicolon'=>';', 'colon'=>':', 'tab'=>'\\t');
+        if (isset($CFG->CSV_DELIMITER) and strlen($CFG->CSV_DELIMITER) === 1 and !in_array($CFG->CSV_DELIMITER, $delimiters)) {
+            $delimiters['cfg'] = $CFG->CSV_DELIMITER;
+        }
+        return $delimiters;
+    }
+
+    /**
+     * Get delimiter character
+     * @static
+     * @param string separator name
+     * @return char delimiter char
+     */
+    function get_delimiter($delimiter_name) {
+        switch ($delimiter_name) {
+            case 'colon':     return ':';
+            case 'semicolon': return ';';
+            case 'tab':       return "\t";
+            case 'cfg':       if (isset($CFG->CSV_DELIMITER)) { return $CFG->CSV_DELIMITER; } // no break; fall back to comma
+            case 'comma':     return ',';
+        }
+    }
+
+    /**
+     * Get encoded delimiter character
+     * @static
+     * @param string separator name
+     * @return char encoded delimiter char
+     */
+    function get_encoded_delimiter($delimiter_name) {
+        global $CFG;
+        if ($delimiter_name == 'cfg' and isset($CFG->CSV_ENCODE)) {
+            return $CFG->CSV_ENCODE;
+        }
+        $delimiter = csv_import_reader::get_delimiter($delimiter_name);
+        return '&#'.ord($delimiter);
+    }
+
+    /**
+     * Create new import id
+     * @static
+     * @param string who imports?
+     * @return int iid
+     */
+    function get_new_iid($type) {
+        global $USER;
+
+        if (!$filename = make_upload_directory('temp/csvimport/'.$type.'/'.$USER->id, false)) {
+            error('Can not create temporary upload directory - verify moodledata permissions!');
+        }
+
+        // use current (non-conflicting) time stamp
+        $iiid = time();
+        while (file_exists($filename.'/'.$iiid)) {
+            $iiid--;
+        }
+
+        return $iiid;
+    }
+}
+?>
\ No newline at end of file
index 65bdfc7b30683ef1b443af598fb2d13ecdef45c0..ced7194d4a6566a2aedea7f0aa86f5b3f5622d00 100644 (file)
@@ -373,6 +373,18 @@ table.flexible .r1 {
     background-color : lime;
 }
 
+#admin-uploaduser .uuinfo {
+    background-color: #8e8;
+}
+
+#admin-uploaduser .uuwarning {
+    background-color: #ee8;
+}
+
+#admin-uploaduser .uuerror {
+    background-color: #e99;
+}
+
 /***
  *** Blocks
  ***/
index 14b03d202ea3f54990afba653b3db324ad10d88b..c066026a79e54e7cf962413bc79d2da6f50ffe21 100644 (file)
@@ -260,6 +260,12 @@ body#admin-index .copyright {
   font-size: 0.75em;
 }
 
+#admin-uploaduser table#uupreview {
+  font-size: 0.8em;
+}
+#admin-uploaduser table#uuresults {
+  font-size: 0.9em;
+}
 
 /***
  *** Blocks
index 2fa8caa6a908af45530836b1ca0b58ab92db57b6..5f1d398fcac421a2c32295cfcc0ba0d3172e6d30 100644 (file)
@@ -1170,6 +1170,16 @@ body#admin-modules table.generaltable td.c0
   display: block;
 }
 
+#admin-uploaduser table#uuresults {
+  margin-bottom: 2em;
+}
+
+#admin-uploaduser table#uupreview,
+#admin-uploaduser table#uuresults td.cell {
+  padding-left: 3px;
+  padding-right: 3px;
+}
+
 /***
  *** Blocks
  ***/