= 3) {
unset($form_state['storage']);
}
$step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
$form_state['storage']['step'] = $step;
switch ($step) {
case 1:
$form['instructions'] = array(
'#type' => 'fieldset',
'#title' => t('User import help'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['instructions']['help'] = array(
'#markup' => theme('uif_form_help'),
);
$file_size_msg = t('Your PHP settings limit the maximum file size per upload to %size. Depending on your server environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site\'s settings.php file, or in the .htaccess file in your Drupal root directory.', array('%size' => format_size(file_upload_max_size())));
$form['user_upload'] = array(
'#type' => 'file',
'#title' => t('Import file'),
'#size' => 40,
'#description' => t('Select the CSV file to be imported.') . '
' . $file_size_msg,
);
$preview_count = drupal_map_assoc(array(0, 1, 10, 100, 1000, 10000, 9999999));
$preview_count[0] = t('None - just do it');
$preview_count[9999999] = t('Preview all');
$form['preview_count'] = array(
'#type' => 'select',
'#title' => t('Users to preview'),
'#default_value' => 10,
'#options' => $preview_count,
'#description' => t('Number of users to preview before importing. Note: If you run out of memory set this lower or increase your memory.')
);
$form['notify'] = array(
'#type' => 'checkbox',
'#title' => t('Notify new users of account'),
'#description' => t('If checked, each newly created user will receive the Welcome, new user created by administrator email using the template on the user settings page. This is the same email sent for admin-created accounts.', array('@url1' => url('admin/user/settings'), '@url2' => url('admin/user/user/create'))),
);
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next')
);
// Set form parameters so we can accept file uploads.
$form['#attributes'] = array('enctype' => 'multipart/form-data');
break;
case 2:
$form['instructions'] = array(
'#markup' => t('Preview these records and when ready to import click Import users.'),
'#prefix' => '
',
'#suffix' => '
',
);
$form['user_preview'] = array(
'#markup' => $form_state['storage']['user_preview'],
'#prefix' => '',
'#suffix' => '
',
);
$form['back'] = array(
'#type' => 'submit',
'#value' => t('Back'),
'#submit' => array('uif_import_form_back'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import users'),
);
break;
}
return $form;
}
/**
* Validate the import data.
*/
function uif_import_form_validate($form, &$form_state) {
$step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
switch ($step) {
case 1:
// Validate the upload file
$validators = array(
'file_validate_extensions' => array('csv'),
'file_validate_size' => array(file_upload_max_size()),
);
if ($user_file = file_save_upload('user_upload', $validators)) {
$errors = uif_validate_user_file($user_file->uri, $data, $form_state);
if (!empty($errors)) {
form_set_error('user_upload', '- ' . implode('
- ', $errors) . '
');
return;
}
}
else {
form_set_error('user_upload', t('Cannot save the import file to temporary storage. Please try again.'));
return;
}
// Save the validated data to avoid reparsing
$form_state['storage']['data'] = $data;
break;
}
}
/**
* Form submission handler.
*/
function uif_import_form_submit($form, &$form_state) {
$step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
if (1 == $step) {
$form_state['rebuild'] = TRUE;
$form_state['storage']['notify'] = isset($form_state['values']['notify']) ? $form_state['values']['notify'] : FALSE;
$preview_count = $form_state['values']['preview_count'];
if ($preview_count) {
$form_state['storage']['preview_count'] = $preview_count;
$form_state['storage']['user_preview'] = theme('uif_preview_users', array('data' => $form_state['storage']['data'], 'limit' => $preview_count));
}
else {
$step = 2;
}
}
if (2 == $step) {
$form_state['rebuild'] = TRUE;
uif_batch_import_users($form_state);
}
$form_state['storage']['step'] = $step + 1;
}
/**
* Read the user import file and validate on the way.
*
* @param $uri
* filepath to the user import file
* @param $data
* returns with array of users
* @return
* FALSE if no errors found
* array of error strings if error found
*/
function uif_validate_user_file($uri, &$data, $form_state) {
$data = array();
$data['user'] = array();
$line = 0;
// Without this fgetcsv() fails for Mac-created files
ini_set('auto_detect_line_endings', TRUE);
if ($fp = fopen($uri, 'r')) {
// Read the header and allow alterations
$header_row = fgetcsv($fp);
drupal_alter('uif_header', $header_row);
$header_row = uif_normalize_header(array_map('trim', $header_row));
$line++;
$errors = module_invoke_all('uif_validate_header', $header_row, $form_state);
uif_add_line_number($errors, $line);
if (!empty($errors)) {
return $errors;
}
$data['header'] = $header_row;
// Read the data
$errors = array();
while (!feof($fp) && (count($errors) < 20)) {
// Read a row and allow alterations
$row = fgetcsv($fp);
drupal_alter('uif_row', $row, $header_row);
$line++;
if (uif_row_has_data($row)) {
$user_row = uif_clean_and_key_row($header_row, $row, $line);
$uid = db_query_range('SELECT uid FROM {users} WHERE mail = :mail', 0, 1, array(':mail' => $user_row['email']))->fetchField();
$more_errors = module_invoke_all('uif_validate_user', $user_row, $uid, $header_row, $form_state);
uif_add_line_number($more_errors, $line);
$errors = array_merge($errors, $more_errors);
$data['user'][] = $user_row;
}
}
// Any errors?
if (!empty($errors)) {
return $errors;
}
}
else {
return t('Cannot open that import file.');
}
// Final validation opportunity after header and all users validated individually.
$errors = module_invoke_all('uif_validate_all_users', $data['user'], $form_state);
if (!empty($errors)) {
return $errors;
}
}
/**
* Trim all elements of $row, and pad $row out to the number of columns in the
* $header. Then replace keys in $row with $header values.
*/
function uif_clean_and_key_row($header, $row, $line) {
$row = array_map('trim', $row);
$raw_row = $row;
$row = array_map('uif_clean_value', $row);
for ($i = 0; $i < count($row); $i++) {
if ($raw_row[$i] !== $row[$i]) {
$vars = array('@line' => $line, '@column' => $header[$i]);
drupal_set_message(t('Warning on row @line: Non UTF-8 characters were removed from @column column.', $vars), 'warning');
}
}
if (count($row) < count($header)) {
$row = array_merge($row, array_fill(count($row), count($header) - count($row), ''));
drupal_set_message(t('Warning on row @line: Empty values added for missing data.', array('@line' => $line)), 'warning');
}
elseif (count($row) > count($header)) {
array_splice($row, count($header));
drupal_set_message(t('Warning on row @line: Data values beyond header were truncated.', array('@line' => $line)), 'warning');
}
$row = array_combine($header, $row);
return $row;
}
/**
* Check that input is UTF-8.
*/
function uif_clean_value($value) {
if (!drupal_validate_utf8($value)) {
// Remove all chars except LF, CR, and basic ascii
return preg_replace('/[^\x0A\x0D\x20-\x7E]/', '', $value);
}
return $value;
}
/**
* Is there data in the row?
*/
function uif_row_has_data($row) {
if (isset($row) && is_array($row)) {
foreach ($row as $value) {
$value = trim($value);
if (!empty($value)) {
return TRUE;
}
}
}
return FALSE;
}
/**
* Normalize the header columns.
*/
function uif_normalize_header($header) {
$normal_header = array();
foreach ($header as $column) {
$normal_header[] = strtolower($column);
}
return $normal_header;
}
/**
* Implementation of hook_uif_validate_header().
*/
function uif_uif_validate_header($header) {
if (!in_array('email', $header)) {
return t('I can find no email column in the import file.');
}
}
/**
* Implementation of hook_uif_validate_user().
*/
function uif_uif_validate_user($user_data, $uid, $header = NULL) {
if (!valid_email_address($user_data['email'])) {
return t('Missing or invalid email address !mail.', array('!mail' => $user_data['email']));
}
}
/**
* Prepend the line number on the error.
*/
function uif_add_line_number(&$errors, $line) {
foreach ($errors as &$error) {
$error = t('Error on row !line:', array('!line' => $line)) . $error;
}
}
/**
* Return user to starting point on template multi-form.
*/
function uif_import_form_back($form, &$form_state) {
$form_state['storage']['step'] = 1;
}
/**
* Theme preview of all users.
*/
function theme_uif_preview_users($variables) {
$data = $variables['data'];
$limit = $variables['limit'];
$current = 0;
$output = '';
foreach ($data['user'] as $user_data) {
$current++;
if ($current > $limit) {
break;
}
$output .= theme('uif_preview_one_user', array('data' => $user_data));
}
if (!$output) {
$output = t('There are no users to import.');
}
return $output;
}
/**
* Theme preview of a single user.
*/
function theme_uif_preview_one_user($variables) {
$user_data = $variables['data'];
$rows = array();
foreach ($user_data as $field => $value) {
$rows[] = array($field, $value);
}
$user_exists = db_query('SELECT COUNT(*) FROM {users} WHERE mail = :mail', array(':mail' => $user_data['email']))->fetchField();
$annotation = $user_exists ? t('update') : t('create');
$heading = $user_data['email'] . ' (' . $annotation . ')';
return '' . $heading . '
' . theme('table', array('rows' => $rows));
}
/**
* Batch import all users.
*/
function uif_batch_import_users($form_state) {
$batch = array(
'title' => t('Importing users'),
'operations' => array(
array('uif_batch_import_users_process', array($form_state))
),
'progress_message' => '', // uses count(operations) which is irrelevant in this case
'finished' => 'uif_batch_import_users_finished',
'file' => drupal_get_path('module', 'uif') . '/uif.admin.inc',
);
batch_set($batch);
}
/**
* User import batch processing.
*/
function uif_batch_import_users_process($form_state, &$context) {
// Initialize
if (empty($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['max'] = count($form_state['storage']['data']['user']);
$context['results']['created'] = 0;
$context['results']['updated'] = 0;
}
// Process max 20 users at a time
$processed = 0;
$notify = $form_state['storage']['notify'];
while ($context['sandbox']['progress'] < $context['sandbox']['max'] && $processed < 20) {
$index = $context['sandbox']['progress'];
uif_import_user($form_state['storage']['data']['user'][$index], $notify, $context['results'], $form_state);
$context['sandbox']['progress']++;
$processed++;
}
// Finished yet?
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
}
}
/**
* User import batch completion.
*/
function uif_batch_import_users_finished($success, $results, $operations) {
if ($success) {
global $user;
if (isset($results['self'])) {
uif_update_user($results['self'], $user->uid);
$results['updated']++;
unset($results['self']);
}
$done = t('User import complete.');
$created = $results['created'] ? format_plural($results['created'], 'One user was created.', '@count users were created.') . ' ' : '';
$updated = $results['updated'] ? format_plural($results['updated'], 'One user was updated.', '@count users were updated.') . ' ' : '';
$more = t('View the user list.', array('@url' => url('admin/people')));
drupal_set_message($done . $created . $updated . $more);
}
else {
drupal_set_message(t('An error occurred and processing did not complete.'), 'error');
}
}
/**
* Import one user.
*/
function uif_import_user($user_data, $notify, &$results, $form_state) {
if ($uid = db_query('SELECT uid FROM {users} WHERE mail = :mail', array(':mail' => $user_data['email']))->fetchField()) {
global $user;
if ($uid === $user->uid) {
$results['self'] = $user_data;
return;
}
$account = uif_update_user($user_data, $uid, $form_state);
$results['updated']++;
}
else {
$account = uif_create_user($user_data, $notify, $form_state);
$results['created']++;
}
}
/**
* Create a new user.
*/
function uif_create_user($user_data, $notify, $form_state) {
$account = array();
$account['mail'] = $user_data['email'];
$account['init'] = $user_data['email'];
$account['status'] = 1;
// Use the provided username if any, or derive it from the email
$username = empty($user_data['username']) ? preg_replace('/@.*$/', '', $user_data['email']) : $user_data['username'];
$account['name'] = uif_unique_username($username);
// Use the provided password if any, otherwise a random one
$pass = !empty($user_data['password']) ? $user_data['password'] : user_password();
$account['pass'] = $pass;
$account = array_merge($account, module_invoke_all('uif_pre_create', $account, $user_data, $form_state));
$account = user_save('', $account);
module_invoke_all('uif_post_create', $account, $user_data, $form_state);
if ($notify) {
$account->password = $pass; // For mail token; _user_mail_notify() expects this
_user_mail_notify('register_admin_created', $account);
}
return $account;
}
/**
* Update an existing user.
*/
function uif_update_user($user_data, $uid, $form_state) {
$account = user_load($uid);
// todo: Support update of user mail, name, and password
// Supporting user mail change requires optional inclusion of uid column, which
// would override use of email column as uid lookup method.
$additions = module_invoke_all('uif_pre_update', $account, $user_data, $form_state);
$account = user_save($account, $additions);
module_invoke_all('uif_post_update', $account, $user_data, $form_state);
return $account;
}
/**
* Given a starting point for a Drupal username (e.g. the name portion of an email address) return
* a legal, unique Drupal username.
*
* @param $name
* A name from which to base the final user name. May contain illegal characters; these will be stripped.
*
* @param $uid
* (optional) Uid to ignore when searching for unique user (e.g. if we update the username after the
* {users} row is inserted)
*
* @return
* A unique user name based on $name.
*
*/
function uif_unique_username($name, $uid = 0) {
// Strip illegal characters
$name = preg_replace('/[^\x{80}-\x{F7} a-zA-Z0-9@_.\'-]/', '', $name);
// Strip leading and trailing whitespace
$name = trim($name);
// Convert any other series of spaces to a single space
$name = preg_replace('/ +/', ' ', $name);
// If there's nothing left use a default
$name = ('' === $name) ? t('user') : $name;
// Truncate to reasonable size
$name = (drupal_strlen($name) > (USERNAME_MAX_LENGTH - 10)) ? drupal_substr($name, 0, USERNAME_MAX_LENGTH - 11) : $name;
// Iterate until we find a unique name
$i = 0;
do {
$newname = empty($i) ? $name : $name . '_' . $i;
$args = array(':uid' => $uid, ':name' => $newname);
$found = db_query_range('SELECT uid from {users} WHERE uid <> :uid AND name = :name', 0, 1, $args)->fetchField();
$i++;
} while ($found);
return $newname;
}
/**
* Theme function for import form help.
*/
function theme_uif_form_help() {
$basic_help = '' . t('Choose an import file. You\'ll have a chance to preview the data before doing the import. The import file must have a header row with a name in each column for the value you are importing. The header names are not case sensitive. Importable fields include:') . '
';
$items = array(
t('email (required) - the user\'s email'),
t('username (optional) - a name for the user. If not provided, a name is created based on the email.'),
t('password (optional) - a password for the user. If not provided, a password is generated.')
);
$basic_help .= theme('item_list', array('items' => $items));
$helps = module_invoke_all('uif_help');
array_unshift($helps, $basic_help);
$output = '';
foreach ($helps as $help) {
$output .= '' . $help . '
';
}
return $output;
}