Drupal Batch CSV Importing Template

This is copied from https://github.com/xurizaemon/csvimport/blob/master/csvimport.module

Author is Chris Burgess

Thanks to him!

<?php
 
/**
 * Demonstration module.
 *
 * - Provide form for upload of a CSV file.
 * - On submission, trigger a batch task which iterates through each row in the file.
 */
 
/**
 * Implement hook_menu()
 */
function csvimport_menu() {
  $items['admin/content/csv_import'] = array(
    'title' => 'Import CSV',
    'description' => 'Import content from a <abbr title="Comma Separated Values">CSV</abbr> or <abbr title="Tab Separated Values">TSV</abbr> file.',
    'access callback' => 'user_access',
    'access arguments' => array('access site administration'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('csvimport_form'),
//  'file' => 'csvimport.admin.inc',
  );
  return $items ;
}
 
/**
 * Build a form to upload CSV to.
 */
function csvimport_form() {
  $form['#attributes'] = array(
    'enctype' => 'multipart/form-data'
  );
  $form['csvfile'] = array( 
    '#title' => t('CSV File'),
    '#type'  => 'file',
    '#description' => ($max_size = parse_size(ini_get('upload_max_filesize'))) ? t('Due to server restrictions, the <strong>maximum upload file size is !max_size</strong>. Files that exceed this size will be disregarded.', array('!max_size' => format_size($max_size))) : '',    
  ) ;
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Commence Import'),
  ) ;
  $form['#validate'] = array(
    'csvimport_validate_fileupload',
    'csvimport_form_validate',
  ) ;
  return $form ;
}
 
/**
 * Validate the file upload. It must be a CSV, and we must
 * successfully save it to our import directory.
 */
function csvimport_validate_fileupload(&$form, &$form_state) {
  $validators = array(
    'file_validate_extensions' => array( 'csv' ),
  ) ;
  if ( $file = file_save_upload('csvfile', $validators, file_directory_path()) ) {
    // The file was saved using file_save_upload() and was added to
    // the files table as a temporary file. We'll make a copy and let
    // the garbage collector delete the original upload.
    $directory = file_create_path(variable_get('csvimport','csvimport')) ;
    if ( file_check_directory($directory, FILE_CREATE_DIRECTORY) ) {
      $destination = $directory .'/' . $file->filename;
      if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
        $form_state['values']['csvupload'] = $destination;
      }
      else {
        form_set_error('csvimport', t('Unable to copy upload file to !dest', array('!dest' => $destination)));
      }      
    }
  }
}
 
/**
 * Validate the upload. Ensure that the CSV looks something like we
 * expect it to.
 */
function csvimport_form_validate(&$form, &$form_state) {
  if ( isset( $form_state['values']['csvupload'] ) ) {
    if ( $handle = fopen($form_state['values']['csvupload'], 'r') ) {
      $line_count = 1 ;
      $first = TRUE ;
      if ( $line = fgetcsv($handle, 4096) ) {
        /**
         * Validate the uploaded CSV here. 
         *
         * The example CSV happens to have cell A1 ($line[0]) as
         * below; we validate it only.
         *
         * You'll probably want to check several headers, eg:
         *   if ( $line[0] == 'Index' || $line[1] != 'Supplier' || $line[2] != 'Title' ) 
         */
        // if ( $line[0] != 'Example CSV for csvimport.module - http://github.com/xurizaemon/csvimport' ) {
        //   form_set_error('csvfile', t('Sorry, this file does not match the expected format.')) ;
        // }
      }
      fclose($handle);
    }
    else {
      form_set_error('csvfile', t('Unable to read uploaded file !filepath', array('!filepath' => $form_state['values']['csvupload'])));
    }
  }
}
 
/**
 * Handle form submission. Read the CSV into a set of batch operations
 * and fire them off.
 */
function csvimport_form_submit(&$form, &$form_state) {
  $batch = array(
    'title' => t('Importing CSV ...'),
    'operations' => array(),
    'init_message' => t('Commencing'),
    'progress_message' => t('Processed @current out of @total.'),
    'error_message' => t('An error occurred during processing'),
    'finished' => 'csvimport_import_finished',
  ) ;
  if ( isset( $form_state['values']['csvupload'] ) ) {
    if ( $handle = fopen($form_state['values']['csvupload'], 'r') ) {
      $batch['operations'][] = array('_csvimport_remember_filename', array( $form_state['values']['csvupload'] ) ) ;
      $line_count = 1 ;
      $first = TRUE ;
      $line = fgetcsv($handle, 4096);
      while ( $line = fgetcsv($handle, 4096) ) {
        /**
         * we use base64_encode to ensure we don't overload the batch
         * processor by stuffing complex objects into it
         */
        $batch['operations'][] = array('_csvimport_import_line', array(array_map('base64_encode', $line)));        
      }
      fclose($handle);
    } // we caught this in csvimport_form_validate()
  } // we caught this in csvimport_form_validate()
  batch_set($batch);
}
 
/**
 * Handle batch completion.
 */
function csvimport_import_finished($success, $results, $operations) {
  if ( !empty($results['failed_rows']) ) {
    $dir  = file_directory_path() . '/csvimport/' ;
    if ( file_check_directory( $dir, FILE_CREATE_DIRECTORY ) ) {
      $csv_filename = 'failed_rows-'. basename($results['uploaded_filename']); // we validated extension on upload
      $csv_filepath = $dir .'/'. $csv_filename;
      $targs = array(
        '!csv_url' => l(check_plain($csv_filename), $csv_filepath),
        '%csv_filename' => $csv_filename,
        '%csv_filepath' => $csv_filepath,
      ) ;
      if ( $handle = fopen($csv_filepath, 'w+') ) {
        foreach( $results['failed_rows'] as $failed_row ) {
          fputcsv($handle, $failed_row);
        }
        fclose($handle);
        drupal_set_message(t('Some rows failed to import. You may download a CSV of these rows: !csv_url', $targs), 'error');
      }
      else {
        drupal_set_message(t('Some rows failed to import, but unable to write error CSV to %csv_filepath', $targs), 'error');
      }
    }
    else {
      drupal_set_message(t('Some rows failed to import, but unable to create directory for error CSV at %csv_directory', $targs), 'error');
    }
  }
  return t('The CSV import has completed.');
}
 
/**
 * Remember the uploaded CSV filename
 *
 * @TODO is there a better way to pass a value from inception of the
 * batch to the finished function?
 */
function _csvimport_remember_filename($filename, &$context) {
  $context['results']['uploaded_filename'] = $filename ;
}
 
/**
 * Process a single line.
 */
function _csvimport_import_line($line, &$context) {
  $context['results']['rows_imported']++;
  $line = $cleaned_line = array_map('base64_decode', $line);
 
  /**
   * Simply show the import row count.
   */
  $context['message'] = t('Importing row !c', array( '!c' => $context['results']['rows_imported'] ));
 
  /**
   * Alternatively, our example CSV happens to have the title in the
   * third column, so we can uncomment this line to display "Importing
   * Blahblah" as each row is parsed. 
   *
   * You can comment out the line above if you uncomment this one.
   */
  $context['message'] = t('Importing %title', array('%title' => $line[2]));
 
  /**
   * In order to slow importing and debug better, we can uncomment
   * this line to make each import slightly slower.
   */
  usleep(2500);
 
  /**
   * If the first two columns in the row are "ROW", "FAILS" then we
   * will add that row to the CSV we'll return to the importing person
   * after the import completes.
   */
  if ( $line[1] == 'ROW' && $line[2] == 'FAILS' ) {
    $context['results']['failed_rows'][] = $line ;
  }
}