Explorar o código

Changed the way URLs for features and stocks are created. Admins can now buil the URL string using place holders

spficklin %!s(int64=11) %!d(string=hai) anos
pai
achega
921557cca7

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

@@ -346,64 +346,61 @@ function get_tripal_feature_admin_form_title_set(&$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']['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/FID1034, where the element just after the final 
-      slash is the unique identifier for the feature.'),
-  );
+
   $options = array(
-    '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]',
+    '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: \'feature\', [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('Unique Identifier'),
-    '#type'          => 'radios',
-    '#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.'),
+    '#title'         => t('URL components'),
+    '#type'          => 'checkboxes',
     '#required'      => FALSE,
     '#options'       => $options,
-    '#default_value' => variable_get('chado_feature_url', 'internal ID'),
-  );
-
-  $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', 'FID'),
-  );
+    '#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']['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'),

+ 13 - 22
tripal_feature/includes/tripal_feature.sync_features.inc

@@ -140,9 +140,12 @@ function tripal_feature_set_urls($job_id = NULL) {
  */
 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', 'SID');
+  // get the starting 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
 
   // get the feature 
   $values = array('feature_id' => $node->feature_id);        
@@ -161,25 +164,13 @@ function tripal_feature_get_feature_url($node) {
   $cvterm = tripal_core_chado_select('cvterm', array('name'), $values);
   $type = preg_replace('/\s/', '_', $cvterm[0]->name);
   
-  switch ($alias_type) {
-    case 'feature_name':
-      $url_alias = "feature/" . $feature->name;
-      break;
-    case 'feature_unique_name':
-      $url_alias = "feature/" . $feature->uniquename;
-      break;
-    case 'genus_species_uqname':
-      $url_alias = "feature/" . $genus . "_" . $species . "/" . $feature->uniquename;
-      break;
-    case 'genus_species_name':
-      $url_alias = "feature/" . $genus . "_" . $species . "/" . $feature->name;
-      break;
-    case 'genus_species_type_uname':
-      $url_alias = "feature/" . $genus . "_" . $species . "/" . $type . "/" . $feature->uniquename;
-      break;
-    default:
-      $url_alias = "$aprefix$node->feature_id";
-  }
+  // 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);
  
   return $url_alias;
 }

+ 14 - 4
tripal_feature/tripal_feature.module

@@ -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;
@@ -2304,6 +2302,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
@@ -2350,7 +2360,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>";

+ 48 - 62
tripal_stock/includes/tripal_stock.admin.inc

@@ -15,30 +15,6 @@
 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);       
@@ -62,12 +38,14 @@ function tripal_stock_admin() {
 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') :      
-      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') :
@@ -76,11 +54,11 @@ function tripal_stock_admin_validate($form, &$form_state) {
       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;
   }
+
 }
 
 /**
@@ -132,46 +110,54 @@ function get_tripal_stock_admin_form_url_set(&$form) {
     '#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]',
+    '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: \'stock\', [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('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.'),
+    '#title'         => t('URL components'),
+    '#type'          => 'checkboxes',
     '#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'),
-  );
-
+    '#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'),

+ 13 - 22
tripal_stock/includes/tripal_stock.sync_stocks.inc

@@ -138,9 +138,12 @@ function tripal_stock_set_urls($job_id = NULL) {
  */
 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 starting 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);        
@@ -159,25 +162,13 @@ function tripal_stock_get_stock_url($node) {
   $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/" . $stock->name;
-      break;
-    case 'stock_unique_name':
-      $url_alias = "stock/" . $stock->uniquename;
-      break;
-    case 'genus_species_uqname':
-      $url_alias = "stock/" . $genus . "_" . $species . "/" . $stock->uniquename;
-      break;
-    case 'genus_species_name':
-      $url_alias = "stock/" . $genus . "_" . $species . "/" . $stock->name;
-      break;
-    case 'genus_species_type_uname':
-      $url_alias = "stock/" . $genus . "_" . $species . "/" . $type . "/" . $stock->uniquename;
-      break;
-    default:
-      $url_alias = "$aprefix$node->stock_id";
-  }
+  // 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);
  
   return $url_alias;
 }

+ 103 - 12
tripal_stock/tripal_stock.module

@@ -126,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;
 }
 
@@ -559,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) {
@@ -1158,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;
+}