Browse Source

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

Shawna 8 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_id'    => 'properties',
           '#tripal_toc_title' => '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(
         $node->content['tripal_contact_relationships'] = array(
           '#theme' => 'tripal_contact_relationships',
           '#theme' => 'tripal_contact_relationships',
           '#node' => $node,
           '#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.
       // yet been synced.
       $query = "SELECT " . implode(', ', $select) . ' ' .
       $query = "SELECT " . implode(', ', $select) . ' ' .
                'FROM {' . $base_table . '} ' . $base_table . ' ' . implode(' ', $joins) . ' '.
                '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";
                "WHERE CT.$base_table_id IS NULL";
 
 
       // extend the where clause if needed
       // extend the where clause if needed
@@ -862,7 +862,7 @@ function chado_node_sync_records($base_table, $max_sync = FALSE,
   $query = "
   $query = "
     SELECT " . implode(', ', $select) . ' ' .
     SELECT " . implode(', ', $select) . ' ' .
     'FROM {' . $base_table . '} ' . $base_table . ' ' . implode(' ', $joins) . ' '.
     '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 ";
     "WHERE CT.$base_table_id IS NULL ";
 
 
   // extend the where clause if needed
   // 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 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
       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
       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.
       '<p>' . t('The list below shows all possible content panes that can appear.
       However, those without content are automatically hidden and do not
       However, those without content are automatically hidden and do not
       appear in the TOC.' . '</p>'),
       appear in the TOC.' . '</p>'),
@@ -653,7 +653,8 @@ function tripal_core_content_type_toc_form($form, &$form_state, $content_type) {
 
 
   $form["#tree"] = TRUE;
   $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";
   $sql = "SELECT nid FROM {node} WHERE type = :type LIMIT 1 OFFSET 0";
   $nid = db_query($sql, array(':type' => $content_type))->fetchField();
   $nid = db_query($sql, array(':type' => $content_type))->fetchField();
   if (!$nid) {
   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
 <?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,
  * 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,
  * 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).
  * or they can come from sub children (e.g. CDS coding sequences).
- *   
+ *
  * This template will show all types depending on the data available.
  * This template will show all types depending on the data available.
  *
  *
  */
  */
@@ -12,7 +12,7 @@
 $feature = $variables['node']->feature;
 $feature = $variables['node']->feature;
 
 
 // number of bases per line in FASTA format
 // 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
 // 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
 // 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) {
    strcmp($feature->type_id->name,'pseudomolecule') !=0) {
   $feature = chado_expand_var($feature,'field','feature.residues');
   $feature = chado_expand_var($feature,'field','feature.residues');
   $residues = $feature->residues;
   $residues = $feature->residues;
-} 
+}
 
 
 // get the sequence derived from alignments
 // get the sequence derived from alignments
 $feature = $variables['node']->feature;
 $feature = $variables['node']->feature;
 $featureloc_sequences = $feature->featureloc_sequences;
 $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
   $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
   $list_items = array(); // a list to be used for theming of content on this page
-  
+
   // ADD IN RESIDUES FOR THIS FEATURE
   // ADD IN RESIDUES FOR THIS FEATURE
   // add in the residues if they are present
   // add in the residues if they are present
   if ($residues) {
   if ($residues) {
     $list_items[] = '<a href="#residues">' . $feature->type_id->name . ' sequence</a>';
     $list_items[] = '<a href="#residues">' . $feature->type_id->name . ' sequence</a>';
-     
+
     // format the sequence to break every 50 residues
     // format the sequence to break every 50 residues
     $sequences_html .= '<a name="residues"></a>';
     $sequences_html .= '<a name="residues"></a>';
     $sequences_html .= '<div id="residues" class="tripal_feature-sequence-item">';
     $sequences_html .= '<div id="residues" class="tripal_feature-sequence-item">';
     $sequences_html .= '<p><b>' . $feature->type_id->name . ' sequence</b></p>';
     $sequences_html .= '<p><b>' . $feature->type_id->name . ' sequence</b></p>';
     $sequences_html .= '<pre class="tripal_feature-sequence">';
     $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 .= wordwrap($feature->residues, $num_bases, "<br>", TRUE);
     $sequences_html .= '</pre>';
     $sequences_html .= '</pre>';
     $sequences_html .= '<a href="#sequences-top">back to top</a>';
     $sequences_html .= '<a href="#sequences-top">back to top</a>';
     $sequences_html .= '</div>';
     $sequences_html .= '</div>';
-    
+
   }
   }
-  
+
   // ADD IN RELATIONSHIP SEQUENCES (e.g. proteins)
   // 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
   // template for how the 'all_relationships' is provided. It is this
   // variable that we use to get the proteins.
   // variable that we use to get the proteins.
   $all_relationships = $feature->all_relationships;
   $all_relationships = $feature->all_relationships;
@@ -64,27 +64,27 @@ if ($residues or count($featureloc_sequences) > 0) {
   foreach ($object_rels as $rel_type => $rels){
   foreach ($object_rels as $rel_type => $rels){
     foreach ($rels as $subject_type => $subjects){
     foreach ($rels as $subject_type => $subjects){
       foreach ($subjects as $subject){
       foreach ($subjects as $subject){
-        
+
         // add in protein sequence if it has residues
         // add in protein sequence if it has residues
         if ($rel_type == 'derives from' and $subject_type == 'polypeptide') {
         if ($rel_type == 'derives from' and $subject_type == 'polypeptide') {
           $protein = $subject->record->subject_id;
           $protein = $subject->record->subject_id;
           $protein = chado_expand_var($protein, 'field', 'feature.residues');
           $protein = chado_expand_var($protein, 'field', 'feature.residues');
-          
+
           if ($protein->residues) {
           if ($protein->residues) {
             $list_items[] = '<a href="#residues">protein sequence</a>';
             $list_items[] = '<a href="#residues">protein sequence</a>';
             $sequences_html .= '<a name="protein-' . $protein->feature_id . '"></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 .= '<div id="protein-' . $protein->feature_id . '" class="tripal_feature-sequence-item">';
             $sequences_html .= '<p><b>protein sequence of ' . $protein->name . '</b></p>';
             $sequences_html .= '<p><b>protein sequence of ' . $protein->name . '</b></p>';
             $sequences_html .= '<pre class="tripal_feature-sequence">';
             $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 .= wordwrap($protein->residues, $num_bases, "<br>", TRUE);
             $sequences_html .= '</pre>';
             $sequences_html .= '</pre>';
             $sequences_html .= '<a href="#sequences-top">back to top</a>';
             $sequences_html .= '<a href="#sequences-top">back to top</a>';
             $sequences_html .= '</div>';
             $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
         // should be returned in order of rank
         if ($rel_type == 'part of' and $subject_type == 'CDS') {
         if ($rel_type == 'part of' and $subject_type == 'CDS') {
           $cds = $subject->record->subject_id;
           $cds = $subject->record->subject_id;
@@ -94,14 +94,14 @@ if ($residues or count($featureloc_sequences) > 0) {
             $coding_seq .= $cds->residues;
             $coding_seq .= $cds->residues;
           }
           }
         }
         }
-        
+
         // add any other sequences that are related through a relationship
         // add any other sequences that are related through a relationship
         // and that have values in the 'residues' column
         // and that have values in the 'residues' column
-        
+
       }
       }
     }
     }
   }
   }
-  
+
   // CODING SEQUENCES FROM RELATIONSHIPS
   // CODING SEQUENCES FROM RELATIONSHIPS
   // add in any CDS sequences.
   // add in any CDS sequences.
   if ($has_coding_seq) {
   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 .= '<a href="#sequences-top">back to top</a>';
     $sequences_html .= '</div>';
     $sequences_html .= '</div>';
   }
   }
-  
+
   /* ADD IN ALIGNMENT SEQUENCES FOR THIS FEATURE
   /* ADD IN ALIGNMENT SEQUENCES FOR THIS FEATURE
    * For retreiving the sequence from an alignment we would typically make a call to
    * For retreiving the sequence from an alignment we would typically make a call to
    * chado_expand_var function.  For example, to retrieve all
    * 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
    * 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
    * 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
    * been done by the tripal_feature module and the sequences are made available in
-   * the variable: 
+   * the variable:
    *
    *
    *   $feature->featureloc_sequences
    *   $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 .= '<a href="#sequences-top">back to top</a>';
       $sequences_html .= '</div>';
       $sequences_html .= '</div>';
     }
     }
-    
+
     // check to see if this alignment has any CDS. If so, generate a CDS sequence
     // check to see if this alignment has any CDS. If so, generate a CDS sequence
     $cds_sequence = tripal_get_feature_sequences(
     $cds_sequence = tripal_get_feature_sequences(
         array(
         array(
@@ -183,14 +183,14 @@ if ($residues or count($featureloc_sequences) > 0) {
         $sequences_html .= '</div>';
         $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
   <?php
-  
+
   // first add a list at the top of the page that can be formatted as the
   // 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
   // the list rather than hard-code the HTML here.  Instructions for how
   // to create the list can be found here:
   // to create the list can be found here:
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_item_list/7
   // 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>
           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
       <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
           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>
           will appear on the mRNA page.</li>
       <li>This feature is aligned to another feature (e.g. scaffold, or chromosome) and this feature has
       <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
           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>
     </ul>
     </p>';
     </p>';
   print tripal_set_message($message, TRIPAL_INFO, array('return_html' => 1));
   print tripal_set_message($message, TRIPAL_INFO, array('return_html' => 1));
-  
+
   // now print the sequences
   // now print the sequences
   print $sequences_html;
   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),
     'sub_feature_types' => explode(',', $child),
     'relationship_type' => $relationship,
     'relationship_type' => $relationship,
     'relationship_part' => $rel_part,
     'relationship_part' => $rel_part,
-    'print' => TRUE,
     'width' => $width
     '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(
   $items['find/sequences'] = array(
     'title' => 'Sequence Retrieval',
     'title' => 'Sequence Retrieval',
     'description' => 'Download a file of sequences',
     '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'),
     '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,
     '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'),
     '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,
     'type' => MENU_CALLBACK,
   );
   );
 
 
@@ -322,7 +326,7 @@ function tripal_feature_theme($existing, $type, $theme, $path) {
 
 
   // Themed Forms
   // Themed Forms
   $items['tripal_feature_seq_extract_form'] = array(
   $items['tripal_feature_seq_extract_form'] = array(
-     'arguments' => array('form'),
+     'render element' => 'form',
   );
   );
 
 
   // D3 Charts.
   // D3 Charts.
@@ -472,7 +476,7 @@ function tripal_feature_match_features_page($id) {
       INNER JOIN {cvterm} CVT on CVT.cvterm_id = F.type_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 {feature_synonym} FS on FS.feature_id = F.feature_id
       LEFT JOIN {synonym} S on S.synonym_id = FS.synonym_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
     WHERE
       F.uniquename = :uname or
       F.uniquename = :uname or
       F.name = :fname or
       F.name = :fname or
@@ -529,12 +533,6 @@ function tripal_feature_match_features_page($id) {
  * @ingroup tripal_feature
  * @ingroup tripal_feature
  */
  */
 function tripal_feature_form_alter(&$form, &$form_state, $form_id) {
 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
   // turn off preview button for insert/updates
   if ($form_id == "chado_feature_node_form") {
   if ($form_id == "chado_feature_node_form") {
     $form['actions']['preview']['#access'] = FALSE;
     $form['actions']['preview']['#access'] = FALSE;

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

@@ -1,65 +1,65 @@
 <?php
 <?php
 $featuremap = $variables['node']->featuremap;
 $featuremap = $variables['node']->featuremap;
 
 
-// expand featuremap to include pubs 
+// expand featuremap to include pubs
 $options = array('return_array' => 1);
 $options = array('return_array' => 1);
 $featuremap = chado_expand_var($featuremap, 'table', 'featuremap_pub', $options);
 $featuremap = chado_expand_var($featuremap, 'table', 'featuremap_pub', $options);
-$featuremap_pubs = $featuremap->featuremap_pub; 
+$featuremap_pubs = $featuremap->featuremap_pub;
 
 
 
 
 if (count($featuremap_pubs) > 0) { ?>
 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.
   // the $headers array is an array of fields to use as the colum headers.
   // additional documentation can be found here
   // additional documentation can be found here
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
   $headers = array('Year', 'Publication');
   $headers = array('Year', 'Publication');
-  
+
   // the $rows array contains an array of rows where each row is an array
   // 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
   // of values for each column of the table in that row.  Additional documentation
   // can be found here:
   // can be found here:
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
   $rows = array();
   $rows = array();
-  
+
   foreach ($featuremap_pubs as $featuremap_pub) {
   foreach ($featuremap_pubs as $featuremap_pub) {
     $pub = $featuremap_pub->pub_id;
     $pub = $featuremap_pub->pub_id;
     $pub = chado_expand_var($pub, 'field', 'pub.title');
     $pub = chado_expand_var($pub, 'field', 'pub.title');
     $citation = $pub->title;  // use the title as the default citation
     $citation = $pub->title;  // use the title as the default citation
-    
+
     // get the citation for this pub if it exists
     // get the citation for this pub if it exists
     $values = array(
     $values = array(
-      'pub_id' => $pub->pub_id, 
+      'pub_id' => $pub->pub_id,
       'type_id' => array(
       'type_id' => array(
         'name' => 'Citation',
         'name' => 'Citation',
       ),
       ),
     );
     );
     $options = array('return_array' => 1);
     $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) {
     if (count($citation_prop) == 1) {
       $citation_prop = chado_expand_var($citation_prop, 'field', 'pubprop.value');
       $citation_prop = chado_expand_var($citation_prop, 'field', 'pubprop.value');
       $citation = $citation_prop[0]->value;
       $citation = $citation_prop[0]->value;
     }
     }
-    
+
     // if the publication is synced then link to it
     // if the publication is synced then link to it
     if (property_exists($pub, 'nid')) {
     if (property_exists($pub, 'nid')) {
       // replace the title with a link
       // replace the title with a link
       $link = l($pub->title, 'node/' . $pub->nid ,array('attributes' => array('target' => '_blank')));
       $link = l($pub->title, 'node/' . $pub->nid ,array('attributes' => array('target' => '_blank')));
       $patterns = array(
       $patterns = array(
-        '/(\()/', '/(\))/', 
+        '/(\()/', '/(\))/',
         '/(\])/', '/(\[)/',
         '/(\])/', '/(\[)/',
         '/(\{)/', '/(\})/',
         '/(\{)/', '/(\})/',
-        '/(\+)/', '/(\.)/', '/(\?)/', 
+        '/(\+)/', '/(\.)/', '/(\?)/',
       );
       );
       $fixed_title = preg_replace($patterns, "\\\\$1", $pub->title);
       $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(
     $rows[] = array(
       $pub->pyear,
       $pub->pyear,
       $citation,
       $citation,
     );
     );
   }
   }
-  
+
   // the $table array contains the headers and rows array as well as other
   // the $table array contains the headers and rows array as well as other
   // options for controlling the display of the table.  Additional
   // options for controlling the display of the table.  Additional
   // documentation can be found here:
   // documentation can be found here:
@@ -76,8 +76,8 @@ if (count($featuremap_pubs) > 0) { ?>
     'colgroups' => array(),
     'colgroups' => array(),
     'empty' => '',
     'empty' => '',
   );
   );
-  
+
   // once we have our table array structure defined, we call Drupal's theme_table()
   // once we have our table array structure defined, we call Drupal's theme_table()
   // function to generate the 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
   // ADDITIONAL DBXREFS FORM
   //---------------------------------------------
   //---------------------------------------------
   $details = array(
   $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
   // Adds the form elements to your current form
   chado_add_node_form_dbxrefs($form, $form_state, $details);
   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
     // to automatically generate a citation if a uniquename doesn't already exist
     $pub_arr = array();
     $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
     // get the properties from the form
     $properties = chado_retrieve_node_form_properties($node);
     $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
  * @ingroup tripal_pub
  */
  */
 function tripal_pub_search_form_validate($form, &$form_state) {
 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'];
   $from_year    = $form_state['values']['from_year'];
   $to_year      = $form_state['values']['to_year'];
   $to_year      = $form_state['values']['to_year'];
   $op           = $form_state['values']['op'];
   $op           = $form_state['values']['op'];
@@ -416,7 +416,7 @@ function tripal_pub_search_form_validate($form, &$form_state) {
  * @ingroup tripal_pub
  * @ingroup tripal_pub
  */
  */
 function tripal_pub_search_form_submit($form, &$form_state) {
 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'];
   $from_year    = $form_state['values']['from_year'];
   $to_year      = $form_state['values']['to_year'];
   $to_year      = $form_state['values']['to_year'];
   $op           = $form_state['values']['op'];
   $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
   // build the SQL based on the criteria provided by the user
   $select = "SELECT DISTINCT P.*, CP.nid ";
   $select = "SELECT DISTINCT P.*, CP.nid ";
   $from   = "FROM {pub} P
   $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
                INNER JOIN {cvterm} CVT on CVT.cvterm_id = P.type_id
             ";
             ";
   $where  = "WHERE (NOT P.title = 'null') "; // always exclude the dummy pub
   $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.
   // the $headers array is an array of fields to use as the colum headers.
   // additional documentation can be found here
   // additional documentation can be found here
   // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
   // 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
   // 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
   // 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
     FROM {stock} S
       INNER JOIN {organism} O on S.organism_id = O.organism_id
       INNER JOIN {organism} O on S.organism_id = O.organism_id
       INNER JOIN {cvterm} CVT on CVT.cvterm_id = S.type_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
     WHERE
       S.uniquename = :uname or S.name = :name
       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) {
   catch (Exception $e) {
     $transaction->rollback();
     $transaction->rollback();
-    $error = _drupal_decode_exception($e);
     drupal_set_message(t("Failed to create content type: %message.",
     drupal_set_message(t("Failed to create content type: %message.",
-        array('%message' => $error['!message'])), 'error');
+      array('%message' => $e->getMessage())), 'error');
     return FALSE;
     return FALSE;
   }
   }
 
 
@@ -561,6 +560,7 @@ function tripal_create_bundle_fields($bundle, $term) {
 
 
   // Iterate through all of the field instances and create them.
   // Iterate through all of the field instances and create them.
   foreach ($info as $field_name => $details) {
   foreach ($info as $field_name => $details) {
+
     // If the field is already attached to this bundle then skip it.
     // If the field is already attached to this bundle then skip it.
     $field = field_info_field($details['field_name']);
     $field = field_info_field($details['field_name']);
     if ($field and array_key_exists('bundles', $field) and
     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'])) {
         in_array($bundle->name, $field['bundles']['TripalEntity'])) {
       continue;
       continue;
     }
     }
+
     // Create the field instance.
     // Create the field instance.
     $instance = field_create_instance($details);
     $instance = field_create_instance($details);
     $added[] = $field_name;
     $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
  * @param $message
  *   The message to be displayed to the tripal administrators
  *   The message to be displayed to the tripal administrators
  * @param $importance
  * @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_CRITICAL: Critical conditions.
  *     - TRIPAL_ERROR: Error conditions.
  *     - TRIPAL_ERROR: Error conditions.
  *     - TRIPAL_WARNING: Warning conditions.
  *     - TRIPAL_WARNING: Warning conditions.
@@ -144,9 +145,9 @@ function tripal_report_error($type, $severity, $message, $variables = array(), $
  *     - TRIPAL_DEBUG: Debug-level messages.
  *     - TRIPAL_DEBUG: Debug-level messages.
  * @param $options
  * @param $options
  *   Any options to apply to the current message. Supported options include:
  *   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()) {
 function tripal_set_message($message, $importance = TRIPAL_INFO, $options = array()) {
   global $user;
   global $user;

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

@@ -218,6 +218,11 @@ function tripal_add_term($details) {
  *   cannot be found.
  *   cannot be found.
  */
  */
 function tripal_get_term_details($vocabulary, $accession) {
 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
   // 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
   // switch to the desired vocabulary type. For now, we'll just use the
   // first one in the list.
   // 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) {
 function tripal_admin_add_type_form($form, &$form_state) {
 
 
-
   $stores = module_invoke_all('vocab_storage_info');
   $stores = module_invoke_all('vocab_storage_info');
   if (!is_array($stores) or count($stores) == 0) {
   if (!is_array($stores) or count($stores) == 0) {
     tripal_set_message('A storage backend is not enabled for managing
     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'
       '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
   // 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.
   // 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',
         '#type' => 'item',
         '#markup' => '<i>' . t('There is no term that matches the entered text.') . '</i>'
         '#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.
     // 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;
   return $form;
 }
 }

+ 8 - 4
tripal/includes/TripalFieldQuery.inc

@@ -6,9 +6,14 @@
  */
  */
 class TripalFieldQuery extends EntityFieldQuery {
 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() {
   public function execute() {
     // Give a chance for other modules to alter the query.
     // Give a chance for other modules to alter the query.
     drupal_alter('entity_query', $this);
     drupal_alter('entity_query', $this);
@@ -73,7 +78,6 @@ class TripalFieldQuery extends EntityFieldQuery {
     }
     }
   }
   }
 
 
-
   /**
   /**
    * Determines the query callback to use for this entity query.
    * 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().
    *   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
     // Use the override from $this->executeCallback. It can be set either
     // while building the query, or using hook_entity_query_alter().
     // while building the query, or using hook_entity_query_alter().
     if (function_exists($this->executeCallback)) {
     if (function_exists($this->executeCallback)) {

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

@@ -15,8 +15,7 @@ class TripalField {
   public static $default_label = 'Tripal Field';
   public static $default_label = 'Tripal Field';
 
 
   // The default description for this 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
   // Provide a list of global settings. These can be accessed witihn the
   // globalSettingsForm.  When the globalSettingsForm is submitted then
   // globalSettingsForm.  When the globalSettingsForm is submitted then
@@ -91,6 +90,9 @@ class TripalField {
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
   protected $instance;
 
 
+  // The term array that provides all the details about the controlled
+  // vocabulary term that this field maps to.
+  protected $term;
 
 
   // --------------------------------------------------------------------------
   // --------------------------------------------------------------------------
   //                     CONSTRUCTOR
   //                     CONSTRUCTOR
@@ -105,11 +107,31 @@ class TripalField {
    *   An array containing the instance data as returned by field_instance_info().
    *   An array containing the instance data as returned by field_instance_info().
    */
    */
   public function __construct($field, $instance) {
   public function __construct($field, $instance) {
+    $vocabulary = $term = NULL;
     $this->field = $field;
     $this->field = $field;
     $this->instance = $instance;
     $this->instance = $instance;
 
 
     $class = get_called_class();
     $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) {
     if (!$instance) {
       tripal_set_message(t('Missing instance of field "%field"', array('%field' => $field['field_name'])), TRIPAL_ERROR);
       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'];
     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
   //                            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.
    * 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.
    * 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.
    * 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
    * @param $query
    *   A query object appropriate for the data storage backend. For example,
    *   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;
     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_boolean_operator.inc
 files[] = views_handlers/tripal_views_handler_filter_entity_string.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_filter_string_selectbox.inc
+files[] = views_handlers/tripal_views_handler_sort.inc
 files[] = views_handlers/tripal_views_handler_sort_entity_string.inc
 files[] = views_handlers/tripal_views_handler_sort_entity_string.inc
 
 
 files[] = tripal_views_query.inc
 files[] = tripal_views_query.inc

+ 51 - 20
tripal/tripal.views.inc

@@ -36,6 +36,53 @@ function tripal_views_data() {
   return $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.
  * Integreates the Tripal fields with Views.
  */
  */
@@ -50,6 +97,8 @@ function tripal_views_data_fields(&$data) {
       continue;
       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.
     // Call the hook_field_views_data() but only for the tripal module.
     // Otherwise the other modules will expect that this is an SQL-based
     // Otherwise the other modules will expect that this is an SQL-based
     // view.
     // view.
@@ -120,7 +169,7 @@ function tripal_views_data_tripal_entity(&$data) {
         'handler' => 'tripal_views_handler_filter',
         'handler' => 'tripal_views_handler_filter',
       ),
       ),
       'sort' => array(
       'sort' => array(
-        'handler' => 'views_handler_sort',
+        'handler' => 'tripal_views_handler_sort',
       ),
       ),
     );
     );
     $data[$table]['link'] = array(
     $data[$table]['link'] = array(
@@ -129,12 +178,6 @@ function tripal_views_data_tripal_entity(&$data) {
       'field' => array(
       'field' => array(
         'handler' => 'tripal_views_handler_field_entity_link',
         '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(
     $data[$table]['edit_link'] = array(
       'title' => t('Edit Link'),
       'title' => t('Edit Link'),
@@ -142,12 +185,6 @@ function tripal_views_data_tripal_entity(&$data) {
       'field' => array(
       'field' => array(
         'handler' => 'tripal_views_handler_field_entity_link_edit',
         '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(
     $data[$table]['delete_link'] = array(
       'title' => t('Delete Link'),
       'title' => t('Delete Link'),
@@ -155,12 +192,6 @@ function tripal_views_data_tripal_entity(&$data) {
       'field' => array(
       'field' => array(
         'handler' => 'tripal_views_handler_field_entity_link_delete',
         '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(
     $data[$table]['status'] = array(
       'title' => t('Published'),
       '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
         'use equal' => TRUE, // Use status = 1 instead of status <> 0 in WHERE statment
       ),
       ),
       'sort' => array(
       '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 {
 class tripal_views_query extends views_plugin_query {
 
 
-  // The EntityFieldQuery object.
+  /**
+   * The EntityFieldQuery object used to perform the query
+   */
   var $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;
   var $fields;
 
 
+  /**
+   * The filters that are to be included in the query.
+   */
   var $filters;
   var $filters;
 
 
+  /**
+   * The sort item that are to be included in the query.
+   */
+  var $order;
+
+
 
 
   /**
   /**
    * Ensure a table exists in the queue.
    * 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);
     parent::init($base_table, $base_field, $options);
     $this->fields = array();
     $this->fields = array();
     $this->where = array();
     $this->where = array();
+    $this->order = array();
 
 
     // Creqte the TripalFieldQuery object.
     // Creqte the TripalFieldQuery object.
     $this->query = new TripalFieldQuery();
     $this->query = new TripalFieldQuery();
+    $this->cquery = new TripalFieldQuery();
+    $this->cquery->count();
 
 
     // Convert the $base_table into the bundle table.  Because every
     // Convert the $base_table into the bundle table.  Because every
     // tripal site will have different bundle tables we have to do the
     // 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.
     // Make sure we only query on the entities for this bundle type.
     $this->query->entityCondition('entity_type', 'TripalEntity');
     $this->query->entityCondition('entity_type', 'TripalEntity');
     $this->query->entityCondition('bundle', $bundle->name);
     $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.
       // Handle the bundle properties separate from real fields.
       if ($field_name == 'entity_id' or $field_name == 'status') {
       if ($field_name == 'entity_id' or $field_name == 'status') {
         $this->query->propertyCondition($field_name, $value, $operator);
         $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 {
       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) {
   function build(&$view) {
     // Make the query distinct if the option was set.
     // 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.
     // Let the pager modify the query to add limits.
     $this->pager->query();
     $this->pager->query();
 
 
-    $cquery = clone $this->query;
     $view->build_info['query'] = $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) {
   function execute(&$view) {
     $query = $view->build_info['query'];
     $query = $view->build_info['query'];
-    $count_query = $view->build_info['count_query'];
+    $cquery = $view->build_info['count_query'];
 
 
     if ($query) {
     if ($query) {
       $start = microtime(TRUE);
       $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
           // really should create a new tripal_views_plugin_pager class
           // and call the corresponding function here, but due to time
           // and call the corresponding function here, but due to time
           // constraints this is the shortcut.
           // 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'])) {
           if (!empty($this->pager->options['offset'])) {
             $this->pager->total_items -= $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
         // Get the IDs
         $results = $query->execute();
         $results = $query->execute();
-
         $entity_ids = array_keys($results['TripalEntity']);
         $entity_ids = array_keys($results['TripalEntity']);
 
 
         $this->pager->post_execute($view->result);
         $this->pager->post_execute($view->result);
-
         if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
         if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
           $view->total_rows = $this->pager->get_total_items();
           $view->total_rows = $this->pager->get_total_items();
         }
         }
@@ -205,6 +268,13 @@ class tripal_views_query extends views_plugin_query {
         $field_ids = array();
         $field_ids = array();
         foreach ($this->fields as $details) {
         foreach ($this->fields as $details) {
           $field_name = $details['field_name'];
           $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);
           $field = field_info_field($field_name);
           if ($field) {
           if ($field) {
             $fields[$field_name] = $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);
         $entities = tripal_load_entity('TripalEntity', $entity_ids, FALSE, $field_ids);
         $i = 0;
         $i = 0;
         foreach ($entities as $entity_id => $entity) {
         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;
               $view->result[$i]->$field_name = $entity->status;
               continue;
               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)) {
             if (array_key_exists($field_name, $fields)) {
               $items = field_get_items('TripalEntity', $entity, $field_name);
               $items = field_get_items('TripalEntity', $entity, $field_name);
               $view->result[$i]->$field_name = $items;
               $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;
     $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.
    * 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.
    *   Optional name of the field where the value is stored.
    */
    */
   function get_value($values, $field = NULL) {
   function get_value($values, $field = NULL) {
-
     $field_name = $this->field_alias;
     $field_name = $this->field_alias;
 
 
     if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
     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);
       $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:
     // 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
 <?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.
  * 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'];
       $select_if_duplicate = $form_state['storage']['template_array'][$form_state['storage']['original_priority']]['select_if_duplicate'];
     }
     }
     else {
     else {
-      $select_if_duplicate = 1;
+      $select_if_duplicate = 0;
     }
     }
 
 
     // get default for the update if duplicate
     // 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) {
   if ($syncd_only) {
     $sql = "
     $sql = "
       SELECT *
       SELECT *
-      FROM public.chado_organism CO
+      FROM {chado_organism} CO
         INNER JOIN {organism} O ON O.organism_id = CO.organism_id
         INNER JOIN {organism} O ON O.organism_id = CO.organism_id
       ORDER BY O.genus, O.species
       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
   // Review
   //----------------------
   //----------------------
-  if ($pub_type == 'Review') {
+  else if ($pub_type == 'Review') {
     if (array_key_exists('Authors', $pub)) {
     if (array_key_exists('Authors', $pub)) {
       $citation = $pub['Authors'] . '. ';
       $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 ";
   $select = "SELECT T.$pkey_field as record_id ";
   $from = "
   $from = "
     FROM {" . $table . "} T
     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
   // For migration of Tripal v2 nodes to entities we want to include the
   // coresponding chado linker table.
   // coresponding chado linker table.
   if ($sync_node && db_table_exists('chado_' . $table)) {
   if ($sync_node && db_table_exists('chado_' . $table)) {
     $select = "SELECT T.$pkey_field as record_id, CT.nid ";
     $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 ";
   $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
  * @param $sql
  *   The sql statement to execute
  *   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
   // if Chado is local to the database then prefix the Chado table
   // names with 'chado'.
   // names with 'chado'.
   if ($is_local) {
   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('/\{(.*?)\}/', '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
     // 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)) {
     if (preg_match('/chado.featureloc/i', $sql) or preg_match('/chado.feature/i', $sql)) {
       $previous_db = chado_set_active('chado') ;
       $previous_db = chado_set_active('chado') ;
@@ -1648,6 +1659,16 @@ function chado_query($sql, $args = array()) {
       $results = db_query($sql, $args);
       $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
   // if Chado is not local to the Drupal database then we have to
   // switch to another database
   // switch to another database
   else {
   else {
@@ -1683,7 +1704,12 @@ function chado_query($sql, $args = array()) {
  */
  */
 function chado_pager_query($query, $args, $limit, $element, $count_query = '') {
 function chado_pager_query($query, $args, $limit, $element, $count_query = '') {
   // get the page and offset for the pager
   // 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;
   $offset = $limit * $page;
   $q = $_GET['q'];
   $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;
   $cvterm_id = array_key_exists('cvterm_id', $params) ? $params['cvterm_id'] : NULL;
   $vocabulary = array_key_exists('vocabulary', $params) ? $params['vocabulary'] : NULL;
   $vocabulary = array_key_exists('vocabulary', $params) ? $params['vocabulary'] : NULL;
   $accession = array_key_exists('accession', $params) ? $params['accession'] : NULL;
   $accession = array_key_exists('accession', $params) ? $params['accession'] : NULL;
+  $cvterm = NULL;
 
 
   if ($cvterm_id) {
   if ($cvterm_id) {
     $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $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);
     $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';
   public static $default_label = 'Chado Field';
 
 
   // The default description for this 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
   // A list of global settings. These can be accessed witihn the
   // globalSettingsForm.  When the globalSettingsForm is submitted then
   // 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()
    * @see TripalField::instanceSettingsForm()
    */
    */
@@ -109,25 +142,4 @@ class ChadoField extends TripalField {
     );
     );
     return $element;
     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;
   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()
    * @see TripalField::load()
@@ -70,6 +117,10 @@ class chado_linker__contact extends ChadoField {
     $field_table = $this->instance['settings']['chado_table'];
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
     $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.
     // Get the FK that links to the base record.
     $schema = chado_get_schema($field_table);
     $schema = chado_get_schema($field_table);
     $base_table = $entity->chado_table;
     $base_table = $entity->chado_table;
@@ -111,27 +162,12 @@ class chado_linker__contact extends ChadoField {
         $contact = $contact_linker->contact_id;
         $contact = $contact_linker->contact_id;
         $entity->{$field_name}['und'][$i] = array(
         $entity->{$field_name}['und'][$i] = array(
           'value' => 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 . '__' . $pkey => $contact_linker->$pkey,
           'chado-' . $field_table . '__' . $fkey_lcolumn => $contact_linker->$fkey_lcolumn->$fkey_lcolumn,
           'chado-' . $field_table . '__' . $fkey_lcolumn => $contact_linker->$fkey_lcolumn->$fkey_lcolumn,
           'chado-' . $field_table . '__' . 'contact_id' => $contact->contact_id
           'chado-' . $field_table . '__' . 'contact_id' => $contact->contact_id
@@ -155,9 +191,13 @@ class chado_linker__contact extends ChadoField {
     $alias = 'contact_linker';
     $alias = 'contact_linker';
     $operator = $condition['operator'];
     $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);
       $query->condition("C.name", $condition['value'], $operator);
     }
     }
     if ($condition['column'] == 'contact.type') {
     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;
   public static $no_ui = FALSE;
 
 
 
 
+
   /**
   /**
    *
    *
    * @see TripalField::load()
    * @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));
     $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.type_id", $cvterm->cvterm_id);
     $query->condition("$alias.value", $condition['value'], $operator);
     $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'];
     $field_name = $this->field['field_name'];
     $chado_table = $this->instance['settings']['chado_table'];
     $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
     // If more than one value has been found display all values in an unordered
     // list.
     // 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 {
     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.
     // to allow the site admin to change the term settings above.
     'term_fixed' => FALSE,
     '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.
   // The default widget for this field.
   public static $default_widget = 'data__accession_widget';
   public static $default_widget = 'data__accession_widget';
 
 
@@ -114,8 +92,10 @@ class data__accession extends ChadoField {
     $alias = 'dbx_linker';
     $alias = 'dbx_linker';
     $operator = $condition['operator'];
     $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') {
     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);
       $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';
   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()
    * @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.
   // The default formatter for this field.
   public static $default_formatter = 'data__sequence_formatter';
   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()
    * @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.
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
   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;
   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()
    * @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.
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
   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;
   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()
    * @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;
   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()
    * @see TripalField::load()
@@ -66,14 +111,15 @@ class local__source_data extends ChadoField {
     $field_table = $this->instance['settings']['chado_table'];
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
     $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(
     $entity->{$field_name}['und'][0] = array(
       'value' => 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__sourcename' => $analysis->sourcename,
       'chado-analysis__sourceversion' => $analysis->sourceversion,
       '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) {
       if (!$organism_id or $organism_id == 0) {
         $errors[$field_name]['und'][0][] = array(
         $errors[$field_name]['und'][0][] = array(
           'message' =>  t("Please specify an organism."),
           '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'];
     $alias = $this->field['field_name'];
     $operator = $condition['operator'];
     $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.
     // If the column is a subfield.
@@ -344,7 +305,7 @@ class obi__organism extends ChadoField {
       $query->condition("$alias.infraspecific_name", $condition['value'], $operator);
       $query->condition("$alias.infraspecific_name", $condition['value'], $operator);
     }
     }
     if ($condition['column'] == $infraspecific_type_term) {
     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);
       $query->condition("CVT.name", $condition['value'], $operator);
     }
     }
   }
   }
@@ -353,27 +314,30 @@ class obi__organism extends ChadoField {
    * @see ChadoField::queryOrder()
    * @see ChadoField::queryOrder()
    */
    */
   public function queryOrder($query, $order) {
   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.
     // 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']);
       $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']);
       $query->orderBy("$alias.infraspecific_name", $order['direction']);
     }
     }
-    if ($order['column'] == 'organism.infraspecies') {
+    if ($order['column'] == $infraspecific_type_term) {
       if (!in_array('CVT', $joins)) {
       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']);
       $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.
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
   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()
    * @see TripalField::load()
@@ -66,6 +119,10 @@ class ogi__location_on_map extends ChadoField {
     $field_name = $this->field['field_name'];
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $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.
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
     $entity->{$field_name}['und'][0] = array(
       'value' => array(),
       'value' => array(),
@@ -94,10 +151,10 @@ class ogi__location_on_map extends ChadoField {
         $value = array (
         $value = array (
           // Map.
           // Map.
           'data:1274' => array(
           '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')) {
         if (property_exists($featuremap, 'entity_id')) {
           $value['data:1274']['entity'] = 'TripalEntity:' . $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;
   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()
    * @see TripalField::load()
@@ -143,14 +185,14 @@ class sbo__database_cross_reference extends ChadoField {
     $operator = $condition['operator'];
     $operator = $condition['operator'];
 
 
     if ($condition['column'] == 'database_cross_reference.database_name') {
     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);
       $query->condition("DB.name", $condition['value'], $operator);
     }
     }
     if ($condition['column'] == 'database_cross_reference.accession') {
     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);
       $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;
   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()
    * @see TripalField::load()
@@ -112,7 +125,7 @@ class sbo__phenotype extends ChadoField {
         else {
         else {
           $entity->{$field_name}['und'][$i]['value']['value'] =  $phenotype->value;
           $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 . '__' . $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 . '__' . $fkey_lcolumn] = $phenotype_linker->$fkey_lcolumn->$fkey_lcolumn;
         $entity->{$field_name}['und'][$i][$field_table . '__' . 'phenotype_id'] = $phenotype->phenotype_id;
         $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;
   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()
    * @see TripalField::load()
@@ -445,49 +524,61 @@ class sbo__relationship extends ChadoField {
     $bpkey = $bschema['primary key'][0];
     $bpkey = $bschema['primary key'][0];
     $operator = $condition['operator'];
     $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.
     // 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);
       $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);
       $query->condition("base2.name", $condition['value'], $operator);
     }
     }
 
 
     // Filter by unique name of the subject or object.
     // 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);
       $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);
       $query->condition("base2.uniquename", $condition['value'], $operator);
     }
     }
 
 
     // Filter by the type of the subject or object
     // 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);
       $query->condition("ObjectCVT.name", $condition['value'], $operator);
     }
     }
 
 
     // Filter by relationship type
     // Filter by relationship type
     if ($condition['column'] == 'relationship.relationship_type') {
     if ($condition['column'] == 'relationship.relationship_type') {
       // This filter commented out because it's way to slow...
       // 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);
 //       $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
     // Build the pager
     $items_per_page = array_key_exists('items_per_page', $this->instance['settings']) ? $this->instance['settings']['items_per_page'] : 10;
     $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;
     $total_pages = (int) ($total_records / $items_per_page) + 1;
     $pelement = 0; //$this->getPagerElementID();
     $pelement = 0; //$this->getPagerElementID();
     $current_page = pager_default_initialize($total_records, $items_per_page, $pelement);
     $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';
   public static $default_formatter = 'schema__additional_type_formatter';
 
 
 
 
-
   /**
   /**
    *
    *
    * @see TripalField::load()
    * @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'];
     $operator = $condition['operator'];
 
 
     if ($condition['column'] == 'alternatename') {
     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']);
       $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.
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
   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()
    * @see TripalField::load()
@@ -124,17 +136,17 @@ class schema__publication extends ChadoField {
     if ($condition['column'] == 'publication.database_cross_reference') {
     if ($condition['column'] == 'publication.database_cross_reference') {
       list($db_name, $accession) = explode(':', $condition['value']);
       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("DB.name", $db_name);
       $query->condition("DBX.accession", $accession);
       $query->condition("DBX.accession", $accession);
     }
     }
 
 
     if ($condition['column'] == 'publication.title') {
     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);
       $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().
   // and field_create_instance().
   public static $no_ui = FALSE;
   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()
    * @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'];
     $alias = $this->field['field_name'];
     $operator = $condition['operator'];
     $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);
     $query->condition("$alias.name", $condition['value'], $operator);
   }
   }
 
 
@@ -105,7 +105,7 @@ class sio__vocabulary extends ChadoField {
     $joins = $query->getTables();
     $joins = $query->getTables();
     if (!in_array($this->field['field_name'], $joins)) {
     if (!in_array($this->field['field_name'], $joins)) {
       $alias = $this->field['field_name'];
       $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']);
       $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.
   // The default formatter for this field.
   public static $default_formatter = 'so__cds_formatter';
   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()
    * @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;
   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()
    * @see TripalField::load()
@@ -105,10 +145,9 @@ class so__genotype extends ChadoField {
         $genotype = $genotype_linker->genotype_id;
         $genotype = $genotype_linker->genotype_id;
         $entity->{$field_name}['und'][$i] = array(
         $entity->{$field_name}['und'][$i] = array(
           'value' => 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 . '__' . $pkey => $genotype_linker->$pkey,
           $field_table . '__' . $fkey_lcolumn => $genotype_linker->$fkey_lcolumn->$fkey_lcolumn,
           $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.
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
   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()
    * @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.
   // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
   protected $instance;
   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()
    * @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
   // build the values array for inserting or updating
   $values = array(
   $values = array(
     'title'       => $pub_details['Title'],
     'title'       => $pub_details['Title'],
-    'volume'      => $pub_details['Volume'],
+    'volume'      => (isset($pub_details['Volume'])) ? $pub_details['Volume'] : '',
     'series_name' => $series_name,
     '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'],
     'uniquename'  => $pub_details['Citation'],
     'type_id'     => $pub_type->cvterm_id,
     '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) {
 function tripal_pub_add_authors($pub_id, $authors, $do_contact) {
   $rank = 0;
   $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";
   $sql = "DELETE FROM {pubauthor} WHERE pub_id = :pub_id";
   chado_query($sql, array(':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) {
   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;
       continue;
     }
     }
     // remove the 'valid' property as we don't have a CV term for it
     // 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
     // construct the contact.name field using the author information
     $name = '';
     $name = '';
     $type = 'Person';
     $type = 'Person';
-    if ($author['Given Name']) {
+    if (isset($author['Given Name'])) {
       $name .= $author['Given Name'];
       $name .= $author['Given Name'];
     }
     }
-    if ($author['Surname']) {
+    if (isset($author['Surname'])) {
       $name .= ' ' . $author['Surname'];
       $name .= ' ' . $author['Surname'];
     }
     }
-    if ($author['Suffix']) {
+    if (isset($author['Suffix'])) {
       $name .= ' ' . $author['Suffix'];
       $name .= ' ' . $author['Suffix'];
     }
     }
-    if ($author['Collective']) {
+    if (isset($author['Collective'])) {
       $name = $author['Collective'];
       $name = $author['Collective'];
       $type = '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);
     $real_version = chado_get_version(TRUE);
 
 
     // Create custom tables depending on the Chado version installed.
     // Create custom tables depending on the Chado version installed.
+    drush_print("Creating Tripal Materialized Views and Custom Tables...");
     $chado_version = chado_get_version();
     $chado_version = chado_get_version();
     if ($chado_version == '1.1') {
     if ($chado_version == '1.1') {
       tripal_chado_add_v1_1_custom_tables();
       tripal_chado_add_v1_1_custom_tables();
@@ -171,16 +172,21 @@ function tripal_chado_prepare_chado() {
     }
     }
 
 
     // Import commonly used ontologies if needed.
     // Import commonly used ontologies if needed.
+    drush_print("Loading Ontologies...");
     tripal_chado_load_ontologies();
     tripal_chado_load_ontologies();
 
 
     // Populate the semantic web associations for Chado tables/fields.
     // Populate the semantic web associations for Chado tables/fields.
+    drush_print("Making semantic connections for Chado tables/fields...");
     tripal_chado_populate_chado_semweb_table();
     tripal_chado_populate_chado_semweb_table();
 
 
     // Initialize the population of the chado_cvterm_mapping table.  This will
     // Initialize the population of the chado_cvterm_mapping table.  This will
     // map existing data types already in Chado so that when users want to
     // map existing data types already in Chado so that when users want to
     // add new content types it simplifies the form for them.
     // add new content types it simplifies the form for them.
+    drush_print("Map Chado Controlled vocabularies to Tripal Terms...");
     tripal_chado_map_cvterms();
     tripal_chado_map_cvterms();
 
 
+    drush_print("Creating common Tripal Content Types...");
+
     // Create the 'Organism' entity type. This uses the obi:organism term.
     // Create the 'Organism' entity type. This uses the obi:organism term.
     $error = '';
     $error = '';
     $args = array(
     $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;
   $table = $bundle->data_table;
 
 
   if ($table == 'organism') {
   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') {
   if ($table == 'analysis') {
     $format[] = array(
     $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.
   // Now set any ordering.
   foreach ($query->order as $index => $sort) {
   foreach ($query->order as $index => $sort) {
     // Add in property ordering.
     // Add in property ordering.
-    if ($order['type'] == 'property') {
-
+    if ($sort['type'] == 'property') {
+       // TODO: support ordering by bundle properties.
     }
     }
     // Add in filter ordering
     // Add in filter ordering
     if ($sort['type'] == 'field') {
     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
   // 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.
   // 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['data_table'] = $default['table'];
     $storage_args['type_column'] = $default['type_column'];
     $storage_args['type_column'] = $default['type_column'];
     return;
     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_table' => $chado_bundle->data_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
     'chado_type_column' => $chado_bundle->type_column,
     'chado_type_column' => $chado_bundle->type_column,
+    'chado_type_value' => $chado_bundle->type_value,
   );
   );
 
 
   $info = array();
   $info = array();
@@ -44,8 +45,9 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
 
 
   $table_name = $details['chado_table'];
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_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'];
   $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
   // Iterate through the columns of the table and see if fields have been
   // created for each one. If not, then create them.
   // 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.
     // 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;
       continue;
     }
     }
     $cvterm = tripal_get_chado_semweb_term($table_name, $column_name, array('return_object' => TRUE));
     $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
     // 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') {
          $column_name == 'type_id') {
       continue;
       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) {
 function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type, $bundle) {
   $table_name = $details['chado_table'];
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_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'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
 
   $schema = chado_get_schema($table_name);
   $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
   // by the type_id field or on the organism table where the type_id is mean
   // as an infraspecific name type.
   // as an infraspecific name type.
   if (array_key_exists('type_id', $schema['fields']) and
   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_name = 'schema__additional_type';
     $field_type = 'schema__additional_type';
     $field_type = 'schema__additional_type';
     $info[$field_name] = array(
     $info[$field_name] = array(
@@ -409,8 +412,9 @@ function tripal_chado_bundle_fields_info_linker(&$info, $details, $entity_type,
 
 
   $table_name = $details['chado_table'];
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_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'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
 
   // CONTACTS
   // CONTACTS
   $contact_table = $table_name . '_contact';
   $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
       // The tripal_analysis_KEGG, tripal_analysis_blast, and
       // tripal_analysis_interpro modules store results in the analysisprop
       // tripal_analysis_interpro modules store results in the analysisprop
       // table which is probably not the best place, but we don't want to
       // 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
       if ($prop_table == 'analysisprop' and
           ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
           ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
            $term->dbxref_id->db_id->name == 'tripal')) {
            $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_table' => $chado_bundle->data_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
     'chado_type_column' => $chado_bundle->type_column,
     'chado_type_column' => $chado_bundle->type_column,
+    'chado_type_value' => $chado_bundle->type_value,
   );
   );
 
 
   $info = array();
   $info = array();
@@ -674,8 +679,9 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
   // Get Chado information
   // Get Chado information
   $table_name = $details['chado_table'];
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_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'];
   $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
   // Iterate through the columns of the table and see if fields have been
   // created for each one. If not, then create them.
   // 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.
     // 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;
       continue;
     }
     }
 
 
     // Skip the type field that will always be custom
     // 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') {
         $column_name == 'type_id') {
       continue;
       continue;
     }
     }
@@ -723,7 +729,7 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
     }
     }
 
 
     // Skip the type field.
     // 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;
       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) {
 function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle, $details) {
   $table_name = $details['chado_table'];
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_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'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
   $schema = chado_get_schema($table_name);
   $schema = chado_get_schema($table_name);
 
 
   // BASE TYPE_ID
   // 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
   // by the type_id field or on the organism table where the type_id is mean
   // as an infraspecific name type.
   // as an infraspecific name type.
   if (array_key_exists('type_id', $schema['fields']) and
   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_name = 'schema__additional_type';
     $is_required = FALSE;
     $is_required = FALSE;
     if (array_key_exists('not null', $schema['fields']['type_id']) and
     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'];
   $table_name = $details['chado_table'];
   $type_table = $details['chado_type_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'];
   $cvterm_id  = $details['chado_cvterm_id'];
+  $type_value = $details['chado_type_value'];
 
 
   // CONTACTS
   // CONTACTS
   $contact_table = $table_name . '_contact';
   $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);
      $schema = chado_get_schema($prop_table);
      $tpkey = $tschema['primary key'][0];
      $tpkey = $tschema['primary key'][0];
      $pkey = $schema['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();
      $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(
            '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 =
         $sql =
            "SELECT count(*)
            "SELECT count(*)
             FROM {organism} O
             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();
         $org_count = chado_query($sql)->fetchField();
         if ($org_count > 0) {
         if ($org_count > 0) {
@@ -147,7 +147,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
         $sql =
         "SELECT count(*)
         "SELECT count(*)
           FROM {analysis} A
           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();
         $ana_count = chado_query($sql)->fetchField();
         if ($ana_count > 0) {
         if ($ana_count > 0) {
@@ -163,7 +163,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
         $sql =
           "SELECT count(*)
           "SELECT count(*)
            FROM {project} P
            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();
         $proj_count = chado_query($sql)->fetchField();
         if ($proj_count > 0) {
         if ($proj_count > 0) {
@@ -179,7 +179,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
         $sql =
           "SELECT count(*)
           "SELECT count(*)
             FROM {featuremap} M
             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();
         $map_count = chado_query($sql)->fetchField();
         if ($map_count > 0) {
         if ($map_count > 0) {
@@ -195,7 +195,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
         $sql =
           "SELECT count(*)
           "SELECT count(*)
            FROM {pub} P
            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();
         $proj_count = chado_query($sql)->fetchField();
         if ($proj_count > 0) {
         if ($proj_count > 0) {
@@ -212,7 +212,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
         $sql =
         $sql =
             "SELECT V.name AS type, X.accession, db.name AS vocabulary , count(*) AS num
             "SELECT V.name AS type, X.accession, db.name AS vocabulary , count(*) AS num
               FROM {" . $table . "} T
               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 {cvterm} V ON V.cvterm_id = T.type_id
               INNER JOIN {dbxref} X ON X.dbxref_id = V.dbxref_id
               INNER JOIN {dbxref} X ON X.dbxref_id = V.dbxref_id
               INNER JOIN {db} ON db.db_id = X.db_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 = "
       $sql = "
         SELECT V.name AS type, X.accession, db.name AS vocabulary
         SELECT V.name AS type, X.accession, db.name AS vocabulary
         FROM {" . $table . "} T
         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 {cvterm} V ON V.cvterm_id = T.type_id
           INNER JOIN {dbxref} X ON X.dbxref_id = V.dbxref_id
           INNER JOIN {dbxref} X ON X.dbxref_id = V.dbxref_id
           INNER JOIN {db} ON db.db_id = X.db_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.',
     'definition' => 'The name of the item.',
   ));
   ));
   tripal_associate_chado_semweb_term(NULL, 'name', $term);
   tripal_associate_chado_semweb_term(NULL, 'name', $term);
+  tripal_associate_chado_semweb_term('analysis', 'sourcename', $term);
 
 
   $term = tripal_insert_cvterm(array(
   $term = tripal_insert_cvterm(array(
     'id' => 'schema:alternateName',
     '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.',
     '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(NULL, 'dbxref_id', $term);
+  tripal_associate_chado_semweb_term('dbxref', 'accession', $term);
 
 
   $term = tripal_insert_cvterm(array(
   $term = tripal_insert_cvterm(array(
     'id' => 'data:2044',
     'id' => 'data:2044',
@@ -597,6 +599,8 @@ function tripal_chado_populate_vocab_IAO() {
     'having the same name.',
     'having the same name.',
   ));
   ));
   tripal_associate_chado_semweb_term('analysis', 'programversion', $term);
   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(
   $term = tripal_insert_cvterm(array(
     'id' => 'IAO:0000064',
     'id' => 'IAO:0000064',

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

@@ -57,4 +57,8 @@
 
 
 #autocomplete {
 #autocomplete {
   width: 600px !important;
   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);
     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.
   // Maximum number of jobs that can be run in parallel.
   // @todo: Implement actually changing this setting (see above note).
   // @todo: Implement actually changing this setting (see above note).
-  protected $max_num_jobs = 2;
+  protected $max_num_jobs = 3;
 
 
   /**
   /**
    * Implements DaemonAPIDaemon::executeTask() function.
    * Implements DaemonAPIDaemon::executeTask() function.
@@ -301,4 +301,17 @@ class TripalDaemon extends DrushDaemon {
 
 
     return $status_details;
     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.',
       'show-log' => 'Show the log file.',
     ),
     ),
     'options' => array(
     '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.',
       'num_lines' => 'The number of lines of the log file to show.',
       'child' => array(
       'child' => array(
         'hidden' => TRUE,
         'hidden' => TRUE,
@@ -40,7 +42,7 @@ function tripal_daemon_drush_command() {
     'examples' => array(
     'examples' => array(
       'drush trpjob-daemon start' => 'Start the daemon.',
       'drush trpjob-daemon start' => 'Start the daemon.',
       'drush trpjob-daemon status' => 'Show the current status of 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' => '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.',
       '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.
  *   you want the daemon to do.
  */
  */
 function drush_tripal_daemon_tripal_jobs_daemon($action) {
 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 we couldn't match this field argument to a field and entity then return
     if (!$entity) {
     if (!$entity) {
-      throw new Exception("Canno find this entity.");
+      throw new Exception("Cannot find this entity.");
     }
     }
 
 
     list($field, $instance, $term) = $this->findField($bundle, $expfield);
     list($field, $instance, $term) = $this->findField($bundle, $expfield);
@@ -485,22 +485,46 @@ class TripalEntityService_v0_1 extends TripalWebService {
         $op = $matches[2];
         $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);
       $subkeys = explode(',', $key);
       if (count($subkeys) > 0) {
       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.
       // Map the values in the filters to their appropriate field names.
       if (array_key_exists($key, $field_mapping)) {
       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 {
       else {
         throw new Exception("The filter term, '$key', is not available for use.");
         throw new Exception("The filter term, '$key', is not available for use.");