Kaynağa Gözat

Added ability to set URLs for stocks. Added ability to configure format of stock and feature page titles. Changed the stock syncing to match that of features. Removed the ability to set the title for stocks and features. Titles get set automatically now.

spficklin 11 yıl önce
ebeveyn
işleme
ff37627b86

+ 102 - 45
tripal_feature/includes/tripal_feature.admin.inc

@@ -21,13 +21,14 @@ function tripal_feature_admin() {
   }
   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 +89,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.";
@@ -126,7 +127,9 @@ function tripal_feature_admin() {
   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 ".
@@ -150,47 +153,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 +203,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 +235,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 +264,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,36 +301,80 @@ function get_tripal_feature_admin_form_taxonomy_set(&$form) {
 
 }
 
+/**
+ * 
+ * @param $form
+ */
+function get_tripal_feature_admin_form_title_set(&$form) {
+
+  $form['title'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Feature Page Titles'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['title']['desc'] = array(
+    '#type'        => 'markup',
+    '#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(
+    'feature_unique_name'  => 'Feature unique name',
+    'feature_name'         => 'Feature name',
+    'unique_constraint'    => 'Feature Name, uniquename, type and species',
+  );
+  $form['title']['chado_feature_title'] = array(
+    '#title'         => t('Feature Page Titles'),
+    '#type'          => 'radios',
+    '#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_title', 'unique_constraint'),
+  );
+}
+/**
+ * 
+ * @param $form
+ */
 function get_tripal_feature_admin_form_url_set(&$form) {
 
   $form['url'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Feature URL Path')
+    '#title' => t('Feature URL Path'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
   $form['url']['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 will have a unique URL which consists of 
+      the site domain followed by a unique identifer: for example 
+      http://my-tripal-site.org/FID1034, where the element just after the final 
+      slash is the unique identifier for the feature.'),
   );
   $options = array(
-    'internal ID'          => 'Internal ID (Chado feature_id)',
-    'feature unique name'  => 'Feature unique name',
-    'feature name'         => 'Feature name',
+    'internal ID'          => 'Internal ID (Uses the Chado feature_id. Please set the ID Prefix below.)',
+    '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]',
+    'genus_species_type_uname'  => 'Genus + species + type + unique name (e.g. http://your.site.url/[genus]/[genus]_[species]/[type]/[unique name]',
   );
   
   $form['url']['chado_feature_url'] = array(
     '#title'         => t('Unique Identifier'),
     '#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 an identifier type from the list above that is 
+      guaranteed to be unique for all features you want to sync. If in doubt it 
+      is safest to coose the internal ID or the final option with the genus, species, 
+      type and unique name. Click the \'Save Configuration\' button at the bottom to save 
+      your selection Click the \'Set Feature URLs\' button to submit a job to reset 
+      the URLs for all synced features.'),
     '#required'      => FALSE,
     '#options'       => $options,
     '#default_value' => variable_get('chado_feature_url', 'internal ID'),
@@ -341,13 +387,24 @@ function get_tripal_feature_admin_form_url_set(&$form) {
                         "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'),
+    '#default_value' => variable_get('chado_feature_accession_prefix', 'FID'),
+  );
+  
+  $form['url']['note'] = array(
+    '#type'        => 'markup',
+    '#value' => t('<b>Note:</b> It is important to set a unique URL for each feature, and that
+       URL must be unique. However, it may be difficult for 3rd party programs (e.g. GBrowse) to
+       link to features when the intenal ID or the URL contains genus, species and or type information.
+       Therefore, Tripal provides a way to link to any feature simply using the name, unique name or
+       synonym.  If you use the URL http://your.site.url/feature/[feature], where [feature] is a name,
+       unique name, or synonym, then Tripal will automatically redirect to the feature page that matches.
+       If there are more than one match, a list of available features will be provided from which the 
+       user can select.<br>'),
   );
 
   $form['url']['button'] = array(
     '#type' => 'submit',
     '#value' => t('Set Feature URLs'),
-    '#weight' => 3,
   );
 }
 
@@ -520,7 +577,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 +606,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>";

+ 50 - 42
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,
   );
 
@@ -126,48 +125,64 @@ function tripal_feature_set_urls($job_id = NULL) {
   $sql = "SELECT * FROM {chado_feature}";
   $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);
-    }
+
+    print "Setting URL alias for feature $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
+    $url_alias = tripal_feature_get_feature_url($node);
+    path_set_alias("node/$node->nid", $url_alias);
   }
 }
 /**
  *
  */
