Browse Source

Merged Lacey's changes

E.Cannon 9 years ago
parent
commit
6a260f4cc8

+ 136 - 32
api/blast_ui.api.inc

@@ -90,6 +90,61 @@ function get_blast_database_options($type) {
   return $options;
 }
 
+/**
+ * Retrieve all the information for a blast job in a standardized node-like format.
+ *
+ * @param $job_id
+ *   The non-encoded tripal job_id.
+ * @retun
+ *   An object describing the blast job.
+ */
+function get_BLAST_job($job_id) {
+
+  $blastjob = db_query('SELECT * FROM blastjob WHERE job_id=:id', array(':id' => $job_id))->fetchObject();
+  $tripal_job = tripal_get_job($job_id);
+  
+  $job = new stdClass();
+  $job->job_id = $job_id;
+  $job->program = $blastjob->blast_program;
+  $job->options = unserialize($blastjob->options);
+  $job->date_submitted = $tripal_job->submit_date;
+  $job->date_started = $tripal_job->start_time;
+  $job->date_completed = $tripal_job->end_time;
+  
+  // TARGET BLAST DATABASE.
+  // If a provided blast database was used then load details.
+  if ($blastjob->target_blastdb ) {
+    $job->blastdb = get_blast_database(array('nid' => $blastjob->target_blastdb));
+  }
+  // Otherwise the user uploaded their own database so provide what information we can.
+  else {
+    $job->blastdb = new stdClass();
+    $job->blastdb->db_name = 'User Uploaded';
+    $job->blastdb->db_path = $blastjob->target_file;
+    $job->blastdb->linkout = new stdClass();
+    $job->blastdb->linkout->none = TRUE;
+    
+    if ($job->program == 'blastp' OR $job->program == 'tblastn') {
+      $job->blastdb->db_dbtype = 'protein';
+    }
+    else {
+      $job->blastdb->db_dbtype = 'nucleotide';
+    }
+  }
+  
+  // FILES.
+  $job->files = new stdClass();
+  $job->files->query = $blastjob->query_file;
+  $job->files->target = $blastjob->target_file;
+  $job->files->result = new stdClass();
+  $job->files->result->archive = $blastjob->result_filestub . '.asn';
+  $job->files->result->xml = $blastjob->result_filestub . '.xml';
+  $job->files->result->tsv = $blastjob->result_filestub . '.tsv';
+  $job->files->result->html = $blastjob->result_filestub . '.html';
+  
+  return $job;
+}
+
 /**
  * Run BLAST (should be called from the command-line)
  *
@@ -108,12 +163,10 @@ function get_blast_database_options($type) {
  */
 function run_BLAST_tripal_job($program, $query, $database, $output_filestub, $options, $job_id = NULL) {
 
-  $output_file = file_directory_temp() .  DIRECTORY_SEPARATOR . $output_filestub . '.blast.asn';
-  $output_dir = variable_get('file_public_path', conf_path() . '/files') 
-              . DIRECTORY_SEPARATOR . 'tripal' . DIRECTORY_SEPARATOR . 'tripal_blast';
-  $output_file_xml = $output_dir . DIRECTORY_SEPARATOR . $output_filestub . '.blast.xml';
-  $output_file_tsv = $output_dir . DIRECTORY_SEPARATOR . $output_filestub . '.blast.tsv';
-  $output_file_html = $output_dir . DIRECTORY_SEPARATOR . $output_filestub . '.blast.html';
+  $output_file = $output_filestub . '.asn';
+  $output_file_xml = $output_filestub . '.xml';
+  $output_file_tsv = $output_filestub . '.tsv';
+  $output_file_html = $output_filestub . '.html';
 
   print "\nExecuting $program\n\n";
   print "Query: $query\n";
@@ -287,7 +340,8 @@ function get_blastdb_linkout_regex($node, $options = array()) {
 /**
  * Return a list of recent blast jobs to be displayed to the user.
  *
- * NOTE: The calling function will be expected to do the rendering.
+ * @param $programs
+ *   An array of blast programs you want jobs to be displayed for (ie: blastn, blastx, tblastn, blastp)
  *
  * @return
  *   An array of recent jobs.
@@ -297,43 +351,30 @@ function get_recent_blast_jobs($programs = array()) {
   $filter_jobs = !empty($programs);
   
   // Retrieve any recent jobs from the session variable.
-  $sid = session_id();  
-  if (isset($_SESSION['all_jobs'][$sid])) {
+  if (isset($_SESSION['blast_jobs'])) {
 
     $jobs = array();
-    foreach ($_SESSION['all_jobs'][$sid] as $job_id => $job) {
+    foreach ($_SESSION['blast_jobs'] as $job_secret) {
       $add = TRUE;
       
+      $job_id = blast_ui_reveal_secret($job_secret);
+      $job = get_BLAST_job($job_id);
+      
       // @TODO: Check that the results are still available.
       // This is meant to replace the arbitrary only show jobs executed less than 48 hrs ago.
-      
+
       // Remove jobs from the list that are not of the correct program.
-      if ($filter_jobs AND !in_array($job['program'], $programs)) {
+      if ($filter_jobs AND !in_array($job->program, $programs)) {
         $add = FALSE;
       }
-      
+
       if ($add) {
 
-        // Format the query information for display.
-        // Easiest case: if there is only one query header then show it.
-        if (sizeof($job['query_defs']) == 1 AND isset($job['query_defs'][0])) {
-          $job['query_info'] = $job['query_defs'][0];
-        }
-        // If we have at least one header then show that along with the count of queries.
-        elseif (isset($job['query_defs'][0])) {
-          $job['query_info'] = sizeof($job['query_defs']) . ' queries including "' . $job['query_defs'][0] . '"';
-        }
-        // If they provided a sequence with no header.
-        elseif (empty($job['query_defs'])) {
-          $job['query_info'] = 'Unnamed Query';
-        }
-        // At the very least show the count of queries.
-        else {
-          $job['query_info'] = sizeof($job['query_defs']) . ' queries';
-        }
-      
-        $jobs[$job_id] = $job;
+        $job->query_summary = format_query_headers($job->files->query);
+
+        $jobs[] = $job;
       }
+
     }
       
     return $jobs;
@@ -343,6 +384,69 @@ function get_recent_blast_jobs($programs = array()) {
   }
 }
 
+/**
+ * Retrieve the number of recent jobs.
+ */
+function get_number_of_recent_jobs() {
+  if (isset($_SESSION['blast_jobs'])) {
+    return sizeof($_SESSION['blast_jobs']);
+  } 
+  return 0;
+}
+
+/** 
+ * Summarize a fasta file based on it's headers.
+ *
+ * @param $file
+ *   The full path to the FASTA file.
+ *
+ * @return
+ *   A string describing the number of sequences and often including the first query header.
+ */
+function format_query_headers($file) {
+
+  $headers = array();
+  exec('grep ">" '.$file, $headers);
+
+  // Easiest case: if there is only one query header then show it.
+  if (sizeof($headers) == 1 AND isset($headers[0])) {
+    return ltrim($headers[0], '>');
+  }
+  // If we have at least one header then show that along with the count of queries.
+  elseif (isset($headers[0])) {
+    return sizeof($headers) . ' queries including "' . ltrim($headers[0], '>') . '"';
+  }
+  // If they provided a sequence with no header.
+  elseif (empty($headers)) {
+    return 'Unnamed Query';
+  }
+  // At the very least show the count of queries.
+  else {
+    return sizeof($headers) . ' queries';
+  }
+
+}
+
+/** 
+ * Sort recent blast jobs by the date they were submitted. 
+ * Ascending is in order of submission.
+ *
+ * THIS FUNCTION SHOULD BY USED BY USORT().
+ */
+function sort_blast_jobs_by_date_submitted_asc($a, $b) {
+  return ($a->date_submitted - $b->date_submitted);
+}
+
+/** 
+ * Sort recent blast jobs by the date they were submitted. 
+ * Descending is most recent first.
+ *
+ * THIS FUNCTION SHOULD BY USED BY USORT().
+ */
+function sort_blast_jobs_by_date_submitted_desc($a, $b) {
+  return ($b->date_submitted - $a->date_submitted);
+}
+
 /**
  * Generate an image of HSPs for a given hit.
  *          

+ 113 - 1
blast_ui.install

@@ -26,6 +26,7 @@ function blast_ui_install() {
  */
 function blast_ui_schema(){
 
+  // A table ot keep extra information related to blastdb nodes.
   $schema['blastdb'] = array(
     'description' => t('The base table for blastdb node'),
     'fields' => array(
@@ -79,7 +80,58 @@ function blast_ui_schema(){
        'nid' => array('nid'),
     ),
   );
-
+  
+  // BLAST JOBS
+  // ------------------------
+  // Keeps track of additional information related to tripal blast jobs.
+  $schema['blastjob'] = array(
+    'description' => t('Keeps track of additional information related to tripal blast jobs.'),
+    'fields' => array(
+      'job_id' => array(
+        'description' => t('The Tripal job_id for the blast job.'),
+        'type' => 'int',
+        'unsigned' => true,
+        'not null' => true,
+      ),
+      'blast_program' => array(
+        'description' => t('The program to use to run the blast (ie: blastn, blastp, etc.).'),
+        'type' => 'varchar',
+        'length' => 20,
+        'not null' => true,
+      ),
+      'target_blastdb' => array(
+        'description' => t('The nid of the blastdb used to search against; NULL if target was uploaded.'),
+        'type' => 'int',
+        'unsigned' => true,
+      ),
+      'target_file' => array(
+        'description' => t('The absolute path to the uploaded blast database after it was run through makeblastdb; NULL if target was NOT uploaded.'),
+        'type' => 'text',
+      ),
+      'query_file' => array(
+        'description' => t('The absolute path to the query file.'),
+        'type' => 'text',
+      ),
+      'result_filestub' => array(
+        'description' => t('The absolute path and filename (without extension) of the blast results.'),
+        'type' => 'text',
+      ),
+      'options' => array(
+        'description' => t('A serialized array of options selected for the blast job where the key is the machine name of the option used when calling blast (ie: gapextend) and the value is the value of the option.'),
+        'type' => 'text',
+      ),
+    ),
+    'primary key' => array('job_id'),
+    'foreign keys' => array(
+      'job_id' => array(
+        'table' => 'tripal_jobs',
+        'columns' => array(
+          'job_id' => 'job_id',
+        ),
+      ),
+    ),
+  );
+  
   return $schema;
 }
 
@@ -136,3 +188,63 @@ function blast_ui_update_7102() {
   );
 
 }
+
+/**
+ * Add saving of blast job information for recent job list & resubmit functionality.
+ */
+function blast_ui_update_7103() {
+  $schema = array();
+
+  // Keeps track of additional information related to tripal blast jobs.
+  $schema['blastjob'] = array(
+    'description' => t('Keeps track of additional information related to tripal blast jobs.'),
+    'fields' => array(
+      'job_id' => array(
+        'description' => t('The Tripal job_id for the blast job.'),
+        'type' => 'int',
+        'unsigned' => true,
+        'not null' => true,
+      ),
+      'blast_program' => array(
+        'description' => t('The program to use to run the blast (ie: blastn, blastp, etc.).'),
+        'type' => 'varchar',
+        'length' => 20,
+        'not null' => true,
+      ),
+      'target_blastdb' => array(
+        'description' => t('The nid of the blastdb used to search against; NULL if target was uploaded.'),
+        'type' => 'int',
+        'unsigned' => true,
+      ),
+      'target_file' => array(
+        'description' => t('The absolute path to the uploaded blast database after it was run through makeblastdb; NULL if target was NOT uploaded.'),
+        'type' => 'text',
+      ),
+      'query_file' => array(
+        'description' => t('The absolute path to the query file.'),
+        'type' => 'text',
+      ),
+      'result_filestub' => array(
+        'description' => t('The absolute path and filename (without extension) of the blast results.'),
+        'type' => 'text',
+      ),
+      'options' => array(
+        'description' => t('A serialized array of options selected for the blast job where the key is the machine name of the option used when calling blast (ie: gapextend) and the value is the value of the option.'),
+        'type' => 'text',
+      ),
+    ),
+    'primary key' => array('job_id'),
+    'foreign keys' => array(
+      'job_id' => array(
+        'table' => 'tripal_jobs',
+        'columns' => array(
+          'job_id' => 'job_id',
+        ),
+      ),
+    ),
+  );
+  
+  // First create the tables.
+  db_create_table('blastjob', $schema['blastjob']);
+  
+}

+ 10 - 4
blast_ui.module

@@ -16,7 +16,6 @@ require_once 'includes/blast_ui.node.inc';
 
 // BLAST Link-out functionality.
 require_once 'includes/blast_ui.linkouts.inc';
-include_once('includes/blast_ui.custom.inc');
 
 // Functions specific to themeing (ie: preprocess)
 require_once 'theme/blast_ui.theme.inc';
@@ -169,7 +168,14 @@ function blast_ui_theme() {
     'variables' => array('hsps' => NULL),
     'path' => "$path/theme",
   );
-
+  
+  // Lists the recent blast jobs for a given user/session.
+  $items['blast_recent_jobs'] = array(
+    'template' => 'blast_recent_jobs',
+    'variables' => array('programs' => NULL),
+    'path' => "$path/theme",
+  );
+  
   // Module Help
   $items['blast_help'] = array(
     'template' => 'blast_help',
@@ -214,12 +220,12 @@ function blast_ui_help($path, $arg) {
  *  Return HTML output of the BLAST results to be displayed to the user
  *
  */
-function show_blast_output($job_id) {
+function show_blast_output($job_string) {
 
   // BLASTs are run as a Tripal job. As such we need to determine whether the current
   // BLAST is in the queue, running or complete in order to determine what to show the user
   //decode the job_id
-  $job_id = base64_decode($job_id);
+  $job_id = blast_ui_reveal_secret($job_string);
   $job = tripal_get_job($job_id);
 
   // 1) Job is in the Queue

+ 0 - 129
includes/blast_ui.custom.inc

@@ -1,129 +0,0 @@
-<?php
-/*
- * customized for PeanutBase
- *
- */
- 
-function tripal_custom_generate_linkout($url_prefix, $hit, $info, $options = array()) {
-// uncomment to see contents of hit object
-//echo "hit:<pre>";var_dump($hit);echo "</pre>";
-// uncomment to see contents of info object
-//echo "info:<pre>";var_dump($info);echo "</pre>";
-
-  $url = false;
-  $hit_name = $hit->{'Hit_def'};
-//echo "hit name: $hit_name ... ";
-
-  if ($info['Target'] == 'All genomes') {
-    // default regex
-    $regex = "/^\w+\.(\S+).*/";
-    
-    if (preg_match('/^aradu/', $hit_name) == 1) {
-      $blastdb_name = 'PeanutBase_aradu_gbrowse';
-    }
-    else if (preg_match('/^araip/', $hit_name) == 1) {
-      $blastdb_name = 'PeanutBase_araip_gbrowse';
-    }
-    else if (preg_match('/^cajca/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_cajca_gbrowse';
-    }
-    else if (preg_match('/^cicar/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_cicar_gbrowse';
-    }
-    else if (preg_match('/^glyma/', $hit_name) == 1) {
-      $blastdb_name = 'SoyBase_glyma_gbrowse';
-    }
-    else if (preg_match('/^lotja/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_lotja_gbrowse';
-    }
-    else if (preg_match('/^medtr/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_medtr_gbrowse';
-    }
-    else if (preg_match('/^phavu/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_phavu_gbrowse';
-    }
-    else if (preg_match('/^vigra/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_vigra_gbrowse';
-    }
-    else {
-      // Don't know what to do with this hit
-      drupal_set_message("Don't know how to create GBrowse linkout for $hit_name", 'error');
-    }  
-
-    $hit_name = preg_replace($regex, '$1', $hit_name);
-    $hit->{'linkout_id'} = $hit_name;
-    if ($blastdb_info = tripal_custom_getBLASTdbinfo($blastdb_name)) {
-//echo "generate gbrowse link with " . $blastdb_info['urlprefix'] . " $hit<br>";
-      $url = tripal_blast_generate_linkout_gbrowse($blastdb_info['urlprefix'], $hit, $info, $options);
-    }
-  }//handle All genomes BLAST target
-  
-  else {
-    // default regex:
-    $regex = "/^\w+\.(\S+)/";
-    
-    if (preg_match('/^aradu/', $hit_name) == 1) {
-      $blastdb_name = 'PeanutBase_aradu_gene';
-    }
-    else if (preg_match('/^araip/', $hit_name) == 1) {
-      $blastdb_name = 'PeanutBase_araip_gene';
-    }
-    else if (preg_match('/^cajca/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_cajca_gene';
-    }
-    else if (preg_match('/^cicar/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_cicar_gene';
-    }
-    else if (preg_match('/^glyma/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_glyma_gene';
-      if (preg_match("/^\w+\.(\S+)\.p/", $hit_name)) {
-        //glyma protein
-        $regex = "/^\w+\.(\S+)\.p/";
-      }
-    }
-    else if (preg_match('/^lotja/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_lotja_gene';
-    }
-    else if (preg_match('/^medtr/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_medtr_gene';
-    }
-    else if (preg_match('/^phavu/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_phavu_gene';
-    }
-    else if (preg_match('/^vigra/', $hit_name) == 1) {
-      $blastdb_name = 'LegumeInfo_vigra_gene';
-    }
-    else {
-      // Don't know what to do with this hit
-      drupal_set_message("Don't know how to create linkout for $hit_name", 'error');
-    }  
-
-    $hit_name = preg_match($regex, $hit_name, $matches);
-//echo "<br>Using $regex, found matches:<pre>" . var_dump($matches);echo "</pre>";
-//echo "<br>Use [" . $matches[1] . "]<br>";
-    $hit->{'linkout_id'} = $matches[1];
-//echo "look for gene link for $blastdb_name with " . $hit->{'linkout_id'} . "<br>";
-    if ($blastdb_info = tripal_custom_getBLASTdbinfo($blastdb_name)) {
-//echo "generate gbrowse link with " . $blastdb_info['urlprefix'] . " $hit<br>";
-      $url = tripal_blast_generate_linkout_link($blastdb_info['urlprefix'], $hit, $info, $options);
-    }
-  }
-  return $url;
-}
-
-
-function tripal_custom_getBLASTdbinfo($blastdb_name) {
-  $sql = "
-    SELECT urlprefix 
-    FROM {db} WHERE name='$blastdb_name'";
-//echo "$sql<br>";
-  if ($res = chado_query($sql)) {
-    if ($row=$res->fetchObject()) {
-      return array(
-        'urlprefix' => $row->urlprefix, 
-      );
-    }
-  }
-  
-  return false;
-}//tripal_custom_getBLASTdbinfo

+ 72 - 51
includes/blast_ui.form_advanced_options.inc

@@ -23,17 +23,17 @@
  *   The current state fo the form passed in as $form.
  */
 function blast_ui_blastn_advanced_options_form(&$form, $form_state) {
-  $all_job_data = variable_get('job_data', '');
-  if (isset($_GET['jid']) && isset($all_job_data)) {    
-     $jid = base64_decode($_GET['jid']);
-     $job_data = $all_job_data[$jid];
+  
+  // Edit and Resubmit functionality.
+  // We want to pull up the details from a previous blast and fill them in as defaults
+  // for this blast.
+  if (isset($form_state['prev_blast'])) {
+    $defaults = _get_default_values($form_state['prev_blast']->options, 'blastn');
   }
   else {
-    $job_data = array();
-    $jid = 0;
+    $defaults = _get_default_values(array(), 'blastn');
   }
-  $defaults = _get_default_values($job_data, 'blastn');
-
+  
   // General parameters
   //.........................
 
@@ -171,16 +171,16 @@ function blast_ui_blastn_advanced_options_form_submit($form, $form_state) {
  *   The current state fo the form passed in as $form.
  */
 function blast_ui_blastx_advanced_options_form(&$form, $form_state) {
-  $all_job_data = variable_get('job_data', '');
-  if (isset($_GET['jid']) && isset($all_job_data)) {    
-     $jid = base64_decode($_GET['jid']);
-     $job_data = $all_job_data[$jid];
+
+  // Edit and Resubmit functionality.
+  // We want to pull up the details from a previous blast and fill them in as defaults
+  // for this blast.
+  if (isset($form_state['prev_blast'])) {
+    $defaults = _get_default_values($form_state['prev_blast']->options, 'blastn');
   }
   else {
-    $job_data = array();
-    $jid = 0;
+    $defaults = _get_default_values(array(), 'blastx');
   }
-  $defaults = _get_default_values($job_data, 'blastx');
 
    $form['ALG']['GParam'] = array(
    '#type' => 'fieldset',
@@ -306,16 +306,16 @@ function blast_ui_blastx_advanced_options_form_submit($form, $form_state) {
  *   The current state fo the form passed in as $form.
  */
 function blast_ui_blastp_advanced_options_form(&$form, $form_state) {
-  $all_job_data = variable_get('job_data', '');
-  if (isset($_GET['jid']) && isset($all_job_data)) {    
-     $jid = base64_decode($_GET['jid']);
-     $job_data = $all_job_data[$jid];
+
+  // Edit and Resubmit functionality.
+  // We want to pull up the details from a previous blast and fill them in as defaults
+  // for this blast.
+  if (isset($form_state['prev_blast'])) {
+    $defaults = _get_default_values($form_state['prev_blast']->options, 'blastn');
   }
   else {
-    $job_data = array();
-    $jid = 0;
+    $defaults = _get_default_values(array(), 'blastp');
   }
-  $defaults = _get_default_values($job_data, 'blastp');
 
   //General parameters
 
@@ -968,16 +968,16 @@ function ajax_dependent_dropdown_callback($form, $form_state) {
  *   The current state fo the form passed in as $form.
  */
 function blast_ui_tblastn_advanced_options_form(&$form, $form_state) {
-  $all_job_data = variable_get('job_data', '');
-  if (isset($_GET['jid']) && isset($all_job_data)) {    
-     $jid = base64_decode($_GET['jid']);
-     $job_data = $all_job_data[$jid];
+
+  // Edit and Resubmit functionality.
+  // We want to pull up the details from a previous blast and fill them in as defaults
+  // for this blast.
+  if (isset($form_state['prev_blast'])) {
+    $defaults = _get_default_values($form_state['prev_blast']->options, 'blastn');
   }
   else {
-    $job_data = array();
-    $jid = 0;
+    $defaults = _get_default_values(array(), 'tblastn');
   }
-  $defaults = _get_default_values($job_data, 'tblastn');
 
   $form['ALG']['GParam'] = array(
    '#type' => 'fieldset',
@@ -1078,28 +1078,28 @@ function blast_ui_tblastn_advanced_options_form_submit($form, $form_state) {
 
 }
 
-/*
+/**
  * Get default form values; may come from saved job data if user is re-running
  *   a previous job.
  */
-function _get_default_values($job_data) {
+function _get_default_values($options) {
   // restore previous values or set to default
-  $max_target = (isset($job_data['options']['max_target_seqs'])) 
-  					? $job_data['options']['max_target_seqs'] : 10;
-  $short_queries = (isset($job_data['options']['shortQueries'])) 
-  					? $job_data['options']['shortQueries'] : true;
-  $evalue = (isset($job_data['options']['evalue'])) 
-  					? $job_data['options']['evalue'] : .001;
-  $word_size = (isset($job_data['options']['word_size'])) 
-  					? $job_data['options']['word_size'] : 11;
-  $qRange = (isset($job_data['options']['culling_limit'])) 
-  					? $job_data['options']['culling_limit'] : 0;
+  $max_target = (isset($options['max_target_seqs'])) 
+  					? $options['max_target_seqs'] : 10;
+  $short_queries = (isset($options['shortQueries'])) 
+  					? $options['shortQueries'] : true;
+  $evalue = (isset($options['evalue'])) 
+  					? $options['evalue'] : .001;
+  $word_size = (isset($options['word_size'])) 
+  					? $options['word_size'] : 11;
+  $qRange = (isset($options['culling_limit'])) 
+  					? $options['culling_limit'] : 0;
 
   $matchmiss = 0;
-  $reward = (isset($job_data['options']['reward'])) 
-  					? $job_data['options']['reward'] : 1;
-  $penalty = (isset($job_data['options']['penalty'])) 
-  					? $job_data['options']['penalty'] : -2;
+  $reward = (isset($options['reward'])) 
+  					? $options['reward'] : 1;
+  $penalty = (isset($options['penalty'])) 
+  					? $options['penalty'] : -2;
   if ($reward == 1) {
   	switch ($penalty) {
   		case -1: $matchmiss = 5; break;
@@ -1119,10 +1119,10 @@ function _get_default_values($job_data) {
   }
   
   $gap = 0;
-  $gapopen = (isset($job_data['options']['gapopen'])) 
-  					? $job_data['options']['gapopen'] : 5;
-  $gapextend = (isset($job_data['options']['gapextend'])) 
-  					? $job_data['options']['gapextend'] : 2;
+  $gapopen = (isset($options['gapopen'])) 
+  					? $options['gapopen'] : 5;
+  $gapextend = (isset($options['gapextend'])) 
+  					? $options['gapextend'] : 2;
   if ($gapextend == 2) {
   	switch ($gapopen) {
   		case 5: $gap = 0; break;
@@ -1142,8 +1142,8 @@ function _get_default_values($job_data) {
 // eksc- need to implement query range limit
 //  $q_range = 0;
   
-  $matrix = (isset($job_data['options']['matrix'])) 
-  					? $job_data['options']['matrix'] : 'PAM30';
+  $matrix = (isset($options['matrix'])) 
+  					? $options['matrix'] : 'PAM30';
   return array(
   	'max_target_seqs' => $max_target,
   	'short_queries'   => $short_queries,
@@ -1156,6 +1156,12 @@ function _get_default_values($job_data) {
   );
 }//_get_default_values
 
+/**
+ * Get a list of options for the max_target_seq blast option.
+ *
+ * The options are the same for all programs
+ * and describe the maximum number of aligned sequences to keep.
+ */
 function _get_max_target($which) {
 	switch ($which) {
 	  case 'blastn':
@@ -1176,6 +1182,9 @@ function _get_max_target($which) {
 	}//switch
 }
 
+/**
+ * Get a list of options for work size.
+ */
 function _get_word_size($which) {
 	switch ($which) {
 		case 'blastn':
@@ -1204,6 +1213,9 @@ function _get_word_size($which) {
 	}//switch
 }
 
+/**
+ * Get a list of options for match/mismatch ratio.
+ */
 function _get_match_mismatch($which) {
 	switch ($which) {
 		case 'blastn':
@@ -1218,6 +1230,9 @@ function _get_match_mismatch($which) {
   }//switch
 }
 
+/**
+ * Get a list of options for gaps.
+ */
 function _get_gap($which) {
 	switch ($which) {
 		case 'blastn':
@@ -1233,6 +1248,9 @@ function _get_gap($which) {
    }//switch
 }
 
+/**
+ * Translate above gap options into blast gap open and extend costs.
+ */
 function _set_gap($gap_key) {
  switch ($gap_key) {
    case 0:
@@ -1268,6 +1286,9 @@ function _set_gap($gap_key) {
   return array('gapOpen' => $gapOpen, 'gapExtend' => $gapExtend);
 }
 
+/**
+ * Translate mismatch/match ratio option into blast penalty/reward options.
+ */
 function _set_match_mismatch($m_m) {
   switch ($m_m) {
    case 0:

+ 141 - 125
includes/blast_ui.form_per_program.inc

@@ -15,7 +15,7 @@
  */
 function blast_ui_per_blast_program_form($form, $form_state) {
 
-  //  CSS support to the form
+  // CSS support to the form
   $form['#attached']['css'] = array(
     drupal_get_path('module', 'blast_ui') . '/theme/css/form.css',
   );
@@ -23,16 +23,44 @@ function blast_ui_per_blast_program_form($form, $form_state) {
   // We are going to lay out this form as two choices: either look at a recent blast
   // or execute a new one. We want to theme accordingly so set a class to aid us in such.
   $form['#attributes'] = array('class' => array('blast-choice-form'));
-  
-  // @deepaksomanadh - Code added for edit and resubmit funcitonality
-  //   Approach: persist the form data and read it back using JobID
-  $job_data = variable_get('job_data', '');
-  if (isset($_GET['jid']) && isset($job_data)) {    
-    $jid = base64_decode($_GET['jid']);
-  }  
-  else {
-    $job_data = array();
-    $jid = 0;
+
+  // Determine some defaults.
+  $defaults = array(
+    'FASTA' => NULL,
+    'SELECT_DB' => NULL,
+  );
+    
+  // Edit and Resubmit functionality.
+  // We want to pull up the details from a previous blast and fill them in as defaults
+  // for this blast.
+  // @todo: handle file uploads better; currently for the query we put the file contents
+  // in the text area causing reupload and we simply do not support re-using of an uploaded target.
+  if (isset($_GET['resubmit'])) {    
+    $prev_blast = get_BLAST_job(blast_ui_reveal_secret($_GET['resubmit']));
+    
+    // First of all warn if the uploaded their search target last time 
+    // since we don't support that now.
+    if (!isset($prev_blast->blastdb->nid)) {
+      drupal_set_message('You will need to re-upload your <em>Search Target</em> database.','warning');
+    }
+    // Andi f they didn't upload a target then set a default for the select list.
+    else {
+      $defaults['SELECT_DB'] = $prev_blast->blastdb->nid;
+    }
+    
+    // Finally set a default for the query. Since we don't support defaults for file uploads,
+    // we need to get the contents of the file and put them in our textarea.
+    if (is_readable($prev_blast->files->query)) {
+      $defaults['FASTA'] = file_get_contents($prev_blast->files->query);
+    }
+    // There should always be a query file (both if uploaded or not) so if we cant find it
+    // then it must have been cleaned up :-( -- warn the user.
+    else {
+      drupal_set_message('Unable to retrieve previous query sequence; please re-upload it.', 'error');
+    }
+    
+    // Finally save the previous blast details for use by the advanced option forms.
+    $form_state['prev_blast'] = $prev_blast;
   }
 
   // Determine the BLAST program.
@@ -80,12 +108,9 @@ function blast_ui_per_blast_program_form($form, $form_state) {
   );
 
   // CHOOSE RECENT BLAST RESULTS
-  //-----------------------------------
-  // Gets the list of recent jobs filtered to the current blast program (ie: blastn).
-  $recent_jobs = get_recent_blast_jobs(array($blast_program));
-  
+  //-----------------------------------  
   // If there are recent jobs then show a table of them.
-  if ($recent_jobs) {
+  if (get_number_of_recent_jobs()) {
 
     $form['A'] = array(
       '#type' => 'fieldset',
@@ -94,28 +119,10 @@ function blast_ui_per_blast_program_form($form, $form_state) {
       '#collapsible' => TRUE,
       '#collapsed' => TRUE
     );
-    
-    $table = array(
-      'header' => array('Query Information', 'Search Target', 'Date Requested', ''),
-      'rows' => array(),
-      'attributes' => array('class' => array('tripal-blast', 'recent-jobs')),
-      'sticky' => TRUE,
-    );
-      
-    foreach ($recent_jobs as $job) {
-
-      // Define a row for the current job.
-      $table['rows'][] = array(
-        $job['query_info'],
-        $job['target'],
-        $job['date'],
-        l('See Results', $job['job_output_url'])
-      );
-    }
 
     $form['A']['job_table'] = array(
       '#type' => 'markup',
-      '#markup' => theme('table', $table),
+      '#markup' => theme('blast_recent_jobs', array($blast_program)),
     );
   }
 
@@ -165,7 +172,7 @@ function blast_ui_per_blast_program_form($form, $form_state) {
     '#type' => 'textarea',
     '#title' => t('Enter FASTA sequence(s)'),
     '#description'=>t('Enter query sequence(s) in the text area.'),
-    '#default_value' => isset($job_data[$jid]['fasta']) ? $job_data[$jid]['fasta'] : '',
+    '#default_value' => $defaults['FASTA'],
     '#prefix' => '<div id="fasta-textarea">',
     '#suffix' => '</div>',
   );
@@ -216,7 +223,7 @@ function blast_ui_per_blast_program_form($form, $form_state) {
     '#type' => 'select',
     '#title' => t('%type BLAST Databases:', array('%type' => ucfirst($db_type))),
     '#options' => $options,
-    '#default_value' => isset($job_data[$jid]['db_option']) ? $job_data[$jid]['db_option'] : 0,
+    '#default_value' => $defaults['SELECT_DB'],
   );
 
   if (variable_get('blast_ui_allow_target_upload', FALSE)) {
@@ -386,19 +393,38 @@ function blast_ui_per_blast_program_form_submit($form, &$form_state) {
     $mdb_type = 'prot';
   }
 
-  // If the query was submitted via the texrfield then create a file containing it
+  // We want to save information about the blast job to the database for recent jobs &
+  // edit and resubmit functionality.
+  // First set defaults.
+  $blastjob = array(
+    'job_id' => NULL, 
+    'blast_program' => $form_state['values']['blast_program'],
+    'target_blastdb' => (isset($form_state['values']['SELECT_DB'])) ? $form_state['values']['SELECT_DB'] : NULL,
+    'target_file' => NULL,
+    'query_file' => NULL,
+    'result_filestub' => NULL,
+    'options' => serialize(array())
+  );
+  
+  // QUERY
+  //-------------------------
+  // BLAST can only take the query as a file;
+  // therefore, if it was submitted via the textfield we need to create a file containing
+  // the submitted sequence.
   if (isset($form_state['qFlag'])) {
     if ($form_state['qFlag'] == 'seqQuery') {
       $seq_content = $form_state['values']['FASTA'];
-      $query = '/tmp/' . date('YMd_His') . '_query.fasta';
-      file_put_contents ($query , $seq_content);
+      $blastjob['query_file'] = '/tmp/' . date('YMd_His') . '_query.fasta';
+      file_put_contents ($blastjob['query_file'], $seq_content);
     }
     elseif ($form_state['qFlag'] == 'upQuery') {
-      $query = $form_state['upQuery_path'];
+      $blastjob['query_file'] = $form_state['upQuery_path'];
     }
   }
 
-  // If the BLAST database was uploaded then use it to run the BLAST
+  // TARGET
+  //-------------------------
+  // If the BLAST database was uploaded then we need to format it to make it compatible with blast.
   if ($form_state['dbFlag'] == 'upDB') {
 
     // Since we only support using the -db flag (not -subject) we need to create a
@@ -421,8 +447,8 @@ your sequence headers include pipes (i.e.: | ) they adhere to '
 
       $error = TRUE;
     }
-  }//upload target db
-  
+    
+  }
   // Otherwise, we are using one of the website provided BLAST databases so form the
   // BLAST command accordingly
   elseif ($form_state['dbFlag'] == 'blastdb') {
@@ -431,21 +457,10 @@ your sequence headers include pipes (i.e.: | ) they adhere to '
     $blastdb_name = $blastdb_node->db_name;
     $blastdb_with_path = $blastdb_node->db_path;
   }
+  
+  $blastjob['target_file'] = $blastdb_with_path;
 
-  // Now let each program process its own advanced options.
-  $advanced_options = array();
-  $advanced_options_form_submit = 'blast_ui_' . $blast_program . '_advanced_options_form_submit';
-  if (function_exists($advanced_options_form_submit)) {
-    $advanced_options = call_user_func_array(
-      $advanced_options_form_submit,
-      array($form['B'], $form_state)
-    );
-  }
-  else {
-  	$advanced_options = array('none' => 0);
-  }
-
-  // Set path to a BLAST target file to check for its existence
+  // Determine the path to the blast database with extension.
   if ($mdb_type == 'nucl' && (preg_match('/\.[pn]al/', $blastdb_with_path) == 0)) {  
     // Suffix may be .nsq or .nal
     if (is_readable("$blastdb_with_path.nsq")) {
@@ -466,98 +481,99 @@ your sequence headers include pipes (i.e.: | ) they adhere to '
   }
   else {
     $blastdb_with_suffix = $blastdb_with_path;
-  }    
+  }
+  
+  if (!is_readable($blastdb_with_suffix)) {
+    $error = TRUE;
+    
+    $dbfile_uploaded_msg = ($form_state['dbFlag'] == 'upDB') 
+        ? 'The BLAST database was submitted via user upload.' 
+        : 'Existing BLAST Database was chosen.';
+    tripal_report_error(
+      'blast_ui',
+      TRIPAL_ERROR,
+      "BLAST database %db unaccessible. %msg",
+      array('%db' => $blastdb_with_path, '%msg' => $dbfile_uploaded_msg)
+    );
+    $msg = "$dbfile_uploaded_msg BLAST database '$blastdb_with_path' is unaccessible. ";
+    $msg .= "Please contact the site administrator.";
+    drupal_set_message($msg, 'error');
+  }
+  
+  // ADVANCED OPTIONS
+  //-------------------------
+  // Now let each program process its own advanced options.
+  $advanced_options = array();
+  $advanced_options_form_submit = 'blast_ui_' . $blast_program . '_advanced_options_form_submit';
+  if (function_exists($advanced_options_form_submit)) {
+    $advanced_options = call_user_func_array(
+      $advanced_options_form_submit,
+      array($form['B'], $form_state)
+    );
+  }
+  else {
+  	$advanced_options = array('none' => 0);
+  }
+  
+  $blastjob['options'] = serialize($advanced_options);
 
+  // SUBMIT JOB TO TRIPAL
+  //-------------------------
   // Actually submit the BLAST Tripal Job
-  if (is_readable($blastdb_with_suffix)) {
+  if (!$error) {
     // BLAST target exists.
     global $user;
 
-    $output_filestub = date('YMd_His');
+    // We want to save all result files (.asn, .xml, .tsv, .html) in the public files directory.
+    // Usually [drupal root]/sites/default/files.
+    $output_dir = variable_get('file_public_path', conf_path() . '/files') 
+      . DIRECTORY_SEPARATOR . 'tripal' . DIRECTORY_SEPARATOR . 'tripal_blast';
+    $output_filestub = $output_dir . DIRECTORY_SEPARATOR . date('YMd_His') . '.blast';
+    
     $job_args = array(
       'program' => $blast_program,
-      'query' => $query,
+      'query' => $blastjob['query_file'],
       'database' => $blastdb_with_path,
       'output_filename' => $output_filestub,
       'options' => $advanced_options
     );
-    
+
     $job_id = tripal_add_job(
-      t('BLAST (@program): @query', array('@program' => $blast_program, '@query' => $query)),
+      t('BLAST (@program): @query', array('@program' => $blast_program, '@query' => $blastjob['query_file'])),
       'blast_job',
       'run_BLAST_tripal_job',
       $job_args,
       $user->uid
     );
     
-    $job_data = variable_get('job_data', '');
-    $seq_rows = explode(PHP_EOL, $seq_content);
-    foreach($seq_rows as $row) {
-      if (strpos($row, ">") !== FALSE) {
-       $query_def[] = trim($row, "> \t\n\r\0\x0B");
-      }
-    }
-  
-    $job_data[$job_id] = 
-      array(
-        'program'   => $blast_program,
-        'job_url'   => current_path(),
-        'fasta'     => $seq_content,
-        'query_def' => $query_def,
-        'db_name'   => $blastdb_node->db_name,
-        'db_option' => $selected_db,
-        'options'   => $advanced_options,
-      );
+    $blastjob['result_filestub'] = $output_filestub;
+    $blastjob['job_id'] = $job_id;
+
+    // SAVE JOB INFO
+    //-------------------------
+    drupal_write_record('blastjob', $blastjob);
+
+    //Encode the job_id
+    $job_encode_id = blast_ui_make_secret($job_id);
     
-    variable_set('job_data', $job_data);
-    //@deepaksomanadh create session and save the recent jobs in respective session
-    if (session_status() === PHP_SESSION_NONE) {
-       session_start();
+    // RECENT JOBS
+    //-------------------------
+    if (!isset($_SESSION['blast_jobs'])) {
+      $_SESSION['blast_jobs'] = array();
     }
-    $sid = session_id();
-    $job_encode_id = base64_encode($job_id);
-    $job_url = "blast/report/$job_encode_id";
+    $_SESSION['blast_jobs'][] = $job_encode_id;
 
-    $all_jobs = $_SESSION['all_jobs'];
-    
-    $session_jobs = $all_jobs[$sid];
-    $session_jobs[$job_id] = 
-      array(
-        'job_output_url'=> $job_url, 
-        'query_defs'    => $query_def,
-        'target'        => $blastdb_name,
-        'program'       => $blast_program,
-        'date'          => date('Y-M-d h:i:s'),
-       );
-    $all_jobs[$sid] = $session_jobs;
-    $_SESSION['all_jobs'] = $all_jobs;
-  
-// Comment out this line to run BLAST by hand via the command:
-//   drush trp-run-jobs --username=admin 
-    tripal_jobs_launch(1, $job_id);
-    
-    //Encode the job_id
-    $job_encode_id = base64_encode($job_id);
+    // NOTE: Originally there was a call to tripal_launch_jobs() here. That should
+    // NEVER be done since it runs possibly long jobs in the page load causing time-out
+    // issues. If you do not want to run tripal jobs manually, look into installing
+    // Tripal daemon which will run jobs as they're submitted or set up a cron job to
+    // launch the tripal jobs on a specified schedule.
     
     // Redirect to the BLAST results page
     drupal_goto("blast/report/$job_encode_id");
-  }//BLAST target is readable
-  
-  // BLAST target is unreadable
-  else {
-    $dbfile_uploaded_msg = ($form_state['dbFlag'] == 'upDB') 
-        ? 'The BLAST database was submitted via user upload.' 
-        : 'Existing BLAST Database was chosen.';
-    tripal_report_error(
-      'blast_ui',
-      TRIPAL_ERROR,
-      "BLAST database %db unaccessible. %msg",
-      array('%db' => $blastdb_with_path, '%msg' => $dbfile_uploaded_msg)
-    );
-    $msg = "$dbfile_uploaded_msg BLAST database '$blastdb_with_path' is unaccessible. ";
-    $msg .= "Please contact the site administrator.";
-    drupal_set_message($msg, 'error');
   }
+
+
 }
 
 /**

+ 153 - 35
includes/blast_ui.linkouts.inc

@@ -64,21 +64,42 @@ function blast_ui_blast_linkout_info() {
     // This function will have full access to the blast hit and database
     // prefix information and is expected to return a URL.
     'process function' => 'tripal_blast_generate_linkout_link',
+    // Help text to show in the BLAST Database create/edit form so that 
+    // users will know how to use this link-out type. Specifically, info
+    // about your assumptions for the URL prefix are very helpful.
+    // HTML is aloud but do not enclose in <p>.
+    'help' => 'The External Database choosen below provides its URL prefix when 
+      determining the URL to link-out to. If the link-out type is "Generic Link" then 
+      the hit identifier (determined using fasta header format or regular expression) is 
+      concatenated to the end of the url prefix. For example, if your hit is for "Chr01" 
+      and the URL prefix is "http://myfriendstripalsite.org/name/" then the complete URL 
+      is simply &#60;a href="http://myfriendstripalsite.org/name/Chr01"&#62;Chr01&#60;/a&#62;.',
   );
 
   $types['gbrowse'] = array(
     'name' => 'GBrowse',
     'process function' => 'tripal_blast_generate_linkout_gbrowse',
+    'help' => 'The link created will add a BLAST track to the GBrowse (specified by the 
+      External Database) that shows the HSPs as well as indicating the overall hit. 
+      <strong><em>It is assumed that the Reference of the GBrowse is the same as this BLAST 
+      database (even the names must be consistent).</em></strong> Furthermore, the URL prefix 
+      supplied is expected to have an empty query (?) or be properly ended (;). For
+      example, "http://mydomain.com/gb/gbrowse/tripalus_databasica/?" OR
+      "http://mydomain.com/gb/gbrowse/tripalus_databasica/?label=genes+markers;"',
   );
 
   $types['jbrowse'] = array(
     'name' => 'JBrowse',
     'process function' => 'tripal_blast_generate_linkout_jbrowse',
-  );
-
-  $types['custom'] = array(
-    'name' => 'Custom',
-    'process function' => 'tripal_custom_generate_linkout',
+    'help' => 'The link created will add a "Blast Result" track to the JBrowse (specified by the 
+      External Database) that shows the HSPs as well as indicating the overall hit. 
+      <strong><em>It is assumed that the Reference of the JBrowse is the same as this BLAST 
+      database (even the names must be consistent).</em></strong> Furthermore, the URL prefix 
+      supplied is expected to have an empty query (?) or be properly ended (&). For
+      example, "http://mydomain.com/jbrowse/tripalus_databasica/?" OR
+      "http://mydomain.com/jbrowse/tripalus_databasica/?tracks=genes,markers,blast&". 
+      Also <strong><em>the Blast Result track is NOT Displayed by default</em></strong>. Either include "blast" 
+      using the "tracks" directive in the URL prefix or specify it in your JBrowse.conf.',
   );
 
   return $types;
@@ -146,29 +167,54 @@ function tripal_blast_generate_linkout_link($url_prefix, $hit, $info, $options =
  */
 function tripal_blast_generate_linkout_gbrowse($url_prefix, $hit, $info, $options = array()) {
 
+  // First we need to collect the HSPs to define the ranges we want to
+  // display on the JBrowse.
   $ranges = array();
+  // We also keep track of all the coordinates in order to later
+  // calculate the smallest and largest coordinate.
   $coords = array();
   foreach($info['HSPs'] as $hsp) {
-     $start = min($hsp['Hsp_hit-from'], $hsp['Hsp_hit-to']);
-     $stop = max($hsp['Hsp_hit-from'], $hsp['Hsp_hit-to']);
-     array_push($ranges, "$start..$stop");
-     array_push($coords, $start, $stop);
-   }
-   $min = min($coords);
-   $max = max($coords);
-   $joined_ranges = join ("," , $ranges);
-//   $track_name = $hit->{'hit_name'} . '_' . $info['query_name'] . '_' . $info['e-value'];
-   $track_name = $hit->{'linkout_id'} . '_' . $info['query_name'] . '_' . $info['e-value'];
-   
-//   $url_postfix = 'query=';
-   $url_postfix = 'start=' . $min . ';stop=' . $max;
-//   $url_postfix .= ';ref=' . $hit->{'hit_name'};
-//   $url_postfix .= ';add=' . $hit->{'hit_name'} . '+BLAST+Query+' . $joined_ranges;
-   $url_postfix .= ';ref=' . $hit->{'linkout_id'};
-   $url_postfix .= ';add=' . $hit->{'linkout_id'} . '+BLAST+Query+' . $joined_ranges;
-   $url_postfix .= ';h_feat=Query';
-
-   return $url_prefix . $url_postfix;
+  
+    $start = min($hsp['Hsp_hit-from'], $hsp['Hsp_hit-to']);
+    $stop = max($hsp['Hsp_hit-from'], $hsp['Hsp_hit-to']);
+    
+    // Format the hsp for inclusion in the new track later.
+    array_push($ranges, "$start..$stop");
+    
+    // Add both the start & stop to the coordinate list.
+    array_push($coords, $start, $stop);
+  }
+  // Calculate the minimum & maximum coordinates.
+  $min = min($coords);
+  $max = max($coords);
+
+  // Now we are finally ready to build the URL.
+  // First lets set the location of the hit so the GBrowse focuses in on the correct region.
+  $query = array();
+  $query['ref'] = 'ref=' . $hit->{'linkout_id'};
+  $query['start'] = 'start=' . $min;
+  $query['stop'] = 'stop=' . $max;
+  
+  // Next we want to add our BLAST hit to the GBrowse.
+  $query['add'] = format_string(
+    'add=!ref+!trackname+!featurename+!hspcoords',
+    array(
+      '!ref' => $hit->{'linkout_id'},
+      '!trackname' => 'BLAST',
+      '!featurename' => 'BlastHit',
+      '!hspcoords' => join ("," , $ranges),
+    )
+  );
+  
+  // Highlight our newly added feature.
+  $query['highlight'] = format_string(
+    'h_feat=!featurename',
+    array('!featurename' => 'BlastHit')
+  );
+  
+  $url_postfix = implode(';', $query);
+
+  return $url_prefix . $url_postfix;
 }
 
 /**
@@ -176,9 +222,20 @@ function tripal_blast_generate_linkout_gbrowse($url_prefix, $hit, $info, $option
  *
  * NOTE: Assumes the hit is a backbone feature in the JBrowse linked to.
  *  Otherwise, the basic link can be used.
+ * NOTE: This linkout creates a "blast" track but doesn't make it visible. This is to
+ *  allow your default tracks to be visible and give contect to your blast hit. You
+ *  should include "blast" in your jbrowse.conf default track list to ensure your
+ *  users can always see their hits. If you don't have access to the jbrowse.conf,
+ *  you can place the tracks you want to see including 'blast' in the url prefix 
+ *  (see example below under @param $url_prefix).
  *
  * @param $url_prefix
- *   The URL prefix for the BLAST Database queried.
+ *   The URL prefix for the BLAST Database queried. It is assumed that the url prefix
+ *   includes the ? and if there are any key=vale pairs that the last symbol is &.
+ *   For example,
+ *     http://myserver.com/jbrowse/databasica/?
+ *     http://myserver.com/jbrowse/databasica/?tracks=myfavtrack,anoktrack,blast&
+ * 
  * @param $hit
  *   The blast XML hit object. This object has the following keys based on the
  *   XML: Hit_num, Hit_id, Hit_def, Hit_accession, Hit_len and Hit_hsps.
@@ -200,20 +257,81 @@ function tripal_blast_generate_linkout_gbrowse($url_prefix, $hit, $info, $option
  */
 function tripal_blast_generate_linkout_jbrowse($url_prefix, $hit, $info, $options = array()) {
 
+  // First we need to collect the HSPs to define the ranges we want to
+  // display on the JBrowse.
   $ranges = array();
+  // We also keep track of all the coordinates in order to later
+  // calculate the smallest and largest coordinate.
   $coords = array();
-  $hsps = array();
+  $count = 0;
   foreach($info['HSPs'] as $hsp) {
-   $hsp_start = $hsp['Hsp_hit-from'];
-   $hsp_end = $hsp['Hsp_hit-to'] ;
-   array_push($coords,$hsp['Hsp_hit-from'] , $hsp['Hsp_hit-to'] );
-   array_push($ranges, '{"start":'.$hsp_start.',"end":'.$hsp_end.',"type":"match_part"}');
+    $count++;
+    
+    $strand = '1';
+    $hsp_start = $hsp['Hsp_hit-from'];
+    $hsp_end = $hsp['Hsp_hit-to'];
+    
+    // Handle alignments on the negative strand.
+    if (($hsp_end - $hsp_start) < 0) {
+      $strand = '-1';
+      $hsp_start = $hsp['Hsp_hit-to'];
+      $hsp_end = $hsp['Hsp_hit-from'];
+    }
+    
+    // Add both the start & stop to the coordinate list.
+    array_push($coords,$hsp['Hsp_hit-from'] , $hsp['Hsp_hit-to'] );
+
+    // Format the hsp for inclusion in the subfeatures section of the track later.
+    $hsp_def = format_string(
+      '{"start":!start,"end":!end,"strand":"!strand","type":"!type"}',
+      array(
+        '!start' => $hsp_start,
+        '!end' => $hsp_end,
+        '!strand' => $strand,
+        '!type' => 'match_part'
+      )
+    );
+    array_push($ranges, $hsp_def);
   }
+  // Calculate the minimum & maximum coordinates.
   $min = min($coords);
   $max = max($coords);
-  $url_postfix = '&addFeatures=[{"seq_id":"'.$hit->{'hit_name'}.'","score":"'.$info['e-value'].'","start":'.$min.',"end":'.$max.',"type":"match","name":"MyBLASTHit","subfeatures":[';
-  $joined_ranges = join ("," , $ranges);
-  $url_postfix = $url_postfix . $joined_ranges . ']}]&addTracks=[{"label":"BLAST","type":"JBrowse/View/Track/HTMLFeatures","store":"url"}]&loc='.$hit->{'hit_name'}.'&tracks=DNA%2CBLAST&highlight=';
+  
+  // We also want some white-space on either side of out hit 
+  // when we show it in the JBrowse. To make this generic,
+  // we want our blast hit to take up 2/3 of the screen thus
+  // we have 1/6 per side for white-space.
+  $buffer = round(($max - $min) / 6);
+  $screen_start = $min - $buffer;
+  $screen_end = $max + $buffer;
+
+  // Now we are finally ready to build the URL.
+  // First lets set the location of the hit so the JBrowse focuses in on the correct region.
+  $jbrowse_query = array();
+  $jbrowse_query['loc'] = format_string(
+    'loc=!ref:!start..!stop',
+    array(
+      '!ref' => $hit->{'linkout_id'},
+      '!start' => $screen_start,
+      '!stop' => $screen_end,
+    )
+  );
+
+  // Next we want to add our BLAST hit to the JBrowse.
+  $jbrowse_query['addFeatures'] = format_string(
+    'addFeatures=[{"seq_id":"!id","start":!min,"end":!max,"name":"!name","subfeatures":[!hspcoords]}]',
+    array(
+      '!id' => $hit->{'linkout_id'},
+      '!name' => $info['query_name'] . ' Blast Hit',
+      '!min' => $min,
+      '!max' => $max,
+      '!hspcoords' => join ("," , $ranges)
+    ));
+
+  // Then add a track to display our new feature.
+  $jbrowse_query['addTracks'] = 'addTracks=[{"label":"blast","key":"BLAST Result","type":"JBrowse/View/Track/HTMLFeatures","store":"url"}]';
+
+  $url_postfix = implode('&', $jbrowse_query);
 
   return $url_prefix . $url_postfix;
-}
+}

+ 81 - 39
includes/blast_ui.node.inc

@@ -67,6 +67,10 @@ function blastdb_form($node, &$form_state) {
   $form = array();
 
   $form['#validate'] = array('blastdb_form_validate');
+  
+  $form['#attached']['css'] = array(
+    drupal_get_path('module', 'blast_ui') . '/theme/css/form.css',
+  );
 
   $form['core'] = array(
     '#type' => 'fieldset',
@@ -101,48 +105,70 @@ function blastdb_form($node, &$form_state) {
 
   $form['dbxref'] = array(
     '#type' => 'fieldset',
-    '#title' => 'Database References',
-    '#description' => 'These settings can be used to tell the BLAST UI that '
-      . 'information about the records in this BLAST database can be found '
-      . 'either in the current website or an external website.'
+    '#title' => 'Link-outs',
+    '#description' => 'These settings will be used to <em>transform the hit name into a 
+      link to additional information</em>.',
+    '#prefix' => '<div id="link-outs">',
+    '#suffix' => '</div>',
   );
 
-  $description = array(
-    'default' => 'A single word followed by a free-text definition. '
+  $regex = array(
+    'default' => array(
+      'title' => 'Generic',
+      'help' => 'A single word followed by a free-text definition. '
       . 'The first word contains only alphanumeric characters and optionally '
-      . 'underscores and will be used as the ID of the sequence.',
-    'genbank' => 'Follows the same format as the first option '
+      . 'underscores and will be used as the ID of the sequence.'
+    ),
+    'genbank' => array(
+      'title' => 'NCBI GenBank',
+      'help' => 'Follows the same format as the first option '
       . 'except that the first "word" is of the following form: '
-      . 'gb|accession|locus. The accession will be used as the ID of the sequence.',
-    'embl' => 'Follows the same format as the first option '
+      . 'gb|accession|locus. The accession will be used as the ID of the sequence.'
+    ),
+    'embl' => array(
+      'title' => 'EMBL Data Library',
+      'help' => 'Follows the same format as the first option '
       . 'except that the first "word" is of the following form: '
-      . 'emb|accession|locus. The accession will be used as the ID of the sequence.',
-    'swissprot' => 'Follows the same format as the first option '
+      . 'emb|accession|locus. The accession will be used as the ID of the sequence.'
+    ),
+    'swissprot' => array(
+      'title' => 'SWISS-PROT',
+      'help' => 'Follows the same format as the first option '
       . 'except that the first "word" is of the following form: '
-      . 'sp|accession|entry name. The accession will be used as the ID of the sequence.',
-    'custom' => 'Allows you to use a regular expression (define one below) to '
+      . 'sp|accession|entry name. The accession will be used as the ID of the sequence.'
+    ),
+    'custom' => array(
+      'title' => 'Custom Format',
+      'help' => 'Allows you to use a regular expression (define one below) to '
       . 'extract a specifc portion of the FASTA header to be used as the ID.'
+    ),
   );
+  $regex_type = (isset($node->linkout->regex_type)) ? $node->linkout->regex_type : 'default';
+  $regex_type = (isset($form_state['values'])) ? $form_state['values']['dbxref_id_type'] : $regex_type;
   $form['dbxref']['dbxref_id_type'] = array(
     '#type' => 'radios',
     '#title' => 'FASTA header format',
     '#description' => 'Choose the format that matches the format of the FASTA '
       . 'headers in this BLAST database or choose custom to define your own '
-      . 'using regular expressions. This ID will be appended to the URL Prefix '
-      . ' of the database selected below.',
+      . 'using regular expressions. This ID will be used to create the URL for the link-out.',
     '#options' => array(
-      'default' => '<span title="' . $description['default'] . '">Generic</span>',
-      'genbank' => '<span title="' . $description['genbank'] . '">NCBI GenBank</span>',
-      'embl' => '<span title="' . $description['embl'] . '">EMBL Data Library</span>',
-      'swissprot' => '<span title="' . $description['swissprot'] . '">SWISS-PROT</span>',
-      'custom' => '<span title="' . $description['custom'] . '">Custom Format</span>',
+      'default' => '<span title="' . $regex['default']['help'] . '">' . $regex['default']['title'] . '</span>',
+      'genbank' => '<span title="' . $regex['genbank']['help'] . '">' . $regex['genbank']['title'] . '</span>',
+      'embl' => '<span title="' . $regex['embl']['help'] . '">' . $regex['embl']['title'] . '</span>',
+      'swissprot' => '<span title="' . $regex['swissprot']['help'] . '">' . $regex['swissprot']['title'] . '</span>',
+      'custom' => '<span title="' . $regex['custom']['help'] . '">' . $regex['custom']['title'] . '</span>',
     ),
-    '#default_value' => (isset($node->linkout->regex_type)) ? $node->linkout->regex_type : 'default',
+    '#default_value' => $regex_type,
     '#ajax' => array(
       'callback' => 'ajax_blast_ui_node_linkout_custom_callback',
-      'wrapper' => 'link-regex',
+      'wrapper' => 'link-outs',
     )
   );
+  // Add information about each format to the description.
+  if ($regex_type) {
+    $form['dbxref']['dbxref_id_type']['#description'] .= '
+      <p class="blastdb-extra-info"><strong>'.$regex[$regex_type]['title'].'</strong>: '.$regex[$regex_type]['help'].'</p>';
+  }
 
   $hide_regex = TRUE;
   if (isset($form_state['values']['dbxref_id_type'])) {
@@ -161,8 +187,6 @@ function blastdb_form($node, &$form_state) {
       . 'available if custom was choosen for the FASTA header format above.',
       array('@url' => 'http://php.net/manual/en/reference.pcre.pattern.syntax.php')),
     '#disabled' => $hide_regex,
-    '#prefix' => '<div id="link-regex">',
-    '#suffix' => '</div>',
     '#default_value' => (isset($node->linkout->regex)) ? $node->linkout->regex : ''
   );
 
@@ -186,21 +210,27 @@ function blastdb_form($node, &$form_state) {
   foreach ($types as $machine_name => $details) {
     $options[$machine_name] = (isset($details['name'])) ? $details['name'] : $machine_name;
   }
+  $linkout_type = (isset($node->linkout->type)) ? $node->linkout->type : 'link';
+  $linkout_type = (isset($form_state['values'])) ? $form_state['values']['dbxref_linkout_type'] : $linkout_type;
   $form['dbxref']['dbxref_linkout_type'] = array(
-    '#type' => 'select',
+    '#type' => 'radios',
     '#title' => 'Link-out Type',
-    '#description' => 'This determines how the URL to be linked to is formed. '
-                    . 'NOTE: this is very dependant on the External Database '
-                    . 'chosen above since it needs to be able to support the '
-                    . 'type of linking choosen. For example, only External '
-                    . 'Databases which reference a GBrowse instance can use the '
-                    . 'GBrowse link type.  If a link requires customized code, '
-                    . 'select "custom" and add the code to the file ' 
-                    . 'includes/blast_ui.custom/inc.',
+    '#description' => 'This determines how the URL to be linked to is formed. <strong>Make 
+      sure the database chosen supports this type of link</strong> (ie: the database 
+      should point to a GBrowse instance if you choose GBrowse here).',
     '#options' => $options,
-    '#default_value' => (isset($node->linkout->type)) ? $node->linkout->type : 'link'
+    '#default_value' => $linkout_type,
+    '#ajax' => array(
+      'callback' => 'ajax_blast_ui_node_linkout_custom_callback',
+      'wrapper' => 'link-outs',
+    )
   );
-
+  // Add information about each format to the description.
+  if ($linkout_type) {
+    $form['dbxref']['dbxref_linkout_type']['#description'] .= '
+      <p class="blastdb-extra-info"><strong>'.$types[$linkout_type]['name'].'</strong>: '.$types[$linkout_type]['help'].'</p>';
+  }
+  
   return $form;
 }
 
@@ -322,7 +352,7 @@ function blastdb_delete($node) {
 }
 
 /**
- * Implements hook_load()   .
+ * Implements hook_load().
  */
 function blastdb_load($nodes) {
 
@@ -334,12 +364,23 @@ function blastdb_load($nodes) {
   $result = db_query($sql, array(':nids' => array_keys($nodes)));
 
   foreach ($result as $record) {
+    // Does this BLAST node have a custom linkout? 
+    //   (Is there a better way to determine this?)
+    $custom_linkout = ($record->dbxref_linkout_type != '' 
+                         && $record->dbxref_linkout_type != 'link' 
+                         && $record->dbxref_linkout_type != 'gbrowse'
+                         && $record->dbxref_linkout_type != 'jbrowse');
+
     $nodes[$record->nid]->db_name = $record->name;
     $nodes[$record->nid]->db_path = $record->path;
     $nodes[$record->nid]->title = $record->name;
     $nodes[$record->nid]->db_dbtype = $record->dbtype;
 
-    if ($record->dbxref_id_regex) {
+    // There will be a hit linkout if there is a regex pattern for the id
+    //   and a database (db record) to generate a URL
+    //   OR if a custom linkout has been set, which may or may not require a 
+    //   regex and/or database.
+    if ($record->dbxref_id_regex AND $record->dbxref_db_id || $custom_linkout) {
       $nodes[$record->nid]->linkout = new stdClass();
 
       if (preg_match('/\/.*\//', $record->dbxref_id_regex)) {
@@ -370,6 +411,7 @@ function blastdb_load($nodes) {
       }
     }
     else {
+      // No linkout
       $nodes[$record->nid]->linkout = new stdClass();
       $nodes[$record->nid]->linkout->regex = '';
       $nodes[$record->nid]->linkout->db_id = 0;
@@ -387,5 +429,5 @@ function blastdb_load($nodes) {
  * when someone selects custom.
  */
 function ajax_blast_ui_node_linkout_custom_callback($form, $form_state) {
-  return $form['dbxref']['regex'];
+  return $form['dbxref'];
 }

+ 2 - 31
theme/blast_nucleotide_user_menupage.tpl.php

@@ -43,8 +43,8 @@ local alignment search tool. J. Mol. Biol., 215, 403–410.</blockquote>
     <th>BLAST Program</th>
   </tr>
   <tr class= "blast-highlighted">
-    <td>Nucleotide</td>
     <td rowspan="2">Nucleotide</td>
+    <td>Nucleotide</td>
     <td><?php print l('blastn', './blast/nucleotide/nucleotide');?>:
       Search a nucleotide database using a nucleotide query.</td>
   </tr>
@@ -66,33 +66,4 @@ local alignment search tool. J. Mol. Biol., 215, 403–410.</blockquote>
   </tr>
 </table>
 
-<!-- Recent Jobs -->
-<?php
-
-  // Gets the list of recent jobs filtered to the current blast program (ie: blastn).
-  $recent_jobs = get_recent_blast_jobs(array('blastn','blastx'));
-  if ($recent_jobs) {
-  
-    print '<h2>Recent Jobs</h2>';
-    
-    $table = array(
-      'header' => array('Query Information', 'Search Target', 'Date Requested', ''),
-      'rows' => array(),
-      'attributes' => array('class' => array('tripal-blast', 'recent-jobs')),
-      'sticky' => FALSE
-    );
-  
-    foreach ($recent_jobs as $job) {
-
-      // Define a row for the current job.
-      $table['rows'][] = array(
-        $job['query_info'],
-        $job['target'],
-        $job['date'],
-        l('See Results', $job['job_output_url'])
-      );
-    }
-    
-    print theme('table', $table);
-  }
-?>
+<?php print theme('blast_recent_jobs', array('blastn','blastx')); ?>

+ 1 - 30
theme/blast_protein_user_menupage.tpl.php

@@ -65,33 +65,4 @@ local alignment search tool. J. Mol. Biol., 215, 403–410.</blockquote>
   </tr>
 </table>
 
-<!-- Recent Jobs -->
-<?php
-
-  // Gets the list of recent jobs filtered to the current blast program (ie: blastn).
-  $recent_jobs = get_recent_blast_jobs(array('tblastn','blastp'));
-  if ($recent_jobs) {
-  
-    print '<h2>Recent Jobs</h2>';
-    
-    $table = array(
-      'header' => array('Query Information', 'Search Target', 'Date Requested', ''),
-      'rows' => array(),
-      'attributes' => array('class' => array('tripal-blast', 'recent-jobs')),
-      'sticky' => FALSE
-    );
-  
-    foreach ($recent_jobs as $job) {
-
-      // Define a row for the current job.
-      $table['rows'][] = array(
-        $job['query_info'],
-        $job['target'],
-        $job['date'],
-        l('See Results', $job['job_output_url'])
-      );
-    }
-    
-    print theme('table', $table);
-  }
-?>
+<?php print theme('blast_recent_jobs', array('tblastn','blastp')); ?>

+ 34 - 0
theme/blast_recent_jobs.tpl.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ *
+ */
+
+// Gets the list of recent jobs filtered to the current blast program (ie: blastn).
+$recent_jobs = get_recent_blast_jobs($programs);
+if ($recent_jobs) {
+
+  usort($recent_jobs, 'sort_blast_jobs_by_date_submitted_desc');
+  
+  print '<h2>Recent Jobs</h2>';
+  
+  $table = array(
+    'header' => array('Query Information', 'Search Target', 'Date Requested', ''),
+    'rows' => array(),
+    'attributes' => array('class' => array('tripal-blast', 'recent-jobs')),
+    'sticky' => FALSE
+  );
+
+  foreach ($recent_jobs as $job) {
+
+    // Define a row for the current job.
+    $table['rows'][] = array(
+      $job->query_summary,
+      $job->blastdb->db_name,
+      format_date($job->date_submitted, 'medium'),
+      l('See Results', 'blast/report/'.blast_ui_make_secret($job->job_id))
+    );
+  }
+  
+  print theme('table', $table);
+}
+?>

+ 120 - 160
theme/blast_report.tpl.php

@@ -1,36 +1,28 @@
 <?php
-
 /**
  * Display the results of a BLAST job execution
- *
- * Variables Available in this template:
- *   $xml_filename: The full path & filename of XML file containing the BLAST results
- *    @deepaksomanadh: $job_data = meta data related to the current job
  */
- 
-// uncomment this to see the contents of the $blastdb object
-//echo "blastdb:<pre>";var_dump($blastdb);echo "</pre>";
 
 // Set ourselves up to do link-out if our blast database is configured to do so.
 $linkout = FALSE;
-
-if ($blastdb->linkout->none === FALSE) {
-  $linkout_type  = $blastdb->linkout->type;
-  $linkout_regex = $blastdb->linkout->regex;
+if ($blast_job->blastdb->linkout->none === FALSE) {
+  $linkout_type  = $blast_job->blastdb->linkout->type;
+  $linkout_regex = $blast_job->blastdb->linkout->regex;
   
   // Note that URL prefix is not required if linkout type is 'custom'
-  if (isset($blastdb->linkout->db_id->urlprefix) && !empty($blastdb->linkout->db_id->urlprefix)) {
-    $linkout_urlprefix = $blastdb->linkout->db_id->urlprefix;
+  if (isset($blast_job->blastdb->linkout->db_id->urlprefix) && !empty($blast_job->blastdb->linkout->db_id->urlprefix)) {
+    $linkout_urlprefix = $blast_job->blastdb->linkout->db_id->urlprefix;
   }
 
   // Check that we can determine the linkout URL.
   // (ie: that the function specified to do so, exists).
-  if (function_exists($blastdb->linkout->url_function)) {
-    $url_function = $blastdb->linkout->url_function;
+  if (function_exists($blast_job->blastdb->linkout->url_function)) {
+    $url_function = $blast_job->blastdb->linkout->url_function;
     $linkout = TRUE;
   }
 }
 
+
 // Handle no hits. This following array will hold the names of all query
 // sequences which didn't have any hits.
 $query_with_no_hits = array();
@@ -42,12 +34,6 @@ $no_hits = TRUE;
 ?>
 
 <script type="text/javascript">
-  window.onload = function() {
-    if (!window.location.hash) {
-      window.location = window.location + '#loaded';
-      window.location.reload();
-    }
-  }
 
   // JQuery controlling display of the alignment information (hidden by default)
   $(document).ready(function(){
@@ -80,20 +66,20 @@ $no_hits = TRUE;
 <div class="blast-job-info">
 <?php if($xml): ?>
   <div class="blast-download-info"><strong>Download</strong>:
-    <a href="<?php print '../../' . $html_filename; ?>">Alignment</a>,
-    <a href="<?php print '../../' . $tsv_filename; ?>">Tab-Delimited</a>,
-    <a href="<?php print '../../' . $xml_filename; ?>">XML</a>
+    <a href="<?php print '../../' . $blast_job->files->result->html; ?>">Alignment</a>,
+    <a href="<?php print '../../' . $blast_job->files->result->tsv; ?>">Tab-Delimited</a>,
+    <a href="<?php print '../../' . $blast_job->files->result->xml; ?>">XML</a>
   </div>
 <?php endif; ?>
   <br />
   <div class="blast-query-info"><strong>Query Information</strong>: 
-    <?php print $blast_job->display['query_info'];?></div>
+    <?php print $blast_job->files->query;?></div>
   <div class="blast-target-info"><strong>Search Target</strong>: 
-    <?php print $blast_job->display['target'];?></div>
+    <?php print $blast_job->blastdb->db_name;?></div>
   <div class="blast-date-info"><strong>Submission Date</strong>: 
-    <?php print $blast_job->display['date'];?></div>
+    <?php print format_date($blast_job->date_submitted, 'medium');?></div>
   <div class="blast-cmd-info"><strong>BLAST Command executed</strong>: 
-    <?php print $blast_job->display['blast_cmd'];?></div>
+    <?php print $blast_job->blast_cmd;?></div>
 </div>
 <br />
 
@@ -129,8 +115,8 @@ and click the <em>target name </em> to get more information about the target hit
   // significance and 2) additional information including the alignment
   foreach ($xml->{'BlastOutput_iterations'}->children() as $iteration) {
     $children_count = $iteration->{'Iteration_hits'}->children()->count();
-    //@deepaksomanadh: Code added for BLAST visualization
-    // parameters that need to be passed for BLAST image generation
+
+    // Save some information needed for the hit visualization.
     $target_name = '';
     $q_name = $xml->{'BlastOutput_query-def'};
     $query_size = $xml->{'BlastOutput_query-len'};
@@ -143,13 +129,21 @@ and click the <em>target name </em> to get more information about the target hit
           $zebra_class = ($count % 2 == 0) ? 'even' : 'odd';
           $no_hits = FALSE;
 
-          // RETRIEVE INFO
+          // SUMMARY ROW
+          // -- Save additional information needed for the summary.
+          $score = (float) $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_score'};
+          $evalue = (float) $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_evalue'};
+          $query_name = (string) $iteration->{'Iteration_query-def'};
+          
+          // If the id is of the form gnl|BL_ORD_ID|### then the parseids flag
+          // to makeblastdb did a really poor job. In thhis case we want to use
+          // the def to provide the original FASTA header.
+          // @todo Deepak changed this to use just the hit_def; inquire as to why.
           $hit_name = (preg_match('/BL_ORD_ID/', $hit->{'Hit_id'})) ? $hit->{'Hit_def'} : $hit->{'Hit_id'};  
-          $score = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_score'};
-          $evalue = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_evalue'};
-          $query_name = $iteration->{'Iteration_query-def'};
+          // Used for the hit visualization to ensure the name isn't truncated.
+          $hit_name_short = (preg_match('/^([^\s]+)/', $hit_name, $matches)) ? $matches[1] : $hit_name;
 
-          // Round e-val to two decimal values
+          // Round e-value to two decimal values.
           $rounded_evalue = '';
           if (strpos($evalue,'e') != false) {
             $evalue_split = explode('e', $evalue);
@@ -158,21 +152,36 @@ and click the <em>target name </em> to get more information about the target hit
           }
           else { 
             $rounded_evalue = $evalue;
-          }        
+          }
+          
+          // State what should be in the summary row for theme_table() later.
+          $summary_row = array(
+            'data' => array(
+              'arrow-col' => array('data' => '<div class="arrow"></div>', 'class' => array('arrow-col')),
+              'number' => array('data' => $count, 'class' => array('number')),
+              'query' => array('data' => $query_name, 'class' => array('query')),
+              'hit' => array('data' => $hit_name, 'class' => array('hit')),
+              'evalue' => array('data' => $rounded_evalue, 'class' => array('evalue')),
+            ),
+            'class' => array('result-summary')
+          );
         
           // ALIGNMENT ROW (collapsed by default)
           // Process HSPs
-          // @deepaksomanadh: Code added for BLAST visualization
-          // hit array and corresponding bit scores 
-          // hits=4263001_4262263_1_742;4260037_4259524_895_1411;&scores=722;473;
           $HSPs = array();
+
+          // We need to save some additional summary information in order to draw the
+          // hit visualization. First, initialize some variables...
           $track_start = INF;
           $track_end = -1;
           $hsps_range = '';
           $hit_hsps = '';
           $hit_hsp_score = '';
           $target_size = $hit->{'Hit_len'};
-    
+          $Hsp_bit_score = '';
+          
+          // Then for each hit hsp, keep track of the start of first hsp and the end of
+          // the last hsp. Keep in mind that hsps might not be recorded in order.
           foreach ($hit->{'Hit_hsps'}->children() as $hsp_xml) {
             $HSPs[] = (array) $hsp_xml;
     
@@ -182,101 +191,31 @@ and click the <em>target name </em> to get more information about the target hit
             if ($track_end < $hsp_xml->{'Hsp_hit-to'}) {
               $track_end = $hsp_xml->{'Hsp_hit-to'} . "";
             }
-          }  
-          $range_start = (int) $track_start - 50000;
-          $range_end = (int) $track_end + 50000;
-        
-          if ($range_start < 1) 
-            $range_start = 1;  
 
-          // For BLAST visualization 
-          $target_size = $hit->{'Hit_len'};
-          $Hsp_bit_score = '';
-          foreach ($hit->{'Hit_hsps'}->children() as $hsp_xml) {
+            // The BLAST visualization code requires the hsps to be formatted in a
+            // very specific manner. Here we build up the strings to be submitted.
+            // hits=4263001_4262263_1_742;4260037_4259524_895_1411;&scores=722;473;
             $hit_hsps .=  $hsp_xml->{'Hsp_hit-from'} . '_' . 
                           $hsp_xml->{'Hsp_hit-to'} . '_' . 
                           $hsp_xml->{'Hsp_query-from'} . '_' . $hsp_xml->{'Hsp_query-to'} . 
                           ';';  
-            $Hsp_bit_score .=   $hsp_xml->{'Hsp_bit-score'} .';';              
-          }      
-          
-          // SUMMARY ROW
-          // If the id is of the form gnl|BL_ORD_ID|### then the parseids flag
-          // to makeblastdb did a really poor job. In this case we want to use
-          // the def to provide the original FASTA header.
-          
-          // If our BLAST DB is configured to handle link-outs then use the
-          // regex & URL prefix provided to create one.
-          $hit_name = $hit->{'Hit_def'};
-          $hit_name_short = (preg_match('/^([^\s]+)/', $hit_name, $matches)) ? $matches[1] : $hit_name;
-          $query_name = $iteration->{'Iteration_query-def'};
- 
-          if ($linkout) {
-//echo "link out regex: $linkout_regex executed on [$hit_name]<br>";
-//preg_match($linkout_regex, $hit_name, $linkout_match);
-//echo "<br>matches:<pre>" . var_dump($linkout_match);echo "</pre>";
-            if (preg_match($linkout_regex, $hit_name, $linkout_match)) {
-              $linkout_id = $linkout_match[1];
-              $hit->{'linkout_id'} = $linkout_id;
-              $hit->{'hit_name'} = $hit_name;
-            }
-            
-            $hit_url = call_user_func(
-              $url_function,
-              $linkout_urlprefix,
-              $hit,
-              array(
-                'query_name' => $query_name,
-                'score'      => $score,
-                'e-value'    => $evalue,
-                'HSPs'       => $HSPs,
-                'Target'     => $blastdb->title,
-              )
-            );
-            
-            // The linkout id might have been set/changed by the custom linkout code.
-            if ($linkout_type == 'custom' && $hit->{'linkout_id'}) {
-              $linkout_id = $hit->{'linkout_id'};
-            }
-
-
-            if ($hit_url) {
-/* eksc- l() URL-encodes the URL path too, which is often not what we want.
-                  $hit_name = l(
-                    $linkout_id,
-                    $hit_url,
-                    array('attributes' => array('target' => '_blank'))
-                  );
-*/
-               $hit_name = "
-                  <a href=\"$hit_url\" target=\"_blank\">
-                    $linkout_id
-                  </a>";
-            }
-          }//handle linkout
-
-          //@deepaksomanadh: Code added for BLAST visualization
-          // get the image and display
+            $Hsp_bit_score .=   $hsp_xml->{'Hsp_bit-score'} .';';    
+          }
+          // Finally record the range.
+          // @todo figure out why we arbitrarily subtract 50,000 here...
+          // @more removing the 50,000 and using track start/end appears to cause no change...
+          $range_start = (int) $track_start;// - 50000;
+          $range_end = (int) $track_end;// + 50000;
+          if ($range_start < 1) $range_start = 1;  
+   
+
+          // Call the function to generate the hit image.
           $hit_img = generate_blast_hit_image($target_name, $Hsp_bit_score, $hit_hsps, 
                                    $target_size, $query_size, $q_name, $hit_name_short);
-          
-          $row = array(
-            'data' => array(
-              'arrow-col' => array('data' => '<div class="arrow"></div>', 'class' => array('arrow-col')),
-              'number' => array('data' => $count, 'class' => array('number')),
-              'query' => array('data' => $query_name, 'class' => array('query')),
-              'hit' => array('data' => $hit_name, 'class' => array('hit')),
-              'evalue' => array('data' => $rounded_evalue, 'class' => array('evalue')),
-              'arrow-col' => array('data' => '<div class="arrow"></div>', 'class' => array('arrow-col'))
-            ),
-            'class' => array('result-summary')
-          );
-          $rows[] = $row;
 
-          // ALIGNMENT ROW (collapsed by default)
-          // Process HSPs
 
-          $row = array(
+          // State what should be in the alignment row for theme_table() later.
+          $alignment_row = array(
             'data' => array(
               'arrow' => array(
                 'data' => theme('blast_report_alignment_row', array('HSPs' => $HSPs, 'hit_visualization' => $hit_img)),
@@ -286,7 +225,57 @@ and click the <em>target name </em> to get more information about the target hit
             'class' => array('alignment-row', $zebra_class),
             'no_striping' => TRUE
           );
-          $rows[] = $row;
+          
+          // LINK-OUTS.
+          // It was determined above whether link-outs were supported for the
+          // tripal blast database used as a search target. Thus we only want to 
+          // determine a link-out if it's actually supported... ;-)
+          if ($linkout) {
+          
+            // First extract the linkout text using the regex provided through
+            // the Tripal blast database node.
+            if (preg_match($linkout_regex, $hit_name, $linkout_match)) {
+              $hit->{'linkout_id'} = $linkout_match[1];
+              $hit->{'hit_name'} = $hit_name;
+            
+              // Allow custom functions to determine the URL to support more complicated
+              // link-outs rather than just using the tripal database prefix.
+              $hit_url = call_user_func(
+                $url_function,
+                $linkout_urlprefix,
+                $hit,
+                array(
+                  'query_name' => $query_name,
+                  'score'      => $score,
+                  'e-value'    => $evalue,
+                  'HSPs'       => $HSPs,
+                  'Target'     => $blast_job->blastdb->db_name,
+                )
+              );
+
+              // Create Link.
+              if ($hit_url) {
+                // It is important to url-encode links, especially in this case,
+                // since jbrowse links have double quotes in them which result in
+                // truncated links due to <a href="yoururl"></a> (notice the double quotes).
+                $hit_name = l(
+                  $hit->{'linkout_id'},
+                  $hit_url,
+                  array('attributes' => array('target' => '_blank'))
+                );
+                
+                //HACK: change '%3F' to '?' to work around a Drupal bug"
+                $hit_name = str_replace('%3F', '?', $hit_name);
+              }
+            }
+                        
+            // Replace the target name with the link.
+            $summary_row['data']['hit']['data'] = $hit_name;
+          }
+          
+          // ADD TO TABLE ROWS
+          $rows[] = $summary_row;
+          $rows[] = $alignment_row;
 
         }//end of if - checks $hit
       }//end of foreach - iteration_hits
@@ -331,38 +320,9 @@ else {
 
 <p><?php print l(
   'Edit this query and re-submit', 
-  $blast_job->form_options['job_url'],
-  array('query' => array('jid' => base64_encode($job_id))));
+  $blast_form_url,
+  array('query' => array('resubmit' => blast_ui_make_secret($job_id))));
 ?></p>
 </div>
 
-<!-- Recent Jobs -->
-<?php
-
-  // Gets the list of recent jobs filtered to the current blast program (ie: blastn).
-  
-  if ($recent_jobs) {
-  
-    print '<h2>Recent Jobs</h2>';
-    
-    $table = array(
-      'header' => array('Query Information', 'Search Target', 'Date Requested', ''),
-      'rows' => array(),
-      'attributes' => array('class' => array('tripal-blast', 'recent-jobs')),
-      'sticky' => FALSE
-    );
-  
-    foreach ($recent_jobs as $job) {
-
-      // Define a row for the current job.
-      $table['rows'][] = array(
-        $job['query_info'],
-        $job['target'],
-        $job['date'],
-        l('See Results', $job['job_output_url'])
-      );
-    }
-    
-    print theme('table', $table);
-  }
-?>
+<?php print theme('blast_recent_jobs', array()); ?>

+ 169 - 31
theme/blast_ui.theme.inc

@@ -21,44 +21,38 @@ function blast_ui_preprocess_show_blast_report(&$vars) {
   drupal_add_css($path . '/theme/css/blast_report.css');
   drupal_add_js('http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js');
 
-  // Get the filename of the BLAST results
-  $job = tripal_get_job($vars['job_id']);
-  $job_args = unserialize($job->arguments);
+  // Get blast job details.
+  $vars['blast_job'] = get_BLAST_job($vars['job_id']);
   
-//eksc- could stand better use of module settings and fewer hardcoded paths.
-  $vars['xml_filename'] = variable_get('file_public_path', conf_path() . '/files') . '/tripal/tripal_blast/' . $job_args['output_filename'] . '.blast.xml';
-  $vars['tsv_filename'] = variable_get('file_public_path', conf_path() . '/files') . '/tripal/tripal_blast/' . $job_args['output_filename'] . '.blast.tsv';
-  $vars['html_filename'] = variable_get('file_public_path', conf_path() . '/files') . '/tripal/tripal_blast/' . $job_args['output_filename'] . '.blast.html';
-
-  // Add the blast database node.
-  // This is needed for link-out functionality.
-  $vars['blastdb'] = get_blast_database(array('path' => $job_args['database']));
-  
-  // Get the recent job information.
-  $vars['recent_jobs'] = get_recent_blast_jobs();
-  
-  // Make job information available in the template.
-  $vars['blast_job'] = $job;
-  $job_data = variable_get('job_data', '');
-  if (isset($job_data[ $vars['job_id'] ])) {
-    $vars['blast_job']->form_options = $job_data[ $vars['job_id'] ];
-  }
-  if (isset($vars['recent_jobs'][ $vars['job_id'] ])) {
-    $vars['blast_job']->display = $vars['recent_jobs'][ $vars['job_id'] ];
+  // Determine the blast command for display.
+  $vars['blast_job']->blast_cmd = $vars['blast_job']->program;
+  foreach($vars['blast_job']->options as $key => $value) {
+      $vars['blast_job']->blast_cmd .= ' -' . $key. ' ' . $value ;
   }
   
-  // Determine the blast command for display.
-  $vars['blast_job']->display['blast_cmd'] = $vars['blast_job']->form_options['program'];
-  foreach($vars['blast_job']->form_options['options'] as $key => $value) {
-      $vars['blast_job']->display['blast_cmd'] .= ' -' . $key. ' ' . $value ;
+  // Determine the URL of the blast form.
+  $vars['blast_form_url'] = 'blast/nucleotide/nucleotide';
+  switch($vars['blast_job']->program) {
+    case 'blastn':
+      $vars['blast_form_url'] = 'blast/nucleotide/nucleotide';
+      break;
+    case 'blastx':
+      $vars['blast_form_url'] = 'blast/nucleotide/protein';
+      break;
+    case 'tblastn':
+      $vars['blast_form_url'] = 'blast/protein/nucleotide';
+      break;
+    case 'blastp':
+      $vars['blast_form_url'] = 'blast/protein/protein';
+      break;
   }
   
   // Load the XML file.
   $vars['xml'] = NULL;
-  if (is_readable($vars['xml_filename'])) {
-    $vars['xml'] = simplexml_load_file($vars['xml_filename']);
+  $full_path_xml = DRUPAL_ROOT . DIRECTORY_SEPARATOR . $vars['blast_job']->files->result->xml;
+  if (is_readable($full_path_xml)) {
+    $vars['xml'] = simplexml_load_file($full_path_xml);
   }
-
 }
 
 /**
@@ -87,4 +81,148 @@ function blast_ui_theme_registry_alter(&$theme_registry) {
       }
     }
   }
-}
+}
+
+/**
+ * Makes the tripal job_id unrecognizable.
+ * 
+ * @param $job_id
+ *   The tripal job_id of the blast you want to make secret.
+ *
+ * @return
+ *   A short string representing the job_id.
+ */
+function blast_ui_make_secret($job_id) {
+
+  $mapping = blast_ui_secret_mapping();
+  $secret = str_replace(array_keys($mapping), $mapping, $job_id);
+  
+  return $secret;
+}
+
+/**
+ * Reveals the true job_id for your secret blast result.
+ *
+ * @param $secret
+ *    The job_id previously made secret by blast_ui_make_secret().
+ *
+ * @return
+ *    The revealed tripal job_id.
+ */
+function blast_ui_reveal_secret($secret) {
+
+  $mapping = blast_ui_secret_mapping(TRUE);
+  $job_id = str_replace(array_keys($mapping), $mapping, $secret);
+  
+  // Check that the job_id exists if it is an integer.
+  if (is_numeric($job_id)) {
+
+    $exists = db_query('SELECT job_id FROM {tripal_jobs} WHERE job_id=:id', array(':id' => $job_id))->fetchField();
+    if (!$exists) {
+      tripal_report_error(
+        'blast_ui',
+        TRIPAL_ERROR,
+        'Unable to decode the blast job_id from :id.',
+        array(':id' => $secret)
+      );
+    }
+    else {
+      return $job_id;
+    }
+  }  
+  // Last ditch effort: maybe this job was encoded before the upgrade?
+  else {
+
+    $job_id = base64_decode($secret);
+    if (is_numeric($job_id)) {
+      $exists = db_query('SELECT job_id FROM {tripal_jobs} WHERE job_id=:id', array(':id' => $job_id))->fetchField();
+      if (!$exists) {
+        tripal_report_error(
+          'blast_ui',
+          TRIPAL_ERROR,
+          'Unable to decode the blast job_id from :id.',
+          array(':id' => $secret)
+        );
+      }
+      else {
+        return $job_id;
+      }
+    }
+    else {
+      tripal_report_error(
+        'blast_ui',
+        TRIPAL_ERROR,
+        'Unable to decode the blast job_id from :id.',
+        array(':id' => $secret)
+      );
+    }
+  }
+  
+  return FALSE;
+}
+
+/**
+ * A single location for keeping track of the mapping used in our secrets.
+ */
+function blast_ui_secret_mapping($reveal = FALSE) {
+  $mapping = array(
+    1 => 'P',
+    2 => 'sA',
+    3 => 'b',
+    4 => 'Q',
+    5 => 'Hi',
+    6 => 'yt',
+    7 => 'f',
+    8 => 'zE',
+    9 => 'Km',
+    0 => 'jVo',
+  );
+  
+  // Since this is an open-source module with all the code publically available, 
+  // our secret is not very secret... We are ok with this since the liklihood of 
+  // profiting by stealing random blast results is pretty low. That said, if this bothers
+  // you, feel free to implement the following function in a private module to change
+  // this mapping to something that cannot easily be looked up on github. ;-).
+  // NOTE: Ensure that the mapping you come up with is unique to ensure that the
+  // job_id can be consistently revealed or your users might end up unable to find 
+  // their own blast results...
+  if (function_exists('private_make_mapping_ultra_secret')) {
+    $mapping = private_make_mapping_ultra_secret($mapping);
+  }
+  
+  if ($reveal) {
+    return array_flip($mapping);
+  }
+  else {
+    return $mapping;
+  }
+}
+
+/**
+ * Tests your secret mapping over a set of random integers 
+ * to ensure the job_id can be recovered.
+ *
+ * @param $num_iterations
+ *    An integer representing the number of times you wish to test your mapping.
+ */
+function blast_ui_test_secret_mapping($num_iterations = 10000) {
+
+  $all_work = TRUE;
+  
+  for($i = 0; $i <= $num_iterations; $i++) {
+    $job_id = mt_rand(0,100000);
+    
+    $secret = blast_ui_make_secret($job_id);
+    $recovered_job_id = blast_ui_reveal_secret($secret);
+    
+    if ($job_id != $recovered_job_id) {
+      drupal_set_message("Unable to recover job_id: $job_id; Secret: $secret.",'error');
+      $all_work = FALSE;
+    }
+  }
+  
+  if ($all_work) {
+    drupal_Set_message("Secret Mapping works over $num_iterations iterations with random integers.");
+  }
+  
+}

+ 3 - 34
theme/blast_user_menupage.tpl.php

@@ -1,10 +1,8 @@
 <?php
-
 /**
  * @file
  *
  */
-
 ?>
 
 <p>In bioinformatics, BLAST (Basic Local Alignment Search Tool) is an algorithm
@@ -37,7 +35,7 @@ local alignment search tool. J. Mol. Biol., 215, 403–410.</blockquote>
     <th>BLAST Program</th>
   </tr>
   <tr>
-    <td  rowspan="2"><?php print l('Nucleotide', './blast/nucleotide');?></td>
+    <td rowspan="2"><?php print l('Nucleotide', './blast/nucleotide');?></td>
     <td>Nucleotide</td>
     <td><?php print l('blastn', './blast/nucleotide/nucleotide');?>:
       Search a nucleotide database using a nucleotide query.</td>
@@ -48,7 +46,7 @@ local alignment search tool. J. Mol. Biol., 215, 403–410.</blockquote>
       Search protein database using a translated nucleotide query.</td>
   </tr>
   <tr>
-    <td  rowspan="2"><?php print l('Protein', './blast/protein');?></td>
+    <td rowspan="2"><?php print l('Protein', './blast/protein');?></td>
     <td>Nucleotide</td>
     <td><?php print l('tblastn', './blast/protein/nucleotide');?>:
       Search translated nucleotide database using a protein query.</td>
@@ -60,33 +58,4 @@ local alignment search tool. J. Mol. Biol., 215, 403–410.</blockquote>
   </tr>
 </table>
 
-<!-- Recent Jobs -->
-<?php
-
-  // Gets the list of recent jobs filtered to the current blast program (ie: blastn).
-  $recent_jobs = get_recent_blast_jobs();
-  if ($recent_jobs) {
-  
-    print '<h2>Recent Jobs</h2>';
-    
-    $table = array(
-      'header' => array('Query Information', 'Search Target', 'Date Requested', ''),
-      'rows' => array(),
-      'attributes' => array('class' => array('tripal-blast', 'recent-jobs')),
-      'sticky' => FALSE
-    );
-  
-    foreach ($recent_jobs as $job) {
-
-      // Define a row for the current job.
-      $table['rows'][] = array(
-        $job['query_info'],
-        $job['target'],
-        $job['date'],
-        l('See Results', $job['job_output_url'])
-      );
-    }
-    
-    print theme('table', $table);
-  }
-?>
+<?php print theme('blast_recent_jobs', array()); ?>

+ 6 - 0
theme/css/form.css

@@ -3,3 +3,9 @@ div.center {
   margin-right: auto;
   width:70%
 } 
+
+.blastdb-extra-info {
+  padding: 10px;
+  border: 1px solid #be7;
+  background-color: #f8fff0;
+}

+ 2 - 0
theme/node--blastdb.tpl.php

@@ -99,10 +99,12 @@
       <tr><th>Human-Readable Name</th><td><?php print $node->db_name; ?></td></tr>
       <tr><th>Database Path</th><td><?php print $node->db_path; ?></td></tr>
       <tr><th>Database Type</th><td><?php print $node->db_dbtype; ?></td></tr>
+<?php if ($node->linkout->none === FALSE ) { ?>
       <tr><th>FASTA Header Format</th><td><?php print $node->linkout->regex_type; ?></td></tr>
       <tr><th>External Database</th><td><?php print $node->linkout->db_id->name; ?></td></tr>
       <tr><th>RegEx</th><td><?php print $node->linkout->regex; ?></td></tr>
       <tr><th>Link-out Type</th><td><?php print $node->linkout->type; ?></td></tr>
+<?php } ?>
     </table>
 
     <?php