Fragmented Thought

Bulk Uploading Redirects In Drupal 7

By

Published

Lance Gliser

Heads up! This content is more than six months old. Take some time to verify everything still works as expected.

Had a client today with a list of 135ish redirects that needed entered, and a decent enough budget for me to do more than shove them in. I put together an admin page that allows you to upload a csv to be processed containing an internal source, destination, and redirect type (status code). Of course, this does require Drupal (7), and the redirect module.

But, if you add the pieces of this below to any given site, you'll have a nice page in the redirects section that will allow you to upload an easy csv. Here's the description it produces:

Upload a file of source and destination redirects mappings to bulk create redirects.

File

Files must be less than 2 MB. <br />Allowed file extentions: csv. <br />My file contains a header row. <br />Column Descriptions

Column 1: Source <br />Note that any existing paths will be changed to the new target. Source paths must be entered WITHOUT leading slashes.

Column 2: Destination <br />Note that any existing paths will be changed to the new target. Internal destination should be entered WITHOUT leading slashes.

Column 3: HTTP Status Code Number <br />Your options are:

  • 300 Multiple Choices
  • 301 Moved Permanently
  • 302 Found
  • 303 See Other
  • 304 Not Modified
  • 305 Use Proxy
  • 307 Temporary Redirect
/** * Impelements hook_menu */ function custom_menu(){ $items = array(); if( module_exists('redirect') ){ $items['admin/config/search/redirect/upload'] = array( 'type' => MENU_LOCAL_TASK, 'title' => 'Upload', 'page callback' => '_custom_admin_redirect_upload', 'description' => 'Allows users to upload a bulk csv of redirects to be created.', 'access arguments' => array('administer redirects'), 'file' => 'custom.admin.inc', ); } return $items; } /** * Vendor Upload Page */ function _custom_admin_redirect_upload(){ $page = array(); $page['form'] = array( '#weight' => 5, 'definition' => drupal_get_form('custom_admin_redirect_upload_form'), ); return $page; } function custom_admin_redirect_upload_form($form_state){ $form = array(); $form['#attributes'] = array('enctype' => "multipart/form-data"); $form['directions'] = array( '#markup' => '<p>' . t('Upload a file of source and destination redirects mappings to bulk create redirects.') . '</p>' ); $form['csv'] = array( '#type' => 'file', '#title' => t('File'), '#size' => 40, '#description' => implode('<br />', array( t('Files must be less than !size.', array('!size' => '<strong>' . format_size( parse_size(file_upload_max_size()) ) . '</strong>') ), t('Allowed file extentions: !list.', array('!list' => '<strong>csv</strong>') ), )), ); $form['includes_header'] = array( '#type' => 'checkbox', '#default_value' => 1, '#title' => t('My file contains a header row.'), '#options' => array( 1 => t('Yes'), 0 => t('No'), ), ); $form['column_description'] = array( '#prefix' => '<h2>' . t('Column Descriptions') . '</h2>', ); $form['column_description']['columns'] = array( '#prefix' => '<dl>', '#suffix' => '</dl>', ); $form['column_description']['columns'][] = array( '#prefix' => '<dt>' . t('Column 1') . ': ' . t('Source') . '</dt>', '#markup' => '<dd>' . t( 'Note that any existing paths will be changed to the new target.' . ' Source paths must be entered WITHOUT leading slashes.' ) . '</dd>', ); $form['column_description']['columns'][] = array( '#prefix' => '<dt>' . t('Column 2') . ': ' . t('Destination') . '</dt>', '#markup' => '<dd>' . t( 'Note that any existing paths will be changed to the new target.' . ' Internal destination should be entered WITHOUT leading slashes.' ) . '</dd>', ); $form['column_description']['columns'][] = array( '#prefix' => '<dt>' . t('Column 3') . ': ' . t('HTTP Status Code Number') . '</dt>', '#markup' => '<p>' . t('Your options are:') . '</p>' . '<ul><li>' . implode('</li><li>', redirect_status_code_options()) . '</li></ul>', ); $form['import']['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), ); return $form; } function custom_admin_redirect_upload_form_validate($form, &$form_state){ // Validate file $validators = array( 'file_validate_name_length' => array(), 'file_validate_extensions' => array('csv'), ); $file = file_save_upload('csv', $validators); if (!$file){ form_set_error('upload', 'You must select a valid file to upload.'); } else { // Manually add the uploaded file to the $form_state $form_state['values']['csv']['title'] = $file->filename; $form_state['values']['csv']['file'] = $file; $form_state['values']['csv']['file']->filesize = $file->filesize; } } function custom_admin_redirect_upload_form_submit($form, &$form_state){ // Create a batch $parameters = array(); $batch = array( 'title' => t('Scanning Vendors'), 'operations' => array( array( '_custom_redirect_csv_batch', array( $parameters, array( 'path' => $form_state['values']['csv']['file']->uri, 'size' => $form_state['values']['csv']['file']->filesize, 'includes_header' => $form_state['values']['includes_header'], ), ), ), ), 'finished' => '_custom_redirect_csv_batch_finished', 'file' => drupal_get_path('module', 'custom') . '/custom.admin.inc', ); batch_set($batch); } /** * Batch callbacks */ /** * Foreground based csv batch * @param array $parameters * @param array $file_info * @param array $context */ function _custom_redirect_csv_batch($parameters, $file_info, &$context){ global $user; // open the file for reading $file_handle = fopen($file_info['path'], 'r'); if( empty($context['sandbox']['initialized']) ){ $context['results']['parameters'] = $parameters; $context['results']['total'] = 0; $context['results']['errors'] = array(); if( $file_info['includes_header'] ){ // get file line as csv to skip the header $csv_line = fgetcsv($file_handle); // retain current file pointer position $context['sandbox']['file_pointer_position'] = ftell($file_handle); } else { $context['sandbox']['file_pointer_position'] = 0; } $context['finished'] = .01; $context['message'] = t('Initialized'); $context['sandbox']['initialized'] = TRUE; if( variable_get('development', FALSE) ){ watchdog( 'custom' ,'Bulk redirect csv upload started by user %uid with %size file size.' ,array( '%uid' => $user->uid, '%size' => $file_info['size'], ) ,WATCHDOG_NOTICE ); } return; } // Jump to location in file fseek($file_handle, $context['sandbox']['file_pointer_position']); // loop through the file and stop at batch limit for ($i = 0; $i < 50; $i++) { // get file line as csv $csv_line = fgetcsv($file_handle); // retain current file pointer position $context['sandbox']['file_pointer_position'] = ftell($file_handle); if(!$csv_line){ break; } // Clean and name the variables for sanity $source = trim($csv_line[0]); $destination = trim($csv_line[1]); $http_status_code = trim($csv_line[2]); // Ensure the source is a valid path if( stripos($source, 'http') !== FALSE || substr($source, 0, 1) == '/' ){ $context['results']['errors'][] = t('The source path %value is not valid.', array('%value' => $source)); continue; } // Normalize the path. $destination = drupal_get_normal_path($destination); if (!valid_url($destination) && !valid_url($destination, TRUE) && $destination != '<front>' && $destination != '') { $context['results']['errors'][] = t('The redirect path %value is not valid.', array('%value' => $destination)); continue; } // Check if a redirect already exists somehow $redirect = redirect_load_by_source($source); if($redirect){ // Update $redirect->redirect = $destination; $redirect->status_code = $http_status_code; } else { // Create $redirect = new stdClass(); redirect_object_prepare($redirect); $redirect->source = $source; $redirect->redirect = $destination; $redirect->status_code = $http_status_code; } redirect_save($redirect); $context['results']['total']++; } // check for EOF if ( feof($file_handle) ) { // complete the batch process $context['finished'] = 1; $context['message'] = t('All redirects processed.'); } else { // Inform the batch engine that we are not finished, // and provide an estimation of the completion level we reached. $context['finished'] = $context['sandbox']['file_pointer_position'] / $file_info['size']; $context['message'] = t('Processed %number redirects.', array('%number' => $context['results']['total']) ); } fclose($file_handle); } /** * Foreground csv batch finished * @param boolean $success * @param array $results * @param array $operations */ function _custom_redirect_csv_batch_finished($success, $results, $operations){ global $user; if( variable_get('development', FALSE) ){ watchdog( 'custom' ,'Bulk redirect csv upload completed by user %user.' ,array( '%user' => $user->uid, ) ,WATCHDOG_NOTICE ); } // The 'success' parameter means no fatal PHP errors were detected. All // other error management should be handled using 'results'. if (!$success) { drupal_set_message( t('A fatal error occured during import. The batch stopped processing.'), 'error'); } else { drupal_set_message( t('Import complete. Total records processed: %total.', array( '%total' => $results['total'], ))); if( !empty($results['errors']) ){ drupal_set_message( t('The following %number occured during processing: !errors', array( '!errors' => '<ol><li>' . implode('</li><li>', $results['errors']) . '</li></ol>', '%number' => count($results['errors']), )), 'error'); } drupal_goto('admin/config/search/redirect'); } }