Commit 4b171026 authored by Nigel McNie's avatar Nigel McNie
Browse files

Import UI: Allow user accounts to be created by uploading a LEAP2A file.



Still some minor things to clean up, but it works reasonably well.

I experimented with a horizontal UI.. I think it would be better if a
graphic designer could make it more obvious that it's a 1-2-3 process.
But everyone I've tried it on has managed to create a user account.

Still todo:
 - progress indication for when a leap2a file is uploaded
 - some minor tweaks to the way some data is imported
 - needs documentation on the wiki and a help icon or two
Signed-off-by: default avatarNigel McNie <nigel@catalyst.net.nz>
parent 75dcf99b
......@@ -35,53 +35,10 @@ define('SECTION_PLUGINNAME', 'admin');
require_once('pieforms/pieform.php');
require_once('institution.php');
// Site-wide account settings
$elements = array();
$elements['username'] = array(
'type' => 'text',
'title' => get_string('username'),
'rules' => array('required' => true),
);
$elements['firstname'] = array(
'type' => 'text',
'title' => get_string('firstname'),
'rules' => array('required' => true),
);
$elements['lastname'] = array(
'type' => 'text',
'title' => get_string('lastname'),
'rules' => array('required' => true),
);
$elements['email'] = array(
'type' => 'text',
'title' => get_string('email'),
'rules' => array('required' => true),
);
$elements['password'] = array(
'type' => 'text',
'title' => get_string('password'),
'rules' => array('required' => true),
);
if ($USER->get('admin')) {
$elements['staff'] = array(
'type' => 'checkbox',
'title' => get_string('sitestaff','admin'),
);
$elements['admin'] = array(
'type' => 'checkbox',
'title' => get_string('siteadmin','admin'),
);
}
$elements['quota'] = array(
'type' => 'bytes',
'title' => get_string('filequota','admin'),
'rules' => array('integer' => true),
'defaultvalue' => get_config_plugin('artefact', 'file', 'defaultquota'),
);
if ($USER->get('admin')) {
$authinstances = auth_get_auth_instances();
} else {
}
else {
$admininstitutions = $USER->get('admininstitutions');
$authinstances = auth_get_auth_instances_for_institutions($admininstitutions);
if (empty($authinstances)) {
......@@ -90,7 +47,9 @@ if ($USER->get('admin')) {
}
}
if (count($authinstances) > 0) {
$authinstancecount = count($authinstances);
if ($authinstancecount) {
$options = array();
$external = false;
......@@ -102,35 +61,79 @@ if (count($authinstances) > 0) {
}
}
}
}
$elements['authinstance'] = array(
$elements = array(
'firstname' => array(
'type' => 'text',
'title' => get_string('firstname'),
'rules' => array('required' => true),
),
'lastname' => array(
'type' => 'text',
'title' => get_string('lastname'),
'rules' => array('required' => true),
),
'email' => array(
'type' => 'text',
'title' => get_string('email'),
'rules' => array('required' => true),
),
'leap2afile' => array(
'type' => 'file',
'title' => '',
),
'username' => array(
'type' => 'text',
'title' => get_string('username'),
'rules' => array('required' => true),
),
'password' => array(
'type' => 'text',
'title' => get_string('password'),
'rules' => array('required' => true),
),
'staff' => array(
'type' => 'checkbox',
'title' => get_string('sitestaff', 'admin'),
'ignore' => !$USER->get('admin'),
),
'admin' => array(
'type' => 'checkbox',
'title' => get_string('siteadmin', 'admin'),
'ignore' => !$USER->get('admin'),
),
'quota' => array(
'type' => 'bytes',
'title' => get_string('filequota','admin'),
'rules' => array('integer' => true, 'minvalue' => 0),
'defaultvalue' => get_config_plugin('artefact', 'file', 'defaultquota'),
),
'authinstance' => array(
'type' => 'select',
'title' => get_string('institution'),
'options' => $options,
'defaultvalue' => 1,
'rules' => array('required' => true),
);
$elements['institutionadmin'] = array(
'ignore' => !$authinstancecount,
),
'institutionadmin' => array(
'type' => 'checkbox',
'title' => get_string('institutionadministrator','admin'),
);
if ($external) {
$elements['remoteusername'] = array(
'type' => 'text',
'title' => get_string('remoteusername', 'admin'),
'description' => get_string('remoteusernamedescription', 'admin', hsc(get_config('sitename'))),
);
}
}
$elements['submit'] = array(
'ignore' => !$authinstancecount,
),
'submit' => array(
'type' => 'submit',
'value' => get_string('createuser','admin'),
'value' => get_string('createuser', 'admin'),
),
);
$form = pieform(array(
'name' => 'adduser',
'renderer' => 'table',
'autofocus' => false,
'template' => 'adduser.php',
'templatedir' => pieform_template_dir('adduser.php'),
'plugintype' => 'core',
'pluginname' => 'admin',
'elements' => $elements,
......@@ -138,7 +141,7 @@ $form = pieform(array(
function adduser_validate(Pieform $form, $values) {
global $USER;
global $USER, $LEAP2A_FILE;
$authobj = AuthFactory::create($values['authinstance']);
......@@ -154,8 +157,8 @@ function adduser_validate(Pieform $form, $values) {
// Don't exceed max user accounts for the institution
if ($institution->isFull()) {
$SESSION->add_error_msg(get_string('institutionmaxusersexceeded', 'admin'));
redirect('/admin/users/add.php');
$form->set_error('authinstance', get_string('institutionmaxusersexceeded', 'admin'));
return;
}
$username = $values['username'];
......@@ -166,58 +169,117 @@ function adduser_validate(Pieform $form, $values) {
if (method_exists($authobj, 'is_username_valid') && !$authobj->is_username_valid($username)) {
$form->set_error('username', get_string('usernameinvalidform', 'auth.internal'));
return;
}
if (!$form->get_error('username') && record_exists_select('usr', 'LOWER(username) = ?', strtolower($username))) {
$form->set_error('username', get_string('usernamealreadytaken', 'auth.internal'));
}
if (method_exists($authobj, 'is_password_valid') && !$authobj->is_password_valid($password)) {
$form->set_error('password', get_string('passwordinvalidform', 'auth.' . $authobj->type));
}
if (isset($_POST['createmethod']) && $_POST['createmethod'] == 'leap2a') {
$form->set_error('firstname', null);
$form->set_error('lastname', null);
$form->set_error('email', null);
if (!$values['leap2afile']) {
$form->set_error('leap2afile', $form->i18n('rule', 'required', 'required'));
return;
}
$date = time();
$nicedate = date('Y/m/d h:i:s', $date);
$niceuser = preg_replace('/[^a-zA-Z0-9_-]/', '-', $values['username']);
$uploaddir = get_config('dataroot') . 'import/' . $niceuser . '-' . $date . '/';
$filename = $uploaddir . $values['leap2afile']['name'];
check_dir_exists($uploaddir);
if (!move_uploaded_file($values['leap2afile']['tmp_name'], $filename)) {
$form->set_error('leap2afile', get_string('failedtoobtainuploadedleapfile', 'admin'));
}
if ($values['leap2afile']['type'] == 'application/zip') {
// Unzip the file
$command = sprintf('%s %s %s %s',
escapeshellcmd(get_config('pathtounzip')),
escapeshellarg($filename),
get_config('unzipdirarg'),
escapeshellarg($uploaddir)
);
$output = array();
exec($command, $output, $returnvar);
if ($returnvar != 0) {
log_debug("unzip command failed with return value $returnvar");
// Let's make it obvious if the cause is obvious :)
if ($returnvar == 127) {
log_debug("This means that 'unzip' isn't installed, or the config var \$cfg->pathtounzip is not"
. " pointing at unzip (see Mahara's file lib/config-defaults.php)");
}
$form->set_error('leap2afile', get_string('failedtounzipleap2afile', 'admin'));
return;
}
$filename = $uploaddir . 'leap2a.xml';
if (!is_file($filename)) {
$form->set_error('leap2afile', get_string('noleap2axmlfiledetected', 'admin'));
return;
}
}
else if ($values['leap2afile']['type'] != 'text/xml') {
$form->set_error('leap2afile', get_string('fileisnotaziporxmlfile', 'admin'));
}
$LEAP2A_FILE = $filename;
}
else {
if (!$form->get_error('firstname') && !preg_match('/\S/', $firstname)) {
$form->set_error('firstname', $form->i18n('required'));
$form->set_error('firstname', $form->i18n('rule', 'required', 'required'));
}
if (!$form->get_error('lastname') && !preg_match('/\S/', $lastname)) {
$form->set_error('lastname', $form->i18n('required'));
$form->set_error('lastname', $form->i18n('rule', 'required', 'required'));
}
if (!$form->get_error('email')) {
require_once('phpmailer/class.phpmailer.php');
if (!$form->get_error('email') && !PHPMailer::ValidateAddress($email)) {
$form->set_error('email', get_string('invalidemailaddress', 'artefact.internal'));
}
if (record_exists('usr', 'email', $email)
|| record_exists('artefact_internal_profile_email', 'email', $email)) {
$form->set_error('email', get_string('emailalreadytaken', 'auth.internal'));
}
if (method_exists($authobj, 'is_password_valid') && !$authobj->is_password_valid($password)) {
$form->set_error('password', get_string('passwordinvalidform', 'auth.' . $authobj->type));
return;
}
}
}
function adduser_submit(Pieform $form, $values) {
global $USER, $SESSION, $LEAP2A_FILE;
db_begin();
$user = new StdClass;
$user->authinstance = $values['authinstance'];
$user->username = $values['username'];
$user->firstname = $values['firstname'];
$user->lastname = $values['lastname'];
$user->email = $values['email'];
$user->password = $values['password'];
$user->quota = $values['quota'];
$user->passwordchange = 1;
global $USER, $SESSION;
ini_set('max_execution_time', 180);
// Create user
$user = (object)array(
'authinstance' => $values['authinstance'],
'username' => $values['username'],
'firstname' => ($values['firstname']) ? $values['firstname'] : 'Imported',
'lastname' => ($values['lastname']) ? $values['lastname'] : 'User',
'password' => $values['password'],
'quota' => $values['quota'],
'passwordchange' => 1,
);
if ($USER->get('admin')) { // Not editable by institutional admins
$user->staff = (int) ($values['staff'] == 'on');
$user->admin = (int) ($values['admin'] == 'on');
}
$authinstance = get_record('auth_instance', 'id', $values['authinstance']);
if(!isset($values['remoteusername'])){
if (!isset($values['remoteusername'])){
$values['remoteusername'] = null;
}
db_begin();
$user->id = create_user($user, array(), $authinstance->institution, $authinstance, $values['remoteusername']);
if (isset($user->admin) && $user->admin) {
......@@ -229,8 +291,44 @@ function adduser_submit(Pieform $form, $values) {
set_field('usr_institution', 'admin', 1, 'usr', $user->id, 'institution', $authinstance->institution);
}
if (isset($values['leap2afile'])) {
// And we're good to go
$filename = substr($LEAP2A_FILE, strlen(get_config('dataroot')));
$logfile = dirname($LEAP2A_FILE) . '/import.log';
require_once(get_config('docroot') . 'import/lib.php');
safe_require('import', 'leap');
$importer = PluginImport::create_importer(null, (object)array(
'token' => '',
//'host' => '',
'usr' => $user->id,
'queue' => (int)!(PluginImport::import_immediately_allowed()), // import allowed straight away? Then don't queue
'ready' => 0, // maybe 1?
'expirytime' => db_format_timestamp(time()+(60*60*24)),
'format' => 'leap',
'data' => array('filename' => $filename),
'loglevel' => PluginImportLeap::LOG_LEVEL_VERBOSE,
'logtargets' => LOG_TARGET_FILE,
'logfile' => $logfile,
'profile' => true,
));
try {
$importer->process();
log_info("Imported user account $user->id from leap2a file, see $logfile for a full log");
}
catch (ImportException $e) {
log_info("LEAP2A import failed: " . $e->getMessage());
die_info(get_string('leap2aimportfailed', 'admin'));
}
// Reload the user details, as various fields are changed by the
// importer when importing (e.g. firstname/lastname)
$user = get_record('usr', 'id', $user->id);
}
db_commit();
if (!empty($user->email)) {
try {
email_user($user, $USER, get_string('accountcreated', 'mahara', get_config('sitename')),
get_string('accountcreatedchangepasswordtext', 'mahara', $user->firstname, get_config('sitename'), $user->username, $user->password, get_config('wwwroot'), get_config('sitename')),
......@@ -240,11 +338,13 @@ function adduser_submit(Pieform $form, $values) {
catch (EmailException $e) {
$SESSION->add_error_msg(get_string('newuseremailnotsent', 'admin'));
}
}
redirect('/admin/users/edit.php?id='.$user->id);
$SESSION->add_ok_msg(get_string('newusercreated', 'admin'));
redirect('/admin/users/edit.php?id=' . $user->id);
}
$smarty = smarty();
$smarty = smarty(array('adminadduser'));
$smarty->assign('form', $form);
$smarty->assign('PAGEHEADING', hsc(TITLE));
$smarty->display('admin/users/add.tpl');
......
var current;
function move_step(i) {
var selected = getFirstParentByTagAndClassName(i, 'td', 'step');
if (selected != current) {
addElementClass(selected, 'current');
if (current) {
removeElementClass(current, 'current');
}
current = selected;
}
}
addLoadEvent(function() {
var step1_spans = getElementsByTagAndClassName('span', 'requiredmarker', 'step1');
var step1_inputs = getElementsByTagAndClassName('input', 'required', 'step1');
var leap2a_input = $('adduser_leap2afile');
var leap2a_label = $('leap2a_label');
leap2a_input.disabled = true;
addElementClass('step1', 'current');
/**
* state = true if the user selects the leap2a radio button, else false
*/
function set_step1_requiredfields(state) {
if (state) {
forEach(step1_spans, function(span) {
setStyle(span, {'visibility': 'hidden'});
});
forEach(step1_inputs, function(input) {
removeElementClass(input, 'required');
});
}
else {
forEach(step1_spans, function(span) {
setStyle(span, {'visibility': 'visible'});
});
forEach(step1_inputs, function(input) {
addElementClass(input, 'required');
});
}
$('adduser_firstname').disabled = state;
$('adduser_lastname').disabled = state;
$('adduser_email').disabled = state;
$('adduser_leap2afile').disabled = !state;
}
forEach(getElementsByTagAndClassName('input', 'ic', 'adduser'), function(i) {
connect(i, 'onclick', function(e) {
set_step1_requiredfields(i.id == 'uploadleap');
});
if (i.checked) {
set_step1_requiredfields(i.id == 'uploadleap');
}
});
current = getFirstElementByTagAndClassName('td', 'step1', 'adduser');
forEach(getElementsByTagAndClassName('input', null, 'adduser'), function(i) {
connect(i, 'onfocus', partial(move_step, i));
connect(i, 'onclick', partial(move_step, i));
});
});
......@@ -384,9 +384,25 @@ $string['deleteusernote'] = 'Please note that this operation <strong>cannot be u
// Add User
$string['adduser'] = 'Add User';
$string['adduserdescription'] = 'Create a new user';
$string['adduserpagedescription'] = '<p>Here you may add a new user to the system. Once added, they will be sent an e-mail informing them of their new account, including their username and password. They will be asked to change their password upon first log in.</p>';
$string['basicinformationforthisuser'] = 'Basic information for this user.';
$string['clickthebuttontocreatetheuser'] = 'Click the button to create the user.';
$string['createnewuserfromscratch'] = 'Create new user from scratch';
$string['createuser'] = 'Create User';
$string['failedtoobtainuploadedleapfile'] = 'Failed to obtain the uploaded LEAP2A file';
$string['failedtounzipleap2afile'] = 'Failed to unzip the LEAP2A file. Check the error log for more information';
$string['fileisnotaziporxmlfile'] = 'This file has not been detected to be a zipfile or XML file';
$string['howdoyouwanttocreatethisuser'] = 'How do you want to create this user?';
$string['leap2aimportfailed'] = '<p><strong>Sorry - Importing the LEAP2A file failed.</strong></p><p>This could be because you did not select a valid LEAP2A file to upload. Alternatively, there may be a bug in Mahara causing your file to fail, even though it is valid.</p><p>Please <a href="add.php">go back and try again</a>, and if the problem persists, you may want to post to the <a href="http://mahara.org/forums/">Mahara Forums</a> to ask for help. Be prepared to be asked for a copy of your file!</p>';
$string['newuseremailnotsent'] = 'Failed to send welcome email to new user.';
$string['newusercreated'] = 'New user account created successfully';
$string['noleap2axmlfiledetected'] = 'No leap2a.xml file detected - please check your export file again';
$string['Or...'] = 'Or...';
$string['userwillreceiveemailandhastochangepassword'] = 'They will receive an e-mail informing them of their new account details. On first log in, they will be forced to change their password.';
$string['uploadleap2afile'] = 'Upload LEAP2A File';
$string['usercreationmethod'] = '1 - User Creation Method';
$string['basicdetails'] = '2 - Basic Details';
$string['create'] = '3 - Create';
// Login as
$string['loginasuser'] = 'Login as %s';
......@@ -478,6 +494,7 @@ $string['institutionusersupdated_inviteUser'] = 'Invitations sent';
$string['maxuseraccounts'] = 'Maximum User Accounts Allowed';
$string['maxuseraccountsdescription'] = 'The maximum number of user accounts that can be associated with the institution. If there is no limit, this field should be left blank.';
$string['institutionmaxusersexceeded'] = 'This institution is full, you will have to increase the number of allowed users in this institution before this user can be added.';
$string['institutionuserserrortoomanyusers'] = 'The users were not added. The number of members cannot exceed the maximum allowed for the institution. You can add fewer users, remove some users from the institution, or ask the site administrator to increase the maximum number of users.';
$string['institutionuserserrortoomanyinvites'] = 'Your invitations were not sent. The number of existing members plus the number of outstanding invitations cannot exceed the institution\'s maximum number of users. You can invite fewer users, remove some users from the institution, or ask the site administrator to increase the maximum number of users.';
......
<?php
echo $form_tag;
?>
<table id="adduser-t">
<thead>
<tr>
<th class="step1"><?php echo get_string('usercreationmethod', 'admin'); ?></th>
<th></th>
<th class="step2"><?php echo get_string('basicdetails', 'admin'); ?></th>
<th></th>
<th class="step3"><?php echo get_string('create', 'admin'); ?></th>
</tr>
</thead>
<tbody>
<tr id="tr-fc">
<td><?php echo get_string('howdoyouwanttocreatethisuser', 'admin'); ?></td>
<td></td>
<td><?php echo get_string('basicinformationforthisuser', 'admin'); ?></td>
<td></td>
<td><?php echo get_string('clickthebuttontocreatetheuser', 'admin'); ?></td>
</tr>
<tr>
<td class="step step1" id="step1">
<div class="choice"><input type="radio" name="createmethod" class="ic"<?php if (!isset($_POST['createmethod']) || $_POST['createmethod'] == 'scratch') { ?> checked="checked"<?php } ?> id="createfromscratch" value="scratch"> <label for="createfromscratch"><?php echo get_string('createnewuserfromscratch', 'admin'); ?></label></div>
<table>
<?php foreach (array('firstname', 'lastname', 'email') as $field) { ?>
<tr>
<th><?php echo $elements[$field]['labelhtml']; ?></th>
<td><?php echo $elements[$field]['html']; ?></td>
</tr>
<?php if ($elements[$field]['error']) { ?>
<tr>
<td class="errmsg" colspan="2"><?php echo $elements[$field]['error']; ?></td>
</tr>
<?php } ?>
<?php } ?>
</table>
<div id="or"><?php echo get_string('Or...', 'admin'); ?></div>
<div class="choice"><input type="radio" name="createmethod" class="ic"<?php if (isset($_POST['createmethod']) && $_POST['createmethod'] == 'leap2a') { ?> checked="checked"<?php } ?> id="uploadleap" value="leap2a"> <label for="uploadleap"><?php echo get_string('uploadleap2afile', 'admin'); ?></label> ?</div>
<?php echo $elements['leap2afile']['html']; ?>
<?php if ($elements['leap2afile']['error']) { ?>
<div class="errmsg"><?php echo $elements['leap2afile']['error']; ?></div>
<?php } ?>
</td>
<td class="filler">&raquo;</td>
<td class="step step2">
<table>
<?php foreach(array('username', 'password', 'staff', 'admin', 'quota', 'authinstance', 'institutionadmin') as $field) { ?>
<tr>
<th><?php echo $elements[$field]['labelhtml']; ?></th>
<td><?php echo $elements[$field]['html'];
if (isset($elements[$field]['error'])) { echo '<div class="errmsg">' . $elements[$field]['error'] . '</div>'; }
?></td>
</tr>
<?php } ?>
</table>
</td>
<td class="filler">&raquo;</td>
<td class="step step3">
<?php echo $elements['submit']['html']; ?>
<div id="step3info"><?php echo get_string('userwillreceiveemailandhastochangepassword', 'admin'); ?></div>
</td>
</tr>
</tbody>
</table>
<?php
echo $hidden_elements;
echo '</form>';
?>
......@@ -269,6 +269,69 @@ table.tablerenderer th.desc {
/* MANAGE USERS > ADD USER */
#adduser-t {
margin: 0 auto;
min-width: 800px;
max-width: 1000px;
width: 950px;
}
#adduser-t td {
/*padding-right: 3em;*/
}
#adduser-t .current {
background-color: #f0f8ff;
}
#adduser thead th {
text-align: left;
font-size: 1.3em;
border-bottom: none !important;
}
#adduser #tr-fc td {
padding-bottom: 1em;
}
#adduser label {
font-weight: bold;
font-size: 1em;
}
#adduser table table label {
font-size: .9em;
}
#adduser div.choice {
font-size: 1.1em;
padding-bottom: .5em;
}
#adduser #or {
margin-top: .5em;
}
#adduser #uploadleap {
margin-top: 1em;
}
#adduser td.step3 {
text-align: center;