uif.admin.inc in User Import Framework 6
Same filename and directory in other branches
Simple, extensible user import from a CSV file.
File
uif.admin.incView source
<?php
/**
* @file
* Simple, extensible user import from a CSV file.
*/
/**
* User import multi-part form.
*/
function uif_import_form(&$form_state) {
// Cause return to beginning if we just completed an import
if ($form_state['storage']['step'] >= 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(
'#prefix' => '<div id="uif_form_help">',
'#suffix' => '</div>',
'#value' => 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.') . '<br />' . $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 <em>Welcome, new user created by administrator</em> email using the template on the <a href="@url1">user settings page</a>. This is the same email sent for <a href="@url2">admin-created accounts</a>.', 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(
'#prefix' => '<div id="uif_form_instructions">',
'#suffix' => '</div>',
'#value' => t('Preview these records and when ready to import click Import users.'),
);
$form['user_preview'] = array(
'#prefix' => '<div id="uif_user_preview">',
'#suffix' => '</div>',
'#value' => $form_state['storage']['user_preview'],
);
$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->filepath, $data, $form_state);
if (!empty($errors)) {
form_set_error('user_upload', '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>');
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['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', $form_state['storage']['data'], $preview_count);
}
else {
$step = 2;
}
}
if (2 == $step) {
uif_batch_import_users($form_state);
}
$form_state['storage']['step'] = $step + 1;
}
/**
* Read the user import file and validate on the way.
*
* @param $filepath
* 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($filepath, &$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($filepath, '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_result(db_query_range("SELECT uid FROM {users} WHERE mail LIKE '%s'", $user_row['email'], 0, 1));
$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) {
foreach ($header as $column) {
if ('email' === $column) {
$email_found = TRUE;
}
}
if (!$email_found) {
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($data, $limit) {
$current = 0;
foreach ($data['user'] as $user_data) {
$current++;
if ($current > $limit) {
break;
}
$output .= theme('uif_preview_one_user', $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($user_data) {
$rows = array();
foreach ($user_data as $field => $value) {
$rows[] = array(
$field,
$value,
);
}
$user_exists = db_result(db_query("SELECT count(*) FROM {users} WHERE mail='%s'", $user_data['email']));
$annotation = $user_exists ? t('update') : t('create');
$heading = $user_data['email'] . ' (' . $annotation . ')';
return '<h3>' . $heading . '</h3>' . theme('table', NULL, $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) {
$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 <a href="@url">user list</a>.', array(
'@url' => url('admin/user/user'),
));
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_result(db_query("SELECT uid FROM {users} WHERE mail LIKE '%s'", $user_data['email']))) {
$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 = $user_data['password'] ? $user_data['password'] : user_password();
$account['pass'] = $pass;
// If access is not set, no users will be able to view the
// new user's profile until such time that the newly
// created user logs in for the first time.
$account['access'] = time();
$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;
$found = db_result(db_query_range("SELECT uid from {users} WHERE uid <> %d AND name = '%s'", $uid, $newname, 0, 1));
$i++;
} while ($found);
return $newname;
}
/**
* Theme function for import form help.
*/
function theme_uif_form_help() {
$basic_help = '<p>' . 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:') . '</p>';
$basic_help .= '<ul><li>' . t('email (required) - the user\'s email') . '</li>';
$basic_help .= '<li>' . t('username (optional) - a name for the user. If not provided, a name is created based on the email.') . '</li>';
$basic_help .= '<li>' . t('password (optional) - a password for the user. If not provided, a password is generated.') . '</li></ul>';
$helps = module_invoke_all('uif_help');
array_unshift($helps, $basic_help);
foreach ($helps as $help) {
$output .= '<div class="uif_help_section">' . $help . '</div>';
}
$fieldset = array(
'#type' => 'fieldset',
'#title' => t('User import help'),
'#value' => $output,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
return theme('fieldset', $fieldset);
}
Functions
Name | Description |
---|---|
theme_uif_form_help | Theme function for import form help. |
theme_uif_preview_one_user | Theme preview of a single user. |
theme_uif_preview_users | Theme preview of all users. |
uif_add_line_number | Prepend the line number on the error. |
uif_batch_import_users | Batch import all users. |
uif_batch_import_users_finished | User import batch completion. |
uif_batch_import_users_process | User import batch processing. |
uif_clean_and_key_row | 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. |
uif_clean_value | Check that input is UTF-8. |
uif_create_user | Create a new user. |
uif_import_form | User import multi-part form. |
uif_import_form_back | Return user to starting point on template multi-form. |
uif_import_form_submit | Form submission handler. |
uif_import_form_validate | Validate the import data. |
uif_import_user | Import one user. |
uif_normalize_header | Normalize the header columns. |
uif_row_has_data | Is there data in the row? |
uif_uif_validate_header | Implementation of hook_uif_validate_header(). |
uif_uif_validate_user | Implementation of hook_uif_validate_user(). |
uif_unique_username | Given a starting point for a Drupal username (e.g. the name portion of an email address) return a legal, unique Drupal username. |
uif_update_user | Update an existing user. |
uif_validate_user_file | Read the user import file and validate on the way. |