Răsfoiți Sursa

added a field and filter handler for a sequence. Also fixed a fwe butgs in the gff loader, added a new function in the feature API for generating a reverse compliment

spficklin 12 ani în urmă
părinte
comite
5816ad9df7

+ 19 - 1
tripal_core/api/tripal_core.api.inc

@@ -232,6 +232,10 @@ function tripal_core_chado_insert($table, $values, $options = array()) {
       $itypes[] = "%d";
       $idatatypes[] = 'int';
     }
+    elseif (strcasecmp($table_desc['fields'][$field]['type'], 'boolean')==0) {
+      $itypes[] = "%s";
+      $idatatypes[] = 'bool';    
+    }
     else {
       $itypes[] = "'%s'";
       $idatatypes[] = 'text';
@@ -630,7 +634,13 @@ function tripal_core_chado_update($table, $match, $values) {
  *         'is_obsolete' => 0
  *      ),
  *   );
- *   $result = tripal_core_chado_select('feature',$columns,$values);
+ *   $options = array(
+ *     'statement_name' => 'sel_feature_genus_species_cvname'
+ *     'order_by' => array(
+ *        'name' => 'ASC'
+ *     ),
+ *   ); 
+ *   $result = tripal_core_chado_select('feature',$columns,$values,$options);
  * @endcode
  * The above code selects a record from the feature table using the three fields
  * that uniquely identify a feature.  The $columns array simply lists the columns
@@ -789,6 +799,14 @@ function tripal_core_chado_select($table, $columns, $values, $options = NULL) {
           $idatatypes[] = 'int';
           $pvalues[] = $value[0];
         }
