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 ;
}
}