array( 'arguments' => array(), ), 'user_import_edit' => array( 'arguments' => array('form' => NULL), ), 'user_import_errors_display' => array( 'arguments' => array('settings' => NULL, 'data' => NULL, 'total' => NULL), ), 'user_import_username_errors' => array( 'arguments' => array('errors' => NULL), ), ); } /** * Implementation of hook_help(). */ function user_import_help($path, $arg) { switch ($path) { case 'admin/user/user_import': return t("Import or update users from a comma separated file (csv). Click 'Import' to start a new import."); } } /** * Implementation of hook_perm(). */ function user_import_perm() { return array('import users'); } /** * Implementation of hook_menu(). */ function user_import_menu() { $items['admin/user/user_import'] = array( 'title' => 'User Imports', 'description' => 'Import or update users from a comma separated file (csv).', 'page callback' => 'user_import_list', 'access arguments' => array('import users'), ); $items['admin/user/user_import/list'] = array( 'title' => 'List Imports', 'access arguments' => array('import users'), 'weight' => -10, 'type' => MENU_DEFAULT_LOCAL_TASK ); $items['admin/user/user_import/add'] = array( 'title' => 'Import', 'page callback' => 'user_import_preferences', 'access arguments' => array('import users'), 'weight' => -5, 'type' => MENU_LOCAL_TASK ); $items['admin/user/user_import/continue'] = array( 'title' => 'Continue', 'page callback' => 'user_import_continue', 'access arguments' => array('import users'), 'type' => MENU_CALLBACK ); $items['admin/user/user_import/import'] = array( 'title' => 'Import', 'page callback' => 'user_import_import', 'access arguments' => array('import users'), 'type' => MENU_CALLBACK ); $items['admin/user/user_import/delete'] = array( 'title' => 'Delete Import', 'page callback' => 'user_import_delete', 'access arguments' => array('import users'), 'type' => MENU_CALLBACK ); $items['admin/user/user_import/configure'] = array( 'title' => 'Configure', 'page callback' => 'drupal_get_form', 'page arguments' => array('user_import_configure_form'), 'access arguments' => array('import users'), 'type' => MENU_LOCAL_TASK ); $items['user_import/delete'] = array( 'title' => 'Remove Info', 'page callback' => 'user_import_limited_delete', 'type' => MENU_CALLBACK, 'access arguments' => array('limited user import'), ); $items['user_import/errors'] = array( 'title' => 'Import Errors', 'page callback' => 'user_import_limited_errors', 'type' => MENU_CALLBACK, 'access arguments' => array('limited user import'), ); return $items; } /** * Implementation of hook_cron(). */ function user_import_cron() { $imports = _user_import_settings_select(); if (!$imports) return; foreach ($imports as $import) { if ($import['setting'] == 'test' || $import['setting'] == 'import') _user_import_process($import); } return; } // - - - - - - - - FORMS - - - - - - - - /** * Configuration form define (settings affect all user imports) */ function user_import_configure_form() { $form['performance'] = array( '#type' => 'fieldset', '#title' => t('Performance'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['performance']['user_import_max'] = array( '#type' => 'textfield', '#title' => t('Maximum Users/Process'), '#default_value' => variable_get('user_import_max', 250), '#size' => 10, '#maxlength' => 10, '#description' => t('Maximum number of users to import each time the file is processed, useful for controling the rate at which emails are sent out.'), ); $form['performance']['user_import_line_max'] = array( '#type' => 'textfield', '#title' => t('Maximum length of line'), '#default_value' => variable_get('user_import_line_max', 1000), '#size' => 10, '#maxlength' => 10, '#description' => t('The default is set at 1,000 characters, if a line in your csv is longer than this you should set a higher maximum here. Setting higher maximums will slow down imports.'), ); $saved_templates = _user_import_settings_select(NULL, 'GET TEMPLATES'); if (!empty($saved_templates)) { $form['settings_templates'] = array( '#type' => 'fieldset', '#title' => t('Settings Templates'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $templates_list = array('-- none --'); foreach ($saved_templates AS $template) { $templates_list[$template['import_id']] = $template['name']; $templates_delete[$template['import_id']] = $template['name']; } $form['settings_templates']['user_import_settings'] = array( '#type' => 'select', '#title' => t('Default Settings'), '#description' => t('Select if you want to use a previously saved set of settings as default for all imports.'), '#default_value' => variable_get('user_import_settings', 0), '#options' => $templates_list, ); $form['settings_templates']['templates'] = array( '#type' => 'checkboxes', '#title' => t('Delete Templates'), '#options' => $templates_delete, ); } $form['submit'] = array( '#type' => 'submit', '#value' => t('Save'), ); return $form; } function user_import_configure_form_validate($form, &$form_state) { if (is_numeric($form_state['values']['user_import_max'])) { if ($form_state['values']['user_import_max'] < 10) form_set_error('user_import_max', t("Value should be at least 10.")); } else { form_set_error('user_import_max', t('Value must be a number.')); } if (is_numeric($form_state['values']['user_import_line_max'])) { if ($form_state['values']['user_import_line_max'] < 1000) form_set_error('user_import_line_max', t("Value must be higher than 1000.")); if ($form_state['values']['user_import_line_max'] > 1000000) form_set_error('user_import_line_max', t("Value must be lower than 1,000,000.")); } else { form_set_error('user_import_line_max', t('Value must be a number.')); } return; } function user_import_configure_form_submit($form, &$form_state) { settype($form_state['values']['user_import_max'], 'integer'); settype($form_state['values']['user_import_line_max'], 'integer'); variable_set('user_import_max', $form_state['values']['user_import_max']); variable_set('user_import_line_max', $form_state['values']['user_import_line_max']); variable_set('user_import_settings', $form_state['values']['user_import_settings']); if (!empty($form_state['values']['templates'])) { foreach ($form_state['values']['templates'] as $import_id) { if (!empty($import_id)) { $template = _user_import_settings_select($import_id); if (!empty($deleted)) $deleted .= ', '; $deleted .= $template['name']; _user_import_settings_deletion($import_id); } } } if (!empty($deleted)) drupal_set_message(t('Settings templates deleted: @deleted', array('@deleted' => $deleted))); drupal_set_message(t('Configuration settings have been saved.')); $form_state['redirect'] = 'admin/user/user_import'; } function user_import_add_form($import_id = NULL) { $form = array(); $ftp_files = _user_import_ftp_files(); user_import_add_file_form($form, $ftp_files); $settings = _user_import_settings_select(NULL, 'get saved'); if ($settings) { $saved_settings = array(t('-- none --')); foreach ($settings AS $settings_set) { $saved_settings[$settings_set['import_id']] = $settings_set['name']; } $form['import_template_select'] = array( '#type' => 'select', '#title' => t('Saved Settings'), '#description' => t('Select if you want to use a previously saved set of settings.'), '#default_value' => variable_get('user_import_settings', 0), '#options' => $saved_settings, ); } $form['next'] = array( '#type' => 'submit', '#value' => t('Next') ); // Set form parameters so we can accept file uploads. $form['#attributes'] = array('enctype' => 'multipart/form-data'); return $form; } function user_import_add_form_validate($form, &$form_state) { $file = _user_import_file(NULL, $form_state['values']['file_ftp']); // check file uploaded OK if (empty($file->filename)) form_set_error('file_upload', t('A file must be uploaded or selected from FTP updates.')); /** * @todo check file matches saved settings selected */ return; } function user_import_add_form_submit($form, &$form_state) { $file = _user_import_file(NULL, $form_state['values']['file_ftp']); $form_state['values']['options']['ftp'] = empty($form_state['values']['file_ftp']) ? FALSE : TRUE; $form_state['values']['filename'] = $file->filename; $form_state['values']['oldfilename'] = $file->filename; $form_state['values']['filepath'] = $file->filepath; $form_state['values']['setting'] = 'file set'; // create import setting $import = _user_import_settings_save($form_state['values']); if (!empty($form_state['values']['import_template_select'])) $settings_template = check_plain($form_state['values']['import_template_select']); $form_state['redirect'] = 'admin/user/user_import/add/'. $import['import_id'] .'/'. $settings_template; } function user_import_edit($form_state, $import_id, $template_id = NULL) { // load code for supported modules user_import_load_supported(); $form = array(); $import = _user_import_settings_select($import_id); $import['template_id'] = $template_id; $form['ftp'] = array( '#type' => 'value', '#value' => $import['options']['ftp'], ); // add setting template values if ($import['setting'] == 'file set') $import = _user_import_initialise_import($import); $form['import_id'] = array( '#type' => 'value', '#value' => $import_id, ); $form['setting'] = array( '#type' => 'value', '#value' => $import['setting'], ); $form['return_path'] = array( '#type' => 'value', '#default_value' => 'admin/user/user_import', ); $form['og_id'] = array( '#type' => 'value', '#default_value' => 0, ); // don't use hook because these need to be added in this order; user_import_edit_file_fields($form, $import); user_import_form_field_match($form, $import); $collapsed = (empty($import['name'])) ? FALSE: TRUE; $additional_fieldsets = module_invoke_all('user_import_form_fieldset', $import, $collapsed); if (is_array($additional_fieldsets)) $form = $form + $additional_fieldsets; $update_user = module_invoke_all('user_import_form_update_user'); if (is_array($update_user)) { $form['update_user'] = array( '#type' => 'fieldset', '#title' => t('Update Existing Users'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#tree' => TRUE, ); foreach ($update_user as $module => $display) { $options = array(UPDATE_NONE => t('No Update'), UPDATE_REPLACE => t('Replace Data'), UPDATE_ADD => t('Add Data')); if ($display['exclude_add'] == TRUE) unset($options[UPDATE_ADD]); if ($display['exclude_replace'] == TRUE) unset($options[UPDATE_REPLACE]); $form['update_user'][$module] = array( '#type' => 'radios', '#title' => $display['title'], '#options' => $options, '#default_value' => empty($import['options']['update_user'][$module]) ? UPDATE_NONE : $import['options']['update_user'][$module], '#description' => $display['description'], ); } } // don't show test option if import has started if ($import['setting'] != 'import' && $import['setting'] != 'imported') { $form['test'] = array( '#type' => 'submit', '#value' => t('Test'), '#weight' => 100, '#submit' => array('user_import_test_submit', 'user_import_edit_submit'), ); } $form['import'] = array( '#type' => 'submit', '#value' => t('Import'), '#weight' => 100, '#submit' => array('user_import_import_submit', 'user_import_edit_submit'), ); $form['cancel'] = array( '#type' => 'submit', '#value' => t('Cancel'), '#weight' => 100, '#validate' => array('user_import_edit_cancel_validate'), ); return $form; } function user_import_edit_cancel_validate($form, &$form_state) { // if import was being added - delete file if ($form_state['values']['setting'] == 'file set') { $settings = _user_import_settings_select($form_state['values']['import_id']); _user_import_settings_deletion($form_state['values']['import_id']); _user_import_file_deletion($settings['filepath'], $settings['filename'], $settings['oldfilename'], $settings['options']['ftp']); } $form_state['redirect'] = 'admin/user/user_import'; } function user_import_edit_validate($form, &$form_state) { $fields = array(); foreach ($form_state['values']['field_match'] as $row => $values) { // check each field is unique if ($values['field_match'] != '0' && $values['field_match'] != '-------------' && in_array($values['field_match'], $fields)) { form_set_error('field_match', t('Database fields can only be matched to one column of the csv file.')); } $fields[$values['field_match']] = $values['field_match']; // check email address has been selected if ($values['field_match'] == 'user-email') $email = TRUE; } if (!$email) form_set_error('email', t('One column of the csv file must be set as the email address.')); if ($form_state['values']['name']) { $form_state['values']['name'] = rtrim($form_state['values']['name']); if (drupal_strlen($form_state['values']['name']) < 1 || drupal_strlen($form_state['values']['name']) > 25) { form_set_error('name', t('Name of saved settings must be 25 characters or less.')); } } return; } /** * Save a new template. */ function user_import_template_new_submit($form, &$form_state) { // save settings for import _user_import_settings_save($form_state['values']); // save settings for template $import_id = $form_state['values']['import_id']; $form_state['values']['setting'] = 'template'; unset($form_state['values']['import_id']); _user_import_initialise_import($form_state['values']); drupal_set_message(t("'%name' was saved as a settings template.", array('%name' => $form_state['values']['name']))); // reload settings page $form_state['redirect'] = 'admin/user/user_import/add/' . $import_id; return; } /** * Update an existing template. */ function user_import_template_update_submit($form, &$form_state) { // save settings for import $import_id = $form_state['values']['import_id']; _user_import_settings_save($form_state['values']); // get template details $template_id = db_result(db_query("SELECT import_id from {user_import} where setting = 'template' AND name= '%s' LIMIT 1", $form['#current_template'])); // save settings for template $form_state['values']['setting'] = 'template'; $form_state['values']['import_id'] = $template_id; $form_state['values']['name'] = $form['#current_template']; _user_import_initialise_import($form_state['values']); drupal_set_message (t("'%name' settings template was updated.", array('%name' => $form['#current_template']))); // reload settings page $form_state['redirect'] = 'admin/user/user_import/add/' . $import_id; return; } /** * */ function user_import_test_submit($form, &$form_state) { $form_state['values']['setting'] = 'test'; drupal_set_message(t('Tested')); } /** * */ function user_import_import_submit($form, &$form_state) { $form_state['values']['setting'] = 'import'; drupal_set_message (t('Imported')); } /** * */ function user_import_edit_submit($form, &$form_state) { if ($form_state['values']['setting'] == 'file set') $filepath = file_move($form_state['values']['filepath'], file_directory_path() . '/' . $form_state['values']['filename']); if (!empty($form_state['values']['og_id'])) $form_state['values']['groups'][$form_state['values']['og_id']] = $form_state['values']['og_id']; $form_state['values']['options']['ftp'] = $form_state['values']['ftp']; $form_state['values'] = _user_import_settings_save($form_state['values']); $form_state['values']['save']['update'] = NULL; $form_state['values']['import_template_id'] = NULL; $form_state['values']['save']['name'] = NULL; $form_state['redirect'] = $form_state['values']['return_path']; _user_import_process($form_state['values']); } function user_import_add_file_form(&$form, $ftp_files = NULL) { $form['browser'] = array( '#type' => 'fieldset', '#title' => t('Browser Upload'), '#collapsible' => TRUE, '#description' => t("Upload a CSV file."), ); if (function_exists('file_upload_max_size')) $file_size = t('Maximum file size: !size MB.', array('!size' => file_upload_max_size())); $form['browser']['file_upload'] = array( '#type' => 'file', '#title' => t('CSV File'), '#size' => 40, '#description' => t('Select the CSV file to be imported. ') . $file_size, ); if (!empty($ftp_files)) { $form['ftp'] = array( '#type' => 'fieldset', '#title' => t('FTP Upload'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => t("Any files uploaded to the 'user_import' directory using FTP can be selected for import here. Useful if the import file is too large for upload via the browser."), ); $form['ftp']['file_ftp'] = array( '#type' => 'radios', '#title' => t('Files'), '#default_value' => 0, '#options' => $ftp_files, ); // reload the page to show any files that have been added by FTP $form['ftp']['scan'] = array( '#type' => 'submit', '#value' => t('Check for new files'), '#validate' => array(), '#submit' => array(), ); } return; } /** * Saves options on content type configuration form * @todo check if this is cruft * @todo check $form['type'] */ function user_import_content_type_submit($form, &$form_state) { // user import template for Organic Groups content type $templates = variable_get('user_import_og_template', array()); $templates[$form['type']] = $form_state['values']['user_import_og']; variable_set('user_import_og_template', $templates); } // - - - - - - - - PAGES - - - - - - - - function user_import_preferences($import_id = NULL, $template_id = NULL) { if (empty($import_id)) { $output .= drupal_get_form('user_import_add_form'); } else { $output .= drupal_get_form('user_import_edit', $import_id, $template_id); } return $output; } function user_import_list($action = NULL, $import_id = NULL) { // clear incomplete imports _user_import_incomplete_deletion(); if (!empty($import_id) && is_numeric($import_id)) { $pager_id = 1; $max = 25; $import = _user_import_settings_select($import_id); $total = db_result(db_query("SELECT count(data) FROM {user_import_errors} WHERE import_id = %d", $import['import_id'])); $results = pager_query("SELECT * FROM {user_import_errors} WHERE import_id = %d", $max, $pager_id, NULL, array($import['import_id'])); while ($line = db_fetch_array($results)) { $line['data'] = unserialize($line['data']); $line['errors'] = unserialize($line['errors']); $file_lines[] = $line; } $output = theme('user_import_errors_display', $import, $file_lines, $total); $output .= theme('pager', NULL, $max, $pager_id); } else { $output = theme_user_import_list(); } return $output; } function user_import_continue($import_id = NULL) { if (!empty($import_id) && is_numeric($import_id)) { $import = _user_import_settings_select($import_id); _user_import_process($import); } drupal_goto('admin/user/user_import'); } function user_import_import($import_id = NULL) { if (!empty($import_id) && is_numeric($import_id)) { $import = _user_import_settings_select($import_id); _user_import_initialise_import($import); } drupal_goto('admin/user/user_import'); } function user_import_errors_display($import_id = NULL) { if (empty($import_id) || !is_numeric($import_id)) drupal_goto('admin/user/user_import'); $import = _user_import_settings_select($import_id); $output = theme('user_import_errors_display', $import); return $output; } function user_import_limited_errors($import_id = NULL, $template_id = NULL) { if (empty($import_id) || !is_numeric($import_id)) drupal_goto('user_import/' . $template_id); $pager_id = 1; $max = 25; $import = _user_import_settings_select($import_id); $total = db_result(db_query("SELECT count(data) FROM {user_import_errors} WHERE import_id = %d", $import['import_id'])); if (empty($total)) { $output .= theme('There were no import errors'); } else { $results = pager_query("SELECT * FROM {user_import_errors} WHERE import_id = %d", $max, $pager_id, NULL, array($import['import_id'])); while ($line = db_fetch_array($results)) { $line['data'] = unserialize($line['data']); $line['errors'] = unserialize($line['errors']); $file_lines[] = $line; } $output .= theme('user_import_errors_display', $import, $file_lines, $total); $output .= theme('pager', NULL, $max, $pager_id); } $output .= l(t('Return'), "user_import/$template_id/$import_id"); return $output; } // errors for user being imported function user_import_errors($error = FALSE, $clear = FALSE) { static $errors = array(); if ($clear) $errors = array(); if ($error) $errors[] = $error; return $errors; } function user_import_limited_delete($import_id = NULL, $template_id = NULL) { user_import_delete($import_id, "user_import/$template_id"); } function user_import_delete($import_id = NULL, $return_path = 'admin/user/user_import') { if (empty($import_id) || !is_numeric($import_id)) drupal_goto($return_path); $import = _user_import_settings_select($import_id); _user_import_settings_deletion($import_id); _user_import_file_deletion($import['filepath'], $import['filename'], $import['oldfilename'], $import['options']['ftp']); drupal_goto($return_path); return; } // - - - - - - - - THEMES - - - - - - - - function theme_user_import_list() { $imports = _user_import_settings_select(); if (!$imports) return ' '; foreach ($imports as $import) { // header labels $import_label = ($import['setting'] == 'tested' || $import['setting'] == 'test') ? t('importable') : t('imported'); $header = array(t('file'), t('started'), t('processed'), $import_label, t('errors'), t('status')); // info row $errors = db_result(db_query("SELECT COUNT(import_id) FROM {user_import_errors} WHERE import_id = %d", $import['import_id'])); $errors_link = ($errors == 0) ? '0': l($errors, 'admin/user/user_import/errors/' . $import['import_id']); $rows[0] = array( check_plain($import['oldfilename']), format_date($import['started'], 'small'), array("data" => $import['processed'], "align" => 'center'), array("data" => $import['valid'], "align" => 'center'), array("data" => $errors_link, "align" => 'center'), $import['setting'], ); $output .= theme('table', $header, $rows); // action buttons $settings_link = l(t('Settings'), 'admin/user/user_import/add/' . $import['import_id']); $delete_link = l(t('Delete'), 'admin/user/user_import/delete/' . $import['import_id']); $continue_link = l(t('Continue Processing'), 'admin/user/user_import/continue/' . $import['import_id']); $import_link = l(t('Import'), 'admin/user/user_import/import/' . $import['import_id']); $output .= $settings_link . ' | '; $output .= $delete_link; if ($import['setting'] == 'tested' || $import['setting'] == 'test') $output .= ' | ' . $import_link; if ($import['setting'] == 'test' || $import['setting'] == 'import') $output .= ' | ' . $continue_link; } return $output; } function theme_user_import_edit($form) { $header = array(t('csv column'), t('Drupal fields'), t('username'), t('abbreviate')); foreach (element_children($form['field_match']) as $key) { $rows[] = array( drupal_render($form['field_match'][$key]['csv']), drupal_render($form['field_match'][$key]['field_match']), drupal_render($form['field_match'][$key]['username']), drupal_render($form['field_match'][$key]['abbreviate']), ); } $form['field_match']['#value'] = theme('table', $header, $rows); $output .= drupal_render($form['remove']); $output .= drupal_render($form['options']); $output .= drupal_render($form['field_match']); $output .= drupal_render($form); return $output; } function theme_user_import_errors_display($settings, $data, $total) { $error_count = 0; $field_match = _user_import_unconcatenate_field_match($settings['field_match']); $header[0] = t('Email Address'); foreach ($data as $data_row) { $row = NULL; foreach ($data_row['data'] as $type => $fields) { if (!empty($fields)) { foreach ($fields as $field_id => $field_data) { foreach ($field_match as $column_info) { if ($column_info['type'] == $type && $column_info['field_id'] == $field_id) { if (!empty($column_info['username'])) { $header[$column_info['username']] = t('Name %sort', array('%sort' => $column_info['username'])); $row[$column_info['username']] = array("data" => $field_data[0], "align" => "left"); } if ($column_info['field_id'] == 'email') { $row[0] = array("data" => $field_data[0], "align" => "left"); } } } } } } ksort($row); $row[] = implode('
', $data_row['errors']); $rows[] = $row; } $output .= '