+        elseif (strcasecmp($table_desc['fields'][$field]['type'], 'boolean')==0) {
+          $sql .= "$field $operator %s AND ";
+          $psql .= "$field $operator \$" . $i . " AND ";
+          $args[] = $value[0];
+          // set the variables needed for the prepared statement
+          $idatatypes[] = 'bool';
+          $pvalues[] = $value[0];             
+        }
         // else the type is a text
         else {
           if (in_array($field, $options['case_insensitive_columns'])) {

+ 1 - 1
tripal_core/includes/jobs.php

@@ -79,7 +79,7 @@ function tripal_add_job($job_name, $modulename, $callback, $arguments, $uid, $pr
   }
   if (drupal_write_record('tripal_jobs', $record)) {
     $jobs_url = url("admin/tripal/tripal_jobs");
-    drupal_set_message(t("Job '%job_name' submitted.  Check the <a href='!jobs_url'>jobs page</a> for status", array('%job_name' => $job_name, '!jobs_url' => url($jobs_url))));
+    drupal_set_message(t("Job '%job_name' submitted.  Check the <a href='!jobs_url'>jobs page</a> for status", array('%job_name' => $job_name, '!jobs_url' => $jobs_url)));
   }
   else {
     drupal_set_message(t("Failed to add job %job_name.", array('%job_name' => $job_name)), 'error');

+ 32 - 0
tripal_feature/api/tripal_feature.api.inc

@@ -496,3 +496,35 @@ function tripal_feature_delete_property($feature_id, $property, $cv_name='tripal
 function tripal_feature_delete_property_by_id($featureprop_id) {
   return tripal_core_delete_property_by_id('feature', $featureprop_id);
 }
+
+/**
+ * Performs a reverse compliment of a nucleotide sequence
+ *
+ * @param $sequence
+ *   The nucelotide sequence
+ *
+ * @return
+ *   an upper-case reverse complemented sequence
+ *
+ * @ingroup tripal_feature_api
+ */
+ function trpial_feature_reverse_complement($sequence) {
+
+    $seq = strtoupper($sequence);
+    $seq = strrev($seq);
+    $seq = str_replace("A", "t", $seq);
+    $seq = str_replace("T", "a", $seq);
+    $seq = str_replace("G", "c", $seq);
+    $seq = str_replace("C", "g", $seq);
+    $seq = str_replace("Y", "r", $seq);
+    $seq = str_replace("R", "y", $seq);
+    $seq = str_replace("W", "w", $seq);
+    $seq = str_replace("S", "s", $seq);
+    $seq = str_replace("K", "m", $seq);
+    $seq = str_replace("M", "k", $seq);
+    $seq = str_replace("D", "h", $seq);
+    $seq = str_replace("V", "b", $seq);
+    $seq = str_replace("H", "d", $seq);
+    $seq = str_replace("B", "v", $seq);
+    return strtoupper($seq);
+ }

+ 90 - 41
tripal_feature/includes/gff_loader.inc

@@ -488,14 +488,21 @@ function tripal_feature_load_gff3($gff_file, $organism_id, $analysis_id, $add_on
         }
         // add target relationships
         if (array_key_exists('Target', $tags)) {
-          $target = explode(" ", $tags['Target'][0]);
+          $target = preg_split('/\s+/', $tags['Target'][0]);
           $target_feature = $target[0];
-          $target_start = $target[1];
-          $target_end = $target[2];
-          $target_dir = $target[3];
-          #print "Target: $target_feature, $target_start-$target_end\n";
+          $start = $target[1];
+          $end = $target[2];
+          $target_strand = $target[3];          
+          $target_fmin = $start - 1;
+          $target_fmax = $end;
+          if ($end < $start) {
+            $target_fmin = $end - 1;
+            $target_fmax = $start;
+          }
+          
+          #print "Target: $target_feature, $target_fmin-$target_fmax $target_dir\n";
           tripal_feature_load_gff3_featureloc($feature, $organism,
-            $target_feature, $target_start, $target_end, $strand, $phase, $attr_fmin_partial,
+            $target_feature, $target_fmin, $target_fmax, $target_strand, $phase, $attr_fmin_partial,
             $attr_fmax_partial, $attr_residue_info, $attr_locgroup);
         }
         // add gap information.  This goes in simply as a property
@@ -1080,67 +1087,109 @@ function tripal_feature_load_gff3_feature($organism, $analysis_id, $cvterm, $uni
 function tripal_feature_load_gff3_featureloc($feature, $organism, $landmark, $fmin,
   $fmax, $strand, $phase, $is_fmin_partial, $is_fmax_partial, $residue_info, $locgroup) {
 
-  // get the source feature
-  $sql = "SELECT * FROM {feature}
-         WHERE organism_id = %d and uniquename = '%s'";
-  $srcfeature = db_fetch_object(db_query($sql, $organism->organism_id, $landmark));
-  if (!$srcfeature) {
+  $select = array(
+    'organism_id' => $organism->organism_id,
+    'uniquename' => $landmark,
+  );
+  $options = array('statement_name' => 'sel_feature_organism_id_uniquename');
+  $r = tripal_core_chado_select('feature', array('*'), $select, $options);
+  if (count($r)==0) {
     print "ERROR: cannot find landmark feature $landmark.  Cannot add the feature location record\n";
     return 0;
   }
+  $srcfeature = $r[0];
 
-
-  // TODO: create an attribute that recognizes the residue_info,locgroup, is_fmin_partial and is_fmax_partial, right now these are
-  //       hardcoded to be false and 0 below.
+  // TODO: create an attribute that recognizes the residue_info,locgroup, 
+  //  is_fmin_partial and is_fmax_partial, right now these are
+  //  hardcoded to be false and 0 below.
 
 
   // check to see if this featureloc already exists, but also keep track of the
   // last rank value
   $rank = 0;
   $exists = 0;
-  $featureloc_sql = "SELECT FL.featureloc_id,FL.fmin,FL.fmax,F.uniquename as srcname,
-                       rank
-                    FROM {featureloc} FL
-                      INNER JOIN {feature} F on F.feature_id = FL.srcfeature_id
-                    WHERE FL.feature_id = %d
-                    ORDER BY rank ASC";
-  $recs = db_query($featureloc_sql, $feature->feature_id);
-  while ($featureloc = db_fetch_object($recs)) {
-    if (strcmp($featureloc->srcname, $landmark)==0 and
-      $featureloc->fmin == $fmin and $featureloc->fmax == $fmax) {
-        // this is the same featureloc, so do nothing... no need to update
-        //TODO: need more checks here
+  $select = array('feature_id' => $feature->feature_id);
+  $options = array(
+    'statement_name' => 'sel_featureloc_feature_id',
+    'order_by' => array(
+       'rank' => 'ASC'
+    ),
+  );  
+  $locrecs = tripal_core_chado_select('featureloc', array('*'), $select, $options);
+
+  foreach ($locrecs as $featureloc) {
+    $select = array('feature_id' => $featureloc->srcfeature_id);
+    $options = array('statement_name' => 'sel_feature_feature_id');
+    $locsfeature = tripal_core_chado_select('feature', array('*'), $select, $options);
+
+    // check to make sure we don't already have this featureloc record
+    // if we do we don't want to readd it
+//    print "Src Name:'" . $locsfeature[0]->name . "' == '$landmark'\n";
+//    print "Fmin:    '$featureloc->fmin' == '$fmin'\n";
+//    print "Fmax:    '$featureloc->fmax' == '$fmax'\n";
+//    print "Strand:  '$featureloc->strand' == '$strand'\n";        
+    
+    // the source feature name and at least the fmin and fmax must be the same
+    // for an update of the featureloc, otherwise we'll insert a new record.
+    if (strcmp($locsfeature[0]->name, $landmark)==0 and 
+       ($featureloc->fmin == $fmin or $featureloc->fmax == $fmax)){
+      $match = array('featureloc_id' => $featureloc->featureloc_id);
+      $values = array();
+      $exists = 1;
+      if($featureloc->fmin != $fmin){
+         $values['fmin'] = $fmin;
+      }
+      if($featureloc->fmax != $fmax) {
+         $values['fmax'] = $fmax;
+      }
+      if($featureloc->strand != $strand){
+         $values['strand'] = $strand;
+      }
+      if(count($values) > 0){
+        tripal_core_chado_update('featureloc', $match, $values);
+        print "   Updated featureloc\n";
+      }
+      else {
         print "   No change to featureloc\n";
-        $exists = 1;
+      }
     }
-  $rank = $featureloc->rank + 1;
+    $rank = $featureloc->rank + 1;
   }
   if (!$exists) {
-    $rank++;
+
     // this feature location is new so add it
     if (!$phase) {
       $phase = 'NULL';
     }
-    if (strcmp($is_fmin_partial, 'f')==0) {
+    if (strcmp($is_fmin_partial, 'f')==0 or !$is_fmin_partial) {
       $is_fmin_partial = 'false';
     }
-    elseif (strcmp($is_fmin_partial, 't')==0) {
+    elseif (strcmp($is_fmin_partial, 't')==0 or $is_fmin_partial = 1) {
       $is_fmin_partial = 'true';
     }
-    if (strcmp($is_fmax_partial, 'f')==0) {
+    if (strcmp($is_fmax_partial, 'f')==0 or !$is_fmax_partial) {
       $is_fmax_partial = 'false';
     }
-    elseif (strcmp($is_fmax_partial, 't')==0) {
+    elseif (strcmp($is_fmax_partial, 't')==0 or $is_fmax_partial = 1) {
       $is_fmax_partial = 'true';
     }
-    print "   Adding featureloc $srcfeature->uniquename fmin: $fmin, fmax: $fmax, strand: $strand, phase: $phase, rank: $rank\n";
-    $fl_isql = "INSERT INTO {featureloc}
-                  (feature_id, srcfeature_id, fmin, is_fmin_partial, fmax, is_fmax_partial,
-                   strand, phase, residue_info, locgroup, rank)
-               VALUES (%d,%d,%d,%s,%d,%s,%d,%s,'%s',%d,%d)";
-    $result = db_query($fl_isql, $feature->feature_id, $srcfeature->feature_id, $fmin, $is_fmin_partial, $fmax, $is_fmax_partial,
-      $strand, $phase, $residue_info, $locgroup, $rank);
-    if (!$result) {
+    print "   Adding featureloc $srcfeature->uniquename fmin: $fmin (is_partial: $is_fmin_partial), fmax: $fmax (is_partial: $is_fmin_partial), strand: $strand, phase: $phase, rank: $rank\n";
+    $values = array(
+       'feature_id'      => $feature->feature_id,
+       'srcfeature_id'   => $srcfeature->feature_id,
+       'fmin'            => $fmin,
+       'is_fmin_partial' => $is_fmin_partial,
+       'fmax'            => $fmax,
+       'is_fmax_partial' => $is_fmax_partial,
+       'strand'          => $strand,
+       'phase'           => $phase,
+       'residue_info'    => $residue_info,
+       'locgroup'        => $locgroup,
+       'rank'            => $rank 
+    );
+    $options = array('statement_name' => 'ins_featureloc_all');
+    $success = tripal_core_chado_insert('featureloc', $values, $options);
+    if (!$success) {
       print "ERROR: failed to insert featureloc\n";
       exit;
       return 0;

+ 4 - 1
tripal_views/tripal_views.views.inc

@@ -75,6 +75,9 @@ function tripal_views_views_handlers() {
       'tripal_views_handler_filter_select_string' => array(
         'parent' => 'chado_views_handler_filter_string',
       ),
+      'tripal_views_handler_filter_sequence' => array(
+        'parent' => 'chado_views_handler_filter_string',
+      ),
       
       // Custom Tripal Field Handlers
       'tripal_views_handler_field_aggregate' => array(
@@ -424,7 +427,7 @@ function tripal_views_views_data() {
     'title' => t('Search Results'),
     'help' => t('Delay results until Apply/Search is clicked by the user.'),
     'filter' => array(
-      'handler' => 'views_handler_filter_no_results',
+      'handler' => 'tripal_views_handler_filter_no_results',
     ),
   );
 

+ 341 - 0
tripal_views/views/handlers/tripal_views_handler_field_sequence.inc

@@ -0,0 +1,341 @@
+<?php
+
+/**
+ * @file
+ * A chado wrapper for the views_handler_field.
+ *
+ * Handles display of sequence data.  If will aggregate sequences that need
+ * to be aggregated (e.g. coding sequences) and provide 
+ */
+class tripal_views_handler_field_sequence extends chado_views_handler_field {
+
+  function init(&$view, $options) {
+    parent::init($view, $options);
+    
+    // to speed things up we need to make sure we have a persistent connection
+    tripal_db_persistent_chado(); 
+    
+    if (!tripal_core_is_sql_prepared('sequence_by_parent')) {
+      // prepare the queries we're going to use later during the render phase
+      // This SQL statement uses conditionals in the select clause to handle
+      // cases cases where the alignment is in the reverse direction and when
+      // the upstream and downstream extensions go beyond the lenght of the 
+      // parent sequence.
+      $psql ='PREPARE sequence_by_parent (int, int, int) AS 
+              SELECT
+                OF.name srcname, FL.srcfeature_id, FL.strand,  
+
+                CASE 
+                  WHEN FL.strand >= 0 THEN 
+                    CASE 
+                       WHEN FL.fmin - $1 <= 0 THEN 0
+                       ELSE FL.fmin - $1
+                    END
+                  WHEN FL.strand < 0 THEN
+                    CASE 
+                       WHEN FL.fmin - $1 <= 0 THEN 0
+                       ELSE FL.fmin - $1
+                    END                   
+                END as adjfmin,                                
+                                
+                CASE 
+                  WHEN FL.strand >= 0 THEN
+                    CASE 
+                      WHEN FL.fmax + $2 > OF.seqlen THEN OF.seqlen 
+                      ELSE FL.fmax + $2
+                    END
+                  WHEN FL.strand < 0 THEN
+                    CASE
+                      WHEN FL.fmax + $2 > OF.seqlen THEN OF.seqlen
+                      ELSE FL.fmax + $2   
+                    END               
+                END as adjfmax,     
+                
+                CASE 
+                  WHEN FL.strand >= 0 THEN 
+                    CASE 
+                       WHEN FL.fmin - $1 <= 0 THEN FL.fmin
+                       ELSE $1
+                    END
+                  ELSE
+                    CASE 
+                       WHEN FL.fmax + $1 > OF.seqlen THEN OF.seqlen - FL.fmax
+                       ELSE $1
+                    END                   
+                END as upstream,
+                
+                CASE 
+                  WHEN FL.strand >= 0 THEN 
+                    CASE 
+                       WHEN FL.fmax + $2 > OF.seqlen THEN OF.seqlen - FL.fmax
+                       ELSE $2
+                    END
+                  ELSE
+                    CASE 
+                       WHEN FL.fmin - $2 <= 0 THEN FL.fmin
+                       ELSE $2
+                    END                   
+                END as downstream,            
+                              
+                CASE 
+                  WHEN FL.strand >= 0 THEN 
+                    CASE 
+                       WHEN FL.fmin - $1 <= 0 THEN substring(OF.residues from 1 for ((FL.fmax - FL.fmin) + $1 + $2)) 
+                       ELSE substring(OF.residues from (FL.fmin + 1 - $1) for ((FL.fmax - FL.fmin) + $1 + $2))
+                    END
+                  WHEN FL.strand < 0 THEN
+                    CASE 
+                      WHEN FL.fmin - $2 <= 0 THEN substring(OF.residues from 1 for ((FL.fmax - FL.fmin) + $1 + $2)) 
+                      ELSE substring(OF.residues from (FL.fmin + 1 - $2) for ((FL.fmax - FL.fmin) + $1 + $2))      
+                    END
+                END as residues
+              FROM featureloc FL 
+                INNER JOIN feature SF on FL.feature_id = SF.feature_id
+                INNER JOIN feature OF on FL.srcfeature_id = OF.feature_id                
+              WHERE SF.feature_id = $3';
+              
+      $status = chado_query($psql);
+      if (!$status) {
+        watchdog('tripal_views_handler_field_sequence', 
+          "init: not able to prepare SQL statement '%name'", 
+          array('%name' => 'sequence_by_parent'), 'WATCHDOG ERROR');
+      }
+      // this query is meant to get all of the sub features of any given
+      // feature (arg #1) and order them as they appear on the reference
+      // feature (arg #2).
+      $psql ='PREPARE sub_features (int, int) AS 
+              SELECT SF.feature_id, CVT.name as type_name, SF.type_id
+              FROM feature_relationship FR
+                INNER JOIN feature SF on SF.feature_id = FR.subject_id
+                INNER JOIN cvterm CVT on CVT.cvterm_id = SF.type_id
+                INNER JOIN featureloc FL on FL.feature_id = FR.subject_id
+                INNER JOIN feature PF on PF.feature_id = FL.srcfeature_id
+              WHERE FR.object_id = $1 and PF.feature_id = $2
+              ORDER BY FL.fmin ASC';
+              
+      $status = chado_query($psql);
+      if (!$status) {
+        watchdog('tripal_views_handler_field_sequence', 
+          "init: not able to prepare SQL statement '%name'", 
+          array('%name' => 'ssub_features'), 'WATCHDOG ERROR');
+      }
+      $psql ='PREPARE count_sub_features (int, int) AS 
+              SELECT count(*) as num_children
+              FROM feature_relationship FR
+                INNER JOIN feature SF on SF.feature_id = FR.subject_id
+                INNER JOIN cvterm CVT on CVT.cvterm_id = SF.type_id
+                INNER JOIN featureloc FL on FL.feature_id = FR.subject_id
+                INNER JOIN feature PF on PF.feature_id = FL.srcfeature_id
+              WHERE FR.object_id = $1 and PF.feature_id = $2';
+              
+      $status = chado_query($psql);
+      if (!$status) {
+        watchdog('tripal_views_handler_field_sequence', 
+          "init: not able to prepare SQL statement '%name'", 
+          array('%name' => 'count_sub_features'), 'WATCHDOG ERROR');
+      }
+    }
+  }
+
+  /**
+   * Defines the options form (form available to admin when they add a field to a view)
+   */
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+
+    $form['display'] = array(
+      '#type' => 'fieldset',
+      '#title' => 'Format Output',
+      '#description' => t('Alter the way a sequence is displayed')
+    );
+
+    $form['display']['num_bases_per_line'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Number of bases per line'),
+      '#description' => t('Specify the number of bases per line. An HTML <br> tag ' .
+        'will be inserted after the number of bases indicated. If no value is ' .
+        'provided. The sequence will be one long string (default)'),
+      '#default_value' => $this->options['display']['num_bases_per_line'],
+    );
+    $form['display']['derive_from_parent'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Derive sequence from parent'),
+      '#description' => t('Rather than use the sequence from the \'residues\' of this feature, you may ' .
+        'derive the sequence from the parent features to which it is aligned. This is useful in the case that the feature ' .
+        'does not have sequence associated with it and we need to get it through it\'s alignment. ' . 
+        'Note: this will slow queries with large numbers of results on the page.'),
+      '#default_value' => $this->options['display']['derive_from_parent'],
+    );
+    $form['display']['aggregate'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Aggregate sub features'),
+      '#description' => t('If the feature has sub features (e.g. CDS of an mRNA) then check this '.
+        'box to filter the sequence to only include the sub features.  Gaps between sub features will be '.
+        'excluded from the sequence.  This is useful for obtaining a complete CDS from an mRNA '.
+        'without intronic sequence'),
+      '#default_value' => $this->options['display']['aggregate'],
+    );
+  }
+
+  function query() {
+    parent::query();
+    
+    // if we are going to get the sequence from the parent then
+    // we will need to do more queries in the render function
+    // and we must have the feature_id to do those
+    if($this->options['display']['derive_from_parent']){
+      $this->ensure_my_table();
+      $this->query->add_field($this->table,'feature_id');
+    }
+  }
+    
+  /**
+  * Prior to display of results we want to format the sequence
+  */
+  function render($values) {
+    $residues = '';
+      
+    // get the number of bases to show per line
+    $num_bases_per_line = $this->options['display']['num_bases_per_line'];
+
+    // get the residues from the feature.residues column
+    $field = $this->field_alias;
+    
+    // get the feature id
+    $feature_id = $values->feature_feature_id;
+    
+    // the upstream and downstream values get set by the 
+    // tripal_views_handlers_filter_sequence.inc
+    $upstream = $this->view->sequence_q['upstream'];
+    $downstream = $this->view->sequence_q['downstream'];           
+    if (!$upstream) {
+       $upstream = 0;
+    }
+    if (!$downstream) {
+       $downstream = 0;
+    }
+    
+    // if we need to get the sequence from the parent but there is no aggregation
+    // then do so now.
+    if ($this->options['display']['derive_from_parent']) {             
+      
+      // execute our prepared statement
+      if (tripal_core_is_sql_prepared('sequence_by_parent')) {
+        $sql = "EXECUTE sequence_by_parent (%d, %d, %d)";
+        $parents = chado_query($sql, $upstream, $downstream, $feature_id);
+      }
+
+      while ($parent = db_fetch_object($parents)) {  
+        $seq = '';  // initialize the sequence for each parent
+                
+        // if we are to aggregate then we will ignore the feature returned
+        // by the query above and rebuild it using the sub features
+        if ($this->options['display']['aggregate']){
+          
+          // now get the sub features that are located on the parent.
+          $sql = "EXECUTE sub_features (%d, %d)";
+          $children = chado_query($sql, $feature_id, $parent->srcfeature_id);
+          $sql = "EXECUTE count_sub_features (%d, %d)";
+          $num_children = db_fetch_object(chado_query($sql, $feature_id, $parent->srcfeature_id));
+                 
+          // iterate through the sub features and concat their sequences. They
+          // should already be in order.
+          $types = array();
+          $i = 0;
+          while($child = db_fetch_object($children)) {
+            // keep up with the types
+            if(!in_array($child->type_name,$types)){
+              $types[] = $child->type_name;
+            }
+            
+            $sql = "EXECUTE sequence_by_parent (%d, %d, %d)";
+
+            // if the first sub feature we need to include the upstream bases
+            if ($i == 0 and $parent->strand >= 0) {
+              // -------------------------- ref
+              //    ....---->  ---->        
+              //     up    1       2         
+              $q = chado_query($sql, $upstream, 0, $child->feature_id);
+            }
+            elseif ($i == 0 and $parent->strand < 0) {
+              // -------------------------- ref
+              //    ....<----  <----
+              //    down  1       2
+              $q = chado_query($sql, 0, $downstream, $child->feature_id);
+            }          
+            // if the last sub feature we need to include the downstream bases
+            elseif ($i == $num_children->num_children - 1 and $parent->strand >= 0) {
+              // -------------------------- ref
+              //        ---->  ---->....
+              //          1       2 down
+              $q = chado_query($sql, 0, $downstream, $child->feature_id);
+            }
+            elseif ($i == $num_children->num_children - 1 and $parent->strand < 0) {
+              // -------------------------- ref
+              //        <----  <----....
+              //          1       2  up
+              $q = chado_query($sql, $upstream, 0, $child->feature_id);
+            }
+            
+            // for internal sub features we don't want upstream or downstream bases
+            else {         
+              $sql = "EXECUTE sequence_by_parent (%d, %d, %d)";
+              $q = chado_query($sql, 0, 0, $child->feature_id);
+            }
+            
+            $j = 0;
+            while($subseq = db_fetch_object($q)){
+              // there should only be one mapping for each sub feature to the 
+              // same parent.  If there are more then we can't resolve it so
+              // return a message 
+              if($j > 0 ){
+                return 'Cannot determine sequence.  Sub features map to multiple locations';
+              }
+              // concatenate the sequences of all the sub features            
+              $seq .= $subseq->residues;   
+              $j++;
+            }                 
+            $i++;
+          } 
+        } 
+        // if this isn't an aggregate then use the parent residues
+        else {
+           $seq = $parent->residues;
+        }
+                              
+        // get the reverse compliment if feature is on the reverse strand
+        $dir = 'forward';
+        if ($parent->strand < 0) {
+          $seq = trpial_feature_reverse_complement($seq);
+          $dir = 'reverse';
+        }
+        
+        // now format for display
+        $seq = wordwrap($seq, $num_bases_per_line, "<br>", TRUE);
+        $residues .= ">" . $parent->srcname . ":" . ($parent->adjfmin + 1) . ".." . $parent->adjfmax ." ($dir). ";
+        if (count($types) > 0) {
+          $residues .= "Excludes all bases but those of type(s): " . implode(', ',$types) . ". " ;
+        }
+        if ($parent->upstream > 0) {
+           $residues .= "Includes " . $parent->upstream . " bases upstream.  ";
+        }
+        if ($parent->downstream > 0) {
+           $residues .= "Includes " . $parent->downstream . " bases downstream.  ";
+        }
+        $residues .= "<br>\n$seq\n<br>";          
+      }
+    }
+    // if we are not getting the sequence from the parent sequence then
+    // use what comes through from the feature record
+    else {
+      $residues = $values->$field; 
+      $residues = wordwrap($residues, $num_bases_per_line, "<br>", TRUE);  
+    }
+    
+    // format the residues for display
+    if($residues and $num_bases_per_line){
+      $residues = '<span style="font-family: monospace;">' . $residues . '</span>';
+    }
+    return $residues;         
+  } 
+}

+ 90 - 0
tripal_views/views/handlers/tripal_views_handler_filter_sequence.inc

@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Purpose: This Handler provides a file upload field by extending the
+ * views_handler_filter object.
+ *
+ * @ingroup tripal_views_integration
+ */
+class tripal_views_handler_filter_sequence extends views_handler_filter {
+
+  /**
+   * Defines the value field in both the views filter options form
+   *   and the exposed form
+   */
+  function value_form(&$form, &$form_state) {
+    parent::value_form($form, $form_state);
+
+    $this->value_form = array(
+       '#type' => 'checkbox',
+       '#title' => t('Provide fields for upstream and downstream'),
+       '#default_value' => $this->value,
+       '#multiple' => FALSE,
+       '#description' => 
+         t('Check here to provide form fields to allow the user to retrieve ' .
+           'sequences beyond the feature residues.  This requires that the ' .
+           'feature is aligned to a parent sequence and the optin to derive ' .
+           'the sequence from the parent is turned on in the field settings.'),
+    );
+    $form['value'] = &$this->value_form;
+  }
+
+  /**
+   * Ensures the upload field gets rendered when the filter is exposed. It also
+   * changes the form type from a GET to a POST so that file uploads will work.
+   */
+  function exposed_form(&$form, &$form_state) {
+
+    // don't do anything if the form isn't exposed.
+    if (empty($this->options['exposed'])) {
+      return;
+    }
+    $form[$value] = $form['value'];
+    unset($form[$value]['#title']);
+
+    // add the upstream and downstream boxes
+    $form['upstream'] = array(
+       '#type' => 'textfield',
+       '#title' => t('Get Upstream Bases'),
+       '#description' => t('Specify the number of upstream bases to include in the sequnce'),
+    );
+    $form['downstream'] = array(
+       '#type' => 'textfield',
+       '#title' => t('Get Downstream Bases'),
+       '#description' => t('Specify the number of upstream bases to include in the sequnce'),
+    );
+  }
+
+  /**
+   *  Validates the input form
+   */
+  function exposed_validate(&$form, &$form_state) {
+     $upstream = $form_state['values']['upstream'];
+     $downstream = $form_state['values']['downstream'];
+     
+     if($upstream < 0){
+        form_set_error($form_state['values']['upstream'], 
+           'Please provide a positive number for upstream bases');
+     }
+     if($upstream and !preg_match('/^\d+$/',$upstream)){
+        form_set_error($form_state['values']['upstream'], 
+           'Please provide a decimal number for upstream bases');
+     }
+
+     if($downstream < 0){
+        form_set_error($form_state['values']['upstream'], 
+           'Please provide a positive number for downstream bases');
+     }
+     if($downstream and !preg_match('/^\d+$/',$downstream)){
+        form_set_error($form_state['values']['upstream'], 
+           'Please provide a decimal number for downstream bases');
+     }
+     
+     // we need the values provided by the user so that the field
+     // handler can generate the results properly.  These values
+     // will get stored in the view object.  This may not be the best place
+     $this->view->sequence_q['upstream'] = $upstream;
+     $this->view->sequence_q['downstream'] = $downstream;
+  }
+}