From e4e38544f83b2c66ef06460b13d22f26ef12dec5 Mon Sep 17 00:00:00 2001 From: skodak Date: Mon, 5 Nov 2007 00:43:37 +0000 Subject: [PATCH] MDL-11996 bulk user upload - improvements, bugfixes and cleanup; new csv import library included ;-) --- admin/uploaduser.php | 1274 +++++++++++++++++---------- admin/uploaduser_form.php | 215 ++++- lang/en_utf8/admin.php | 34 +- lang/en_utf8/error.php | 26 +- lang/en_utf8/help/uploadusers2.html | 45 +- lang/en_utf8/moodle.php | 8 +- lib/csvlib.class.php | 268 ++++++ theme/standard/styles_color.css | 12 + theme/standard/styles_fonts.css | 6 + theme/standard/styles_layout.css | 10 + 10 files changed, 1348 insertions(+), 550 deletions(-) create mode 100644 lib/csvlib.class.php diff --git a/admin/uploaduser.php b/admin/uploaduser.php index 246951b318cb6..3be938c3a4d79 100755 --- a/admin/uploaduser.php +++ b/admin/uploaduser.php @@ -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 @@ 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 @@ 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 '

'; - 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 . '
'; - ++$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 . '
'; - $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 . '
'; - $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 . '
'; - $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 . '
'; - } + $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) . '
'; + } + 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) . '
'; - } 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 '

'; - 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 '
'; - print_continue($return); + $upt->flush(); + $upt->close(); // close table + + $cir->close(); + $cir->cleanup(true); + + print_box_start('boxwidthnarrow boxaligncenter generalbox', 'uploadresults'); + echo '

'; + if ($optype != UU_UPDATE) { + echo get_string('userscreated', 'admin').': '.$usersnew.'
'; + } + if ($optype == UU_UPDATE or $optype == UU_ADD_UPDATE) { + echo get_string('usersupdated', 'admin').': '.$usersupdated.'
'; + } + if ($allowdeletes) { + echo get_string('usersdeleted', 'admin').': '.$deletes.'
'; + echo get_string('deleteerrors', 'admin').': '.$deleteerrors.'
'; + } + if ($allowrenames) { + echo get_string('usersrenamed', 'admin').': '.$renames.'
'; + echo get_string('renameerrors', 'admin').': '.$renameerrors.'
'; + } + if ($usersskipped) { + echo get_string('usersskipped', 'admin').': '.$usersskipped.'
'; + } + echo get_string('errors', 'admin').': '.$userserrors.'

