Browse Source

Merge branch '6.x-1.x' into 7.x-2.x

spficklin 11 years ago
parent
commit
69e0f8271f

+ 14 - 2
tripal_analysis/tripal_analysis.module

@@ -391,14 +391,26 @@ function chado_analysis_update($node) {
 
   // now add in the properties by first removing any the analysis
   // already has and adding the ones we have
-  tripal_core_chado_delete('analysisprop', array('analysis_id' => $analysis_id));
+  $sql = "
+    DELETE FROM {analysisprop} WHERE analysis_id = %d AND type_id IN (
+      SELECT CVT.cvterm_id
+      FROM  {cvterm} CVT
+        INNER JOIN {cv} ON CVT.cv_id = CV.cv_id
+      WHERE CV.name = 'analysis_property')
+  ";
+  $success = chado_query($sql, $analysis_id);
+  if (!$success) {
+  	drupal_set_message("Cannot update analysis properties", "error");
+  	watchdog('t_analysis', "Cannot update analysis properties.", array(), WATCHDOG_ERROR);
+  	return;
+  }
   foreach ($properties as $property => $elements) {
     foreach ($elements as $rank => $value) {
       $status = tripal_analysis_insert_property($analysis_id, $property, $value, FALSE, 'analysis_property');
       if (!$status) {
         drupal_set_message("Error cannot add property: '$property'", "error");
         watchdog('t_analysis', "Error cannot add property: '%prop'",
-        array('%prop' => $property), WATCHDOG_ERROR);
+          array('%prop' => $property), WATCHDOG_ERROR);
       }
     }
   }

+ 2 - 2
tripal_core/api/tripal_core_ahah.api.inc

