Skip to content

Example Workflow

Dickson Law edited this page Apr 2, 2018 · 1 revision

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

Step 0: Creating the Plugin

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

Step 1: Adding the Upload Form

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.

Step 2: Adding the Upload Form's Processing

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.

Step 3: Add the Waiting Screen

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).

Step 4: Add the Finished Screen

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.