Browse Source

Merge branch '7.x-3.x' into 7.x-3.x-dashboard

Shawna 7 years ago
parent
commit
15ec504de9
76 changed files with 2428 additions and 5413 deletions
  1. 8 6
      legacy/tripal_contact/includes/tripal_contact.chado_node.inc
  2. 2 2
      legacy/tripal_core/api/tripal_core.chado_nodes.api.inc
  3. 3 2
      legacy/tripal_core/includes/tripal_core.toc.inc
  4. 506 0
      legacy/tripal_feature/includes/tripal_feature.seq_extract.inc
  5. 28 28
      legacy/tripal_feature/theme/templates/tripal_feature_sequence.tpl.php
  6. 10 3
      legacy/tripal_feature/tripal_feature.drush.inc
  7. 10 12
      legacy/tripal_feature/tripal_feature.module
  8. 16 16
      legacy/tripal_featuremap/theme/templates/tripal_featuremap_publication.tpl.php
  9. 6 3
      legacy/tripal_library/includes/tripal_library.chado_node.inc
  10. 4 2
      legacy/tripal_pub/includes/tripal_pub.chado_node.inc
  11. 3 3
      legacy/tripal_pub/includes/tripal_pub.pub_search.inc
  12. 1 1
      legacy/tripal_pub/theme/templates/tripal_pub_features.tpl.php
  13. 1 1
      legacy/tripal_stock/tripal_stock.module
  14. 3 2
      tripal/api/tripal.entities.api.inc
  15. 6 5
      tripal/api/tripal.notice.api.inc
  16. 5 0
      tripal/api/tripal.terms.api.inc
  17. 3 4
      tripal/includes/TripalBundleUIController.inc
  18. 8 4
      tripal/includes/TripalFieldQuery.inc
  19. 362 56
      tripal/includes/TripalFields/TripalField.inc
  20. 6 8
      tripal/includes/tripal.fields.inc
  21. 1 0
      tripal/tripal.info
  22. 51 20
      tripal/tripal.views.inc
  23. 110 30
      tripal/tripal_views_query.inc
  24. 12 0
      tripal/views_handlers/tripal_views_handler_field.inc
  25. 57 5
      tripal/views_handlers/tripal_views_handler_field_element.inc
  26. 21 0
      tripal/views_handlers/tripal_views_handler_sort.inc
  27. 0 10
      tripal/views_handlers/tripal_views_handler_sort_entity_string.inc
  28. 1 1
      tripal_bulk_loader/includes/tripal_bulk_loader.admin.templates.inc
  29. 1 1
      tripal_chado/api/modules/tripal_chado.organism.api.inc
  30. 1 1
      tripal_chado/api/modules/tripal_chado.pub.api.inc
  31. 2 2
      tripal_chado/api/tripal_chado.api.inc
  32. 33 7
      tripal_chado/api/tripal_chado.query.api.inc
  33. 12 8
      tripal_chado/api/tripal_chado.schema.api.inc
  34. 35 23
      tripal_chado/includes/TripalFields/ChadoField.inc
  35. 64 24
      tripal_chado/includes/TripalFields/chado_linker__contact/chado_linker__contact.inc
  36. 1 0
      tripal_chado/includes/TripalFields/chado_linker__cvterm/chado_linker__cvterm.inc
  37. 1 1
      tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop.inc
  38. 17 29
      tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop_formatter.inc
  39. 3 23
      tripal_chado/includes/TripalFields/data__accession/data__accession.inc
  40. 13 0
      tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence.inc
  41. 15 0
      tripal_chado/includes/TripalFields/data__sequence/data__sequence.inc
  42. 13 0
      tripal_chado/includes/TripalFields/data__sequence_checksum/data__sequence_checksum.inc
  43. 60 0
      tripal_chado/includes/TripalFields/data__sequence_coordinates/data__sequence_coordinates.inc
  44. 15 0
      tripal_chado/includes/TripalFields/data__sequence_length/data__sequence_length.inc
  45. 14 0
      tripal_chado/includes/TripalFields/go__gene_expression/go__gene_expression.inc
  46. 52 6
      tripal_chado/includes/TripalFields/local__source_data/local__source_data.inc
  47. 86 122
      tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc
  48. 61 4
      tripal_chado/includes/TripalFields/ogi__location_on_map/ogi__location_on_map.inc
  49. 47 5
      tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference.inc
  50. 14 1
      tripal_chado/includes/TripalFields/sbo__phenotype/sbo__phenotype.inc
  51. 114 23
      tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship.inc
  52. 1 1
      tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship_formatter.inc
  53. 0 1
      tripal_chado/includes/TripalFields/schema__additional_type/schema__additional_type.inc
  54. 2 2
      tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name.inc
  55. 19 7
      tripal_chado/includes/TripalFields/schema__publication/schema__publication.inc
  56. 44 0
      tripal_chado/includes/TripalFields/sio__references/sio__references.inc
  57. 2 2
      tripal_chado/includes/TripalFields/sio__vocabulary/sio__vocabulary.inc
  58. 14 0
      tripal_chado/includes/TripalFields/so__cds/so__cds.inc
  59. 43 4
      tripal_chado/includes/TripalFields/so__genotype/so__genotype.inc
  60. 51 1
      tripal_chado/includes/TripalFields/so__transcript/so__transcript.inc
  61. 14 0
      tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon.inc
  62. 0 1004
      tripal_chado/includes/loaders/tripal_chado.fasta_loader.inc
  63. 0 2319
      tripal_chado/includes/loaders/tripal_chado.gff_loader.inc
  64. 0 1456
      tripal_chado/includes/loaders/tripal_chado.obo_loader.inc
  65. 14 13
      tripal_chado/includes/loaders/tripal_chado.pub_importers.inc
  66. 6 0
      tripal_chado/includes/setup/tripal_chado.setup.inc
  67. 12 4
      tripal_chado/includes/tripal_chado.entity.inc
  68. 3 3
      tripal_chado/includes/tripal_chado.field_storage.inc
  69. 124 72
      tripal_chado/includes/tripal_chado.fields.inc
  70. 7 7
      tripal_chado/includes/tripal_chado.migrate.inc
  71. 4 0
      tripal_chado/includes/tripal_chado.semweb.inc
  72. 4 0
      tripal_chado/theme/css/tripal_chado.css
  73. 43 0
      tripal_chado/tripal_chado.install
  74. 14 1
      tripal_daemon/TripalDaemon.inc
  75. 55 2
      tripal_daemon/tripal_daemon.drush.inc
  76. 34 10
      tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc

+ 8 - 6
legacy/tripal_contact/includes/tripal_contact.chado_node.inc

@@ -611,12 +611,14 @@ function tripal_contact_node_view($node, $view_mode, $langcode) {
           '#tripal_toc_id'    => 'properties',
           '#tripal_toc_title' => 'Properties',
         );
-        $node->content['tripal_contact_publications'] = array(
-          '#theme' => 'tripal_contact_publications',
-          '#node' => $node,
-          '#tripal_toc_id'    => 'publications',
-          '#tripal_toc_title' => 'Publications',
-        );
+        if (module_exists('tripal_pub')) {
+          $node->content['tripal_contact_publications'] = array(
+            '#theme' => 'tripal_contact_publications',
+            '#node' => $node,
+            '#tripal_toc_id'    => 'publications',
+            '#tripal_toc_title' => 'Publications',
+          );
+        }
         $node->content['tripal_contact_relationships'] = array(
           '#theme' => 'tripal_contact_relationships',
           '#node' => $node,

+ 2 - 2
legacy/tripal_core/api/tripal_core.chado_nodes.api.inc

@@ -552,7 +552,7 @@ function chado_node_sync_form($form, &$form_state) {
       // yet been synced.
       $query = "SELECT " . implode(', ', $select) . ' ' .
                'FROM {' . $base_table . '} ' . $base_table . ' ' . implode(' ', $joins) . ' '.
-               "  LEFT JOIN public.$linking_table CT ON CT.$base_table_id = $base_table.$base_table_id " .
+               "  LEFT JOIN {" . $linking_table . "} CT ON CT.$base_table_id = $base_table.$base_table_id " .
                "WHERE CT.$base_table_id IS NULL";
 
       // extend the where clause if needed
@@ -862,7 +862,7 @@ function chado_node_sync_records($base_table, $max_sync = FALSE,
   $query = "
     SELECT " . implode(', ', $select) . ' ' .
     'FROM {' . $base_table . '} ' . $base_table . ' ' . implode(' ', $joins) . ' '.
-    "  LEFT JOIN public.$linking_table CT ON CT.$base_table_id = $base_table.$base_table_id " .
+    "  LEFT JOIN {" . $linking_table . "} CT ON CT.$base_table_id = $base_table.$base_table_id " .
     "WHERE CT.$base_table_id IS NULL ";
 
   // extend the where clause if needed

+ 3 - 2
legacy/tripal_core/includes/tripal_core.toc.inc

@@ -28,7 +28,7 @@ function tripal_core_node_toc_form($form, &$form_state, $node) {
       the following order in the Table of Contents (TOC). You may rename
       the titles or drag and drop them to change the order.  <b>Any changes will
       only apply to this page</b>. If you would like to make changes apply to multiple
-      pages of the same tpye, please visit the $admin_link. ") . '</p>' .
+      pages of the same type, please visit the $admin_link. ") . '</p>' .
       '<p>' . t('The list below shows all possible content panes that can appear.
       However, those without content are automatically hidden and do not
       appear in the TOC.' . '</p>'),
@@ -653,7 +653,8 @@ function tripal_core_content_type_toc_form($form, &$form_state, $content_type) {
 
   $form["#tree"] = TRUE;
 
-  // Get a single node of this type so we can get all the possible content for it
+  // Get a single node of this type so we can get all the possible content
+  // for it.
   $sql = "SELECT nid FROM {node} WHERE type = :type LIMIT 1 OFFSET 0";
   $nid = db_query($sql, array(':type' => $content_type))->fetchField();
   if (!$nid) {

+ 506 - 0
legacy/tripal_feature/includes/tripal_feature.seq_extract.inc

@@ -0,0 +1,506 @@
+<?php
+/**
+ * @file
+ * Interface for downloading feature sequences
+ */
+
+/**
+ * The page allowing users to download feature sequences
+ *
+ * @ingroup tripal_feature
+ */
+function tripal_feature_seq_extract_download() {
+
+  if (!array_key_exists('tripal_feature_seq_extract', $_SESSION)) {
+    drupal_goto('find/sequences');
+  }
+
+  $genus      = $_SESSION['tripal_feature_seq_extract']['genus'];
+  $species    = $_SESSION['tripal_feature_seq_extract']['species'];
+  $analysis   = $_SESSION['tripal_feature_seq_extract']['analysis'];
+  $ftype      = $_SESSION['tripal_feature_seq_extract']['ftype'];
+  $fnames     = $_SESSION['tripal_feature_seq_extract']['fnames'];
+  $upstream   = $_SESSION['tripal_feature_seq_extract']['upstream'];
+  $downstream = $_SESSION['tripal_feature_seq_extract']['downstream'];
+  $format     = $_SESSION['tripal_feature_seq_extract']['format'];
+  $use_parent = $_SESSION['tripal_feature_seq_extract']['use_parent'];
+  $aggregate  = $_SESSION['tripal_feature_seq_extract']['aggregate'];
+  $agg_types  = $_SESSION['tripal_feature_seq_extract']['agg_types'];
+
+  // Split the sub features and remove any surrounding white space
+  $agg_types = preg_split("/[\n|,]/", $agg_types);
+  for ($i = 0; $i < count($agg_types); $i++) {
+    $agg_types[$i] = trim($agg_types[$i]);
+  }
+
+
+
+  header('Content-Type: text; utf-8');
+  if ($ftype == 'polypeptide') {
+    header('Content-Disposition: attachment; filename="sequences.fna"');
+  }
+  else {
+    header('Content-Disposition: attachment; filename="sequences.fnn"');
+  }
+
+  $seqs = tripal_get_bulk_feature_sequences(array(
+    'genus' => $genus,
+    'species' => $species,
+    'analysis_name' => $analysis,
+    'type' => $ftype,
+    'feature_name' => $fnames['items_array'],
+    'upstream' => $upstream,
+    'downstream' => $downstream,
+    'output_format' => $format,
+    'derive_from_parent' => $use_parent,
+    'aggregate' => $aggregate,
+    'sub_feature_types' => $agg_types,
+    'width' => 60
+  ));
+
+  if (count($seqs) == 0) {
+    print ">No sequences found that match the criteria.";
+  }
+
+  foreach ($seqs as $seq) {
+    print ">" . $seq['defline'] . "\r\n";
+    print $seq['residues'] . "\r\n";
+  }
+}
+
+
+
+/**
+ * Form to choose which features to extract sequence for
+ *
+ * @ingroup tripal_feature
+ */
+function tripal_feature_seq_extract_form($form, &$form_state) {
+
+  $form['#true'] = TRUE;
+
+  // Intialize the defaults
+  $dgenus      = '';
+  $dspecies    = '';
+  $danalysis   = '';
+  $dftype      = '';
+  $dfnames     = '';
+  $dupstream   = '';
+  $ddownstream = '';
+  $duse_parent = '';
+  $daggregate  = '';
+  $dagg_types  = '';
+
+  if (array_key_exists('tripal_feature_seq_extract', $_SESSION)) {
+    $dgenus      = $_SESSION['tripal_feature_seq_extract']['genus'];
+    $dspecies    = $_SESSION['tripal_feature_seq_extract']['species'];
+    $danalysis   = $_SESSION['tripal_feature_seq_extract']['analysis'];
+    $dftype      = $_SESSION['tripal_feature_seq_extract']['ftype'];
+    $dfnames     = $_SESSION['tripal_feature_seq_extract']['fnames'];
+    $dupstream   = $_SESSION['tripal_feature_seq_extract']['upstream'];
+    $ddownstream = $_SESSION['tripal_feature_seq_extract']['downstream'];
+    $duse_parent = $_SESSION['tripal_feature_seq_extract']['use_parent'];
+    $daggregate  = $_SESSION['tripal_feature_seq_extract']['aggregate'];
+    $dagg_types  = $_SESSION['tripal_feature_seq_extract']['agg_types'];
+  }
+
+  // we want to allow the query string to provide values for the form
+  if (array_key_exists('fnames', $_GET) and $_GET['fnames']) {
+    $dfnames = $_GET['fnames'];
+  }
+  if (array_key_exists('genus', $_GET) and $_GET['genus']) {
+    $dgenus = $_GET['genus'];
+  }
+  if (array_key_exists('species', $_GET) and $_GET['species']) {
+    $dspecies = $_GET['species'];
+  }
+  if (array_key_exists('ftype', $_GET) and $_GET['ftype']) {
+    $dftype = $_GET['ftype'];
+  }
+  if (array_key_exists('analysis', $_GET) and $_GET['analysis']) {
+    $danalysis = $_GET['analysis'];
+  }
+  if (array_key_exists('upstream', $_GET) and $_GET['upstream']) {
+    $dupstream = $_GET['upstream'];
+  }
+  if (array_key_exists('downstream', $_GET) and $_GET['downstream']) {
+    $ddownstream = $_GET['downstream'];
+  }
+  if (array_key_exists('use_parent', $_GET) and $_GET['use_parent']) {
+    $duse_parent = $_GET['use_parent'];
+  }
+  if (array_key_exists('aggregate', $_GET) and $_GET['aggregate']) {
+    $daggregate = $_GET['aggregate'];
+  }
+  if (array_key_exists('agg_types', $_GET) and $_GET['agg_types']) {
+    $dagg_types = $_GET['agg_types'];
+  }
+
+  // get defaults from the form state
+  if (array_key_exists('values', $form_state)) {
+    $dgenus      = $form_state['values']['genus'];
+    $dspecies    = $form_state['values']['species'];
+    $danalysis   = $form_state['values']['analysis'];
+    $dftype      = $form_state['values']['ftype'];
+    $dfnames     = $form_state['values']['fnames'];
+    $dupstream   = $form_state['values']['upstream'];
+    $ddownstream = $form_state['values']['downstream'];
+    $dformat     = $form_state['values']['format'];
+    $duse_parent = $form_state['values']['use_parent'];
+    $daggregate  = $form_state['values']['aggregate'];
+    $dagg_types  = $form_state['values']['agg_types'];
+  }
+
+  // Because we're using Tripal's file_upload_combo form element we
+  // need to allow the form to upload files
+  $form['#attributes']['enctype'] = 'multipart/form-data';
+  $form['#method'] = 'POST';
+
+  $form['description'] = array(
+    '#markup' => t('Use this form to retrieve sequences in FASTA format.')
+  );
+
+  $sql = "
+    SELECT DISTINCT genus
+    FROM {organism}
+    ORDER BY genus
+  ";
+  $results = chado_query($sql);
+  $genus = array();
+  $genus[] = '';
+  while ($organism = $results->fetchObject()) {
+    $genus[$organism->genus] = $organism->genus;
+  }
+
+  $form['genus'] = array(
+    '#title'         => t('Genus'),
+    '#type'          => 'select',
+    '#options'       => $genus,
+    '#default_value' => $dgenus,
+    '#multiple'      => FALSE,
+    '#description'   => t('The organism\'s genus. If specified, features for all organism with this genus will be retrieved.'),
+    '#ajax' => array(
+      'callback'    => 'tripal_feature_seq_extract_form_ajax_callback',
+      'wrapper' => 'tripal-feature-seq-extract-form',
+      'event'   => 'change',
+      'method'  => 'replace',
+    ),
+  );
+
+  $species = array();
+  $species[] = '';
+  if ($dgenus) {
+    $sql = "
+      SELECT DISTINCT species
+      FROM {organism}
+      WHERE genus = :genus
+      ORDER BY species
+    ";
+    $results = chado_query($sql, array(':genus' => $dgenus));
+    while ($organism = $results->fetchObject()) {
+      $species[$organism->species] = $organism->species;
+    }
+  }
+  $form['species'] = array(
+    '#title'         => t('Species'),
+    '#type'          => 'select',
+    '#options'       => $species,
+    '#default_value' => $dspecies,
+    '#multiple'      => FALSE,
+    '#description'   => t('The organism\'s species name. If specified, features for all organisms with this species will be retrieved.  Please first select a genus'),
+    '#ajax' => array(
+      'callback'    => 'tripal_feature_seq_extract_form_ajax_callback',
+      'wrapper' => 'tripal-feature-seq-extract-form',
+      'event'   => 'change',
+      'method'  => 'replace',
+    ),
+  );
+
+  $analyses = array();
+  $analyses[] = '';
+  if ($dgenus) {
+    $sql = "
+      SELECT DISTINCT A.analysis_id, A.name
+      FROM {analysis_organism} AO
+        INNER JOIN {analysis} A ON A.analysis_id = AO.analysis_id
+        INNER JOIN {organism} O ON O.organism_id = AO.organism_id
+      WHERE O.genus = :genus
+    ";
+    $args = array();
+    $args[':genus'] = $dgenus;
+    if ($dspecies) {
+      $sql .= " AND O.species = :species ";
+      $args[':species'] = $dspecies;
+    }
+    $sql .=" ORDER BY A.name ";
+    $results = chado_query($sql, $args);
+    while ($analysis = $results->fetchObject()) {
+      $analyses[$analysis->name] = $analysis->name;
+    }
+  }
+  $form['analysis'] = array(
+    '#title'         => t('Analyses'),
+    '#type'          => 'select',
+    '#options'       => $analyses,
+    '#default_value' => $danalysis,
+    '#multiple'      => FALSE,
+    '#description'  => t('You can limit sequences by the analyses to which it was derived or was used. If specified, only features associated with the specific analysis will be retrieved.'),
+  );
+
+  $ftype = array();
+  $ftype[] = '';
+  if ($dgenus) {
+    $sql = "
+      SELECT DISTINCT OFC.cvterm_id, OFC.feature_type
+      FROM {organism_feature_count} OFC
+      WHERE OFC.genus = :genus
+    ";
+    $args = array();
+    $args['genus'] = $dgenus;
+    if ($dspecies) {
+      $sql .= " AND OFC.species = :species";
+      $args['species'] = $dspecies;
+    }
+    $sql .= " ORDER BY OFC.feature_type ";
+    $results = chado_query($sql, $args);
+
+    while ($type = $results->fetchObject()) {
+      $ftype[$type->feature_type] = $type->feature_type;
+    }
+  }
+  $form['ftype'] = array(
+    '#title'         => t('Feature Type'),
+    '#type'          => 'select',
+    '#options'       => $ftype,
+    '#multiple'      => FALSE,
+    '#default_value' => $dftype,
+    '#description'   => t('The type of feature to retrieve (e.g. mRNA). All
+        features that match this type will be retrieved.'),
+  );
+
+  $form['fnames'] = array(
+    '#title'         => t('Feature Name'),
+    '#type'          => 'file_upload_combo',
+    '#default_value' => $dfnames,
+    '#description'   => t('The names of the features to retrieve. Separate each
+         with a new line or comma. Leave blank to retrieve all features
+        matching other criteria.'),
+    '#rows'          => 8
+  );
+  $form['upstream'] = array(
+    '#title'         => t('Upstream Bases'),
+    '#type'          => 'textfield',
+    '#description'   => t('A numeric value specifying the number of upstream
+         bases to include. Only works if the feature is aligned to a larger
+         sequence.'),
+    '#default_value' => $dupstream,
+    '#size'          => 5,
+  );
+  $form['downstream'] = array(
+    '#title'         => t('Downstream Bases'),
+    '#type'          => 'textfield',
+    '#description'   => t('A numeric value specifying the number of downstream
+        bases to incldue.  Only works if the feature is aligned to a larger
+        sequence.'),
+    '#default_value' => $ddownstream,
+    '#size'          => 5,
+  );
+  $form['advanced'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'Advanced',
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE
+  );
+
+  $form['advanced']['use_parent'] = array(
+    '#title'         => t('Use Parent'),
+    '#type'          => 'checkbox',
+    '#default_value' => $duse_parent,
+    '#description'   => t('Check this box to retrieve the sequence from the
+        parent in an alignment rather than the feature itself. This is useful
+        if the same feature is aligned to multiple parents and you would like
+        to retrieve the underlying sequence from each parent.'),
+  );
+  $form['advanced']['aggregate'] = array(
+    '#title'         => t('Aggregate'),
+    '#type'          => 'checkbox',
+    '#default_value' => $daggregate,
+    '#description'   => t('Check this box to aggregate sub features into a
+        single sequence.  This is useful, for example, for obtaining CDS
+        sequence from an mRNA. Rather than retrieve the mRNA sequence, the
+        sub features of the mRNA will be aggregated and that will be returned.')
+  );
+  $form['advanced']['agg_types'] = array(
+    '#title'         => t('Types to aggregate'),
+    '#type'          => 'textarea',
+    '#default_value' => $dagg_types,
+    '#description'   => t('Set this argument to the type of children to
+        aggregate.  This is useful in the case where a gene has exons, CDSs
+        and UTRs.  In this case, you may only want to aggregate CDSs and
+        exclude exons.  If you want to aggregate both CDSs and UTRs you
+        could specify both.  Please place each type on a new line.')
+  );
+  $form['retrieve_btn'] = array(
+    '#type' => 'submit',
+    '#name' => 'retrieve',
+    '#value' => 'Retrieve Sequences',
+  );
+
+  if (user_access('administer tripal')) {
+    $notice = tripal_set_message("Administrators, the " .
+        l('organism_feature_count', 'admin/tripal/schema/mviews') . " and " .
+        l('analysis_organism', 'admin/tripal/schema/mviews') . " materialized
+        views must be populated before using this form.  Those views should be re-populated
+        when new data is added.", TRIPAL_NOTICE, array('return_html' => TRUE));
+  }
+
+  $form['#prefix'] = '<div id="tripal-feature-seq-extract-form">';
+  $form['#suffix'] = $notice . '</div>';
+
+  return $form;
+}
+
+/**
+ * Theme the Form to choose which features to extract sequence for
+ *
+ * @ingroup tripal_feature
+ */
+function theme_tripal_feature_seq_extract_form(&$variables) {
+  $form = $variables['form'];
+
+  $headers = array();
+  $rows = array(
+    0 => array(
+      array('data' => drupal_render($form['description']), 'colspan' => 3),
+    ),
+    1 => array(
+      drupal_render($form['genus']),
+      drupal_render($form['species']) ,
+      drupal_render($form['ftype']),
+    ),
+    2 => array(
+      array('data' => drupal_render($form['analysis']), 'colspan' => 3),
+      //drupal_render($form['format']),
+    ),
+    3 => array(
+      array('data' =>  drupal_render($form['fnames']), 'colspan' => 2),
+      drupal_render($form['upstream']) .
+      drupal_render($form['downstream']) .
+      drupal_render($form['format']),
+    ),
+    4 => array(
+      array(
+        'data' =>  drupal_render($form['advanced']),
+        'colspan' => 3,
+      ),
+    ),
+    5 => array(
+      array(
+        'data' =>  drupal_render($form['retrieve_btn']) . drupal_render($form['reset_btn']),
+        'colspan' => 3,
+      ),
+    ),
+  );
+
+  $table_vars = array(
+    'header' => $headers,
+    'rows' => $rows,
+    'attributes' => array(
+      'id' => 'tripal-feature-seq-extract-form-table',
+      'border' => '0'
+    ),
+    'sticky' => FALSE,
+    'colgroups' => array(),
+    'empty' => '',
+  );
+
+  $form['rendered_form'] = array(
+    '#type' => 'item',
+    '#markup' => theme('table', $table_vars),
+  );
+  return drupal_render_children($form);
+}
+/**
+ * Ajax function which returns the form via ajax
+ */
+function tripal_feature_seq_extract_form_ajax_callback($form, &$form_state) {
+  return $form;
+}
+
+/**
+ * Validate the extract sequence form
+ *
+ * @ingroup tripal_feature
+ */
+function tripal_feature_seq_extract_form_validate($form, &$form_state) {
+  $genus      = $form_state['values']['genus'];
+  $species    = $form_state['values']['species'];
+  $analysis   = $form_state['values']['analysis'];
+  $ftype      = $form_state['values']['ftype'];
+  $fnames     = $form_state['values']['fnames'];
+  $upstream   = $form_state['values']['upstream'];
+  $downstream = $form_state['values']['downstream'];
+  $use_parent = $form_state['values']['use_parent'];
+  $aggregate  = $form_state['values']['aggregate'];
+  $agg_types  = $form_state['values']['agg_types'];
+
+  if ($upstream and !preg_match('/^\d+$/', $upstream)) {
+    form_set_error('upstream', 'Please enter a positive numeric value for the upstream bases');
+  }
+   if ($downstream and !preg_match('/^\d+$/', $downstream)) {
+    form_set_error('downstream', 'Please enter a positive numeric value for the downstream bases');
+  }
+  if (!$genus and !$species and !$ftype and !$fnames) {
+    form_set_error('', 'Please provide a feature name, a feature type or a genus.');
+  }
+  if ($ftype == 'polypeptide' and $upstream) {
+    form_set_error('upstream', 'When the sequence type is protein the upstream value must be unset.');
+  }
+  if ($ftype == 'polypeptide' and $downstream) {
+    form_set_error('downstream', 'When the sequence type is protein the downstream value must be unset.');
+  }
+  if ($ftype == 'polypeptide' and $use_parent) {
+    form_set_error('use_parent', 'When the sequence type is protein the "Use Parent" option must not be set.');
+  }
+}
+
+/**
+ * Submit the extract sequence form
+ *
+ * @ingroup tripal_feature
+ */
+function tripal_feature_seq_extract_form_submit($form, &$form_state) {
+  $genus      = $form_state['values']['genus'];
+  $species    = $form_state['values']['species'];
+  $analysis   = $form_state['values']['analysis'];
+  $ftype      = $form_state['values']['ftype'];
+  $fnames     = $form_state['values']['fnames'];
+  $upstream   = $form_state['values']['upstream'];
+  $downstream = $form_state['values']['downstream'];
+  $use_parent = $form_state['values']['use_parent'];
+  $aggregate  = $form_state['values']['aggregate'];
+  $agg_types  = $form_state['values']['agg_types'];
+
+  // we must use the parent sequence if the user has selected
+  // the upstream, downstream or to aggregate
+  if ($upstream or $downstream or $aggregate) {
+    $use_parent = 1;
+  }
+
+  if ($form_state['clicked_button']['#name'] == 'retrieve') {
+    $_SESSION['tripal_feature_seq_extract']['genus']      = $genus;
+    $_SESSION['tripal_feature_seq_extract']['species']    = $species;
+    $_SESSION['tripal_feature_seq_extract']['analysis']   = $analysis;
+    $_SESSION['tripal_feature_seq_extract']['ftype']      = $ftype;
+    $_SESSION['tripal_feature_seq_extract']['fnames']     = $fnames;
+    $_SESSION['tripal_feature_seq_extract']['upstream']   = $upstream;
+    $_SESSION['tripal_feature_seq_extract']['downstream'] = $downstream;
+    $_SESSION['tripal_feature_seq_extract']['format']     = 'fasta_txt';
+    $_SESSION['tripal_feature_seq_extract']['use_parent'] = $use_parent;
+    $_SESSION['tripal_feature_seq_extract']['aggregate']  = $aggregate;
+    $_SESSION['tripal_feature_seq_extract']['agg_types']  = $agg_types;
+    $_SESSION['tripal_feature_seq_extract']['download']   = 1;
+
+    drupal_goto('find/sequences/download');
+  }
+}

+ 28 - 28
legacy/tripal_feature/theme/templates/tripal_feature_sequence.tpl.php

@@ -1,10 +1,10 @@
 <?php
 /*
- * There are several ways that sequences can be displayed.  They can come from the 
+ * There are several ways that sequences can be displayed.  They can come from the
  * feature.residues column,  they can come from an alignment with another feature,
  * they can come from a protein sequence that has relationship with this sequence,
  * or they can come from sub children (e.g. CDS coding sequences).
- *   
+ *
  * This template will show all types depending on the data available.
  *
  */
@@ -12,7 +12,7 @@
 $feature = $variables['node']->feature;
 
 // number of bases per line in FASTA format
-$num_bases = 50; 
+$num_bases = 50;
 
 // we don't want to get the sequence for traditionally large types. They are
 // too big,  bog down the web browser, take longer to load and it's not
@@ -24,37 +24,37 @@ if(strcmp($feature->type_id->name,'scaffold') !=0 and
    strcmp($feature->type_id->name,'pseudomolecule') !=0) {
   $feature = chado_expand_var($feature,'field','feature.residues');
   $residues = $feature->residues;
-} 
+}
 
 // get the sequence derived from alignments
 $feature = $variables['node']->feature;
 $featureloc_sequences = $feature->featureloc_sequences;
 
-if ($residues or count($featureloc_sequences) > 0) { 
+if ($residues or count($featureloc_sequences) > 0) {
 
   $sequences_html = '';  // a variable for holding all sequences HTML text
   $list_items = array(); // a list to be used for theming of content on this page
-  
+
   // ADD IN RESIDUES FOR THIS FEATURE
   // add in the residues if they are present
   if ($residues) {
     $list_items[] = '<a href="#residues">' . $feature->type_id->name . ' sequence</a>';
-     
+
     // format the sequence to break every 50 residues
     $sequences_html .= '<a name="residues"></a>';
     $sequences_html .= '<div id="residues" class="tripal_feature-sequence-item">';
     $sequences_html .= '<p><b>' . $feature->type_id->name . ' sequence</b></p>';
     $sequences_html .= '<pre class="tripal_feature-sequence">';
-    $sequences_html .= '>' . tripal_get_fasta_defline($feature, '', NULL, '', strlen($feature->residues)) . "\n";
+    $sequences_html .= '>' . tripal_get_fasta_defline($feature, '', NULL, '', strlen($feature->residues)) . "<br>";
     $sequences_html .= wordwrap($feature->residues, $num_bases, "<br>", TRUE);
     $sequences_html .= '</pre>';
     $sequences_html .= '<a href="#sequences-top">back to top</a>';
     $sequences_html .= '</div>';
-    
+
   }
-  
+
   // ADD IN RELATIONSHIP SEQUENCES (e.g. proteins)
-  // see the explanation in the tripal_feature_relationships.tpl.php 
+  // see the explanation in the tripal_feature_relationships.tpl.php
   // template for how the 'all_relationships' is provided. It is this
   // variable that we use to get the proteins.
   $all_relationships = $feature->all_relationships;
@@ -64,27 +64,27 @@ if ($residues or count($featureloc_sequences) > 0) {
   foreach ($object_rels as $rel_type => $rels){
     foreach ($rels as $subject_type => $subjects){
       foreach ($subjects as $subject){
-        
+
         // add in protein sequence if it has residues
         if ($rel_type == 'derives from' and $subject_type == 'polypeptide') {
           $protein = $subject->record->subject_id;
           $protein = chado_expand_var($protein, 'field', 'feature.residues');
-          
+
           if ($protein->residues) {
             $list_items[] = '<a href="#residues">protein sequence</a>';
             $sequences_html .= '<a name="protein-' . $protein->feature_id . '"></a>';
             $sequences_html .= '<div id="protein-' . $protein->feature_id . '" class="tripal_feature-sequence-item">';
             $sequences_html .= '<p><b>protein sequence of ' . $protein->name . '</b></p>';
             $sequences_html .= '<pre class="tripal_feature-sequence">';
-            $sequences_html .= '>' . tripal_get_fasta_defline($protein, '', NULL, '', strlen($protein->residues)) . "\n";
+            $sequences_html .= '>' . tripal_get_fasta_defline($protein, '', NULL, '', strlen($protein->residues)) . "<br>";
             $sequences_html .= wordwrap($protein->residues, $num_bases, "<br>", TRUE);
             $sequences_html .= '</pre>';
             $sequences_html .= '<a href="#sequences-top">back to top</a>';
             $sequences_html .= '</div>';
           }
         }
-        
-        // If the CDS has sequences then concatenate those. The objects 
+
+        // If the CDS has sequences then concatenate those. The objects
         // should be returned in order of rank
         if ($rel_type == 'part of' and $subject_type == 'CDS') {
           $cds = $subject->record->subject_id;
@@ -94,14 +94,14 @@ if ($residues or count($featureloc_sequences) > 0) {
             $coding_seq .= $cds->residues;
           }
         }
-        
+
         // add any other sequences that are related through a relationship
         // and that have values in the 'residues' column
-        
+
       }
     }
   }
-  
+
   // CODING SEQUENCES FROM RELATIONSHIPS
   // add in any CDS sequences.
   if ($has_coding_seq) {
@@ -115,7 +115,7 @@ if ($residues or count($featureloc_sequences) > 0) {
     $sequences_html .= '<a href="#sequences-top">back to top</a>';
     $sequences_html .= '</div>';
   }
-  
+
   /* ADD IN ALIGNMENT SEQUENCES FOR THIS FEATURE
    * For retreiving the sequence from an alignment we would typically make a call to
    * chado_expand_var function.  For example, to retrieve all
@@ -127,7 +127,7 @@ if ($residues or count($featureloc_sequences) > 0) {
    * Then all of the sequences would need to be retreived from the alignments and
    * formatted for display below.  However, to simplify this template, this has already
    * been done by the tripal_feature module and the sequences are made available in
-   * the variable: 
+   * the variable:
    *
    *   $feature->featureloc_sequences
    */
@@ -148,7 +148,7 @@ if ($residues or count($featureloc_sequences) > 0) {
       $sequences_html .= '<a href="#sequences-top">back to top</a>';
       $sequences_html .= '</div>';
     }
-    
+
     // check to see if this alignment has any CDS. If so, generate a CDS sequence
     $cds_sequence = tripal_get_feature_sequences(
         array(
@@ -183,14 +183,14 @@ if ($residues or count($featureloc_sequences) > 0) {
         $sequences_html .= '</div>';
       }
     }
-  } 
+  }
   ?>
 
-  <div class="tripal_feature-data-block-desc tripal-data-block-desc">The following sequences are available for this feature:</div> 
+  <div class="tripal_feature-data-block-desc tripal-data-block-desc">The following sequences are available for this feature:</div>
   <?php
-  
+
   // first add a list at the top of the page that can be formatted as the
-  // user desires.  We use the theme_item_list function of Drupal to create 
+  // user desires.  We use the theme_item_list function of Drupal to create
   // the list rather than hard-code the HTML here.  Instructions for how
   // to create the list can be found here:
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_item_list/7
@@ -216,7 +216,7 @@ if ($residues or count($featureloc_sequences) > 0) {
           is associated with an mRNA feature and protein sequences will appear on the mRNA page.</li>
       <li>This feature has one or more CDS features associated via the "feature_relationship" table of Chado with a
           relationship of type "part of". If the CDS features have residues then those will be concatenated
-          and presented as a sequence. Typically, CDSs are associated with an mRNA feature and CDS sequences 
+          and presented as a sequence. Typically, CDSs are associated with an mRNA feature and CDS sequences
           will appear on the mRNA page.</li>
       <li>This feature is aligned to another feature (e.g. scaffold, or chromosome) and this feature has
           one or more CDS features associated.  The CDS sequenes underlying the alignment will be
@@ -224,7 +224,7 @@ if ($residues or count($featureloc_sequences) > 0) {
     </ul>
     </p>';
   print tripal_set_message($message, TRIPAL_INFO, array('return_html' => 1));
-  
+
   // now print the sequences
   print $sequences_html;
 }

+ 10 - 3
legacy/tripal_feature/tripal_feature.drush.inc

@@ -106,11 +106,18 @@ function drush_tripal_feature_tripal_get_sequence() {
     'sub_feature_types' => explode(',', $child),
     'relationship_type' => $relationship,
     'relationship_part' => $rel_part,
-    'print' => TRUE,
     'width' => $width
   );
-  
-  tripal_get_bulk_feature_sequences($options);
+
+  $seqs = tripal_get_bulk_feature_sequences($options);
+  if (count($seqs) == 0) {
+      print "No sequences found that match the criteria.";
+  }
+
+  foreach ($seqs as $seq) {
+    print ">" . $seq['defline'] . "\r\n";
+    print $seq['residues'] . "\r\n";
+  }
 }
 
 /**

+ 10 - 12
legacy/tripal_feature/tripal_feature.module

@@ -112,15 +112,19 @@ function tripal_feature_menu() {
   $items['find/sequences'] = array(
     'title' => 'Sequence Retrieval',
     'description' => 'Download a file of sequences',
-    'page callback' => 'tripal_feature_seq_extract_page',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_feature_seq_extract_form'),
     'access arguments' => array('access chado_feature content'),
+    'file' =>  'includes/tripal_feature.seq_extract.inc',
+    'file path' => drupal_get_path('module', 'tripal_feature'),
     'type' => MENU_CALLBACK,
   );
 
-  $items['find/sequences/ajax'] = array(
-    'title' => 'Sequence Retrieval',
-    'page callback' => 'tripal_feature_seq_extract_form_ahah_update',
+  $items['find/sequences/download'] = array(
+    'page callback' => 'tripal_feature_seq_extract_download',
     'access arguments' => array('access chado_feature content'),
+    'file' =>  'includes/tripal_feature.seq_extract.inc',
+    'file path' => drupal_get_path('module', 'tripal_feature'),
     'type' => MENU_CALLBACK,
   );
 
@@ -322,7 +326,7 @@ function tripal_feature_theme($existing, $type, $theme, $path) {
 
   // Themed Forms
   $items['tripal_feature_seq_extract_form'] = array(
-     'arguments' => array('form'),
+     'render element' => 'form',
   );
 
   // D3 Charts.
@@ -472,7 +476,7 @@ function tripal_feature_match_features_page($id) {
       INNER JOIN {cvterm} CVT on CVT.cvterm_id = F.type_id
       LEFT JOIN {feature_synonym} FS on FS.feature_id = F.feature_id
       LEFT JOIN {synonym} S on S.synonym_id = FS.synonym_id
-      INNER JOIN public.chado_feature CF on CF.feature_id = F.feature_id
+      INNER JOIN {chado_feature} CF on CF.feature_id = F.feature_id
     WHERE
       F.uniquename = :uname or
       F.name = :fname or
@@ -529,12 +533,6 @@ function tripal_feature_match_features_page($id) {
  * @ingroup tripal_feature
  */
 function tripal_feature_form_alter(&$form, &$form_state, $form_id) {
-  if ($form_id == "tripal_feature_seq_extract_form") {
-    // updating the form through the ahah callback sets the action of
-    // the form to the ahah callback URL. We need to set it back
-    // to the normal form URL
-    $form['#action'] = url("find/sequences");
-  }
   // turn off preview button for insert/updates
   if ($form_id == "chado_feature_node_form") {
     $form['actions']['preview']['#access'] = FALSE;

+ 16 - 16
legacy/tripal_featuremap/theme/templates/tripal_featuremap_publication.tpl.php

@@ -1,65 +1,65 @@
 <?php
 $featuremap = $variables['node']->featuremap;
 
-// expand featuremap to include pubs 
+// expand featuremap to include pubs
 $options = array('return_array' => 1);
 $featuremap = chado_expand_var($featuremap, 'table', 'featuremap_pub', $options);
-$featuremap_pubs = $featuremap->featuremap_pub; 
+$featuremap_pubs = $featuremap->featuremap_pub;
 
 
 if (count($featuremap_pubs) > 0) { ?>
-  <div class="tripal_featuremap_pub-data-block-desc tripal-data-block-desc"></div> <?php 
+  <div class="tripal_featuremap_pub-data-block-desc tripal-data-block-desc"></div> <?php
 
   // the $headers array is an array of fields to use as the colum headers.
   // additional documentation can be found here
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
   $headers = array('Year', 'Publication');
-  
+
   // the $rows array contains an array of rows where each row is an array
   // of values for each column of the table in that row.  Additional documentation
   // can be found here:
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
   $rows = array();
-  
+
   foreach ($featuremap_pubs as $featuremap_pub) {
     $pub = $featuremap_pub->pub_id;
     $pub = chado_expand_var($pub, 'field', 'pub.title');
     $citation = $pub->title;  // use the title as the default citation
-    
+
     // get the citation for this pub if it exists
     $values = array(
-      'pub_id' => $pub->pub_id, 
+      'pub_id' => $pub->pub_id,
       'type_id' => array(
         'name' => 'Citation',
       ),
     );
     $options = array('return_array' => 1);
-    $citation_prop = chado_generate_var('pubprop', $values, $options); 
+    $citation_prop = chado_generate_var('pubprop', $values, $options);
     if (count($citation_prop) == 1) {
       $citation_prop = chado_expand_var($citation_prop, 'field', 'pubprop.value');
       $citation = $citation_prop[0]->value;
     }
-    
+
     // if the publication is synced then link to it
     if (property_exists($pub, 'nid')) {
       // replace the title with a link
       $link = l($pub->title, 'node/' . $pub->nid ,array('attributes' => array('target' => '_blank')));
       $patterns = array(
-        '/(\()/', '/(\))/', 
+        '/(\()/', '/(\))/',
         '/(\])/', '/(\[)/',
         '/(\{)/', '/(\})/',
-        '/(\+)/', '/(\.)/', '/(\?)/', 
+        '/(\+)/', '/(\.)/', '/(\?)/',
       );
       $fixed_title = preg_replace($patterns, "\\\\$1", $pub->title);
-      $citation = preg_replace('/' . $fixed_title . '/', $link, $citation);
+      $citation = preg_replace('/' . str_replace('/', ' ', $fixed_title) . '/', $link, $citation);
     }
-    
+
     $rows[] = array(
       $pub->pyear,
       $citation,
     );
   }
-  
+
   // the $table array contains the headers and rows array as well as other
   // options for controlling the display of the table.  Additional
   // documentation can be found here:
@@ -76,8 +76,8 @@ if (count($featuremap_pubs) > 0) { ?>
     'colgroups' => array(),
     'empty' => '',
   );
-  
+
   // once we have our table array structure defined, we call Drupal's theme_table()
   // function to generate the table.
-  print theme_table($table);  
+  print theme_table($table);
 }

+ 6 - 3
legacy/tripal_library/includes/tripal_library.chado_node.inc

@@ -206,9 +206,12 @@ function chado_library_form($node, &$form_state) {
   // ADDITIONAL DBXREFS FORM
   //---------------------------------------------
   $details = array(
-    'linking_table' => 'library_dbxref',  // the name of the _dbxref table
-    'base_foreign_key' => 'library_id',   // the name of the key in your base chado table
-    'base_key_value' => $library_id       // the value of library_id for this record
+    // The name of the _dbxref table.
+    'linking_table' => 'library_dbxref',
+    // The name of the key in your base chado table.
+    'base_foreign_key' => 'library_id',
+    // The value of library_id for this record.
+    'base_key_value' => $library_id
   );
   // Adds the form elements to your current form
   chado_add_node_form_dbxrefs($form, $form_state, $details);

+ 4 - 2
legacy/tripal_pub/includes/tripal_pub.chado_node.inc

@@ -607,8 +607,10 @@ function chado_pub_insert($node) {
     // to automatically generate a citation if a uniquename doesn't already exist
     $pub_arr = array();
 
-    $properties = array(); // stores all of the properties we need to add
-    $cross_refs = array(); // stores any cross references for this publication
+    // Stores all of the properties we need to add.
+    $properties = array();
+    // Stores any cross references for this publication.
+    $cross_refs = array();
 
     // get the properties from the form
     $properties = chado_retrieve_node_form_properties($node);

+ 3 - 3
legacy/tripal_pub/includes/tripal_pub.pub_search.inc

@@ -386,7 +386,7 @@ function tripal_pub_search_form_ajax_button_validate() {
  * @ingroup tripal_pub
  */
 function tripal_pub_search_form_validate($form, &$form_state) {
-  $num_criteria = $form_state['values']['num_criteria'];
+  $num_criteria = $form_state['storage']['num_criteria'];
   $from_year    = $form_state['values']['from_year'];
   $to_year      = $form_state['values']['to_year'];
   $op           = $form_state['values']['op'];
@@ -416,7 +416,7 @@ function tripal_pub_search_form_validate($form, &$form_state) {
  * @ingroup tripal_pub
  */
 function tripal_pub_search_form_submit($form, &$form_state) {
-  $num_criteria = $form_state['values']['num_criteria'];
+  $num_criteria = $form_state['storage']['num_criteria'];
   $from_year    = $form_state['values']['from_year'];
   $to_year      = $form_state['values']['to_year'];
   $op           = $form_state['values']['op'];
@@ -575,7 +575,7 @@ function tripal_search_publications($search_array, $offset, $limit, &$total_reco
   // build the SQL based on the criteria provided by the user
   $select = "SELECT DISTINCT P.*, CP.nid ";
   $from   = "FROM {pub} P
-               LEFT JOIN public.chado_pub CP on P.pub_id = CP.pub_id
+               LEFT JOIN {chado_pub} CP on P.pub_id = CP.pub_id
                INNER JOIN {cvterm} CVT on CVT.cvterm_id = P.type_id
             ";
   $where  = "WHERE (NOT P.title = 'null') "; // always exclude the dummy pub

+ 1 - 1
legacy/tripal_pub/theme/templates/tripal_pub_features.tpl.php

@@ -37,7 +37,7 @@ if(count($features) > 0){ ?>
   // the $headers array is an array of fields to use as the colum headers.
   // additional documentation can be found here
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
-  $headers = array('Feature Name', 'Uniquenaem', 'Type');
+  $headers = array('Feature Name', 'Uniquename', 'Type');
 
   // the $rows array contains an array of rows where each row is an array
   // of values for each column of the table in that row.  Additional documentation

+ 1 - 1
legacy/tripal_stock/tripal_stock.module

@@ -392,7 +392,7 @@ function tripal_stock_match_stocks_page($id) {
     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
+      INNER JOIN {chado_stock} CS on CS.stock_id = S.stock_id
     WHERE
       S.uniquename = :uname or S.name = :name
   ";

+ 3 - 2
tripal/api/tripal.entities.api.inc

@@ -339,9 +339,8 @@ function tripal_create_bundle($args, &$error = '') {
   }
   catch (Exception $e) {
     $transaction->rollback();
-    $error = _drupal_decode_exception($e);
     drupal_set_message(t("Failed to create content type: %message.",
-        array('%message' => $error['!message'])), 'error');
+      array('%message' => $e->getMessage())), 'error');
     return FALSE;
   }
 
@@ -561,6 +560,7 @@ function tripal_create_bundle_fields($bundle, $term) {
 
   // Iterate through all of the field instances and create them.
   foreach ($info as $field_name => $details) {
+
     // If the field is already attached to this bundle then skip it.
     $field = field_info_field($details['field_name']);
     if ($field and array_key_exists('bundles', $field) and
@@ -568,6 +568,7 @@ function tripal_create_bundle_fields($bundle, $term) {
         in_array($bundle->name, $field['bundles']['TripalEntity'])) {
       continue;
     }
+
     // Create the field instance.
     $instance = field_create_instance($details);
     $added[] = $field_name;

+ 6 - 5
tripal/api/tripal.notice.api.inc

@@ -134,8 +134,9 @@ function tripal_report_error($type, $severity, $message, $variables = array(), $
  * @param $message
  *   The message to be displayed to the tripal administrators
  * @param $importance
- *   The level of importance for this message. In the future this will be used to allow
- *   administrators to filter some of these messages. It can be one of the following:
+ *   The level of importance for this message. In the future this will be used
+ *   to allow administrators to filter some of these messages. It can be one of
+ *   the following:
  *     - TRIPAL_CRITICAL: Critical conditions.
  *     - TRIPAL_ERROR: Error conditions.
  *     - TRIPAL_WARNING: Warning conditions.
@@ -144,9 +145,9 @@ function tripal_report_error($type, $severity, $message, $variables = array(), $
  *     - TRIPAL_DEBUG: Debug-level messages.
  * @param $options
  *   Any options to apply to the current message. Supported options include:
- *     - return_html: return HTML instead of setting a drupal message. This can be
- *         used to place a tripal message in a particular place in the page.
- *         The default is FALSE.
+ *     - return_html: return HTML instead of setting a drupal message. This can
+ *       be used to place a tripal message in a particular place in the page.
+ *       The default is FALSE.
  */
 function tripal_set_message($message, $importance = TRIPAL_INFO, $options = array()) {
   global $user;

+ 5 - 0
tripal/api/tripal.terms.api.inc

@@ -218,6 +218,11 @@ function tripal_add_term($details) {
  *   cannot be found.
  */
 function tripal_get_term_details($vocabulary, $accession) {
+
+  if (empty($vocabulary) OR empty($accession)) {
+    tripal_report_error('tripal_term', TRIPAL_ERROR, 'Unable to retrieve details for term due to missing vocabulary and/or accession.');
+  }
+
   // TODO: we need some sort of administrative interface that lets the user
   // switch to the desired vocabulary type. For now, we'll just use the
   // first one in the list.

+ 3 - 4
tripal/includes/TripalBundleUIController.inc

@@ -487,7 +487,6 @@ function tripal_bundle_access($op, $type = NULL, $account = NULL) {
  */
 function tripal_admin_add_type_form($form, &$form_state) {
 
-
   $stores = module_invoke_all('vocab_storage_info');
   if (!is_array($stores) or count($stores) == 0) {
     tripal_set_message('A storage backend is not enabled for managing
@@ -529,6 +528,8 @@ function tripal_admin_add_type_form($form, &$form_state) {
       'method' => 'replace'
     ),
   );
+  $form['#prefix'] = '<div id = "tripal-vocab-select-form">';
+  $form['#suffix'] = '</div>';
 
   // If the term has been provided by the user then we want to search for
   // matching terms in the database and let them select among any matches.
@@ -588,6 +589,7 @@ function tripal_admin_add_type_form($form, &$form_state) {
         '#type' => 'item',
         '#markup' => '<i>' . t('There is no term that matches the entered text.') . '</i>'
       );
+      return $form;
     }
 
     // Now let the user select where the data type will be stored.
@@ -644,9 +646,6 @@ function tripal_admin_add_type_form($form, &$form_state) {
     }
   }
 
-  $form['#prefix'] = '<div id = "tripal-vocab-select-form">';
-  $form['#suffix'] = '</div>';
-
 
   return $form;
 }

+ 8 - 4
tripal/includes/TripalFieldQuery.inc

@@ -6,9 +6,14 @@
  */
 class TripalFieldQuery extends EntityFieldQuery {
 
-  protected $field_storage = array();
-
+  /**
+   * Holds a list of fields that should be included in the results
+   */
+  protected $includes = array();
 
+  /**
+   * Overrides the EntityFieldQuery::execute() function.
+   */
   public function execute() {
     // Give a chance for other modules to alter the query.
     drupal_alter('entity_query', $this);
@@ -73,7 +78,6 @@ class TripalFieldQuery extends EntityFieldQuery {
     }
   }
 
-
   /**
    * Determines the query callback to use for this entity query.
    *
@@ -89,7 +93,7 @@ class TripalFieldQuery extends EntityFieldQuery {
    *   A callback that can be used with call_user_func().
    *
    */
-  public function queryStorageCallback($storage) {
+  protected function queryStorageCallback($storage) {
     // Use the override from $this->executeCallback. It can be set either
     // while building the query, or using hook_entity_query_alter().
     if (function_exists($this->executeCallback)) {

+ 362 - 56
tripal/includes/TripalFields/TripalField.inc

@@ -15,8 +15,7 @@ class TripalField {
   public static $default_label = 'Tripal Field';
 
   // The default description for this field.
-  public static $default_description = 'The generic base class for all Tripal fields. ' .
-    'Replace this text as appropriate for the child implementation.';
+  public static $default_description = 'The generic base class for all Tripal fields. Replace this text as appropriate for the child implementation.';
 
   // Provide a list of global settings. These can be accessed witihn the
   // globalSettingsForm.  When the globalSettingsForm is submitted then
@@ -91,6 +90,9 @@ class TripalField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
 
+  // The term array that provides all the details about the controlled
+  // vocabulary term that this field maps to.
+  protected $term;
 
   // --------------------------------------------------------------------------
   //                     CONSTRUCTOR
@@ -105,11 +107,31 @@ class TripalField {
    *   An array containing the instance data as returned by field_instance_info().
    */
   public function __construct($field, $instance) {
+    $vocabulary = $term = NULL;
     $this->field = $field;
     $this->instance = $instance;
 
     $class = get_called_class();
 
+    // Use the term info defined in the class by default (assuming it's not schema:Thing ;-).
+    if ($class::$default_instance_settings['term_name'] != 'Thing') {
+      $vocabulary = $class::$default_instance_settings['term_vocabulary'];
+      $accession = $class::$default_instance_settings['term_accession'];
+    }
+
+    // Allow the constructor to override the term info.
+    $vocabulary = isset($this->instance['settings']['term_vocabulary']) ? $this->instance['settings']['term_vocabulary'] : $vocabulary;
+    $accession = isset($this->instance['settings']['term_accession']) ? $this->instance['settings']['term_accession'] : $accession;
+
+    // Assuming we have term info, load the term.
+    if (!empty($vocabulary) AND !empty($accession)) {
+      $this->term = tripal_get_term_details($vocabulary, $accession);
+    }
+    else {
+      tripal_report_error('tripal_field', TRIPAL_ERROR, 'Unable to instantiate Field :name due to missing vocabulary and/or accession.',
+        array(':name' => $class::$default_label));
+    }
+
     if (!$instance) {
       tripal_set_message(t('Missing instance of field "%field"', array('%field' => $field['field_name'])), TRIPAL_ERROR);
     }
@@ -146,7 +168,7 @@ class TripalField {
 
 
   // --------------------------------------------------------------------------
-  //                 GETTERS AND SETTERS -- DO NOT OVERRIDE
+  //                 DO NOT OVERRIDE THESE FUNCTIONS
   // --------------------------------------------------------------------------
 
   /**
@@ -174,6 +196,218 @@ class TripalField {
     return $this->field['id'];
   }
 
+  public function getFieldTerm(){
+    return $this->term;
+  }
+
+  public function getFieldTermID() {
+    $class = get_called_class();
+    return $this->instance['settings']['term_vocabulary'] . ':' . $this->instance['settings']['term_accession'];
+  }
+
+  /**
+   * Describes this field to Tripal web services.
+   *
+   * The child class need not implement this function has all of the details
+   * provided for elements by the elementInfo() function are used to generate
+   * the details needed for Views.
+   *
+   * @return
+   *   An associative array with the keys available for searching. The value
+   *   is the term array for the element.
+   */
+  public function webServicesData() {
+    $elements = $this->elementInfo();
+
+    $field_term = $this->getFieldTermID();
+    $field_term_name = strtolower(preg_replace('/[^\w]/', '_', $this->term['name']));
+    $field_details = $elements[$field_term];
+
+    $searchable_keys = array();
+    if (array_key_exists('searchable', $field_details) and $field_details['searchable']) {
+      $searchable_keys[$field_term_name] = $field_term;
+    }
+
+    // Now add any entries for child elements.
+    if (array_key_exists('elements', $field_details)) {
+      $elements = $field_details['elements'];
+      foreach ($elements as $element_name => $element_details) {
+        $this->_addWebServiceElement($searchable_keys, $field_term_name, $field_term, $element_name, $element_details);
+      }
+    }
+
+    return $searchable_keys;
+  }
+
+  /**
+   *
+   * @param $searchabe_keys
+   * @param $field_name
+   * @param $element_name
+   * @param $element_details
+   */
+  protected function _addWebServiceElement(&$searchable_keys, $parent_term_name, $parent_term, $element_name, $element_details) {
+    // Skip the 'entity' element, as we'll never make this searchable or
+    // viewable. It's meant for linking.
+    if ($element_name == 'entity') {
+      return;
+    }
+
+    list($vocabulary, $accession) = explode(':', $element_name);
+    $term = tripal_get_term_details($vocabulary, $accession);
+    $field_term = $parent_term . ',' . $term['vocabulary']['short_name'] . ':' . $term['accession'];
+    $field_term_name = $parent_term_name . '.' . strtolower(preg_replace('/[^\w]/', '_', $term['name']));
+
+    // Is the field searchable?
+    if (array_key_exists('searchable', $element_details) and $element_details['searchable']) {
+      $searchable_keys[$field_term_name] =  $field_term;
+    }
+
+    // Now add any entries for child elements.
+    if (array_key_exists('elements', $element_details)) {
+      $elements = $element_details['elements'];
+      foreach ($elements as $element_name => $element_details) {
+        $this->_addWebServiceElement($searchable_keys, $field_term_name, $field_term, $element_name, $element_details);
+      }
+    }
+  }
+
+  /**
+   * Describes this field to Views.
+   *
+   * The child class need not implement this function has all of the details
+   * provided for elements by the elementInfo() function are used to generate
+   * the details needed for Views.
+   *
+   * @param $view_base_id
+   *   Views was originally designed to integrate with SQL tables. And
+   *   each field is associated with a table.  Because these are TripalFields
+   *   and views is not directly querying the tables it doesn't make sense to
+   *   associate fields with a table, but we must associate the fields with
+   *   the bundle.  Each bundle is uniquely identified with the $view_base_id
+   *   that is passed here.
+   *
+   * @return
+   *   An associative array describing the data structure. Primary key is the
+   *   name used internally by Views for the bundle that is provided by
+   *   the $view_base_id. The returned array should be compatible with the
+   *   instructions provided by the hook_views_data() function.
+   */
+  public function viewsData($view_base_id) {
+    $data = array();
+    $field_name = $this->field['field_name'];
+    $field_term = $this->getFieldTermID();
+
+    $elements = $this->elementInfo();
+    $field_details = $elements[$field_term];
+
+    // Get any titles or help text that is overriden.
+    $title = ucfirst($this->instance['label']);
+    if (array_key_exists('label', $field_details)) {
+      $title = $field_details['label'];
+    }
+    $help = $this->instance['description'];
+    if (array_key_exists('help', $field_details)) {
+      $help = $field_details['help'];
+    }
+
+    // Build the entry for the field.
+    $data[$view_base_id][$field_name] = array(
+      'title' => $title,
+      'help' => $help,
+      'field' => array(
+        'handler' => 'tripal_views_handler_field',
+        'click sortable' => TRUE,
+      ),
+    );
+    // Is the field sortable?
+    if (array_key_exists('sortable', $field_details) and $field_details['sortable']) {
+      $data[$view_base_id][$field_name]['sort']['handler'] = 'tripal_views_handler_sort';
+    }
+
+    // Is the field searchable?
+    if (array_key_exists('searchable', $field_details) and $field_details['searchable']) {
+      $filter_handler = 'tripal_views_handler_filter_string';
+      if (array_key_exists('type', $field_details) and $field_details['type'] == 'numeric') {
+        $filter_handler = 'tripal_views_handler_filter';
+      }
+      $data[$view_base_id][$field_name]['filter'] = array(
+        'handler' => $filter_handler,
+      );
+    }
+
+    // Now add any entries for child elements.
+    if (array_key_exists('elements', $field_details)) {
+      $elements = $field_details['elements'];
+      foreach ($elements as $element_name => $element_details) {
+        $this->_addViewsDataElement($data, $view_base_id, $field_name, $element_name, $element_details);
+      }
+    }
+    return $data;
+  }
+
+  /**
+   *
+   * @param unknown $data
+   * @param unknown $view_base_id
+   * @param unknown $parent
+   * @param unknown $element_name
+   * @param unknown $element_details
+   */
+  protected function _addViewsDataElement(&$data, $view_base_id, $parent, $element_name, $element_details) {
+    // Skip the 'entity' element, as we'll never make this searchable or
+    // viewable. It's meant for linking.
+    if ($element_name == 'entity') {
+      return;
+    }
+    $field_name = $parent . '.' . $element_name;
+    list($vocabulary, $accession) = explode(':', $element_name);
+    $term = tripal_get_term_details($vocabulary, $accession);
+
+    // Get any titles or help text that is overriden.
+    $title = ucfirst($term['name']);
+    if (array_key_exists('label', $element_details)) {
+      $title = $element_details['label'];
+    }
+    $help = $term['definition'];
+    if (array_key_exists('help', $element_details)) {
+      $help = $element_details['help'];
+    }
+
+    // Build the entry for the field.
+    $data[$view_base_id][$field_name] = array(
+      'title' => $title,
+      'help' => $help,
+      'field' => array(
+        'handler' => 'tripal_views_handler_field_element',
+      ),
+    );
+    // Is the field sortable?
+    if (array_key_exists('sortable', $element_details) and $element_details['sortable']) {
+      $data[$view_base_id][$field_name]['sort']['handler'] = 'tripal_views_handler_sort';
+      $data[$view_base_id][$field_name]['field']['click sortable'] = TRUE;
+    }
+
+    // Is the field searchable?
+    if (array_key_exists('searchable', $element_details) and $element_details['searchable']) {
+      $filter_handler = 'tripal_views_handler_filter_element_string';
+      if (array_key_exists('type', $element_details) and $element_details['type'] == 'numeric') {
+        $filter_handler = 'tripal_views_handler_filter';
+      }
+      $data[$view_base_id][$field_name]['filter'] = array(
+        'handler' => $filter_handler,
+      );
+    }
+
+    // Recusrively add any entries for child elements.
+    if (array_key_exists('elements', $element_details)) {
+      $elements = $element_details['elements'];
+      foreach ($elements as $element_name => $element_details) {
+        $this->_addViewsDataElement($data, $view_base_id, $field_name, $element_name, $element_details);
+      }
+    }
+  }
+
   // --------------------------------------------------------------------------
   //                            OVERRIDEABLE FUNCTIONS
   // --------------------------------------------------------------------------
@@ -242,6 +476,113 @@ class TripalField {
 
   }
 
+  /**
+   * Provides the list of elements returned by the 'value' of the field. \
+   *
+   * The elements provided by this function are used to integrate with
+   * Drupal Views and Web services.  The return value is an associative array
+   * that contains all of the elements that will be returned by the
+   * 'value' of this field. If the value field returns an element which
+   * is not defined here a warning will be generated.
+   *
+   * The array structure should contain at the top-level a key of the form
+   * {db}:{accession}. This represents the term that this field belongs to.
+   * The value of this top-level key is an array with the following keys:
+   *   -name: this key is not actually used but is availble to improve
+   *     readability of the array.  Because the key is a vocabulary term
+   *     conaining only the accession it's not always clear what it means.
+   *     Providing a 'name' key helps other's know what the term is.
+   *   -searchable:  TRUE if the element can be used for filtering the content
+   *     type to which tis field is attached.  FALSE if not.
+   *   -operations:  an array of filtering operations that can be used for this
+   *     field.  These include: 'eq', 'ne', 'contains', 'starts', 'gt', 'lt',
+   *     'gte', 'lte'.  These opertaions are applicable to strings: 'eq', 'ne',
+   *     'contains', and 'starts'.  These operations are applicable for numeric
+   *     values: 'gt', 'lt', 'gte', 'lte'.
+   *   -label: The label (if applicable) to appear for the elmeent. The default
+   *     is to use the term's name.
+   *   -help: Help text (if applicable) to appear for the element. The default
+   *     is to use the term's definition.
+   *   -type: The data type: e.g. 'string' or 'numeric'. Default is 'string'.
+   *   -sortable: TRUE if the element can be sorted.  FALSE if not.
+   *   -elements:  If this field value is a simple scalar (i.e. string or
+   *     number) then this key is not needed. But, if the 'value' of the
+   *     field is an array with sub keys then those subkeys must be defined
+   *     using this key.  The members of the element array follows the same
+   *     format as the top-level key and the above subkeys can be used as well.
+   *
+   * The following code provides an example for describing the value elements
+   * of this field.  The Tripal Chado module provides an obi__organism field
+   * that attaches organism details to content types such as genes, mRNA,
+   * stocks, etc.  It provides a label containing the full scientific name of
+   * the organism as well as the genus, species, infraspecific name,
+   * and infraspecific type. If the organism to which the field belong is
+   * published then an entity ID is provided.  The following array describes
+   * all of these.
+   * @code
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'contains', 'starts'),
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+        'elements' => array(
+          'rdfs:label' => array(
+            'searchable' => TRUE,
+            'name' => 'scientfic_name',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'TAXRANK:0000005' => array(
+            'searchable' => TRUE,
+            'name' => 'genus',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'TAXRANK:0000006' => array(
+            'searchable' => TRUE,
+            'name' => 'species',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'TAXRANK:0000045' => array(
+            'searchable' => TRUE,
+            'name' => 'infraspecies',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'local:infraspecific_type' => array(
+            'searchable' => TRUE,
+            'name' => 'infraspecific_type',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'entity' => array(
+            'searchable' => FALSE,
+          ),
+        ),
+      )
+    );
+   * @endcode
+   *
+   * If a field does not have a complex nested set of values, but simply returns
+   * a scalar then the default elementInfo provides default string-based
+   * searchabilty.
+   *
+   * @return
+   *   An associative array of the value elements provided by this field.
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'ne', 'contains', 'starts'),
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+      ),
+    );
+  }
+
   /**
    * Provides a form for the 'Field Settings' of the field management page.
    *
@@ -270,47 +611,6 @@ class TripalField {
 
   }
 
-  /**
-   * Describes this field to Views.
-   *
-   * An array of views data, in the same format as the return value of
-   * hook_views_data().
-   *
-   * @param $view_base_id
-   *   Views was originally designed to integrate with SQL tables. And
-   *   each field is associated with a table.  Because these are TripalFields
-   *   and views is not directly querying the tables it doesn't make sense to
-   *   associate fields with a table, but we must associate the fields with
-   *   the bundle.  Each bundle is uniquely identified with the $view_base_id
-   *   that is passed here.
-   *
-   * @return
-   *   An associative array describing the data structure. Primary key is the
-   *   name used internally by Views for the bundle that is provided by
-   *   the $view_base_id. The returned array should be compatible with the
-   *   instructions provided by the hook_views_data() function.
-   */
-  public function viewsData($view_base_id) {
-    $data = array();
-    $field_name = $this->field['field_name'];
-
-    $data[$view_base_id][$field_name] = array(
-      'title' => $this->instance['label'],
-      'help' => $this->instance['description'],
-      'field' => array(
-        'handler' => 'tripal_views_handler_field',
-        'click sortable' => TRUE,
-      ),
-      'filter' => array(
-        'handler' => 'tripal_views_handler_filter_string',
-      ),
-      'sort' => array(
-        'handler' => 'views_handler_sort',
-      )
-    );
-    return $data;
-  }
-
   /**
    * Provides a form for the 'Field Settings' of an instance of this field.
    *
@@ -371,18 +671,24 @@ class TripalField {
   /**
    * Used to filter records that match a given condition.
    *
-   * Entities can be filtered using the fields.  This function should be
-   * implemented if the field  supports filtering.  To provide filtering,
-   * the $query object should be updated to including any joins and
-   * conditions necessary. The following rules should be followed when
-   * implementing this function:
-   * - Any keys from the value array that support filtering should be set
-   *   in the $default_settings['searchable_keys'] array of this class file.
-   * - Implement support for every key in the
-   *   $default_settings['searchable_keys'] array.
-   * - However, avoid making filteres for non-indexed database columns.
-   * - This function should never set the fields that should be returned
-   *   nor ordering or group by.
+   * Records that belong to a content type can be filtered using the fields.
+   * This function should be implemented if the field  supports filtering as
+   * specified in the elementInfo() function.  With this function, the query
+   * object appropriate for the storage back-end is passed into the function.
+   *
+   * The condition array passesd in will have three values:
+   *   - column:  the key indicating how the filter should occur.
+   *   - op: the operation to perform (e.g. equals, contains, starts with etc.
+   *   - value:  the value for filtering.
+   *
+   * The column used for filtering will be a comma-speperated list of
+   * controlled vocabulary IDs. This comma-separate list corresponds directly
+   * to the heirarchy of elements provided by the elementInfo() function.
+   * For example, if a field provides organism information then it may use
+   * the OBI:0100026 term for the field, and the term TAXRANK:0000005 for the
+   * term to indicate the 'Genus'.  If these fields are properly organized in
+   * the elementInfo() function then the "column" of the condition when
+   * a user wants to search by genus will be: OBI:0100026,TAXRANK:0000005.
    *
    * @param $query
    *   A query object appropriate for the data storage backend. For example,

+ 6 - 8
tripal/includes/tripal.fields.inc

@@ -853,16 +853,14 @@ function tripal_field_is_empty($item, $field) {
     return TRUE;
   }
 
-  // Iterate through all of the fields and if at least one has a value
-  // the field is not empty.
-  foreach ($item as $form_field_name => $value) {
-    if (isset($value) and $value != NULL and $value != '') {
-      return FALSE;
-    }
+  // If there is a value field but there's nothing in it, the the field is
+  // empty.
+  if (array_key_exists('value', $item) and empty($item['value'])){
+    return TRUE;
   }
 
-  // Otherwise, the field is empty.
-  return TRUE;
+  // Otherwise, the field is not empty.
+  return FALSE;
 }
 
 /**

+ 1 - 0
tripal/tripal.info

@@ -23,6 +23,7 @@ files[] = views_handlers/tripal_views_handler_filter_element_string.inc
 files[] = views_handlers/tripal_views_handler_filter_boolean_operator.inc
 files[] = views_handlers/tripal_views_handler_filter_entity_string.inc
 files[] = views_handlers/tripal_views_handler_filter_string_selectbox.inc
+files[] = views_handlers/tripal_views_handler_sort.inc
 files[] = views_handlers/tripal_views_handler_sort_entity_string.inc
 
 files[] = tripal_views_query.inc

+ 51 - 20
tripal/tripal.views.inc

@@ -36,6 +36,53 @@ function tripal_views_data() {
   return $data;
 }
 
+/**
+ * Implements hook views_data_alter()
+ *
+ * Ensures that all fields attached to TripalEntities use the proper
+ * handlers.
+ */
+function tripal_views_data_alter(&$data) {
+
+  $fields = field_info_fields();
+  $tripal_fields = tripal_get_field_types();
+
+  // Iterate through all of the fields.
+  foreach ($fields as $field) {
+
+    // Skip fields that aren't attached to TripalEntity entities.
+    if (!array_key_exists('TripalEntity', $field['bundles'])) {
+      continue;
+    }
+
+    // Iterate through the bundles to which this field is attached and
+    // if it is a TripalField field then we'll call the viewsData function.
+    $bundles = $field['bundles']['TripalEntity'];
+    $result = array();
+    foreach ($bundles as $bundle_name) {
+      $field_name = $field['field_name'];
+
+      // Skip fields that aren't setup for views with this bundle.
+      // Fields should be associated with the bundle's term identifier
+      // (i.e. [vocab]__[accession].
+      $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));
+      $term = tripal_load_term_entity(array('term_id' => $bundle->term_id));
+      $bundle_term_id = $term->vocab->vocabulary . '__' . $term->accession;
+      if (!array_key_exists($bundle_term_id, $data)) {
+        continue;
+      }
+
+      // Skip fields implemented using a TripalField class. These fields
+      // should already have the proper handlers because the class sets
+      // them up automatically.
+      if (in_array($field_name, $tripal_fields)) {
+        continue;
+      }
+
+      $data[$bundle_term_id][$field_name]['sort']['handler'] = 'tripal_views_handler_sort';
+    }
+  }
+}
 /**
  * Integreates the Tripal fields with Views.
  */
@@ -50,6 +97,8 @@ function tripal_views_data_fields(&$data) {
       continue;
     }
 
+    // TODO: do we really need this hook? Just substitute the code here
+    // for what that hook does... it's only called in one plac.e
     // Call the hook_field_views_data() but only for the tripal module.
     // Otherwise the other modules will expect that this is an SQL-based
     // view.
@@ -120,7 +169,7 @@ function tripal_views_data_tripal_entity(&$data) {
         'handler' => 'tripal_views_handler_filter',
       ),
       'sort' => array(
-        'handler' => 'views_handler_sort',
+        'handler' => 'tripal_views_handler_sort',
       ),
     );
     $data[$table]['link'] = array(
@@ -129,12 +178,6 @@ function tripal_views_data_tripal_entity(&$data) {
       'field' => array(
         'handler' => 'tripal_views_handler_field_entity_link',
       ),
-      'filter' => array(
-        'handler' => 'tripal_views_handler_filter',
-      ),
-      'sort' => array(
-        'handler' => 'views_handler_sort',
-      ),
     );
     $data[$table]['edit_link'] = array(
       'title' => t('Edit Link'),
@@ -142,12 +185,6 @@ function tripal_views_data_tripal_entity(&$data) {
       'field' => array(
         'handler' => 'tripal_views_handler_field_entity_link_edit',
       ),
-      'filter' => array(
-        'handler' => 'tripal_views_handler_filter',
-      ),
-      'sort' => array(
-        'handler' => 'views_handler_sort',
-      ),
     );
     $data[$table]['delete_link'] = array(
       'title' => t('Delete Link'),
@@ -155,12 +192,6 @@ function tripal_views_data_tripal_entity(&$data) {
       'field' => array(
         'handler' => 'tripal_views_handler_field_entity_link_delete',
       ),
-      'filter' => array(
-        'handler' => 'tripal_views_handler_filter',
-      ),
-      'sort' => array(
-        'handler' => 'views_handler_sort',
-      ),
     );
     $data[$table]['status'] = array(
       'title' => t('Published'),
@@ -179,7 +210,7 @@ function tripal_views_data_tripal_entity(&$data) {
         'use equal' => TRUE, // Use status = 1 instead of status <> 0 in WHERE statment
       ),
       'sort' => array(
-        'handler' => 'views_handler_sort',
+        'handler' => 'tripal_views_handler_sort',
       ),
     );
   }

+ 110 - 30
tripal/tripal_views_query.inc

@@ -2,13 +2,34 @@
 
 class tripal_views_query extends views_plugin_query {
 
-  // The EntityFieldQuery object.
+  /**
+   * The EntityFieldQuery object used to perform the query
+   */
   var $query;
 
+  /**
+   * The EntityFieldQuery object used to perform the count query.
+   * It will not include the order by and only fields that are used in
+   * filters.
+   */
+  var $cquery;
+
+  /**
+   * The fields that are to be included in the query.
+   */
   var $fields;
 
+  /**
+   * The filters that are to be included in the query.
+   */
   var $filters;
 
+  /**
+   * The sort item that are to be included in the query.
+   */
+  var $order;
+
+
 
   /**
    * Ensure a table exists in the queue.
@@ -42,9 +63,12 @@ class tripal_views_query extends views_plugin_query {
     parent::init($base_table, $base_field, $options);
     $this->fields = array();
     $this->where = array();
+    $this->order = array();
 
     // Creqte the TripalFieldQuery object.
     $this->query = new TripalFieldQuery();
+    $this->cquery = new TripalFieldQuery();
+    $this->cquery->count();
 
     // Convert the $base_table into the bundle table.  Because every
     // tripal site will have different bundle tables we have to do the
@@ -56,6 +80,9 @@ class tripal_views_query extends views_plugin_query {
     // Make sure we only query on the entities for this bundle type.
     $this->query->entityCondition('entity_type', 'TripalEntity');
     $this->query->entityCondition('bundle', $bundle->name);
+
+    $this->cquery->entityCondition('entity_type', 'TripalEntity');
+    $this->cquery->entityCondition('bundle', $bundle->name);
   }
   /**
    *
@@ -100,40 +127,78 @@ class tripal_views_query extends views_plugin_query {
       // Handle the bundle properties separate from real fields.
       if ($field_name == 'entity_id' or $field_name == 'status') {
         $this->query->propertyCondition($field_name, $value, $operator);
+        $this->cquery->propertyCondition($field_name, $value, $operator);
+        return;
       }
-      else if ($field_name == 'link' or $field_name == 'edit_link' or $field_name == 'delete_link') {
-        // TODO: not sure how to handle these just yet.
+
+      // If the field_name comes to us with a period in it then it means that
+      // we need to separate the field name from sub-element names.
+      $matches = array();
+      if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
+         $field_name = $matches[1];
+         $element_name = $matches[2];
+         if (tripal_load_include_field_class($field_name)) {
+           $field = field_info_field($field_name);
+           $instance = field_info_instance('TripalEntity', $field_name, $this->query->entityConditions['bundle']['value']);
+           $field_obj = new $field_name($field, $instance);
+           $element_name = $field_obj->getFieldTermID() . ',' . $element_name;
+           // Replace periods with commas.
+           $element_name = preg_replace('/\./', ',', $element_name);
+           $this->query->fieldCondition($field_name, $element_name, $value, $operator);
+           $this->cquery->fieldCondition($field_name, $element_name, $value, $operator);
+         }
+         else {
+           throw new Exception("Unkown element id: '$element_name'.");
+         }
       }
       else {
-        // If the field_name comes to us with a period in it then it means that
-        // we need to separate the field name from sub-element names.
-        $matches = array();
-        if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
-           $field_name = $matches[1];
-           $element_name = $matches[2];
-           // Remove the double underscore from the $element_name and put
-           // back the colon
-           $element_name = preg_replace('/__/', ':', $element_name);
-          $this->query->fieldCondition($field_name, $element_name, $value, $operator);
-        }
-        else {
-          $this->query->fieldCondition($field_name, $field_name, $value, $operator);
-        }
+        $instance = field_info_instance('TripalEntity', $field_name, $this->query->entityConditions['bundle']['value']);
+        $field_term = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+        $this->query->fieldCondition($field_name, $field_term, $value, $operator);
+        $this->cquery->fieldCondition($field_name, $field_term, $value, $operator);
       }
     }
   }
 
-  public function add_orderby($table, $field = NULL, $order = 'ASC',
-      $alias = '', $params = array()) {
+  /**
+   * Overrides add_orderby().
+   */
+  public function add_orderby($table, $field_name = NULL, $order = 'ASC', $alias = '', $params = array()) {
 
-    $this->orderby[] = array(
-      'field' => $field,
-      'direction' => strtoupper($order)
-    );
+    if ($field_name) {
+      // Make sure we don't put the orderby in more than once.
+      foreach ($this->order as $index => $order_details) {
+        if ($order_details['field'] == $field_name) {
+          return;
+        }
+      }
+      $this->order[] = array(
+        'field' => $field_name,
+        'direction' => strtoupper($order)
+      );
 
+      // If the field_name comes to us with a period in it then it means that
+      // we need to separate the field name from sub-element names.
+      $matches = array();
+      if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
+        $field_name = $matches[1];
+        $element_name = $matches[2];
+        $field = field_info_field($field_name);
+        $instance = field_info_instance('TripalEntity', $field_name, $this->query->entityConditions['bundle']['value']);
+        $element_name = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession']  . ',' . $element_name;
+        $this->query->fieldOrderBy($field_name, $element_name, $order);
+
+      }
+      else {
+        $instance = field_info_instance('TripalEntity', $field_name, $this->query->entityConditions['bundle']['value']);
+        $field_term = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+        $this->query->fieldOrderBy($field_name, $field_term, $order);
+      }
+    }
   }
+
   /**
-   *
+   * Overrides build().
    */
   function build(&$view) {
     // Make the query distinct if the option was set.
@@ -149,9 +214,8 @@ class tripal_views_query extends views_plugin_query {
     // Let the pager modify the query to add limits.
     $this->pager->query();
 
-    $cquery = clone $this->query;
     $view->build_info['query'] = $this->query;
-    $view->build_info['count_query'] = $cquery->count();
+    $view->build_info['count_query'] = $this->cquery;
   }
   /**
    *
@@ -159,7 +223,7 @@ class tripal_views_query extends views_plugin_query {
    */
   function execute(&$view) {
     $query = $view->build_info['query'];
-    $count_query = $view->build_info['count_query'];
+    $cquery = $view->build_info['count_query'];
 
     if ($query) {
       $start = microtime(TRUE);
@@ -174,7 +238,8 @@ class tripal_views_query extends views_plugin_query {
           // really should create a new tripal_views_plugin_pager class
           // and call the corresponding function here, but due to time
           // constraints this is the shortcut.
-          $this->pager->total_items = $count_query->execute();
+          $total_items = $cquery->execute();
+          $this->pager->total_items = $total_items;
           if (!empty($this->pager->options['offset'])) {
             $this->pager->total_items -= $this->pager->options['offset'];
           };
@@ -191,11 +256,9 @@ class tripal_views_query extends views_plugin_query {
 
         // Get the IDs
         $results = $query->execute();
-
         $entity_ids = array_keys($results['TripalEntity']);
 
         $this->pager->post_execute($view->result);
-
         if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
           $view->total_rows = $this->pager->get_total_items();
         }
@@ -205,6 +268,13 @@ class tripal_views_query extends views_plugin_query {
         $field_ids = array();
         foreach ($this->fields as $details) {
           $field_name = $details['field_name'];
+          // If the field_name comes to us with a period in it then it means that
+          // we need to separate the field name from sub-element names.
+          $matches = array();
+          if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
+            $field_name = $matches[1];
+            $element_name = $matches[2];
+          }
           $field = field_info_field($field_name);
           if ($field) {
             $fields[$field_name] = $field;
@@ -212,6 +282,7 @@ class tripal_views_query extends views_plugin_query {
           }
         }
 
+        // Get the entity IDs from the query.
         $entities = tripal_load_entity('TripalEntity', $entity_ids, FALSE, $field_ids);
         $i = 0;
         foreach ($entities as $entity_id => $entity) {
@@ -242,6 +313,15 @@ class tripal_views_query extends views_plugin_query {
               $view->result[$i]->$field_name = $entity->status;
               continue;
             }
+
+            // If the field_name comes to us with a period in it then it means that
+            // we need to separate the field name from sub-element names.
+            $matches = array();
+            if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
+              $field_name = $matches[1];
+              $element_name = $matches[2];
+            }
+
             if (array_key_exists($field_name, $fields)) {
               $items = field_get_items('TripalEntity', $entity, $field_name);
               $view->result[$i]->$field_name = $items;

+ 12 - 0
tripal/views_handlers/tripal_views_handler_field.inc

@@ -22,6 +22,18 @@ class tripal_views_handler_field extends views_handler_field {
     $this->field_alias = $this->real_field;
   }
 
+  /**
+   * Overrides click_sort().
+   */
+  function click_sort($order) {
+    if (isset($this->field_alias)) {
+      // Since fields should always have themselves already added, just
+      // add a sort on the field.
+      $params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array();
+      $this->query->add_orderby($this->table_alias, $this->real_field, $order, $this->field_alias, $params);
+    }
+  }
+
   /**
    * Get the value that's supposed to be rendered.
    *

+ 57 - 5
tripal/views_handlers/tripal_views_handler_field_element.inc

@@ -33,7 +33,6 @@ class tripal_views_handler_field_element extends tripal_views_handler_field {
    *   Optional name of the field where the value is stored.
    */
   function get_value($values, $field = NULL) {
-
     $field_name = $this->field_alias;
 
     if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
@@ -62,12 +61,65 @@ class tripal_views_handler_field_element extends tripal_views_handler_field {
       $element_name = preg_replace('/__/', ':', $element_name);
     }
 
-    $value = $this->get_value($values);
+    // Get the items for this field.
+    $items = $this->get_value($values);
+
+    // Is this a nested element? If not make sure we have an array of element
+    // names to make it easier to deal with the name below.
+    $elements = explode('.', $element_name);
+    if (count($elements) == 0) {
+      $elements[] = $element_name;
+    }
 
     // Handle single value fields:
-    if (count($value == 1)) {
-      return $this->sanitize_value($value[0]['value'][$element_name], 'xss');
+    if (count($items) == 0) {
+      return '';
+    }
+    else if (count($items) == 1) {
+      $item = $items[0];
+      $element = array_shift($elements);
+      if (is_array($item['value'][$element])) {
+        return $this->_get_element_value($elements, $item['value'][$element]);
+      }
+      else {
+        return $this->sanitize_value($item['value'][$element_name], 'xss');
+      }
+    }
+    else if (count($items) <= 10) {
+      $element = array_shift($elements);
+      $element_values = array();
+      foreach ($items as $index => $item) {
+        if (is_array($item['value'][$element])) {
+          $element_values[] = $this->_get_element_value($elements, $item['value'][$element]);
+        }
+        else {
+          $element_values[] = $this->sanitize_value($item['value'][$element], 'xss');
+        }
+      }
+      // TODO: theming this way should probably be handled by a sepcial
+      // field handler that the user can tweak. But for now we'll just do this.
+      return theme_item_list(array(
+        'items' => $element_values,
+        'title' => NULL,
+        'type' => 'ul',
+        'attributes' => array(),
+      ));
+    }
+    else {
+      return t('Too many values to show.');
+    }
+  }
+
+  /**
+   * A recursive function for retrieving a nested element value.
+   */
+  private function _get_element_value($elements, $item_values) {
+    $element = array_shift($elements);
+    if (is_array($item_values[$element])) {
+      return $this->_get_element_value($elements, $item_values[$element]);
+    }
+    else {
+      return $this->sanitize_value($item_values[$element], 'xss');
     }
-    return '';
   }
 }

+ 21 - 0
tripal/views_handlers/tripal_views_handler_sort.inc

@@ -0,0 +1,21 @@
+<?php
+
+
+/**
+ * Base sort handler that has no options and performs a simple sort.
+ *
+ * @ingroup views_sort_handlers
+ */
+class tripal_views_handler_sort extends views_handler_sort {
+
+  /**
+   * Called to add the sort to a query.
+   */
+  function query() {
+    $field_name = $this->real_field;
+    $this->ensure_my_table();
+    // Add the field.
+    $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']);
+  }
+
+}

+ 0 - 10
tripal/views_handlers/tripal_views_handler_sort_entity_string.inc

@@ -1,15 +1,5 @@
 <?php
 
-/**
- * @file
- * @todo.
- */
-
-/**
- * @defgroup views_sort_handlers Views sort handlers
- * @{
- * Handlers to tell Views how to sort queries.
- */
 
 /**
  * Base sort handler that has no options and performs a simple sort.

+ 1 - 1
tripal_bulk_loader/includes/tripal_bulk_loader.admin.templates.inc

@@ -937,7 +937,7 @@ function tripal_bulk_loader_edit_template_record_form($form, &$form_state) {
       $select_if_duplicate = $form_state['storage']['template_array'][$form_state['storage']['original_priority']]['select_if_duplicate'];
     }
     else {
-      $select_if_duplicate = 1;
+      $select_if_duplicate = 0;
     }
 
     // get default for the update if duplicate

+ 1 - 1
tripal_chado/api/modules/tripal_chado.organism.api.inc

@@ -145,7 +145,7 @@ function tripal_get_organism_select_options($syncd_only = TRUE) {
   if ($syncd_only) {
     $sql = "
       SELECT *
-      FROM public.chado_organism CO
+      FROM {chado_organism} CO
         INNER JOIN {organism} O ON O.organism_id = CO.organism_id
       ORDER BY O.genus, O.species
     ";

+ 1 - 1
tripal_chado/api/modules/tripal_chado.pub.api.inc

@@ -809,7 +809,7 @@ function tripal_pub_create_citation($pub) {
   //----------------------
   // Review
   //----------------------
-  if ($pub_type == 'Review') {
+  else if ($pub_type == 'Review') {
     if (array_key_exists('Authors', $pub)) {
       $citation = $pub['Authors'] . '. ';
     }

+ 2 - 2
tripal_chado/api/tripal_chado.api.inc

@@ -70,14 +70,14 @@ function tripal_chado_publish_records($values, $job_id = NULL) {
   $select = "SELECT T.$pkey_field as record_id ";
   $from = "
     FROM {" . $table . "} T
-      LEFT JOIN public.$chado_entity_table CE on CE.record_id = T.$pkey_field
+      LEFT JOIN {" . $chado_entity_table . "} CE on CE.record_id = T.$pkey_field
   ";
 
   // For migration of Tripal v2 nodes to entities we want to include the
   // coresponding chado linker table.
   if ($sync_node && db_table_exists('chado_' . $table)) {
     $select = "SELECT T.$pkey_field as record_id, CT.nid ";
-    $from .= " INNER JOIN public.chado_$table CT ON CT.$pkey_field = T.$pkey_field";
+    $from .= " INNER JOIN {chado_" . $table . "} CT ON CT.$pkey_field = T.$pkey_field";
   }
   $where = " WHERE CE.record_id IS NULL ";
 

+ 33 - 7
tripal_chado/api/tripal_chado.query.api.inc

@@ -1580,10 +1580,12 @@ function chado_select_record_check_value_type(&$op, &$value, $type) {
 }
 
 /**
- * Use this function instead of db_query() to avoid switching databases
- * when making query to the chado database
+ * A substitute for db_query() when querying from Chado.
  *
- * Will use a chado persistent connection if it already exists
+ * This function is needed to avoid switching databases when making query to
+ * the chado database.
+ *
+ * Will use a chado persistent connection if it already exists.
  *
  * @param $sql
  *   The sql statement to execute
@@ -1625,11 +1627,20 @@ function chado_query($sql, $args = array()) {
   // if Chado is local to the database then prefix the Chado table
   // names with 'chado'.
   if ($is_local) {
-    $sql = preg_replace('/\n/', '', $sql);  // remove carriage returns
+    // Remove carriage returns from the SQL.
+    $sql = preg_replace('/\n/', '', $sql);
+
+    // Prefix the tables with their correct schema.
+    // Chado tables should be enclosed in curly brackets (ie: {feature} )
+    // and Drupal tables should be enclosed in square brackets
+    // (ie: [tripal_jobs] ).
+    // @todo: remove assumption that the chado schema is called 'chado' and the
+    // drupal schema is called 'public'.
     $sql = preg_replace('/\{(.*?)\}/', 'chado.$1', $sql);
+    $sql = preg_replace('/\[(\w+)\]/', 'public.$1', $sql);
 
-    // the featureloc table has some indexes that use function that call other functions
-    // and those calls do not reference a schema, therefore, any tables with featureloc
+    // The featureloc table has some indexes that use function that call other
+    // functions and those calls do not reference a schema, therefore, any tables with featureloc
     // must automaticaly have the chado schema set as active to find
     if (preg_match('/chado.featureloc/i', $sql) or preg_match('/chado.feature/i', $sql)) {
       $previous_db = chado_set_active('chado') ;
@@ -1648,6 +1659,16 @@ function chado_query($sql, $args = array()) {
       $results = db_query($sql, $args);
     }
   }
+  // Check for any cross schema joins (ie: both drupal and chado tables
+  // represented and if present don't execute the query but instead warn the
+  // administrator.
+  else if (preg_match('/\[(\w*?)\]/', $sql)) {
+    tripal_report_error('chado_query', TRIPAL_ERROR,
+      'The following query does not support external chado databases. Please file an issue with the Drupal.org Tripal Project. Query: @query',
+       array('@query' => $sql)
+    );
+    return FALSE;
+  }
   // if Chado is not local to the Drupal database then we have to
   // switch to another database
   else {
@@ -1683,7 +1704,12 @@ function chado_query($sql, $args = array()) {
  */
 function chado_pager_query($query, $args, $limit, $element, $count_query = '') {
   // get the page and offset for the pager
-  $page = isset($_GET['page']) ? $_GET['page'] : '0';
+  $page_arg = isset($_GET['page']) ? $_GET['page'] : '0';
+  $pages = explode(',', $page_arg);
+  $page = 0;
+  if (count($pages) >= $element) {
+     $page = key_exists($element, $pages) ? $pages[$element] : 0;
+  }
   $offset = $limit * $page;
   $q = $_GET['q'];
 

+ 12 - 8
tripal_chado/api/tripal_chado.schema.api.inc

@@ -633,6 +633,7 @@ function chado_get_cvterm_mapping($params) {
   $cvterm_id = array_key_exists('cvterm_id', $params) ? $params['cvterm_id'] : NULL;
   $vocabulary = array_key_exists('vocabulary', $params) ? $params['vocabulary'] : NULL;
   $accession = array_key_exists('accession', $params) ? $params['accession'] : NULL;
+  $cvterm = NULL;
 
   if ($cvterm_id) {
     $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
@@ -649,13 +650,16 @@ function chado_get_cvterm_mapping($params) {
     $cvterm = chado_generate_var('cvterm', $match);
   }
 
-  $result = db_select('chado_cvterm_mapping', 'tcm')
-    ->fields('tcm')
-    ->condition('cvterm_id', $cvterm->cvterm_id)
-    ->execute();
-  $result = $result->fetchObject();
-  if ($result) {
-    $result->cvterm = $cvterm;
+  if ($cvterm) {
+    $result = db_select('chado_cvterm_mapping', 'tcm')
+      ->fields('tcm')
+      ->condition('cvterm_id', $cvterm->cvterm_id)
+      ->execute();
+    $result = $result->fetchObject();
+    if ($result) {
+      $result->cvterm = $cvterm;
+    }
+    return $result;
   }
-  return $result;
+  return NULL;
 }

+ 35 - 23
tripal_chado/includes/TripalFields/ChadoField.inc

@@ -6,8 +6,7 @@ class ChadoField extends TripalField {
   public static $default_label = 'Chado Field';
 
   // The default description for this field.
-  public static $default_description = 'The generic base class for all Chado fields. ' .
-    'Replace this text as appropriate for the child implementation.';
+  public static $default_description = 'The generic base class for all Chado fields. Replace this text as appropriate for the child implementation.';
 
   // A list of global settings. These can be accessed witihn the
   // globalSettingsForm.  When the globalSettingsForm is submitted then
@@ -88,6 +87,40 @@ class ChadoField extends TripalField {
 
   }
 
+  /**
+   * A convient way to join a table to a query without duplicates.
+   *
+   * @param $query
+   *   The SelectQuery object.
+   * @param $table
+   *   The table to join.
+   * @param $alias
+   *   The table alias to use.
+   * @param $condition
+   *   The join condition.
+   * @param $type
+   *   The type of join: INNER, LEFT OUTER, or RIGHT OUTER.
+   */
+  protected function queryJoinOnce($query, $table, $alias, $condition, $type = 'INNER') {
+    $joins = $query->getTables();
+
+    // If this join is already present then don't add it again.
+    if (in_array($alias, array_keys($joins))) {
+      return;
+    }
+
+    switch($type) {
+      case 'LEFT OUTER':
+        $query->leftjoin($table, $alias, $condition);
+        break;
+      case 'RIGHT OUTER':
+        $query->rightjoin($table, $alias, $condition);
+        break;
+      default:
+        $query->innerjoin($table, $alias, $condition);
+    }
+  }
+
   /**
    * @see TripalField::instanceSettingsForm()
    */
@@ -109,25 +142,4 @@ class ChadoField extends TripalField {
     );
     return $element;
   }
-
-  /**
-   * @see TripalField::viewsData()
-   */
-  public function viewsData($view_base_id) {
-    $data = array();
-    $field_name = $this->field['field_name'];
-
-    $data[$view_base_id][$field_name] = array(
-      'title' => $this->instance['label'],
-      'help' => $this->instance['description'],
-      // By default our Chado fields are complicated and we only want to support
-      // viewing them, and not filtering by them.  Each field cand override as
-      // needed.
-      'field' => array(
-        'handler' => 'tripal_views_handler_field',
-        'click sortable' => FALSE,
-      )
-    );
-    return $data;
-  }
 }

+ 64 - 24
tripal_chado/includes/TripalFields/chado_linker__contact/chado_linker__contact.inc

@@ -57,7 +57,54 @@ class chado_linker__contact extends ChadoField {
   protected $instance;
 
 
-
+  /**
+   * @see TripalField::elements()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+
+    $type_term = tripal_get_chado_semweb_term('contact', 'type_id');
+    $name_term = tripal_get_chado_semweb_term('contact', 'name');
+    $description_term = tripal_get_chado_semweb_term('contact', 'description');
+
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'contains', 'starts'),
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+        'type' => 'string',
+        'elements' => array(
+          $type_term => array(
+            'searchable' => TRUE,
+            'name' => 'type',
+            'label' => 'Contact Type',
+            'help' => 'The type of contact',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          $name_term => array(
+            'searchable' => TRUE,
+            'name' => 'name',
+            'label' => 'Contact Name',
+            'help' => 'The name of the contact.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          $description_term => array(
+            'searchable' => TRUE,
+            'name' => 'description',
+            'label' => 'Contact Description',
+            'help' => 'A descriptoin of the contact.',
+            'operations' => array('contains'),
+            'sortable' => FALSE,
+          ),
+          'entity' => array(
+            'searchable' => FALSE,
+          ),
+        ),
+      )
+    );
+  }
   /**
    *
    * @see TripalField::load()
@@ -70,6 +117,10 @@ class chado_linker__contact extends ChadoField {
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
+    $type_term = tripal_get_chado_semweb_term('contact', 'type');
+    $name_term = tripal_get_chado_semweb_term('contact', 'name');
+    $description_term = tripal_get_chado_semweb_term('contact', 'description');
+
     // Get the FK that links to the base record.
     $schema = chado_get_schema($field_table);
     $base_table = $entity->chado_table;
@@ -111,27 +162,12 @@ class chado_linker__contact extends ChadoField {
         $contact = $contact_linker->contact_id;
         $entity->{$field_name}['und'][$i] = array(
           'value' => array(
-            'type' => $contact->type_id ? $contact->type_id->name : '',
-            'name' => $contact->name,
-            'description' => $contact->description,
-          ),
-          // Add in the semantic web settings.  This array is expected by
-          // other Tripal modules that handle semantic web for fields.
-          'semantic_web' => array(
-            'type' => $contact->type_id ? $contact->type_id->dbxref_id->db_id->name . ':' . $contact->type_id->dbxref_id->accession : '',
-            'name' => tripal_get_chado_semweb_term('contact', 'name'),
-            'description' => tripal_get_chado_semweb_term('contact', 'description'),
-          ),
-          // Add in subfield mapping to Chado tables. This is used by the
-          // chado_field_storage for performing queries on sub element values.
-          // It should be a comma-separated list (no spacing) of the field names
-          // as foreign keys are followed starting from the Chado table to which
-          // this field maps.
-          'chado_mapping' => array(
-            'type' => 'type_id,name',
-            'name' => 'contact_id,name',
-            'description' => 'contact_id,name'
+            $type_term => $contact->type_id ? $contact->type_id->name : '',
+            $name_term => $contact->name,
+            $description_term => $contact->description,
           ),
+          // Add elements that are not part of the values but used by
+          // the chado storage back-end for saving values.
           'chado-' . $field_table . '__' . $pkey => $contact_linker->$pkey,
           'chado-' . $field_table . '__' . $fkey_lcolumn => $contact_linker->$fkey_lcolumn->$fkey_lcolumn,
           'chado-' . $field_table . '__' . 'contact_id' => $contact->contact_id
@@ -155,9 +191,13 @@ class chado_linker__contact extends ChadoField {
     $alias = 'contact_linker';
     $operator = $condition['operator'];
 
-    if ($condition['column'] == 'contact.name') {
-      $query->join($contact_linker, $alias, "base.$bpkey = $alias.$bpkey");
-      $query->join('contact', 'C', "C.contact_id = $alias.contact_id");
+    $type_term = tripal_get_chado_semweb_term('contact', 'type');
+    $name_term = tripal_get_chado_semweb_term('contact', 'name');
+    $description_term = tripal_get_chado_semweb_term('contact', 'description');
+
+    if ($condition['column'] == $name_term) {
+      $this->queryJoinOnce($query, $contact_linker, $alias, "base.$bpkey = $alias.$bpkey");
+      $this->queryJoinOnce($query, 'contact', 'C', "C.contact_id = $alias.contact_id");
       $query->condition("C.name", $condition['value'], $operator);
     }
     if ($condition['column'] == 'contact.type') {

+ 1 - 0
tripal_chado/includes/TripalFields/chado_linker__cvterm/chado_linker__cvterm.inc

@@ -51,6 +51,7 @@ class chado_linker__cvterm extends ChadoField {
   public static $no_ui = FALSE;
 
 
+
   /**
    *
    * @see TripalField::load()

+ 1 - 1
tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop.inc

@@ -140,7 +140,7 @@ class chado_linker__prop extends ChadoField {
 
     $cvterm = tripal_get_cvterm(array('id' => $vocab . ':' . $accession));
 
-    $query->join($prop_linker, $alias, "base.$bpkey = $alias.$bpkey");
+    $this->queryJoinOnce($query, $prop_linker, $alias, "base.$bpkey = $alias.$bpkey");
     $query->condition("$alias.type_id", $cvterm->cvterm_id);
     $query->condition("$alias.value", $condition['value'], $operator);
 

+ 17 - 29
tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop_formatter.inc

@@ -24,40 +24,28 @@ class chado_linker__prop_formatter extends ChadoFieldFormatter {
     $field_name = $this->field['field_name'];
     $chado_table = $this->instance['settings']['chado_table'];
 
-    $content = array();
-    // If $items is not empty and it has more than one value all values need to
-    // be added to an array for display.
-    if ($items) {
-      if (count($items) > 1) {
-        foreach ($items as $index => $item) {
-          $content[$index] = $item['value'];
-        }
-      }
-      else {
-        $content = $items[0]['value'];
-      }
+    $list = array();
+    foreach ($items as $index => $item) {
+      $list[$index] = $item['value'];
     }
+
     // If more than one value has been found display all values in an unordered
     // list.
-    if (count($content) > 1) {
-      $bullets = '<ul>';
-      $bullets .= '<li>';
-      $bullets .= implode("</li><li>", $content);
-      $bullets .= '</li>';
-      $bullets .= '</ul>';
-      $element[0] = array(
-        // We create a render array to produce the desired markup,
-        '#type' => 'markup',
-        '#markup' => $bullets,
-      );
+    if (count($list) > 1) {
+      $content = theme_item_list(array(
+        'items' => $list,
+        'title' => '',
+        'attributes' => array('class' => array($entity->bundle . '-properties-list', 'properties-field-list')),
+        'type' => 'ul'
+      ));
     }
-    // If it's a single value field display without bullets.
     else {
-      $element[0] = array(
-        // We create a render array to produce the desired markup,
-        '#type' => 'markup',
-        '#markup' => $content,
-      );
+      $content = $list[0];
     }
+    $element[0] = array(
+      // We create a render array to produce the desired markup,
+      '#type' => 'markup',
+      '#markup' => $content,
+    );
   }
 }

+ 3 - 23
tripal_chado/includes/TripalFields/data__accession/data__accession.inc

@@ -36,28 +36,6 @@ class data__accession extends ChadoField {
     // to allow the site admin to change the term settings above.
     'term_fixed' => FALSE,
   );
-
-  // In order for this field to integrate with Drupal Views, a set of
-  // handlers must be specififed.  These include handlers for
-  // the field, for the filter, and the sort.  Within this variable,
-  // the key must be one of: field, filter, sort; and the value
-  // is the settings for those handlers as would be provided by
-  // a hook_views_data().  The following defaults make a field visible
-  // using the default formatter of the field, allow for filtering using
-  // a string value and sortable.  in order for filters to work you
-  // must implement the query() function.
-  public static $default_view_handlers = array(
-    'field' => array(
-      'handler' => 'tripal_views_handler_field',
-      'click sortable' => TRUE,
-    ),
-    'filter' => array(
-      'handler' => 'tripal_views_handler_filter_string',
-    ),
-    'sort' => array(
-      'handler' => 'views_handler_sort',
-    ),
-  );
   // The default widget for this field.
   public static $default_widget = 'data__accession_widget';
 
@@ -114,8 +92,10 @@ class data__accession extends ChadoField {
     $alias = 'dbx_linker';
     $operator = $condition['operator'];
 
+    // We don't offer any sub elements so the value coming in should
+    // always be the field_name.
     if ($condition['column'] == 'data__accession') {
-      $query->join('dbxref', 'DBX', "DBX.dbxref_id = base.dbxref_id");
+      $this->queryJoinOnce($query, 'dbxref', 'DBX', "DBX.dbxref_id = base.dbxref_id");
       $query->condition("DBX.accession", $condition['value'], $operator);
     }
   }

+ 13 - 0
tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence.inc

@@ -44,6 +44,19 @@ class data__protein_sequence extends ChadoField {
   public static $default_formatter = 'data__protein_sequence_formatter';
 
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+      ),
+    );
+  }
   /**
    * @see TripalField::load()
    */

+ 15 - 0
tripal_chado/includes/TripalFields/data__sequence/data__sequence.inc

@@ -43,6 +43,21 @@ class data__sequence extends ChadoField {
   // The default formatter for this field.
   public static $default_formatter = 'data__sequence_formatter';
 
+
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+      ),
+    );
+  }
+
   /**
    * @see TripalField::load()
    */

+ 13 - 0
tripal_chado/includes/TripalFields/data__sequence_checksum/data__sequence_checksum.inc

@@ -54,5 +54,18 @@ class data__sequence_checksum extends ChadoField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+      ),
+    );
+  }
 
 }

+ 60 - 0
tripal_chado/includes/TripalFields/data__sequence_coordinates/data__sequence_coordinates.inc

@@ -55,6 +55,66 @@ class data__sequence_coordinates extends ChadoField {
   protected $instance;
 
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'elements' => array(
+          'data:3002' => array(
+            'searchable' => TRUE,
+            'name' => 'source_feature',
+            'label' => 'Location Reference Name',
+            'help' => 'The genomic feature on which this feature is localized.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'local:fmin' => array(
+            'searchable' => TRUE,
+            'name' => 'feature min',
+            'label' => 'Location Start Position',
+            'help' => 'The start position',
+            'type' => 'numeric',
+            'operations' => array('eq', 'gt', 'lt', 'gte' ,'lte'),
+            'sortable' => TRUE,
+          ),
+          'local:fmax' => array(
+            'searchable' => TRUE,
+            'name' => 'feature max',
+            'label' => 'Location End Position',
+            'help' => 'The end position',
+            'type' => 'numeric',
+            'operations' => array('eq', 'gt', 'lt', 'gte' ,'lte'),
+            'sortable' => TRUE,
+          ),
+          'data:2336' => array(
+            'searchable' => TRUE,
+            'name' => 'phase',
+            'type' => 'numeric',
+            'label' => 'Location Phase',
+            'help' => 'The phase of the feature (applicable only to coding sequences).',
+            'operations' => array('eq', 'gt', 'lt', 'gte' ,'lte'),
+            'sortable' => TRUE,
+          ),
+          'data:0853' => array(
+            'searchable' => TRUE,
+            'name' => 'strand',
+            'type' => 'numeric',
+            'label' => 'Location Strand',
+            'help' => 'The orientation of this feature where it is localized',
+            'operations' => array('eq', 'gt', 'lt', 'gte' ,'lte'),
+            'sortable' => TRUE,
+          ),
+        ),
+      ),
+    );
+  }
+
   /**
    *
    * @see TripalField::load()

+ 15 - 0
tripal_chado/includes/TripalFields/data__sequence_length/data__sequence_length.inc

@@ -55,4 +55,19 @@ class data__sequence_length extends ChadoField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'gt', 'lt', 'gte', 'lte'),
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+        'type' => 'numeric',
+      ),
+    );
+  }
+
 }

+ 14 - 0
tripal_chado/includes/TripalFields/go__gene_expression/go__gene_expression.inc

@@ -60,6 +60,20 @@ class go__gene_expression extends ChadoField {
   protected $instance;
 
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+      ),
+    );
+  }
+
   /**
    *
    * @see TripalField::load()

+ 52 - 6
tripal_chado/includes/TripalFields/local__source_data/local__source_data.inc

@@ -55,6 +55,51 @@ class local__source_data extends ChadoField {
   protected $instance;
 
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+
+    $sourcename_term = tripal_get_chado_semweb_term('analysis', 'sourcename');
+    $sourceversion_term = tripal_get_chado_semweb_term('analysis', 'sourceversion');
+    $sourceuri_term = tripal_get_chado_semweb_term('analysis', 'sourceuri');
+
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'elements' => array(
+          $sourcename_term => array(
+            'searchable' => TRUE,
+            'name' => 'sourcename',
+            'label' => 'Data Source Name',
+            'help' => 'The name of the data source used for the analysis.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          $sourceversion_term => array(
+            'searchable' => FALSE,
+            'name' => 'sourceversion',
+            'label' => 'Data Source Version',
+            'help' => 'If applicable, the version number of the source data used for the analysis.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          $sourceuri_term => array(
+            'searchable' => FALSE,
+            'name' => 'sourceuri',
+            'label' => 'Data Source URI',
+            'help' => 'If applicable, the universal resource indicator (e.g. URL) of the source data used for the analysis.',
+            'operations' => array(),
+            'sortable' => FALSE,
+          ),
+        ),
+      ),
+    );
+  }
+
   /**
    *
    * @see TripalField::load()
@@ -66,14 +111,15 @@ class local__source_data extends ChadoField {
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
+    $sourcename_term = tripal_get_chado_semweb_term('analysis', 'sourcename');
+    $sourceversion_term = tripal_get_chado_semweb_term('analysis', 'sourceversion');
+    $sourceuri_term = tripal_get_chado_semweb_term('analysis', 'sourceuri');
+
     $entity->{$field_name}['und'][0] = array(
       'value' => array(
-        // The name of the data source.
-        'schema:name' => $analysis->sourcename,
-        // The version of the data source.
-        'IAO:0000129' => $analysis->sourceversion,
-        // The URI of the data source.
-        'data:1047' => $analysis->sourceuri,
+        $sourcename_term => $analysis->sourcename,
+        $sourceversion_term => $analysis->sourceversion,
+        $sourceuri_term => $analysis->sourceuri,
       ),
       'chado-analysis__sourcename' => $analysis->sourcename,
       'chado-analysis__sourceversion' => $analysis->sourceversion,

+ 86 - 122
tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc

@@ -78,7 +78,7 @@ class obi__organism extends ChadoField {
       if (!$organism_id or $organism_id == 0) {
         $errors[$field_name]['und'][0][] = array(
           'message' =>  t("Please specify an organism."),
-          'error' => 'chado_base__organism_id'
+          'error' => 'obi__organism_id'
         );
       }
     }
@@ -210,108 +210,59 @@ class obi__organism extends ChadoField {
   }
 
   /**
-   * @see ChadoField::viewsData()
+   * @see TripalField::elementInfo()
    */
-  public function viewsData($view_base_id) {
-    $data = array();
-    $options = array('return_object' => TRUE);
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
 
-    // Add a views field for the field.
-    $field_name = $this->field['field_name'];
-    $data[$view_base_id][$field_name] = array(
-      'title' => $this->instance['label'],
-      'help' => $this->instance['description'],
-      'field' => array(
-        'handler' => 'tripal_views_handler_field',
-        'click sortable' => TRUE,
-      ),
-      'filter' => array(
-        'handler' => 'tripal_views_handler_filter_string',
-      ),
-      'sort' => array(
-        'handler' => 'views_handler_sort',
-      )
-    );
-
-    // Add a views field for the genus.
-    $cvterm = tripal_get_chado_semweb_term('organism', 'genus', $options);
-    $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
-    $element_name = tripal_format_views_field_element($field_name, $term);
-    $data[$view_base_id][$element_name] = array(
-      'title' => ucfirst($term['name']),
-      'help' => $term['definition'],
-      'field' => array(
-        'handler' => 'tripal_views_handler_field_element',
-        'click sortable' => TRUE,
-      ),
-      'filter' => array(
-        'handler' => 'tripal_views_handler_filter_element_string',
-      ),
-      'sort' => array(
-        'handler' => 'views_handler_sort',
-      )
-    );
+    $genus_term = tripal_get_chado_semweb_term('organism', 'genus');
+    $species_term = tripal_get_chado_semweb_term('organism', 'species');
+    $infraspecific_name_term = tripal_get_chado_semweb_term('organism', 'infraspecific_name');
+    $infraspecific_type_term = tripal_get_chado_semweb_term('organism', 'type_id');
 
-    // Add a views field for the species.
-    $cvterm = tripal_get_chado_semweb_term('organism', 'species', $options);
-    $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
-    $element_name = tripal_format_views_field_element($field_name, $term);
-    $data[$view_base_id][$element_name] = array(
-      'title' => ucfirst($term['name']),
-      'help' => $term['definition'],
-      'field' => array(
-        'handler' => 'tripal_views_handler_field_element',
-        'click sortable' => TRUE,
-      ),
-      'filter' => array(
-        'handler' => 'tripal_views_handler_filter_element_string',
-      ),
-      'sort' => array(
-        'handler' => 'views_handler_sort',
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'contains', 'starts'),
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+        'type' => 'string',
+        'elements' => array(
+          'rdfs:label' => array(
+            'searchable' => TRUE,
+            'name' => 'scientfic_name',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          $genus_term => array(
+            'searchable' => TRUE,
+            'name' => 'genus',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          $species_term => array(
+            'searchable' => TRUE,
+            'name' => 'species',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          $infraspecific_name_term => array(
+            'searchable' => TRUE,
+            'name' => 'infraspecies',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          $infraspecific_type_term => array(
+            'searchable' => TRUE,
+            'name' => 'infraspecific_type',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'entity' => array(
+            'searchable' => FALSE,
+          ),
+        ),
       )
     );
-
-    // Suppor the Chado v1.3 fields.
-    if(chado_get_version() >= '1.3') {
-      // Add a views field for the infraspecific name.
-      $cvterm = tripal_get_chado_semweb_term('organism', 'infraspecific_name', $options);
-      $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
-      $element_name = tripal_format_views_field_element($field_name, $term);
-      $data[$view_base_id][$element_name] = array(
-        'title' => ucfirst($term['name']),
-        'help' => $term['definition'],
-        'field' => array(
-          'handler' => 'tripal_views_handler_field_element',
-          'click sortable' => TRUE,
-        ),
-        'filter' => array(
-          'handler' => 'tripal_views_handler_filter_element_string',
-        ),
-        'sort' => array(
-          'handler' => 'views_handler_sort',
-        )
-      );
-
-      // Add a views field for the infraspecific type.
-      $cvterm = tripal_get_chado_semweb_term('organism', 'type_id', $options);
-      $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
-      $element_name = tripal_format_views_field_element($field_name, $term);
-      $data[$view_base_id][$element_name] = array(
-        'title' => ucfirst($term['name']),
-        'help' => $term['definition'],
-        'field' => array(
-          'handler' => 'tripal_views_handler_field_element',
-          'click sortable' => TRUE,
-        ),
-        'filter' => array(
-          'handler' => 'tripal_views_handler_filter_element_string',
-        ),
-        'sort' => array(
-          'handler' => 'views_handler_sort',
-        )
-      );
-    }
-    return $data;
   }
 
   /**
@@ -321,16 +272,26 @@ class obi__organism extends ChadoField {
     $alias = $this->field['field_name'];
     $operator = $condition['operator'];
 
-    $genus_term = tripal_get_chado_semweb_term('organism', 'genus');
-    $species_term = tripal_get_chado_semweb_term('organism', 'species');
-    $infraspecific_name_term = tripal_get_chado_semweb_term('organism', 'infraspecific_name');
-    $infraspecific_type_term = tripal_get_chado_semweb_term('organism', 'type_id');
-
-    $query->join('organism', $alias, "base.organism_id = $alias.organism_id");
-
-    // If the column is the field name.
-    if ($condition['column'] == 'obi__organism') {
-      $query->where("CONCAT($alias.genus, ' ', $alias.species) $operator :full_name",  array(':full_name' => $condition['value']));
+    $field_term_id = $this->getFieldTermID();
+    $genus_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'genus');
+    $species_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'species');
+    $infraspecific_name_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'infraspecific_name');
+    $infraspecific_type_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'type_id');
+
+    // Join to the organism table for this field.
+    $this->queryJoinOnce($query, 'organism', $alias, "base.organism_id = $alias.organism_id");
+
+    // If the column is the field name then we're during a search on the full
+    // scientific name.
+    if ($condition['column'] == $field_term_id or
+        $condition['column'] == $field_term_id . ',rdfs:label') {
+      if (chado_get_version() <= 1.3) {
+        $query->where("CONCAT($alias.genus, ' ', $alias.species) $operator :full_name",  array(':full_name' => $condition['value']));
+      }
+      else {
+        $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', 'base.infraspecific_type = ' . $alias . '_cvterm.type_id', 'LEFT OUTER');
+        $query->where("CONCAT($alias.genus, ' ', $alias.species, ' ', " . $alias . "'_cvterm.name', ' ', $alias.infraspecific_name) $operator :full_name",  array(':full_name' => $condition['value']));
+      }
     }
 
     // If the column is a subfield.
@@ -344,7 +305,7 @@ class obi__organism extends ChadoField {
       $query->condition("$alias.infraspecific_name", $condition['value'], $operator);
     }
     if ($condition['column'] == $infraspecific_type_term) {
-      $query->join('cvterm', 'CVT', "base.type_id = CVT.cvterm_id");
+      $this->queryJoinOnce($query, 'cvterm', 'CVT', "base.type_id = CVT.cvterm_id");
       $query->condition("CVT.name", $condition['value'], $operator);
     }
   }
@@ -353,27 +314,30 @@ class obi__organism extends ChadoField {
    * @see ChadoField::queryOrder()
    */
   public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
 
-    // If the table hasn't yet been joined then add it.
-    $joins = $query->getTables();
-    if (!in_array($this->field['field_name'], $joins)) {
-      $alias = $this->field['field_name'];
-      $query->join('organism', $alias, "base.organism_id = $alias.organism_id");
-    }
+    $field_term_id = $this->getFieldTermID();
+    $genus_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'genus');
+    $species_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'species');
+    $infraspecific_name_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'infraspecific_name');
+    $infraspecific_type_term = $field_term_id . ',' . tripal_get_chado_semweb_term('organism', 'type_id');
+
+    // Join to the organism table for this field.
+    $this->queryJoinOnce($query, 'organism', $alias, "base.organism_id = $alias.organism_id");
 
     // Now perform the sort.
-    if ($order['column'] == 'organism.species') {
-      $query->orderBy("$alias.genus", $order['direction']);
-    }
-    if ($order['column'] == 'organism.genus') {
+    if ($order['column'] == $species_term) {
       $query->orderBy("$alias.species", $order['direction']);
     }
-    if ($order['column'] == 'organism.infraspecies') {
+    if ($order['column'] == $genus_term) {
+      $query->orderBy("$alias.genus", $order['direction']);
+    }
+    if ($order['column'] == $infraspecific_name_term) {
       $query->orderBy("$alias.infraspecific_name", $order['direction']);
     }
-    if ($order['column'] == 'organism.infraspecies') {
+    if ($order['column'] == $infraspecific_type_term) {
       if (!in_array('CVT', $joins)) {
-        $query->join('cvterm', 'CVT', "base.type_id = CVT.cvterm_id");
+        $this->queryJoinOnce($query, 'cvterm', 'CVT', "base.type_id = CVT.cvterm_id");
       }
       $query->orderBy("CVT.name", $order['direction']);
     }

+ 61 - 4
tripal_chado/includes/TripalFields/ogi__location_on_map/ogi__location_on_map.inc

@@ -54,7 +54,60 @@ class ogi__location_on_map extends ChadoField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
 
-
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+
+    $name_term = tripal_get_chado_semweb_term('featuremap', 'name');
+    $description_term = tripal_get_chado_semweb_term('featuremap', 'description');
+    $mappos_term = tripal_get_chado_semweb_term('featurepos', 'mappos');
+
+    return array(
+      $field_term => array(
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'elements' => array(
+          'data:1274' => array(
+            'name' => 'Map',
+            'searchable' => FALSE,
+            'sortable' => FALSE,
+            'elements' => array(
+              $name_term => array(
+                'name' => 'map_name',
+                'label' => 'Map Name',
+                'help' => 'The name of the map.',
+                'searchable' => TRUE,
+                'operations' => array('eq', 'ne', 'contains', 'starts'),
+                'sortable' => FALSE,
+              ),
+              $description_term => array(
+                'name' => 'map_description',
+                'label' => 'Map Description',
+                'help' => 'A description of the map.',
+                'searchable' => TRUE,
+                'operations' => array('eq', 'ne', 'contains', 'starts'),
+                'sortable' => FALSE,
+              ),
+            ),
+          ),
+          $mappos_term => array(
+            'name' => 'map_position_type',
+            'label' => 'Map Position Type',
+            'help' => 'Maps may use different coordinate systems. This indicates the type of coordinate.',
+            'searchable' => TRUE,
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          'entity' => array(
+            'searchable' => FALSE,
+            'sortable' => FALSE,
+          ),
+        ),
+      ),
+    );
+  }
   /**
    *
    * @see TripalField::load()
@@ -66,6 +119,10 @@ class ogi__location_on_map extends ChadoField {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
 
+    $name_term = tripal_get_chado_semweb_term('featuremap', 'name');
+    $description_term = tripal_get_chado_semweb_term('featuremap', 'description');
+    $mappos_term = tripal_get_chado_semweb_term('featurepos', 'mappos');
+
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
       'value' => array(),
@@ -94,10 +151,10 @@ class ogi__location_on_map extends ChadoField {
         $value = array (
           // Map.
           'data:1274' => array(
-            'schema:name' => $featuremap->name,
-            'schema:description' => $featuremap->description,
+            $name_term => $featuremap->name,
+            $description_term => $featuremap->description,
           ),
-          'SIO:000056' => $featurepos->mappos
+          $mappos_term => $featurepos->mappos
         );
         if (property_exists($featuremap, 'entity_id')) {
           $value['data:1274']['entity'] = 'TripalEntity:' . $featuremap->entity_id;

+ 47 - 5
tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference.inc

@@ -57,6 +57,48 @@ class sbo__database_cross_reference extends ChadoField {
   protected $instance;
 
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+
+    $dbname_term = tripal_get_chado_semweb_term('db', 'name');
+    $accession_term = tripal_get_chado_semweb_term('dbxref', 'accession');
+
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'elements' => array(
+          $dbname_term => array(
+            'searchable' => TRUE,
+            'name' => 'db_name',
+            'label' => 'Database Name',
+            'help' => 'The name of the remote database that houses the cross reference.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          $accession_term => array(
+            'searchable' => TRUE,
+            'name' => 'accession',
+            'label' => 'Database Accession',
+            'help' => 'The unique accession (identifier) in the database that houses the cross reference.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'schema:url' => array(
+            'searchable' => FALSE,
+            'name' => 'URL',
+            'label' => 'Database URL',
+            'help' => 'The URL of the database that houses the cross reference.',
+            'sortable' => FALSE,
+          ),
+        ),
+      ),
+    );
+  }
   /**
    *
    * @see TripalField::load()
@@ -143,14 +185,14 @@ class sbo__database_cross_reference extends ChadoField {
     $operator = $condition['operator'];
 
     if ($condition['column'] == 'database_cross_reference.database_name') {
-      $query->join($dbxref_linker, $alias, "base.$bpkey = $alias.$bpkey");
-      $query->join('dbxref', 'DBX', "DBX.dbxref_id = $alias.dbxref_id");
-      $query->join('db', 'DB', "DB.db_id = DBX.db_id");
+      $this->queryJoinOnce($query, $dbxref_linker, $alias, "base.$bpkey = $alias.$bpkey");
+      $this->queryJoinOnce($query, 'dbxref', 'DBX', "DBX.dbxref_id = $alias.dbxref_id");
+      $this->queryJoinOnce($query, 'db', 'DB', "DB.db_id = DBX.db_id");
       $query->condition("DB.name", $condition['value'], $operator);
     }
     if ($condition['column'] == 'database_cross_reference.accession') {
-      $query->join($dbxref_linker, $alias, "base.$bpkey = $alias.$bpkey");
-      $query->join('dbxref', 'DBX', "DBX.dbxref_id = $alias.dbxref_id");
+      $this->queryJoinOnce($query, $dbxref_linker, $alias, "base.$bpkey = $alias.$bpkey");
+      $this->queryJoinOnce($query, 'dbxref', 'DBX', "DBX.dbxref_id = $alias.dbxref_id");
       $query->condition("DBX.accession", $condition['value'], $operator);
     }
   }

+ 14 - 1
tripal_chado/includes/TripalFields/sbo__phenotype/sbo__phenotype.inc

@@ -57,6 +57,19 @@ class sbo__phenotype extends ChadoField {
   protected $instance;
 
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'ne', 'contains', 'starts'),
+        'sortable' => TRUE,
+        'searchable' => TRUE,
+      ),
+    );
+  }
   /**
    *
    * @see TripalField::load()
@@ -112,7 +125,7 @@ class sbo__phenotype extends ChadoField {
         else {
           $entity->{$field_name}['und'][$i]['value']['value'] =  $phenotype->value;
         }
-        
+
         $entity->{$field_name}['und'][$i][$field_table . '__' . $pkey] = $phenotype_linker->$pkey;
         $entity->{$field_name}['und'][$i][$field_table . '__' . $fkey_lcolumn] = $phenotype_linker->$fkey_lcolumn->$fkey_lcolumn;
         $entity->{$field_name}['und'][$i][$field_table . '__' . 'phenotype_id'] = $phenotype->phenotype_id;

+ 114 - 23
tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship.inc

@@ -71,6 +71,85 @@ class sbo__relationship extends ChadoField {
   protected $instance;
 
 
+  /**
+   * @see TripalField::elements()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+
+    return array(
+      $field_term => array(
+        'operations' => array('eq', 'contains', 'starts'),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'type' => 'string',
+        'elements' => array(
+          'local:relationship_subject' => array(
+            'searchable' => FALSE,
+            'name' => 'relationship_subject',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+            'elements' => array(
+              'rdfs:type' => array(
+                'searchable' => TRUE,
+                'name' => 'subject_type',
+                'label' => 'Relationship Subject Type',
+                'help' => 'The subject\'s data type in a relationship clause',
+                'operations' => array('eq', 'ne', 'contains', 'starts'),
+                'sortable' => TRUE,
+              ),
+              'schema:name' => array(
+                'searchable' => TRUE,
+                'name' => 'subject_name',
+                'label' => 'Relationship Subject Name',
+                'help' => 'The subject\'s name in a relationship clause',
+                'operations' => array('eq', 'ne', 'contains', 'starts'),
+                'sortable' => TRUE,
+              ),
+              'entity' => array(
+                'searchable' => FALSE,
+                'sortable' => FALSE,
+              )
+            ),
+          ),
+          'local:relationship_type' => array(
+            'searchable' => TRUE,
+            'name' => 'relationship_type',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'local:relationship_object' => array(
+            'searchable' => FALSE,
+            'name' => 'species',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+            'elements' => array(
+              'rdfs:type' => array(
+                'searchable' => TRUE,
+                'name' => 'object_type',
+                'label' => 'Relationship Object Type',
+                'help' => 'The objects\'s data type in a relationship clause',
+                'operations' => array('eq', 'ne', 'contains', 'starts'),
+                'sortable' => TRUE,
+              ),
+              'schema:name' => array(
+                'searchable' => TRUE,
+                'name' => 'object_name',
+                'label' => 'Relationship Object Name',
+                'help' => 'The objects\'s name in a relationship clause',
+                'operations' => array('eq', 'ne', 'contains', 'starts'),
+                'sortable' => TRUE,
+              ),
+              'entity' => array(
+                'searchable' => FALSE,
+                'sortable' => FALSE,
+              )
+            ),
+          ),
+        ),
+      )
+    );
+  }
   /**
    *
    * @see TripalField::load()
@@ -445,49 +524,61 @@ class sbo__relationship extends ChadoField {
     $bpkey = $bschema['primary key'][0];
     $operator = $condition['operator'];
 
+    // Bulid the list of expected elements that will be provided.
+    $field_term_id = $this->getFieldTermID();
+    $rel_subject = $field_term_id . ',local:relationship_subject';
+    $rel_subject_type =  $rel_subject . ',' . 'rdfs:type';
+    $rel_subject_name =  $rel_subject . ',' . 'schema:name';
+    $rel_subject_identifier = $rel_subject . ',' . 'data:0842';
+    $rel_type = $field_term_id . ',local:relationship_type';
+    $rel_object = $field_term_id . ',local:relationship_object';
+    $rel_object_type = $rel_object . ',' . 'rdfs:type';
+    $rel_object_name = $rel_object . ',' . 'schema:name';
+    $rel_object_identifier = $rel_object . ',' . 'data:0842';
+
     // Filter by the name of the subject or object.
-    if ($condition['column'] == 'relationship.clause_subject.name') {
-      $query->join($chado_table, $alias, "base.$bpkey = $alias.object_id");
-      $query->join($base_table, 'base2', "base2.$bpkey = $alias.subject_id");
+    if ($condition['column'] == $rel_subject_name) {
+      $this->queryJoinOnce($query, $chado_table, $alias, "base.$bpkey = $alias.object_id");
+      $this->queryJoinOnce($query, $base_table, 'base2', "base2.$bpkey = $alias.subject_id");
       $query->condition("base2.name", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'relationship.clause_predicate.name') {
-      $query->join($chado_table, $alias, "base.$bpkey = $alias.subject_id");
-      $query->join($base_table, 'base2', "base2.$bpkey = $alias.object_id");
+    if ($condition['column'] == $rel_object_name) {
+      $this->queryJoinOnce($query, $chado_table, $alias, "base.$bpkey = $alias.subject_id");
+      $this->queryJoinOnce($query, $base_table, 'base2', "base2.$bpkey = $alias.object_id");
       $query->condition("base2.name", $condition['value'], $operator);
     }
 
     // Filter by unique name of the subject or object.
-    if ($condition['column'] == 'relationship.clause_subject.identifier') {
-      $query->join($chado_table, $alias, "base.$bpkey = $alias.object_id");
-      $query->join($base_table, 'base2', "base2.$bpkey = $alias.subject_id");
+    if ($condition['column'] == $rel_subject_identifier) {
+      $this->queryJoinOnce($query, $chado_table, $alias, "base.$bpkey = $alias.object_id");
+      $this->queryJoinOnce($query, $base_table, 'base2', "base2.$bpkey = $alias.subject_id");
       $query->condition("base2.uniquename", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'relationship.clause_predicate.identifier') {
-      $query->join($chado_table, $alias, "base.$bpkey = $alias.subject_id");
-      $query->join($base_table, 'base2', "base2.$bpkey = $alias.object_id");
+    if ($condition['column'] == $rel_object_identifier) {
+      $this->queryJoinOnce($query, $chado_table, $alias, "base.$bpkey = $alias.subject_id");
+      $this->queryJoinOnce($query, $base_table, 'base2', "base2.$bpkey = $alias.object_id");
       $query->condition("base2.uniquename", $condition['value'], $operator);
     }
 
     // Filter by the type of the subject or object
-    if ($condition['column'] == 'relationship.clause_subject.type') {
-      $query->join($chado_table, $alias, "base.$bpkey = $alias.object_id");
-      $query->join($base_table, 'base2', "base2.$bpkey = $alias.subject_id");
-      $query->join('cvterm', 'SubjectCVT', "SubjectCVT.cvterm_id = base2.type_id");
-      $query->condition("SubjectCVT.name", $condition['value'], $operator);
+    if ($condition['column'] == $rel_subject_type) {
+      $this->queryJoinOnce($query, $chado_table, $alias, "base.$bpkey = $alias.object_id");
+      $this->queryJoinOnce($query, $base_table, 'base2', "base2.$bpkey = $alias.subject_id");
+      $this->queryJoinOnce($query, 'cvterm', 'SubjectCVT', "SubjectCVT.cvterm_id = base2.type_id");
+      $this->queryJoinOnce($query, "SubjectCVT.name", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'relationship.clause_predicate.type') {
-      $query->join($chado_table, $alias, "base.$bpkey = $alias.subject_id");
-      $query->join($base_table, 'base2', "base2.$bpkey = $alias.object_id");
-      $query->join('cvterm', 'ObjectCVT', "ObjectCVT.cvterm_id = base2.type_id");
+    if ($condition['column'] == $rel_object_type) {
+      $this->queryJoinOnce($query, $chado_table, $alias, "base.$bpkey = $alias.subject_id");
+      $this->queryJoinOnce($query, $base_table, 'base2', "base2.$bpkey = $alias.object_id");
+      $this->queryJoinOnce($query, 'cvterm', 'ObjectCVT', "ObjectCVT.cvterm_id = base2.type_id");
       $query->condition("ObjectCVT.name", $condition['value'], $operator);
     }
 
     // Filter by relationship type
     if ($condition['column'] == 'relationship.relationship_type') {
       // This filter commented out because it's way to slow...
-//       $query->join($chado_table, $alias, "base.$bpkey = $alias.subject_id OR base.$bpkey = $alias.object_id");
-//       $query->join('cvterm', 'RelTypeCVT', "RelTypeCVT.cvterm_id = $alias.type_id");
+//       $this->queryJoinOnce($query, $chado_table, $alias, "base.$bpkey = $alias.subject_id OR base.$bpkey = $alias.object_id");
+//       $this->queryJoinOnce($query, 'cvterm', 'RelTypeCVT', "RelTypeCVT.cvterm_id = $alias.type_id");
 //       $query->condition("RelTypeCVT.name", $condition['value'], $operator);
     }
   }

+ 1 - 1
tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship_formatter.inc

@@ -104,7 +104,7 @@ class sbo__relationship_formatter extends ChadoFieldFormatter {
 
     // Build the pager
     $items_per_page = array_key_exists('items_per_page', $this->instance['settings']) ? $this->instance['settings']['items_per_page'] : 10;
-    $total_records = count($items);
+    $total_records = count($rows);
     $total_pages = (int) ($total_records / $items_per_page) + 1;
     $pelement = 0; //$this->getPagerElementID();
     $current_page = pager_default_initialize($total_records, $items_per_page, $pelement);

+ 0 - 1
tripal_chado/includes/TripalFields/schema__additional_type/schema__additional_type.inc

@@ -44,7 +44,6 @@ class schema__additional_type extends ChadoField {
   public static $default_formatter = 'schema__additional_type_formatter';
 
 
-
   /**
    *
    * @see TripalField::load()

+ 2 - 2
tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name.inc

@@ -114,8 +114,8 @@ class schema__alternate_name extends ChadoField {
     $operator = $condition['operator'];
 
     if ($condition['column'] == 'alternatename') {
-      $query->join($syn_linker, $alias, "base.$bpkey = $alias.$bpkey");
-      $query->join('synonym', 'SYN', "SYN.synonym_id = $alias.synonym_id");
+      $this->queryJoinOnce($query, $syn_linker, $alias, "base.$bpkey = $alias.$bpkey");
+      $this->queryJoinOnce($query, 'synonym', 'SYN', "SYN.synonym_id = $alias.synonym_id");
       $query->condition("SYN.name", $condition['value']);
     }
   }

+ 19 - 7
tripal_chado/includes/TripalFields/schema__publication/schema__publication.inc

@@ -56,7 +56,19 @@ class schema__publication extends ChadoField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
 
-
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+      ),
+    );
+  }
   /**
    *
    * @see TripalField::load()
@@ -124,17 +136,17 @@ class schema__publication extends ChadoField {
     if ($condition['column'] == 'publication.database_cross_reference') {
       list($db_name, $accession) = explode(':', $condition['value']);
 
-      $query->join($pub_linker, $alias, "base.$bpkey = $alias.$bpkey");
-      $query->join('pub_dbxref', 'PDBX', "PDBX.pub_id = $alias.pub_id");
-      $query->join('dbxref', 'DBX', "DBX.dbxref_id = PDBX.dbxref_id");
-      $query->join('db', 'DB', "DB.db_id = DBX.db_id");
+      $this->queryJoinOnce($query, $pub_linker, $alias, "base.$bpkey = $alias.$bpkey");
+      $this->queryJoinOnce($query, 'pub_dbxref', 'PDBX', "PDBX.pub_id = $alias.pub_id");
+      $this->queryJoinOnce($query, 'dbxref', 'DBX', "DBX.dbxref_id = PDBX.dbxref_id");
+      $this->queryJoinOnce($query, 'db', 'DB', "DB.db_id = DBX.db_id");
       $query->condition("DB.name", $db_name);
       $query->condition("DBX.accession", $accession);
     }
 
     if ($condition['column'] == 'publication.title') {
-      $query->join($pub_linker, $alias, "base.$bpkey = $alias.$bpkey");
-      $query->join('pub', 'PUB', "PUB.pub_id = $alias.pub_id");
+      $this->queryJoinOnce($query, $pub_linker, $alias, "base.$bpkey = $alias.$bpkey");
+      $this->queryJoinOnce($query, 'pub', 'PUB', "PUB.pub_id = $alias.pub_id");
       $query->condition('PUB.title', $condition['value'], $operator);
     }
 

+ 44 - 0
tripal_chado/includes/TripalFields/sio__references/sio__references.inc

@@ -55,6 +55,50 @@ class sio__references extends ChadoField {
   // and field_create_instance().
   public static $no_ui = FALSE;
 
+
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'elements' => array(
+          'rdfs:type' => array(
+            'searchable' => TRUE,
+            'name' => 'type',
+            'label' => 'Reference Type',
+            'help' => 'The type of item referred to by the publication.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'schema:name' => array(
+            'searchable' => TRUE,
+            'name' => 'name',
+            'label' => 'Reference Name',
+            'help' => 'The name of the item referred to by the publication.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'data:0842'=> array(
+            'searchable' => TRUE,
+            'name' => 'identifier',
+            'label' => 'Reference Identifier',
+            'help' => 'The unique identifier of the item referred to by the publication.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => TRUE,
+          ),
+          'entity'=> array(
+            'searchable' => FALSE,
+            'sortable' => FALSE,
+          ),
+        ),
+      ),
+    );
+  }
   /**
    *
    * @see TripalField::load()

+ 2 - 2
tripal_chado/includes/TripalFields/sio__vocabulary/sio__vocabulary.inc

@@ -92,7 +92,7 @@ class sio__vocabulary extends ChadoField {
     $alias = $this->field['field_name'];
     $operator = $condition['operator'];
 
-    $query->join('cv', $alias, "base.cv_id = $alias.cv_id");
+    $this->queryJoinOnce($query, 'cv', $alias, "base.cv_id = $alias.cv_id");
     $query->condition("$alias.name", $condition['value'], $operator);
   }
 
@@ -105,7 +105,7 @@ class sio__vocabulary extends ChadoField {
     $joins = $query->getTables();
     if (!in_array($this->field['field_name'], $joins)) {
       $alias = $this->field['field_name'];
-      $query->join('cv', $alias, "base.cv_id = $alias.cv_id");
+      $this->queryJoinOnce($query, 'cv', $alias, "base.cv_id = $alias.cv_id");
       $query->orderBy("$alias.name", $order['direction']);
     }
   }

+ 14 - 0
tripal_chado/includes/TripalFields/so__cds/so__cds.inc

@@ -43,6 +43,20 @@ class so__cds extends ChadoField {
   // The default formatter for this field.
   public static $default_formatter = 'so__cds_formatter';
 
+
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+      ),
+    );
+  }
+
   /**
    * @see TripalField::load()
    */

+ 43 - 4
tripal_chado/includes/TripalFields/so__genotype/so__genotype.inc

@@ -56,6 +56,46 @@ class so__genotype extends ChadoField {
   protected $instance;
 
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'elements' => array(
+          'rdfs:type' => array(
+            'searchable' => FALSE,
+            'name' => 'genotype_type_name',
+            'label' => 'Genotype Type',
+            'help' => 'The type of genotype.',
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          'schema:name' => array(
+            'name' => 'genotype_name',
+            'label' => 'Genotype Name',
+            'help' => 'The name of the genotype.',
+            'searchable' => FALSE,
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          'schema:description' => array(
+            'name' => 'genotype_description',
+            'label' => 'Genotype Description',
+            'help' => 'A description of the genotype.',
+            'searchable' => FALSE,
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+        )
+      ),
+    );
+  }
+
   /**
    *
    * @see TripalField::load()
@@ -105,10 +145,9 @@ class so__genotype extends ChadoField {
         $genotype = $genotype_linker->genotype_id;
         $entity->{$field_name}['und'][$i] = array(
           'value' => array(
-            '@type' => $genotype->type_id->dbxref_id->db_id->name . ':' . $genotype->type_id->dbxref_id->accession,
-            'type' => $genotype->type_id->name,
-            'name' => $genotype->name,
-            'description' => $genotype->description,
+            'rdfs:type' => $genotype->type_id->name,
+            'schema:name' => $genotype->name,
+            'schema:description' => $genotype->description,
           ),
           $field_table . '__' . $pkey => $genotype_linker->$pkey,
           $field_table . '__' . $fkey_lcolumn => $genotype_linker->$fkey_lcolumn->$fkey_lcolumn,

+ 51 - 1
tripal_chado/includes/TripalFields/so__transcript/so__transcript.inc

@@ -54,7 +54,57 @@ class so__transcript extends ChadoField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
 
-
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'elements' => array(
+          'rdfs:type' => array(
+            'name' => 'transcript_type',
+            'label' => 'Transcript Type',
+            'help' => 'The type of a transcript.',
+            'searchable' => FALSE,
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          'schema:name' => array(
+            'name' => 'transcript_name',
+            'label' => 'Transcript Name',
+            'help' => 'The name of a transcript.',
+            'searchable' => FALSE,
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          'data:0842' => array(
+            'name' => 'transcript_uniquename',
+            'label' => 'Transcript Identifier',
+            'help' => 'The unique identifier of a transcript.',
+            'searchable' => FALSE,
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          'SO:0000735' => array(
+            'name' => 'loc',
+            'label' => 'Transcript Location',
+            'help' => 'The location of the transcript on a reference feature.',
+            'searchable' => FALSE,
+            'operations' => array('eq', 'ne', 'contains', 'starts'),
+            'sortable' => FALSE,
+          ),
+          'entity' => array(
+            'searchable' => FALSE,
+            'sortable' => FALSE,
+          ),
+        ),
+      ),
+    );
+  }
   /**
    *
    * @see TripalField::load()

+ 14 - 0
tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon.inc

@@ -54,6 +54,20 @@ class taxrank__infraspecific_taxon extends ChadoField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
 
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+      ),
+    );
+  }
+
   /**
    *
    * @see TripalField::load()

+ 0 - 1004
tripal_chado/includes/loaders/tripal_chado.fasta_loader.inc

@@ -1,1004 +0,0 @@
-<?php
-/**
- * @file
- * Provides fasta loading functionality. Creates features based on their specification
- * in a fasta file.
- */
-
-/**
- * @defgroup fasta_loader FASTA Feature Loader
- * @ingroup tripal_chado
- * @{
- * Provides fasta loading functionality.
- * Creates features based on their specification
- * in a fasta file.
- * @}
- */
-
-/**
- * The form to submit a fasta loading job
- *
- * @ingroup fasta_loader
- */
-function tripal_feature_fasta_load_form() {
-  $form['fasta_file'] = array('#type' => 'textfield','#title' => t('FASTA File'),
-    '#description' => t('Please enter the full system path for the FASTA file, or a path within the Drupal
-                           installation (e.g. /sites/default/files/xyz.obo).  The path must be accessible to the
-                           server on which this Drupal instance is running.'),'#required' => TRUE
-  );
-
-  // get the list of organisms
-  $sql = "SELECT * FROM {organism} ORDER BY genus, species";
-  $org_rset = chado_query($sql);
-  $organisms = array();
-  $organisms[''] = '';
-  while ($organism = $org_rset->fetchObject()) {
-    $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 to which these sequences are associated"),
-    '#required' => TRUE,'#options' => $organisms
-  );
-
-  // get the sequence ontology CV ID
-  $values = array('name' => 'sequence');
-  $cv = chado_select_record('cv', array('cv_id'), $values);
-  $cv_id = $cv[0]->cv_id;
-
-  $form['seqtype'] = array('#type' => 'textfield','#title' => t('Sequence Type'),
-    '#required' => TRUE,
-    '#description' => t('Please enter the Sequence Ontology (SO) term name that describes the sequences in the FASTA file (e.g. gene, mRNA, polypeptide, etc...)'),
-    '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/cvterm/$cv_id"
-  );
-
-  $form['method'] = array('#type' => 'radios','#title' => 'Method','#required' => TRUE,
-    '#options' => array(t('Insert only'),t('Update only'),t('Insert and update')
-    ),
-    '#description' => t('Select how features in the FASTA file are handled.
-       Select "Insert only" to insert the new features. If a feature already
-       exists with the same name or unique name and type then it is skipped.
-       Select "Update only" to only update featues that already exist in the
-       database.  Select "Insert and Update" to insert features that do
-       not exist and upate those that do.'),'#default_value' => 2
-  );
-  $form['match_type'] = array('#type' => 'radios','#title' => 'Name Match Type','#required' => TRUE,
-    '#options' => array(t('Name'),t('Unique name')
-    ),
-    '#description' => t('Used for "updates only" or "insert and update" methods. Not required if method type is "insert".
-      Feature data is stored in Chado with both a human-readable
-      name and a unique name. If the features in your FASTA file are uniquely identified using
-      a human-readable name then select the "Name" button. If your features are
-      uniquely identified using the unique name then select the "Unique name" button.  If you
-      loaded your features first using the GFF loader then the unique name of each
-      features were indicated by the "ID=" attribute and the name by the "Name=" attribute.
-      By default, the FASTA loader will use the first word (character string
-      before the first space) as  the name for your feature. If
-      this does not uniquely identify your feature consider specifying a regular expression in the advanced section below.
-      Additionally, you may import both a name and a unique name for each sequence using the advanced options.'),
-    '#default_value' => 1
-  );
-
-  $form['analysis'] = array('#type' => 'fieldset','#title' => t('Analysis Used to Derive Features'),
-    '#collapsed' => TRUE
-  );
-  $form['analysis']['desc'] = array(
-    '#markup' => t("Why specify an analysis for a data load?  All data comes
-       from some place, even if downloaded from Genbank. By specifying
-       analysis details for all data uploads, it allows an end user to reproduce the
-       data set, but at least indicates the source of the data.")
-  );
-
-  // get the list of organisms
-  $sql = "SELECT * FROM {analysis} ORDER BY name";
-  $org_rset = chado_query($sql);
-  $analyses = array();
-  $analyses[''] = '';
-  while ($analysis = $org_rset->fetchObject()) {
-    $analyses[$analysis->analysis_id] = "$analysis->name ($analysis->program $analysis->programversion, $analysis->sourcename)";
-  }
-  $form['analysis']['analysis_id'] = array('#title' => t('Analysis'),'#type' => t('select'),
-    '#description' => t("Choose the analysis to which these features are associated"),
-    '#required' => TRUE,'#options' => $analyses
-  );
-
-  // Advanced Options
-  $form['advanced'] = array('#type' => 'fieldset','#title' => t('Advanced Options'),
-    '#collapsible' => TRUE,'#collapsed' => TRUE
-  );
-  $form['advanced']['re_help'] = array('#type' => 'item',
-    '#value' => t('A regular expression is an advanced method for extracting information from a string of text.
-                   Your FASTA file may contain both a human-readable name and a unique name for each sequence.
-                   If you want to import
-                   both the name and unique name for all sequences, then you must provide regular expressions
-                   so that the loader knows how to separate them.
-                   Otherwise the name and uniquename will be the same.
-                   By default, this loader will use the first word in the definition
-                   lines of the FASTA file
-                   as the name or unique name of the feature.')
-  );
-  $form['advanced']['re_name'] = array('#type' => 'textfield',
-    '#title' => t('Regular expression for the name'),'#required' => FALSE,
-    '#description' => t('Enter the regular expression that will extract the
-       feature name from the FASTA definition line. For example, for a
-       defintion line with a name and unique name separated by a bar \'|\' (>seqname|uniquename),
-       the regular expression for the name would be, "^(.*?)\|.*$".  All FASTA
-       definition lines begin with the ">" symbol.  You do not need to incldue
-       this symbol in your regular expression.')
-  );
-  $form['advanced']['re_uname'] = array('#type' => 'textfield',
-    '#title' => t('Regular expression for the unique name'),'#required' => FALSE,
-    '#description' => t('Enter the regular expression that will extract the
-       feature name from the FASTA definition line. For example, for a
-       defintion line with a name and unique name separated by a bar \'|\' (>seqname|uniquename),
-       the regular expression for the unique name would be "^.*?\|(.*)$").  All FASTA
-       definition lines begin with the ">" symbol.  You do not need to incldue
-       this symbol in your regular expression.')
-  );
-
-  // Advanced database cross reference options.
-  $form['advanced']['db'] = array('#type' => 'fieldset',
-    '#title' => t('External Database Reference'),'#weight' => 6,'#collapsed' => TRUE
-  );
-  $form['advanced']['db']['re_accession'] = array('#type' => 'textfield',
-    '#title' => t('Regular expression for the accession'),'#required' => FALSE,
-    '#description' => t('Enter the regular expression that will extract the accession for the external database for each feature from the FASTA definition line.'),
-    '#weight' => 2
-  );
-
-  // get the list of databases
-  $sql = "SELECT * FROM {db} ORDER BY name";
-  $db_rset = chado_query($sql);
-  $dbs = array();
-  $dbs[''] = '';
-  while ($db = $db_rset->fetchObject()) {
-    $dbs[$db->db_id] = "$db->name";
-  }
-  $form['advanced']['db']['db_id'] = array('#title' => t('External Database'),
-    '#type' => t('select'),
-    '#description' => t("Plese choose an external database for which these sequences have a cross reference."),
-    '#required' => FALSE,'#options' => $dbs,'#weight' => 1
-  );
-
-  $form['advanced']['relationship'] = array('#type' => 'fieldset','#title' => t('Relationships'),
-    '#weight' => 6,'#collapsed' => TRUE
-  );
-  $rels = array();
-  $rels[''] = '';
-  $rels['part_of'] = 'part of';
-  $rels['derives_from'] = 'produced by (derives from)';
-
-  // Advanced references options
-  $form['advanced']['relationship']['rel_type'] = array('#title' => t('Relationship Type'),
-    '#type' => t('select'),
-    '#description' => t("Use this option to create associations, or relationships between the
-                        features of this FASTA file and existing features in the database. For
-                        example, to associate a FASTA file of peptides to existing genes or transcript sequence,
-                        select the type 'produced by'. For a CDS sequences select the type 'part of'"),
-    '#required' => FALSE,'#options' => $rels,'#weight' => 5
-  );
-  $form['advanced']['relationship']['re_subject'] = array('#type' => 'textfield',
-    '#title' => t('Regular expression for the parent'),'#required' => FALSE,
-    '#description' => t('Enter the regular expression that will extract the unique
-                         name needed to identify the existing sequence for which the
-                         relationship type selected above will apply.'),'#weight' => 6
-  );
-  $form['advanced']['relationship']['parent_type'] = array('#type' => 'textfield',
-    '#title' => t('Parent Type'),'#required' => FALSE,
-    '#description' => t('Please enter the Sequence Ontology term for the parent.  For example
-                         if the FASTA file being loaded is a set of proteins that are
-                         products of genes, then use the SO term \'gene\' or \'transcript\' or equivalent. However,
-                         this type must match the type for already loaded features.'),
-    '#weight' => 7
-  );
-
-  $form['button'] = array('#type' => 'submit','#value' => t('Import FASTA file'),'#weight' => 10
-  );
-  return $form;
-}
-
-/**
- * Validate the fasta loader job form
- *
- * @ingroup fasta_loader
- */
-function tripal_feature_fasta_load_form_validate($form, &$form_state) {
-  $fasta_file = trim($form_state['values']['fasta_file']);
-  $organism_id = $form_state['values']['organism_id'];
-  $type = trim($form_state['values']['seqtype']);
-  $method = trim($form_state['values']['method']);
-  $match_type = trim($form_state['values']['match_type']);
-  $re_name = trim($form_state['values']['re_name']);
-  $re_uname = trim($form_state['values']['re_uname']);
-  $re_accession = trim($form_state['values']['re_accession']);
-  $db_id = $form_state['values']['db_id'];
-  $rel_type = $form_state['values']['rel_type'];
-  $re_subject = trim($form_state['values']['re_subject']);
-  $parent_type = trim($form_state['values']['parent_type']);
-
-  if ($method == 0) {
-    $method = 'Insert only';
-  }
-  if ($method == 1) {
-    $method = 'Update only';
-  }
-  if ($method == 2) {
-    $method = 'Insert and update';
-  }
-
-  if ($match_type == 0) {
-    $match_type = 'Name';
-  }
-
-  if ($match_type == 1) {
-    $match_type = 'Unique name';
-  }
-
-  if ($re_name and !$re_uname and strcmp($match_type, 'Unique name') == 0) {
-    form_set_error('re_uname', t("You must provide a regular expression to identify the sequence unique name"));
-  }
-
-  if (!$re_name and $re_uname and strcmp($match_type, 'Name') == 0) {
-    form_set_error('re_name', t("You must provide a regular expression to identify the sequence name"));
-  }
-
-  // check to see if the file is located local to Drupal
-  $fasta_file = trim($fasta_file);
-  $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $fasta_file;
-  if (!file_exists($dfile)) {
-    // if not local to Drupal, the file must be someplace else, just use
-    // the full path provided
-    $dfile = $fasta_file;
-  }
-  if (!file_exists($dfile)) {
-    form_set_error('fasta_file', t("Cannot find the file on the system. Check that the file exists or that the web server has permissions to read the file."));
-  }
-
-  // make sure if a relationship is specified that all fields are provided.
-  if (($rel_type or $parent_type) and !$re_subject) {
-    form_set_error('re_subject', t("Please provide a regular expression for the parent"));
-  }
-  if (($rel_type or $re_subject) and !$parent_type) {
-    form_set_error('parent_type', t("Please provide a SO term for the parent"));
-  }
-  if (($parent_type or $re_subject) and !$rel_type) {
-    form_set_error('rel_type', t("Please select a relationship type"));
-  }
-
-  // make sure if a database is specified that all fields are provided
-  if ($db_id and !$re_accession) {
-    form_set_error('re_accession', t("Please provide a regular expression for the accession"));
-  }
-  if ($re_accession and !$db_id) {
-    form_set_error('db_id', t("Please select a database"));
-  }
-
-  // check to make sure the types exists
-  $cvtermsql = "SELECT CVT.cvterm_id
-               FROM {cvterm} CVT
-                  INNER JOIN {cv} CV on CVT.cv_id = CV.cv_id
-                  LEFT JOIN {cvtermsynonym} CVTS on CVTS.cvterm_id = CVT.cvterm_id
-               WHERE cv.name = :cvname and (CVT.name = :name or CVTS.synonym = :synonym)";
-  $cvterm = chado_query($cvtermsql, array(':cvname' => 'sequence',':name' => $type,
-    ':synonym' => $type
-  ))->fetchObject();
-  if (!$cvterm) {
-    form_set_error('type', t("The Sequence Ontology (SO) term selected for the sequence type is not available in the database. Please check spelling or select another."));
-  }
-  if ($rel_type) {
-    $cvterm = chado_query($cvtermsql, array(':cvname' => 'sequence',':name' => $parent_type,
-      ':synonym' => $parent_type
-    ))->fetchObject();
-    if (!$cvterm) {
-      form_set_error('parent_type', t("The Sequence Ontology (SO) term selected for the parent relationship is not available in the database. Please check spelling or select another."));
-    }
-  }
-
-  // check to make sure the 'relationship' and 'sequence' ontologies are loaded
-  $form_state['storage']['dfile'] = $dfile;
-}
-
-/**
- * Submit a fasta loading job
- *
- * @ingroup fasta_loader
- */
-function tripal_feature_fasta_load_form_submit($form, &$form_state) {
-  global $user;
-
-  $dfile = $form_state['storage']['dfile'];
-  $organism_id = $form_state['values']['organism_id'];
-  $type = trim($form_state['values']['seqtype']);
-  $method = trim($form_state['values']['method']);
-  $match_type = trim($form_state['values']['match_type']);
-  $re_name = trim($form_state['values']['re_name']);
-  $re_uname = trim($form_state['values']['re_uname']);
-  $re_accession = trim($form_state['values']['re_accession']);
-  $db_id = $form_state['values']['db_id'];
-  $rel_type = $form_state['values']['rel_type'];
-  $re_subject = trim($form_state['values']['re_subject']);
-  $parent_type = trim($form_state['values']['parent_type']);
-  $analysis_id = $form_state['values']['analysis_id'];
-
-  if ($method == 0) {
-    $method = 'Insert only';
-  }
-  if ($method == 1) {
-    $method = 'Update only';
-  }
-  if ($method == 2) {
-    $method = 'Insert and update';
-  }
-
-  if ($match_type == 0) {
-    $match_type = 'Name';
-  }
-
-  if ($match_type == 1) {
-    $match_type = 'Unique name';
-  }
-
-  $args = array($dfile,$organism_id,$type,$re_name,$re_uname,$re_accession,$db_id,$rel_type,
-    $re_subject,$parent_type,$method,$user->uid,$analysis_id,$match_type
-  );
-
-  $fname = preg_replace("/.*\/(.*)/", "$1", $dfile);
-  $includes = array(
-    module_load_include('inc', 'tripal_chado', 'includes/loaders/tripal_chado.fasta_loader'),
-  );
-  tripal_add_job("Import FASTA file: $fname", 'tripal_chado', 'tripal_feature_load_fasta', $args, $user->uid, 10, $includes);
-}
-
-/**
- * Actually load a fasta file.
- * This is the function called by tripal jobs
- *
- * @param $dfile The
- *          full path to the fasta file to load
- * @param $organism_id The
- *          organism_id of the organism these features are from
- * @param $type The
- *          type of features contained in the fasta file
- * @param $re_name A
- *          regular expression to extract the feature.name from the fasta header
- * @param $re_uname A
- *          regular expression to extract the feature.uniquename from the fasta header
- * @param $re_accession A
- *          regular expression to extract the accession of the feature.dbxref_id
- * @param $db_id The
- *          db_id of the above dbxref
- * @param $rel_type The
- *          type of relationship when creating a feature_relationship between this
- *          feature (object) and an extracted subject
- * @param $re_subject The
- *          regular expression to extract the uniquename of the feature to be the subject
- *          of the above specified relationship
- * @param $parent_type The
- *          type of the parent feature
- * @param $method The
- *          method of feature adding. (ie: 'Insert only', 'Update only', 'Insert and update')
- * @param $uid The
- *          user id of the user who submitted the job
- * @param $analysis_id The
- *          analysis_id to associate the features in this fasta file with
- * @param $match_type Whether
- *          to match existing features based on the 'Name' or 'Unique name'
- * @param $job =
- *          NULL
- *          The tripal job
- *
- *          @ingroup fasta_loader
- */
-function tripal_feature_load_fasta($dfile, $organism_id, $type, $re_name, $re_uname, $re_accession,
-  $db_id, $rel_type, $re_subject, $parent_type, $method, $uid, $analysis_id, $match_type,
-  $job = NULL) {
-  $transaction = db_transaction();
-  print "\nNOTE: Loading of this GFF file is performed using a database transaction. \n" .
-     "If the load fails or is terminated prematurely then the entire set of \n" .
-     "insertions/updates is rolled back and will not be found in the database\n\n";
-  try {
-
-    // First get the type for this sequence.
-    $cvtermsql = "
-      SELECT CVT.cvterm_id
-      FROM {cvterm} CVT
-        INNER JOIN {cv} CV on CVT.cv_id = CV.cv_id
-        LEFT JOIN {cvtermsynonym} CVTS on CVTS.cvterm_id = CVT.cvterm_id
-      WHERE cv.name = :cvname and (CVT.name = :name or CVTS.synonym = :synonym)
-    ";
-    $cvterm = chado_query($cvtermsql, array(':cvname' => 'sequence',':name' => $type,':synonym' => $type))->fetchObject();
-    if (!$cvterm) {
-      tripal_report_error("T_fasta_loader", TRIPAL_ERROR,
-        "Cannot find the term type: '%type'", array('%type' => $type));
-      return 0;
-    }
-
-    // Second, if there is a parent type then get that.
-    if ($parent_type) {
-      $parentcvterm = chado_query($cvtermsql, array(':cvname' => 'sequence', ':name' => $parent_type,':synonym' => $parent_type))->fetchObject();
-      if (!$parentcvterm) {
-        tripal_report_error("T_fasta_loader", TRIPAL_ERROR, "Cannot find the paretne term type: '%type'", array(
-          '%type' => $parentcvterm
-        ));
-        return 0;
-      }
-    }
-
-    // Third, if there is a relationship type then get that.
-    if ($rel_type) {
-      $relcvterm = chado_query($cvtermsql, array(':cvname' => 'sequence',':name' => $rel_type,':synonym' => $rel_type))->fetchObject();
-      if (!$relcvterm) {
-        tripal_report_error("T_fasta_loader", TRIPAL_ERROR, "Cannot find the relationship term type: '%type'", array(
-          '%type' => $relcvterm
-        ));
-        return 0;
-      }
-    }
-
-    // We need to get the table schema to make sure we don't overrun the
-    // size of fields with what our regular expressions retrieve
-    $feature_tbl = chado_get_schema('feature');
-    $dbxref_tbl = chado_get_schema('dbxref');
-
-    print "Step 1: finding sequences\n";
-    $filesize = filesize($dfile);
-    $fh = fopen($dfile, 'r');
-    if (!$fh) {
-      tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "cannot open file: %dfile", array(
-        '%dfile' => $dfile
-      ));
-      return 0;
-    }
-
-    // Calculate the interval at which we will print to the screen that status.
-    $interval = intval($filesize * 0.01);
-    if ($interval < 1) {
-      $interval = 1;
-    }
-    $inv_read = 0;
-    $num_read = 0;
-
-    // Iterate through the lines of the file. Keep a record for
-    // where in the file each line is at for later import.
-    $seqs = array();
-    $num_seqs = 0;
-    $prev_pos = 0;
-    $set_start = FALSE;
-    while ($line = fgets($fh)) {
-      $num_read += strlen($line);
-      $intv_read += strlen($line);
-
-      // If we encounter a definition line then get the name, uniquename,
-      // accession and relationship subject from the definition line.
-      if (preg_match('/^>/', $line)) {
-
-        // Remove the > symbol from the defline.
-        $defline = preg_replace("/^>/", '', $line);
-
-        // Get the feature name if a regular expression is provided.
-        if ($re_name) {
-          if (!preg_match("/$re_name/", $defline, $matches)) {
-            tripal_report_error('trp-fasta', "ERROR: Regular expression for the feature name finds nothing. Line %line.", array(
-              '%line' => $i
-            ), 'error');
-          }
-          elseif (strlen($matches[1]) > $feature_tbl['fields']['name']['length']) {
-            tripal_report_error('trp-fasta', "WARNING: Regular expression retrieves a value too long for the feature name. Line %line.", array(
-              '%line' => $i
-            ), 'error');
-          }
-          else {
-            $name = trim($matches[1]);
-          }
-        }
-        // If the match_type is name and no regular expression was provided
-        // then use the first word as the name, otherwise we don't set the name.
-        elseif (strcmp($match_type, 'Name') == 0) {
-          if (preg_match("/^\s*(.*?)[\s\|].*$/", $defline, $matches)) {
-            if (strlen($matches[1]) > $feature_tbl['fields']['name']['length']) {
-              tripal_report_error('trp-fasta', "WARNING: Regular expression retrieves a feature name too long for the feature name. Line %line.", array(
-                '%line' => $i), 'error');
-            }
-            else {
-              $name = trim($matches[1]);
-            }
-          }
-          else {
-            tripal_report_error('trp-fasta', "ERROR: Cannot find a feature name. Line %line.", array(
-              '%line' => $i), 'error');
-          }
-        }
-
-        // Get the feature uniquename if a regular expression is provided.
-        if ($re_uname) {
-          if (!preg_match("/$re_uname/", $defline, $matches)) {
-            tripal_report_error('trp-fasta', "ERROR: Regular expression for the feature unique name finds nothing. Line %line.", array(
-              '%line' => $i), 'error');
-          }
-          $uname = trim($matches[1]);
-        }
-        // If the match_type is name and no regular expression was provided
-        // then use the first word as the name, otherwise, we don't set the
-        // unqiuename.
-        elseif (strcmp($match_type, 'Unique name') == 0) {
-          if (preg_match("/^\s*(.*?)[\s\|].*$/", $defline, $matches)) {
-            $uname = trim($matches[1]);
-          }
-          else {
-            tripal_report_error('trp-fasta', "ERROR: Cannot find a feature unique name. Line %line.", array(
-              '%line' => $i), 'error');
-          }
-        }
-
-        // Get the accession if a regular expression is provided.
-        preg_match("/$re_accession/", $defline, $matches);
-        if (strlen($matches[1]) > $dbxref_tbl['fields']['accession']['length']) {
-          tripal_report_error('trp-fasta', "WARNING: Regular expression retrieves an accession too long for the feature name. " .
-             "Cannot add cross reference. Line %line.", array('%line' => $i
-            ), 'warning');
-        }
-        else {
-          $accession = trim($matches[1]);
-        }
-
-        // Get the relationship subject
-        preg_match("/$re_subject/", $line, $matches);
-        $subject = trim($matches[1]);
-
-        // Add the details to the sequence.
-        $seqs[$num_seqs] = array(
-          'name' => $name,
-          'uname' => $uname,
-          'accession' => $accession,
-          'subject' => $subject,
-          'seq_start' => ftell($fh)
-        );
-        $set_start = TRUE;
-        // If this isn't the first sequence, then we want to specify where
-        // the previous sequence ended.
-        if ($num_seqs > 0) {
-          $seqs[$num_seqs - 1]['seq_end'] = $prev_pos;
-        }
-        $num_seqs++;
-      }
-      // Keep the current file position so we can use it to set the sequence
-      // ending position
-      $prev_pos = ftell($fh);
-
-      // update the job status every % bytes
-      if ($job and $intv_read >= $interval) {
-        $intv_read = 0;
-        $percent = sprintf("%.2f", ($num_read / $filesize) * 100);
-        if ($name) {
-          print "Parsing Line $i (" . $percent . "%). Memory: " . number_format(memory_get_usage()) .
-             " bytes.\r";
-        }
-        else {
-          print "Parsing Line $i (" . $percent . "%). Memory: " . number_format(memory_get_usage()) .
-             " bytes.\r";
-        }
-        tripal_set_job_progress($job, intval(($num_read / $filesize) * 100));
-      }
-    }
-    $percent = sprintf("%.2f", ($num_read / $filesize) * 100);
-    print "Parsing Line $i (" . $percent . "%). Memory: " . number_format(memory_get_usage()) .
-       " bytes.\r";
-    tripal_set_job_progress($job, 50);
-
-    // Set the end position for the last sequence.
-    $seqs[$num_seqs - 1]['seq_end'] = $num_read - strlen($line);
-
-    // Now that we know where the sequences are in the file we need to add them.
-    print "\nStep 2: Importing sequences\n";
-    for ($i = 0; $i < $num_seqs; $i++) {
-      $seq = $seqs[$i];
-      print "Importing " . ($i + 1) . " of $num_seqs. ";
-      if ($name) {
-        print "Current feature: " . $seq['name'] . ".\n";
-      }
-      else {
-        print "Current feature: " . $seq['uname'] . ".\n";
-      }
-
-      tripal_feature_load_fasta_feature($fh, $seq['name'], $seq['uname'], $db_id, $seq['accession'], $seq['subject'], $rel_type, $parent_type, $analysis_id, $organism_id, $cvterm, $source, $method, $re_name, $match_type, $parentcvterm, $relcvterm, $seq['seq_start'], $seq['seq_end']);
-    }
-    tripal_set_job_progress($job, 100);
-    fclose($fh);
-  }
-  catch (Exception $e) {
-    fclose($fh);
-    $transaction->rollback();
-    print "\n"; // make sure we start errors on new line
-    watchdog_exception('T_fasta_loader', $e);
-    print "FAILED: Rolling back database changes...\n";
-  }
-
-  print "\nDone\n";
-}
-
-/**
- * A helper function for tripal_feature_load_fasta() to load a single feature
- *
- * @ingroup fasta_loader
- */
-function tripal_feature_load_fasta_feature($fh, $name, $uname, $db_id, $accession, $parent,
-  $rel_type, $parent_type, $analysis_id, $organism_id, $cvterm, $source, $method, $re_name,
-  $match_type, $parentcvterm, $relcvterm, $seq_start, $seq_end) {
-
-  // Check to see if this feature already exists if the match_type is 'Name'.
-  if (strcmp($match_type, 'Name') == 0) {
-    $values = array('organism_id' => $organism_id,'name' => $name,'type_id' => $cvterm->cvterm_id
-    );
-    $results = chado_select_record('feature', array('feature_id'
-    ), $values);
-    if (count($results) > 1) {
-      tripal_report_error('T_fasta_loader', "Multiple features exist with the name '%name' of type
-               '%type' for the organism.  skipping", array('%name' => $name,'%type' => $type));
-      return 0;
-    }
-    if (count($results) == 1) {
-      $feature = $results[0];
-    }
-  }
-
-  // Check if this feature already exists if the match_type is 'Unique Name'.
-  if (strcmp($match_type, 'Unique name') == 0) {
-    $values = array(
-      'organism_id' => $organism_id,
-      'uniquename' => $uname,
-      'type_id' => $cvterm->cvterm_id
-    );
-
-    $results = chado_select_record('feature', array('feature_id'), $values);
-    if (count($results) > 1) {
-      tripal_report_error('T_fasta_loader', TRIPAL_WARNING, "Multiple features exist with the name '%name' of type '%type' for the organism.  skipping", array(
-        '%name' => $name,'%type' => $type));
-      return 0;
-    }
-    if (count($results) == 1) {
-      $feature = $results[0];
-    }
-
-    // If the feature exists but this is an "insert only" then skip.
-    if ($feature and (strcmp($method, 'Insert only') == 0)) {
-      tripal_report_error('T_fasta_loader', TRIPAL_WARNING, "Feature already exists '%name' ('%uname') while matching on %type. Skipping insert.", array(
-        '%name' => $name,'%uname' => $uname,'%type' => drupal_strtolower($match_type)
-      ));
-      return 0;
-    }
-  }
-
-  // If we don't have a feature and we're doing an insert then do the insert.
-  $inserted = 0;
-  if (!$feature and (strcmp($method, 'Insert only') == 0 or strcmp($method, 'Insert and update') == 0)) {
-    // If we have a unique name but not a name then set them to be the same
-    if (!$uname) {
-      $uname = $name;
-    }
-    elseif (!$name) {
-      $name = $uname;
-    }
-
-    // Insert the feature record.
-    $values = array(
-      'organism_id' => $organism_id,
-      'name' => $name,
-      'uniquename' => $uname,
-      'type_id' => $cvterm->cvterm_id
-    );
-    $success = chado_insert_record('feature', $values);
-    if (!$success) {
-      tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to insert feature '%name (%uname)'", array(
-        '%name' => $name,'%uname' => $numane));
-      return 0;
-    }
-
-    // now get the feature we just inserted
-    $values = array(
-      'organism_id' => $organism_id,
-      'uniquename' => $uname,
-      'type_id' => $cvterm->cvterm_id
-    );
-    $results = chado_select_record('feature', array('feature_id'), $values);
-    if (count($results) == 1) {
-      $inserted = 1;
-      $feature = $results[0];
-    }
-    else {
-      tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to retreive newly inserted feature '%name (%uname)'", array(
-        '%name' => $name,'%uname' => $numane));
-      return 0;
-    }
-
-    // Add the residues for this feature
-    tripal_feature_load_fasta_residues($fh, $feature->feature_id, $seq_start, $seq_end);
-  }
-
-  // if we don't have a feature and the user wants to do an update then fail
-  if (!$feature and (strcmp($method, 'Update only') == 0 or
-     drupal_strcmp($method, 'Insert and update') == 0)) {
-    tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to find feature '%name' ('%uname') while matching on " .
-      drupal_strtolower($match_type), array('%name' => $name,'%uname' => $uname));
-    return 0;
-  }
-
-  // if we do have a feature and this is an update then proceed with the update
-  if ($feature and !$inserted and (strcmp($method, 'Update only') == 0 or
-     strcmp($method, 'Insert and update') == 0)) {
-
-    // if the user wants to match on the Name field
-    if (strcmp($match_type, 'Name') == 0) {
-
-      // if we're matching on the name but do not have a unique name then we
-      // don't want to update the uniquename.
-      $values = array();
-      if ($uname) {
-
-        // First check to make sure that by changing the unique name of this
-        // feature that we won't conflict with another existing feature of
-        // the same name
-        $values = array(
-          'organism_id' => $organism_id,
-          'uniquename' => $uname,
-          'type_id' => $cvterm->cvterm_id
-        );
-        $results = chado_select_record('feature', array('feature_id'
-        ), $values);
-        if (count($results) > 0) {
-          tripal_report_error('T_fasta_loader', "Cannot update the feature '%name' with a uniquename of '%uname' and type of '%type' as it
-            conflicts with an existing feature with the same uniquename and type.", array(
-            '%name' => $name,'%uname' => $uname,'%type' => $type
-          ));
-          return 0;
-        }
-
-        // the changes to the uniquename don't conflict so proceed with the update
-        $values = array('uniquename' => $uname);
-        $match = array(
-          'name' => $name,
-          'organism_id' => $organism_id,
-          'type_id' => $cvterm->cvterm_id
-        );
-
-        // perform the update
-        $success = chado_update_record('feature', $match, $values);
-        if (!$success) {
-          tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to update feature '%name' ('%name')", array(
-            '%name' => $name,'%uiname' => $uname
-          ));
-          return 0;
-        }
-      }
-    }
-
-    // If the user wants to match on the unique name field.
-    if (strcmp($match_type, 'Unique name') == 0) {
-      // If we're matching on the uniquename and have a new name then
-      // we want to update the name.
-      $values = array();
-      if ($name) {
-        $values = array('name' => $name);
-        $match = array(
-          'uniquename' => $uname,
-          'organism_id' => $organism_id,
-          'type_id' => $cvterm->cvterm_id
-        );
-        $success = chado_update_record('feature', $match, $values);
-        if (!$success) {
-          tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to update feature '%name' ('%name')", array(
-            '%name' => $name,'%uiname' => $uname
-          ));
-          return 0;
-        }
-      }
-    }
-  }
-
-  // Update the residues for this feature
-  tripal_feature_load_fasta_residues($fh, $feature->feature_id, $seq_start, $seq_end);
-
-  // add in the analysis link
-  if ($analysis_id) {
-    // if the association doens't alredy exist then add one
-    $values = array(
-      'analysis_id' => $analysis_id,
-      'feature_id' => $feature->feature_id
-    );
-    $results = chado_select_record('analysisfeature', array('analysisfeature_id'), $values);
-    if (count($results) == 0) {
-      $success = chado_insert_record('analysisfeature', $values);
-      if (!$success) {
-        tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to associate analysis and feature '%name' ('%name')", array(
-          '%name' => $name,'%uname' => $uname
-        ));
-        return 0;
-      }
-    }
-  }
-
-  // now add the database cross reference
-  if ($db_id) {
-    // check to see if this accession reference exists, if not add it
-    $values = array(
-      'db_id' => $db_id,
-      'accession' => $accession
-    );
-    $results = chado_select_record('dbxref', array('dbxref_id'), $values);
-    // if the accession doesn't exist then add it
-    if (count($results) == 0) {
-      $results = chado_insert_record('dbxref', $values);
-      if (!$results) {
-        tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to add database accession '%accession'", array(
-          '%accession' => $accession));
-        return 0;
-      }
-      $results = chado_select_record('dbxref', array('dbxref_id'), $values);
-      if (count($results) == 1) {
-        $dbxref = $results[0];
-      }
-      else {
-        tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to retreive newly inserted dbxref '%name (%uname)'", array(
-          '%name' => $name,'%uname' => $numane));
-        return 0;
-      }
-    }
-    else {
-      $dbxref = $results[0];
-    }
-
-    // check to see if the feature dbxref record exists if not, then add it
-    $values = array(
-      'feature_id' => $feature->feature_id,
-      'dbxref_id' => $dbxref->dbxref_id
-    );
-    $results = chado_select_record('feature_dbxref', array('feature_dbxref_id'), $values);
-    if (count($results) == 0) {
-      $success = chado_insert_record('feature_dbxref', $values);
-      if (!$success) {
-        tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to add associate database accession '%accession' with feature", array(
-          '%accession' => $accession
-        ));
-        return 0;
-      }
-    }
-  }
-
-  // now add in the relationship if one exists. If not, then add it
-  if ($rel_type) {
-    $values = array('organism_id' => $organism_id,'uniquename' => $parent,
-      'type_id' => $parentcvterm->cvterm_id
-    );
-    $results = chado_select_record('feature', array('feature_id'
-    ), $values);
-    if (count($results) != 1) {
-      tripal_report_error('T_fasta_loader', "Cannot find a unique feature for the parent '%parent' of type
-               '%type' for the feature.", array(
-        '%parent' => $parent,'%type' => $parent_type
-      ));
-      return 0;
-    }
-    $parent_feature = $results[0];
-
-    // check to see if the relationship already exists if not then add it
-    $values = array(
-      'subject_id' => $feature->feature_id,
-      'object_id' => $parent_feature->feature_id,
-      'type_id' => $relcvterm->cvterm_id
-    );
-    $results = chado_select_record('feature_relationship', array('feature_relationship_id'), $values);
-    if (count($results) == 0) {
-      $success = chado_insert_record('feature_relationship', $values);
-      if (!$success) {
-        tripal_report_error('T_fasta_loader', TRIPAL_ERROR, "Failed to add associate database accession '%accession' with feature", array(
-          '%accession' => $accession
-        ));
-        return 0;
-      }
-    }
-  }
-}
-
-/**
- * Adds the residues column to the feature.
- *
- * This function seeks to the proper location in the file for the sequence
- * and reads in chunks of sequence and appends them to the feature.residues
- * column in the database.
- *
- * @param unknown $fh
- * @param unknown $feature_id
- * @param unknown $seq_start
- * @param unknown $seq_end
- */
-function tripal_feature_load_fasta_residues($fh, $feature_id, $seq_start, $seq_end) {
-
-  // First position the file at the beginning of the sequence
-  fseek($fh, $seq_start, SEEK_SET);
-  $chunk_size = 100000000;
-  $chunk = '';
-  $seqlen = ($seq_end - $seq_start) + 1;
-
-  // Calculate the interval at which we updated the precent complete.
-  $interval = intval($seqlen * 0.01);
-  if ($interval < 1) {
-    $interval = 1;
-  }
-  // We don't to repeat the update too often or it slows things down, so
-  // if the interval is less than 1000 then bring it up to that.
-  if ($interval < 100000) {
-    $interval = 100000;
-  }
-  $chunk_intv_read = 0;
-  $intv_read = 0;
-  $num_read = 0;
-  $total_seq_size = 0;
-
-  // First, make sure we don't have a null in the residues
-  $sql = "UPDATE {feature} SET residues = '' WHERE feature_id = :feature_id";
-  chado_query($sql, array(':feature_id' => $feature_id
-  ));
-
-  // Read in the lines until we reach the end of the sequence. Once we
-  // get a specific bytes read then append the sequence to the one in the
-  // database.
-  print "Sequence complete: 0%. Memory: " . number_format(memory_get_usage()) . " bytes. \r";
-  while ($line = fgets($fh)) {
-    $num_read += strlen($line) + 1;
-    $chunk_intv_read += strlen($line) + 1;
-    $intv_read += strlen($line) + 1;
-    $chunk .= trim($line);
-
-    // If we've read in enough of the sequence then append it to the database.
-    if ($chunk_intv_read >= $chunk_size) {
-      $sql = "
-        UPDATE {feature}
-        SET residues = residues || :chunk
-        WHERE feature_id = :feature_id
-      ";
-      $success = chado_query($sql, array(':feature_id' => $feature_id,':chunk' => $chunk
-      ));
-      if (!$success) {
-        return FALSE;
-      }
-      $total_seq_size += strlen($chunk);
-      $chunk = '';
-      $chunk_intv_read = 0;
-    }
-    if ($intv_read >= $interval) {
-      $percent = sprintf("%.2f", ($total_seq_size / $seqlen) * 100);
-      print "Sequence complete: " . $percent . "%. Memory: " . number_format(memory_get_usage()) .
-         " bytes. \r";
-      $intv_read = 0;
-    }
-
-    // If we've reached the ned of the sequence then break out of the loop
-    if (ftell($fh) == $seq_end) {
-      break;
-    }
-  }
-
-  // write the last bit of sequence if it remains
-  if (strlen($chunk) > 0) {
-    $sql = "
-        UPDATE {feature}
-        SET residues = residues || :chunk
-        WHERE feature_id = :feature_id
-      ";
-    $success = chado_query($sql, array(':feature_id' => $feature_id,':chunk' => $chunk
-    ));
-    if (!$success) {
-      return FALSE;
-    }
-    $total_seq_size += strlen($chunk);
-    $chunk = '';
-    $chunk_intv_read = 0;
-  }
-
-  // Now update the seqlen and md5checksum fields
-  $sql = "UPDATE {feature} SET seqlen = char_length(residues),  md5checksum = md5(residues) WHERE feature_id = :feature_id";
-  chado_query($sql, array(':feature_id' => $feature_id
-  ));
-
-  $percent = sprintf("%.2f", ($num_read / $seqlen) * 100);
-  print "Sequence complete: " . $percent . "%. Memory: " . number_format(memory_get_usage()) .
-     " bytes. \r";
-}

+ 0 - 2319
tripal_chado/includes/loaders/tripal_chado.gff_loader.inc

@@ -1,2319 +0,0 @@
-<?php
-/**
- * @file
- * Provides gff3 loading functionality. Creates features based on their specification
- * in a GFF3 file.
- */
-
-/**
- * @defgroup gff3_loader GFF3 Feature Loader
- * @ingroup tripal_chado
- * @{
- * Provides gff3 loading functionality. Creates features based on their specification in a GFF3 file.
- * @}
- */
-
-/**
- * The form to submit a GFF3 loading job
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_gff3_load_form() {
-
-  $form['gff_file']= array(
-    '#type'          => 'textfield',
-    '#title'         => t('GFF3 File'),
-    '#description'   => t('Please enter the full system path for the GFF file, or a path within the Drupal
-                           installation (e.g. /sites/default/files/xyz.gff).  The path must be accessible to the
-                           server on which this Drupal instance is running.'),
-    '#required' => TRUE,
-  );
-  // get the list of organisms
-  $sql = "SELECT * FROM {organism} ORDER BY genus, species";
-  $org_rset = chado_query($sql);
-  $organisms = array();
-  $organisms[''] = '';
-  while ($organism = $org_rset->fetchObject()) {
-    $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 to which these sequences are associated"),
-    '#required'    => TRUE,
-    '#options'     => $organisms,
-  );
-
-  // get the list of analyses
-  $sql = "SELECT * FROM {analysis} ORDER BY name";
-  $org_rset = chado_query($sql);
-  $analyses = array();
-  $analyses[''] = '';
-  while ($analysis = $org_rset->fetchObject()) {
-    $analyses[$analysis->analysis_id] = "$analysis->name ($analysis->program $analysis->programversion, $analysis->sourcename)";
-  }
-  $form['analysis_id'] = array(
-   '#title'       => t('Analysis'),
-   '#type'        => t('select'),
-   '#description' => t("Choose the analysis to which these features are associated.
-       Why specify an analysis for a data load?  All data comes
-       from some place, even if downloaded from Genbank. By specifying
-       analysis details for all data imports it allows an end user to reproduce the
-       data set, but at least indicates the source of the data."),
-   '#required'    => TRUE,
-   '#options'     => $analyses,
-  );
-
-  $form['line_number']= array(
-    '#type'          => 'textfield',
-    '#title'         => t('Start Line Number'),
-    '#description'   => t('Enter the line number in the GFF file where you would like to begin processing.  The
-      first line is line number 1.  This option is useful for examining loading problems with large GFF files.'),
-    '#size' => 10,
-  );
-
-  $form['landmark_type'] = array(
-    '#title'       => t('Landmark Type'),
-    '#type'        => t('textfield'),
-    '#description' => t("Optional. Use this field to specify a Sequence Ontology type
-       for the landmark sequences in the GFF fie (e.g. 'chromosome'). If the GFF file
-       contains a '##sequence-region' line that describes the landmark sequences to
-       which all others are aligned and a type is provided here then the features
-       will be created if they do not already exist.  If they do exist then this
-       field is not used."),
-  );
-
-  $form['alt_id_attr'] = array(
-    '#title'       => t('ID Attribute'),
-    '#type'        => t('textfield'),
-    '#description' => t("Optional. Sometimes lines in the GFF file are missing the
-      required ID attribute that specifies the unique name of the feature, but there
-      may be another attribute that can uniquely identify the feature.  If so,
-      you may specify the name of the attribute to use for the name."),
-  );
-
-  // Advanced Options
-  $form['advanced'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Advanced Options'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-
-  $form['advanced']['protein_names'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Protein Names'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE,
-    '#weight' => 5,
-  );
-
-  $form['advanced']['protein_names']['re_help'] = array(
-    '#type' => 'item',
-    '#markup' => t('A regular expression is an advanced method for extracting information from a string of text.
-                   If your GFF3 file does not contain polypeptide (or protein) features, but contains CDS features, proteins will be automatically created.
-                   By default the loader will give each protein a name based on the name of the corresponding mRNA followed by the "-protein" suffix.
-                   If you want to customize the name of the created protein, you can use the following regex.')
-  );
-  $form['advanced']['protein_names']['re_mrna'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Regular expression for the mRNA name'),
-    '#required' => FALSE,
-    '#description' => t('Enter the regular expression that will extract portions of
-       the mRNA unique name. For example, for a
-       mRNA with a unique name finishing by -RX (e.g. SPECIES0000001-RA),
-       the regular expression would be, "^(.*?)-R([A-Z]+)$".')
-  );
-  $form['advanced']['protein_names']['re_protein'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Replacement string for the protein name'),
-    '#required' => FALSE,
-    '#description' => t('Enter the replacement string that will be used to create
-       the protein name based on the mRNA regular expression. For example, for a
-       mRNA regular expression "^(.*?)-R()[A-Z]+)$", the corresponding protein regular
-       expression would be "$1-P$2".')
-  );
-
-  $form['advanced']['import_options'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Import Options'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE,
-    '#weight' => 0,
-  );
-
-  $form['advanced']['import_options']['use_transaction']= array(
-    '#type' => 'checkbox',
-    '#title' => t('Use a transaction'),
-    '#required' => FALSE,
-    '#description' => t('Use a database transaction when loading the GFF file.  If an error occurs
-      the entire datset loaded prior to the failure will be rolled back and will not be available
-      in the database.  If this option is unchecked and failure occurs all records up to the point
-      of failure will be present in the database.'),
-    '#default_value' => 1,
-  );
-  $form['advanced']['import_options']['add_only']= array(
-    '#type' => 'checkbox',
-    '#title' => t('Import only new features'),
-    '#required' => FALSE,
-    '#description' => t('The job will skip features in the GFF file that already
-                         exist in the database and import only new features.'),
-  );
-  $form['advanced']['import_options']['update']= array(
-    '#type' => 'checkbox',
-    '#title' => t('Import all and update'),
-    '#required' => FALSE,
-    '#default_value' => 'checked',
-    '#description' => t('Existing features will be updated and new features will be added.  Attributes
-                         for a feature that are not present in the GFF but which are present in the
-                         database will not be altered.'),
-    '#default_value' => 1,
-  );
-// SPF: there are bugs in refreshing and removing features.  The bugs arise
-//      if a feature in the GFF does not have a uniquename. GenSAS will auto
-//      generate this uniquename and it will not be the same as a previous
-//      load because it uses the date.  This causes orphaned CDS/exons, UTRs
-//      to be left behind during a delete or refresh.  So, the short term
-//      fix is to remove these options.
-//   $form['import_options']['refresh']= array(
-//     '#type' => 'checkbox',
-//     '#title' => t('Import all and replace'),
-//     '#required' => FALSE,
-//     '#description' => t('Existing features will be updated and feature properties not
-//                          present in the GFF file will be removed.'),
-//   );
-//   $form['import_options']['remove']= array(
-//     '#type' => 'checkbox',
-//     '#title' => t('Delete features'),
-//     '#required' => FALSE,
-//     '#description' => t('Features present in the GFF file that exist in the database
-//                          will be removed rather than imported'),
-//   );
-  $form['advanced']['import_options']['create_organism']= array(
-    '#type' => 'checkbox',
-    '#title' => t('Create organism'),
-    '#required' => FALSE,
-    '#description' => t('The Tripal GFF loader supports the "organism" attribute. This allows features of a
-       different organism to be aligned to the landmark sequence of another species.  The format of the
-       attribute is "organism=[genus]:[species]", where [genus] is the organism\'s genus and [species] is the
-       species name. Check this box to automatically add the organism to the database if it does not already exists.
-       Otherwise lines with an oraganism attribute where the organism is not present in the database will be skipped.'),
-  );
-
-  $form['advanced']['targets'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Targets'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE,
-    '#weight' => 1,
-  );
-  $form['advanced']['targets']['adesc'] = array(
-    '#markup' => t("When alignments are represented in the GFF file (e.g. such as
-       alignments of cDNA sequences to a whole genome, or blast matches), they are
-       represented using two feature types: 'match' (or cDNA_match, EST_match, etc.)
-       and 'match_part'.  These features may also have a 'Target' attribute to
-       specify the sequence that is being aligned.
-       However, the organism to which the aligned sequence belongs may not be present in the
-       GFF file.  Here you can specify the organism and feature type of the target sequences.
-       The options here will apply to all targets unless the organism and type are explicity
-       set in the GFF file using the 'target_organism' and 'target_type' attributes."),
-  );
-  $form['advanced']['targets']['target_organism_id'] = array(
-    '#title'       => t('Target Organism'),
-    '#type'        => t('select'),
-    '#description' => t("Optional. Choose the organism to which target sequences belong.
-      Select this only if target sequences belong to a different organism than the
-      one specified above. And only choose an organism here if all of the target sequences
-      belong to the same species.  If the targets in the GFF file belong to multiple
-      different species then the organism must be specified using the 'target_organism=genus:species'
-      attribute in the GFF file."),
-    '#options'     => $organisms,
-  );
-  $form['advanced']['targets']['target_type'] = array(
-    '#title'       => t('Target Type'),
-    '#type'        => t('textfield'),
-    '#description' => t("Optional. If the unique name for a target sequence is not unique (e.g. a protein
-       and an mRNA have the same name) then you must specify the type for all targets in the GFF file. If
-       the targets are of different types then the type must be specified using the 'target_type=type' attribute
-       in the GFF file. This must be a valid Sequence Ontology (SO) term."),
-  );
-  $form['advanced']['targets']['create_target']= array(
-    '#type' => 'checkbox',
-    '#title' => t('Create Target'),
-    '#required' => FALSE,
-    '#description' => t("If the target feature cannot be found, create one using the organism and type specified above, or
-       using the 'target_organism' and 'target_type' fields specified in the GFF file.  Values specified in the
-       GFF file take precedence over those specified above."),
-  );
-
-  $form['button'] = array(
-    '#type' => 'submit',
-    '#value' => t('Import GFF3 file'),
-    '#weight' => 10,
-  );
-
-  return $form;
-}
-
-/**
- * Validate the GFF3 loading job form
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_gff3_load_form_validate($form, &$form_state) {
-
-  $gff_file = trim($form_state['values']['gff_file']);
-  $organism_id = $form_state['values']['organism_id'];
-  $target_organism_id = $form_state['values']['target_organism_id'];
-  $target_type = trim($form_state['values']['target_type']);
-  $create_target = $form_state['values']['create_target'];
-  $create_organism = $form_state['values']['create_organism'];
-  $add_only = $form_state['values']['add_only'];
-  $update   = $form_state['values']['update'];
-  $refresh  = 0; //$form_state['values']['refresh'];
-  $remove   = 0; //$form_state['values']['remove'];
-  $use_transaction   = $form_state['values']['use_transaction'];
-  $line_number   = trim($form_state['values']['line_number']);
-  $landmark_type   = trim($form_state['values']['landmark_type']);
-  $alt_id_attr   = trim($form_state['values']['alt_id_attr']);
-  $re_mrna = trim($form_state['values']['re_mrna']);
-  $re_protein = trim($form_state['values']['re_protein']);
-
-
-
-  // check to see if the file is located local to Drupal
-  $gff_file = trim($gff_file);
-  $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $gff_file;
-  if (!file_exists($dfile)) {
-    // if not local to Drupal, the file must be someplace else, just use
-    // the full path provided
-    $dfile = $gff_file;
-  }
-  if (!file_exists($dfile)) {
-    form_set_error('gff_file', t("Cannot find the file on the system. Check that the file exists or that the web server has permissions to read the file."));
-  }
-
-  // @coder-ignore: there are no functions being called here
-  if (($add_only AND ($update   OR $refresh  OR $remove)) OR
-      ($update   AND ($add_only OR $refresh  OR $remove)) OR
-      ($refresh  AND ($update   OR $add_only OR $remove)) OR
-      ($remove   AND ($update   OR $refresh  OR $add_only))) {
-    form_set_error('add_only', t("Please select only one checkbox from the import options section"));
-  }
-
-  if ($line_number and !is_numeric($line_number) or $line_number < 0) {
-    form_set_error('line_number', t("Please provide an integer line number greater than zero."));
-  }
-
-  if (!($re_mrna and $re_protein) and ($re_mrna or $re_protein)) {
-    form_set_error('re_uname', t("You must provide both a regular expression for mRNA and a replacement string for protein"));
-  }
-
-  // check the regular expression to make sure it is valid
-  set_error_handler(function() {}, E_WARNING);
-  $result_re = preg_match("/" . $re_mrna . "/", null);
-  $result = preg_replace("/" . $re_mrna . "/", $re_protein, null);
-  restore_error_handler();
-  if ($result_re === FALSE) {
-    form_set_error('re_mrna', 'Invalid regular expression.');
-  } else if ($result === FALSE) {
-    form_set_error('re_protein', 'Invalid replacement string.');
-  }
-}
-
-/**
- * Submit the GFF3 loading job
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_gff3_load_form_submit($form, &$form_state) {
-  global $user;
-
-  $gff_file = trim($form_state['values']['gff_file']);
-  $organism_id = $form_state['values']['organism_id'];
-  $add_only = $form_state['values']['add_only'];
-  $update   = $form_state['values']['update'];
-  $refresh  = 0; //$form_state['values']['refresh'];
-  $remove   = 0; //$form_state['values']['remove'];
-  $analysis_id = $form_state['values']['analysis_id'];
-  $use_transaction   = $form_state['values']['use_transaction'];
-  $target_organism_id = $form_state['values']['target_organism_id'];
-  $target_type = trim($form_state['values']['target_type']);
-  $create_target = $form_state['values']['create_target'];
-  $line_number   = trim($form_state['values']['line_number']);
-  $landmark_type   = trim($form_state['values']['landmark_type']);
-  $alt_id_attr   = trim($form_state['values']['alt_id_attr']);
-  $create_organism = $form_state['values']['create_organism'];
-  $re_mrna = trim($form_state['values']['re_mrna']);
-  $re_protein = trim($form_state['values']['re_protein']);
-
-
-  $args = array($gff_file, $organism_id, $analysis_id, $add_only,
-    $update, $refresh, $remove, $use_transaction, $target_organism_id,
-    $target_type, $create_target, $line_number, $landmark_type, $alt_id_attr,
-    $create_organism, $re_mrna, $re_protein);
-
-  $type = '';
-  if ($add_only) {
-    $type = 'import only new features';
-  }
-  if ($update) {
-    $type = 'import all and update';
-  }
-  if ($refresh) {
-    $type = 'import all and replace';
-  }
-  if ($remove) {
-    $type = 'delete features';
-  }
-  $fname = preg_replace("/.*\/(.*)/", "$1", $gff_file);
-  $includes = array(
-    module_load_include('inc', 'tripal_chado', 'includes/loaders/tripal_chado.gff_loader'),
-  );
-  tripal_add_job("$type GFF3 file: $fname", 'tripal_chado',
-    'tripal_feature_load_gff3', $args, $user->uid, 10, $includes);
-
-  return '';
-}
-
-/**
- * Actually load a GFF3 file. This is the function called by tripal jobs
- *
- * @param $gff_file
- *   The full path to the GFF file on the filesystem
- * @param $organism_id
- *   The organism_id of the organism to which the features in the GFF belong
- * @param $analysis_id
- *   The anlaysis_id of the analysis from which the features in the GFF were generated
- * @param $add_only
- *   Set to 1 if feature should be added only.  In the case where a feature
- *   already exists, it will not be updated.  Default is 0
- * @param $update
- *   Set to 1 to update existing features. New features will be added. Attributes
- *   for a feature that are not present in the GFF but which are present in the
- *   database will not be altered. Default is 1
- * @param $refresh
- *   Set to 1 to update existing features. New features will be added. Attributes
- *   for a feature that are not present in the GFF but which are present in the
- *   database will be removed. Default is 0
- * @param $remove
- *   Set to 1 to remove features present in the GFF file that exist in the database.
- *   Default is 0.
- * @param $use_transaction
- *   Set to 1 to use a transaction when loading the GFF. Any failure during
- *   loading will result in the rollback of any changes. Default is 1.
- * @param $target_organism_id
- *   If the GFF file contains a 'Target' attribute then the feature and the
- *   target will have an alignment created, but to find the proper target
- *   feature the target organism must also be known.  If different from the
- *   organism specified for the GFF file, then use  this argument to specify
- *   the target organism.  Only use this argument if all target sequences belong
- *   to the same species. If the targets in the GFF file belong to multiple
- *   different species then the organism must be specified using the
- *   'target_organism=genus:species' attribute in the GFF file. Default is NULL.
- * @param $target_type
- *   If the GFF file contains a 'Target' attribute then the feature and the
- *   target will have an alignment created, but to find the proper target
- *   feature the target organism must also be known.  This can be used to
- *   specify the target feature type to help with identification of the target
- *   feature.  Only use this argument if all target sequences types are the same.
- *   If the targets are of different types then the type must be specified using
- *   the 'target_type=type' attribute in the GFF file. This must be a valid
- *   Sequence Ontology (SO) term. Default is NULL
- * @param $create_target
- *   Set to 1 to create the target feature if it cannot be found in the
- *   database. Default is 0
- * @param $start_line
- *   Set this to the line in the GFF file where importing should start. This
- *   is useful for testing and debugging GFF files that may have problems and
- *   you want to start at a particular line to speed testing.  Default = 1
- * @param $landmark_type
- *   Use this argument to specify a Sequence Ontology term name for the landmark
- *   sequences in the GFF fie (e.g. 'chromosome'), if the GFF file contains a
- *   '##sequence-region' line that describes the landmark sequences. Default = ''
- * @param $alt_id_attr
- *   Sometimes lines in the GFF file are missing the required ID attribute that
- *   specifies the unique name of the feature. If so, you may specify the
- *   name of an existing attribute to use for the ID.
- * @param $create_organism
- *   The Tripal GFF loader supports the "organism" attribute. This allows
- *   features of a different organism to be aligned to the landmark sequence of
- *   another species. The format of the attribute is "organism=[genus]:[species]",
- *   where [genus] is the organism's genus and [species] is the species name.
- *   Check this box to automatically add the organism to the database if it does
- *   not already exists. Otherwise lines with an oraganism attribute where the
- *   organism is not present in the database will be skipped.
- * @param $re_mrna A
- *          regular expression to extract portions from mRNA id
- * @param $re_protein A
- *          replacement string to generate the protein id
- * @param $job
- *  The tripal job_id.  Only used by the Tripal Jobs subsystem.
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3($gff_file, $organism_id, $analysis_id,
-  $add_only = 0, $update = 1, $refresh = 0, $remove = 0, $use_transaction = 1,
-  $target_organism_id = NULL, $target_type = NULL,  $create_target = 0,
-  $start_line = 1, $landmark_type = '', $alt_id_attr = '',  $create_organism = FALSE,
-  $re_mrna = '', $re_protein = '', $job = NULL) {
-
-  $ret = array();
-  $date = getdate();
-
-  // An array that stores CVterms that have been looked up so we don't have
-  // to do the database query every time.
-  $cvterm_lookup = array();
-
-  // An array that stores Landmarks that have been looked up so we don't have
-  // to do the database query every time.
-  $landmark_lookup = array();
-
-  // empty the temp tables
-  $sql = "DELETE FROM {tripal_gff_temp}";
-  chado_query($sql);
-  $sql = "DELETE FROM {tripal_gffcds_temp}";
-  chado_query($sql);
-  $sql = "DELETE FROM {tripal_gffprotein_temp}";
-  chado_query($sql);
-
-  // begin the transaction
-  $transaction = null;
-  if ($use_transaction) {
-    $transaction = db_transaction();
-    print "\nNOTE: Loading of this GFF file is performed using a database transaction. \n" .
-         "If the load fails or is terminated prematurely then the entire set of \n" .
-         "insertions/updates is rolled back and will not be found in the database\n\n";
-  }
-  try {
-
-    // check to see if the file is located local to Drupal
-    $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $gff_file;
-    if (!file_exists($dfile)) {
-      // if not local to Drupal, the file must be someplace else, just use
-      // the full path provided
-      $dfile = $gff_file;
-    }
-    if (!file_exists($dfile)) {
-      tripal_report_error('tripal_chado', TRIPAL_ERROR, "Cannot find the file: %dfile",
-        array('%dfile' => $dfile));
-      return 0;
-    }
-
-    print "Opening $gff_file\n";
-
-    //$lines = file($dfile,FILE_SKIP_EMPTY_LINES);
-    $fh = fopen($dfile, 'r');
-    if (!$fh) {
-      tripal_report_error('tripal_chado', TRIPAL_ERROR, "cannot open file: %dfile",
-        array('%dfile' => $dfile));
-      return 0;
-    }
-    $filesize = filesize($dfile);
-
-    // get the controlled vocaubulary that we'll be using.  The
-    // default is the 'sequence' ontology
-    $sql = "SELECT * FROM {cv} WHERE name = :cvname";
-    $cv = chado_query($sql, array(':cvname' => 'sequence'))->fetchObject();
-    if (!$cv) {
-      tripal_report_error('tripal_chado', TRIPAL_ERROR,
-        "Cannot find the 'sequence' ontology", array());
-      return '';
-    }
-    // get the organism for which this GFF3 file belongs
-    $sql = "SELECT * FROM {organism} WHERE organism_id = :organism_id";
-    $organism = chado_query($sql, array(':organism_id' => $organism_id))->fetchObject();
-
-    $interval = intval($filesize * 0.0001);
-    if ($interval == 0) {
-      $interval = 1;
-    }
-    $in_fasta = 0;
-    $line_num = 0;
-    $num_read = 0;
-    $intv_read = 0;
-
-    // prepare the statement used to get the cvterm for each feature.
-    $sel_cvterm_sql = "
-      SELECT CVT.cvterm_id, CVT.cv_id, CVT.name, CVT.definition,
-        CVT.dbxref_id, CVT.is_obsolete, CVT.is_relationshiptype
-      FROM {cvterm} CVT
-        INNER JOIN {cv} CV on CVT.cv_id = CV.cv_id
-        LEFT JOIN {cvtermsynonym} CVTS on CVTS.cvterm_id = CVT.cvterm_id
-      WHERE CV.cv_id = :cv_id and
-       (lower(CVT.name) = lower(:name) or lower(CVTS.synonym) = lower(:synonym))
-     ";
-
-    // If a landmark type was provided then pre-retrieve that.
-    if ($landmark_type) {
-      $query = array(
-        ':cv_id' => $cv->cv_id,
-        ':name' => $landmark_type,
-        ':synonym' => $landmark_type
-      );
-      $result = chado_query($sel_cvterm_sql, $query);
-      $landmark_cvterm = $result->fetchObject();
-      if (!$landmark_cvterm) {
-        tripal_report_error('tripal_chado', TRIPAL_ERROR,
-          'cannot find landmark feature type \'%landmark_type\'.',
-          array('%landmark_type' => $landmark_type));
-        return '';
-      }
-    }
-
-    // iterate through each line of the GFF file
-    print "Parsing Line $line_num (0.00%). Memory: " . number_format(memory_get_usage()) . " bytes\r";
-    while ($line = fgets($fh)) {
-      $line_num++;
-      $size = drupal_strlen($line);
-      $num_read += $size;
-      $intv_read += $size;
-
-      if ($line_num < $start_line) {
-        continue;
-      }
-
-      // update the job status every 1% features
-      if ($job and $intv_read >= $interval) {
-        $intv_read = 0;
-        $percent = sprintf("%.2f", ($num_read / $filesize) * 100);
-        print "Parsing Line $line_num (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
-        tripal_set_job_progress($job, intval(($num_read / $filesize) * 100));
-      }
-
-      // check to see if we have FASTA section, if so then set the variable
-      // to start parsing
-      if (preg_match('/^##FASTA/i', $line)) {
-        print "Parsing FASTA portion...\n";
-        if ($remove) {
-          // we're done because this is a delete operation so break out of the loop.
-          break;
-        }
-        tripal_feature_load_gff3_fasta($fh, $interval, $num_read, $intv_read, $line_num, $filesize, $job);
-        continue;
-      }
-      // if the ##sequence-region line is present then we want to add a new feature
-      if (preg_match('/^##sequence-region (.*?) (\d+) (\d+)$/i', $line, $region_matches)) {
-        $rid = $region_matches[1];
-        $rstart = $region_matches[2];
-        $rend = $region_matches[3];
-        if ($landmark_type) {
-          tripal_feature_load_gff3_feature($organism, $analysis_id, $landmark_cvterm, $rid,
-            $rid, '', 'f', 'f', 1, 0);
-        }
-        continue;
-      }
-
-      // skip comments
-      if (preg_match('/^#/', $line)) {
-        continue;
-      }
-
-      // skip empty lines
-      if (preg_match('/^\s*$/', $line)) {
-        continue;
-      }
-
-      // get the columns
-      $cols = explode("\t", $line);
-      if (sizeof($cols) != 9) {
-        tripal_report_error('tripal_chado', TRIPAL_ERROR, 'improper number of columns on line %line_num',
-          array('%line_num' => $line_num));
-        return '';
-      }
-
-      // get the column values
-      $landmark = $cols[0];
-      $source   = $cols[1];
-      $type     = $cols[2];
-      $start    = $cols[3];
-      $end      = $cols[4];
-      $score    = $cols[5];
-      $strand   = $cols[6];
-      $phase    = $cols[7];
-      $attrs    = explode(";", $cols[8]);  // split by a semicolon
-
-      // ready the start and stop for chado.  Chado expects these positions
-      // to be zero-based, so we substract 1 from the fmin
-      $fmin = $start - 1;
-      $fmax = $end;
-      if ($end < $start) {
-        $fmin = $end - 1;
-        $fmax = $start;
-      }
-
-      // format the strand for chado
-      if (strcmp($strand, '.') == 0) {
-        $strand = 0;
-      }
-      elseif (strcmp($strand, '+') == 0) {
-        $strand = 1;
-      }
-      elseif (strcmp($strand, '-') == 0) {
-        $strand = -1;
-      }
-      if (strcmp($phase, '.') == 0) {
-        $phase = '';
-      }
-      if (array_key_exists($type, $cvterm_lookup)) {
-        $cvterm = $cvterm_lookup[$type];
-      }
-      else {
-        $result = chado_query($sel_cvterm_sql, array(':cv_id' => $cv->cv_id, ':name' => $type, ':synonym' => $type));
-        $cvterm = $result->fetchObject();
-        $cvterm_lookup[$type] = $cvterm;
-        if (!$cvterm) {
-          tripal_report_error('tripal_chado', TRIPAL_ERROR, 'cannot find feature term \'%type\' on line %line_num of the GFF file',
-            array('%type' => $type, '%line_num' => $line_num));
-          return '';
-        }
-      }
-
-      // break apart each of the attributes
-      $tags = array();
-      $attr_name = '';
-      $attr_uniquename = '';
-      $attr_residue_info = '';
-      $attr_locgroup = 0;
-      $attr_fmin_partial = 'f';
-      $attr_fmax_partial = 'f';
-      $attr_is_obsolete = 'f';
-      $attr_is_analysis = 'f';
-      $attr_others = [];
-      $residues = '';
-
-      // the organism to which a feature belongs can be set in the GFF
-      // file using the 'organism' attribute.  By default we
-      // set the $feature_organism variable to the default organism for the landmark
-      $attr_organism = '';
-      $feature_organism = $organism;
-
-      foreach ($attrs as $attr) {
-        $attr = rtrim($attr);
-        $attr = ltrim($attr);
-        if (strcmp($attr, '')==0) {
-          continue;
-        }
-        if (!preg_match('/^[^\=]+\=.+$/', $attr)) {
-          tripal_report_error('tripal_chado', TRIPAL_ERROR, 'Attribute is not correctly formatted on line %line_num: %attr',
-            array('%line_num' => $line_num, '%attr' => $attr));
-          return '';
-        }
-
-        // break apart each tag
-        $tag = preg_split("/=/", $attr, 2);  // split by equals sign
-
-        // multiple instances of an attribute are separated by commas
-        $tag_name = $tag[0];
-        if (!array_key_exists($tag_name, $tags)) {
-          $tags[$tag_name] = array();
-        }
-        $tags[$tag_name] = array_merge($tags[$tag_name], explode(",", $tag[1]));  // split by comma
-
-
-        // replace the URL escape codes for each tag
-        for ($i = 0; $i < count($tags[$tag_name]); $i++) {
-          $tags[$tag_name][$i] = urldecode($tags[$tag_name][$i]);
-        }
-
-        // get the name and ID tags
-        $skip_feature = 0;  // if there is a problem with any of the attributes this variable gets set
-        if (strcmp($tag_name, 'ID') == 0) {
-          $attr_uniquename =  urldecode($tag[1]);
-        }
-        elseif (strcmp($tag_name, 'Name') == 0) {
-          $attr_name =  urldecode($tag[1]);
-        }
-        elseif (strcmp($tag_name, 'organism') == 0) {
-          $attr_organism = urldecode($tag[1]);
-          $org_matches = array();
-          if (preg_match('/^(.*?):(.*?)$/', $attr_organism, $org_matches)) {
-            $values = array(
-              'genus' => $org_matches[1],
-              'species' => $org_matches[2],
-            );
-            $org = chado_select_record('organism', array("*"), $values);
-            if (count($org) == 0) {
-              if ($create_organism) {
-                $feature_organism = (object) chado_insert_record('organism', $values);
-                if (!$feature_organism) {
-                  tripal_report_error('tripal_chado', TRIPAL_ERROR, "Could not add the organism, '%org', from line %line. Skipping this line. ",
-                    array('%org' => $attr_organism, '%line' => $line_num));
-                  $skip_feature = 1;
-                }
-              }
-              else {
-                tripal_report_error('tripal_chado', TRIPAL_ERROR, "The organism attribute '%org' on line %line does not exist. Skipping this line. ",
-                  array('%org' => $attr_organism, '%line' => $line_num));
-                $skip_feature = 1;
-              }
-            }
-            else {
-              // We found the organism in the database so use it.
-              $feature_organism = $org[0];
-            }
-          }
-          else {
-            tripal_report_error('tripal_chado', TRIPAL_ERROR, "The organism attribute '%org' on line %line is not properly formated. It " .
-              "should be of the form: organism=Genus:species.  Skipping this line.",
-              array('%org' => $attr_organism, '%line' => $line_num));
-            $skip_feature = 1;
-          }
-        }
-        // Get the list of non-reserved attributes.
-        elseif (strcmp($tag_name, 'Alias') != 0        and strcmp($tag_name, 'Parent') != 0 and
-                strcmp($tag_name, 'Target') != 0       and strcmp($tag_name, 'Gap') != 0 and
-                strcmp($tag_name, 'Derives_from') != 0 and strcmp($tag_name, 'Note') != 0 and
-                strcmp($tag_name, 'Dbxref') != 0       and strcmp($tag_name, 'Ontology_term') != 0 and
-                strcmp($tag_name, 'Is_circular') != 0  and strcmp($tag_name, 'target_organism') != 0 and
-                strcmp($tag_name, 'target_type') != 0  and strcmp($tag_name, 'organism' != 0)) {
-          foreach ($tags[$tag_name] as $value) {
-            $attr_others[$tag_name][] = $value;
-          }
-        }
-      }
-      // If neither name nor uniquename are provided then generate one.
-      if (!$attr_uniquename and !$attr_name) {
-        // Check if an alternate ID field is suggested, if so, then use
-        // that for the name.
-        if (array_key_exists($alt_id_attr, $tags)) {
-          $attr_uniquename = $tags[$alt_id_attr][0];
-          $attr_name = $attr_uniquename;
-        }
-        // If the row has a parent then generate a uniquename using the parent name
-        // add the date to the name in the event there are more than one child with
-        // the same parent.
-        elseif (array_key_exists('Parent', $tags)) {
-          $attr_uniquename = $tags['Parent'][0] . "-$type-$landmark-" . $date[0] . ":" . ($fmin + 1) . ".." . $fmax;
-          $attr_name = $attr_uniquename;
-        }
-        // Generate a unique name based on the date, type and location
-        // and set the name to simply be the type.
-        else {
-          $attr_uniquename = $date[0] . "-$type-$landmark:" . ($fmin + 1) . ".." . $fmax;
-          $attr_name = $type;
-        }
-      }
-
-      // If a name is not specified then use the unique name as the name
-      if (strcmp($attr_name, '') == 0) {
-        $attr_name = $attr_uniquename;
-      }
-
-      // If an ID attribute is not specified then we must generate a
-      // unique ID. Do this by combining the attribute name with the date
-      // and line number.
-      if (!$attr_uniquename) {
-        $attr_uniquename = $attr_name . '-' . $date[0] . '-' . $line_num;
-      }
-
-      // Make sure the landmark sequence exists in the database.  If the user
-      // has not specified a landmark type (and it's not required in the GFF
-      // format) then we don't know the type of the landmark so we'll hope
-      // that it's unique across all types for the organism. Only do this
-      // test if the landmark and the feature are different.
-      if (!$remove and !(strcmp($landmark, $attr_uniquename) == 0 or strcmp($landmark, $attr_name) == 0) and !in_array($landmark, $landmark_lookup)) {
-
-        $select = array(
-          'organism_id' => $organism->organism_id,
-          'uniquename'  => $landmark,
-        );
-        $columns = array('count(*) as num_landmarks');
-        if ($landmark_type) {
-          $select['type_id'] = array(
-            'name' => $landmark_type,
-          );
-        }
-        $count = chado_select_record('feature', $columns, $select);
-        if (!$count or count($count) == 0 or $count[0]->num_landmarks == 0) {
-          // now look for the landmark using the name rather than uniquename.
-          $select = array(
-            'organism_id' => $organism->organism_id,
-            'name'  => $landmark,
-          );
-          $columns = array('count(*) as num_landmarks');
-          if ($landmark_type) {
-            $select['type_id'] = array(
-              'name' => $landmark_type,
-            );
-          }
-          $count = chado_select_record('feature', $columns, $select);
-          if (!$count or count($count) == 0 or $count[0]->num_landmarks == 0) {
-            tripal_report_error('tripal_chado', TRIPAL_ERROR, "The landmark '%landmark' cannot be found for this organism (%species) " .
-                  "Please add the landmark and then retry the import of this GFF3 " .
-                  "file", array('%landmark' => $landmark, '%species' => $organism->genus . " " . $organism->species));
-            return '';
-          }
-          elseif ($count[0]->num_landmarks > 1) {
-            tripal_report_error('tripal_chado', TRIPAL_ERROR, "The landmark '%landmark' has more than one entry for this organism (%species) " .
-                  "Cannot continue", array('%landmark' => $landmark, '%species' => $organism->genus . " " . $organism->species));
-            return '';
-          }
-
-        }
-        if ($count[0]->num_landmarks > 1) {
-          tripal_report_error('tripal_chado', TRIPAL_ERROR, "The landmark '%landmark' is not unique for this organism. " .
-                "The features cannot be associated", array('%landmark' => $landmark));
-          return '';
-        }
-
-        // The landmark was found, remember it
-        $landmark_lookup[] = $landmark;
-      }
-/*
-      // If the option is to remove or refresh then we want to remove
-      // the feature from the database.
-      if ($remove or $refresh) {
-        // Next remove the feature itself.
-        $sql = "DELETE FROM {feature}
-                WHERE organism_id = %d and uniquename = '%s' and type_id = %d";
-        $match = array(
-          'organism_id' => $feature_organism->organism_id,
-          'uniquename'  => $attr_uniquename,
-          'type_id'     => $cvterm->cvterm_id
-        );
-        $result = chado_delete_record('feature', $match);
-        if (!$result) {
-          tripal_report_error('tripal_chado', TRIPAL_ERROR, "cannot delete feature %attr_uniquename",
-            array('%attr_uniquename' => $attr_uniquename));
-        }
-        $feature = 0;
-        unset($result);
-      }
- */
-      // Add or update the feature and all properties.
-      if ($update or $refresh or $add_only) {
-
-        // Add/update the feature.
-        $feature = tripal_feature_load_gff3_feature($feature_organism, $analysis_id, $cvterm,
-          $attr_uniquename, $attr_name, $residues, $attr_is_analysis,
-          $attr_is_obsolete, $add_only, $score);
-
-        if ($feature) {
-
-          // Add a record for this feature to the tripal_gff_temp table for
-          // later lookup.
-          $values = array(
-            'feature_id' => $feature->feature_id,
-            'organism_id' => $feature->organism_id,
-            'type_name' => $type,
-            'uniquename' => $feature->uniquename
-          );
-          // make sure this record doesn't already exist in our temp table
-          $results = chado_select_record('tripal_gff_temp', array('*'), $values);
-
-          if (count($results) == 0) {
-            $result = chado_insert_record('tripal_gff_temp', $values);
-            if (!$result) {
-              tripal_report_error('tripal_chado', TRIPAL_ERROR, "Cound not save record in temporary table, Cannot continue.", array());
-              exit;
-            }
-          }
-          // add/update the featureloc if the landmark and the ID are not the same
-          // if they are the same then this entry in the GFF is probably a landmark identifier
-          if (strcmp($landmark, $attr_uniquename) !=0 ) {
-            tripal_feature_load_gff3_featureloc($feature, $organism,
-              $landmark, $fmin, $fmax, $strand, $phase, $attr_fmin_partial,
-              $attr_fmax_partial, $attr_residue_info, $attr_locgroup);
-          }
-
-          // add any aliases for this feature
-          if (array_key_exists('Alias', $tags)) {
-            tripal_feature_load_gff3_alias($feature, $tags['Alias']);
-          }
-          // add any dbxrefs for this feature
-          if (array_key_exists('Dbxref', $tags)) {
-            tripal_feature_load_gff3_dbxref($feature, $tags['Dbxref']);
-          }
-          // add any ontology terms for this feature
-          if (array_key_exists('Ontology_term', $tags)) {
-            tripal_feature_load_gff3_ontology($feature, $tags['Ontology_term']);
-          }
-          // add parent relationships
-          if (array_key_exists('Parent', $tags)) {
-            tripal_feature_load_gff3_parents($feature, $cvterm, $tags['Parent'],
-              $feature_organism->organism_id, $strand, $phase, $fmin, $fmax);
-          }
-
-          // add target relationships
-          if (array_key_exists('Target', $tags)) {
-            tripal_feature_load_gff3_target($feature, $tags, $target_organism_id, $target_type, $create_target, $attr_locgroup);
-          }
-          // add gap information.  This goes in simply as a property
-          if (array_key_exists('Gap', $tags)) {
-            foreach ($tags['Gap'] as $value) {
-              tripal_feature_load_gff3_property($feature, 'Gap', $value);
-            }
-          }
-          // add notes. This goes in simply as a property
-          if (array_key_exists('Note', $tags)) {
-            foreach ($tags['Note'] as $value) {
-                tripal_feature_load_gff3_property($feature, 'Note', $value);
-            }
-          }
-          // add the Derives_from relationship (e.g. polycistronic genes).
-          if (array_key_exists('Derives_from', $tags)) {
-            tripal_feature_load_gff3_derives_from($feature, $cvterm, $tags['Derives_from'][0],
-              $feature_organism, $fmin, $fmax);
-          }
-          // add in the GFF3_source dbxref so that GBrowse can find the feature using the source column
-          $source_ref = array('GFF_source:' . $source);
-          tripal_feature_load_gff3_dbxref($feature, $source_ref);
-          // add any additional attributes
-          if ($attr_others) {
-            foreach ($attr_others as $tag_name => $values) {
-              foreach ($values as $value) {
-                tripal_feature_load_gff3_property($feature, $tag_name, $value);
-              }
-            }
-          }
-
-        }
-      }
-    }
-
-    // Do some last bit of processing.
-    if (!$remove) {
-
-      // First, add any protein sequences if needed.
-      $sql = "SELECT feature_id FROM {tripal_gffcds_temp} LIMIT 1 OFFSET 1";
-      $has_cds = chado_query($sql)->fetchField();
-      if ($has_cds) {
-        print "\nAdding protein sequences if CDS exist and no proteins in GFF...\n";
-        $sql = "
-          SELECT F.feature_id, F.name, F.uniquename, TGCT.strand,
-            CVT.cvterm_id, CVT.name as feature_type,
-            min(TGCT.fmin) as fmin, max(TGCT.fmax) as fmax,
-            TGPT.feature_id as protein_id, TGPT.fmin as protein_fmin,
-            TGPT.fmax as protein_fmax, FLM.uniquename as landmark
-          FROM {tripal_gffcds_temp} TGCT
-            INNER JOIN {feature} F on F.feature_id = TGCT.parent_id
-            INNER JOIN {cvterm} CVT on CVT.cvterm_id = F.type_id
-            INNER JOIN {featureloc} L on F.feature_id = L.feature_id
-            INNER JOIN {feature} FLM on L.srcfeature_id = FLM.feature_id
-            LEFT JOIN {tripal_gffprotein_temp} TGPT on TGPT.parent_id = F.feature_id
-          GROUP BY F.feature_id, F.name, F.uniquename, CVT.cvterm_id, CVT.name,
-            TGPT.feature_id, TGPT.fmin, TGPT.fmax, TGCT.strand, FLM.uniquename
-        ";
-        $results = chado_query($sql);
-        $protein_cvterm = tripal_get_cvterm(array(
-          'name' => 'polypeptide',
-          'cv_id' => array(
-            'name' => 'sequence'
-          )
-        ));
-        while ($result = $results->fetchObject()) {
-          // If a protein exists with this same parent then don't add a new
-          // protein.
-          if (!$result->protein_id) {
-            // Get details about this protein
-            if ($re_mrna and $re_protein) {
-              // We use a regex to generate protein name from mRNA name
-              $uname = preg_replace("/$re_mrna/", $re_protein, $result->uniquename);
-              $name =  $result->name;
-            }
-            else {
-              // No regex, use the default '-protein' suffix
-              $uname = $result->uniquename . '-protein';
-              $name =  $result->name;
-            }
-            $values = array(
-              'parent_id' => $result->feature_id,
-              'fmin' => $result->fmin
-            );
-            $min_phase = chado_select_record('tripal_gffcds_temp', array('phase'), $values);
-            $values = array(
-              'parent_id' => $result->feature_id,
-              'fmax' => $result->fmax
-            );
-            $max_phase = chado_select_record('tripal_gffcds_temp', array('phase'), $values);
-
-            $pfmin = $result->fmin;
-            $pfmax = $result->fmax;
-            if ($result->strand == '-1') {
-              $pfmax -= $max_phase[0]->phase;
-            }
-            else {
-              $pfmin += $min_phase[0]->phase;
-            }
-
-            // Add the new protein record.
-            $feature = tripal_feature_load_gff3_feature($organism, $analysis_id,
-              $protein_cvterm, $uname, $name, '', 'f', 'f', 1, 0);
-            // Add the derives_from relationship.
-            $cvterm = tripal_get_cvterm(array('cvterm_id' => $result->cvterm_id));
-            tripal_feature_load_gff3_derives_from($feature, $cvterm,
-              $result->uniquename, $organism, $pfmin, $pfmax);
-            // Add the featureloc record. Set the start of the protein to
-            // be the start of the coding sequence minus the phase.
-            tripal_feature_load_gff3_featureloc($feature, $organism, $result->landmark,
-              $pfmin, $pfmax, $result->strand, '', 'f', 'f', '', 0);
-          }
-        }
-      }
-
-      print "\nSetting ranks of children...\n";
-
-      // Get features in a relationship that are also children of an alignment.
-      $sql = "
-        SELECT DISTINCT F.feature_id, F.organism_id, F.type_id,
-          F.uniquename, FL.strand
-        FROM {tripal_gff_temp} TGT
-          INNER JOIN {feature} F                ON TGT.feature_id = F.feature_id
-          INNER JOIN {feature_relationship} FR  ON FR.object_id   = TGT.feature_id
-          INNER JOIN {cvterm} CVT               ON CVT.cvterm_id  = FR.type_id
-          INNER JOIN {featureloc} FL            ON FL.feature_id  = F.feature_id
-        WHERE CVT.name = 'part_of'
-      ";
-      $parents = chado_query($sql);
-
-      // Build and prepare the SQL for selecting the children relationship.
-      $sel_gffchildren_sql = "
-        SELECT DISTINCT FR.feature_relationship_id, FL.fmin, FR.rank
-        FROM {feature_relationship} FR
-          INNER JOIN {featureloc} FL on FL.feature_id = FR.subject_id
-          INNER JOIN {cvterm} CVT on CVT.cvterm_id = FR.type_id
-        WHERE FR.object_id = :feature_id AND CVT.name = 'part_of'
-        ORDER BY FL.fmin ASC
-      ";
-
-      // Now set the rank of any parent/child relationships.  The order is based
-      // on the fmin.  The start rank is 1.  This allows features with other
-      // relationships to be '0' (the default), and doesn't interfer with the
-      // ordering defined here.
-      $num_recs = $parents->rowCount();
-      $i = 1;
-      $interval = intval($num_recs * 0.0001);
-      if ($interval == 0) {
-        $interval = 1;
-      }
-      $percent = sprintf("%.2f", ($i / $num_recs) * 100);
-      print "Setting $i of $num_recs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
-
-      while ($parent = $parents->fetchObject()) {
-
-        if ($i % $interval == 0) {
-          $percent = sprintf("%.2f", ($i / $num_recs) * 100);
-          print "Setting $i of $num_recs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
-        }
-
-        // get the children
-        $result = chado_query($sel_gffchildren_sql, array(':feature_id' => $parent->feature_id));
-
-        // build an array of the children
-        $children = array();
-        while ($child = $result->fetchObject()) {
-           $children[] = $child;
-        }
-
-        // the children list comes sorted in ascending fmin
-        // but if the parent is on the reverse strand we need to
-        // reverse the order of the children.
-        if ($parent->strand == -1) {
-          arsort($children);
-        }
-
-        // first set the ranks to a negative number so that we don't
-        // get a duplicate error message when we try to change any of them
-        $rank = -1;
-        foreach ($children as $child) {
-          $match = array('feature_relationship_id' => $child->feature_relationship_id);
-          $values = array('rank' => $rank);
-          chado_update_record('feature_relationship', $match, $values);
-          $rank--;
-        }
-        // now set the rank correctly. The rank should start at 0.
-        $rank = 0;
-        foreach ($children as $child) {
-          $match = array('feature_relationship_id' => $child->feature_relationship_id);
-          $values = array('rank' => $rank);
-          //print "Was: " . $child->rank . " now $rank ($parent->strand)\n"     ;
-          chado_update_record('feature_relationship', $match, $values);
-          $rank++;
-        }
-        $i++;
-      }
-    }
-  }
-  catch (Exception $e) {
-    print "\n"; // make sure we start errors on new line
-    if ($use_transaction) {
-      $transaction->rollback();
-      print "FAILED: Rolling back database changes...\n";
-    }
-    else {
-      print "FAILED\n";
-    }
-    watchdog_exception('tripal_chado', $e);
-    return 0;
-  }
-
-  print "\nDone\n";
-  return 1;
-}
-
-/**
- * Load the derives from attribute for a gff3 feature
- *
- * @param $feature
- * @param $subject
- * @param $organism
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_derives_from($feature, $cvterm, $object,
-  $organism, $fmin, $fmax) {
-
-  $type = $cvterm->name;
-
-  // First look for the object feature in the temp table to get it's type.
-  $values = array(
-    'organism_id' => $organism->organism_id,
-    'uniquename' => $object,
-  );
-  $result = chado_select_record('tripal_gff_temp', array('type_name'), $values);
-  $type_id = NULL;
-  if (count($result) > 0) {
-    $otype = tripal_get_cvterm(array(
-      'name' => $result[0]->type_name,
-      'cv_id' => array(
-        'name' => 'sequence'
-      )
-    ));
-    if ($otype) {
-      $type_id = $otype->cvterm_id;
-    }
-  }
-
-  // If the object wasn't in the temp table then look for it in the
-  // feature table and get it's type.
-  if (!$type_id) {
-    $result = chado_select_record('feature', array('type_id'), $values);
-    if (count($result) > 1) {
-      watchdog("tripal_chado", "Cannot find feature type for, '%subject' , in 'derives_from' relationship. Multiple matching features exist with this uniquename.",
-        array('%subject' => $object), WATCHDOG_WARNING);
-      return '';
-    }
-    else if (count($result) == 0) {
-      watchdog("tripal_chado", "Cannot find feature type for, '%subject' , in 'derives_from' relationship.",
-        array('%subject' => $object), WATCHDOG_WARNING);
-      return '';
-    }
-    else {
-      $type_id = $result->type_id;
-    }
-  }
-
-  // Get the object feature.
-  $match = array(
-    'organism_id' => $organism->organism_id,
-    'uniquename' => $object,
-    'type_id' => $type_id,
-  );
-  $ofeature = chado_select_record('feature', array('feature_id'), $match);
-  if (count($ofeature) == 0) {
-    tripal_report_error('tripal_chado', TRIPAL_ERROR, "Could not add 'Derives_from' relationship " .
-      "for %uniquename and %subject.  Subject feature, '%subject', " .
-      "cannot be found", array('%uniquename' => $feature->uniquename, '%subject' => $subject));
-    return;
-  }
-
-  // If this feature is a protein then add it to the tripal_gffprotein_temp.
-  if ($type == 'protein' or $type == 'polypeptide') {
-    $values = array(
-      'feature_id' => $feature->feature_id,
-      'parent_id' => $ofeature[0]->feature_id,
-      'fmin' => $fmin,
-      'fmax' => $fmax
-    );
-    $result = chado_insert_record('tripal_gffprotein_temp', $values);
-    if (!$result) {
-      tripal_report_error('tripal_chado', TRIPAL_ERROR, "Cound not save record in temporary protein table, Cannot continue.", array());
-      exit;
-    }
-  }
-
-   // Now check to see if the relationship already exists. If it does
-   // then just return.
-  $values = array(
-    'object_id' => $ofeature[0]->feature_id,
-    'subject_id' => $feature->feature_id,
-    'type_id' => array(
-       'cv_id' => array(
-          'name' => 'sequence'
-        ),
-       'name' => 'derives_from',
-    ),
-    'rank' => 0
-  );
-  $rel = chado_select_record('feature_relationship', array('*'), $values);
-  if (count($rel) > 0) {
-    return;
-  }
-
-  // finally insert the relationship if it doesn't exist
-  $ret = chado_insert_record('feature_relationship', $values);
-  if (!$ret) {
-    tripal_report_error("tripal_chado", TRIPAL_WARNING, "Could not add 'Derives_from' relationship for $feature->uniquename and $subject",
-      array());
-  }
-}
-
-/**
- * Load the parents for a gff3 feature
- *
- * @param $feature
- * @param $cvterm
- * @param $parents
- * @param $organism_id
- * @param $fmin
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_parents($feature, $cvterm, $parents,
-  $organism_id, $strand, $phase, $fmin, $fmax) {
-
-  $uname = $feature->uniquename;
-  $type = $cvterm->name;
-  $rel_type = 'part_of';
-
-  // Prepare these SQL statements that will be used repeatedly.
-  $cvterm_sql = "
-    SELECT CVT.cvterm_id
-    FROM {cvterm} CVT
-      INNER JOIN {cv} CV on CVT.cv_id = CV.cv_id
-      LEFT JOIN {cvtermsynonym} CVTS on CVTS.cvterm_id = CVT.cvterm_id
-    WHERE cv.name = :cvname and (CVT.name = :name or CVTS.synonym = :synonym)
-  ";
-
-  // Iterate through the parents in the list.
-  foreach ($parents as $parent) {
-    // Get the parent cvterm.
-    $values = array(
-      'organism_id' => $organism_id,
-      'uniquename' => $parent,
-    );
-    $result = chado_select_record('tripal_gff_temp', array('type_name'), $values);
-    if (count($result) == 0) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot find parent: %parent", array('%parent' => $parent));
-       return '';
-    }
-    $parent_type = $result[0]->type_name;
-
-    // try to find the parent
-    $parentcvterm = chado_query($cvterm_sql, array(':cvname' => 'sequence', ':name' => $parent_type, ':synonym' => $parent_type))->fetchObject();
-    $relcvterm = chado_query($cvterm_sql, array(':cvname' => 'sequence', ':name' => $rel_type, ':synonym' => $rel_type))->fetchObject();
-    if (!$relcvterm) {
-      tripal_report_error("tripal_feature", TRIPAL_WARNING, "Cannot find the term, 'part_of', from the sequence ontology. This term is used for associating parent and children features. Please check that the ontology is fully imported.");
-      exit;
-    }
-    $values = array(
-        'organism_id' => $organism_id,
-        'uniquename' => $parent,
-        'type_id' => $parentcvterm->cvterm_id,
-    );
-    $result = chado_select_record('feature', array('feature_id'), $values);
-    $parent_feature = $result[0];
-
-    // if the parent exists then add the relationship otherwise print error and skip
-    if ($parent_feature) {
-
-      // check to see if the relationship already exists
-      $values = array(
-        'object_id' => $parent_feature->feature_id,
-        'subject_id' => $feature->feature_id,
-        'type_id' => $relcvterm->cvterm_id,
-      );
-      $rel = chado_select_record('feature_relationship', array('*'), $values);
-
-      if (count($rel) > 0) {
-      }
-      else {
-        // the relationship doesn't already exist, so add it.
-        $values = array(
-          'subject_id' => $feature->feature_id,
-          'object_id'  => $parent_feature->feature_id,
-          'type_id' => $relcvterm->cvterm_id,
-        );
-        $result = chado_insert_record('feature_relationship', $values);
-        if (!$result) {
-          tripal_report_error("tripal_chado", TRIPAL_WARNING, "Failed to insert feature relationship '$uname' ($type) $rel_type '$parent' ($parent_type)",
-            array());
-        }
-      }
-
-      // If this feature is a CDS and now that we know the parent we can
-      // add it to the tripal_gffcds_temp table for later lookup.
-      if ($type == 'CDS') {
-        $values = array(
-          'feature_id' => $feature->feature_id,
-          'parent_id' => $parent_feature->feature_id,
-          'fmin' => $fmin,
-          'fmax' => $fmax,
-          'strand' => $strand,
-        );
-        if ($phase) {
-         $values['phase'] = $phase;
-        }
-        $result = chado_insert_record('tripal_gffcds_temp', $values);
-        if (!$result) {
-          tripal_report_error('tripal_chado', TRIPAL_ERROR, "Cound not save record in temporary CDS table, Cannot continue.", array());
-          exit;
-        }
-      }
-    }
-    else {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot establish relationship '$uname' ($type) $rel_type '$parent' ($parent_type): Cannot find the parent",
-        array());
-    }
-  }
-}
-
-/**
- * Load the dbxref attribute for a feature
- *
- * @param $feature
- * @param $dbxrefs
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_dbxref($feature, $dbxrefs) {
-
-  // iterate through each of the dbxrefs
-  foreach ($dbxrefs as $dbxref) {
-
-    // get the database name from the reference.  If it doesn't exist then create one.
-    $ref = explode(":", $dbxref);
-    $dbname = trim($ref[0]);
-    $accession = trim($ref[1]);
-
-    // first look for the database name if it doesn't exist then create one.
-    // first check for the fully qualified URI (e.g. DB:<dbname>. If that
-    // can't be found then look for the name as is.  If it still can't be found
-    // the create the database
-    $values = array('name' => "DB:$dbname");
-    $db = chado_select_record('db', array('db_id'), $values);
-    if (count($db) == 0) {
-      $values = array('name' => "$dbname");
-      $db = chado_select_record('db', array('db_id'), $values);
-    }
-    if (count($db) == 0) {
-      $values = array(
-        'name' => $dbname,
-        'description' => 'Added automatically by the GFF loader'
-      );
-      $success = chado_insert_record('db', $values);
-      if ($success) {
-        $values = array('name' => "$dbname");
-        $db = chado_select_record('db', array('db_id'), $values);
-      }
-      else {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot find or add the database $dbname", array());
-        return 0;
-      }
-    }
-    $db = $db[0];
-
-    // now check to see if the accession exists
-    $values = array(
-      'accession' => $accession,
-      'db_id' => $db->db_id
-    );
-    $dbxref = chado_select_record('dbxref', array('dbxref_id'), $values);
-
-    // if the accession doesn't exist then we want to add it
-    if (sizeof($dbxref) == 0) {
-      $values = array(
-        'db_id' => $db->db_id,
-        'accession' => $accession,
-        'version' => ''
-      );
-      $ret = chado_insert_record('dbxref', $values);
-      $values = array(
-        'accession' => $accession,
-        'db_id' => $db->db_id
-      );
-      $dbxref = chado_select_record('dbxref', array('dbxref_id'), $values);
-    }
-    $dbxref = $dbxref[0];
-
-    // check to see if this feature dbxref already exists
-    $values = array(
-      'dbxref_id' => $dbxref->dbxref_id,
-      'feature_id' => $feature->feature_id
-    );
-    $fdbx = chado_select_record('feature_dbxref', array('feature_dbxref_id'), $values);
-
-    // now associate this feature with the database reference if it doesn't
-    // already exist
-    if (sizeof($fdbx) == 0) {
-      $values = array(
-        'dbxref_id' => $dbxref->dbxref_id,
-        'feature_id' => $feature->feature_id
-      );
-      $success = chado_insert_record('feature_dbxref', $values);
-      if (!$success) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Failed to insert Dbxref: $dbname:$accession", array());
-        return 0;
-      }
-    }
-  }
-  return 1;
-}
-
-/**
- * Load the cvterms for a feature. Assumes there is a dbxref.accession matching a cvterm.name
- *
- * @param $feature
- * @param $dbxrefs
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_ontology($feature, $dbxrefs) {
-
-   // iterate through each of the dbxrefs
-  foreach ($dbxrefs as $dbxref) {
-
-    // get the database name from the reference.  If it doesn't exist then create one.
-    $ref = explode(":", $dbxref);
-    $dbname = trim($ref[0]);
-    $accession = trim($ref[1]);
-
-    // first look for the database name
-    $db = chado_select_record('db', array('db_id'), array('name' => "DB:$dbname"));
-    if (sizeof($db) == 0) {
-      // now look for the name without the 'DB:' prefix.
-      $db = chado_select_record('db', array('db_id'), array('name' => "$dbname"));
-      if (sizeof($db) == 0) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Database, $dbname, is not present. Cannot associate term: $dbname:$accession", array());
-        return 0;
-      }
-    }
-    $db = $db[0];
-
-    // now check to see if the accession exists
-    $dbxref = chado_select_record('dbxref', array('dbxref_id'),
-      array('accession' => $accession, 'db_id' => $db->db_id));
-    if (sizeof($dbxref) == 0) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Accession, $accession is missing for reference: $dbname:$accession", array());
-      return 0;
-    }
-    $dbxref = $dbxref[0];
-
-    // now check to see if the cvterm exists
-    $cvterm = chado_select_record('cvterm', array('cvterm_id'), array(
-       'dbxref_id' => $dbxref->dbxref_id));
-    // if it doesn't exist in the cvterm table, look for an alternate id
-    if (sizeof($cvterm) == 0) {
-      $cvterm = chado_select_record('cvterm_dbxref', array('cvterm_id'), array(
-        'dbxref_id' => $dbxref->dbxref_id));
-      if (sizeof($cvterm) == 0) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "CV Term is missing for reference: $dbname:$accession", array());
-        return 0;
-      }
-    }
-    $cvterm = $cvterm[0];
-
-
-    // check to see if this feature cvterm already exists
-    $fcvt = chado_select_record('feature_cvterm', array('feature_cvterm_id'),
-      array('cvterm_id' => $cvterm->cvterm_id, 'feature_id' => $feature->feature_id));
-
-    // now associate this feature with the cvterm if it doesn't already exist
-    if (sizeof($fcvt)==0) {
-      $values = array(
-        'cvterm_id' => $cvterm->cvterm_id,
-        'feature_id' => $feature->feature_id,
-        'pub_id' => array(
-          'uniquename' => 'null',
-        ),
-      );
-      $success = chado_insert_record('feature_cvterm', $values);
-
-      if (!$success) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Failed to insert ontology term: $dbname:$accession", array());
-        return 0;
-      }
-    }
-  }
-  return 1;
-}
-
-/**
- * Load any aliases for a feature
- *
- * @param $feature
- * @param $aliases
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_alias($feature, $aliases) {
-
-  // make sure we have a 'synonym_type' vocabulary
-  $select = array('name' => 'synonym_type');
-  $results = chado_select_record('cv', array('*'), $select);
-
-  if (count($results) == 0) {
-    // insert the 'synonym_type' vocabulary
-    $values = array(
-      'name' => 'synonym_type',
-      'definition' => 'vocabulary for synonym types',
-    );
-    $success = chado_insert_record('cv', $values);
-    if (!$success) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Failed to add the synonyms type vocabulary", array());
-      return 0;
-    }
-    // now that we've added the cv we need to get the record
-    $results = chado_select_record('cv', array('*'), $select);
-    if (count($results) > 0) {
-      $syncv = $results[0];
-    }
-  }
-  else {
-    $syncv = $results[0];
-  }
-
-  // get the 'exact' cvterm, which is the type of synonym we're adding
-  $select = array(
-     'name' => 'exact',
-     'cv_id' => array(
-        'name' => 'synonym_type'
-     ),
-  );
-  $result = chado_select_record('cvterm', array('*'), $select);
-  if (count($result) == 0) {
-    $term = array(
-      'name' => 'exact',
-      'id' => "synonym_type:exact",
-      'definition' => '',
-      'is_obsolete' => 0,
-      'cv_name' => $syncv->name,
-      'is_relationship' => FALSE
-    );
-    $syntype = tripal_insert_cvterm($term, array('update_existing' => TRUE));
-    if (!$syntype) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot add synonym type: internal:$type", array());
-      return 0;
-    }
-  }
-  else {
-    $syntype = $result[0];
-  }
-
-  // iterate through all of the aliases and add each one
-  foreach ($aliases as $alias) {
-
-    // check to see if the alias already exists in the synonym table
-    // if not, then add it
-    $select = array(
-       'name' => $alias,
-       'type_id' => $syntype->cvterm_id,
-    );
-    $result = chado_select_record('synonym', array('*'), $select);
-    if (count($result) == 0) {
-      $values = array(
-         'name' => $alias,
-         'type_id' => $syntype->cvterm_id,
-         'synonym_sgml' => '',
-      );
-      $success = chado_insert_record('synonym', $values);
-      if (!$success) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot add alias $alias to synonym table", array());
-        return 0;
-      }
-      $result = chado_select_record('synonym', array('*'), $select);
-      $synonym = $result[0];
-    }
-    else {
-      $synonym = $result[0];
-    }
-
-    // check to see if we have a NULL publication in the pub table.  If not,
-    // then add one.
-    $select = array('uniquename' => 'null');
-    $result = chado_select_record('pub', array('*'), $select);
-    if (count($result) == 0) {
-      $pub_sql = "
-        INSERT INTO {pub} (uniquename,type_id)
-        VALUES (:uname,
-          (SELECT cvterm_id
-           FROM {cvterm} CVT
-             INNER JOIN {dbxref} DBX ON DBX.dbxref_id = CVT.dbxref_id
-             INNER JOIN {db} DB      ON DB.db_id      = DBX.db_id
-           WHERE CVT.name = :type_id))
-      ";
-      $status = chado_query($psql);
-      if (!$status) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot prepare statement 'ins_pub_uniquename_typeid", array());
-        return 0;
-      }
-
-      // insert the null pub
-      $result = chado_query($pub_sql, array(':uname' => 'null', ':type_id' => 'null'))->fetchObject();
-      if (!$result) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot add null publication needed for setup of alias", array());
-        return 0;
-      }
-      $result = chado_select_record('pub', array('*'), $select);
-      $pub = $result[0];
-    }
-    else {
-      $pub = $result[0];
-    }
-
-    // check to see if the synonym exists in the feature_synonym table
-    // if not, then add it.
-    $values = array(
-       'synonym_id' => $synonym->synonym_id,
-       'feature_id' => $feature->feature_id,
-       'pub_id' => $pub->pub_id,
-    );
-    $columns = array('feature_synonym_id');
-    $result = chado_select_record('feature_synonym', $columns, $values);
-    if (count($result) == 0) {
-      $values = array(
-         'synonym_id' => $synonym->synonym_id,
-         'feature_id' => $feature->feature_id,
-         'pub_id' => $pub->pub_id,
-      );
-      $success = chado_insert_record('feature_synonym', $values);
-
-      if (!$success) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot add alias $alias to feature synonym table", array());
-        return 0;
-      }
-    }
-  }
-  return 1;
-}
-
-/**
- * Create the feature record & link it to it's analysis
- *
- * @param $organism
- * @param $analysis_id
- * @param $cvterm
- * @param $uniquename
- * @param $name
- * @param $residues
- * @param $is_analysis
- * @param $is_obsolete
- * @param $add_only
- * @param $score
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_feature($organism, $analysis_id, $cvterm, $uniquename,
-  $name, $residues, $is_analysis = 'f', $is_obsolete = 'f', $add_only, $score) {
-
-  // Check to see if the feature already exists.
-  $feature = NULL;
-  $fselect = array(
-    'organism_id' => $organism->organism_id,
-    'uniquename' => $uniquename,
-    'type_id' => $cvterm->cvterm_id
-  );
-  $columns = array('feature_id', 'name', 'uniquename', 'seqlen', 'organism_id', 'type_id');
-  $result = chado_select_record('feature', $columns, $fselect);
-  if (count($result) > 0) {
-    $feature = $result[0];
-  }
-
-  if (strcmp($is_obsolete, 'f')==0 or $is_obsolete == 0) {
-    $is_obsolete = 'FALSE';
-  }
-  if (strcmp($is_obsolete, 't')==0 or $is_obsolete == 1) {
-    $is_obsolete = 'TRUE';
-  }
-  if (strcmp($is_analysis, 'f')==0 or $is_analysis == 0) {
-    $is_analysis = 'FALSE';
-  }
-  if (strcmp($is_analysis, 't')==0 or $is_analysis == 1) {
-    $is_analysis = 'TRUE';
-  }
-
-  // Insert the feature if it does not exist otherwise perform an update.
-  if (!$feature) {
-    $values = array(
-      'organism_id' => $organism->organism_id,
-      'name' => $name,
-      'uniquename' => $uniquename,
-      'md5checksum' => md5($residues),
-      'type_id' => $cvterm->cvterm_id,
-      'is_analysis' => $is_analysis,
-      'is_obsolete' => $is_obsolete,
-    );
-    $feature = (object) chado_insert_record('feature', $values);
-    if (!$feature) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Failed to insert feature '$uniquename' ($cvterm->name)", array());
-      return 0;
-    }
-  }
-  elseif (!$add_only) {
-    $values = array(
-      'name' => $name,
-      'md5checksum' => md5($residues),
-      'is_analysis' => $is_analysis,
-      'is_obsolete' => $is_obsolete,
-    );
-    $match = array(
-      'organism_id' => $organism->organism_id,
-      'uniquename' => $uniquename,
-      'type_id' => $cvterm->cvterm_id,
-    );
-    $result = chado_update_record('feature', $match, $values);
-    if (!$result) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Failed to update feature '$uniquename' ($cvterm->name)", array());
-      return 0;
-    }
-  }
-  else {
-    // The feature exists and we don't want to update it so return
-    // a value of 0.  This will stop all downstream property additions
-    return $feature;
-  }
-
-  // Add the analysisfeature entry to the analysisfeature table if
-  // it doesn't already exist.
-  $af_values = array(
-    'analysis_id' => $analysis_id,
-    'feature_id' => $feature->feature_id
-  );
-  $afeature = chado_select_record('analysisfeature', array('analysisfeature_id'), $af_values);
-  if (count($afeature)==0) {
-    // if a score is available then set that to be the significance field
-    if (strcmp($score, '.') != 0) {
-      $af_values['significance'] = $score;
-    }
-    if (!chado_insert_record('analysisfeature', $af_values)) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Could not add analysisfeature record: $analysis_id, $feature->feature_id", array());
-    }
-  }
-  else {
-    // if a score is available then set that to be the significance field
-    $new_vals = array();
-    if (strcmp($score, '.')!=0) {
-      $new_vals['significance'] = $score;
-    }
-    else {
-      $new_vals['significance'] = '__NULL__';
-    }
-    if (!$add_only) {
-      $ret = chado_update_record('analysisfeature', $af_values, $new_vals);
-      if (!$ret) {
-        tripal_report_error("tripal_chado", TRIPAL_WARNING, "Could not update analysisfeature record: $analysis_id, $feature->feature_id", array());
-      }
-    }
-  }
-
-  return $feature;
-}
-
-/**
- * Insert the location of the feature
- *
- * @param $feature
- * @param $organism
- * @param $landmark
- * @param $fmin
- * @param $fmax
- * @param $strand
- * @param $phase
- * @param $is_fmin_partial
- * @param $is_fmax_partial
- * @param $residue_info
- * @param $locgroup
- * @param $landmark_type_id
- * @param $landmark_organism_id
- * @param $create_landmark
- * @param $landmark_is_target
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_featureloc($feature, $organism, $landmark, $fmin,
-  $fmax, $strand, $phase, $is_fmin_partial, $is_fmax_partial, $residue_info, $locgroup,
-  $landmark_type_id = '', $landmark_organism_id = '', $create_landmark = 0,
-  $landmark_is_target = 0) {
-
-  $select = array(
-    'organism_id' => $landmark_organism_id ? $landmark_organism_id : $organism->organism_id,
-    'uniquename' => $landmark,
-  );
-  if ($landmark_type_id) {
-    $select['type_id'] = $landmark_type_id;
-  }
-  $results = chado_select_record('feature', array('feature_id'), $select);
-
-  $srcfeature = '';
-  if (count($results)==0) {
-    // so we couldn't find the landmark using the uniquename. Let's try the 'name'.
-    // if we return only a single result then we can proceed. Otherwise give an
-    $select = array(
-      'organism_id' => $landmark_organism_id ? $landmark_organism_id : $organism->organism_id,
-      'name' => $landmark,
-    );
-    if ($landmark_type_id) {
-      $select['type_id'] = $landmark_type_id;
-    }
-    $results = chado_select_record('feature', array('feature_id'), $select);
-    if (count($results) == 0) {
-       // if the landmark is the target feature in a matched alignment then try one more time to
-       // find it by querying any feature with the same uniquename. If we find one then use it.
-       if ($landmark_is_target) {
-         $select = array('uniquename' => $landmark);
-         $results = chado_select_record('feature', array('feature_id'), $select);
-         if (count($results) == 1) {
-           $srcfeature = $results[0];
-         }
-       }
-
-       if (!$srcfeature) {
-         // we couldn't find the landmark feature, so if the user has requested we create it then do so
-         // but only if we have a type id
-         if ($create_landmark and $landmark_type_id) {
-            $values = array(
-              'organism_id' => $landmark_organism_id ? $landmark_organism_id : $organism->organism_id,
-              'name' => $landmark,
-              'uniquename' => $landmark,
-              'type_id' => $landmark_type_id
-            );
-            $results = chado_insert_record('feature', $values);
-            if (!$results) {
-              tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot find landmark feature: '%landmark', nor could it be inserted",
-                array('%landmark' => $landmark));
-              return 0;
-            }
-            $srcfeature = new stdClass();
-            $srcfeature->feature_id = $results['feature_id'];
-         }
-         else {
-           tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot find unique landmark feature: '%landmark'.",
-             array('%landmark' => $landmark));
-           return 0;
-         }
-       }
-    }
-    elseif (count($results) > 1) {
-       tripal_report_error("tripal_chado", TRIPAL_WARNING, "multiple landmarks exist with the name: '%landmark'.  Cannot
-         resolve which one to use. Cannot add the feature location record",
-         array('%landmark' => $landmark));
-       return 0;
-    }
-    else {
-      $srcfeature = $results[0];
-    }
-  }
-  elseif (count($results) > 1) {
-    tripal_report_error("tripal_chado", TRIPAL_WARNING, "multiple landmarks exist with the name: '%landmark'.  Cannot
-      resolve which one to use. Cannot add the feature location record",
-      array('%landmark' => $landmark));
-    return 0;
-  }
-  else {
-    $srcfeature = $results[0];
-  }
-
-  // TODO: create an attribute that recognizes the residue_info,locgroup,
-  //  is_fmin_partial and is_fmax_partial, right now these are
-  //  hardcoded to be false and 0 below.
-
-  // check to see if this featureloc already exists, but also keep track of the
-  // last rank value
-  $rank = 0;
-  $exists = 0;
-  $select = array('feature_id' => $feature->feature_id);
-  $options = array(
-    'order_by' => array(
-       'rank' => 'ASC'
-    ),
-  );
-
-  $locrecs = chado_select_record('featureloc', array('*'), $select, $options);
-
-  foreach ($locrecs as $featureloc) {
-    // it is possible for the featureloc->srcfeature_id to be NULL. This can happen if the srcfeature
-    // is not known (according to chado table field descriptions).  If it's null then just skip this entry
-    if (!$featureloc->srcfeature_id) {
-      continue;
-    }
-    $select = array('feature_id' => $featureloc->srcfeature_id);
-    $columns = array('feature_id', 'name');
-    $locsfeature = chado_select_record('feature', $columns, $select);
-
-    // the source feature name and at least the fmin and fmax must be the same
-    // for an update of the featureloc, otherwise we'll insert a new record.
-    if (strcmp($locsfeature[0]->name, $landmark)==0 and
-       ($featureloc->fmin == $fmin or $featureloc->fmax == $fmax)) {
-      $match = array('featureloc_id' => $featureloc->featureloc_id);
-      $values = array();
-      $exists = 1;
-      if ($featureloc->fmin != $fmin) {
-         $values['fmin'] = $fmin;
-      }
-      if ($featureloc->fmax != $fmax) {
-         $values['fmax'] = $fmax;
-      }
-      if ($featureloc->strand != $strand) {
-         $values['strand'] = $strand;
-      }
-      if (count($values) > 0) {
-        chado_update_record('featureloc', $match, $values);
-      }
-    }
-    $rank = $featureloc->rank + 1;
-  }
-  if (!$exists) {
-
-    // this feature location is new so add it
-    if (strcmp($is_fmin_partial, 'f')==0 or !$is_fmin_partial) {
-      $is_fmin_partial = 'FALSE';
-    }
-    elseif (strcmp($is_fmin_partial, 't')==0 or $is_fmin_partial = 1) {
-      $is_fmin_partial = 'TRUE';
-    }
-    if (strcmp($is_fmax_partial, 'f')==0 or !$is_fmax_partial) {
-      $is_fmax_partial = 'FALSE';
-    }
-    elseif (strcmp($is_fmax_partial, 't')==0 or $is_fmax_partial = 1) {
-      $is_fmax_partial = 'TRUE';
-    }
-    $values = array(
-       'feature_id'      => $feature->feature_id,
-       'srcfeature_id'   => $srcfeature->feature_id,
-       'fmin'            => $fmin,
-       'is_fmin_partial' => $is_fmin_partial,
-       'fmax'            => $fmax,
-       'is_fmax_partial' => $is_fmax_partial,
-       'strand'          => $strand,
-       'residue_info'    => $residue_info,
-       'locgroup'        => $locgroup,
-       'rank'            => $rank
-    );
-    if ($phase) {
-      $values['phase'] = $phase;
-    }
-    $success = chado_insert_record('featureloc', $values);
-    if (!$success) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Failed to insert featureloc", array());
-      exit;
-      return 0;
-    }
-  }
-  return 1;
-}
-
-/**
- * Load a preoprty (featurepop) for the feature
- *
- * @param $feature
- * @param $property
- * @param $value
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_property($feature, $property, $value) {
-
-  // first make sure the cvterm exists.  if not, then add it
-  $select = array(
-     'name' => $property,
-     'cv_id' => array(
-        'name' => 'feature_property',
-     ),
-  );
-  $result = chado_select_record('cvterm', array('*'), $select);
-
-  // if we don't have a property like this already, then add it otherwise, just return
-  if (count($result) == 0) {
-    $term = array(
-      'id' => "null:$property",
-      'name' => $property,
-      'namespace' => 'feature_property',
-      'is_obsolete' => 0,
-      'cv_name' => 'feature_property',
-      'is_relationship' => FALSE
-    );
-    $cvterm = (object) tripal_insert_cvterm($term, array('update_existing' => FALSE));
-    if (!$cvterm) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "Cannot add cvterm, $property", array());
-      return 0;
-    }
-  }
-  else {
-    $cvterm = $result[0];
-  }
-
-
-  // check to see if the property already exists for this feature
-  // if it does but the value is unique then increment the rank and add it.
-  // if the value is not unique then don't add it.
-  $add = 1;
-  $rank = 0;
-  $select = array(
-     'feature_id' => $feature->feature_id,
-     'type_id' => $cvterm->cvterm_id,
-  );
-  $options = array(
-    'order_by' => array(
-      'rank' => 'ASC',
-    ),
-  );
-  $results = chado_select_record('featureprop', array('*'), $select, $options);
-  foreach ($results as $prop) {
-    if (strcmp($prop->value, $value)==0) {
-      $add = NULL; // don't add it, it already exists
-    }
-    $rank = $prop->rank + 1;
-  }
-
-  // add the property if we pass the check above
-  if ($add) {
-    $values = array(
-       'feature_id' => $feature->feature_id,
-       'type_id' => $cvterm->cvterm_id,
-       'value' => $value,
-       'rank' => $rank,
-    );
-    $result = chado_insert_record('featureprop', $values);
-    if (!$result) {
-      tripal_report_error("tripal_chado", TRIPAL_WARNING, "cannot add featureprop, $property", array());
-    }
-  }
-}
-
-/**
- * Load the FASTA sequences at the bottom of a GFF3 file
- *
- * @param $fh
- * @param $interval
- * @param $num_read
- * @param $intv_read
- * @param $line_num
- * @param $filesize
- * @param $job
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_fasta($fh, $interval, &$num_read, &$intv_read, &$line_num, $filesize, $job) {
-  print "\nLoading FASTA sequences\n";
-  $residues = '';
-  $id = NULL;
-
-  $percent = sprintf("%.2f", ($num_read / $filesize) * 100);
-  print "Parsing Line $line_num (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
-  // iterate through the remaining lines of the file
-  while ($line = fgets($fh)) {
-
-    $line_num++;
-    $size = drupal_strlen($line);
-    $num_read += $size;
-    $intv_read += $size;
-
-    $line = trim($line);
-
-    // update the job status every 1% features
-    if ($job and $intv_read >= $interval) {
-      $intv_read = 0;
-      $percent = sprintf("%.2f", ($num_read / $filesize) * 100);
-      print "Parsing Line $line_num (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
-      tripal_set_job_progress($job, intval(($num_read / $filesize) * 100));
-    }
-
-    // if we encounter a definition line then get the name, uniquename,
-    // accession and relationship subject from the definition line
-    if (preg_match('/^>/', $line)) {
-
-      // if we are beginning a new sequence then save to the database the last one we just finished.
-      if ($id) {
-        $values = array('uniquename' => $id);
-        $result = chado_select_record('tripal_gff_temp', array('*'), $values);
-        if (count($result) == 0) {
-          tripal_report_error('tripal_chado', TRIPAL_WARNING, 'Cannot find feature to assign FASTA sequence: %uname',
-             array('%uname' => $id));
-        }
-        else {
-          // if we have a feature then add the residues
-          $feature = $result[0];
-          $values = array(
-            'residues' => $residues,
-            'seqlen' => strlen($residues)
-          );
-          $match = array('feature_id' => $feature->feature_id);
-          chado_update_record('feature', $match, $values);
-        }
-      }
-
-      // get the feature ID for this ID from the tripal_gff_temp table. It
-      // should be the name up to the first space
-      $id = preg_replace('/^>([^\s]+).*$/', '\1', $line);
-      $residues = '';
-    }
-    else {
-      $residues .= trim($line);
-    }
-  }
-
-  // add in the last sequence
-  $values = array('uniquename' => $id);
-  $result = chado_select_record('tripal_gff_temp', array('*'), $values);
-  if (count($result) == 0) {
-    tripal_report_error('tripal_chado', TRIPAL_WARNING, 'Cannot find feature to assign FASTA sequence: %uname',
-       array('%uname' => $id));
-  }
-  else {
-    // if we have a feature then add the residues
-    $feature = $result[0];
-    $values = array(
-      'residues' => $residues,
-      'seqlen' => strlen($residues)
-    );
-    $match = array('feature_id' => $feature->feature_id);
-    chado_update_record('feature', $match, $values);
-  }
-
-}
-
-/**
- * Load the target attribute of a gff3 record
- *
- * @param $feature
- * @param $tags
- * @param $target_organism_id
- * @param $target_type
- * @param $create_target
- * @param $attr_locgroup
- *
- * @ingroup gff3_loader
- */
-function tripal_feature_load_gff3_target($feature, $tags, $target_organism_id, $target_type, $create_target, $attr_locgroup) {
-  // format is: "target_id start end [strand]", where strand is optional and may be "+" or "-"
-  $matched = preg_match('/^(.*?)\s+(\d+)\s+(\d+)(\s+[\+|\-])*$/', trim($tags['Target'][0]), $matches);
-
-  // the organism and type of the target may also be specified as an attribute. If so, then get that
-  // information
-  $gff_target_organism = array_key_exists('target_organism', $tags) ? $tags['target_organism'][0] : '';
-  $gff_target_type = array_key_exists('target_type', $tags) ? $tags['target_type'][0] : '';
-
-  // if we have matches and the Target is in the correct format then load the alignment
-  if ($matched) {
-    $target_feature = $matches[1];
-    $start = $matches[2];
-    $end = $matches[3];
-    // if we have an optional strand, convert it to a numeric value.
-    if ($matches[4]) {
-      if (preg_match('/^\+$/', trim($matches[4]))) {
-        $target_strand = 1;
-      }
-      elseif (preg_match('/^\-$/', trim($matches[4]))) {
-        $target_strand = -1;
-      }
-      else {
-        $target_strand = 0;
-      }
-    }
-    else {
-       $target_strand = 0;
-    }
-
-    $target_fmin = $start - 1;
-    $target_fmax = $end;
-    if ($end < $start) {
-      $target_fmin = $end - 1;
-      $target_fmax = $start;
-    }
-
-    // default the target organism to be the value passed into the function, but if the GFF
-    // file species the target organism then use that instead.
-    $t_organism_id = $target_organism_id;
-    if ($gff_target_organism) {
-      // get the genus and species
-      $success = preg_match('/^(.*?):(.*?)$/', $gff_target_organism, $matches);
-      if ($success) {
-        $values = array(
-          'genus' => $matches[1],
-          'species' => $matches[2],
-        );
-        $torganism = chado_select_record('organism', array('organism_id'), $values);
-        if (count($torganism) == 1) {
-          $t_organism_id = $torganism[0]->organism_id;
-        }
-        else {
-          tripal_report_error('tripal_chado', TRIPAL_WARNING, "Cannot find organism for target %target.",
-            array('%target' => $gff_target_organism));
-          $t_organism_id = '';
-        }
-      }
-      else {
-        tripal_report_error('tripal_chado', TRIPAL_WARNING, "The target_organism attribute is improperly formatted: %target.
-          It should be target_organism=genus:species.",
-          array('%target' => $gff_target_organism));
-        $t_organism_id = '';
-      }
-    }
-
-    // default the target type to be the value passed into the function, but if the GFF file
-    // species the target type then use that instead
-    $t_type_id = '';
-    if ($target_type) {
-      $values = array(
-        'name' => $target_type,
-        'cv_id' => array(
-           'name' => 'sequence',
-        )
-      );
-      $type = chado_select_record('cvterm', array('cvterm_id'), $values);
-      if (count($type) == 1) {
-        $t_type_id = $type[0]->cvterm_id;
-      }
-      else {
-        tripal_report_error('tripal_chado', TRIPAL_ERROR, "The target type does not exist in the sequence ontology: %type. ",
-          array('%type' => $target_type));
-        exit;
-      }
-    }
-    if ($gff_target_type) {
-      $values = array(
-        'name' => $gff_target_type,
-        'cv_id' => array(
-           'name' => 'sequence',
-        )
-      );
-
-      // get the cvterm_id for the target type
-      $type = chado_select_record('cvterm', array('cvterm_id'), $values);
-      if (count($type) == 1) {
-        $t_type_id = $type[0]->cvterm_id;
-      }
-      else {
-        // check to see if this is a synonym
-        $sql = "
-          SELECT CVTS.cvterm_id
-          FROM {cvtermsynonym} CVTS
-            INNER JOIN {cvterm} CVT ON CVT.cvterm_id = CVTS.cvterm_id
-            INNER JOIN {cv} CV      ON CV.cv_id = CVT.cv_id
-          WHERE CV.name = 'sequence' and CVTS.synonym = :synonym
-        ";
-        $synonym = chado_query($sql, array(':synonym' => $gff_target_type))->fetchObject();
-        if ($synonym) {
-          $t_type_id = $synonym->cvterm_id;
-        }
-        else {
-          tripal_report_error('tripal_chado', TRIPAL_WARNING, "The target_type attribute does not exist in the sequence ontology: %type. ",
-            array('%type' => $gff_target_type));
-          $t_type_id = '';
-        }
-      }
-    }
-
-    // we want to add a featureloc record that uses the target feature as the srcfeature (landmark)
-    // and the landmark as the feature.
-    tripal_feature_load_gff3_featureloc($feature, $organism, $target_feature, $target_fmin,
-      $target_fmax, $target_strand, $phase, $attr_fmin_partial, $attr_fmax_partial, $attr_residue_info,
-      $attr_locgroup, $t_type_id, $t_organism_id, $create_target, TRUE);
-  }
-  // the target attribute is not correctly formatted
-  else {
-    tripal_report_error('tripal_chado', TRIPAL_ERROR, "Could not add 'Target' alignment as it is improperly formatted:  '%target'",
-      array('%target' => $tags['Target'][0]));
-  }
-}

+ 0 - 1456
tripal_chado/includes/loaders/tripal_chado.obo_loader.inc

@@ -1,1456 +0,0 @@
-<?php
-/**
- * @file
- * Functions to aid in loading ontologies into the chado cv module
- */
-
-/**
- * @defgroup tripal_obo_loader Ontology Loader
- * @ingroup tripal_chado
- * @{
- * Functions to aid in loading ontologies into the chado cv module
- * @}
- */
-
-/**
- * Provides the form to load an already existing controlled
- *  Vocabulary into chado
- *
- * @param $form
- *   The form array
- * @param $form_state
- *   The form state array
- *
- * @return
- *   The form array with new additions
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_form($form, &$form_state) {
-
-  // get a list of db from chado for user to choose
-  $sql = "SELECT * FROM {tripal_cv_obo} ORDER BY name";
-  $results = db_query($sql);
-
-  $obos = array();
-  $obos[] = 'Select a Vocabulary';
-  foreach ($results as $obo) {
-    $obos[$obo->obo_id] = $obo->name;
-  }
-
-  $obo_id = '';
-  if (array_key_exists('values', $form_state)) {
-    $obo_id = array_key_exists('obo_id', $form_state['values']) ? $form_state['values']['obo_id'] : '';
-  }
-
-
-  $form['instructions']['info'] = array(
-    '#type' => 'item',
-    '#markup' => t('This page allows you to load vocabularies and ontologies
-      that are in OBO format. Once loaded, the terms from these
-      vocabularies can be used to create content.
-      You may use the form below to either reload a vocabulary that is already
-      loaded (as when new updates to that vocabulary are available) or load a new
-      vocabulary.'),
-  );
-
-  $form['obo_existing'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Use a Saved Ontology OBO Reference'),
-    '#prefix' => '<span id="obo-existing-fieldset">',
-    '#suffix' => '</span>'
-  );
-
-  $form['obo_existing']['existing_instructions']= array(
-    '#type' => 'item',
-    '#markup' => t('The vocabularies listed in the select box below have bene pre-populated
-      upon installation of Tripal or have been previously loaded.  Select one to edit
-      its settings or submit for loading.  You may reload any vocabulary that has
-      already been loaded to retrieve any new updates.'),
-  );
-
-  $form['obo_existing']['obo_id'] = array(
-    '#title' => t('Ontology OBO File Reference'),
-    '#type' => 'select',
-    '#options' => $obos,
-    '#ajax' => array(
-      'callback' => 'tripal_cv_obo_form_ajax_callback',
-      'wrapper' => 'obo-existing-fieldset',
-    ),
-    '#description' => t('Select a vocabulary to import.')
-  );
-
-  // If the user has selected an OBO ID then get the form elements for
-  // updating.
-  if ($obo_id) {
-    $uobo_name = '';
-    $uobo_url = '';
-    $uobo_file = '';
-
-    $vocab = db_select('tripal_cv_obo', 't')
-      ->fields('t', array('name', 'path'))
-      ->condition('obo_id', $obo_id)
-      ->execute()
-      ->fetchObject();
-    $uobo_name = $vocab->name;
-    if (preg_match('/^http/', $vocab->path)) {
-      $uobo_url = $vocab->path;
-    }
-    else {
-      $uobo_file = trim($vocab->path);
-      $matches = array();
-      if (preg_match('/\{(.*?)\}/', $uobo_file, $matches)) {
-        $modpath = drupal_get_path('module', $matches[1]);
-        $uobo_file = preg_replace('/\{.*?\}/', $modpath, $uobo_file);
-      }
-    }
-    // We don't want the previous value to remain. We want the new default to
-    // show up, so remove the input values
-    unset($form_state['input']['uobo_name']);
-    unset($form_state['input']['uobo_url']);
-    unset($form_state['input']['uobo_file']);
-
-    $form['obo_existing']['uobo_name']= array(
-      '#type'          => 'textfield',
-      '#title'         => t('Vocabulary Name'),
-      '#description'   => t('Please provide a name for this vocabulary.  After upload, this name will appear in the drop down
-                           list above for use again later.'),
-      '#default_value'  => $uobo_name,
-    );
-
-    $form['obo_existing']['uobo_url']= array(
-      '#type'          => 'textfield',
-      '#title'         => t('Remote URL'),
-      '#description'   => t('Please enter a URL for the online OBO file.  The file will be downloaded and parsed.
-                           (e.g. http://www.obofoundry.org/ro/ro.obo)'),
-      '#default_value' => $uobo_url,
-    );
-
-    $form['obo_existing']['uobo_file']= array(
-      '#type'          => 'textfield',
-      '#title'         => t('Local File'),
-      '#description'   => t('Please enter the file system path for an OBO
-        definition file. If entering a path relative to
-        the Drupal installation you may use a relative path that excludes the
-        Drupal installation directory (e.g. sites/default/files/xyz.obo). Note
-        that Drupal relative paths have no preceeding slash.
-        Otherwise, please provide the full path on the filesystem.  The path
-        must be accessible to the web server on which this Drupal instance is running.'),
-      '#default_value' => $uobo_file,
-    );
-    $form['obo_existing']['update_obo_details'] = array(
-      '#type' => 'submit',
-      '#value' => 'Update Ontology Details',
-      '#name' => 'update_obo_details'
-    );
-    $form['obo_existing']['update_load_obo'] = array(
-      '#type' => 'submit',
-      '#value' => 'Import Vocabulary',
-      '#name' => 'update_load_obo'
-    );
-  }
-
-  $form['obo_new'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Add a New Ontology OBO Reference'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-
-  $form['obo_new']['path_instructions']= array(
-    '#value' => t('Provide the name and path for the OBO file.  If the vocabulary OBO file
-                   is stored local to the server provide a file name. If the vocabulry is stored remotely,
-                   provide a URL.  Only provide a URL or a local file, not both.'),
-  );
-
-  $form['obo_new']['obo_name']= array(
-    '#type'          => 'textfield',
-    '#title'         => t('New Vocabulary Name'),
-    '#description'   => t('Please provide a name for this vocabulary.  After upload, this name will appear in the drop down
-                           list above for use again later.'),
-  );
-
-  $form['obo_new']['obo_url']= array(
-    '#type'          => 'textfield',
-    '#title'         => t('Remote URL'),
-    '#description'   => t('Please enter a URL for the online OBO file.  The file will be downloaded and parsed.
-                           (e.g. http://www.obofoundry.org/ro/ro.obo)'),
-  );
-
-  $form['obo_new']['obo_file']= array(
-    '#type'  => 'textfield',
-    '#title'  => t('Local File'),
-    '#description' => t('Please enter the file system path for an OBO
-        definition file. If entering a path relative to
-        the Drupal installation you may use a relative path that excludes the
-        Drupal installation directory (e.g. sites/default/files/xyz.obo). Note
-        that Drupal relative paths have no preceeding slash.
-        Otherwise, please provide the full path on the filesystem.  The path
-        must be accessible to the web server on which this Drupal instance is running.'),
-  );
-
-  $form['obo_new']['add_new_obo'] = array(
-    '#type'  => 'submit',
-    '#value' => t('Add this vocabulary'),
-    '#name' => 'add_new_obo',
-  );
-
-  $form['#redirect'] = 'admin/tripal/tripal_chado/obo_loader';
-
-  return $form;
-}
-
-/**
- *
- * @param $form
- * @param $form_state
- */
-function tripal_cv_obo_form_validate($form, &$form_state) {
-  $obo_id    = $form_state['values']['obo_id'];
-  $obo_name  = trim($form_state['values']['obo_name']);
-  $obo_url   = trim($form_state['values']['obo_url']);
-  $obo_file  = trim($form_state['values']['obo_file']);
-  $uobo_name  = array_key_exists('uobo_name', $form_state['values']) ? trim($form_state['values']['uobo_name']) : '';
-  $uobo_url   = array_key_exists('uobo_url', $form_state['values']) ? trim($form_state['values']['uobo_url']) : '';
-  $uobo_file  = array_key_exists('uobo_file', $form_state['values']) ? trim($form_state['values']['uobo_file']) : '';
-
-  // Make sure if the name is changed it doesn't conflict with another OBO.
-  if ($form_state['clicked_button']['#name'] == 'update_obo_details' or
-      $form_state['clicked_button']['#name'] == 'update_load_obo') {
-    // Get the current record
-    $vocab = db_select('tripal_cv_obo', 't')
-      ->fields('t', array('obo_id', 'name', 'path'))
-      ->condition('name', $uobo_name)
-      ->execute()
-      ->fetchObject();
-    if ($vocab and $vocab->obo_id != $obo_id) {
-      form_set_error('uobo_name', 'The vocabulary name must be different from existing vocabularies');
-    }
-    // Make sure the file exists. First check if it is a relative path
-    $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $uobo_file;
-    if (!file_exists($dfile)) {
-      if (!file_exists($uobo_file)) {
-        form_set_error('uobo_file', 'The specified path does not exist or cannot be read.');
-      }
-    }
-    if (!$uobo_url and !$uobo_file) {
-      form_set_error('uobo_url', 'Please provide a URL or a path for the vocabulary.');
-    }
-    if ($uobo_url and $uobo_file) {
-      form_set_error('uobo_url', 'Please provide only a URL or a path for the vocabulary, but not both.');
-    }
-  }
-  if ($form_state['clicked_button']['#name'] == 'add_new_obo') {
-    // Get the current record
-    $vocab = db_select('tripal_cv_obo', 't')
-      ->fields('t', array('obo_id', 'name', 'path'))
-      ->condition('name', $obo_name)
-      ->execute()
-      ->fetchObject();
-    if ($vocab) {
-      form_set_error('obo_name', 'The vocabulary name must be different from existing vocabularies');
-    }
-    // Make sure the file exists. First check if it is a relative path
-    $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $obo_file;
-    if (!file_exists($dfile)) {
-      if (!file_exists($obo_file)) {
-        form_set_error('obo_file', 'The specified path does not exist or cannot be read.');
-      }
-    }
-    if (!$obo_url and !$obo_file) {
-      form_set_error('obo_url', 'Please provide a URL or a path for the vocabulary.');
-    }
-    if ($obo_url and $obo_file) {
-      form_set_error('obo_url', 'Please provide only a URL or a path for the vocabulary, but not both.');
-    }
-  }
-}
-
-/**
- * The submit function for the load ontology form. It registers a
- *   tripal job to import the user specified ontology file
- *
- * @param $form
- *   The form array
- * @param $form_state
- *   The form state array
- *
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_form_submit($form, &$form_state) {
-
-  $obo_id    = $form_state['values']['obo_id'];
-  $obo_name  = trim($form_state['values']['obo_name']);
-  $obo_url   = trim($form_state['values']['obo_url']);
-  $obo_file  = trim($form_state['values']['obo_file']);
-  $uobo_name  = array_key_exists('uobo_name', $form_state['values']) ? trim($form_state['values']['uobo_name']) : '';
-  $uobo_url   = array_key_exists('uobo_url', $form_state['values']) ? trim($form_state['values']['uobo_url']) : '';
-  $uobo_file  = array_key_exists('uobo_file', $form_state['values']) ? trim($form_state['values']['uobo_file']) : '';
-
-  // If the user requested to alter the details then do that.
-  if ($form_state['clicked_button']['#name'] == 'update_obo_details' or
-      $form_state['clicked_button']['#name'] == 'update_load_obo') {
-    $success = db_update('tripal_cv_obo')
-      ->fields(array(
-        'name' => $uobo_name,
-        'path' => $uobo_url ? $uobo_url : $uobo_file,
-      ))
-      ->condition('obo_id', $obo_id)
-      ->execute();
-    if ($success) {
-      drupal_set_message(t("The vocabulary %vocab has been updated.", array('%vocab' => $uobo_name)));
-    }
-    else {
-      drupal_set_message(t("The vocabulary %vocab could not be updated.", array('%vocab' => $uobo_name)), 'error');
-    }
-
-  }
-  // If the user requested to update and load then we've already handled the
-  // update now we just need to load.
-  if ($form_state['clicked_button']['#name'] == 'update_load_obo') {
-    tripal_submit_obo_job(array('obo_id' => $obo_id));
-  }
-  if ($form_state['clicked_button']['#name'] == 'add_new_obo') {
-    $success = db_insert('tripal_cv_obo')
-    ->fields(array(
-      'name' => $obo_name,
-      'path' => $obo_url ? $obo_url : $obo_file,
-    ))
-    ->execute();
-    if ($success) {
-      drupal_set_message(t("The vocabulary %vocab has been added.", array('%vocab' => $obo_name)));
-    }
-    else {
-      drupal_set_message(t("The vocabulary %vocab could not be added.", array('%vocab' => $obo_name)), 'error');
-    }
-  }
-}
-
-/**
- * A wrapper function for importing the user specified OBO file into Chado by
- * specifying the obo_id of the OBO. It requires that the file be in OBO v1.2
- * compatible format.  This function is typically executed via the Tripal jobs
- * management after a user submits a job via the Load Onotloies form.
- *
- * @param $obo_id
- *   An obo_id from the tripal_cv_obo file that specifies which OBO file to import
- * @param $job_id
- *   The job_id of the job from the Tripal jobs management system.
-
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_chado_load_obo_v1_2_id($obo_id, $jobid = NULL) {
-
-  // Get the OBO reference.
-  $sql = "SELECT * FROM {tripal_cv_obo} WHERE obo_id = :obo_id";
-  $obo = db_query($sql, array(':obo_id' => $obo_id))->fetchObject();
-
-  // Convert the module name to the real path if present
-  if (preg_match("/\{(.*?)\}/", $obo->path, $matches)) {
-    $module = $matches[1];
-    $path = drupal_realpath(drupal_get_path('module', $module));
-    $obo->path = preg_replace("/\{.*?\}/", $path, $obo->path);
-  }
-
-  // if the reference is for a remote URL then run the URL processing function
-  if (preg_match("/^https:\/\//", $obo->path) or
-      preg_match("/^http:\/\//", $obo->path) or
-      preg_match("/^ftp:\/\//", $obo->path)) {
-    tripal_chado_load_obo_v1_2_url($obo->name, $obo->path, $jobid, 0);
-  }
-  // if the reference is for a local file then run the file processing function
-  else {
-    // check to see if the file is located local to Drupal
-    $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $obo->path;
-    if (file_exists($dfile)) {
-      tripal_chado_load_obo_v1_2_file($obo->name, $dfile , $jobid, 0);
-    }
-    // if not local to Drupal, the file must be someplace else, just use
-    // the full path provided
-    else {
-      if (file_exists($obo->path)) {
-        tripal_chado_load_obo_v1_2_file($obo->name, $obo->path, $jobid, 0);
-      }
-      else {
-        print "ERROR: could not find OBO file: '$obo->path'\n";
-      }
-    }
-  }
-}
-
-/**
- * A wrapper function for importing the user specified OBO file into Chado by
- * specifying the filename and path of the OBO. It requires that the file be in OBO v1.2
- * compatible format.  This function is typically executed via the Tripal jobs
- * management after a user submits a job via the Load Onotloies form.
- *
- * @param $obo_name
- *   The name of the OBO (typially the ontology or controlled vocabulary name)
- * @param $file
- *   The path on the file system where the ontology can be found
- * @param $job_id
- *   The job_id of the job from the Tripal jobs management system.
- * @param $is_new
- *   Set to TRUE if this is a new ontology that does not yet exist in the
- *   tripal_cv_obo table.  If TRUE the OBO will be added to the table.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_chado_load_obo_v1_2_file($obo_name, $file, $jobid = NULL, $is_new = TRUE) {
-  $newcvs = array();
-
-  if ($is_new) {
-    tripal_insert_obo($obo_name, $file);
-  }
-
-  $success = tripal_chado_load_obo_v1_2($file, $jobid, $newcvs);
-  if ($success) {
-    // update the cvtermpath table
-    tripal_chado_load_update_cvtermpath($newcvs, $jobid);
-    print "\nDone\n";
-  }
-}
-
-/**
- * A wrapper function for importing the user specified OBO file into Chado by
- * specifying the remote URL of the OBO. It requires that the file be in OBO v1.2
- * compatible format.  This function is typically executed via the Tripal jobs
- * management after a user submits a job via the Load Onotloies form.
- *
- * @param $obo_name
- *   The name of the OBO (typially the ontology or controlled vocabulary name)
- * @param $url
- *   The remote URL of the OBO file.
- * @param $job_id
- *   The job_id of the job from the Tripal jobs management system.
- * @param $is_new
- *   Set to TRUE if this is a new ontology that does not yet exist in the
- *   tripal_cv_obo table.  If TRUE the OBO will be added to the table.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_chado_load_obo_v1_2_url($obo_name, $url, $jobid = NULL, $is_new = TRUE) {
-
-  $newcvs = array();
-
-  // first download the OBO
-  $temp = tempnam(sys_get_temp_dir(), 'obo_');
-  print "Downloading URL $url, saving to $temp\n";
-  $url_fh = fopen($url, "r");
-  $obo_fh = fopen($temp, "w");
-  if (!$url_fh) {
-    tripal_cv_obo_quiterror("Unable to download the remote OBO file at $url. Could a firewall be blocking outgoing connections? " .
-      " if you are unable to download the file you may manually downlod the OBO file and use the web interface to " .
-      " specify the location of the file on your server.");
-
-  }
-  while (!feof($url_fh)) {
-    fwrite($obo_fh, fread($url_fh, 255), 255);
-  }
-  fclose($url_fh);
-  fclose($obo_fh);
-
-  if ($is_new) {
-    tripal_insert_obo($obo_name, $url);
-  }
-
-  // second, parse the OBO
-  $success = tripal_chado_load_obo_v1_2($temp, $jobid, $newcvs);
-  if ($success) {
-
-    // update the cvtermpath table
-    tripal_chado_load_update_cvtermpath($newcvs, $jobid);
-    print "Done\n";
-  }
-  // now remove the temp file
-  unlink($temp);
-}
-
-/**
- * A function for executing the cvtermpath function of Chado.  This function
- * populates the cvtermpath table of Chado for quick lookup of term
- * relationships
- *
- * @param $newcvs
- *   An associative array of controlled vocabularies to update.  The key must be
- *   the name of the vocabulary and the value the cv_id from the cv table of chado.
- * @param $jobid
- *   The job_id of the job from the Tripal jobs management system.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_chado_load_update_cvtermpath($newcvs, $jobid) {
-
-  print "\nUpdating cvtermpath table.  This may take a while...\n";
-  foreach ($newcvs as $namespace => $cvid) {
-    tripal_update_cvtermpath($cvid, $jobid);
-  }
-}
-
-/**
- * Imports a given OBO file into Chado.  This function is usually called by
- * one of three wrapper functions:  tripal_chado_load_obo_v1_2_id,
- * tripal_chado_load_obo_v1_2_file or tirpal_cv_load_obo_v1_2_url. But, it can
- * be called directly if the full path to an OBO file is available on the
- * file system.
- *
- * @param $flie
- *   The full path to the OBO file on the file system
- * @param $jobid
- *   The job_id of the job from the Tripal jobs management system.
- * @param $newcvs
- *   An empty array passed by reference that upon return will contain the list
- *   of newly added vocabularies.  The key will contain the CV name and the
- *   value the new cv_id
- *
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_chado_load_obo_v1_2($file, $jobid = NULL, &$newcvs) {
-
-  //$transaction = db_transaction();
-
-  print "\nNOTE: Loading of this OBO file is performed using a database transaction. \n" .
-      "If the load fails or is terminated prematurely then the entire set of \n" .
-      "insertions/updates is rolled back and will not be found in the database\n\n";
-  try {
-    $header = array();
-
-    // make sure our temporary table exists
-    $ret = array();
-
-    // empty the temp table
-    $sql = "DELETE FROM {tripal_obo_temp}";
-    chado_query($sql);
-
-    print "Step 1: Preloading File $file\n";
-
-    // parse the obo file
-    $default_db = tripal_cv_obo_parse($file, $header, $jobid);
-
-    // add the CV for this ontology to the database.  The v1.2 definition
-    // specifies a 'default-namespace' to be used if a 'namespace' is not
-    // present for each stanza.  Some ontologies have adopted the v1.4 method
-    // in their v1.2 files and not including it.
-    if (array_key_exists('default-namespace', $header)) {
-      $defaultcv = tripal_insert_cv($header['default-namespace'][0], '');
-      if (!$defaultcv) {
-        tripal_cv_obo_quiterror('Cannot add namespace ' . $header['default-namespace'][0]);
-      }
-      $newcvs[$header['default-namespace'][0]] = $defaultcv->cv_id;
-    }
-    // if the 'default-namespace' is missing
-    else {
-
-      // look to see if an 'ontology' key is present.  It is part of the v1.4
-      // specification so it shouldn't be in the file, but just in case
-      if (array_key_exists('ontology', $header)) {
-        $defaultcv = tripal_insert_cv(strtoupper($header['ontology'][0]), '');
-        if (!$defaultcv) {
-          tripal_cv_obo_quiterror('Cannot add namespace ' . strtoupper($header['ontology'][0]));
-        }
-        $newcvs[strtoupper(strtoupper($header['ontology'][0]))] = $defaultcv->cv_id;
-      }
-      else {
-        tripal_cv_obo_quiterror("Could not find a namespace for this OBO file.");
-      }
-      watchdog('t_obo_loader', "This OBO is missing the 'default-namespace' header. It is not possible to determine which vocabulary terms without a 'namespace' key should go.  Instead, those terms will be placed in the '%vocab' vocabulary.",
-        array('%vocab' => $defaultcv->name), WATCHDOG_WARNING);
-    }
-
-    // add any typedefs to the vocabulary first
-    print "\nStep 2: Loading type defs...\n";
-    tripal_cv_obo_load_typedefs($defaultcv, $newcvs, $default_db, $jobid);
-
-    // next add terms to the vocabulary
-    print "\nStep 3: Loading terms...\n";
-    if (!tripal_cv_obo_process_terms($defaultcv, $jobid, $newcvs, $default_db)) {
-      tripal_cv_obo_quiterror('Cannot add terms from this ontology');
-    }
-  }
-  catch (Exception $e) {
-   //$transaction->rollback();
-    print "\n"; // make sure we start errors on new line
-    print "FAILED. Rolling back database changes...\n";
-    watchdog_exception('T_obo_loader', $e);
-    return FALSE;
-  }
-
-  return TRUE;
-}
-
-/**
- * Immediately terminates loading of the OBO file.
- *
- * @param $message
- *   The error message to present to the user
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_quiterror($message) {
-
-  tripal_report_error("T_obo_loader", TRIPAL_ERROR, $message, array());
-  exit;
-
-}
-
-/**
- * OBO files are divided into a typedefs terms section and vocabulary terms section.
- * This function loads the typedef terms from the OBO.
- *
- * @param $defaultcv
- *   A database object containing a record from the cv table for the
- *   default controlled vocabulary
- * @param $newcvs
- *   An associative array of controlled vocabularies for this OBO.  The key must be
- *   the name of the vocabulary and the value the cv_id from the cv table of chado.
- * @param $default_db
- *   The name of the default database.
- * @param $jobid
- *   The job_id of the job from the Tripal jobs management system.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_load_typedefs($defaultcv, $newcvs, $default_db, $jobid) {
-  $sql = "SELECT * FROM {tripal_obo_temp} WHERE type = 'Typedef' ";
-  $typedefs = chado_query($sql);
-
-  $sql = "
-    SELECT count(*) as num_terms
-    FROM {tripal_obo_temp}
-    WHERE type = 'Typedef'
-  ";
-  $result = chado_query($sql)->fetchObject();
-  $count = $result->num_terms;
-
-  // calculate the interval for updates
-  $interval = intval($count * 0.0001);
-  if ($interval < 1) {
-    $interval = 1;
-  }
-  $i = 0;
-  foreach ($typedefs as $typedef) {
-    $term = unserialize(base64_decode($typedef->stanza));
-
-    // update the job status every interval
-    if ($i % $interval == 0) {
-      $complete = ($i / $count) * 33.33333333;
-      if ($jobid) {
-        tripal_set_job_progress($jobid, intval($complete + 33.33333333));
-      }
-      printf("%d of %d records. (%0.2f%%) Memory: %s bytes\r", $i, $count, $complete * 3, number_format(memory_get_usage()));
-    }
-
-    tripal_cv_obo_process_term($term, $defaultcv->name, 1, $newcvs, $default_db);
-
-    $i++;
-  }
-
-  // Set the final status.
-  if ($count > 0) {
-    $complete = ($i / $count) * 33.33333333;
-  }
-  else {
-    $complete = 33.33333333;
-  }
-  if ($jobid) {
-    tripal_set_job_progress($jobid, intval($complete + 33.33333333));
-  }
-  printf("%d of %d records. (%0.2f%%) Memory: %s bytes\r", $i, $count, $complete * 3, number_format(memory_get_usage()));
-
-  return 1;
-}
-
-/**
- * OBO files are divided into a typedefs section and a terms section.  This
- * function loads the typedef terms from the OBO.
- *
- * @param $defaultcv
- *   A database object containing a record from the cv table for the
- *   default controlled vocabulary
- * @param $jobid
- *  The job_id of the job from the Tripal jobs management system.
- * @param $newcvs
- *   An associative array of controlled vocabularies for this OBO.  The key must be
- *   the name of the vocabulary and the value the cv_id from the cv table of chado.
- * @param $default_db
- *   The name of the default database.
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_process_terms($defaultcv, $jobid = NULL, &$newcvs, $default_db) {
-
-  $i = 0;
-
-  // iterate through each term from the OBO file and add it
-  $sql = "
-    SELECT * FROM {tripal_obo_temp}
-    WHERE type = 'Term'
-    ORDER BY id
-  ";
-  $terms = chado_query($sql);
-
-  $sql = "
-    SELECT count(*) as num_terms
-    FROM {tripal_obo_temp}
-    WHERE type = 'Term'
-  ";
-  $result = chado_query($sql)->fetchObject();
-  $count = $result->num_terms;
-
-  // calculate the interval for updates
-  $interval = intval($count * 0.0001);
-  if ($interval < 1) {
-    $interval = 1;
-  }
-  foreach ($terms as $t) {
-    $term = unserialize(base64_decode($t->stanza));
-
-    // update the job status every interval
-    if ($i % $interval == 0) {
-      $complete = ($i / $count) * 33.33333333;
-      if ($jobid) {
-        tripal_set_job_progress($jobid, intval($complete + 66.666666));
-      }
-      printf("%d of %d records. (%0.2f%%) Memory: %s bytes\r", $i, $count, $complete * 3, number_format(memory_get_usage()));
-    }
-
-    // add/update this term
-    if (!tripal_cv_obo_process_term($term, $defaultcv->name, 0, $newcvs, $default_db)) {
-      tripal_cv_obo_quiterror("Failed to process terms from the ontology");
-    }
-
-    $i++;
-  }
-
-  // set the final status
-  if ($count > 0) {
-    $complete = ($i / $count) * 33.33333333;
-  }
-  else {
-    $complete = 33.33333333;
-  }
-  if ($jobid) {
-    tripal_set_job_progress($jobid, intval($complete + 66.666666));
-  }
-  printf("%d of %d records. (%0.2f%%) Memory: %s bytes\r", $i, $count, $complete * 3, number_format(memory_get_usage()));
-
-
-  return 1;
-}
-
-/**
- * Uses the provided term array to add/update information to Chado about the
- * term including the term, dbxref, synonyms, properties, and relationships.
- *
- * @param $term
- *   An array representing the cvterm.
- * @param $defaultcv
- *   The name of the default controlled vocabulary
- * @is_relationship
- *   Set to 1 if this term is a relationship term
- * @default_db
- *   The name of the default database.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_process_term($term, $defaultcv, $is_relationship = 0, &$newcvs, $default_db) {
-
-  // make sure we have a namespace for this term
-  if (!array_key_exists('namespace', $term) and !($defaultcv or $defaultcv == '')) {
-    tripal_cv_obo_quiterror("Cannot add the term: no namespace defined. " . $term['id'][0]);
-  }
-
-  // construct the term array for sending to the tripal_chado_add_cvterm function
-  // for adding a new cvterm
-  $t = array();
-  $t['id'] = $term['id'][0];
-  $t['name'] = $term['name'][0];
-  if (array_key_exists('def', $term)) {
-    $t['definition'] = $term['def'][0];
-  }
-  if (array_key_exists('subset', $term)) {
-    $t['subset'] = $term['subset'][0];
-  }
-  if (array_key_exists('namespace', $term)) {
-    $t['namespace'] = $term['namespace'][0];
-  }
-  if (array_key_exists('is_obsolete', $term)) {
-    $t['is_obsolete'] = $term['is_obsolete'][0];
-  }
-
-  $t['cv_name'] = $defaultcv;
-  $t['is_relationship'] = $is_relationship;
-  $t['db_name'] = $default_db;
-
-  // add the cvterm
-  $cvterm = tripal_insert_cvterm($t, array('update_existing' => TRUE));
-  if (!$cvterm) {
-    tripal_cv_obo_quiterror("Cannot add the term " . $term['id'][0]);
-  }
-
-  if (array_key_exists('namespace', $term)) {
-    $newcvs[$term['namespace'][0]] = $cvterm->cv_id;
-  }
-
-  // now handle other properites
-  if (array_key_exists('is_anonymous', $term)) {
-    //print "WARNING: unhandled tag: is_anonymous\n";
-  }
-  if (array_key_exists('alt_id', $term)) {
-    foreach ($term['alt_id'] as $alt_id) {
-      if (!tripal_cv_obo_add_cvterm_dbxref($cvterm, $alt_id)) {
-        tripal_cv_obo_quiterror("Cannot add alternate id $alt_id");
-      }
-    }
-  }
-
-  if (array_key_exists('subset', $term)) {
-    //print "WARNING: unhandled tag: subset\n";
-  }
-  // add synonyms for this cvterm
-  if (array_key_exists('synonym', $term)) {
-    if (!tripal_cv_obo_add_synonyms($term, $cvterm)) {
-      tripal_cv_obo_quiterror("Cannot add synonyms");
-    }
-  }
-
-  // reformat the deprecated 'exact_synonym, narrow_synonym, and broad_synonym'
-  // types to be of the v1.2 standard
-  if (array_key_exists('exact_synonym', $term) or array_key_exists('narrow_synonym', $term) or array_key_exists('broad_synonym', $term)) {
-    if (array_key_exists('exact_synonym', $term)) {
-      foreach ($term['exact_synonym'] as $synonym) {
-        $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 EXACT $2', $synonym);
-        $term['synonym'][] = $new;
-      }
-    }
-    if (array_key_exists('narrow_synonym', $term)) {
-      foreach ($term['narrow_synonym'] as $synonym) {
-        $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 NARROW $2', $synonym);
-        $term['synonym'][] = $new;
-      }
-    }
-    if (array_key_exists('broad_synonym', $term)) {
-      foreach ($term['broad_synonym'] as $synonym) {
-        $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 BROAD $2', $synonym);
-        $term['synonym'][] = $new;
-      }
-    }
-
-    if (!tripal_cv_obo_add_synonyms($term, $cvterm)) {
-      tripal_cv_obo_quiterror("Cannot add/update synonyms");
-    }
-  }
-
-  // add the comment to the cvtermprop table
-  if (array_key_exists('comment', $term)) {
-    $comments = $term['comment'];
-    $j = 0;
-    foreach ($comments as $comment) {
-      if (!tripal_cv_obo_add_cvterm_prop($cvterm, 'comment', $comment, $j)) {
-        tripal_cv_obo_quiterror("Cannot add/update cvterm property");
-      }
-      $j++;
-    }
-  }
-
-  // add any other external dbxrefs
-  if (array_key_exists('xref', $term)) {
-    foreach ($term['xref'] as $xref) {
-      if (!tripal_cv_obo_add_cvterm_dbxref($cvterm, $xref)) {
-        tripal_cv_obo_quiterror("Cannot add/update cvterm database reference (dbxref).");
-      }
-    }
-  }
-
-  if (array_key_exists('xref_analog', $term)) {
-    foreach ($term['xref_analog'] as $xref) {
-      if (!tripal_cv_obo_add_cvterm_dbxref($cvterm, $xref)) {
-        tripal_cv_obo_quiterror("Cannot add/update cvterm database reference (dbxref).");
-      }
-    }
-  }
-  if (array_key_exists('xref_unk', $term)) {
-    foreach ($term['xref_unk'] as $xref) {
-      if (!tripal_cv_obo_add_cvterm_dbxref($cvterm, $xref)) {
-        tripal_cv_obo_quiterror("Cannot add/update cvterm database reference (dbxref).");
-      }
-    }
-  }
-
-  // add is_a relationships for this cvterm
-  if (array_key_exists('is_a', $term)) {
-    foreach ($term['is_a'] as $is_a) {
-      if (!tripal_cv_obo_add_relationship($cvterm, $defaultcv, 'is_a', $is_a, $is_relationship, $default_db)) {
-        tripal_cv_obo_quiterror("Cannot add relationship is_a: $is_a");
-      }
-    }
-  }
-
-  if (array_key_exists('intersection_of', $term)) {
-    //print "WARNING: unhandled tag: intersection_of\n";
-  }
-  if (array_key_exists('union_of', $term)) {
-    //print "WARNING: unhandled tag: union_on\n";
-  }
-  if (array_key_exists('disjoint_from', $term)) {
-    //print "WARNING: unhandled tag: disjoint_from\n";
-  }
-  if (array_key_exists('relationship', $term)) {
-    foreach ($term['relationship'] as $value) {
-      $rel = preg_replace('/^(.+?)\s.+?$/', '\1', $value);
-      $object = preg_replace('/^.+?\s(.+?)$/', '\1', $value);
-      // The Gene Ontology uses 'has_part' for transitive relationships, but
-      // it specifically indicates that 'has_part' should not be used for
-      // grouping annotations.  Unfortunately, this means that when we
-      // try to popoulate the cvtermpath table a 'has_part' relationships
-      // will be used for exactly that purpose: to group annotations.  This
-      // doesn't seem to the be the case for other vocabularies such as the
-      // sequence ontology that uses has_part as primary relationship between
-      // terms. So, when loading the GO, we'll not include has_part
-      // relationships.
-      /*if ($rel == 'has_part' and $cvterm->dbxref_id->db_id->name == 'GO') {
-        continue;
-      }*/
-      if (!tripal_cv_obo_add_relationship($cvterm, $defaultcv, $rel, $object, $is_relationship, $default_db)) {
-        tripal_cv_obo_quiterror("Cannot add relationship $rel: $object");
-      }
-    }
-  }
-  if (array_key_exists('replaced_by', $term)) {
-   //print "WARNING: unhandled tag: replaced_by\n";
-  }
-  if (array_key_exists('consider', $term)) {
-    //print "WARNING: unhandled tag: consider\n";
-  }
-  if (array_key_exists('use_term', $term)) {
-    //print "WARNING: unhandled tag: user_term\n";
-  }
-  if (array_key_exists('builtin', $term)) {
-    //print "WARNING: unhandled tag: builtin\n";
-  }
-  return 1;
-}
-
-/**
- * Adds a cvterm relationship
- *
- * @param $cvterm
- *   A database object for the cvterm
- * @param $rel
- *   The relationship name
- * @param $objname
- *   The relationship term name
- * @param $defaultcv
- *   A database object containing a record from the cv table for the
- *   default controlled vocabulary
- * @object_is_relationship
- *   Set to 1 if this term is a relationship term
- * @default_db
- *   The name of the default database.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_add_relationship($cvterm, $defaultcv, $rel,
-  $objname, $object_is_relationship = 0, $default_db = 'OBO_REL') {
-
-  // make sure the relationship cvterm exists
-  $term = array(
-    'name' => $rel,
-    'id' => "$default_db:$rel",
-    'definition' => '',
-    'is_obsolete' => 0,
-    'cv_name' => $defaultcv,
-    'is_relationship' => TRUE,
-    'db_naame' => $default_db
-  );
-  $relcvterm = tripal_insert_cvterm($term, array('update_existing' => FALSE));
-
-  if (!$relcvterm) {
-    // if the relationship term couldn't be found in the default_db provided
-    // then do on more check to find it in the relationship ontology
-    $term = array(
-      'name' => $rel,
-      'id' => "OBO_REL:$rel",
-      'definition' => '',
-      'is_obsolete' => 0,
-      'cv_name' => $defaultcv,
-      'is_relationship' => TRUE,
-      'db_name' => 'OBO_REL'
-    );
-    $relcvterm = tripal_insert_cvterm($term, array('update_existing' => FALSE));
-    if (!$relcvterm) {
-      tripal_cv_obo_quiterror("Cannot find the relationship term in the current ontology or in the relationship ontology: $rel\n");
-    }
-  }
-
-  // get the object term
-  $oterm = tripal_cv_obo_get_term($objname);
-  if (!$oterm) {
-    tripal_cv_obo_quiterror("Could not find object term $objname\n");
-  }
-
-  $objterm = array();
-  $objterm['id']            = $oterm['id'][0];
-  $objterm['name']          = $oterm['name'][0];
-  if (array_key_exists('def', $oterm)) {
-    $objterm['definition']           = $oterm['def'][0];
-  }
-  if (array_key_exists('subset', $oterm)) {
-    $objterm['subset']      = $oterm['subset'][0];
-  }
-  if (array_key_exists('namespace', $oterm)) {
-    $objterm['namespace']   = $oterm['namespace'][0];
-  }
-  if (array_key_exists('is_obsolete', $oterm)) {
-    $objterm['is_obsolete'] = $oterm['is_obsolete'][0];
-  }
-
-  $objterm['cv_name' ] = $defaultcv;
-  $objterm['is_relationship'] = $object_is_relationship;
-  $objterm['db_name'] = $default_db;
-
-  $objcvterm = tripal_insert_cvterm($objterm, array('update_existing' => TRUE));
-  if (!$objcvterm) {
-    tripal_cv_obo_quiterror("Cannot add cvterm " . $oterm['name'][0]);
-  }
-
-  // check to see if the cvterm_relationship already exists, if not add it
-  $values = array(
-    'type_id'    => $relcvterm->cvterm_id,
-    'subject_id' => $cvterm->cvterm_id,
-    'object_id'  => $objcvterm->cvterm_id
-  );
-  $result = chado_select_record('cvterm_relationship', array('*'), $values);
-  if (count($result) == 0) {
-    $options = array('return_record' => FALSE);
-    $success = chado_insert_record('cvterm_relationship', $values, $options);
-    if (!$success) {
-      tripal_cv_obo_quiterror("Cannot add term relationship: '$cvterm->name' $rel '$objcvterm->name'");
-    }
-  }
-
-  return TRUE;
-}
-
-/**
- * Retreives the term array from the temp loading table for a given term id.
- *
- * @param id
- *   The id of the term to retrieve
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_get_term($id) {
-  $values = array('id' => $id);
-  $result = chado_select_record('tripal_obo_temp', array('stanza'), $values);
-  if (count($result) == 0) {
-    return FALSE;
-  }
-  return unserialize(base64_decode($result[0]->stanza));
-}
-
-/**
- * Adds the synonyms to a term
- *
- * @param term
- *   An array representing the cvterm. It must have a 'synonym' key/value pair.
- * @param cvterm
- *   The database object of the cvterm to which the synonym will be added.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_add_synonyms($term, $cvterm) {
-
-  // make sure we have a 'synonym_type' vocabulary
-  $syncv = tripal_insert_cv(
-    'synonym_type',
-    'A local vocabulary added for synonym types.'
-  );
-
-  // now add the synonyms
-  if (array_key_exists('synonym', $term)) {
-    foreach ($term['synonym'] as $synonym) {
-
-      // separate out the synonym definition and the synonym type
-      $def = preg_replace('/^\s*"(.*)"\s*.*$/', '\1', $synonym);
-      // the scope will be 'EXACT', etc...
-      $scope = drupal_strtolower(preg_replace('/^.*"\s+(.*?)\s+.*$/', '\1', $synonym));
-      if (!$scope) {  // if no scope then default to 'exact'
-        $scope = 'exact';
-      }
-
-      // make sure the synonym type exists in the 'synonym_type' vocabulary
-      $values = array(
-        'name' => $scope,
-        'cv_id' => array(
-          'name' => 'synonym_type',
-        ),
-      );
-      $syntype = tripal_get_cvterm($values);
-
-      // if it doesn't exist then add it
-      if (!$syntype) {
-        // build a 'term' object so we can add the missing term
-        $term = array(
-           'name' => $scope,
-           'id' => "synonym_type:$scope",
-           'definition' => '',
-           'is_obsolete' => 0,
-           'cv_name' => $syncv->name,
-           'is_relationship' => FALSE
-        );
-        $syntype = tripal_insert_cvterm($term, array('update_existing' => TRUE));
-        if (!$syntype) {
-          tripal_cv_obo_quiterror("Cannot add synonym type: internal:$scope");
-        }
-      }
-
-      // make sure the synonym doesn't already exists
-      $values = array(
-        'cvterm_id' => $cvterm->cvterm_id,
-        'synonym' => $def
-      );
-      $results = chado_select_record('cvtermsynonym', array('*'), $values);
-      if (count($results) == 0) {
-        $values = array(
-          'cvterm_id' => $cvterm->cvterm_id,
-          'synonym' => $def,
-          'type_id' => $syntype->cvterm_id
-        );
-        $options = array('return_record' => FALSE);
-        $success = chado_insert_record('cvtermsynonym', $values, $options);
-        if (!$success) {
-          tripal_cv_obo_quiterror("Failed to insert the synonym for term: $name ($def)");
-        }
-      }
-
-      // now add the dbxrefs for the synonym if we have a comma in the middle
-      // of a description then this will cause problems when splitting os lets
-      // just change it so it won't mess up our splitting and then set it back
-      // later.
-      /**
-      $synonym = preg_replace('/(".*?),\s(.*?")/','$1,_$2',$synonym);
-      $dbxrefs = preg_split("/, /",preg_replace('/^.*\[(.*?)\]$/','\1',$synonym));
-      foreach ($dbxrefs as $dbxref) {
-       $dbxref = preg_replace('/,_/',", ",$dbxref);
-        if ($dbxref) {
-          tripal_cv_obo_add_cvterm_dbxref($syn,$dbxref);
-        }
-      }
-      */
-    }
-  }
-
-  return TRUE;
-}
-
-/**
- * Parse the OBO file and populate the templ loading table
- *
- * @param $file
- *   The path on the file system where the ontology can be found
- * @param $header
- *   An array passed by reference that will be populated with the header
- *   information from the OBO file
- * @param $jobid
- *   The job_id of the job from the Tripal jobs management system.
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_parse($obo_file, &$header, $jobid) {
-  $in_header = 1;
-  $stanza = array();
-  $default_db = '';
-  $line_num = 0;
-  $num_read = 0;
-  $intv_read = 0;
-
-  $filesize = filesize($obo_file);
-  $interval = intval($filesize * 0.01);
-  if ($interval < 1) {
-    $interval = 1;
-  }
-
-  // iterate through the lines in the OBO file and parse the stanzas
-  $fh = fopen($obo_file, 'r');
-  while ($line = fgets($fh)) {
-
-    $line_num++;
-    $size = drupal_strlen($line);
-    $num_read += $size;
-    $intv_read += $size;
-    $line = trim($line);
-
-    // update the job status every 1% features
-    if ($intv_read >= $interval) {
-      $percent = sprintf("%.2f", ($num_read / $filesize) * 100);
-      print "Parsing Line $line_num (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
-      if ($jobid) {
-        tripal_set_job_progress($jobid, intval(($num_read / $filesize) * 33.33333333));
-      }
-      $intv_read = 0;
-    }
-
-    // remove newlines
-    $line = rtrim($line);
-
-    // remove any special characters that may be hiding
-    $line = preg_replace('/[^(\x20-\x7F)]*/', '', $line);
-
-    // skip empty lines
-    if (strcmp($line, '') == 0) {
-      continue;
-    }
-
-    //remove comments from end of lines
-    $line = preg_replace('/^(.*?)\!.*$/', '\1', $line);  // TODO: if the explamation is escaped
-
-    // at the first stanza we're out of header
-    if (preg_match('/^\s*\[/', $line)) {
-      $in_header = 0;
-
-      // store the stanza we just finished reading
-      if (sizeof($stanza) > 0) {
-        // add the term to the temp table
-        $values = array(
-          'id' => $stanza['id'][0],
-          'stanza' => base64_encode(serialize($stanza)),
-          'type' => $type,
-        );
-        $success = chado_insert_record('tripal_obo_temp', $values);
-        if (!$success) {
-          tripal_report_error('T_obo_loader', "ERROR: Cannot insert stanza into temporary table.", array(), 'error');
-          exit;
-        }
-
-      }
-      // get the stanza type:  Term, Typedef or Instance
-      $type = preg_replace('/^\s*\[\s*(.+?)\s*\]\s*$/', '\1', $line);
-
-      // start fresh with a new array
-      $stanza = array();
-      continue;
-    }
-    // break apart the line into the tag and value but ignore any escaped colons
-    preg_replace("/\\:/", "|-|-|", $line); // temporarily replace escaped colons
-    $pair = explode(":", $line, 2);
-    $tag = $pair[0];
-    $value = ltrim(rtrim($pair[1]));// remove surrounding spaces
-
-    // if this is the ID then look for the default DB
-    $matches = array();
-    if ($tag == 'id' and preg_match('/^(.+?):.*$/', $value, $matches)) {
-       $default_db = $matches[1];
-    }
-
-    $tag = preg_replace("/\|-\|-\|/", "\:", $tag); // return the escaped colon
-    $value = preg_replace("/\|-\|-\|/", "\:", $value);
-    if ($in_header) {
-      if (!array_key_exists($tag, $header)) {
-        $header[$tag] = array();
-      }
-      $header[$tag][] = $value;
-    }
-    else {
-      if (!array_key_exists($tag, $stanza)) {
-        $stanza[$tag] = array();
-      }
-      $stanza[$tag][] = $value;
-    }
-  }
-  // now add the last term in the file
-  if (sizeof($stanza) > 0) {
-    $values = array(
-      'id' => $stanza['id'][0],
-      'stanza' => base64_encode(serialize($stanza)),
-      'type' => $type,
-    );
-    chado_insert_record('tripal_obo_temp', $values);
-    if (!$success) {
-      tripal_report_error('T_obo_loader', "ERROR: Cannot insert stanza into temporary table.", array(), 'error');
-      exit;
-    }
-    $percent = sprintf("%.2f", ($num_read / $filesize) * 100);
-    print "Parsing Line $line_num (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
-    if ($jobid) {
-      tripal_set_job_progress($jobid, intval(($num_read / $filesize) * 33.33333333));
-    }
-  }
-  return $default_db;
-}
-
-/**
- * Adds a database reference to a cvterm
- *
- * @param cvterm
- *   The database object of the cvterm to which the synonym will be added.
- * @param xref
- *   The cross refernce.  It should be of the form from the OBO specification
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_add_cvterm_dbxref($cvterm, $xref) {
-
-  $dbname = preg_replace('/^(.+?):.*$/', '$1', $xref);
-  $accession = preg_replace('/^.+?:\s*(.*?)(\{.+$|\[.+$|\s.+$|\".+$|$)/', '$1', $xref);
-  $description = preg_replace('/^.+?\"(.+?)\".*?$/', '$1', $xref);
-  $dbxrefs = preg_replace('/^.+?\[(.+?)\].*?$/', '$1', $xref);
-
-  if (!$accession) {
-    tripal_cv_obo_quiterror();
-    tripal_report_error("T_obo_loader", TRIPAL_WARNING, "Cannot add a dbxref without an accession: '$xref'", NULL);
-    return FALSE;
-  }
-
-  // if the xref is a database link, handle that specially
-  if (strcmp($dbname, 'http') == 0) {
-    $accession = $xref;
-    $dbname = 'URL';
-  }
-
-  // add the database
-  $db = tripal_insert_db(array('name' => $dbname));
-  if (!$db) {
-    tripal_cv_obo_quiterror("Cannot find database '$dbname' in Chado.");
-  }
-
-  // now add the dbxref
-  $dbxref = tripal_cv_obo_add_dbxref($db->db_id, $accession, '', $description);
-  if (!$dbxref) {
-    tripal_cv_obo_quiterror("Cannot find or add the database reference (dbxref)");
-  }
-
-  // finally add the cvterm_dbxref but first check to make sure it exists
-  $values = array(
-    'cvterm_id' => $cvterm->cvterm_id,
-    'dbxref_id' => $dbxref->dbxref_id,
-  );
-  $result = chado_select_record('cvterm_dbxref', array('*'), $values);
-  if (count($result) == 0) {
-    $ins_options = array('return_record' => FALSE);
-    $result = chado_insert_record('cvterm_dbxref', $values, $ins_options);
-    if (!$result) {
-      tripal_cv_obo_quiterror("Cannot add cvterm_dbxref: $xref");
-      return FALSE;
-    }
-  }
-
-  return TRUE;
-}
-
-/**
- * Adds a property to a cvterm
- *
- * @param cvterm
- *   A database object for the cvterm to which properties will be added
- * @param $property
- *   The name of the property to add
- * @param $value
- *   The value of the property
- * @param rank
- *   The rank of the property
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_add_cvterm_prop($cvterm, $property, $value, $rank) {
-
-  // make sure the 'cvterm_property_type' CV exists
-  $cv = tripal_insert_cv('cvterm_property_type', '');
-  if (!$cv) {
-    tripal_cv_obo_quiterror("Cannot add/find cvterm_property_type cvterm");
-  }
-
-  // get the property type cvterm.  If it doesn't exist then we want to add it
-  $values = array(
-    'name' => $property,
-    'cv_id' => $cv->cv_id,
-  );
-  $results = chado_select_record('cvterm', array('*'), $values);
-  if (count($results) == 0) {
-    $term = array(
-      'name' => $property,
-      'id' => "internal:$property",
-      'definition' => '',
-      'is_obsolete' => 0,
-      'cv_name' => $cv->name,
-      'is_relationship' => FALSE,
-    );
-    $cvproptype = tripal_insert_cvterm($term, array('update_existing' => FALSE));
-    if (!$cvproptype) {
-      tripal_cv_obo_quiterror("Cannot add cvterm property: internal:$property");
-      return FALSE;
-    }
-  }
-  else {
-    $cvproptype = $results[0];
-  }
-
-  // remove any properties that currently exist for this term.  We'll reset them
-  if ($rank == 0) {
-    $values = array('cvterm_id' => $cvterm->cvterm_id);
-    $success = chado_delete_record('cvtermprop', $values);
-    if (!$success) {
-       tripal_cv_obo_quiterror("Could not remove existing properties to update property $property for term\n");
-       return FALSE;
-    }
-  }
-
-  // now add the property
-  $values = array(
-    'cvterm_id' => $cvterm->cvterm_id,
-    'type_id' => $cvproptype->cvterm_id,
-    'value' => $value,
-    'rank' => $rank,
-  );
-  $options = array('return_record' => FALSE);
-  $result = chado_insert_record('cvtermprop', $values, $options);
-  if (!$result) {
-    tripal_cv_obo_quiterror("Could not add property $property for term\n");
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Adds a database cross reference to a cvterm
- *
- * @param db_id
- *   The database ID of the cross reference
- * @param accession
- *   The cross reference's accession
- * @param $version
- *   The version of the dbxref
- * @param $description
- *   The description of the cross reference
- *
- * @ingroup tripal_obo_loader
- */
-function tripal_cv_obo_add_dbxref($db_id, $accession, $version='', $description='') {
-
-  // check to see if the dbxref exists if not, add it
-  $values = array(
-    'db_id' => $db_id,
-    'accession' => $accession,
-  );
-  $result = chado_select_record('dbxref', array('dbxref_id'), $values);
-  if (count($result) == 0) {
-    $ins_values = array(
-      'db_id'       => $db_id,
-      'accession'   => $accession,
-      'version'     => $version,
-      'description' => $description,
-    );
-    $ins_options = array('return_record' => FALSE);
-    $result = chado_insert_record('dbxref', $ins_values, $ins_options);
-    if (!$result) {
-      tripal_cv_obo_quiterror("Failed to insert the dbxref record $accession");
-      return FALSE;
-    }
-    $result = chado_select_record('dbxref', array('dbxref_id'), $values);
-  }
-  return $result[0];
-}
-
-

+ 14 - 13
tripal_chado/includes/loaders/tripal_chado.pub_importers.inc

@@ -1058,11 +1058,11 @@ function tripal_pub_add_publication($pub_details, &$action, $do_contact = FALSE,
   // build the values array for inserting or updating
   $values = array(
     'title'       => $pub_details['Title'],
-    'volume'      => $pub_details['Volume'],
+    'volume'      => (isset($pub_details['Volume'])) ? $pub_details['Volume'] : '',
     'series_name' => $series_name,
-    'issue'       => $pub_details['Issue'],
-    'pyear'       => $pub_details['Year'],
-    'pages'       => $pub_details['Pages'],
+    'issue'       => (isset($pub_details['Issue'])) ? $pub_details['Issue'] : '',
+    'pyear'       => (isset($pub_details['Year'])) ? $pub_details['Year'] : '',
+    'pages'       => (isset($pub_details['Pages'])) ? $pub_details['Pages'] : '',
     'uniquename'  => $pub_details['Citation'],
     'type_id'     => $pub_type->cvterm_id,
   );
@@ -1207,15 +1207,16 @@ function tripal_pub_add_publication($pub_details, &$action, $do_contact = FALSE,
 function tripal_pub_add_authors($pub_id, $authors, $do_contact) {
   $rank = 0;
 
-  // first remove any of the existing pubauthor entires
+  // First remove any of the existing pubauthor entires.
   $sql = "DELETE FROM {pubauthor} WHERE pub_id = :pub_id";
   chado_query($sql, array(':pub_id' => $pub_id));
 
-  // iterate through the authors and add them to the pubauthors and contact
-  // tables of chado, then link them through the custom pubauthors_contact table
+  // Iterate through the authors and add them to the pubauthors and contact
+  // tables of chado, then link them through the custom pubauthors_contact
+  // table.
   foreach ($authors as $author) {
-    // skip invalid author entires
-    if ($author['valid'] == 'N') {
+    // Skip invalid author entires.
+    if (isset($author['valid']) AND $author['valid'] == 'N') {
       continue;
     }
     // remove the 'valid' property as we don't have a CV term for it
@@ -1224,16 +1225,16 @@ function tripal_pub_add_authors($pub_id, $authors, $do_contact) {
     // construct the contact.name field using the author information
     $name = '';
     $type = 'Person';
-    if ($author['Given Name']) {
+    if (isset($author['Given Name'])) {
       $name .= $author['Given Name'];
     }
-    if ($author['Surname']) {
+    if (isset($author['Surname'])) {
       $name .= ' ' . $author['Surname'];
     }
-    if ($author['Suffix']) {
+    if (isset($author['Suffix'])) {
       $name .= ' ' . $author['Suffix'];
     }
-    if ($author['Collective']) {
+    if (isset($author['Collective'])) {
       $name = $author['Collective'];
       $type = 'Collective';
     }

+ 6 - 0
tripal_chado/includes/setup/tripal_chado.setup.inc

@@ -156,6 +156,7 @@ function tripal_chado_prepare_chado() {
     $real_version = chado_get_version(TRUE);
 
     // Create custom tables depending on the Chado version installed.
+    drush_print("Creating Tripal Materialized Views and Custom Tables...");
     $chado_version = chado_get_version();
     if ($chado_version == '1.1') {
       tripal_chado_add_v1_1_custom_tables();
@@ -171,16 +172,21 @@ function tripal_chado_prepare_chado() {
     }
 
     // Import commonly used ontologies if needed.
+    drush_print("Loading Ontologies...");
     tripal_chado_load_ontologies();
 
     // Populate the semantic web associations for Chado tables/fields.
+    drush_print("Making semantic connections for Chado tables/fields...");
     tripal_chado_populate_chado_semweb_table();
 
     // Initialize the population of the chado_cvterm_mapping table.  This will
     // map existing data types already in Chado so that when users want to
     // add new content types it simplifies the form for them.
+    drush_print("Map Chado Controlled vocabularies to Tripal Terms...");
     tripal_chado_map_cvterms();
 
+    drush_print("Creating common Tripal Content Types...");
+
     // Create the 'Organism' entity type. This uses the obi:organism term.
     $error = '';
     $args = array(

+ 12 - 4
tripal_chado/includes/tripal_chado.entity.inc

@@ -144,10 +144,18 @@ function tripal_chado_tripal_default_title_format($bundle, $available_tokens) {
   $table = $bundle->data_table;
 
   if ($table == 'organism') {
-    $format[] = array(
-      'format' => '[taxrank__genus] [taxrank__species]',
-      'weight' => -5
-    );
+    if (chado_get_version() <= '1.2') {
+      $format[] = array(
+        'format' => '[taxrank__genus] [taxrank__species]',
+        'weight' => -5
+      );
+    }
+    else {
+      $format[] = array(
+        'format' => '[taxrank__genus] [taxrank__species] [taxrank__infraspecies]',
+        'weight' => -5
+      );
+    }
   }
   if ($table == 'analysis') {
     $format[] = array(

+ 3 - 3
tripal_chado/includes/tripal_chado.field_storage.inc

@@ -555,8 +555,8 @@ function tripal_chado_field_storage_query($query) {
   // Now set any ordering.
   foreach ($query->order as $index => $sort) {
     // Add in property ordering.
-    if ($order['type'] == 'property') {
-
+    if ($sort['type'] == 'property') {
+       // TODO: support ordering by bundle properties.
     }
     // Add in filter ordering
     if ($sort['type'] == 'field') {
@@ -1246,7 +1246,7 @@ function tripal_chado_field_storage_bundle_mapping_form_submit($form,
 
   // If we have a type_column then we know this type uses a column in the
   // base table, so we can set the storage args and return.
-  if ($default['type_column']) {
+  if ($default['type_column'] and $default['type_column'] != 'none') {
     $storage_args['data_table'] = $default['table'];
     $storage_args['type_column'] = $default['type_column'];
     return;

+ 124 - 72
tripal_chado/includes/tripal_chado.fields.inc

@@ -20,6 +20,7 @@ function tripal_chado_bundle_fields_info($entity_type, $bundle) {
     'chado_table' => $chado_bundle->data_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
     'chado_type_column' => $chado_bundle->type_column,
+    'chado_type_value' => $chado_bundle->type_value,
   );
 
   $info = array();
@@ -44,8 +45,9 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
 
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_table'];
-  $type_field = $details['chado_type_column'];
+  $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
   // Iterate through the columns of the table and see if fields have been
   // created for each one. If not, then create them.
@@ -74,7 +76,7 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
     }
 
     // Don't create base fields for the primary key and the type_id field.
-    if ($column_name == $pkey or $column_name == $type_field) {
+    if ($column_name == $pkey or $column_name == $type_column) {
       continue;
     }
     $cvterm = tripal_get_chado_semweb_term($table_name, $column_name, array('return_object' => TRUE));
@@ -95,7 +97,7 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
     }
 
     // Skip the type field that will always be custom
-    if (($table_name == $type_table and $column_name == $type_field) or
+    if (($table_name == $type_table and $column_name == $type_column) or
          $column_name == 'type_id') {
       continue;
     }
@@ -179,8 +181,9 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
 function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type, $bundle) {
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_table'];
-  $type_field = $details['chado_type_column'];
+  $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
   $schema = chado_get_schema($table_name);
 
@@ -189,7 +192,7 @@ function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type,
   // by the type_id field or on the organism table where the type_id is mean
   // as an infraspecific name type.
   if (array_key_exists('type_id', $schema['fields']) and
-      $table_name != 'organism' and $type_field != 'type_id') {
+      $table_name != 'organism' and $type_column != 'type_id') {
     $field_name = 'schema__additional_type';
     $field_type = 'schema__additional_type';
     $info[$field_name] = array(
@@ -409,8 +412,9 @@ function tripal_chado_bundle_fields_info_linker(&$info, $details, $entity_type,
 
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_table'];
-  $type_field = $details['chado_type_column'];
+  $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
   // CONTACTS
   $contact_table = $table_name . '_contact';
@@ -537,7 +541,7 @@ function tripal_chado_bundle_fields_info_linker(&$info, $details, $entity_type,
       // The tripal_analysis_KEGG, tripal_analysis_blast, and
       // tripal_analysis_interpro modules store results in the analysisprop
       // table which is probably not the best place, but we don't want to
-      // create a ton of fields for this.
+      // create a ton of fields for this, so skip them.
       if ($prop_table == 'analysisprop' and
           ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
            $term->dbxref_id->db_id->name == 'tripal')) {
@@ -647,6 +651,7 @@ function tripal_chado_bundle_instances_info($entity_type, $bundle) {
     'chado_table' => $chado_bundle->data_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
     'chado_type_column' => $chado_bundle->type_column,
+    'chado_type_value' => $chado_bundle->type_value,
   );
 
   $info = array();
@@ -674,8 +679,9 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
   // Get Chado information
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_table'];
-  $type_field = $details['chado_type_column'];
+  $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
   // Iterate through the columns of the table and see if fields have been
   // created for each one. If not, then create them.
@@ -697,12 +703,12 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
     }
 
     // Don't create base fields for the primary key and the type_id field.
-    if ($column_name == $pkey or $column_name == $type_field) {
+    if ($column_name == $pkey or $column_name == $type_column) {
       continue;
     }
 
     // Skip the type field that will always be custom
-    if (($table_name == $type_table and $column_name == $type_field) or
+    if (($table_name == $type_table and $column_name == $type_column) or
         $column_name == 'type_id') {
       continue;
     }
@@ -723,7 +729,7 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
     }
 
     // Skip the type field.
-    if ($table_name == $type_table and $column_name == $type_field) {
+    if ($table_name == $type_table and $column_name == $type_column) {
       continue;
     }
 
@@ -890,8 +896,9 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
 function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle, $details) {
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_table'];
-  $type_field = $details['chado_type_column'];
+  $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
   $schema = chado_get_schema($table_name);
 
   // BASE TYPE_ID
@@ -899,7 +906,7 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
   // by the type_id field or on the organism table where the type_id is mean
   // as an infraspecific name type.
   if (array_key_exists('type_id', $schema['fields']) and
-      $table_name != 'organism' and $type_field != 'type_id') {
+      $table_name != 'organism' and $type_column != 'type_id') {
     $field_name = 'schema__additional_type';
     $is_required = FALSE;
     if (array_key_exists('not null', $schema['fields']['type_id']) and
@@ -1370,8 +1377,9 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
 
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_table'];
-  $type_field = $details['chado_type_column'];
+  $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
   // CONTACTS
   $contact_table = $table_name . '_contact';
@@ -1624,69 +1632,113 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
      $schema = chado_get_schema($prop_table);
      $tpkey = $tschema['primary key'][0];
      $pkey = $schema['primary key'][0];
-     // Get the list of existing property types for this table.
+
+     // Property tables can be a bit tricky because not all property types
+     // in the prop table are appropriate for each type of data.  Also som
+     // bundle types are resolved via a property.  So, we have to distinguish
+     // between these two cases.
+     $sql = '';
      $args = array();
-     $sql = 'SELECT DISTINCT type_id FROM {' . $prop_table . '}';
-
-//       $sql = "
-//         SELECT DISTINCT P.type_id
-//         FROM {" . $prop_table . "} P
-//       ";
-//       if (array_key_exists('type_id', $tschema['fields'])) {
-//         $sql .= "
-//             INNER JOIN {" . $table_name . "} T on T.$tpkey = P.$tpkey
-//           WHERE T.type_id = :cvterm_id
-//         ";
-//         $args[':cvterm_id'] = $cvterm_id;
-//       }
-
-     $props = chado_query($sql, $args);
-     while ($prop = $props->fetchObject()) {
-
-       $term = chado_generate_var('cvterm', array('cvterm_id' => $prop->type_id));
-
-       // The tripal_analysis_KEGG, tripal_analysis_blast, and
-       // tripal_analysis_interpro modules store results in the analysisprop
-       // table which is probably not the best place, but we don't want to
-       // create a ton of fields for this.
-       if ($prop_table == 'analysisprop' and
-           ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
-            $term->dbxref_id->db_id->name == 'tripal')) {
-         continue;
-       }
 
-       $field_name = strtolower(preg_replace('/[^\w]/','_', $term->dbxref_id->db_id->name . '__' . $term->name));
-       $field_name = substr($field_name, 0, 32);
-       $info[$field_name] = array(
-         'field_name' => $field_name,
-         'entity_type' => $entity_type,
-         'bundle' => $bundle->name,
-         'label' => ucwords(preg_replace('/_/', ' ', $term->name)),
-         'description' => $term->definition,
-         'required' => FALSE,
-         'settings' => array(
-           'auto_attach' => TRUE,
-           'term_vocabulary' => $term->dbxref_id->db_id->name,
-           'term_accession' => $term->dbxref_id->accession,
-           'term_name' => $term->name,
-           'base_table' => $table_name,
-           'chado_table' => $prop_table,
-           'chado_column' => $pkey,
-         ),
-         'widget' => array(
-           'type' => 'chado_linker__prop_widget',
+     // First, is this the case where all of the records in the table are
+     // of this type?  If so, then all properties apply
+     if (!$type_column) {
+        $sql = 'SELECT DISTINCT type_id FROM {' . $prop_table . '}';
+        $props = chado_query($sql, $args);
+     }
+     // Second, if this is the case where a content type is uniquely identified
+     // by a type_id value in the base table, then only properties associated
+     // with that type ID should be used.
+     else if ($type_column and !$type_table) {
+      $sql = "
+        SELECT DISTINCT P.type_id
+        FROM {" . $prop_table . "} P
+          INNER JOIN {" . $table_name . "} T on T.$tpkey = P.$tpkey
+        WHERE T.$type_column = :cvterm_id
+      ";
+      $args[':cvterm_id'] = $cvterm_id;
+      $props = chado_query($sql, $args);
+     }
+     // Third, if this is the case where a content type is uniquely identified
+     // via a term/value pair in the prop table.
+     else if ($type_column and $type_table == $prop_table and !empty($type_value)) {
+       $sql = "
+        SELECT DISTINCT P2.type_id
+        FROM {" . $prop_table . "} P1
+          INNER JOIN {" . $table_name . "} T on T.$tpkey = P1.$tpkey
+          INNER JOIN {" . $prop_table . "} P2 on T.$tpkey = P2.$tpkey
+        WHERE P1.$type_column = :cvterm_id AND P1.value = :prop_value AND
+          P2.type_id != P1.type_id
+       ";
+       $args[':cvterm_id'] = $cvterm_id;
+       $args[':prop_value'] = $type_value;
+       $props = chado_query($sql, $args);
+     }
+     // Fourth, if this is the case where the content type is uinquely identifed
+     // via another table (e.g. cvterm linking table) and not this prop table.
+     else if ($type_column and $type_table != $prop_table and empty($type_value)) {
+       $sql = "
+         SELECT DISTINCT P.type_id
+         FROM {" . $prop_table . "} P
+           INNER JOIN {" . $table_name . "} T on T.$tpkey = P.$tpkey
+           INNER JOIN {" . $type_table . "} TT on TT.$tpkey = T.$tpkey
+         WHERE TT.$type_column = :cvterm_id
+       ";
+       $args[':cvterm_id'] = $cvterm_id;
+       $props = chado_query($sql, $args);
+     }
+     else {
+       // Do nothing;
+     }
+
+     if ($props) {
+       while ($prop = $props->fetchObject()) {
+
+         $term = chado_generate_var('cvterm', array('cvterm_id' => $prop->type_id));
+
+         // The tripal_analysis_KEGG, tripal_analysis_blast, and
+         // tripal_analysis_interpro modules store results in the analysisprop
+         // table which is probably not the best place, but we don't want to
+         // create a ton of fields for this.
+         if ($prop_table == 'analysisprop' and
+             ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
+              $term->dbxref_id->db_id->name == 'tripal')) {
+           continue;
+         }
+
+         $field_name = strtolower(preg_replace('/[^\w]/','_', $term->dbxref_id->db_id->name . '__' . $term->name));
+         $field_name = substr($field_name, 0, 32);
+         $info[$field_name] = array(
+           'field_name' => $field_name,
+           'entity_type' => $entity_type,
+           'bundle' => $bundle->name,
+           'label' => ucwords(preg_replace('/_/', ' ', $term->name)),
+           'description' => $term->definition,
+           'required' => FALSE,
            'settings' => array(
-             'display_label' => 1,
+             'auto_attach' => TRUE,
+             'term_vocabulary' => $term->dbxref_id->db_id->name,
+             'term_accession' => $term->dbxref_id->accession,
+             'term_name' => $term->name,
+             'base_table' => $table_name,
+             'chado_table' => $prop_table,
+             'chado_column' => $pkey,
            ),
-         ),
-         'display' => array(
-           'default' => array(
-             'label' => 'hidden',
-             'type' => 'chado_linker__prop_formatter',
-             'settings' => array(),
+           'widget' => array(
+             'type' => 'chado_linker__prop_widget',
+             'settings' => array(
+               'display_label' => 1,
+             ),
            ),
-         ),
-       );
+           'display' => array(
+             'default' => array(
+               'label' => 'hidden',
+               'type' => 'chado_linker__prop_formatter',
+               'settings' => array(),
+             ),
+           ),
+         );
+       }
      }
    }
 

+ 7 - 7
tripal_chado/includes/tripal_chado.migrate.inc

@@ -131,7 +131,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
            "SELECT count(*)
             FROM {organism} O
-            INNER JOIN public.chado_organism CO ON O.organism_id = CO.organism_id
+            INNER JOIN {chado_organism} CO ON O.organism_id = CO.organism_id
           ";
         $org_count = chado_query($sql)->fetchField();
         if ($org_count > 0) {
@@ -147,7 +147,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
         "SELECT count(*)
           FROM {analysis} A
-          INNER JOIN public.chado_analysis CA ON A.analysis_id = CA.analysis_id
+          INNER JOIN {chado_analysis} CA ON A.analysis_id = CA.analysis_id
          ";
         $ana_count = chado_query($sql)->fetchField();
         if ($ana_count > 0) {
@@ -163,7 +163,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
           "SELECT count(*)
            FROM {project} P
-           INNER JOIN public.chado_project CP ON P.project_id = CP.project_id
+           INNER JOIN {chado_project} CP ON P.project_id = CP.project_id
           ";
         $proj_count = chado_query($sql)->fetchField();
         if ($proj_count > 0) {
@@ -179,7 +179,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
           "SELECT count(*)
             FROM {featuremap} M
-            INNER JOIN public.chado_featuremap CM ON M.featuremap_id = CM.featuremap_id
+            INNER JOIN {chado_featuremap} CM ON M.featuremap_id = CM.featuremap_id
           ";
         $map_count = chado_query($sql)->fetchField();
         if ($map_count > 0) {
@@ -195,7 +195,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
           "SELECT count(*)
            FROM {pub} P
-           INNER JOIN public.chado_pub CP ON P.pub_id = CP.pub_id
+           INNER JOIN {chado_pub} CP ON P.pub_id = CP.pub_id
          ";
         $proj_count = chado_query($sql)->fetchField();
         if ($proj_count > 0) {
@@ -212,7 +212,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
             "SELECT V.name AS type, X.accession, db.name AS vocabulary , count(*) AS num
               FROM {" . $table . "} T
-              INNER JOIN public.$tv2_content_type CT ON T.$pkey = CT.$pkey
+              INNER JOIN {" . $tv2_content_type CT . "} ON T.$pkey = CT.$pkey
               INNER JOIN {cvterm} V ON V.cvterm_id = T.type_id
               INNER JOIN {dbxref} X ON X.dbxref_id = V.dbxref_id
               INNER JOIN {db} ON db.db_id = X.db_id
@@ -671,7 +671,7 @@ function tripal_chado_migrate_map_types($tv2_content_types) {
       $sql = "
         SELECT V.name AS type, X.accession, db.name AS vocabulary
         FROM {" . $table . "} T
-          INNER JOIN public.$tv2_content_type CT ON T.$pkey = CT.$pkey
+          INNER JOIN {" . $tv2_content_type . "} CT ON T.$pkey = CT.$pkey
           INNER JOIN {cvterm} V ON V.cvterm_id = T.type_id
           INNER JOIN {dbxref} X ON X.dbxref_id = V.dbxref_id
           INNER JOIN {db} ON db.db_id = X.db_id

+ 4 - 0
tripal_chado/includes/tripal_chado.semweb.inc

@@ -161,6 +161,7 @@ function tripal_chado_populate_vocab_SCHEMA() {
     'definition' => 'The name of the item.',
   ));
   tripal_associate_chado_semweb_term(NULL, 'name', $term);
+  tripal_associate_chado_semweb_term('analysis', 'sourcename', $term);
 
   $term = tripal_insert_cvterm(array(
     'id' => 'schema:alternateName',
@@ -376,6 +377,7 @@ function tripal_chado_populate_vocab_EDAM() {
     'definition' => 'A persistent (stable) and unique identifier, typically identifying an object (entry) from a database.',
   ));
   tripal_associate_chado_semweb_term(NULL, 'dbxref_id', $term);
+  tripal_associate_chado_semweb_term('dbxref', 'accession', $term);
 
   $term = tripal_insert_cvterm(array(
     'id' => 'data:2044',
@@ -597,6 +599,8 @@ function tripal_chado_populate_vocab_IAO() {
     'having the same name.',
   ));
   tripal_associate_chado_semweb_term('analysis', 'programversion', $term);
+  tripal_associate_chado_semweb_term('analysis', 'sourceversion', $term);
+  tripal_associate_chado_semweb_term(NULL, 'version', $term);
 
   $term = tripal_insert_cvterm(array(
     'id' => 'IAO:0000064',

+ 4 - 0
tripal_chado/theme/css/tripal_chado.css

@@ -57,4 +57,8 @@
 
 #autocomplete {
   width: 600px !important;
+}
+
+.properties-field-list {
+  margin: 0px;
 }

+ 43 - 0
tripal_chado/tripal_chado.install

@@ -1029,3 +1029,46 @@ function tripal_chado_update_7307() {
     throw new DrupalUpdateException('Could not perform update: '. $error);
   }
 }
+
+/**
+ * Add cvterm mapping for the analysis.sourcversion and analysis.sourcename.
+ */
+function tripal_chado_update_7308() {
+  try {
+    $term = tripal_insert_cvterm(array(
+      'id' => 'IAO:0000129',
+      'name' => 'version number',
+      'cv_name' => 'IAO',
+      'definition' => 'A version number is an ' .
+      'information content entity which is a sequence of characters ' .
+      'borne by part of each of a class of manufactured products or its ' .
+      'packaging and indicates its order within a set of other products ' .
+      'having the same name.',
+    ));
+    tripal_associate_chado_semweb_term('analysis', 'sourceversion', $term);
+    tripal_associate_chado_semweb_term(NULL, 'version', $term);
+
+    $term = tripal_insert_cvterm(array(
+      'id' => 'schema:name',
+      'name' => 'name',
+      'cv_name' => 'schema',
+      'definition' => 'The name of the item.',
+    ));
+    tripal_associate_chado_semweb_term('analysis', 'sourcename', $term);
+
+    $term = tripal_insert_cvterm(array(
+      'id' => 'data:2091',
+      'name' => 'Accession',
+      'cv_name' => 'EDAM',
+      'definition' => 'A persistent (stable) and unique identifier, typically identifying an object (entry) from a database.',
+    ));
+    tripal_associate_chado_semweb_term('dbxref', 'accession', $term);
+
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+
+

+ 14 - 1
tripal_daemon/TripalDaemon.inc

@@ -40,7 +40,7 @@ class TripalDaemon extends DrushDaemon {
 
   // Maximum number of jobs that can be run in parallel.
   // @todo: Implement actually changing this setting (see above note).
-  protected $max_num_jobs = 2;
+  protected $max_num_jobs = 3;
 
   /**
    * Implements DaemonAPIDaemon::executeTask() function.
@@ -301,4 +301,17 @@ class TripalDaemon extends DrushDaemon {
 
     return $status_details;
   }
+
+  /**
+   * Set whether we should run parallel jobs or not.
+   *
+   * @param $do_parallel
+   *   Boolean indicating whether to allow parallel processing of jobs.
+   * @param $max_num_jobs
+   *   Integer indicating the maximum number of jobs to run at once.
+   */
+  public function setParallel($do_parallel, $max_num_jobs) {
+    $this->do_parallel = $do_parallel;
+    $this->max_num_jobs = $max_num_jobs;
+  }
 }

+ 55 - 2
tripal_daemon/tripal_daemon.drush.inc

@@ -29,6 +29,8 @@ function tripal_daemon_drush_command() {
       'show-log' => 'Show the log file.',
     ),
     'options' => array(
+      'parallel' => dt('Normally jobs are executed one at a time. But if you are certain no conflicts will occur with other currently running jobs you may set this argument to a value of 1 to make the job run in parallel with other running jobs.'),
+      'max_jobs' => dt('Indicate the maximum number of concurrent jobs. Default is 3; use -1 (unlimited). Ignore if not running parallel jobs'),
       'num_lines' => 'The number of lines of the log file to show.',
       'child' => array(
         'hidden' => TRUE,
@@ -40,7 +42,7 @@ function tripal_daemon_drush_command() {
     'examples' => array(
       'drush trpjob-daemon start' => 'Start the daemon.',
       'drush trpjob-daemon status' => 'Show the current status of the daemon.',
-      'drush trpjob-daemon stop'              => 'Stop the daemon.',
+      'drush trpjob-daemon stop' => 'Stop the daemon.',
       'drush trpjob-daemon show-log' => 'Show the last 10 lines of the log file.',
       'drush trpjob-daemon show-log --num_lines=50' => 'Show the last 10 lines of the log file.',
     ),
@@ -61,5 +63,56 @@ function tripal_daemon_drush_command() {
  *   you want the daemon to do.
  */
 function drush_tripal_daemon_tripal_jobs_daemon($action) {
-  drush_drushd_daemon($action, 'tripal_daemon');
+
+  $parallel = drush_get_option('parallel', FALSE);
+  $max_jobs = drush_get_option('max_jobs', 3);
+
+  // Check if we have the right version of Drush Daemon to support passing
+  // options to the daemon class.
+  $have_support = FALSE;
+  if (function_exists('drushd_instantiate_daemon')) {
+    $have_support = TRUE;
+  }
+
+  // We need to handle start ourselves in order to handle parallel processing
+  if ($parallel AND ($action == 'start')) {
+
+    // Check if we have the right version of Drush Daemon to support passing
+    // options to the daemon class.
+    if (function_exists('drushd_instantiate_daemon')) {
+      // First, instantiate the daemon.
+      $daemon = drushd_instantiate_daemon('tripal_daemon');
+
+      // We always start our daemons in daemon-mode. Thus when the daemon is first
+      // started from drush, we need to fork the process. However, we don't want
+      // our children to fork continuously or we will end up with a fork_bomb.
+      // Thus when we start our child process we pass in the "child" option which
+      // tells our drush command not to fork again but instead to just run
+      // the daemon.
+      if (!drush_get_option('child')) {
+        drush_invoke_process(
+          '@self',
+          'tripal-jobs-daemon',
+          array('start'),
+          array('child' => TRUE, 'parallel' => $parallel, 'max_jobs' => $max_jobs),
+          array('fork' => TRUE)
+        );
+        drush_print(dt("Use 'drush tripal-jobs-daemon status' to check the "
+          . "status of the daemon just started and 'drush tripal-jobs-daemon stop' to stop it.\n"));
+      }
+      else {
+        $daemon->setParallel($parallel, $max_jobs);
+        $daemon->run();
+      }
+    }
+    else {
+      drush_set_error(dt('Error: You need version 2.3 of Drush Daemon to run
+        parallel jobs. Either update your version of Drush Daemon or start the
+        Tripal Daemon without the parallel option.'));
+    }
+  }
+  // Otherwise just let Drush daemon handle it ;-).
+  else {
+    drush_drushd_daemon($action, 'tripal_daemon');
+  }
 }

+ 34 - 10
tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc

@@ -75,7 +75,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
     // If we couldn't match this field argument to a field and entity then return
     if (!$entity) {
-      throw new Exception("Canno find this entity.");
+      throw new Exception("Cannot find this entity.");
     }
 
     list($field, $instance, $term) = $this->findField($bundle, $expfield);
@@ -485,22 +485,46 @@ class TripalEntityService_v0_1 extends TripalWebService {
         $op = $matches[2];
       }
 
-      // Break apart any subkeys and pull the first one out for the term name key.
+      // Break apart any subkeys and pull the first one as this is the parent
+      // field.
       $subkeys = explode(',', $key);
       if (count($subkeys) > 0) {
-        $key = array_shift($subkeys);
+        $key = $subkeys[0];
       }
-      $column_name = $key;
 
       // Map the values in the filters to their appropriate field names.
       if (array_key_exists($key, $field_mapping)) {
-        $field_name = $field_mapping[$key];
-        if (count($subkeys) > 0) {
-          $column_name .= '.' . implode('.', $subkeys);
+        $key_field_name = $field_mapping[$key];
+        $key_field = field_info_field($key_field_name);
+        $key_instance = field_info_instance('TripalEntity', $key_field_name, $bundle->name);
+
+        // Complex fields provied by the TripalField class may have sub
+        // elements that support filtering.  We need to see if the user
+        // wants to filter on those.
+        if (tripal_load_include_field_class($key_field_name)) {
+          // To find out which fields are searchable we'll call the wsData()
+          // function.
+          $key_field = new $key_field_name($key_field, $key_instance);
+          $searchable_keys = $key_field->webServicesData();
+          $criteria = implode('.', $subkeys);
+          if (array_key_exists($criteria, $searchable_keys)) {
+            $new_params[$key_field_name]['value'] = $value;
+            $new_params[$key_field_name]['op'] = $op;
+            $new_params[$key_field_name]['column'] = $searchable_keys[$criteria];
+          }
+          else {
+            throw new Exception("The filter term, '$criteria', is not available for use.");
+          }
+        }
+        // If this field is not a TripalField then it should just have
+        // a simple value and we can query for that.
+        else {
+          $key_field_id = $key_instance['settings']['term_vocabulary'] . ':' . $key_instance['settings']['term_accession'];
+
+          $new_params[$key_field_name]['value'] = $value;
+          $new_params[$key_field_name]['op'] = $op;
+          $new_params[$key_field_name]['column'] = $key_field_id;
         }
-        $new_params[$field_name]['value'] = $value;
-        $new_params[$field_name]['op'] = $op;
-        $new_params[$field_name]['column'] = $column_name;
       }
       else {
         throw new Exception("The filter term, '$key', is not available for use.");