' . t('CSV File: %file', array('%file' => $settings['oldfilename'])) . '
'; $output .= t('Errors: !total', array('!total' => $total)) . '

'; $header['errors'] = t('Errors'); $output .= theme('table', $header, $rows); return $output; } function theme_user_import_username_errors($errors) { if (empty($errors)) { $output = '

' . t('All usernames are OK.') . '

'; } else { $header = array(t('User ID'), t('Email'), t('Username'), t('Error')); $output = theme('table', $header, $errors); } return $output; } // - - - - - - - - FILE HANDLING - - - - - - - - function _user_import_file_deletion($filepath, $filename, $old_filename, $ftp, $message = TRUE) { if ($ftp) { drupal_set_message(t("File '%filename' was uploaded using FTP and should be deleted manually once the import has been completed.", array('%filename' => $filename))); return; } $removed = file_delete($filepath); if (!$message) return; if (empty($removed)) { drupal_set_message(t("File error: file '%old_filename' (%filename) could not be deleted.", array('%old_filename' => $oldfilename, '%filename' => $filename)), 'error'); } else { drupal_set_message(t("File '%old_filename' was deleted.", array('%old_filename' => $old_filename))); } return; } /* * File being used * $import_id - use file info stored in database * $ftp_file - chosen from FTP uploaded files * $uploaded_file - uploaded through browser */ function _user_import_file($import_id = NULL, $ftp_file_selected = NULL) { static $file; if (!empty($file)) return $file; // file was uploaded through browser $file = file_save_upload('file_upload'); if (!empty($file) ) return $file; // file was uploaded by FTP if (!empty($ftp_file_selected)) { $ftp_files = _user_import_ftp_files(); $filepath = drupal_get_path('module', 'user_import'); $filename = $ftp_files[$ftp_file_selected]; $file = new stdClass(); $file->filepath = "$filepath/$filename"; $file->filename = $filename; return $file; } // use file info stored in database if (!empty($import_id)) { $import = _user_import_settings_select($import_id); $file->filepath = $import['filepath']; $file->oldfilename = $import['oldfilename']; $file->filename = $import['filename']; return $file; } return; } /** * open file */ function _user_import_file_open($filepath, $filename) { ini_set('auto_detect_line_endings', true); $handle = @fopen($filepath, "r"); if (!$handle) { form_set_error('file', t("Could not find the csv file '%filename'", array('%filename' => $filename)), 'error'); return t("Please add your file again."); } return $handle; } // get first row of file function _user_import_file_row($filename, $handle) { // hacked from http://drupal.org/node/84902 setlocale(LC_ALL, 'en_US.utf-8'); $data_row = @fgetcsv ($handle, 1000000, ","); if (!$data_row) { form_set_error('file', t("Could not get data, the file '%filename' is either empty or has incompatible line endings.", array('%filename' => $filename)), 'error'); } return $data_row; } // get info on files uploaded via FTP function _user_import_ftp_files() { $directory = opendir( drupal_get_path('module', 'user_import') ); $filenames[] = t('none'); while ($file = readdir($directory)) { if ($file != '.' && $file != '..' && $file != '.DS_Store' && $file != 'CVS' && $file != '.svn' && $file != 'README.txt' && $file != 'LICENSE.txt' && $file != 'UPDATES.txt' && $file != 'user_import.module' && $file != 'user_import.mysql' && $file != 'user_import.install' && $file != 'user_import.info' && $file != 'supported' && $file != 'tests') $filenames[] = $file; } closedir($directory); return $filenames; } // - - - - - - - - MISC - - - - - - - - function user_import_edit_file_fields(&$form, $import) { $form['filename'] = array( '#type' => 'value', '#value' => $import['filename'], ); $form['oldfilename'] = array( '#type' => 'value', '#value' => $import['oldfilename'], ); $form['filepath'] = array( '#type' => 'value', '#value' => $import['filepath'], ); return; } function user_import_form_field_match(&$form, $import) { $collapsed = (empty($import['name'])) ? FALSE: TRUE; $handle = _user_import_file_open($form['filepath']['#value'], $form['filename']['#value']); $data_row = _user_import_file_row($form['filename']['#value'], $handle); $fieldmatch_description = t("Match columns in CSV file to profile fields, leave as '----' if there is no match."); $fieldmatch_description .= '
' . t('Username') . ': ' . t("The Username will be built from CSV columns in the order selected."); $fieldmatch_description .= '
' . t('Abbreviate') . ': ' . t("Use the first letter of a field in uppercase for the Username, e.g. 'john' -> 'J'."); $fieldmatch_description .= '
' . t("If no CSV fields are selected, the Username will be randomly generated."); $form['field_match'] = array( '#type' => 'fieldset', '#title' => t('Field Match'), '#description' => $fieldmatch_description, '#weight' => -90, '#collapsible' => TRUE, '#collapsed' => $collapsed, '#tree' => TRUE, ); // add default and email address options $user_fields[0] = '-------------'; $additional_user_fields = module_invoke_all('user_import_form_field_match'); foreach ($additional_user_fields as $type => $type_options) { if (is_array($type_options)) { foreach ($type_options as $field_id => $label) { $user_fields["$type-$field_id"] = $label; } } } asort($user_fields); $row = 0; $sort = array(t('no'), 1, 2, 3, 4); if (empty($data_row)) return; foreach ($data_row as $data_cell) { $form['field_match'][$row]= array( '#tree' => TRUE, ); $form['field_match'][$row]['csv'] = array( '#value' => check_plain(drupal_substr($data_cell, 0, 40)), ); $form['field_match'][$row]['field_match'] = array( '#type' => 'select', '#default_value' => ($import['field_match'][$row]['field_match']) ? $import['field_match'][$row]['field_match'] : $user_fields[0], '#options' => $user_fields, ); $form['field_match'][$row]['username'] = array( '#type' => 'select', '#default_value' => ($import['field_match'][$row]['username']) ? $import['field_match'][$row]['username'] : $sort[0], '#options' => $sort, ); $form['field_match'][$row]['abbreviate'] = array( '#type' => 'checkbox', '#default_value' => ($import['field_match'][$row]['abbreviate']) ? $import['field_match'][$row]['abbreviate'] : NULL, ); $row++; } return; } function _user_import_create_username($order, $data, $abbreviate, $username_space) { if (is_array($order)) { asort($order); reset($order); $username = ''; while (list ($file_column, $sequence) = each ($order)) { if (!empty($username) && !empty($username_space)) $username .= ' '; $username .= ($abbreviate[$file_column] == 1) ? trim(drupal_strtoupper(chr(ord($data[$file_column])))) : trim($data[$file_column]); } } if (empty($username)) $username = _user_import_random_username(); $username = _user_import_sanitise_username($username); $username = _user_import_unique_username($username, TRUE); return $username; } /** * conform to Drupal username rules */ function _user_import_sanitise_username($username) { // username cannot contain an illegal character /* $username = preg_replace('/[^\x80-\xF7 [:alnum:]@_.-]/', '', $username); $username = preg_replace( '/[\x{80}-\x{A0}'. // Non-printable ISO-8859-1 + NBSP '\x{AD}'. // Soft-hyphen '\x{2000}-\x{200F}'. // Various space characters '\x{2028}-\x{202F}'. // Bidirectional text overrides '\x{205F}-\x{206F}'. // Various text hinting characters '\x{FEFF}'. // Byte order mark '\x{FF01}-\x{FF60}'. // Full-width latin '\x{FFF9}-\x{FFFD}'. // Replacement characters '\x{0}]/u', '', $username); */ // hacked from http://drupal.org/node/84902 $username = mb_ereg_replace ( '/[^\pL\pN@ ]/' , ' ' , $username ); // username cannot contain multiple spaces in a row $username = preg_replace('/[ ]+/', ' ', $username); // username must be less than 56 characters $username = substr($username, 0, 56); // username cannot begin or end with a space $username = trim($username); return $username; } /** * deal with duplicate usernames */ function _user_import_unique_username($username, $start = FALSE) { static $suffix = 1; if ($start) $suffix = 1; if ($suffix < 2) { $duplicate = db_result(db_query("SELECT uid from {users} where name = '%s' LIMIT 1", $username)); } else { $duplicate = db_result(db_query("SELECT uid from {users} where name = '%s' LIMIT 1", "$username $suffix")); } // loop until name is valid if (!empty($duplicate)) { $suffix++; _user_import_unique_username($username); } // add number at end of username if it already exists $username = ($suffix < 2) ? $username : "$username $suffix"; return $username; } function _user_import_profile($key = 'fid', $return_value = NULL) { if (!module_exists('profile')) return; static $fields_static; $fields = array(); // avoid making more than one database call for profile info if (empty($fields_static)) { $results = db_query("SELECT * FROM {profile_fields}"); while ($row = db_fetch_object($results)) { // don't include private fields if (user_access('administer users') || $row->visibility != PROFILE_PRIVATE) { $fields_static[] = $row; } } } if (empty($fields_static)) return array(); // return all profile fields info, or just specific type if (empty($return_value)) { foreach ($fields_static as $field) { $fields[$field->{$key}] = $field; } } else { foreach ($fields_static as $field) { $fields[$field->{$key}] = $field->{$return_value}; } } asort($fields); return $fields; } function _user_import_settings_save($settings) { /** * @todo refactor to put all settings into 'options' column, except settings that have their own column */ // move settings into 'options' column // 'options' column will be used to store new control options - instead of creating a new column for each option $settings['options']['groups'] = $settings['groups']; $settings['options']['existing_og_subscribe'] = $settings['existing_og_subscribe']; $settings['options']['existing_og_subject'] = $settings['existing_og_subject']; $settings['options']['existing_og_markup'] = $settings['existing_og_markup']; $settings['options']['existing_og_message'] = $settings['existing_og_message']; $settings['options']['existing_og_css'] = $settings['existing_og_css']; $settings['options']['subject'] = $settings['subject']; $settings['options']['message'] = $settings['message']; $settings['options']['message_format'] = $settings['message_format']; $settings['options']['message_css'] = $settings['message_css']; $settings['options']['activate'] = $settings['activate']; $settings['options']['subscribed'] = $settings['subscribed']; $settings['options']['update_user'] = $settings['update_user']; $settings['options']['roles_new'] = $settings['roles_new']; // Update settings for existing import if ($settings['import_id']) { db_query("UPDATE {user_import} SET name = '%s', filename = '%s', oldfilename = '%s', filepath = '%s', pointer = %d, processed = %d, valid= %d, first_line_skip = %d, contact = %d, username_space = %d, send_email = %d, field_match = '%s', roles = '%s', options = '%s', setting = '%s' WHERE import_id = %d ", trim($settings['name']), $settings['filename'], $settings['oldfilename'], $settings['filepath'], $settings['pointer'], $settings['processed'], $settings['valid'], $settings['first_line_skip'], $settings['contact'], $settings['username_space'], $settings['send_email'], serialize($settings['field_match']), serialize($settings['roles']), serialize($settings['options']), $settings['setting'], $settings['import_id']); // Save settings for new import } else { db_query("INSERT INTO {user_import} (name, filename, oldfilename, filepath, started, pointer, processed, valid, first_line_skip, contact, username_space, send_email, field_match, roles, options, setting) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d, %d, %d, %d, %d, %d, '%s', '%s', '%s', '%s') ", trim($settings['name']), $settings['filename'], $settings['oldfilename'], $settings['filepath'], time(), $settings['pointer'], $settings['processed'], $settings['valid'], $settings['first_line_skip'], $settings['contact'], $settings['username_space'], $settings['send_email'], serialize($settings['field_match']), serialize($settings['roles']), serialize($settings['options']), $settings['setting']); switch ($GLOBALS['db_type']) { case 'mysql': case 'mysqli': $settings['import_id'] = db_result(db_query("SELECT LAST_INSERT_ID()")); break; case 'pgsql': $settings['import_id'] = db_result(db_query("SELECT currval('{user_import_import_id_seq}')")); break; } } return $settings; } // Update settings for existing import function _user_import_settings_update($pointer, $processed, $valid, $setting, $import_id) { if (empty($import_id)) return; db_query("UPDATE {user_import} SET pointer = %d, processed = %d, valid= %d, setting = '%s' WHERE import_id = %d", $pointer, $processed, $valid, $setting, $import_id); } function _user_import_errors_display_save($import_id, $data, $email, $errors) { $data['email'] = $email; db_query("INSERT INTO {user_import_errors} (import_id, data, errors) VALUES (%d, '%s', '%s') ", $import_id, serialize($data), serialize($errors)); return; } function _user_import_settings_select($import_id = NULL, $template = FALSE) { if (!empty($import_id) && !is_numeric($import_id)) return; if (!empty($import_id)) { $sql = 'SELECT * FROM {user_import} WHERE import_id = %d'; if ($template) $sql .= " AND setting = 'template'"; $import = db_fetch_array(db_query($sql . ' LIMIT 1', $import_id)); if (empty($import)) return FALSE; $import['field_match'] = unserialize($import['field_match']); $import['roles'] = unserialize($import['roles']); $import['options'] = unserialize($import['options']); /** * @todo loop through options and add them to first dimension of array * i.e. $import['groups'] instead of $import['options']['groups'] * all use of $import['options'][foo] will also need to be updated */ } else { $query = ($template) ? "SELECT * FROM {user_import} WHERE setting = 'template'" : "SELECT * FROM {user_import} WHERE setting != 'template' ORDER BY started DESC"; $results = db_query($query); while ($row = db_fetch_array($results)) { $row['field_match'] = unserialize($row['field_match']); $row['roles'] = unserialize($row['roles']); $row['options'] = unserialize($row['options']); /** * @todo loop through options and add them to first dimension of array * i.e. $import['groups'] instead of $import['options']['groups'] * all use of $import['options'][foo] will also need to be updated */ $import[] = $row; } } return $import; } function _user_import_settings_deletion($import_id) { db_query("DELETE FROM {user_import} WHERE import_id = %d", $import_id); db_query("DELETE FROM {user_import_errors} WHERE import_id = %d", $import_id); return; } function _user_import_random_username() { $vowels = 'aoueiy'; $consonants = 'bcdfghjklmnpqrstvwxz'; $length = 8; mt_srand ((double) microtime() * 10000000); $next_vowel = 0; for ($count = 0; $count <= $length; $count++) { if ($next_vowel) { $rand = mt_rand(0, 5); $username.= $vowels{$rand}; $next_vowel = 0; } else { $rand = mt_rand(0, 19); $username .= $consonants{$rand}; $next_vowel = 1; } } return $username; } // check if any updates are to be made function _user_import_update_user_check($settings) { foreach ($settings as $setting) { if ($setting != UPDATE_NONE) return TRUE; } return FALSE; } function _user_import_process($settings) { user_import_load_supported(); $line_max = variable_get('user_import_line_max', 1000); $import_max = variable_get('user_import_max', 250); $field_match = _user_import_unconcatenate_field_match($settings['field_match']); $update_setting = _user_import_update_user_check($settings['options']['update_user']); $update_setting_per_module = $settings['options']['update_user']; ini_set('auto_detect_line_endings', true); $handle = @fopen($settings['filepath'], "r"); // move pointer to where test/import last finished if ($settings['pointer'] != 0) fseek ($handle, $settings['pointer']); // start count of imports on this cron run $send_counter = 0; // hacked from http://drupal.org/node/84902 setlocale(LC_ALL, 'en_US.utf-8'); while ($data = fgetcsv($handle, $line_max, ',')) { $errors = user_import_errors(FALSE, TRUE); // if importing, check we are not over max number of imports per cron if ($settings['setting'] == 'import' && $send_counter >= $import_max) { $finished = TRUE; break; } // don't process empty lines $line_filled = (count($data) == 1 && drupal_strlen($data[0]) == 0) ? FALSE : TRUE; if ($line_filled) { // check if this is first line - if so should we skip? if (!empty($settings['first_line_skip']) && $settings['processed'] == 0) { // reset to false on second process $first_line_skip = (empty($first_line_skip)) ? TRUE : FALSE; } if (!$first_line_skip) { unset($password, $errors, $fields); reset($field_match); foreach ($field_match as $column_id => $column_settings) { $type = $column_settings['type']; $field_id = $column_settings['field_id']; // Skip if this is a field used as part of a username but // not otherwise mapped for import. if ($type != 'username_part') { $fields[$type][$field_id] = module_invoke_all('user_import_data', $settings, $update_setting, $column_settings, $type, $field_id, $data, $column_id); } // Read in data if present for concatenating a user name. if ($column_settings['username'] > 0) { $username_data[$column_id] = $data[$column_id]; $username_order[$column_id] = $column_settings['username']; $username_abbreviate[$column_id]= $column_settings['abbreviate']; } } $errors = user_import_errors(); $account = array(); $existing_account = FALSE; $updated = FALSE; // if we update existing users matched by email (and therefore passed validation even if this email already exists) // look for and use an existing account. if ($update_setting && !empty($fields['user']['email'][0])) { $existing_account = user_load(array('mail' => $fields['user']['email'][0])); if ($existing_account) $account = (array) $existing_account; } // if $account['uid'] is not empty then we can assume the account is being updated $account_additions = module_invoke_all('user_import_pre_save', $settings, $account, $fields, $errors, $update_setting_per_module); foreach($account_additions as $field_name => $value) { $account[$field_name] = $value; } if (empty($errors)) { if ($settings['setting'] == 'import') { if ($existing_account) { $account = user_save($existing_account, $account); $updated = TRUE; } else { // Only set a user name if we are not updating an existing record. $account['name'] = _user_import_create_username($username_order, $username_data, $username_abbreviate, $settings['username_space']); $password = $account['pass']; $account = user_save('', $account); } module_invoke_all('user_import_after_save', $settings, $account, $password, $fields, $updated, $update_setting_per_module); $send_counter++; } $settings['valid']++; } $settings['processed']++; } $settings['pointer'] = ftell($handle); // save lines that have fatal errors if (!empty($errors)) _user_import_errors_display_save($settings['import_id'], $fields, $account['email'], $errors); } } fclose ($handle); if ($settings['setting'] == 'import' && !$finished) $settings['setting'] = 'imported'; if ($settings['setting'] == 'test') $settings['setting'] = 'tested'; _user_import_settings_update($settings['pointer'], $settings['processed'], $settings['valid'], $settings['setting'], $settings['import_id']); return $settings; } function user_import_usernames_invalid($delete = NULL) { $users = db_query("SELECT uid, name, mail from {users} WHERE uid != 0 AND uid != 1"); while ($user = db_fetch_object($users)) { $error = user_validate_name($user->name); if (!empty($error)) { $errors[$user->uid]['uid'] = $user->uid; $errors[$user->uid]['mail'] = $user->mail; $errors[$user->uid]['name'] = $user->name; $errors[$user->uid]['error'] = $error; if (!empty($delete)) { $form_state['values']['account'] = $user; /** * @todo check if this breaks - user_confirm_delete_submit() has changed substantially */ user_confirm_delete_submit($form, $form_state); } } } $output = theme('user_import_username_errors', $errors); return $output; } // move from one stage to the next // set up all necessary variables function _user_import_initialise_import($import) { switch ($import['setting']) { case 'imported': drupal_set_message(t('File has already been imported'), 'error'); break; // add setting template values to new import settings case 'file set': if (empty($import['template_id'])) return $import; $template = _user_import_settings_select($import['template_id']); $template['import_id'] = $import['import_id']; $template['filename'] = $import['filename']; $template['oldfilename'] = $import['oldfilename']; $template['filepath'] = $import['filepath']; $template['started'] = 0; $template['setting'] = 'file set'; return $template; case 'test': case 'tested': $import['setting'] = 'import'; $import['started'] = 0; $import['pointer'] = 0; $import['processed'] = 0; $import['valid'] = 0; _user_import_errors_display_delete($import['import_id']); _user_import_settings_save($import); _user_import_process($import); break; case 'template': unset($import['filename']); unset($import['oldfilename']); unset($import['filepath']); $import['started'] = 0; $import['pointer'] = 0; $import['processed'] = 0; $import['valid'] = 0; _user_import_settings_save($import); break; default: _user_import_process($import); drupal_set_message(t('Imported')); break; } return; } function _user_import_errors_display_delete($import_id) { db_query("DELETE FROM {user_import_errors} WHERE import_id = %d", $import_id); return; } // delete incomplete import settings, where only the file has been uploaded function _user_import_incomplete_deletion() { $results = db_query("SELECT * FROM {user_import} WHERE setting = 'file set'"); while ($import = db_fetch_object($results)) { $options = unserialize($import->options); _user_import_file_deletion($import->filepath, $import->filename, $import->oldfilename, $options['ftp'], FALSE); _user_import_settings_deletion($import->import_id); } return; } function user_import_profile_string($prefix = ',') { if (!module_exists('profile')) return; $profile_fields = _user_import_profile('fid', 'name'); if (!empty($profile_fields)) $profile_string = ', !' . implode($prefix .' !', $profile_fields); return $profile_string; } function user_import_profile_load($user) { $result = db_query('SELECT f.name, f.type, f.fid, v.value FROM {profile_fields} f INNER JOIN {profile_values} v ON f.fid = v.fid WHERE uid = %d', $user->uid); while ($field = db_fetch_object($result)) { if (empty($profile[$field->fid])) { $profile[$field->fid] = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value; } } return $profile; } function _user_import_unconcatenate_field_match($settings) { $settings_updated = array(); foreach ($settings as $column_id => $values) { if (!empty($values['field_match']) || !empty($values['username'])) { // If we have a username but no field_match, set a special type. // This allows us to skip saving the field but still use it in // concatenating a username value. if (empty($values['field_match'])) { $values['type'] = 'username_part'; $values['field_id'] = 'username_part_'. $column_id; } else { $key_parts = explode('-', $values['field_match']); $values['type'] = array_shift($key_parts); $values['field_id'] = implode('-', $key_parts); } unset($values['field_match']); $settings_updated[$column_id] = $values; } } return $settings_updated; } /** * Loads the hooks for the supported modules. */ function user_import_load_supported() { static $loaded = FALSE; if (!$loaded) { $path = drupal_get_path('module', 'user_import') . '/supported'; $files = drupal_system_listing('.*\.inc$', $path, 'name', 0); foreach ($files as $module_name => $file) { if (module_exists($module_name)) { include_once($file->filename); } } $loaded = TRUE; } } /** * Implementation of hook_simpletest(). */ function user_import_simpletest() { $module_name = 'user_import'; $dir = drupal_get_path('module', $module_name). '/tests'; $tests = file_scan_directory($dir, '\.test$'); return array_keys($tests); }