-function tripal_feature_set_feature_url($node, $feature) {
+function tripal_feature_get_feature_url($node) {
 
   // 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));    
+  $aprefix = variable_get('chado_feature_accession_prefix', 'SID');
+
+  // get the feature 
+  $values = array('feature_id' => $node->feature_id);        
+  $feature = tripal_core_chado_select('feature', array('*'), $values);
+
+  $feature = (object) $feature[0];
+  
+  // get the organism
+  $values = array('organism_id' => $feature->organism_id);
+  $organism  = tripal_core_chado_select('organism', array('*'), $values);  
+  $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);
+  $cvterm = tripal_core_chado_select('cvterm', array('name'), $values);
+  $type = preg_replace('/\s/', '_', $cvterm[0]->name);
+  
   switch ($alias_type) {
-    case 'feature name':
+    case 'feature_name':
       $url_alias = $feature->name;
       break;
-    case 'feature unique name':
+    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;
+    case 'genus_species_name':
+      $url_alias = $genus . "/" . $genus . "_" . $species . "/" . $feature->sname;
+      break;
+    case 'genus_species_type_uname':
+      $url_alias = $genus . "/" . $genus . "_" . $species . "/" . $type . "/" . $feature->uniquename;
       break;
     default:
-      $url_alias = "$aprefix$feature->feature_id";
+      $url_alias = "$aprefix$node->feature_id";
   }
-  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);
+ 
+  return $url_alias;
 }
 /**
  *
@@ -176,7 +191,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 +283,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 +305,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 +428,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 '';
 }

+ 82 - 11
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";
@@ -503,7 +503,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);
 
@@ -564,7 +564,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);
   }
@@ -576,6 +576,8 @@ function chado_feature_update($node) {
     WATCHDOG_WARNING
     );
   }
+  
+
 }
 /**
  *
@@ -801,6 +803,7 @@ function chado_feature_form($node, $param) {
     '#value' => $feature->feature_id,
   );
 
+  /*
   $form['title']= array(
     '#type' => 'textfield',
     '#title' => t('Title'),
@@ -808,7 +811,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',
@@ -946,7 +949,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
@@ -962,8 +964,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();
@@ -1820,10 +1840,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':
@@ -2117,7 +2188,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";

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

@@ -506,7 +506,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

@@ -392,7 +392,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++;
   }
 }

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

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

@@ -0,0 +1,275 @@
+<?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.
+  $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'),
+       '#collapsible' => FALSE,
+       '#collapsed' => FALSE,
+    );
+    $form['notice']['message'] = array(
+       '#value' => t("Currently, 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."),
+    );
+    return system_settings_form($form);
+  }
+  
+  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();
+  
+  switch ($form_state['values']['op']) {
+    case  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']);
+      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') :
+      variable_set('chado_stock_url', $form_state['values']['stock_url']);
+      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,
+  );
+  $form['url']['desc'] = array(
+    '#type'        => 'markup',
+    '#value' => t('Each synced stock will have a unique URL which consists of 
+      the site domain followed by a unique identifer: for example 
+      http://my-tripal-site.org/SID1034, where the element just after the final 
+      slash is the unique identifier for the stock.'),
+  );
+  $options = array(
+    'internal ID'               => 'Internal ID (Uses the Chado stock_id. Please set the ID Prefix below)',
+    'stock_unique_name'         => 'Stock unique name',
+    'stock_name'                => 'Stock 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]',
+    'genus_species_type_uname'  => 'Genus + species + type + unique name (e.g. http://your.site.url/[genus]/[genus]_[species]/[type]/[unique name]',
+  );
+  
+  $form['url']['chado_stock_url'] = array(
+    '#title'         => t('Unique Identifier'),
+    '#type'          => 'radios',
+    '#description'   => t('Choose an identifier type from the list above that is 
+      guaranteed to be unique for all stocks. If in doubt it is safest to choose the 
+      internal ID. Click the \'Save Configuration\' button at the bottom to save 
+      your selection Click the \'Set Stock URLs\' button to submit a job to reset 
+      the URLs for all synced stocks.'),
+    '#required'      => FALSE,
+    '#options'       => $options,
+    '#default_value' => variable_get('chado_stock_url', 'internal ID'),
+  );
+
+  $form['url']['chado_stock_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 stock name or unique name then 
+      this prfix is not used"),
+    '#required'    => TRUE,
+    '#default_value' => variable_get('chado_stock_accession_prefix', 'SID'),
+  );
+
+  $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);
+
+}

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

@@ -0,0 +1,430 @@
+<?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);
+}
+/**
+ *
+ */
+function tripal_stock_set_urls($job_id = NULL) {
+  
+  // first get the list of stocks that have been synced
+  $sql = "SELECT * FROM {chado_stock}";
+  $nodes = db_query($sql);
+  while ($node = db_fetch_object($nodes)) {
+
+    print "Setting URL alias for stock $stock->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
+    $url_alias = tripal_stock_get_stock_url($node);
+    path_set_alias("node/$node->nid", $url_alias);
+  }
+}
+/**
+ *
+ */
+function tripal_stock_get_stock_url($node) {
+
+  // determine which URL alias to use
+  $alias_type = variable_get('chado_stock_url', 'internal ID');
+  $aprefix = variable_get('chado_stock_accession_prefix', 'SID');
+
+  // get the stock 
+  $values = array('stock_id' => $node->stock_id);        
+  $stock = tripal_core_chado_select('stock', array('*'), $values);
+
+  $stock = (object) $stock[0];
+  
+  // get the organism
+  $values = array('organism_id' => $stock->organism_id);
+  $organism  = tripal_core_chado_select('organism', array('*'), $values);  
+  $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);
+  $cvterm = tripal_core_chado_select('cvterm', array('name'), $values);
+  $type = preg_replace('/\s/', '_', $cvterm[0]->name);
+  
+  switch ($alias_type) {
+    case 'stock_name':
+      $url_alias = $stock->name;
+      break;
+    case 'stock_unique_name':
+      $url_alias = $stock->uniquename;
+      break;
+    case 'genus_species_uqname':
+      $url_alias = $genus . "/" . $genus . "_" . $species . "/" . $stock->uniquename;
+      break;
+    case 'genus_species_name':
+      $url_alias = $genus . "/" . $genus . "_" . $species . "/" . $stock->sname;
+      break;
+    case 'genus_species_type_uname':
+      $url_alias = $genus . "/" . $genus . "_" . $species . "/" . $type . "/" . $stock->uniquename;
+      break;
+    default:
+      $url_alias = "$aprefix$node->stock_id";
+  }
+ 
+  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), 'error');
+      }
+      exit;
+    }
+    else {
+      $node = node_submit($new_node);
+      node_save($node);
+    }
+  }
+  else {
+    $node = $chado_stock;
+  }
+
+  return '';
+}

+ 140 - 65
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(
@@ -345,6 +355,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 +446,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 +473,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 +498,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
   );
@@ -579,9 +615,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 +630,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 +643,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 +669,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 +684,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 +729,6 @@ function chado_stock_insert($node) {
     );
     return FALSE;
   }
-
 }
 
 /**
@@ -724,10 +750,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 +816,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 +827,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 +839,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 +1083,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) {