Example Workflow
Here is an example for importing a CSV file consisting of user display names, user names, roles, emails and passwords.
Example File
"Alice Anderson",aander,contributor,aanderson@example.com,3ijn2Rso
"Bob Brown",bbrown,contributor,bbrown@example.com,fA092Ja
"Caitlyn Cortland",ccortl,contributor,ccortland@example.com,m2oPi37
Start by filling in the plugin.ini
file. Make sure that BatchUpload
is cited as a dependency, so that it loads first and allows you to safely inherit from BatchUpload_Application_AbstractWizard
:
[info]
name = "Batch Uploader Workflow for Users"
author = "University of Toronto Libraries"
description = "Batch Uploader plugin for creating users from CSV input"
version = "1.0.0"
license = "Apache Licence 2.0"
link = "http://www.github.com/utlib/BatchUploadUsers"
support_link = "http://www.github.com/utlib/BatchUploadUsers/issues"
omeka_minimum_version = "2.4"
omeka_target_version = "2.5"
required_plugins = "BatchUpload"
Then create the main plugin class file BatchUploadUsersPlugin.php
. Attach the initialize
hook and the batch_upload_register_job_type
filter as shown:
class BatchUploadUsersPlugin extends Omeka_Plugin_AbstractPlugin
{
protected $_hooks = array(
'initialize',
);
protected $_filters = array(
'batch_upload_register_job_type',
);
protected $_wizards = array(
'add_users',
);
public function hookInitialize()
{
foreach ($this->_wizards as $supported_job_type)
{
$klass = "BatchUploadUsers_Wizard_" . Inflector::camelize($supported_job_type);
$wizard = new $klass();
$args = $wizard->integrate();
}
}
public function filterBatchUploadRegisterJobType($args)
{
foreach ($this->_wizards as $supported_job_type)
{
$klass = "BatchUploadUsers_Wizard_" . Inflector::camelize($supported_job_type);
$wizard = new $klass();
$args = $wizard->addType($args);
}
return $args;
}
}
Create the file libraries/BatchUploadUsers/Form/AddUsersCsv.php
and all directory layers in between. Add a form with a single file field in it as follows:
class BatchUploadUsers_Form_AddUsersCsv extends Omeka_Form
{
public function init()
{
parent::init();
$this->applyOmekaStyles();
$this->setAutoApplyOmekaStyles(false);
$this->setAttrib('id', 'add-users-csv');
$this->setAttrib('method', 'POST');
$this->setAttrib('enctype', 'multipart/form-data');
$this->addElement('file', 'csvdata', array(
'label' => __("CSV Data Source"),
'description' => __("The CSV File containing display name, user name, role, email and password in that order on each row."),
'required' => true,
));
}
}
Create the file libraries/BatchUploadUsers/Wizard/AddUsers.php
and all directory layers in between. Add the step1Form($args)
method and its associated class in the file as follows:
class BatchUploadUsers_Wizard_AddUsers extends BatchUpload_Application_AbstractWizard
{
public $job_type = "add_users";
public $job_type_description = "Import Users from CSV";
public $steps = 3;
public function step1Form($args)
{
$form = new BatchUploadUsers_Form_AddUsersCsv();
$partialAssigns = $args['partial_assigns'];
$partialAssigns->set('form', $form);
$partialAssigns->set('page_title', __("Upload CSV"));
}
}
This passes a form and the title "Upload CSV" to the form view.
Create the file views/shared/batch_upload_forms/add_users/1.php
and all directory layers in between. Add view code to it as follows:
<script>
jQuery(function() {
jQuery('#next-step').click(function() {
jQuery('#add-users-csv').submit();
});
});
</script>
<section class="seven columns alpha">
<?php
echo $form;
?>
</section>
<section class="three columns omega">
<div id="save" class="panel">
<a id="next-step" class="big green button"><?php echo __("Next Step"); ?></a>
<a href="<?php echo html_escape(admin_url(array('controller' => 'jobs', 'id' => $batch_upload_job->id, 'action' => 'delete-confirm'), 'batchupload_id')); ?>" class="big red button delete-confirm"><?php echo __("Cancel Job"); ?></a>
</div>
</section>
This creates a view similar to others in the Omeka admin interface.
To prevent the batch job from blocking the server, the CSV processing and creation of users will be done in a background process. In Omeka, this is done by inheriting off Omeka_Job_AbstractJob
. Create the file libraries/BatchUploadUsers/Job/AddUsers.php
and implement the following parsing logic:
class BatchUploadUsers_Job_AddUsers extends Omeka_Job_AbstractJob
{
private $_jobId;
public function __construct(array $options)
{
parent::__construct($options);
$this->_jobId = $options['jobId'];
}
public function perform()
{
// Start up
$db = get_db();
// Get the batch upload job
debug("Get upload job");
$job = $db->getTable('BatchUpload_Job')->find($this->_jobId);
// Get the raw CSV string
$jobData = $job->getJsonData();
$csvStr = $jobData['data'];
// Parse the CSV string
debug("Start parsing");
$csvStrs = explode((strpos($csvStr, "\r\n") !== false) ? "\r\n" : "\n", $csvStr);
$csvData = array();
$failedRows = 0;
$allowedRoleValues = array('superuser', 'admin', 'contributor', 'researcher');
foreach ($csvStrs as $csvStr)
{
debug($csvStr);
if (version_compare(PHP_VERSION, '5.5.4', '<'))
{
$row = str_getcsv($csvStr, ',', '"');
}
else
{
$row = str_getcsv($csvStr, ',', '"', "\0");
}
if (count($row) === 5 && !empty($row[1]) && in_array($row[2], $allowedRoleValues) && filter_var($row[3], FILTER_VALIDATE_EMAIL))
{
$csvData[] = $row;
}
else
{
$failedRows++;
}
}
// Create a user per data entry
debug("Got " . count($csvData) . " rows. Start adding users");
$successfulRows = 0;
foreach ($csvData as $csvDataRow)
{
try
{
$user = new User();
$user->active = true;
list($user->name, $user->username, $user->role, $user->email) = $csvDataRow;
$user->save();
$successfulRows++;
}
catch (Exception $ex)
{
debug("Exception when importing user: {$ex->getMessage()}");
$failedRows++;
}
}
// Report back number of users inserted/failed and bump step count
debug("Start reporting back");
$jobData['inserted_count'] = $successfulRows;
$jobData['failed_count'] = $failedRows;
$job->setJsonData($jobData);
$job->step++;
$job->save();
}
}
This background job takes a CSV string as an argument, splits its rows, then iteratively add a new user for each row. Upon completion, it reports back the number of successfully inserted users and failed rows in the upload job's data field, then increments the upload job's step count to report that it has finished.
To fire off this job, add the step1Process()
method to the BatchUploadUsers_Wizard_AddUsers
class:
public function step1Process($args)
{
$job = $args['job'];
$partialAssigns = $args['partial_assigns'];
$form = new BatchUploadUsers_Form_AddUsersCsv();
if ($form->isValid($args['post']))
{
$csvStr = file_get_contents($args['files']['csvdata']['tmp_name']);
try
{
if (!empty($csvStr))
{
$job->setJsonData(array(
'data' => $csvStr,
'inserted_count' => 0,
'failed_count' => 0,
));
$job->step++;
$job->save();
Zend_Registry::get('bootstrap')->getResource('jobs')->sendLongRunning('BatchUploadUsers_Job_AddUsers', array(
'jobId' => $job->id,
));
return;
}
}
catch (Exception $ex)
{
}
}
$partialAssigns->set('form', $form);
$partialAssigns->set('page_title', __("Upload CSV"));
}
This sets off the job with the uploaded CSV in string form and two fields for the job to report back the number of successful imports and failed rows.
Start by setting the title of the screen in a new method step2Form()
in the BatchUploadUsers_Wizard_AddUsers
class:
public function step2Form($args)
{
$partialAssigns = $args['partial_assigns'];
$partialAssigns->set('page_title', __("Processing..."));
$partialAssigns->set('job', $args['job']);
}
Now add the corresponding step view in views/shared/batch_upload_forms/add_users/2.php
:
<script>
jQuery(function() {
setInterval(function() {
jQuery.ajax({
url: <?php echo js_escape(admin_url(array('controller' => 'jobs', 'id' => $job->id, 'action' => 'lookup'), 'batchupload_id')); ?>,
method: 'GET',
success: function(data) {
if (data.step != 2 || data.finished) {
window.location.href = <?php echo js_escape(admin_url(array('controller' => 'jobs', 'id' => $job->id, 'action' => 'refresh'), 'batchupload_id')) ?>;
}
}
});
}, 2000);
});
</script>
<h2><?php echo __("Processing entries..."); ?></h2>
<progress style="width:100%;"></progress>
This simple waiting screen will poll the job status endpoint (/batch-uploader/lookup/:id
) until it sees a step that is no longer 2 (which stands for this waiting screen). When that happens, it redirects to the refreshing endpoint (batch-uploader/refresh/:id
), which will reload the form page (should be for 3 at this point).
Add a new method step3Form()
to the BatchUploadUsers_Wizard_AddUsers
class:
public function step3Form($args)
{
$job = $args['job'];
$jsonData = $job->getJsonData();
$partialAssigns = $args['partial_assigns'];
$partialAssigns->set('page_title', __("Job Completed"));
$partialAssigns->set('inserts', $jsonData['inserted_count']);
$partialAssigns->set('fails', $jsonData['failed_count']);
}
This retrieves the batch upload job along with the number of successful and failed rows, as well as setting the title of the page.
Finish by adding the corresponding step view in views/shared/batch_upload_forms/add_users/3.php
:
<p><?php echo __('Successfully imported %s.', __(plural('one user', '%s users', $inserts), $inserts)); ?></p>
<p>
<?php
switch ($fails) {
case 0: echo __("No CSV rows have failed or been skipped."); break;
case 1: echo __("1 CSV row has failed or been skipped."); break;
default: echo __("%s CSV rows have failed or been skipped.", $fails); break;
}
?>
</p>
<form method="POST">
<button class="green button"><?php echo __('Finish'); ?></button>
</form>
This shows how many CSV rows have succeeded/failed, and provides a button to close the job.