@@ -48,10 +48,10 @@ function tripal_core_ahah_init_form() {
  *
  * @ingroup tripal_ahah_api
  */
-function tripal_core_ahah_prepare_form() {
+function tripal_core_ahah_prepare_form(&$form_state = array()) {
   
   // Retrieve the form from the cache
-  $form_state = array('storage' => NULL);
+  $form_state['storage'] = NULL;
   $form_build_id = filter_xss($_POST['form_build_id']);
   if (!$form_build_id) {
     return FALSE;

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

@@ -1664,7 +1664,7 @@ function tripal_core_expand_chado_vars($object, $type, $to_expand, $table_option
         // if we did not expand this table we should return a message that the foreign table
         // could not be expanded
         if (!$did_expansion) {
-          watchdog('tripal_core', 'tripal_core_expand_chado_vars: Could not expand table, %table. It is ',
+          watchdog('tripal_core', 'tripal_core_expand_chado_vars: Could not expand table, %table. It is ' .
             'not in a foreign key relationship with the base object nor with any other expanded table. ' .
             'Check the table definition to ensure that a proper foreign key relationship is present.',
             array('%table' => $foreign_table), WATCHDOG_ERROR);

+ 2 - 1
tripal_core/includes/form_elements.inc

@@ -117,7 +117,8 @@ function file_upload_combo_validate($element, &$form) {
       foreach ($vals as $i => $value) {
         $values[] = trim($value);
       }
-    }       
+    }
+    fclose($fh);       
   }
   
   // add a new 'items_array' element that contains the array of 

+ 142 - 70
tripal_feature/api/tripal_feature.api.inc

@@ -466,7 +466,16 @@ function tripal_feature_reverse_complement($sequence) {
  * @param $downstream
  *   An integer specifying the number of downstream bases to include in the 
  *   output.
- *
+ * @param $sub_features
+ *   Only include sub features (or child features) of the types provided in the array 
+ * @param $relationship
+ *   If a relationship name is provided (e.g. sequence_of) then any sequences that
+ *   are in relationships of this type with matched sequences are also included
+ * @param $rel_part
+ *   If a relationship is provided in the preceeding argument then the rel_part
+ *   must be either 'object' or 'subject' to indicate which side of the 
+ *   relationship the matched features belong
+ *      
  * @return
  *   The DNA/protein sequence formated as requested.
  *
@@ -474,7 +483,7 @@ function tripal_feature_reverse_complement($sequence) {
  */
 function tripal_feature_get_formatted_sequence($feature_id, $feature_name, 
   $num_bases_per_line, $derive_from_parent, $aggregate, $output_format,
-  $upstream, $downstream, $sub_features = array()) {
+  $upstream, $downstream, $sub_features = array(), $relationship = '', $rel_part = '') {
   
   // to speed things up we need to make sure we have a persistent connection
   $connection = tripal_db_persistent_chado(); 
@@ -486,6 +495,68 @@ function tripal_feature_get_formatted_sequence($feature_id, $feature_name,
      $downstream = 0;
   }
   
+  if ($rel_part == "object" or $rel_part == "subject") { 
+    if($rel_part == "subject") {
+      $psql = '
+        PREPARE feature_rel_get_object (int, text) AS
+        SELECT FO.feature_id, FO.name, FO.uniquename, CVTO.name as feature_type, O.genus, O.species
+        FROM feature FS
+          INNER JOIN feature_relationship FR ON FR.subject_id   = FS.feature_id
+          INNER JOIN cvterm CVTFR            ON CVTFR.cvterm_id = FR.type_id
+          INNER JOIN feature FO              ON FO.feature_id   = FR.object_id
+          INNER JOIN cvterm CVTO             ON CVTO.cvterm_id  = FO.type_id
+          INNER JOIN organism O              ON O.organism_id   = FO.organism_id
+        WHERE 
+          FS.feature_id = $1 AND
+          CVTFR.name    = $2
+      ';  
+      $status = tripal_core_chado_prepare('feature_rel_get_object', $psql, array('int', 'text'));
+      if (!$status) {
+        watchdog('tripal_feature', "init: not able to prepare SQL statement '%name'", 
+          array('%name' => 'feature_by_subject'), 'WATCHDOG ERROR');
+      }    
+      $sql = "EXECUTE feature_rel_get_object(%d,'%s')";
+      $features = chado_query($sql, $feature_id, $relationship); 
+    }
+    if($rel_part == "object") {
+      $psql = '
+        PREPARE feature_rel_get_subject (int, text) AS
+        SELECT FS.feature_id, FS.name, FS.uniquename, CVTO.name as feature_type, O.genus, O.species
+        FROM feature FO
+          INNER JOIN feature_relationship FR ON FR.object_id    = FO.feature_id
+          INNER JOIN cvterm CVTFR            ON CVTFR.cvterm_id = FR.type_id
+          INNER JOIN feature FS              ON FS.feature_id   = FR.subject_id
+          INNER JOIN cvterm CVTO             ON CVTO.cvterm_id  = FS.type_id
+          INNER JOIN organism O              ON O.organism_id   = FS.organism_id
+        WHERE 
+          FO.feature_id = $1 AND
+          CVTFR.name    = $2
+      ';
+      $status = tripal_core_chado_prepare('feature_rel_get_subject', $psql, array('int', 'text'));
+      if (!$status) {
+        watchdog('tripal_feature', "init: not able to prepare SQL statement '%name'", 
+          array('%name' => 'feature_by_object'), 'WATCHDOG ERROR');
+      }
+      $sql = "EXECUTE feature_rel_get_subject(%d,'%s')";
+      $features = chado_query($sql, $feature_id, $relationship);     
+    }
+    $sequences = '';
+    while ($feature = db_fetch_object($features)) {  
+
+      // recurse and get the sequences for these in the relationship
+      if ($rel_part == "subject") {
+        $defline = "$feature_name, $relationship, $feature->uniquename $feature->feature_type ($feature->genus $feature->species)";
+      }
+      if ($rel_part == "object") {
+        $defline = "$feature->uniquename $feature->feature_type ($feature->genus $feature->species), $relationship, $feature_name";
+      }
+      $sequences .= tripal_feature_get_formatted_sequence($feature->feature_id, $defline, 
+        $num_bases_per_line, $derive_from_parent, $aggregate, $output_format,
+        $upstream, $downstream, $sub_features,'','');  
+    }  
+    return $sequences;  
+  }
+  
   // prepare statements we'll need to use later
   if (!tripal_core_is_sql_prepared('sequence_by_parent')) {
     // prepare the queries we're going to use later during the render phase
@@ -493,72 +564,73 @@ function tripal_feature_get_formatted_sequence($feature_id, $feature_name,
     // 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 srcname, srcfeature_id, strand, srctypename, typename,
-              fmin, fmax, upstream, downstream, adjfmin, adjfmax, 
-              substring(residues from (adjfmin + 1) for (upstream + (fmax - fmin) + downstream))  as residues,
-              genus, species
-            FROM (
-              SELECT
-                OF.name srcname, FL.srcfeature_id, FL.strand, 
-                OCVT.name as srctypename, SCVT.name as typename,
-                FL.fmin, FL.fmax, OO.genus, OO.species,
-                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 - $2 <= 0 THEN 0
-                       ELSE FL.fmin - $2
-                    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 + $1 > OF.seqlen THEN OF.seqlen
-                      ELSE FL.fmax + $1   
-                    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,  
-                OF.residues                                                     
-              FROM {featureloc} FL 
-                INNER JOIN {feature} SF on FL.feature_id = SF.feature_id
-                INNER JOIN {cvterm} SCVT on SF.type_id = SCVT.cvterm_id
-                INNER JOIN {feature} OF on FL.srcfeature_id = OF.feature_id                
-                INNER JOIN {cvterm} OCVT on OF.type_id = OCVT.cvterm_id
-                INNER JOIN {organism} OO on OF.organism_id = OO.organism_id
-              WHERE SF.feature_id = $3 and NOT (OF.residues = \'\' or OF.residues IS NULL)) as tbl1
+    $psql ='
+      PREPARE sequence_by_parent (int, int, int) AS 
+      SELECT srcname, srcfeature_id, strand, srctypename, typename,
+        fmin, fmax, upstream, downstream, adjfmin, adjfmax, 
+        substring(residues from (adjfmin + 1) for (upstream + (fmax - fmin) + downstream))  as residues,
+        genus, species
+      FROM (
+        SELECT
+          OF.name srcname, FL.srcfeature_id, FL.strand, 
+          OCVT.name as srctypename, SCVT.name as typename,
+          FL.fmin, FL.fmax, OO.genus, OO.species,
+          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 - $2 <= 0 THEN 0
+                 ELSE FL.fmin - $2
+              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 + $1 > OF.seqlen THEN OF.seqlen
+                ELSE FL.fmax + $1   
+              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,  
+          OF.residues                                                     
+        FROM {featureloc} FL 
+          INNER JOIN {feature} SF on FL.feature_id = SF.feature_id
+          INNER JOIN {cvterm} SCVT on SF.type_id = SCVT.cvterm_id
+          INNER JOIN {feature} OF on FL.srcfeature_id = OF.feature_id                
+          INNER JOIN {cvterm} OCVT on OF.type_id = OCVT.cvterm_id
+          INNER JOIN {organism} OO on OF.organism_id = OO.organism_id
+        WHERE SF.feature_id = $3 and NOT (OF.residues = \'\' or OF.residues IS NULL)) as tbl1
     ';              
     $status = tripal_core_chado_prepare('sequence_by_parent', $psql, array('int', 'int', 'int'));
     if (!$status) {
@@ -627,7 +699,7 @@ function tripal_feature_get_formatted_sequence($feature_id, $feature_name,
         $i = 0;
         while ($child = db_fetch_object($children)) {
           // if the callee has specified that only certain sub features should be
-          // included then continue of this child is not one of those allowed
+          // included then continue if this child is not one of those allowed
           // subfeatures
           if (count($sub_features) > 0 and !in_array($child->type_name, $sub_features)) {
              continue;
@@ -704,7 +776,7 @@ function tripal_feature_get_formatted_sequence($feature_id, $feature_name,
       elseif ($output_format == 'fasta_txt') {
          $seq = wordwrap($seq, $num_bases_per_line, "\r\n", TRUE);
       }
-      $residues .= ">$feature_name. Sequence derived from $parent->srctypename of $parent->genus $parent->species: $parent->srcname:" . ($parent->adjfmin + 1) . ".." . $parent->adjfmax ." ($dir). ";
+      $residues .= ">$feature_name. Sequence derived from feature of type, '$parent->srctypename', of $parent->genus $parent->species: $parent->srcname:" . ($parent->adjfmin + 1) . ".." . $parent->adjfmax ." ($dir). ";
       if (count($types) > 0) {
         $residues .= "Excludes all bases but those of type(s): " . implode(', ', $types) . ". " ;
       }

+ 2 - 2
tripal_feature/includes/gff_loader.inc

@@ -645,14 +645,14 @@ function tripal_feature_load_gff3($gff_file, $organism_id, $analysis_id,
       // the same parent.
       elseif (array_key_exists('Parent', $tags)) {
         $date = getdate();
-        $attr_uniquename = $tags['Parent'][0] . "-$type-$landmark-" . $date[0] . ":$fmin..$fmax";
+        $attr_uniquename = $tags['Parent'][0] . "-$type-$landmark-" . $date[0] . ":" . ($fmin + 1) . ".." . $fmax;
         $attr_name = $attr_uniquename;
       }
       // generate a unique name based on the date, type and location
       // and set the name to simply be the type
       else {
         $date = getdate();
-        $attr_uniquename = $date[0] . "-$type-$landmark:$fmin..$fmax";
+        $attr_uniquename = $date[0] . "-$type-$landmark:" . ($fmin + 1) . ".." . $fmax;
         $attr_name = $type;
       }      
     }

+ 4 - 3
tripal_feature/includes/seq_extract.inc

@@ -455,7 +455,8 @@ function tripal_feature_seq_extract_form_submit($form, &$form_state) {
  * 
  */
 function tripal_feature_seq_extract_get_features($org_commonname, $genus, $species, $analysis_name, 
-  $type, $feature_name, $upstream, $downstream, $output_format, $derive_from_parent, $aggregate, $child) {
+  $type, $feature_name, $upstream, $downstream, $output_format, $derive_from_parent, $aggregate, 
+  $child, $relationship, $rel_part) {
     
   $sub_features = explode(',', $child);
     
@@ -465,7 +466,7 @@ function tripal_feature_seq_extract_get_features($org_commonname, $genus, $speci
   
   if (!$type and !$feature_name and !$genus) {
     print "Please provide a type, feature name or genus\n";
-     eturn;
+     return;
   }
 
   // get the list of features
@@ -533,7 +534,7 @@ function tripal_feature_seq_extract_get_features($org_commonname, $genus, $speci
     // generate the sequence
     $sequence = tripal_feature_get_formatted_sequence($feature_id, $feature_name, 
       $num_bases_per_line, $derive_from_parent, $aggregate, $output_format,
-      $upstream, $downstream, $sub_features);
+      $upstream, $downstream, $sub_features, $relationship, $rel_part);
     
     // print the sequence
     print $sequence;

+ 1 - 1
tripal_feature/includes/tripal_feature-delete.inc

@@ -87,7 +87,7 @@ function tripal_feature_delete_form_validate($form, &$form_state) {
                      INNER JOIN {cv} CV on CVT.cv_id = CV.cv_id
                      LEFT JOIN {cvtermsynonym} CVTS on CVTS.cvterm_id = CVT.cvterm_id
                   WHERE cv.name = '%s' and (CVT.name = '%s' or CVTS.synonym = '%s')";
-    $cvterm = db_fetch_object(db_query($cvtermsql, 'sequence', $seq_type, $seq_type));
+    $cvterm = db_fetch_object(chado_query($cvtermsql, 'sequence', $seq_type, $seq_type));
     if (!$cvterm) {
       form_set_error('seq_type', t("The Sequence Ontology (SO) term selected for the sequence type is not available in the database. Please check spelling or select another."));
     }

+ 119 - 63
tripal_feature/includes/tripal_feature.admin.inc

@@ -12,6 +12,7 @@
  */
 function tripal_feature_admin() {
 
+  /*
   // before proceeding check to see if we have any
   // currently processing jobs. If so, we don't want
   // to give the opportunity to sync libraries
@@ -20,14 +21,15 @@ function tripal_feature_admin() {
     $active_jobs = TRUE;
   }
   if (!$active_jobs) {
-
+*/
+    get_tripal_feature_admin_form_title_set($form);
     get_tripal_feature_admin_form_url_set($form);
 
     $form['browser'] = array(
        '#type' => 'fieldset',
        '#title' => t('Feature Browser'),
-       '#collapsible' => 1,
-       '#collapsed' => 1 ,
+       '#collapsible' => TRUE,
+       '#collapsed' => TRUE,
     );
     $allowedoptions1  = array(
       'show_feature_browser' => "Show the feature browser on the organism page. The browser loads when page loads. This may be slow for large sites.",
@@ -88,8 +90,8 @@ function tripal_feature_admin() {
     $form['summary'] = array(
        '#type' => 'fieldset',
        '#title' => t('Feature Summary Report'),
-       '#collapsible' => 1,
-       '#collapsed' => 1 ,
+       '#collapsible' => TRUE,
+       '#collapsed' => TRUE,
     );
     $allowedoptions2 ['show_feature_summary'] = "Show the feature summary on the organism page. The summary loads when page loads.";
     $allowedoptions2 ['hide_feature_summary'] = "Hide the feature summary on the organism page. Disables the feature summary.";
@@ -122,11 +124,13 @@ function tripal_feature_admin() {
     get_tripal_feature_admin_form_taxonomy_set($form);
     get_tripal_feature_admin_form_reindex_set($form);
     get_tripal_feature_admin_form_cleanup_set($form);
-  }
+/*  }
   else {
     $form['notice'] = array(
        '#type' => 'fieldset',
-       '#title' => t('Feature Management Temporarily Unavailable')
+       '#title' => t('Feature Management Temporarily Unavailable'),
+       '#collapsible' => FALSE,
+       '#collapsed' => FALSE,
     );
     $form['notice']['message'] = array(
        '#value' => t('Currently, feature management jobs are waiting or ".
@@ -136,6 +140,7 @@ function tripal_feature_admin() {
           "jobs page.'),
     );
   }
+  */
   return system_settings_form($form);
 }
 
@@ -150,47 +155,46 @@ function tripal_feature_admin_validate($form, &$form_state) {
 
   variable_set('chado_browser_feature_types', $form_state['values']['feature_types']);
 
-  // if the user wants to sync up the chado features then
-  // add the job to the management queue
   switch ($form_state['values']['op']) {
 
     case  t('Sync all Features') :
       tripal_add_job('Sync all features', 'tripal_feature',
         'tripal_feature_sync_features', $job_args, $user->uid);
-    break;
+      break;
 
     case t('Set/Reset Taxonomy for all feature nodes') :
-    tripal_add_job('Set all feature taxonomy', 'tripal_feature',
-      'tripal_features_set_taxonomy', $job_args, $user->uid);
-    break;
+      tripal_add_job('Set all feature taxonomy', 'tripal_feature',
+        'tripal_features_set_taxonomy', $job_args, $user->uid);
+      break;
 
     case t('Reindex all feature nodes') :
-    tripal_add_job('Reindex all features', 'tripal_feature',
-      'tripal_features_reindex', $job_args, $user->uid);
-    break;
+      tripal_add_job('Reindex all features', 'tripal_feature',
+        'tripal_features_reindex', $job_args, $user->uid);
+      break;
 
     case t('Clean up orphaned features') :
-    tripal_add_job('Cleanup orphaned features', 'tripal_feature',
-      'tripal_features_cleanup', $job_args, $user->uid);
-    break;
+      tripal_add_job('Cleanup orphaned features', 'tripal_feature',
+        'tripal_features_cleanup', $job_args, $user->uid);
+      break;
 
     case t('Set Browser') :
       variable_set('tripal_feature_browse_setting', $form_state['values']['browse_features']);
       variable_set('tripal_library_feature_browse_setting', $form_state['values']['browse_features_library']);
       variable_set('tripal_analysis_feature_browse_setting', $form_state['values']['browse_features_analysis']);
-    break;
+      break;
 
     case t('Set Summary') :
       variable_set('tripal_feature_summary_setting', $form_state['values']['feature_summary']);
       variable_set('tripal_feature_summary_report_mapping', $form_state['values']['feature_mapping']);
-    break;
+      break;
 
     case t('Set Feature URLs') :
       variable_set('chado_feature_url', $form_state['values']['feature_url']);
       tripal_add_job('Set Feature URLs', 'tripal_feature',
         'tripal_feature_set_urls', $job_args, $user->uid);
-    break;
-    }
+      break;
+  }
+    
 }
 /**
  *
@@ -201,8 +205,8 @@ function get_tripal_feature_admin_form_cleanup_set(&$form) {
   $form['cleanup'] = array(
     '#type' => 'fieldset',
     '#title' => t('Clean Up'),
-    '#collapsible' => 1,
-    '#collapsed' => 1 ,
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
   $form['cleanup']['description'] = array(
     '#type' => 'item',
@@ -233,8 +237,8 @@ function get_tripal_feature_admin_form_reindex_set(&$form) {
   $form['reindex'] = array(
     '#type' => 'fieldset',
     '#title' => t('Index/Reindex'),
-    '#collapsible' => 1,
-    '#collapsed' => 1 ,
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
   $form['reindex']['description'] = array(
      '#type' => 'item',
@@ -262,8 +266,8 @@ function get_tripal_feature_admin_form_taxonomy_set(&$form) {
   $form['taxonomy'] = array(
     '#type' => 'fieldset',
     '#title' => t('Set Taxonomy'),
-    '#collapsible' => 1,
-    '#collapsed' => 1 ,
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
 
   $form['taxonomy']['description'] = array(
@@ -299,55 +303,107 @@ function get_tripal_feature_admin_form_taxonomy_set(&$form) {
 
 }
 
-function get_tripal_feature_admin_form_url_set(&$form) {
+/**
+ * 
+ * @param $form
+ */
+function get_tripal_feature_admin_form_title_set(&$form) {
 
-  $form['url'] = array(
+  $form['title'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Feature URL Path')
+    '#title' => t('Feature Page Titles'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
-  $form['url']['desc'] = array(
+  $form['title']['desc'] = array(
     '#type'        => 'markup',
-    '#value' => t('Each synced feature will have a unique URL which consists of '.
-                        'the site domain followed by a unique identifer: for '.
-                        'example http://my-tripal-site.org/ID1034, where the '.
-                        'element just after the final slash is the unique '.
-                        'identifier for the feature.'),
+    '#value' => t('Each synced feature must have a unique page title, however, features
+                   may have the same name if they are of different types or from
+                   different organisms.  Therefore, we must be sure that the 
+                   page titles can uniquely identify the feature being viewed.  Select
+                   an option below that will uniquely identify all features on your site.'),
   );
   $options = array(
-    'internal ID'          => 'Internal ID (Chado feature_id)',
-    'feature unique name'  => 'Feature unique name',
-    'feature name'         => 'Feature name',
-    'genus_species_uqname' => 'Genus + species + unique name (e.g. http://your.site.url/[genus]/[genus]_[species]/[unique_name]',
-    'genus_species_name'   => 'Genus + species + name (e.g. http://your.site.url/[genus]/[genus]_[species]/[name]',
+    'feature_unique_name'  => 'Feature unique name',
+    'feature_name'         => 'Feature name',
+    'unique_constraint'    => 'Feature Name, uniquename, type and species',
   );
-  
-  $form['url']['chado_feature_url'] = array(
-    '#title'         => t('Unique Identifier'),
+  $form['title']['chado_feature_title'] = array(
+    '#title'         => t('Feature Page Titles'),
     '#type'          => 'radios',
-    '#description'   => t('Choose an identifier type '.
-                        'from the list above that is guaranteed to be unique in your synced '.
-                        'dataset. If in doubt it is safest to coose the internal ID. '.
-                        'The descrpitor need not be unique amont the total dataset. '.
-                        'It only need be unique among the synced dataset.'),
+    '#description'   => t('Choose a title type  from the list above that is 
+      guaranteed to be unique for all features.  If in doubt it is safest to 
+      choose the last option as that guarantees uniqueness. Click the 
+      \'Save Configuration\' button at the bottom to save your selection.'),
     '#required'      => FALSE,
     '#options'       => $options,
-    '#default_value' => variable_get('chado_feature_url', 'internal ID'),
+    '#default_value' => variable_get('chado_feature_title', 'unique_constraint'),
+  );
+}
+/**
+ * 
+ * @param $form
+ */
+function get_tripal_feature_admin_form_url_set(&$form) {
+
+ 
+  $form['url'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Feature URL Path'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
 
-  $form['url']['chado_feature_accession_prefix'] = array(
-    '#title'       => t('ID Prefix'),
-    '#type'        => t('textfield'),
-    '#description' => t("If you choose an Internal ID above you must also enter an ID prefix.".
-                        "this prefix will be prepended to the internal ID number (e.g. ID38294). ".
-                        "if you chose to use the feature name or unique name then this prfix is not used"),
-    '#required'    => TRUE,
-    '#default_value' => variable_get('chado_feature_accession_prefix', 'ID'),
+  $options = array(
+    'SID[id]'      => '[id]:' . t('The Chado feature_id'),
+    'feature'      => 'feature:' . t('Chado table name'),
+    '[genus]'      => '[genus]:' . t('Genus to which the feature belongs'),
+    '[species]'    => '[species]:' . t('Species to which the feature belongs'),
+    '[type]'       => '[type]:' . t('The type of feature'),
+    '[uniquename]' => '[uniquename]:' . t('The feature unique name'),
+    '[name]'       => '[name]:' . t('The feature name'),
+    'reset'        => t('Reset'),
   );
+  
 
+  $form['url']['chado_feature_url_string'] = array(
+    '#title' => 'URL Syntax',
+    '#type' => 'textfield',
+    '#description' => t('You may rearrange elements in this text box to
+      customize the URLs.  The available tags include: [id],
+      [uniquename]. [name], [species], [genus], [type]. You can separate or
+      include any text between the tags. Click the "Set Feature URLs" button to 
+      reset the URLs for all feature pages.  Click the "Save Configuration" button to
+      simply save this setup. <b>Important</b>: be sure that whatever you choose will always be unique even considering
+      future data that may be added.  If you include the Chado table name, genus, species, type 
+      and uniquename you are guaranteed to have a unique URL. For example feature/[genus]/[species]/[type]/[uniquename]'),
+    '#size' => 150,
+    '#default_value' => variable_get('chado_feature_url_string', '/feature/[genus]/[species]/[type]/[uniquename]'), 
+  );
+  
+  $form['url']['chado_feature_url'] = array(
+    '#title'         => t('URL components'),
+    '#type'          => 'checkboxes',
+    '#required'      => FALSE,
+    '#options'       => $options,
+    '#description'   => t('Click the item above to make it appear in the URL Syntax box'),
+    '#attributes'    => array(
+      'onclick' => '
+        box = $(\'#edit-chado-feature-url-string\');
+        if (this.value == \'reset\') {
+          box.val(\'\');
+        }
+        else {        
+          box.val(box.val() + "/" + this.value);          
+        }
+        this.checked = false;
+      ',
+    ),
+  );  
+  
   $form['url']['button'] = array(
     '#type' => 'submit',
     '#value' => t('Set Feature URLs'),
-    '#weight' => 3,
   );
 }
 
@@ -520,7 +576,7 @@ function theme_tripal_feature_search_index($node) {
   $content = '';
 
   // get the accession prefix
-  $aprefix = variable_get('chado_feature_accession_prefix', 'ID');
+  $aprefix = variable_get('chado_feature_accession_prefix', 'FID');
 
   $content .= "<h1>$feature->uniquename</h1>. ";
   $content .= "<strong>$aprefix$feature->feature_id.</strong> ";
@@ -549,7 +605,7 @@ function theme_tripal_feature_search_results($node) {
   $content = '';
 
   // get the accession prefix
-  $aprefix = variable_get('chado_feature_accession_prefix', 'ID');
+  $aprefix = variable_get('chado_feature_accession_prefix', 'FID');
 
   $content .= "Feature Name: <h1>$feature->uniquename</h1>. ";
   $content .= "<strong>Accession: $aprefix$feature->feature_id.</strong>";

+ 177 - 61
tripal_feature/includes/syncFeatures.inc → tripal_feature/includes/tripal_feature.sync_features.inc

@@ -10,9 +10,9 @@
 // Parameter f specifies the feature_id to sync
 // -f 0 will sync all features
 
-$arguments = getopt("f:");
+$arguments = getopt("f:t:");
 
-if (isset($arguments['f'])) {
+if (isset($arguments['f']) and isset($arguments['t']) and $arguments['t'] == 'chado_feature') {
   $drupal_base_url = parse_url('http://www.example.com');
   $_SERVER['HTTP_HOST'] = $drupal_base_url['host'];
   $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
@@ -24,10 +24,10 @@ if (isset($arguments['f'])) {
 
   $feature_id = $arguments['f'];
 
-  if ($feature_id > 0 ) {
+  if ($feature_id > 0) {
     tripal_feature_sync_feature($feature_id);
   }
-  else{
+  else {
     print "syncing all features...\n";
     tripal_feature_sync_features();
   }
@@ -51,12 +51,11 @@ function tripal_feature_sync_form() {
   $form['feature_types'] = array(
     '#title'       => t('Feature Types'),
     '#type'        => 'textarea',
-    '#description' => t('Enter the names of the sequence types that the ".
-       "site will support with independent pages.  Pages for these data ".
-       "types will be built automatically for features that exist in the ".
+    '#description' => t("Enter the names of the feature types to sync.  Pages for these feature ".
+       "types will be created automatically for features that exist in the ".
        "chado database.  The names listed here should be spearated by ".
        "spaces or entered separately on new lines. The names must match ".
-       "exactly (spelling and case) with terms in the sequence ontology'),
+       "exactly (spelling and case) with terms in the sequence ontology"),
     '#required'    => TRUE,
     '#default_value' => variable_get('chado_sync_feature_types', 'gene contig'),
   );
@@ -71,7 +70,7 @@ function tripal_feature_sync_form() {
   $form['organism_id'] = array(
     '#title'       => t('Organism'),
     '#type'        => t('select'),
-    '#description' => t("Choose the organism for which features set above will be synced. Only organisms which also have been synced will appear in this list."),
+    '#description' => t("Choose the organism for which features types set above will be synced. Only organisms which also have been synced will appear in this list."),
     '#options'     => $organisms,
   );
 
@@ -115,59 +114,183 @@ function tripal_feature_sync_form_submit($form, &$form_state) {
 
   variable_set('chado_sync_feature_types', $feature_types);
 
-  tripal_add_job($title, 'tripal_feature',
-    'tripal_feature_sync_features', $job_args, $user->uid);
+  tripal_add_job($title, 'tripal_feature', 'tripal_feature_sync_features', $job_args, $user->uid);
 }
 /**
- *
+ *  
+ * @param $na 
+ *   Tripal expects all jobs to have at least one argument. For this function
+ *   we don't need any, so we have this dummy argument as a filler
+ * @param $job_id
  */
-function tripal_feature_set_urls($job_id = NULL) {
-  // first get the list of features that have been synced
+function tripal_feature_set_urls($na = NULL, $job = NULL) {
+  
+  // begin the transaction
+  db_query("BEGIN");
+      
+  print "\nNOTE: Setting of URLs is performed using a database transaction. \n" .
+        "If the load fails or is terminated prematurely then the entire set of \n" .
+        "new URLs will be rolled back and no changes will be made\n\n";
+  
+  // get the number of records we need to set URLs for
+  $csql = "SELECT count(*) FROM {chado_feature}";
+  $num_nodes = db_result(db_query($csql));
+    
+  // calculate the interval at which we will print an update on the screen
+  $num_set = 0;
+  $num_per_interval = 100;
+  
+  // prepate the statements which will quickly add url alias. Because these
+  // are not Chado tables we must manually prepare them 
+  $psql = "
+    PREPARE del_url_alias_by_src (text) AS
+    DELETE FROM {url_alias} WHERE src = \$1
+  ";
+  db_query($psql);
+  $psql = "
+    PREPARE ins_url_alias_nisrds (text, text) AS
+    INSERT INTO url_alias (src, dst) VALUES (\$1, \$2)
+  ";
+  db_query($psql);
+  
+  // get the URL alias syntax string
+  $url_alias = variable_get('chado_feature_url_string', '/feature/[genus]/[species]/[type]/[uniquename]'); 
+  if (!$url_alias) {
+    $url_alias = '/feature/[genus]/[species]/[type]/[uniquename]';
+  } 
+  $url_alias = preg_replace('/^\//', '', $url_alias); // remove any preceeding forward slash
+  
+  
+  // get the list of features that have been synced
   $sql = "SELECT * FROM {chado_feature}";
-  $nodes = db_query($sql);
+  $nodes = db_query($sql);  
   while ($node = db_fetch_object($nodes)) {
-    // now get the feature details
-    $sql = "SELECT * 
-            FROM {feature} F
-              INNER JOIN {organism} O on O.organism_id = F.organism_id
-            WHERE F.feature_id = %d";     
-    $feature = db_fetch_object(chado_query($sql, $node->feature_id));
-    if ($feature) {
-      tripal_feature_set_feature_url($node, $feature);
+   
+    // get the URL alias
+    $src = "node/$node->nid";
+    $dst = tripal_feature_get_feature_url($node, $url_alias);
+    if (!$dst) {
+      db_query('DEALLOCATE "del_url_alias_by_src"');
+      db_query('DEALLOCATE "ins_url_alias_nisrds"');
+      db_query("ROLLBACK"); 
+      return; 
+    }    
+    
+    // if the src and dst is the same (the URL alias couldn't be set)
+    // then skip to the next one. There's nothing we can do about this one.
+    if($src == $dst) {
+      continue;
+    }
+    
+    // remove any previous alias and then add the new one
+    $success = db_query("EXECUTE del_url_alias_by_src('%s')", $src);    
+    if (!$success) {
+      db_query('DEALLOCATE "del_url_alias_by_src"');
+      db_query('DEALLOCATE "ins_url_alias_nisrds"');
+      db_query("ROLLBACK");
+      watchdog('trp-seturl', "Failed Removing URL Alias: %src", array('%src' => $src), WATCHDOG_ERROR);
+      return;
+    }
+    $success = db_query("EXECUTE ins_url_alias_nisrds('%s', '%s')", $src, $dst);
+    if (!$success) {
+      db_query('DEALLOCATE "del_url_alias_by_src"');
+      db_query('DEALLOCATE "ins_url_alias_nisrds"');
+      db_query("ROLLBACK");
+      watchdog('trp-seturl', "Failed Adding URL Alias: %dst", array('%dst' => $dst), WATCHDOG_ERROR);
+      return;
     }
+
+    // update the job status every 1% features
+    if ($job and $num_set % $num_per_interval == 0) {
+      $percent = ($num_set / $num_nodes) * 100;
+      tripal_job_set_progress($job, intval($percent));
+      $percent = sprintf("%.2f", $percent);
+      print "Setting URLs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
+      
+    }
+    $num_set++;
   }
+  $percent = ($num_set / $num_nodes) * 100;
+  tripal_job_set_progress($job, intval($percent));
+  $percent = sprintf("%.2f", $percent);
+  print "Setting URLs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
+  print "\nDone. Set " . number_format($num_set) . " URLs\n";
+  
+  // unprepare the statements
+  db_query('DEALLOCATE "del_url_alias_by_src"');
+  db_query('DEALLOCATE "ins_url_alias_nisrds"');
+  
+  db_query("COMMIT");
 }
 /**
- *
+ * 
+ * @param $node
+ *   A node object containing at least the feature_id and nid
+ * @param $url_alias
+ *   Optional.  This should be the URL alias syntax string that contains
+ *   placeholders such as [id], [genus], [species], [name], [uniquename],
+ *   and [type].  These placeholders will be substituted for actual values.
+ *   If this parameter is not provided then the value of the 
+ *   chado_feature_url_string Drupal variable will be used.
  */
-function tripal_feature_set_feature_url($node, $feature) {
-
-  // determine which URL alias to use
-  $alias_type = variable_get('chado_feature_url', 'internal ID');
-  $aprefix = variable_get('chado_feature_accession_prefix', 'ID');  
-  $genus = preg_replace('/\s/', '_', strtolower($feature->genus));
-  $species = preg_replace('/\s/', '_', strtolower($feature->species));    
-  switch ($alias_type) {
-    case 'feature name':
-      $url_alias = $feature->name;
-      break;
-    case 'feature unique name':
-      $url_alias = $feature->uniquename;
-      break;
-    case 'genus_species_uqname':
-      $url_alias = $genus . "/" . $genus . "_" . $species . "/" . $feature->uniquename;
-      break;
-    case 'genus species name':
-      $url_alias = $genus . "/" . $genus . "_" . $species . "/" . $feature->name;
-      break;
-    default:
-      $url_alias = "$aprefix$feature->feature_id";
+function tripal_feature_get_feature_url($node, $url_alias = NULL) {
+
+  // get the starting URL alias
+  if(!$url_alias) {
+    $url_alias = variable_get('chado_feature_url_string', '/feature/[genus]/[species]/[type]/[uniquename]'); 
+    if (!$url_alias) {
+      $url_alias = '/feature/[genus]/[species]/[type]/[uniquename]';
+    } 
+    $url_alias = preg_replace('/^\//', '', $url_alias); // remove any preceeding forward slash
   }
-  print "Setting URL alias for $feature->name: node/$node->nid => $url_alias\n";
-  // remove any previous alias
-  db_query("DELETE FROM {url_alias} WHERE src = '%s'", "node/$node->nid");
-  // add the new alias
-  path_set_alias("node/$node->nid", $url_alias);
+
+  // get the feature 
+  $values = array('feature_id' => $node->feature_id); 
+  $options = array('statement_name' => 'sel_feature_id');       
+  $feature = tripal_core_chado_select('feature', array('*'), $values, $options);
+  if (!$feature) {
+    watchdog('trp-seturl', "Cannot find feature when setting URL alias for feature: %id", array('%id' => $node->feature_id), WATCHDOG_ERROR);
+    return FALSE;  
+  }
+  $feature = (object) $feature[0];
+  
+  // get the organism
+  $values = array('organism_id' => $feature->organism_id);
+  $options = array('statement_name' => 'sel_organism_id');
+  $organism  = tripal_core_chado_select('organism', array('*'), $values, $options);  
+  if (!$organism) {
+    watchdog('trp-seturl', "Cannot find organism when setting URL alias for feature: %id", array('%id' => $node->feature_id), WATCHDOG_ERROR);
+    return FALSE;  
+  }
+  $genus = preg_replace('/\s/', '_', strtolower($organism[0]->genus));
+  $species = preg_replace('/\s/', '_', strtolower($organism[0]->species)); 
+
+  // get the type
+  $values = array('cvterm_id' => $feature->type_id);
+  $options = array('statement_name' => 'sel_cvterm_id');
+  $cvterm = tripal_core_chado_select('cvterm', array('name'), $values, $options);
+  if (!$cvterm) {
+    watchdog('trp-seturl', "Cannot find type when setting URL alias for feature: %id", array('%id' => $node->feature_id), WATCHDOG_ERROR);
+    return FALSE;  
+  }
+  $type = preg_replace('/\s/', '_', $cvterm[0]->name);
+  
+  // now substitute in the values
+  $url_alias = preg_replace('/\[id\]/', $feature->feature_id, $url_alias);
+  $url_alias = preg_replace('/\[genus\]/', $genus, $url_alias);
+  $url_alias = preg_replace('/\[species\]/', $species, $url_alias);
+  $url_alias = preg_replace('/\[type\]/', $type, $url_alias);
+  $url_alias = preg_replace('/\[name\]/', $feature->name, $url_alias);
+  $url_alias = preg_replace('/\[uniquename\]/', $feature->uniquename, $url_alias);
+ 
+  // the dst field of the url_alias table is only 128 characters long. 
+  // if this is the case then simply return the node URL, we can't set this one
+  if (strlen($url_alias) > 128) {
+    watchdog('trp-seturl', "Cannot set alias longer than 128 characters: %alias.", array('%alias' => $url_alias), WATCHDOG_ERROR);
+    return "node/" . $node->nid;
+  }
+  
+  return $url_alias;
 }
 /**
  *
@@ -176,7 +299,7 @@ function tripal_feature_set_feature_url($node, $feature) {
  */
 function tripal_feature_sync_features($max_sync = 0, $organism_id = NULL,
   $feature_types = NULL, $job_id = NULL) {
-  //print "Syncing features (max of $max_sync)\n";
+  
   $i = 0;
 
   // get the list of available sequence ontology terms for which
@@ -268,7 +391,7 @@ function tripal_feature_sync_features($max_sync = 0, $organism_id = NULL,
       # to avoid this problem we will call this script through an
       # independent system call
       print "$i of $num_ids Syncing feature id: $feature_id\n";
-      $cmd = "php " . drupal_get_path('module', 'tripal_feature') . "/includes/syncFeatures.inc -f $feature_id ";
+      $cmd = "php " . drupal_get_path('module', 'tripal_feature') . "/includes/tripal_feature.sync_features.inc -f $feature_id -t chado_feature";
       system($cmd);
 
     }
@@ -290,7 +413,7 @@ function tripal_feature_sync_feature($feature_id) {
   $create_node = 1;   // set to 0 if the node exists and we just sync and not create
 
   // get the accession prefix
-  $aprefix = variable_get('chado_feature_accession_prefix', 'ID');
+  $aprefix = variable_get('chado_feature_accession_prefix', 'FID');
 
   // if we don't have a feature_id then return
   if (!$feature_id) {
@@ -413,13 +536,6 @@ function tripal_feature_sync_feature($feature_id) {
   drupal_set_message(t("%feature_id ($node->nid): setting taxonomy", array('%feature_id' => $feature_id)));
   tripal_feature_set_taxonomy($node, $feature_id);
 
-  // reindex the node
-  // drupal_set_message(t("$feature_id( $node->nid): indexing"));
-  // tripal_feature_index_feature ($feature_id,$node->nid);
-
-  // set the URL alias for this node
-  tripal_feature_set_feature_url($node, $feature);
-
 
   return '';
 }

+ 12 - 3
tripal_feature/tripal_feature.drush.inc

@@ -41,6 +41,8 @@ function tripal_feature_drush_command() {
       'parent'   => dt('Set this argument to 1 to retrieve the sequence from the parent in an alignment rather than the residues column of the feature itself.'),
       'agg'      => dt('Set this argument to 1 to aggregate sub features into a single sequence.  This is useful, for example, for obtaining CDS sequence from an mRNA'),
       'child'    => dt('Set this argument to the sequence ontology term for the children to aggregate.  This is useful in the case where a gene has exons as well as CDSs and UTRs.  You may sepcify as many feature types as desired by separating each with a single comma (no spaces).'),
+      'relationship'  => dt('Retreives the sequence of any feature in the specified relationship with the matched features.'),
+      'rel_part' => dt('If a relationship is provided, then this will be "subject" or "object" indicating the side of the relationship for the matched features. If the matched features are the "object" then the "subject" features will have their sequences included in the output (and vice versa).'),
     ),
     'examples' => array(
       'Standard example' => 'drush tripal-current-job',
@@ -79,10 +81,17 @@ function drush_tripal_feature_tripal_get_sequence() {
   $derive_from_parent = drush_get_option('parent');
   $aggregate = drush_get_option('agg');
   $child = drush_get_option('child');
-  
+  $relationship = drush_get_option('relationship');
+  $rel_part = drush_get_option('rel_part');
+
+  if($relationship and !$rel_part){
+    print "Please specify both 'relationship' and a 'rel_part' arguments. Both must be used together\n";
+    return;
+  }
+    
   tripal_feature_seq_extract_get_features($org_commonname, $genus, $species, $analysis_name, 
     $type, $feature_name, $upstream, $downstream, $output_format, $derive_from_parent, 
-    $aggregate, $child);   
+    $aggregate, $child, $relationship, $rel_part);
 }
 /*
  * 
@@ -90,4 +99,4 @@ function drush_tripal_feature_tripal_get_sequence() {
 function drush_tripal_feature_sync() {
   $feature_id = drush_get_option('id');  
   tripal_feature_sync_feature($feature_id);
-}
+}

+ 123 - 21
tripal_feature/tripal_feature.module

@@ -14,7 +14,7 @@
  */
 
 require_once "includes/tripal_feature.admin.inc";
-require_once "includes/syncFeatures.inc";
+require_once "includes/tripal_feature.sync_features.inc";
 require_once "includes/indexFeatures.inc";
 require_once "includes/fasta_loader.inc";
 require_once "includes/gff_loader.inc";
@@ -244,12 +244,10 @@ function tripal_feature_menu() {
 
   // the menu link for addressing any feature (by name, uniquename, synonym)
   $items['feature/%'] = array(
-    'title' => 'Matched Features',
-    'description' => 'Shows all features that match the provided ID.  If multiple features match even by name, uniquename or synonym then a page is presented to allow the user to select which one they intended.',
     'page callback' => 'tripal_feature_match_features_page',
     'page arguments' => array(1),
     'access arguments' => array('access chado_feature content'),
-    'type' => MENU_NORMAL_ITEM,
+    'type' => MENU_LOCAL_TASK,
   );
 
   return $items;
@@ -285,6 +283,10 @@ function tripal_feature_theme() {
        'arguments' => array('node' => NULL),
        'template' => 'tripal_feature_sequence',
     ),
+    'tripal_feature_proteins' => array(
+       'arguments' => array('node' => NULL),
+       'template' => 'tripal_feature_proteins',
+    ),
     'tripal_feature_synonyms' => array(
        'arguments' => array('node' => NULL),
        'template' => 'tripal_feature_synonyms',
@@ -499,7 +501,7 @@ function chado_feature_insert($node) {
     'type_id' => $type[0]->cvterm_id,
   );
   $feature = tripal_core_chado_select('feature', array('feature_id'), $values);
-
+    
   // add the genbank accession and synonyms
   chado_feature_add_synonyms($node->synonyms, $feature[0]->feature_id);
 
@@ -560,7 +562,7 @@ function chado_feature_update($node) {
     );
     $options = array('return_record' => TRUE);
     $status = tripal_core_chado_update('feature', $match, $values, $options);
-
+    
     // add the genbank synonyms
     chado_feature_add_synonyms($node->synonyms, $feature_id);
   }
@@ -572,6 +574,8 @@ function chado_feature_update($node) {
     WATCHDOG_WARNING
     );
   }
+  
+
 }
 /**
  *
@@ -797,6 +801,7 @@ function chado_feature_form($node, $param) {
     '#value' => $feature->feature_id,
   );
 
+  /*
   $form['title']= array(
     '#type' => 'textfield',
     '#title' => t('Title'),
@@ -804,7 +809,7 @@ function chado_feature_form($node, $param) {
     '#default_value' => $node->title,
     '#description' => t('The title must be a unique identifier for this feature.  It is recommended to use a combination of uniquename, organism and feature type in the title as this is guranteed to be unique.'),
     '#maxlength' => 255
-  );
+  );*/
 
   $form['uniquename']= array(
     '#type' => 'textfield',
@@ -942,7 +947,6 @@ function chado_feature_validate($node) {
   if ($node->feature_type == 'contig' and $node->gbaccession) {
     form_set_error('gbaccession', t("Contigs cannot have a genbank accession number.  Please change the feature type or remove the accession number"));
   }
-
 }
 /**
  *  When a node is requested by the user this function is called to allow us
@@ -958,8 +962,26 @@ function chado_feature_load($node) {
   $values = array('feature_id' => $feature_id);
   $feature = tripal_core_generate_chado_var('feature', $values);
 
-  if (strcmp($feature->name, $feature->uniquename)==0) {
-     $node->title = $feature->name . " (" . $feature->type_id->name . ") " . $feature->organism_id->genus . " " . $feature->organism_id->species ;
+  // by default, the titles are saved using the unique constraint.  We will
+  // keep it the same, but remove the duplicate name if the unique name and name
+  // are identical
+  $title_type = variable_get('chado_feature_title', 'unique_constraint');
+  if($title_type == 'unique_constraint') {
+    if (strcmp($feature->name, $feature->uniquename)==0) {
+      $node->title = $feature->name . " (" . $feature->type_id->name . ") " . $feature->organism_id->genus . " " . $feature->organism_id->species ;
+    }
+    // in previous version of Tripal, the feature title was simply the unique name. 
+    // so, we recreate the title just to be sure all of our feature pages are consistent
+    else {
+      $node->title = $feature->name . ", " . $feature->uniquename . " (" . $feature->type_id->name . ") " . $feature->organism_id->genus . " " . $feature->organism_id->species ;  
+    }
+  }
+  // set the title to be the feature name or uniquename as configured
+  if($title_type == 'feature_name') {
+    $node->title = $feature->name;
+  }
+  if($title_type == 'feature_unique_name') {
+    $node->title = $feature->uniquename;  
   }
 
   $additions = new stdClass();
@@ -1370,18 +1392,21 @@ function tripal_feature_get_matched_alignments($feature) {
           "  INNER JOIN {featureloc} FL1 on FL1.srcfeature_id = F1.feature_id ".
           "  INNER JOIN {feature} F2 on FL1.feature_id = F2.feature_id ".
           "  INNER JOIN {featureloc} FL2 on FL2.feature_id = F2.feature_id ".
-          "  INNER JOIN {feature} F3 on FL2.srcfeature_id = F3.feature_id ".
           "  INNER JOIN {cvterm} CVT2 on F2.type_id = CVT2.cvterm_id ".
-          "WHERE F1.feature_id = %d and NOT F3.feature_id = %d ".
+          "WHERE F1.feature_id = %d " .
           "  AND (CVT2.name = 'match' or CVT2.name like '%_match') ".
           "ORDER BY FL1.fmin";
 
-   $results = chado_query($sql, $feature->feature_id, $feature->feature_id);
+   $results = chado_query($sql, $feature->feature_id);
 
    // iterate through the results and add them to our featurelocs array
    $featurelocs = array();
    while ($fl = db_fetch_object($results)) {
-      $featurelocs[] = $fl ;
+     // ignore featurelocs where the left and right srcfeature is the same
+     if (strcmp($fl->left_srcfeature_id, $fl->right_srcfeature_id) == 0) {
+       continue;
+     }
+     $featurelocs[] = $fl ;
    }
    return $featurelocs;
 }
@@ -1809,10 +1834,61 @@ function chado_feature_view($node, $teaser = FALSE, $page = FALSE) {
  * @ingroup tripal_feature
  */
 function tripal_feature_nodeapi(&$node, $op, $teaser, $page) {
-
+  
   switch ($op) {
-    // Note that this function only adds feature view to an organism node.
-    // The view of a feature node is controled by the theme *.tpl file
+    
+     // set the title to ensure it is always unique
+    case 'presave':
+      switch ($node->type) {
+        case 'chado_feature':
+          
+          $values = array('organism_id' => $node->organism_id);
+          $organism = tripal_core_chado_select('organism', array('genus','species'), $values);
+          $node->title = $node->fname . ', ' . $node->uniquename . ' (' . $node->feature_type . ') ' . $organism[0]->genus . ' ' . $organism[0]->species;
+          break;
+      }
+      break;
+      
+    // set the URL path after inserting.  We do it here because we do not 
+    // know the feature_id in the presave  
+    case 'insert':
+      switch ($node->type) {
+        case 'chado_feature':
+          if (!$node->feature_id) {
+            $sql = "SELECT * FROM {chado_feature} WHERE nid = %d";
+            $chado_feature = db_fetch_object(db_query($sql, $node->nid));
+            $node->feature_id = $chado_feature->feature_id;
+          }
+
+          
+          // remove any previous alias
+          db_query("DELETE FROM {url_alias} WHERE src = '%s'", "node/$node->nid");
+          
+          // set the URL for this feature page
+          $url_alias = tripal_feature_get_feature_url($node);
+          path_set_alias("node/$node->nid", $url_alias);
+          break;
+      }
+      break;
+      
+    // set the URL path after inserting.  We do it here because we do not 
+    // know the feature_id in the presave  
+    case 'update':
+      switch ($node->type) {
+        case 'chado_feature':
+          
+          // remove any previous alias
+          db_query("DELETE FROM {url_alias} WHERE src = '%s'", "node/$node->nid");
+          
+          // set the URL for this feature page
+          $url_alias = tripal_feature_get_feature_url($node);
+          path_set_alias("node/$node->nid", $url_alias);
+          break;
+      }
+      break;
+      
+      
+    // add items to other nodes, build index and search results
     case 'view':
       switch ($node->type) {
         case 'chado_organism':
@@ -1846,6 +1922,20 @@ function tripal_feature_preprocess_tripal_feature_relationships(&$variables) {
     $feature->all_relationships = tripal_feature_get_feature_relationships($feature);
   }
 }
+
+/**
+ *
+ *
+ * @ingroup tripal_feature
+ */
+function tripal_feature_preprocess_tripal_feature_proteins(&$variables) {
+  // we want to provide a new variable that contains the matched features.
+  $feature = $variables['node']->feature;
+
+  if (!$feature->all_relationships) {
+    $feature->all_relationships = tripal_feature_get_feature_relationships($feature);
+  }
+}
 /**
  *
  *
@@ -1876,7 +1966,7 @@ function tripal_feature_preprocess_tripal_feature_alignments(&$variables) {
 
   // get matched alignments (those with an itermediate 'match' or 'EST_match', etc
   $mfeaturelocs = tripal_feature_get_matched_alignments($feature);
-  $feature->matched_featurelocs = tripal_feature_get_matched_alignments($feature);
+  $feature->matched_featurelocs = mfeaturelocs;
 
   // combine all three alignments into a single array for printing together in
   // a single list
@@ -2092,7 +2182,7 @@ function tripal_feature_del_vocabulary() {
  * @ingroup tripal_feature
  */
 function tripal_feature_return_fasta($feature, $desc) {
-  $fasta  = ">" . variable_get('chado_feature_accession_prefix', 'ID') . "$feature->feature_id|$feature->name";
+  $fasta  = ">" . variable_get('chado_feature_accession_prefix', 'FID') . "$feature->feature_id|$feature->name";
   $fasta .= " $desc\n";
   $fasta .= wordwrap($feature->residues, 50, "\n", TRUE);
   $fasta .= "\n\n";
@@ -2208,6 +2298,18 @@ function tripal_feature_coder_ignore() {
  * features is shown.
  */
 function tripal_feature_match_features_page($id) {
+  
+  // if the URL alias configuration is set such that the URL
+  // always begins with 'feature' then we want to use the ID as it is and
+  // forward it on. Otherwise, try to find the matching feature.
+  $url_alias = variable_get('chado_feature_url_string', '/feature/[genus]/[species]/[type]/[uniquename]'); 
+  if (!$url_alias) {
+    $url_alias = '/feature/[genus]/[species]/[type]/[uniquename]';
+  } 
+  $url_alias = preg_replace('/^\//', '', $url_alias); // remove any preceeding forward slash  
+  if (preg_match('/^feature\//', $url_alias)) {
+    drupal_goto($id);
+  }
 
   $sql = "
     SELECT
@@ -2254,7 +2356,7 @@ function tripal_feature_match_features_page($id) {
   // if we have more than one match then generate the table, otherwise, redirect
   // to the matched feature
   if ($num_matches == 1) {
-    drupal_goto(url("node/" . $curr_match->nid));
+    drupal_goto("node/" . $curr_match->nid);
   }
   if ($num_matches == 0) {
     return "<p>No features matched the given name '$id'</p>";
@@ -2281,4 +2383,4 @@ function tripal_feature_form_alter(&$form, &$form_state, $form_id) {
     // to the normal form URL
     $form['#action'] = url("find/sequences");
   }
-} 
+} 

+ 0 - 1
tripal_library/includes/tripal_library.admin.inc

@@ -507,7 +507,6 @@ function tripal_library_reindex_features($library_id = NULL, $job_id = NULL) {
     if ($job_id and $i % interval == 0) {
       tripal_job_set_progress($job_id, intval(($i/$count)*100));
     }
-    tripal_feature_sync_feature($feature_id);
     $i++;
   }
 }

+ 0 - 1
tripal_organism/includes/tripal_organism.admin.inc

@@ -395,7 +395,6 @@ function tripal_organism_reindex_features($organism_id = NULL, $job_id = NULL) {
     if ($job_id and $i % $interval == 0) {
       tripal_job_set_progress($job_id , intval(($i/$count)*100));
     }
-    tripal_feature_sync_feature($feature_id);
     $i++;
   }
 }

+ 77 - 78
tripal_pub/api/tripal_pub.api.inc

@@ -664,7 +664,7 @@ function tripal_pub_add_publication($pub_details, &$action, $do_contact = FALSE,
       return FALSE;
     }
   }
-  print "PUB ID: $pub_id\n";
+  //print "PUB ID: $pub_id\n";
   // if there is a pub id and we've been told not to update then return
   if ($pub_id and !$update_if_exists) {
     $action = 'skipped';
@@ -1029,13 +1029,13 @@ function tripal_pub_get_publication_array($pub_id, $skip_existing = TRUE) {
   if ($citation) {
     $citation = tripal_core_expand_chado_vars($citation, 'field', 'pubprop.value', $options);
     if (count($citation) > 1) {
-    	watchdog('tripal_pub', "Publication has multiple citations already: %pub_id",
-    	array('%pub_id' => $pubid), WATCHDOG_ERROR);
-    	return FALSE;
+      watchdog('tripal_pub', "Publication has multiple citations already: %pub_id",
+      array('%pub_id' => $pubid), WATCHDOG_ERROR);
+      return FALSE;
     }
     elseif (count($citation) == 1 and $skip_existing == TRUE) {
-    	// skip this publication, it already has a citation
-    	return FALSE;
+      // skip this publication, it already has a citation
+      return FALSE;
     }
   }  
 
@@ -1080,7 +1080,7 @@ function tripal_pub_get_publication_array($pub_id, $skip_existing = TRUE) {
     $sql = "SELECT string_agg(surname || ' ' || givennames, ', ') FROM {pubauthor} WHERE pub_id = %d GROUP BY pub_id";
     $au = db_result(chado_query($sql));
     if ($au) {
-    	$pub_array['Authors'] = $au;
+      $pub_array['Authors'] = $au;
     }
   }
 
@@ -1156,95 +1156,94 @@ function tripal_pub_get_publication_array($pub_id, $skip_existing = TRUE) {
  *   A text string containing the citation
  */
 function tripal_pub_create_citation($pub) {
-	$citation = '';
-	$pub_type = '';
-	
-	// An article may have more than one publication type. For example,
-	// a publication type can be 'Journal Article' but also a 'Clinical Trial'.
-	// Therefore, we need to select the type that makes most sense for 
-	// construction of the citation. Here we'll iterate through them all
-	// and select the one that matches best.
-	if(is_array($pub['Publication Type'])) {
-	  foreach ($pub['Publication Type'] as $ptype) {
-	    if ($ptype == 'Journal Article' ) {
-	      $pub_type = $ptype;
-	      break;
-	    } else if ($ptype == 'Conference Proceedings'){ 
-	      $pub_type = $ptype;
-	      break;
-	    } else if ($ptype == 'Book') {
-	      $pub_type = $ptype;
-	      break;
-	    } else if ($ptype == 'Book Chapter') {
-	      $pub_type = $ptype;
-	      break;
-	    }
-	  }
-	  if (!$pub_type) {
+  $citation = '';
+  $pub_type = '';
+  
+  // An article may have more than one publication type. For example,
+  // a publication type can be 'Journal Article' but also a 'Clinical Trial'.
+  // Therefore, we need to select the type that makes most sense for 
+  // construction of the citation. Here we'll iterate through them all
+  // and select the one that matches best.
+  if(is_array($pub['Publication Type'])) {
+    foreach ($pub['Publication Type'] as $ptype) {
+      if ($ptype == 'Journal Article' ) {
+        $pub_type = $ptype;
+        break;
+      } else if ($ptype == 'Conference Proceedings'){ 
+        $pub_type = $ptype;
+        break;
+      } else if ($ptype == 'Book') {
+        $pub_type = $ptype;
+        break;
+      } else if ($ptype == 'Book Chapter') {
+        $pub_type = $ptype;
+        break;
+      }
+    }
+    if (!$pub_type) {
       watchdog('tripal_pub', "Cannot generate citation for publication type: %types", 
         array('%types' => print_r($pub['Publication Type'], TRUE)), WATCHDOG_ERROR);
       return FALSE;
     }
-	}
-	else {
-	  $pub_type = $pub['Publication Type'];
-	}		
-  print "[$pub_type]\n";
-	//----------------------
+  }
+  else {
+    $pub_type = $pub['Publication Type'];
+  }    
+  //----------------------
   // Journal Article
   //----------------------
-	if ($pub_type == 'Journal Article') {
-	  $citation = $pub['Authors'] . '. ' . $pub['Title'] .  '. ';
-	
-	  if ($pub['Journal Name']) {
-	    $citation .= $pub['Journal Name'] . '. ';
-	  }
-	  elseif ($pub['Journal Abbreviation']) {
-	    $citation .= $pub['Journal Abbreviation'] . '. ';
-	  }
-	  elseif ($pub['Series Name']) {
-	    $citation .= $pub['Series Name'] . '. ';	
-	  }
-	  elseif ($pub['Series Abbreviation']) {
+  if ($pub_type == 'Journal Article') {
+    $citation = $pub['Authors'] . '. ' . $pub['Title'] .  '. ';
+  
+    if ($pub['Journal Name']) {
+      $citation .= $pub['Journal Name'] . '. ';
+    }
+    elseif ($pub['Journal Abbreviation']) {
+      $citation .= $pub['Journal Abbreviation'] . '. ';
+    }
+    elseif ($pub['Series Name']) {
+      $citation .= $pub['Series Name'] . '. ';  
+    }
+    elseif ($pub['Series Abbreviation']) {
       $citation .= $pub['Series Abbreviation'] . '. ';
     }
     if ($pub['Publication Date']) {
-	    $citation .= $pub['Publication Date'];
+      $citation .= $pub['Publication Date'];
     }
     elseif ($pub['Year']) {
-    	$citation .= $pub['Year'];
+      $citation .= $pub['Year'];
     }
-	  if ($pub['Volume'] or $pub['Issue'] or $pub['Pages']) {
-	    $citation .= '; ';
-	  }
-	  if ($pub['Volume']) {
-	    $citation .= $pub['Volume'];
-	  }
-	  if ($pub['Issue']) {
-	    $citation .= '(' . $pub['Issue'] . ')';
-	  }
-	  if ($pub['Pages']) {
-	    if($pub['Volume']) {
-	      $citation .= ':';
-	    }
-	    $citation .= $pub['Pages'];
-	  }
-	  $citation .= '.';
-	}
-	//----------------------
+    if ($pub['Volume'] or $pub['Issue'] or $pub['Pages']) {
+      $citation .= '; ';
+    }
+    if ($pub['Volume']) {
+      $citation .= $pub['Volume'];
+    }
+    if ($pub['Issue']) {
+      $citation .= '(' . $pub['Issue'] . ')';
+    }
+    if ($pub['Pages']) {
+      if($pub['Volume']) {
+        $citation .= ':';
+      }
+      $citation .= $pub['Pages'];
+    }
+    $citation .= '.';
+  }
+  //----------------------
   // Book
   //----------------------
-	elseif ($pub_type == 'Book') {
-	
-	}
-	//----------------------
+  elseif ($pub_type == 'Book') {
+  
+  }
+  //----------------------
   // Book Chapter
   //----------------------
   elseif ($pub_type == 'Book Chapter') {
-  	
+    
   }
-	//----------------------
-	// Conference Proceedings
+  //----------------------
+  // Conference Proceedings
   //----------------------
   elseif ($pub_type == 'Conference Proceedings') {
     $citation = $pub['Authors'] . '. ' . $pub['Title'] .  '. ';

+ 7 - 1
tripal_pub/includes/importers/AGL.inc

@@ -333,7 +333,7 @@ function tripal_pub_remote_search_AGL($search_array, $num_to_retrieve, $pager_id
 
     if (!yaz_ccl_parse($yazc, $ccl, $cclresult)) {
       drupal_set_message('Error parsing search string: ' . $cclresult["errorstring"], "error");
-      watchdog('tripal_pub', 'Error: %errstr', array('%errstr' => $cclresult["errorstring"]), WATCHDOG_ERROR);
+      watchdog('tpub_import', 'Error: %errstr', array('%errstr' => $cclresult["errorstring"]), WATCHDOG_ERROR);
       return array();
     }
     $search_str = $cclresult["rpn"];
@@ -374,6 +374,8 @@ function tripal_pub_AGL_count($search_array) {
       $error_msg .= " $additional";
     }
     drupal_set_message("ERROR preparing search at AGL: ($error_no) $error_msg", "error");
+    watchdog('tpub_import', "ERROR preparing search at AGL: (%error_no) %error_msg",
+              array('%error_no' => $error_no, '%error_msg' => $error_msg), WATCHDOG_ERROR);
     return 0;
   }
   if (!yaz_wait()) {
@@ -384,6 +386,8 @@ function tripal_pub_AGL_count($search_array) {
       $error_msg .= " $additional";
     }
     drupal_set_message("ERROR waiting on search at AGL: ($error_no) $error_msg", "error");
+    watchdog('tpub_import', "ERROR waiting on search at AGL: (%error_no) %error_msg",
+              array('%error_no' => $error_no, '%error_msg' => $error_msg), WATCHDOG_ERROR);
     return 0;
   }
 
@@ -416,6 +420,8 @@ function tripal_pub_AGL_range($search_array, $start = 0, $limit = 10) {
       $error_msg .= " $additional";
     }
     drupal_set_message("ERROR waiting on search at AGL: ($error_no) $error_msg", "error");
+    watchdog('tpub_import', "ERROR waiting on search at AGL: (%error_no) %error_msg",
+              array('%error_no' => $error_no, '%error_msg' => $error_msg), WATCHDOG_ERROR);
     return $pubs;
   }
    

+ 7 - 0
tripal_pub/includes/importers/PMID.inc

@@ -100,6 +100,9 @@ function tripal_pub_remote_search_PMID($search_array, $num_to_retrieve, $pager_i
       $search_str = preg_replace('/PMID:([^\s]*)/', '$1', $search_str);
       $search_str = preg_replace('/\|SCOPE\|/', '[Uid]', $search_str);
     }
+    else {
+      $search_str = preg_replace('/\|SCOPE\|/', '', $search_str);  
+    }
   }
   if ($days) {
     // get the date of the day suggested
@@ -199,6 +202,8 @@ function tripal_pub_PMID_search_init($search_str, $retmax){
   $rfh = fopen($query_url, "r");
   if (!$rfh) {
     drupal_set_message('Could not perform Pubmed query. Cannot connect to Entrez.', 'error');
+    watchdog('tpub_import', "Could not perform Pubmed query. Cannot connect to Entrez.",
+              array(), WATCHDOG_ERROR);
     return 0;
   }
 
@@ -276,6 +281,8 @@ $retmod = 'null', $start = 0, $limit = 10, $args = array()){
   $rfh = fopen($fetch_url, "r");
   if (!$rfh) {
     drupal_set_message('ERROR: Could not perform PubMed query.', 'error');
+    watchdog('tpub_import', "Could not perform PubMed query: %fetch_url.",
+              array('%fetch_url' => $fetch_url), WATCHDOG_ERROR);
     return '';
   }
   $results = '';

+ 4 - 2
tripal_pub/theme/tripal_pub_admin.tpl.php

@@ -55,8 +55,10 @@ have been added to Chado database.</p>
 	<ol>
 		<li>Install the YAZ libraries: sudo apt-get install yaz libyaz4-dev</li>
 		<li>Install the PHP YAZ extension: sudo pecl install yaz</li>
-		<li>Add the text 'extesion=yaz.so' to the appropriate php.ini file
-		(e.g. /etc/php5/apache2filter/php.ini)</li>
+		<li>Add the text 'extension=yaz.so' to the appropriate php.ini file
+		(e.g. /etc/php5/apache2filter/php.ini). On Ubuntu you may need to
+		add it to the php.ini file specfic for the Apache webserver and 
+		also to the php.ini specific for the command-line.</li>
 		<li>Restart the webserver</li>
 	</ol>
 	</li>

+ 0 - 347
tripal_stock/includes/tripal_stock-administration.inc

@@ -1,347 +0,0 @@
-<?php
-/**
- * @file
- * @todo Add file header description
- */
-
-/**
- * Purpose: Provide administration options for chado_stocks
- *
- * @return
- *   Form array (as described by the drupal form api)
- *
- * @ingroup tripal_stock
- */
-function tripal_stock_admin() {
-  $form = array();
-
-  // before proceeding check to see if we have any
-  // currently processing jobs. If so, we don't want
-  // to give the opportunity to sync Stocks
-  $active_jobs = FALSE;
-  if (tripal_get_module_active_jobs('tripal_stock')) {
-    $active_jobs = TRUE;
-  }
-  if ($active_jobs) {
-    $form['notice'] = array(
-       '#type' => 'fieldset',
-       '#title' => t('Stock Management Temporarily Unavailable')
-    );
-    $form['notice']['message'] = array(
-       '#value' => t("Currently, stock management jobs are waiting or ".
-          "are running. Managemment features have been hidden until these ".
-          "jobs complete.  Please check back later once these jobs have ".
-          "finished.  You can view the status of pending jobs in the Tripal ".
-          "jobs page."),
-    );
-  }
-  else {
-
-    // SET Vocabularies -----------------------------------------------------------------------------------------
-    $form['set_cv'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Set Stock Controlled Vocabularies'),
-      '#weight' => -10
-    );
-
-    $form['set_cv']['message'] = array(
-         '#value' => t("This setting allows you to set which chado controlled vocabularies (cv)"
-                ." are used. Cvs are used to control user input for the type of stock,"
-          ." any properties they enter for a stock & the types of relationships"
-          ." between stocks. Only cvs already loaded into chado can be selected here.")
-    );
-
-    // get the list of CVs for the next form element
-    $sql = "SELECT * FROM {cv} ORDER BY name";
-    $results = chado_query($sql);
-    $cv_options = array();
-    while ($r = db_fetch_object($results)) {
-      $cv_options[$r->cv_id] = $r->name;
-    }
-
-    $form['set_cv']['stock_types_cv'] = array(
-     '#type' => 'select',
-     '#title' => t('Controlled Vocabulary governing Stock Types'),
-     '#options' => $cv_options,
-     '#default_value' => variable_get('chado_stock_types_cv', 0)
-    );
-
-    $form['set_cv']['stock_prop_types_cv'] = array(
-     '#type' => 'select',
-     '#title' => t('Controlled Vocabulary governing Types of Stock Properties'),
-     '#description' => t("This cv must contain a cvterm entry where name='synonym'."),
-     '#options' => $cv_options,
-     '#default_value' => variable_get('chado_stock_prop_types_cv', 0)
-    );
-
-    $form['set_cv']['stock_relationship_cv'] = array(
-     '#type' => 'select',
-     '#title' => t('Controlled Vocabulary governing Types of Relationsips between Stocks'),
-     '#options' => $cv_options,
-     '#default_value' => variable_get('chado_stock_relationship_cv', 0)
-    );
-
-    $form['set_cv']['button'] = array(
-      '#type' => 'submit',
-      '#value' => t('Set Controlled Vacabularies')
-    );
-
-    // SYNC STOCKS-----------------------------------------------------------------------------------------------
-    $form['sync'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Sync Stocks'),
-      '#weight' => -10
-    );
-
-    $form['sync']['description'] = array(
-      '#type' => 'item',
-      '#value' => t("Click the 'Sync all Germplasm' button to create Drupal ".
-         "content for stocks in chado. Depending on the ".
-         "number of stocks in the chado database this may take a long ".
-         "time to complete. ")
-    );
-
-    // get the list of organisms
-    $sql = "SELECT * FROM {Organism} ORDER BY genus, species";
-    $org_rset = chado_query($sql);
-    $organisms = array();
-    $organisms[''] = '';
-    while ($organism = db_fetch_object($org_rset)) {
-      $organisms[$organism->organism_id] = "$organism->genus $organism->species ($organism->common_name)";
-    }
-    $form['sync']['organisms'] = array(
-      '#type' => 'checkboxes',
-      '#title' => t('Organisms for which Stocks should be sync\'d'),
-      '#description' => t('Only sync\'d Organisms are listed. Leaving an organism unchecked does not delete already sync\'d Stocks.'),
-      '#options' => $organisms,
-      '#required'    => FALSE,
-      '#prefix'      => '<div id="lib_boxes">',
-      '#suffix'      => '</div>'
-    );
-
-    $form['sync']['button'] = array(
-      '#type' => 'submit',
-      '#value' => t('Sync Stocks')
-    );
-    get_tripal_stock_admin_form_cleanup_set($form);
-  }
-
-  return system_settings_form($form);
-
-}
-
-/**
- * Implements hook_form_validate(): Validates user input
- *
- * @param $form
- *   An array describing the form that was rendered
- * @param $form_state
- *   An array describing the current state of the form including user input
- *
- * @ingroup tripal_stock
- */
-function tripal_stock_admin_validate($form, &$form_state) {
-  global $user;  // we need access to the user info
-  $job_args = array();
-
-  // Sync Stocks
-  if ($form_state['values']['op'] == t('Sync Stocks')) {
-    // Array organism_id => organims common_name
-    //  which only includes those organisms which the user wants to select stocks for
-    $organisms_2b_syncd = $form_state['values']['organisms'];
-
-    //for each organism selected submit job (handled by tripal_stock_sync_stock_set)
-    //  which syncs all stocks with an organism_id equal to the selelcted organism
-    foreach ( $organisms_2b_syncd as $organism_id ) {
-      if ($organism_id != 0) {
-        $job_args[0] = $organism_id;
-        tripal_add_job("Sync Stocks from Organism $organism_id", 'tripal_stock',
-          'tripal_stock_sync_stock_set', $job_args, $user->uid);
-      }
-    }
-  }
-
-  if ($form_state['values']['op'] == t('Set Controlled Vacabularies')) {
-    variable_set('chado_stock_types_cv', $form_state['values']['stock_types_cv']);
-    variable_set('chado_stock_prop_types_cv', $form_state['values']['stock_prop_types_cv']);
-    variable_set('chado_stock_relationship_cv', $form_state['values']['stock_relationship_cv']);
-  }
-
-  // Submit the Cleanup Job if selected
-  if ($form_state['values']['op'] == t('Clean up orphaned stocks')) {
-    tripal_add_job('Cleanup orphaned stocks', 'tripal_stock',
-       'tripal_stock_cleanup', $job_args, $user->uid);
-  }
-}
-
-/**
- * Sync stocks associated with a given organism or sync all stocks
- *
- * Note: This is essentially an API function to make tripal stock sync act similar to other tripal modules
- *
- * @param $organism_id
- *   The ID of the organism to sync all stocks for
- * @param $job_id
- *   The ID of the tripal job
- */
-function tripal_stock_sync_stocks($organism_id, $job_id) {
-
-  if ($organism_id) {
-    return tripal_stock_sync_stock_set($organism_id, $job_id);
-  }
-  else {
-    //get a list of all organisms and sync all stocks for all organisms
-    $organisms = tripal_core_chado_select('organism', array('organism_id','genus','species','common_name'), array());
-    foreach ($organisms as $o) {
-      print "Syncing stocks associated with $o->genus $o->species ($o->common_name)\n";
-      tripal_stock_sync_stock_set($o->organism_id, $job_id);
-    }
-  }
-
-}
-
-/**
- * Syncs all Stocks associated with an organism
- *
- * Note: Handling of multiple organisms is done in tripal_stock_admin_validate()
- *
- * @param $organism_id
- *   The chado primary key of the organism for which stocks should be sync'd
- * @param $job_id
- *   The tripal job ID
- *
- * @return
- *   TRUE if successful; FALSE otherwise
- *
- * @ingroup tripal_stock
- */
-function tripal_stock_sync_stock_set($organism_id, $job_id) {
-  global $user;
-
-  if (!$organism_id) {
-    print '0 Stocks to Sync -No Organisms Selected.\n';
-  }
-  else {
-
-  // Get list of stocks to sync
-  $result = chado_query(
-     "SELECT stock_id, uniquename, type_id, organism_id FROM {stock} WHERE organism_id=%d",
-      $organism_id
-  );
-
-  $stocks_created_count = 0; //keeps track of total number of stocks successfully created
-  $stocks_attempted = 0;
-  // foreach stock to be sync'd -> create node & add stock_id
-  while ( $r = db_fetch_object($result) ) {
-    // $r is the current stock to be sync'd
-    $stocks_attempted++;
-
-    print 'Processing ' . $r->uniquename . "... ";
-
-    // check not already in drupal
-    $in_drupal_query = db_query(
-      "SELECT * FROM {chado_stock} WHERE stock_id=%d",
-      $r->stock_id
-    );
-    if ( !db_fetch_object($in_drupal_query) ) {
-
-      //create new chado_stock node
-      $new_node = new stdClass();
-      $new_node->type = 'chado_stock';
-      $new_node->uid = $user->uid;
-      $new_node->title = $r->uniquename;
-      $new_node->type_id = $r->type_id;
-      $new_node->organism_id = $r->organism_id;
-      $new_node->stock_id = $r->stock_id;
-      $new_node->chado_stock_exists = TRUE;
-
-      //print 'New Node:';
-      //print_r($new_node);
-
-      node_validate($new_node);
-
-      if (!form_get_errors()) {
-        //print 'Try to Create Node ';
-        $node = node_submit($new_node);
-        node_save($node);
-        if ($node->nid) {
-          $stocks_created_count++;
-
-          //Add stock id to chado_stock table
-          /**
-           db_query(
-             "INSERT INTO {chado_stock} (stock_id, nid, vid) VALUES (%d, %d, %d)",
-             $r->stock_id,
-             $node->nid,
-             $node->vid
-           );
-           */
-          }
-        }
-        else {
-          print "Not completed due to errors:\nCreate Stock Form Errors: ";
-          print_r(form_get_errors());
-        }
-        print "Nid=" . $node->nid . "\n";
-      }
-      else {
-        print "Skipped $r->uniquename because it's already in drupal.\n";
-      } //end of if not already in drupal
-    } //end of while still stocks to be sync'd
-  } //end of if organism_id not supplied
-
-  if ($stocks_attempted == 0) {
-    print "No stocks retrieved for organism (" . $organism_id . ")\n";
-    return 1;
-  }
-  else {
-    if ($stocks_created_count > 0) {
-      print "$stocks_created_count Stocks Successfully Created\n";
-      return 1;
-    }
-    else {
-      return 0;
-    }
-  }
-}
-
-/**
- *
- *
- * @ingroup tripal_stock
- */
-function get_tripal_stock_admin_form_cleanup_set(&$form) {
-  $form['cleanup'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Clean Up')
-  );
-  $form['cleanup']['description'] = array(
-     '#type' => 'item',
-     '#value' => t("With Drupal and Chado residing in different databases ".
-        "it is possible that nodes in Drupal and stocks in Chado become ".
-        "\"orphaned\".  This can occur if an stock node in Drupal is ".
-        "deleted but the corresponding chado stock is not and/or vice ".
-        "versa. Click the button below to resolve these discrepancies."),
-     '#weight' => 1,
-  );
-  $form['cleanup']['button'] = array(
-    '#type' => 'submit',
-    '#value' => t('Clean up orphaned stocks'),
-    '#weight' => 2,
-  );
-}
-/**
- * Remove orphaned drupal nodes
- *
- * @param $dummy
- *   Not Used -kept for backwards compatibility
- * @param $job_id
- *   The id of the tripal job executing this function
- *
- * @ingroup tripal_stock
- */
-function tripal_stock_cleanup($dummy = NULL, $job_id = NULL) {
-
-  return tripal_core_clean_orphaned_nodes('stock', $job_id);
-
-}

+ 262 - 0
tripal_stock/includes/tripal_stock.admin.inc

@@ -0,0 +1,262 @@
+<?php
+/**
+ * @file
+ * @todo Add file header description
+ */
+
+/**
+ * Purpose: Provide administration options for chado_stocks
+ *
+ * @return
+ *   Form array (as described by the drupal form api)
+ *
+ * @ingroup tripal_stock
+ */
+function tripal_stock_admin() {
+  $form = array();
+
+  get_tripal_stock_admin_form_title_set($form);
+  get_tripal_stock_admin_form_url_set($form);
+  get_tripal_stock_admin_form_vocabulary_set($form);       
+  get_tripal_stock_admin_form_cleanup_set($form);
+  
+
+  return system_settings_form($form);
+
+}
+
+/**
+ * Implements hook_form_validate(): Validates user input
+ *
+ * @param $form
+ *   An array describing the form that was rendered
+ * @param $form_state
+ *   An array describing the current state of the form including user input
+ *
+ * @ingroup tripal_stock
+ */
+function tripal_stock_admin_validate($form, &$form_state) {
+  global $user;  // we need access to the user info
+  $job_args = array();
+
+  variable_set('chado_stock_types_cv', $form_state['values']['stock_types_cv']);
+  variable_set('chado_stock_prop_types_cv', $form_state['values']['stock_prop_types_cv']);
+  variable_set('chado_stock_relationship_cv', $form_state['values']['stock_relationship_cv']);  
+  variable_set('chado_stock_url_string', $form_state['values']['chado_stock_url_string']);
+  
+  switch ($form_state['values']['op']) {
+    case  t('Set Controlled Vacabularies') :      
+      break;
+      
+    case t('Clean up orphaned stocks') :
+      tripal_add_job('Cleanup orphaned stocks', 'tripal_stock',
+         'tripal_stock_cleanup', $job_args, $user->uid);
+      break;
+    
+    case t('Set Stock URLs') :
+      tripal_add_job('Set Stock URLs', 'tripal_stock',
+        'tripal_stock_set_urls', $job_args, $user->uid);
+      break;
+  }
+
+}
+
+/**
+ * 
+ * @param $form
+ */
+function get_tripal_stock_admin_form_title_set(&$form) {
+
+  $form['title'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Stock Page Titles'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['title']['desc'] = array(
+    '#type'  => 'markup',
+    '#value' => t('Each synced stock must have a unique page title, however, stocks
+      may have the same name if they are of different types or from different 
+      organisms.  Therefore, we must be sure that the page titles can uniquely 
+      identify the stock being viewed.  Select an option below that will 
+      uniquely identify all stocks on your site.'),
+  );
+  $options = array(
+    'stock_unique_name'  => 'Only stock unique name',
+    'stock_name'         => 'Only stock name',
+    'unique_constraint'  => 'Includes stock name, uniquename, type and species',
+  );
+  $form['title']['chado_stock_title'] = array(
+    '#title'         => t('Stock Page Titles'),
+    '#type'          => 'radios',
+    '#description'   => t('Choose a title type  from the list above that is 
+      guaranteed to be unique for all stocks If in doubt it is safest to choose 
+      the last option as that guarantees uniqueness. Click the 
+      \'Save Configuration\' button at the bottom to save your selection.'),
+    '#required'      => FALSE,
+    '#options'       => $options,
+    '#default_value' => variable_get('chado_stock_title', 'unique_constraint'),
+  );
+}
+/**
+ * 
+ * @param  $form
+ */
+function get_tripal_stock_admin_form_url_set(&$form) {
+
+  $form['url'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Stock URL Path'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  $options = array(
+    'SID[id]'      => '[id]:' . t('The Chado stock_id'),
+    'stock'        => 'stock:' . t('Chado table name'),
+    '[genus]'      => '[genus]:' . t('Genus to which the stock belongs'),
+    '[species]'    => '[species]:' . t('Species to which the stock belongs'),
+    '[type]'       => '[type]:' . t('The type of stock'),
+    '[uniquename]' => '[uniquename]:' . t('The stock unique name'),
+    '[name]'       => '[name]:' . t('The stock name'),
+    'reset'        => t('Reset'),
+  );
+  
+
+  $form['url']['chado_stock_url_string'] = array(
+    '#title' => 'URL Syntax',
+    '#type' => 'textfield',
+    '#description' => t('You may rearrange elements in this text box to
+      customize the URLs.  The available tags include: [id],
+      [uniquename]. [name], [species], [genus], [type]. You can separate or
+      include any text between the tags. Click the "Set Stock URLs" button to 
+      reset the URLs for all stock pages.  Click the "Save Configuration" button to
+      simply save this setup. <b>Important</b>: be sure that whatever you choose will always be unique even considering
+      future data that may be added.  If you include the Chado table name, genus, species, type 
+      and uniquename you are guaranteed to have a unique URL. For example stock/[genus]/[species]/[type]/[uniquename]'),
+    '#size' => 150,
+    '#default_value' => variable_get('chado_stock_url_string', '/stock/[genus]/[species]/[type]/[uniquename]'), 
+  );
+  
+  $form['url']['chado_stock_url'] = array(
+    '#title'         => t('URL components'),
+    '#type'          => 'checkboxes',
+    '#required'      => FALSE,
+    '#options'       => $options,
+    '#description'   => t('Click the item above to make it appear in the URL Syntax box'),
+    '#attributes'    => array(
+      'onclick' => '
+        box = $(\'#edit-chado-stock-url-string\');
+        if (this.value == \'reset\') {
+          box.val(\'\');
+        }
+        else {        
+          box.val(box.val() + "/" + this.value);          
+        }
+        this.checked = false;
+      ',
+    ),
+  );  
+  
+  $form['url']['button'] = array(
+    '#type' => 'submit',
+    '#value' => t('Set Stock URLs'),
+  );
+}
+/**
+ * 
+ * @param $form
+ */
+function get_tripal_stock_admin_form_vocabulary_set(&$form) {
+
+  $form['set_cv'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Set Stock Controlled Vocabularies'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  $form['set_cv']['message'] = array(
+       '#value' => t("This setting allows you to set which chado controlled vocabularies (cv)"
+              ." are used. Cvs are used to control user input for the type of stock,"
+        ." any properties they enter for a stock & the types of relationships"
+        ." between stocks. Only cvs already loaded into chado can be selected here.")
+  );
+
+  // get the list of CVs for the next form element
+  $sql = "SELECT * FROM {cv} ORDER BY name";
+  $results = chado_query($sql);
+  $cv_options = array();
+  while ($r = db_fetch_object($results)) {
+    $cv_options[$r->cv_id] = $r->name;
+  }
+
+  $form['set_cv']['stock_types_cv'] = array(
+   '#type' => 'select',
+   '#title' => t('Controlled Vocabulary governing Stock Types'),
+   '#options' => $cv_options,
+   '#default_value' => variable_get('chado_stock_types_cv', 0)
+  );
+
+  $form['set_cv']['stock_prop_types_cv'] = array(
+   '#type' => 'select',
+   '#title' => t('Controlled Vocabulary governing Types of Stock Properties'),
+   '#description' => t("This cv must contain a cvterm entry where name='synonym'."),
+   '#options' => $cv_options,
+   '#default_value' => variable_get('chado_stock_prop_types_cv', 0)
+  );
+
+  $form['set_cv']['stock_relationship_cv'] = array(
+   '#type' => 'select',
+   '#title' => t('Controlled Vocabulary governing Types of Relationsips between Stocks'),
+   '#options' => $cv_options,
+   '#default_value' => variable_get('chado_stock_relationship_cv', 0)
+  );
+
+  $form['set_cv']['button'] = array(
+    '#type' => 'submit',
+    '#value' => t('Set Controlled Vacabularies')
+  );
+}
+/**
+ *
+ *
+ * @ingroup tripal_stock
+ */
+function get_tripal_stock_admin_form_cleanup_set(&$form) {
+  $form['cleanup'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Clean Up'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['cleanup']['description'] = array(
+     '#type' => 'item',
+     '#value' => t("With Drupal and Chado residing in different databases ".
+        "it is possible that nodes in Drupal and stocks in Chado become ".
+        "\"orphaned\".  This can occur if an stock node in Drupal is ".
+        "deleted but the corresponding chado stock is not and/or vice ".
+        "versa. Click the button below to resolve these discrepancies."),
+     '#weight' => 1,
+  );
+  $form['cleanup']['button'] = array(
+    '#type' => 'submit',
+    '#value' => t('Clean up orphaned stocks'),
+    '#weight' => 2,
+  );
+}
+/**
+ * Remove orphaned drupal nodes
+ *
+ * @param $dummy
+ *   Not Used -kept for backwards compatibility
+ * @param $job_id
+ *   The id of the tripal job executing this function
+ *
+ * @ingroup tripal_stock
+ */
+function tripal_stock_cleanup($dummy = NULL, $job_id = NULL) {
+
+  return tripal_core_clean_orphaned_nodes('stock', $job_id);
+
+}

+ 538 - 0
tripal_stock/includes/tripal_stock.sync_stocks.inc

@@ -0,0 +1,538 @@
+<?php
+
+/**
+ * @file
+ * @todo Add file header description
+ */
+
+
+# This script can be run as a stand-alone script to sync all the stocks from chado to drupal
+// Parameter f specifies the stock_id to sync
+// -f 0 will sync all stocks
+
+$arguments = getopt("f:t:");
+
+if (isset($arguments['f']) and isset($arguments['t']) and $arguments['t'] == 'chado_stock') {
+  $drupal_base_url = parse_url('http://www.example.com');
+  $_SERVER['HTTP_HOST'] = $drupal_base_url['host'];
+  $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
+  $_SERVER['REMOTE_ADDR'] = NULL;
+  $_SERVER['REQUEST_METHOD'] = NULL;
+
+  require_once 'includes/bootstrap.inc';
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+
+  $stock_id = $arguments['f'];
+
+  if ($stock_id > 0 ) {
+    tripal_stock_sync_stock($stock_id);
+  }
+  else{
+    print "syncing all stocks...\n";
+    tripal_stock_sync_stocks();
+  }
+}
+/**
+ *
+ */
+function tripal_stock_sync_form() {
+
+  $form['description'] = array(
+  '#type' => 'item',
+  '#value' => t("Stocks of the types listed ".
+     "below in the Stock Types box will be synced (leave blank to sync all types). You may limit the ".
+     "stocks to be synced by a specific organism. Depending on the ".
+     "number of stocks in the chado database this may take a long ".
+     "time to complete. "),
+  );
+
+  $form['stock_types'] = array(
+    '#title'       => t('Stock Types'),
+    '#type'        => 'textarea',
+    '#description' => t("Enter the names of the stock types to sync. " . 
+       "Leave blank to sync all stocks. Pages for these stock ".
+       "types will be created automatically for stocks that exist in the ".
+       "chado database.  The names listed here should be spearated by ".
+       "spaces or entered separately on new lines. The names must match ".
+       "exactly (spelling and case) with terms in the sequence ontology"),
+    '#default_value' => variable_get('chado_sync_stock_types', ''),
+  );
+
+  // get the list of organisms
+  $sql = "SELECT * FROM {organism} ORDER BY genus, species";
+  $orgs = tripal_organism_get_synced();
+  $organisms[] = '';
+  foreach ($orgs as $organism) {
+    $organisms[$organism->organism_id] = "$organism->genus $organism->species ($organism->common_name)";
+  }
+  $form['organism_id'] = array(
+    '#title'       => t('Organism'),
+    '#type'        => t('select'),
+    '#description' => t("Choose the organism for which stocks types set above will be synced. Only organisms which also have been synced will appear in this list."),
+    '#options'     => $organisms,
+  );
+
+
+  $form['button'] = array(
+    '#type' => 'submit',
+    '#value' => t('Sync all Stocks'),
+    '#weight' => 3,
+  );
+
+  return $form;
+}
+/**
+ *
+ */
+function tripal_stock_sync_form_validate($form, &$form_state) {
+  $organism_id   = $form_state['values']['organism_id'];
+  $stock_types = $form_state['values']['stock_types'];
+
+  // nothing to do
+}
+/**
+ *
+ */
+function tripal_stock_sync_form_submit($form, &$form_state) {
+
+  global $user;
+
+  $organism_id   = $form_state['values']['organism_id'];
+  $stock_types = $form_state['values']['stock_types'];
+
+  $job_args = array(0, $organism_id, $stock_types);
+
+  if ($organism_id) {
+    $organism = tripal_core_chado_select('organism', array('genus', 'species'), array('organism_id' => $organism_id));
+    $title = "Sync stocks for " .  $organism[0]->genus . " " . $organism[0]->species;
+  }
+  else {
+    $title = 'Sync stocks';
+  }  
+
+  variable_set('chado_sync_stock_types', $stock_types);
+
+  tripal_add_job($title, 'tripal_stock', 'tripal_stock_sync_stocks', $job_args, $user->uid);
+}
+/**
+ *  
+ * @param $na 
+ *   Tripal expects all jobs to have at least one argument. For this function
+ *   we don't need any, so we have this dummy argument as a filler
+ * @param $job_id
+ */
+function tripal_stock_set_urls($na = NULL, $job = NULL) {
+  
+  // begin the transaction
+  db_query("BEGIN");
+      
+  print "\nNOTE: Setting of URLs is performed using a database transaction. \n" .
+        "If the load fails or is terminated prematurely then the entire set of \n" .
+        "new URLs will be rolled back and no changes will be made\n\n";
+  
+  // get the number of records we need to set URLs for
+  $csql = "SELECT count(*) FROM {chado_stock}";
+  $num_nodes = db_result(db_query($csql));
+    
+  // calculate the interval at which we will print an update on the screen
+  $num_set = 0;
+  $num_per_interval = 100;
+  
+  // prepate the statements which will quickly add url alias. Because these
+  // are not Chado tables we must manually prepare them 
+  $psql = "
+    PREPARE del_url_alias_by_src (text) AS
+    DELETE FROM {url_alias} WHERE src = \$1
+  ";
+  db_query($psql);
+  $psql = "
+    PREPARE ins_url_alias_nisrds (text, text) AS
+    INSERT INTO url_alias (src, dst) VALUES (\$1, \$2)
+  ";
+  db_query($psql);
+  
+  // get the URL alias syntax string
+  $url_alias = variable_get('chado_stock_url_string', '/stock/[genus]/[species]/[type]/[uniquename]'); 
+  if (!$url_alias) {
+    $url_alias = '/stock/[genus]/[species]/[type]/[uniquename]';
+  } 
+  $url_alias = preg_replace('/^\//', '', $url_alias); // remove any preceeding forward slash
+  
+  
+  // get the list of stocks that have been synced
+  $sql = "SELECT * FROM {chado_stock}";
+  $nodes = db_query($sql);  
+  while ($node = db_fetch_object($nodes)) {
+   
+    // get the URL alias
+    $src = "node/$node->nid";
+    $dst = tripal_stock_get_stock_url($node, $url_alias);
+    if (!$dst) {
+      db_query('DEALLOCATE "del_url_alias_by_src"');
+      db_query('DEALLOCATE "ins_url_alias_nisrds"');
+      db_query("ROLLBACK"); 
+      return; 
+    }
+    
+    // if the src and dst is the same (the URL alias couldn't be set)
+    // then skip to the next one. There's nothing we can do about this one.
+    if($src == $dst) {
+      continue;
+    }
+    
+    // remove any previous alias and then add the new one
+    $success = db_query("EXECUTE del_url_alias_by_src('%s')", $src);    
+    if (!$success) {
+      db_query('DEALLOCATE "del_url_alias_by_src"');
+      db_query('DEALLOCATE "ins_url_alias_nisrds"');
+      db_query("ROLLBACK");
+      watchdog('trp-seturl', "Failed Removing URL Alias: %src", array('%src' => $src), WATCHDOG_ERROR);
+      return;
+    }
+    $success = db_query("EXECUTE ins_url_alias_nisrds('%s', '%s')", $src, $dst);
+    if (!$success) {
+      db_query('DEALLOCATE "del_url_alias_by_src"');
+      db_query('DEALLOCATE "ins_url_alias_nisrds"');
+      db_query("ROLLBACK");
+      watchdog('trp-seturl', "Failed Adding URL Alias: %dst", array('%dst' => $dst), WATCHDOG_ERROR);
+      return;
+    }
+
+    // update the job status every 1% stocks
+    if ($job and $num_set % $num_per_interval == 0) {
+      $percent = ($num_set / $num_nodes) * 100;
+      tripal_job_set_progress($job, intval($percent));
+      $percent = sprintf("%.2f", $percent);
+      print "Setting URLs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
+      
+    }
+    $num_set++;
+  }
+  $percent = ($num_set / $num_nodes) * 100;
+  tripal_job_set_progress($job, intval($percent));
+  $percent = sprintf("%.2f", $percent);
+  print "Setting URLs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
+  print "\nDone. Set " . number_format($num_set) . " URLs\n";
+  
+  // unprepare the statements
+  db_query('DEALLOCATE "del_url_alias_by_src"');
+  db_query('DEALLOCATE "ins_url_alias_nisrds"');
+  
+  db_query("COMMIT");
+}
+/**
+ * 
+ * @param $node
+ *   A node object containing at least the stock_id and nid
+ * @param $url_alias
+ *   Optional.  This should be the URL alias syntax string that contains
+ *   placeholders such as [id], [genus], [species], [name], [uniquename],
+ *   and [type].  These placeholders will be substituted for actual values.
+ *   If this parameter is not provided then the value of the 
+ *   chado_stock_url_string Drupal variable will be used.
+ */
+function tripal_stock_get_stock_url($node, $url_alias = NULL) {
+
+  // get the starting URL alias
+  if(!$url_alias) {
+    $url_alias = variable_get('chado_stock_url_string', '/stock/[genus]/[species]/[type]/[uniquename]'); 
+    if (!$url_alias) {
+      $url_alias = '/stock/[genus]/[species]/[type]/[uniquename]';
+    } 
+    $url_alias = preg_replace('/^\//', '', $url_alias); // remove any preceeding forward slash
+  }
+
+  // get the stock 
+  $values = array('stock_id' => $node->stock_id); 
+  $options = array('statement_name' => 'sel_stock_id');       
+  $stock = tripal_core_chado_select('stock', array('*'), $values, $options);
+  if (!$stock) {
+    watchdog('trp-seturl', "Cannot find stock when setting URL alias for stock: %id", array('%id' => $node->stock_id), WATCHDOG_ERROR);
+    return FALSE;  
+  }
+  $stock = (object) $stock[0];
+  
+  // get the organism
+  $values = array('organism_id' => $stock->organism_id);
+  $options = array('statement_name' => 'sel_organism_id');
+  $organism  = tripal_core_chado_select('organism', array('*'), $values, $options);  
+  if (!$organism) {
+    watchdog('trp-seturl', "Cannot find organism when setting URL alias for stock: %id", array('%id' => $node->stock_id), WATCHDOG_ERROR);
+    return FALSE;  
+  }
+  $genus = preg_replace('/\s/', '_', strtolower($organism[0]->genus));
+  $species = preg_replace('/\s/', '_', strtolower($organism[0]->species)); 
+
+  // get the type
+  $values = array('cvterm_id' => $stock->type_id);
+  $options = array('statement_name' => 'sel_cvterm_id');
+  $cvterm = tripal_core_chado_select('cvterm', array('name'), $values, $options);
+  if (!$cvterm) {
+    watchdog('trp-seturl', "Cannot find type when setting URL alias for stock: %id", array('%id' => $node->stock_id), WATCHDOG_ERROR);
+    return FALSE;  
+  }
+  $type = preg_replace('/\s/', '_', $cvterm[0]->name);
+  
+  // now substitute in the values
+  $url_alias = preg_replace('/\[id\]/', $stock->stock_id, $url_alias);
+  $url_alias = preg_replace('/\[genus\]/', $genus, $url_alias);
+  $url_alias = preg_replace('/\[species\]/', $species, $url_alias);
+  $url_alias = preg_replace('/\[type\]/', $type, $url_alias);
+  $url_alias = preg_replace('/\[name\]/', $stock->name, $url_alias);
+  $url_alias = preg_replace('/\[uniquename\]/', $stock->uniquename, $url_alias);
+ 
+  // the dst field of the url_alias table is only 128 characters long. 
+  // if this is the case then simply return the node URL, we can't set this one
+  if (strlen($url_alias) > 128) {
+    watchdog('trp-seturl', "Cannot set alias longer than 128 characters: %alias.", array('%alias' => $url_alias), WATCHDOG_ERROR);
+    return "node/" . $node->nid;
+  }
+  
+  return $url_alias;
+}
+/**
+ *
+ *
+ * @ingroup tripal_stock
+ */
+function tripal_stock_sync_stocks($max_sync = 0, $organism_id = NULL,
+  $stock_types = NULL, $job_id = NULL) {
+  //print "Syncing stocks (max of $max_sync)\n";
+  $i = 0;
+
+  // get the list of available sequence ontology terms for which
+  // we will build drupal pages from stocks in chado.  If a stock
+  // is not one of the specified typse we won't build a node for it.
+  if (!$stock_types) {
+    $allowed_types = variable_get('chado_sync_stock_types', '');
+  }
+  else {
+    $allowed_types = $stock_types;
+  }
+  
+  if ($allowed_types) {
+    $allowed_types = preg_replace("/[\s\n\r]+/", " ", $allowed_types);
+    print "Looking for stocks of type: $allowed_types\n";
+  
+    $so_terms = split(' ', $allowed_types);
+    $where_cvt = "";
+    foreach ($so_terms as $term) {
+      $where_cvt .= "CVT.name = '$term' OR ";
+    }
+    $where_cvt = drupal_substr($where_cvt, 0, drupal_strlen($where_cvt)-3);  # strip trailing 'OR'
+  }
+  else {
+    $where_cvt = '1=1';
+  }
+
+  // get the list of organisms that are synced and only include stocks from
+  // those organisms
+  $orgs = tripal_organism_get_synced();
+  $where_org = "";
+  foreach ($orgs as $org) {
+    if ($organism_id) {
+      if ($org->organism_id and $org->organism_id == $organism_id) {
+        $where_org .= "S.organism_id = $org->organism_id OR ";
+      }
+    }
+    else {
+      if ($org->organism_id) {
+        $where_org .= "S.organism_id = $org->organism_id OR ";
+      }
+    }
+  }
+  $where_org = drupal_substr($where_org, 0, drupal_strlen($where_org)-3);  # strip trailing 'OR'
+
+  // use this SQL statement to get the stocks that we're going to upload
+  $sql = "SELECT stock_id ".
+        "FROM {stock} S ".
+        "  INNER JOIN {cvterm} CVT ON S.type_id = CVT.cvterm_id ".
+        "WHERE ($where_cvt) AND ($where_org) ".
+        "ORDER BY stock_id";
+
+  // get the list of stocks
+  $results = chado_query($sql);
+
+  // load into ids array
+  $count = 0;
+  $ids = array();
+  while ($id = db_fetch_object($results)) {
+    $ids[$count] = $id->stock_id;
+    $count++;
+  }
+
+  // make sure our vocabularies are set before proceeding
+//  tripal_stock_set_vocabulary();
+
+  // pre-create the SQL statement that will be used to check
+  // if a stock has already been synced.  We skip stocks
+  // that have been synced
+  $sql = "SELECT * FROM {chado_stock} WHERE stock_id = %d";
+
+  // Iterate through stocks that need to be synced
+  $interval = intval($count * 0.01);
+  if ($interval < 1) {
+    $interval = 1;
+  }
+  $num_ids = sizeof($ids);
+  $i = 0;
+  foreach ($ids as $stock_id) {
+    // update the job status every 1% stocks
+    if ($job_id and $i % $interval == 0) {
+      tripal_job_set_progress($job_id, intval(($i/$count)*100));
+    }
+    // if we have a maximum number to sync then stop when we get there
+    // if not then just continue on
+    if ($max_sync and $i == $max_sync) {
+      return '';
+    }
+    if (!db_fetch_object(db_query($sql, $stock_id))) {
+
+      # parsing all the stocks can cause memory overruns
+      # we are not sure why PHP does not clean up the memory as it goes
+      # to avoid this problem we will call this script through an
+      # independent system call
+      print ($i + 1) . " of $num_ids Syncing stock id: $stock_id\n";
+      $cmd = "php " . drupal_get_path('module', 'tripal_stock') . "/includes/tripal_stock.sync_stocks.inc -f $stock_id -t chado_stock";
+      print "$cmd\n";
+      system($cmd);
+
+    }
+    $i++;
+  }
+
+  return '';
+}
+
+/**
+ *
+ *
+ * @ingroup tripal_stock
+ */
+function tripal_stock_sync_stock($stock_id) {
+  
+print "\tSyncing stock $stock_id\n";
+  
+global $user;
+  $create_node = 1;   // set to 0 if the node exists and we just sync and not create
+
+  // get the accession prefix
+  $aprefix = variable_get('chado_stock_accession_prefix', 'SID');
+  
+  // if we don't have a stock_id then return
+  if (!$stock_id) {
+    drupal_set_message(t("Please provide a stock_id to sync"));
+    return '';
+  }
+
+  // get information about this stock
+  $fsql = "SELECT S.*, O.genus, O.species,CVT.name as cvname ".
+          "FROM {stock} S ".
+          "  INNER JOIN {cvterm} CVT ON S.type_id = CVT.cvterm_id ".
+          "  INNER JOIN {organism} O ON S.organism_id = O.organism_ID ".
+          "WHERE S.stock_id = %d";
+  $stock = db_fetch_object(chado_query($fsql, $stock_id));
+  
+  /*
+  // get the synonyms for this stock
+  $synsql = "SELECT S.name ".
+            "FROM {stock_synonym} SS ".
+            "  INNER JOIN {synonym} S on SS.synonym_id = S.synonym_id ".
+            "WHERE SS.stock_id = %d";
+  $synonyms = chado_query($synsql, $stock_id);  
+    
+  // now add these synonyms to the stock object as a single string
+  $synstring = '';
+  while ($synonym = db_fetch_object($synonyms)) {
+    $synstring .= "$synonym->name\n";
+  }
+  $stock->synonyms = $synstring;
+  */
+
+  // check to make sure that we don't have any nodes with this stock name as a title
+  // but without a corresponding entry in the chado_stock table if so then we want to
+  // clean up that node.  (If a node is found we don't know if it belongs to our stock or
+  // not since stocks can have the same name/title.)
+  $tsql =  "SELECT * FROM {node} N ".
+           "WHERE title = '%s'";
+  $cnsql = "SELECT * FROM {chado_stock} ".
+           "WHERE nid = %d";
+  $nodes = db_query($tsql, $stock->name);
+  // cycle through all nodes that may have this title
+  while ($node = db_fetch_object($nodes)) {
+    $stock_nid = db_fetch_object(db_query($cnsql, $node->nid));
+    if (!$stock_nid) {
+      drupal_set_message(t("%stock_id: A node is present but the chado_stock entry is missing... correcting", array('%stock_id' => $stock_id)));
+      node_delete($node->nid);
+    }
+  }
+
+  // check if this stock already exists in the chado_stock table.
+  // if we have a chado stock, we want to check to see if we have a node
+  $cfsql = "SELECT * FROM {chado_stock} ".
+           "WHERE stock_id = %d";
+  $nsql =  "SELECT * FROM {node} N ".
+           "WHERE nid = %d";
+  $chado_stock = db_fetch_object(db_query($cfsql, $stock->stock_id));
+  if ($chado_stock) {
+    drupal_set_message(t("%stock_id: A chado_stock entry exists", array('%stock_id' => $stock_id)));
+    $node = db_fetch_object(db_query($nsql, $chado_stock->nid));
+    if (!$node) {
+      // if we have a chado_stock but not a node then we have a problem and
+      // need to cleanup
+      drupal_set_message(t("%stock_id: The node is missing, but has a chado_stock entry... correcting", array('%stock_id' => $stock_id)));
+      $df_sql = "DELETE FROM {chado_stock} WHERE stock_id = %d";
+      db_query($df_sql, $stock_id);
+    }
+    else {
+      drupal_set_message(t("%stock_id: A corresponding node exists", array('%stock_id' => $stock_id)));
+      $create_node = 0;
+    }
+  }
+
+  // if we've encountered an error then just return.
+  if ($error_msg = db_error()) {
+    //print "$error_msg\n";
+    return '';
+  }
+
+  // if a drupal node does not exist for this stock then we want to
+  // create one.  Note that the node_save call in this block
+  // will call the hook_submit function which
+  if ($create_node) {
+    // get the organism for this stock
+    $sql = "SELECT * FROM {organism} WHERE organism_id = %d";
+    $organism = db_fetch_object(chado_query($sql, $stock->organism_id));
+
+    drupal_set_message(t("%stock_id: Creating node $stock->name", array('%stock_id' => $stock_id)));
+    $new_node = new stdClass();
+    $new_node->type = 'chado_stock';
+    $new_node->uid = $user->uid;
+    $new_node->title = "$stock->name, $stock->uniquename ($stock->cvname) $organism->genus $organism->species";
+    $new_node->sname = "$stock->name";
+    $new_node->uniquename = "$stock->uniquename";
+    $new_node->type_id = $stock->type_id;
+    $new_node->organism_id = $stock->organism_id;
+    $new_node->stock_id = $stock->stock_id;        
+    $new_node->chado_stock_exists = TRUE;
+    
+    // validate the node and if okay then submit
+    node_validate($new_node);
+    if ($errors = form_get_errors()) {
+      print "Error encountered validating new node. Cannot sync: $msg\n";
+      foreach ($errors as $key => $msg) {        
+        watchdog('trp-fsync', "%msg", array('%msg' => $msg), WATCHDOG_ERROR);
+      }
+      exit;
+    }
+    else {
+      $node = node_submit($new_node);
+      node_save($node);
+    }
+  }
+  else {
+    $node = $chado_stock;
+  }
+
+  return '';
+}

+ 243 - 77
tripal_stock/tripal_stock.module

@@ -21,7 +21,8 @@
  * Stock Module see the GMOD Wiki Page (http://gmod.org/wiki/Chado_Stock_Module)
  * @}
  */
-require_once("includes/tripal_stock-administration.inc");
+require_once("includes/tripal_stock.admin.inc");
+require_once("includes/tripal_stock.sync_stocks.inc");
 require_once("includes/other_module_api_functions.inc");
 require_once("includes/tripal_stock-secondary_tables.inc");
 require_once("includes/tripal_stock-properties.inc");
@@ -57,6 +58,15 @@ function tripal_stock_menu() {
     'access arguments' => array('administer tripal stocks'),
     'type' => MENU_NORMAL_ITEM
   );
+  
+  $items['admin/tripal/tripal_stock/sync'] = array(
+    'title' => ' Sync Stocks',
+    'description' => 'Sync stocks from Chado with Drupal',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_stock_sync_form'),
+    'access arguments' => array('administer tripal stocks'),
+    'type' => MENU_NORMAL_ITEM,
+  );
 
   // Adding Secondary Properties-----------------
   $items['node/%cs_node/properties'] = array(
@@ -116,6 +126,14 @@ function tripal_stock_menu() {
     'type' => MENU_LOCAL_TASK,
     'weight' => 10,
   );
+  
+  // the menu link for addressing any stock (by name, uniquename, synonym)
+  $items['stock/%'] = array(
+    'page callback' => 'tripal_stock_match_stocks_page',
+    'page arguments' => array(1),
+    'access arguments' => array('access chado_stock content'),
+    'type' => MENU_LOCAL_TASK,
+  );
   return $items;
 }
 
@@ -345,6 +363,29 @@ function chado_stock_load($node) {
   $values = array('stock_id' => $stock_id);
   $stock = tripal_core_generate_chado_var('stock', $values);
   
+  
+  // by default, the titles are saved using the unique constraint.  We will
+  // keep it the same, but remove the duplicate name if the unique name and name
+  // are identical
+  $title_type = variable_get('chado_stock_title', 'unique_constraint');
+  if($title_type == 'unique_constraint') {
+    if (strcmp($stock->name, $stock->uniquename)==0) {
+      $node->title = $stock->name . " (" . $stock->type_id->name . ") " . $stock->organism_id->genus . " " . $stock->organism_id->species ;
+    }
+    // in previous version of Tripal, the stock title was simply the unique name. 
+    // so, we recreate the title just to be sure all of our stock pages are consistent
+    else {
+      $node->title = $stock->name . ", " . $stock->uniquename . " (" . $stock->type_id->name . ") " . $stock->organism_id->genus . " " . $stock->organism_id->species ;  
+    }
+  }
+  // set the title to be the stock name or uniquename as configured
+  if($title_type == 'stock_name') {
+    $node->title = $stock->name;
+  }
+  if($title_type == 'stock_unique_name') {
+    $node->title = $stock->uniquename;  
+  }
+  
   // add this to the node
   $additions = new stdClass();
   $additions->stock = $stock;
@@ -413,7 +454,7 @@ function chado_stock_form($node, $form_state) {
     '#title' => t('Stock Name')
   );
 
-  $form['names']['title'] = array(
+  $form['names']['sname'] = array(
     '#type' => 'textfield',
     '#title' => t('Name'),
     '#default_value' => $node->stock->name,
@@ -440,8 +481,11 @@ function chado_stock_form($node, $form_state) {
   $type_options = tripal_cv_get_cvterm_options( variable_get('chado_stock_types_cv', 'NULL') );
   $type_options[0] = 'Select a Type';
   if ($node->nid == '') {
-  $type_default = 0; }
-  else { $type_default = $node->stock->type_id->cvterm_id; }
+    $type_default = 0; 
+  }
+  else { 
+    $type_default = $node->stock->type_id->cvterm_id; 
+  }
   $form['details']['type_id'] = array(
     '#type' => 'select',
     '#title' => t('Type of Stock'),
@@ -462,7 +506,7 @@ function chado_stock_form($node, $form_state) {
   $form['details']['organism_id'] = array(
     '#type' => 'select',
     '#title' => t('Source Organism for stock'),
-    '#default_value' => $organism_default,
+    '#default_value' => $node->stock->organism_id->organism_id,
     '#options' => $organisms,
     '#required'    => TRUE
   );
@@ -523,21 +567,35 @@ function chado_stock_validate($node, &$form) {
   $int_in_chado_sql = "SELECT count(*) as count FROM {%s} WHERE %s=%d";
   $string_in_chado_sql = "SELECT count(*) as count FROM {%s} WHERE %s='%s'";
 
-  // Validate Uniquename only if add
-  if (empty($node->stock_id)) {
-    $chado_row = db_fetch_object(chado_query("SELECT * FROM {stock} WHERE uniquename='" . $node->uniquename . "'"));
-    if (!empty($chado_row->stock_id)) {
-      $drupal_row = db_fetch_object(db_query("SELECT * FROM {chado_stock} WHERE stock_id=" . $chado_row->stock_id));
-      if (!empty($drupal_row->nid)) {
-        $link = l('node/' . $drupal_row->nid, $node->uniquename);
-        form_set_error('uniquename', "There is already a stock with that uniquename $link. Please enter another uniquename.");
-      }
-      else {
-        form_set_error('uniquename', "There is already a stock with that uniquename (although it's not sync'd with drupal). Please enter another uniquename.");
-      }
+  // if this is an update, we want to make sure that a different stock for
+  // the organism doesn't already have this uniquename. We don't want to give
+  // two sequences the same uniquename
+  if ($node->stock_id) {
+    $sql = "SELECT *
+            FROM {stock} S
+              INNER JOIN {cvterm} CVT ON S.type_id = CVT.cvterm_id
+            WHERE uniquename = '%s'
+             AND organism_id = %d AND CVT.name = '%s' AND NOT stock_id = %d";
+    $result = db_fetch_object(chado_query($sql, $node->uniquename, $node->organism_id, $node->stock_type, $node->stock_id));
+    if ($result) {
+      form_set_error('uniquename', t("Stock update cannot proceed. The stock name '$node->uniquename' is not unique for this organism. Please provide a unique name for this stock."));
     }
   }
 
+  // if this is an insert then we just need to make sure this name doesn't
+  // already exist for this organism if it does then we need to throw an error
+  else {
+    $sql = "SELECT *
+            FROM {Stock} S
+              INNER JOIN {cvterm} CVT ON S.type_id = CVT.cvterm_id
+            WHERE uniquename = '%s'
+             AND organism_id = %d AND CVT.name = '%s'";
+    $result = db_fetch_object(chado_query($sql, $node->uniquename, $node->organism_id, $node->stock_type));
+    if ($result) {
+      form_set_error('uniquename', t("Stock insert cannot proceed. The stock name '$node->uniquename' already exists for this organism. Please provide a unique name for this stock."));
+    }
+  }
+  
 
   // Check Type of Stock is valid cvterm_id in chado ( $form['values']['details']['type_id'] )
   if ( $node->type_id == 0) {
@@ -579,9 +637,9 @@ function chado_stock_validate($node, &$form) {
     if ($num_rows->count != 1) {
       form_set_error('database', 'The database you selected is not valid. Please choose another one.'); }
   }
-
 }
 
+
 /**
  * Implements hook_insert(): Inserts data from chado_stock_form() into drupal and chado
  *
@@ -594,8 +652,8 @@ function chado_stock_validate($node, &$form) {
  * @ingroup tripal_stock
  */
 function chado_stock_insert($node) {
-
-  //If the chado stock exists
+  
+  // If the chado stock exists (e.g. this is only a syncing operation)
   // then don't create but simply link to node
   if ($node->chado_stock_exists) {
     if (!empty($node->stock_id)) {
@@ -607,12 +665,15 @@ function chado_stock_insert($node) {
         $node->stock_id
       );
     }
+    
     return $node;
   }
 
-  // create dbxref
-  if ( !empty($node->accession) ) {
-    if ( !empty($node->database) ) {
+  // before we can add the stock, we must add the dbxref if one has been
+  // provided by the user.
+  $dbxref_status = 0;
+  if (!empty($node->accession) ) {
+    if (!empty($node->database) ) {
       $values = array(
         'db_id' => $node->database,
         'accession' => $node->accession,
@@ -630,11 +691,14 @@ function chado_stock_insert($node) {
           );
         }
       }
-      else { $dbxref_status = 1; }
+      else { 
+        $dbxref_status = 1;
+      }
     }
   }
 
-  // create stock
+  // create stock including the dbxref
+  $stock = '';
   if ($dbxref_status) {
     $values = array(
       'dbxref_id' => array(
@@ -642,56 +706,41 @@ function chado_stock_insert($node) {
         'accession' => $node->accession
       ),
       'organism_id' => $node->organism_id,
-      'name' => $node->title,
+      'name' => $node->sname,
       'uniquename' => $node->uniquename,
       'description' => $node->stock_description,
       'type_id' => $node->type_id
     );
-    $stock_status = tripal_core_chado_insert('stock', $values);
+    $stock = tripal_core_chado_insert('stock', $values);
   }
+  // create a stock without a dbxref
   else {
     $values = array(
       'organism_id' => $node->organism_id,
-      'name' => $node->title,
-      'uniquename' => $node->uniquename,
+      'name'        => $node->sname,
+      'uniquename'  => $node->uniquename,
       'description' => $node->stock_description,
-      'type_id' => $node->type_id
+      'type_id'     => $node->type_id
     );
-    $stock_status = tripal_core_chado_insert('stock', $values);
+    $stock = tripal_core_chado_insert('stock', $values);
   }
-
-  // create drupal chado_stock entry
-  if ($stock_status) {
-    $values = array(
-      'organism_id' => $node->organism_id,
-      'uniquename' => $node->uniquename,
-      'type_id' => $node->type_id
-    );
-    $chado_stock = tripal_core_chado_select('stock', array('stock_id'), $values);
-    if (!empty($chado_stock[0]->stock_id)) {
-      db_query(
-        "INSERT INTO {chado_stock} (nid, vid, stock_id) "
-        ."VALUES (%d, %d, %d)",
-        $node->nid,
-        $node->vid,
-        $chado_stock[0]->stock_id
-      );
-
-        //Move on to next stage of Stock Creation based on next_stage_path field
-      if ($node->simulate_multipart) {
-        $next_stage_path = preg_replace('/%node/', $node->nid, $node->next_step_path);
-        $_REQUEST['destination'] = $next_stage_path;
-      }
-    }
-    else {
-      drupal_set_message(t('Error during stock creation.'), 'error');
-      watchdog('tripal_stock',
-        'Insert Stock: Unable to find newly created stock where values:%values',
-        array('%values' => print_r($values, TRUE)),
-        WATCHDOG_ERROR
-      );
-      return FALSE;
-    }
+  
+  // if the stock creation was succesful then add the URL and the entry in the
+  // chado_stock table
+  if (is_array($stock)) {
+    
+    // convert the stock into an object
+    $stock = (object) $stock;
+
+    // add the entry to the chado_stock table
+    $sql = "INSERT INTO {chado_stock} (nid, vid, stock_id) VALUES (%d, %d, %d)";
+    db_query($sql, $node->nid, $node->vid, $stock->stock_id);
+
+    // Move on to next stage of Stock Creation based on next_stage_path field
+    if ($node->simulate_multipart) {
+      $next_stage_path = preg_replace('/%node/', $node->nid, $node->next_step_path);
+      $_REQUEST['destination'] = $next_stage_path;
+    }    
   }
   else {
     drupal_set_message(t('Error during stock creation.'), 'error');
@@ -702,7 +751,6 @@ function chado_stock_insert($node) {
     );
     return FALSE;
   }
-
 }
 
 /**
@@ -724,10 +772,6 @@ function chado_stock_update($node) {
     // there is no way to handle revisions in Chado but leave
     // this here just to make not we've addressed it.
   }
-//  if ($node->revision) {
-//    chado_stock_insert($node);
-//  }
-//  else {
 
   //update dbxref
   if ($node->database) {
@@ -794,7 +838,7 @@ function chado_stock_update($node) {
   //can't change stock id which is all thats stored in drupal thus only update chado
   $update_values = array(
     'organism_id' => $node->organism_id,
-    'name' => $node->title,
+    'name' => $node->sname,
     'uniquename' => $node->uniquename,
     'description' => $node->stock_description,
     'type_id' => $node->type_id,
@@ -805,11 +849,8 @@ function chado_stock_update($node) {
       'accession' => $node->accession
     );
   }
-  $status = tripal_core_chado_update(
-    'stock',
-    array('stock_id' => $node->stock_id),
-    $update_values
-  );
+  $status = tripal_core_chado_update('stock', array('stock_id' => $node->stock_id), $update_values);
+    
 
   if (!$status) {
     drupal_set_message(t('Unable to update stock'), 'error');
@@ -820,6 +861,11 @@ function chado_stock_update($node) {
       WATCHDOG_ERROR
     );
   }
+  else {
+    // set the URL for this stock page
+    $values = array('stock_id' => $node->stock_id);
+    $stock = tripal_core_chado_select('stock', array('*'), $values);    
+  }
 }
 
 /**
@@ -1059,8 +1105,59 @@ function tripal_stock_preprocess_tripal_stock_relationships(&$variables) {
 function tripal_stock_nodeapi(&$node, $op, $teaser, $page) {
 
   switch ($op) {
-    // Note that this function only adds stock view to an organism/feature
-    // node.
+    
+    // set the title to ensure it is always unique
+    case 'presave':
+      switch ($node->type) {
+        case 'chado_stock':
+          
+          $values = array('organism_id' => $node->organism_id);
+          $organism = tripal_core_chado_select('organism', array('genus','species'), $values);
+          $values = array('cvterm_id' => $node->type_id);
+          $cvterm = tripal_core_chado_select('cvterm', array('name'), $values);
+          $node->title = $node->sname . ', ' . $node->uniquename . ' (' . $cvterm[0]->name . ') ' . $organism[0]->genus . ' ' . $organism[0]->species;
+          break;
+      }
+      break;
+      
+    // set the URL path after inserting.  We do it here because we do not 
+    // know the stock_id in the presave  
+    case 'insert':
+      switch ($node->type) {
+        case 'chado_stock':
+          if (!$node->stock_id) {
+            $sql = "SELECT * FROM {chado_stock} WHERE nid = %d";
+            $chado_stock = db_fetch_object(db_query($sql, $node->nid));
+            $node->stock_id = $chado_stock->stock_id;
+          }
+          
+          // remove any previous alias
+          db_query("DELETE FROM {url_alias} WHERE src = '%s'", "node/$node->nid");
+          
+          // set the URL for this stock page
+          $url_alias = tripal_stock_get_stock_url($node);
+          path_set_alias("node/$node->nid", $url_alias);
+          break;
+      }
+      break;
+      
+    // set the URL path after inserting.  We do it here because we do not 
+    // know the stock_id in the presave  
+    case 'update':
+      switch ($node->type) {
+        case 'chado_stock':
+          
+          // remove any previous alias
+          db_query("DELETE FROM {url_alias} WHERE src = '%s'", "node/$node->nid");
+          
+          // set the URL for this stock page
+          $url_alias = tripal_stock_get_stock_url($node);
+          path_set_alias("node/$node->nid", $url_alias);
+          break;
+      }
+      break;
+      
+    // add items to other nodes, build index and search results
     case 'view':
       // add the stock to the organism/feature search indexing
       if ($node->build_mode == NODE_BUILD_SEARCH_INDEX) {
@@ -1083,3 +1180,72 @@ function tripal_stock_nodeapi(&$node, $op, $teaser, $page) {
   }
 }
 
+/*
+ * Uses the value provided in the $id argument to find all stocks that match
+ * that ID by name, stockname or synonym.  If it matches uniquenly to a single
+ * stock it will redirect to that stock page, otherwise, a list of matching
+ * stocks is shown.
+ */
+function tripal_stock_match_stocks_page($id) {
+  
+  // if the URL alias configuration is set such that the URL
+  // always begins with 'stock' then we want to use the ID as it is and
+  // forward it on. Otherwise, try to find the matching stock.
+  $url_alias = variable_get('chado_stock_url_string', '/stock/[genus]/[species]/[type]/[uniquename]'); 
+  if (!$url_alias) {
+    $url_alias = '/stock/[genus]/[species]/[type]/[uniquename]';
+  } 
+  $url_alias = preg_replace('/^\//', '', $url_alias); // remove any preceeding forward slash  
+  if (preg_match('/^stock\//', $url_alias)) {
+    drupal_goto($id);
+  }
+  
+
+  $sql = "
+    SELECT
+      S.name, S.uniquename, S.stock_id,
+      O.genus, O.species, O.organism_id,
+      CVT.cvterm_id, CVT.name as type_name,
+      CS.nid
+    FROM {stock} S
+      INNER JOIN {organism} O on S.organism_id = O.organism_id
+      INNER JOIN {cvterm} CVT on CVT.cvterm_id = S.type_id
+      INNER JOIN public.chado_stock CS on CS.stock_id = S.stock_id
+    WHERE
+      S.uniquename = '%s' or S.name = '%s'
+  ";
+  $results = chado_query($sql, $id, $id);
+
+  $num_matches = 0;
+
+  // iterate through the matches and build the table for showing matches
+  $header = array('Uniquename', 'Name', 'Type', 'Species');
+  $rows = array();
+  $curr_match;
+  while ($match = db_fetch_object($results)) {
+    $curr_match = $match;
+    $rows[] = array(
+       $match->uniquename,
+       "<a href=\"" . url("node/". $match->nid) ."\">" . $match->name . "</a>",
+       $match->type_name,
+       '<i>' . $match->genus . ' ' . $match->species . '</i>',
+    );
+    $num_matches++;
+  }
+
+  // if we have more than one match then generate the table, otherwise, redirect
+  // to the matched stock
+  if ($num_matches == 1) {
+    drupal_goto("node/" . $curr_match->nid);
+  }
+  if ($num_matches == 0) {
+    return "<p>No stocks matched the given name '$id'</p>";
+  }
+
+  $table_attrs = array(
+    'class' => 'tripal-table tripal-table-horz'
+  );
+  $output = "<p>The following stocks match the name '$id'.</p>";
+  $output .= theme_table($header, $rows, $table_attrs, $caption);
+  return $output;
+}