'; + print_box_end(); + + if ($bulk) { + print_continue($bulknurl); + } else { + print_continue($returnurl); + } admin_externalpage_print_footer(); die; } @@ -509,107 +673,271 @@ 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 ''; -echo ''; -foreach ($header as $h) { - echo ''; +echo '
'.trim($h).'
'; +echo ''; +foreach ($columns as $col) { + echo ''; } echo ''; -while (!feof($fp) and $rowcount <= $previewrows+1) { - $columncount = 0; - $fields = explode($csv_delimiter, fgets($fp, UP_LINE_MAX_SIZE)); - echo ''; - foreach ($fields as $field) { - echo '';; +$cir->init(); +while ($fields = $cir->next()) { + if ($ri > $previewrows) { + echo ''; + foreach ($fields as $field) { + echo '';; + } + break; } - echo ''; -} -if ($rowcount > $previewrows+1) { - echo ''; + $ci = 0; + echo ''; foreach ($fields as $field) { - echo '';; + echo '';; } + echo ''; } -echo '
'.s($col).'
'.trim(str_replace($csv_encode, $csv_delimiter, $field)).'
...
...'.s($field).'
'; -fclose($fp); +$cir->close(); +echo ''; +echo '
'.get_string('uupreprocessedcount', 'admin', $readcount).'
'; $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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + $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 ''; + foreach ($this->_row as $field) { + foreach ($field as $type=>$content) { + if ($field[$type] !== '') { + $field[$type] = ''.$field[$type].''; + } else { + unset($field[$type]); + } + } + echo ''; + } + echo ''; + 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] .='
'; + } + $this->_row[$col][$level] .= s($msg); + } else { + $this->_row[$col][$level] = s($msg); + } + } + + function close() { + echo '
'.get_string('status').''.get_string('uucsvline', 'admin').'ID'.get_string('username').''.get_string('firstname').''.get_string('lastname').''.get_string('email').''.get_string('password').''.get_string('authentication').''.get_string('enrolments').''.get_string('delete').'
'; + if (!empty($field)) { + echo implode('
', $field); + } else { + echo ' '; + } + echo '
'; + } +} -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('/(?$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 , (for comma separated csv files) -//Note: semicolon within a field should be encoded as ; (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; } ?> diff --git a/admin/uploaduser_form.php b/admin/uploaduser_form.php index 140a17685b959..4eefd7bee436a 100644 --- a/admin/uploaduser_form.php +++ b/admin/uploaduser_form.php @@ -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 @@ function definition (){ $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; } } } diff --git a/lang/en_utf8/admin.php b/lang/en_utf8/admin.php index 1635f73cf89b5..417e95d24ef01 100644 --- a/lang/en_utf8/admin.php +++ b/lang/en_utf8/admin.php @@ -1,12 +1,12 @@ cron.php maintenance script 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['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['releasenoteslink'] = 'For information about this version of Moodle, please see the online Release Notes'; $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['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 @@ 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['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'; diff --git a/lang/en_utf8/error.php b/lang/en_utf8/error.php index 312cb4bbc410a..fda552b9259a6 100644 --- a/lang/en_utf8/error.php +++ b/lang/en_utf8/error.php @@ -22,6 +22,10 @@ $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['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.'; diff --git a/lang/en_utf8/help/uploadusers2.html b/lang/en_utf8/help/uploadusers2.html index 4d091abadf00e..837ab9b798ec4 100755 --- a/lang/en_utf8/help/uploadusers2.html +++ b/lang/en_utf8/help/uploadusers2.html @@ -1,38 +1,38 @@

Upload users

-

Firstly, note that it is usually not necessary to import users in bulk - 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.

+

Firstly, note that it is usually not necessary to import users in bulk - 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.

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:

Here is an example of a valid import file:

username, password, firstname, lastname, email, lang, idnumber, maildisplay, course1, group1, type1
jonest, verysecret, Tom, Jones, jonest@someplace.edu, en, 3663737, 1, Intro101, Section 1, 1
reznort, somesecret, Trent, Reznor, reznort@someplace.edu, en_us, 6736733, 0, Advanced202, Section 3, 3

-

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:

-

username, password, firstname, lastname, country, city
-jonest, verysecret, Tom, Jones, RO, Constanta
-, somesecret, Trent, Reznor, , -

Templates

The default values are processed as templates in which the following codes are allowed:

@@ -44,8 +44,9 @@

Templates

Between the percent sign (%) and any code letter (l, f or u) the following modifiers are allowed:

@@ -58,7 +59,7 @@

Templates

  • http://www.example.com/~%u/ = http://www.example.com/~jdoe/ (if the username is jdoe or %-1f%-l)
  • Template processing is done only on default values, and not on the values retrieved from the CSV file.

    -

    In order to create corect Moodle usernames, the username is always converted to lowercase. Moreover, if the "Allow extended characters in usernames" option in the Site policies page is off, characters different to letters, digits, dash (-) and dot (.) are removed. +

    In order to create correct Moodle usernames, the username is always converted to lowercase. Moreover, if the "Allow extended characters in usernames" 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.

    When the "New username duplicate handling" 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 @@

    Templates

    Updating existing accounts

    -

    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 Yes, the existing user account will be updated.

    +

    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.

    When updating existing accounts you can change usernames as well. Set "Allow renames" to Yes and include in your file a field called oldusername.

    Warning: any errors updating existing accounts can affect your users badly. Be careful when using the options to update.

    Deleting accounts

    -

    If the deleted field is present, users with value 1 for it will be deleted. In this case, all the fields may be omitted, except for username (which should be present in the CSV file, or a default value for it should be available).

    +

    If the deleted field is present, users with value 1 for it will be deleted. In this case, all the fields may be omitted, except for username.

    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:

    username, firstname, lastname, deleted
    jonest, Tom, Jones, 0
    diff --git a/lang/en_utf8/moodle.php b/lang/en_utf8/moodle.php index dc9c29473cc10..dc66d531d5dd4 100644 --- a/lang/en_utf8/moodle.php +++ b/lang/en_utf8/moodle.php @@ -23,9 +23,9 @@ $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['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.
    Are you sure you wish to do this?'; diff --git a/lib/csvlib.class.php b/lib/csvlib.class.php new file mode 100644 index 0000000000000..662ceef41f113 --- /dev/null +++ b/lib/csvlib.class.php @@ -0,0 +1,268 @@ +_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 diff --git a/theme/standard/styles_color.css b/theme/standard/styles_color.css index 65bdfc7b30683..ced7194d4a656 100644 --- a/theme/standard/styles_color.css +++ b/theme/standard/styles_color.css @@ -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 ***/ diff --git a/theme/standard/styles_fonts.css b/theme/standard/styles_fonts.css index 14b03d202ea3f..c066026a79e54 100644 --- a/theme/standard/styles_fonts.css +++ b/theme/standard/styles_fonts.css @@ -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 diff --git a/theme/standard/styles_layout.css b/theme/standard/styles_layout.css index 2fa8caa6a908a..5f1d398fcac42 100644 --- a/theme/standard/styles_layout.css +++ b/theme/standard/styles_layout.css @@ -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 ***/