Browse Source

merge fix

Shawna 7 years ago
parent
commit
57e2916b36
99 changed files with 4107 additions and 2227 deletions
  1. 1 1
      legacy/tripal_cv/theme/templates/tripal_cv_help.tpl.php
  2. 10 3
      tripal/api/tripal.entities.api.inc
  3. 2 1
      tripal/includes/TripalBundleUIController.inc
  4. 1 1
      tripal/includes/TripalEntityUIController.inc
  5. 29 13
      tripal/includes/TripalFields/TripalField.inc
  6. 1 1
      tripal/includes/TripalFields/TripalFieldWidget.inc
  7. 38 2
      tripal/includes/TripalImporter.inc
  8. 14 3
      tripal/includes/TripalJob.inc
  9. 4 0
      tripal/includes/tripal.entity.inc
  10. 5 2
      tripal/includes/tripal.fields.inc
  11. 1 0
      tripal/tripal.info
  12. 37 1
      tripal/tripal.module
  13. 24 6
      tripal/tripal.views_default.inc
  14. 39 62
      tripal/tripal_views_query.inc
  15. 187 0
      tripal/views_handlers/tripal_views_handler_filter_numeric.inc
  16. 2 6
      tripal/views_handlers/tripal_views_handler_filter_string.inc
  17. 14 8
      tripal_chado/api/modules/tripal_chado.cv.api.inc
  18. 4 0
      tripal_chado/api/modules/tripal_chado.db.api.inc
  19. 32 16
      tripal_chado/api/modules/tripal_chado.pub.api.inc
  20. 1 1
      tripal_chado/api/tripal_chado.mviews.api.inc
  21. 12 5
      tripal_chado/api/tripal_chado.query.api.inc
  22. 21 0
      tripal_chado/includes/TripalFields/ChadoField.inc
  23. 75 17
      tripal_chado/includes/TripalFields/chado_linker__contact/chado_linker__contact.inc
  24. 31 34
      tripal_chado/includes/TripalFields/chado_linker__contact/chado_linker__contact_widget.inc
  25. 0 178
      tripal_chado/includes/TripalFields/chado_linker__cvterm/chado_linker__cvterm.inc
  26. 0 132
      tripal_chado/includes/TripalFields/chado_linker__cvterm/chado_linker__cvterm_formatter.inc
  27. 0 176
      tripal_chado/includes/TripalFields/chado_linker__cvterm/chado_linker__cvterm_widget.inc
  28. 23 0
      tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop.inc
  29. 38 28
      tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop_widget.inc
  30. 29 17
      tripal_chado/includes/TripalFields/data__accession/data__accession.inc
  31. 58 73
      tripal_chado/includes/TripalFields/data__accession/data__accession_widget.inc
  32. 11 4
      tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence.inc
  33. 1 8
      tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence_formatter.inc
  34. 4 10
      tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence_widget.inc
  35. 15 3
      tripal_chado/includes/TripalFields/data__sequence/data__sequence_widget.inc
  36. 9 9
      tripal_chado/includes/TripalFields/data__sequence_checksum/data__sequence_checksum_widget.inc
  37. 106 30
      tripal_chado/includes/TripalFields/data__sequence_coordinates/data__sequence_coordinates.inc
  38. 11 18
      tripal_chado/includes/TripalFields/data__sequence_coordinates/data__sequence_coordinates_formatter.inc
  39. 0 7
      tripal_chado/includes/TripalFields/data__sequence_length/data__sequence_length_formatter.inc
  40. 9 7
      tripal_chado/includes/TripalFields/data__sequence_length/data__sequence_length_widget.inc
  41. 0 7
      tripal_chado/includes/TripalFields/go__gene_expression/go__gene_expression_formatter.inc
  42. 37 8
      tripal_chado/includes/TripalFields/local__source_data/local__source_data.inc
  43. 7 3
      tripal_chado/includes/TripalFields/local__source_data/local__source_data_formatter.inc
  44. 40 15
      tripal_chado/includes/TripalFields/local__source_data/local__source_data_widget.inc
  45. 2 9
      tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc
  46. 2 2
      tripal_chado/includes/TripalFields/obi__organism/obi__organism_widget.inc
  47. 0 8
      tripal_chado/includes/TripalFields/ogi__location_on_map/ogi__location_on_map_formatter.inc
  48. 58 43
      tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference.inc
  49. 17 12
      tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference_formatter.inc
  50. 39 123
      tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference_widget.inc
  51. 2 2
      tripal_chado/includes/TripalFields/sbo__phenotype/sbo__phenotype.inc
  52. 0 8
      tripal_chado/includes/TripalFields/sbo__phenotype/sbo__phenotype_formatter.inc
  53. 274 369
      tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship.inc
  54. 10 4
      tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship_widget.inc
  55. 38 1
      tripal_chado/includes/TripalFields/schema__additional_type/schema__additional_type.inc
  56. 8 13
      tripal_chado/includes/TripalFields/schema__additional_type/schema__additional_type_formatter.inc
  57. 83 1
      tripal_chado/includes/TripalFields/schema__additional_type/schema__additional_type_widget.inc
  58. 36 13
      tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name.inc
  59. 3 10
      tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name_formatter.inc
  60. 55 35
      tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name_widget.inc
  61. 0 43
      tripal_chado/includes/TripalFields/schema__publication/schema__publication.inc
  62. 2 15
      tripal_chado/includes/TripalFields/schema__publication/schema__publication_formatter.inc
  63. 37 31
      tripal_chado/includes/TripalFields/schema__publication/schema__publication_widget.inc
  64. 346 0
      tripal_chado/includes/TripalFields/sio__annotation/sio__annotation.inc
  65. 78 0
      tripal_chado/includes/TripalFields/sio__annotation/sio__annotation_formatter.inc
  66. 293 0
      tripal_chado/includes/TripalFields/sio__annotation/sio__annotation_widget.inc
  67. 0 30
      tripal_chado/includes/TripalFields/sio__references/sio__references.inc
  68. 0 9
      tripal_chado/includes/TripalFields/sio__references/sio__references_formatter.inc
  69. 1 1
      tripal_chado/includes/TripalFields/sio__vocabulary/sio__vocabulary.inc
  70. 29 2
      tripal_chado/includes/TripalFields/sio__vocabulary/sio__vocabulary_widget.inc
  71. 0 7
      tripal_chado/includes/TripalFields/so__genotype/so__genotype.inc
  72. 0 8
      tripal_chado/includes/TripalFields/so__genotype/so__genotype_formatter.inc
  73. 0 8
      tripal_chado/includes/TripalFields/so__transcript/so__transcript_formatter.inc
  74. 92 6
      tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon.inc
  75. 16 8
      tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon_formatter.inc
  76. 74 14
      tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon_widget.inc
  77. 5 0
      tripal_chado/includes/TripalFields/uo__unit/uo__unit.inc
  78. 42 1
      tripal_chado/includes/TripalFields/uo__unit/uo__unit_widget.inc
  79. 19 25
      tripal_chado/includes/TripalImporter/OBOImporter.inc
  80. 25 2
      tripal_chado/includes/setup/tripal_chado.setup.inc
  81. 13 0
      tripal_chado/includes/tripal_chado.cv.inc
  82. 5 0
      tripal_chado/includes/tripal_chado.db.inc
  83. 12 4
      tripal_chado/includes/tripal_chado.field_storage.inc
  84. 331 200
      tripal_chado/includes/tripal_chado.fields.inc
  85. 2 4
      tripal_chado/includes/tripal_chado.publish.inc
  86. 61 13
      tripal_chado/includes/tripal_chado.semweb.inc
  87. 1 0
      tripal_chado/theme/css/tripal_chado.css
  88. 130 0
      tripal_chado/tripal_chado.install
  89. 1 1
      tripal_chado/tripal_chado.module
  90. 0 4
      tripal_chado/tripal_chado.views_default.inc
  91. 11 3
      tripal_ds/includes/tripal_ds.ds.inc
  92. 0 1
      tripal_ds/tripal_ds.install
  93. 16 5
      tripal_ws/includes/TripalWebService.inc
  94. 0 19
      tripal_ws/includes/TripalWebService/TripalDocService_V0_1.inc
  95. 546 116
      tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc
  96. 138 1
      tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc
  97. 28 6
      tripal_ws/includes/TripalWebServiceCollection.inc
  98. 116 52
      tripal_ws/includes/TripalWebServiceResource.inc
  99. 27 19
      tripal_ws/tripal_ws.module

+ 1 - 1
legacy/tripal_cv/theme/templates/tripal_cv_help.tpl.php

@@ -15,7 +15,7 @@
    users access to view content and create a special role for creating, editing and other administrative tasks.</p></li>
 
       <li><p><b>Loading of Ontologies/Controlled Vocabularies</b>: You can access this loader at <?php
-        print l('Admin->Tripal Management->Tripal CV->Load Ontology With OBO File', 'admin/tripal/tripal_cv/obo_loader')
+        print l('Admin->Tripal Management->Tripal CV->Load Ontology With OBO File', 'admin/tripal/loaders/chado_obo_loader')
         ?>. This loader allows you to choose from a list of common ontologies or
         enter the URL or location to an OBO file. Even the list of common
         ontologies is using a URL ensuring you get the most up to date ontology.</p>

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

@@ -1056,7 +1056,9 @@ function tripal_replace_entity_tokens($string, &$entity, $bundle_entity = NULL)
       // Note: there is a memory leak in field_get_items() so we can't use it
       // here or bulk publising will slowly erode memory.
       //$field_value = field_get_items('TripalEntity', $entity, $field_name);
-      $value = $entity->{$field_name}['und'][0]['value'];
+      if (array_key_exists(0, $entity->{$field_name}['und'])) {
+        $value = $entity->{$field_name}['und'][0]['value'];
+      }
       // TODO: deal with the value when it is not a scalar.
     }
     // The TripalBundle__bundle_id is a special token for substituting the
@@ -1076,8 +1078,13 @@ function tripal_replace_entity_tokens($string, &$entity, $bundle_entity = NULL)
       $value = $entity->id;
     }
 
-    // Perform the replacement of the token with the value.
-    $string = str_replace($token, $value, $string);
+    // We can't support tokens that have multiple elements (i.e. in an array).
+    if (is_array($value)) {
+      $string = str_replace($token, '', $string);
+    }
+    else {
+      $string = str_replace($token, $value, $string);
+    }
   }
 
   return $string;

+ 2 - 1
tripal/includes/TripalBundleUIController.inc

@@ -752,7 +752,8 @@ function tripal_admin_add_type_form_submit($form, &$form_state) {
         $form_state['redirect'] = "admin/structure/bio_data";
       }
       else {
-        drupal_set_message('New data type created.');
+        drupal_set_message('New content type created!');
+        drupal_set_message('Please ' . l("set the user permissions", "admin/people/permissions") . ' for this new content type.');
         tripal_admin_access($bundle);
         $form_state['redirect'] = "admin/structure/bio_data";
       }

+ 1 - 1
tripal/includes/TripalEntityUIController.inc

@@ -648,7 +648,7 @@ function tripal_entity_form_ajax_callback($form, $form_state) {
    $langcode = 'und';
    foreach ($instances as $field_name => $instance) {
      $entity_type = $instance['entity_type'];
-     if($entity_type == 'TripalEntity') {
+     if($entity_type == 'TripalEntity' and array_key_exists($field_name, $form)) {
        foreach ($form[$field_name][$langcode] as $delta => $field_form) {
          if (!preg_match('/^\d+$/', $delta)) {
            continue;

+ 29 - 13
tripal/includes/TripalFields/TripalField.inc

@@ -208,7 +208,7 @@ class TripalField {
   /**
    * Describes this field to Tripal web services.
    *
-   * The child class need not implement this function has all of the details
+   * The child class need not implement this function. It has all of the details
    * provided for elements by the elementInfo() function are used to generate
    * the details needed for Views.
    *
@@ -224,19 +224,28 @@ class TripalField {
     $field_details = $elements[$field_term];
 
     $searchable_keys = array();
+    $sortable_keys = array();
+
     if (array_key_exists('searchable', $field_details) and $field_details['searchable']) {
       $searchable_keys[$field_term_name] = $field_term;
     }
 
+    if (array_key_exists('sortable', $field_details) and $field_details['sortable']) {
+      $sortable_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);
+        $this->_addWebServiceElement($searchable_keys, $sortable_keys, $field_term_name, $field_term, $element_name, $element_details);
       }
     }
 
-    return $searchable_keys;
+    return array(
+      'searchable' => $searchable_keys,
+      'sortable' => $sortable_keys
+    );
   }
 
   /**
@@ -246,7 +255,9 @@ class TripalField {
    * @param $element_name
    * @param $element_details
    */
-  protected function _addWebServiceElement(&$searchable_keys, $parent_term_name, $parent_term, $element_name, $element_details) {
+  protected function _addWebServiceElement(&$searchable_keys, &$sortable_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') {
@@ -262,12 +273,15 @@ class TripalField {
     if (array_key_exists('searchable', $element_details) and $element_details['searchable']) {
       $searchable_keys[$field_term_name] =  $field_term;
     }
+    if (array_key_exists('sortable', $element_details) and $element_details['sortable']) {
+      $sortable_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);
+        $this->_addWebServiceElement($searchable_keys, $sortable_keys, $field_term_name, $field_term, $element_name, $element_details);
       }
     }
   }
@@ -329,7 +343,7 @@ class TripalField {
     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';
+        $filter_handler = 'tripal_views_handler_filter_numeric';
       }
       $data[$view_base_id][$field_name]['filter'] = array(
         'handler' => $filter_handler,
@@ -343,6 +357,7 @@ class TripalField {
         $this->_addViewsDataElement($data, $view_base_id, $field_name, $element_name, $element_details);
       }
     }
+
     return $data;
   }
 
@@ -360,6 +375,11 @@ class TripalField {
     if ($element_name == 'entity') {
       return;
     }
+
+    if (!preg_match('/:/', $element_name)) {
+      return;
+    }
+
     $field_name = $parent . '.' . $element_name;
     list($vocabulary, $accession) = explode(':', $element_name);
     $term = tripal_get_term_details($vocabulary, $accession);
@@ -392,7 +412,7 @@ class TripalField {
     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';
+        $filter_handler = 'tripal_views_handler_filter_numeric';
       }
       $data[$view_base_id][$field_name]['filter'] = array(
         'handler' => $filter_handler,
@@ -422,10 +442,6 @@ class TripalField {
    *    The type of $entity.
    *  @param $entity
    *    The entity for the operation.
-   *  @param $field
-   *    The field structure for the operation.
-   *  @param $instance
-   *    The instance structure for $field on $entity's bundle.
    *  @param $langcode
    *    The language associated with $items.
    *  @param $items
@@ -440,7 +456,7 @@ class TripalField {
    *      - message: The human readable message to be displayed.
    *
    */
-  public function validate($entity_type, $entity, $field, $items, &$errors) {
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
 
   }
 
@@ -658,7 +674,7 @@ class TripalField {
   }
 
   /**
-   * Afte a field instance is created the following function is run.
+   * After a field instance is created the following function is run.
    *
    * This function is equivalent to the hook_field_create_field() hook of
    * the Drupal Field API. This function is invoked after a new field

+ 1 - 1
tripal/includes/TripalFields/TripalFieldWidget.inc

@@ -113,7 +113,7 @@ class TripalFieldWidget {
   }
 
   /**
-   * Performs validation of the widgetForm.
+   * Performs validation of the widget form.
    *
    * Use this validate to ensure that form values are entered correctly.
    * The 'value' key of this field must be set in the $form_state['values']

+ 38 - 2
tripal/includes/TripalImporter.inc

@@ -389,8 +389,16 @@ class TripalImporter {
         $file_remote = $this->arguments['file']['file_remote'];
         $this->logMessage('Download file: !file_remote...', array('!file_remote' => $file_remote));
 
+        // If this file is compressed then keepthe .gz extension so we can
+        // uncompress it.
+        $ext = '';
+        if (preg_match('/^(.*?)\.gz$/', $this->arguments['file']['file_remote'])) {
+          $ext = '.gz';
+        }
         // Create a temporary file.
-        $temp = tempnam("temporary://", 'import_');
+        $temp = tempnam("temporary://", 'import_') . $ext;
+        $this->logMessage("Saving as: !file", array('!file' => $temp));
+
         $url_fh = fopen($file_remote, "r");
         $tmp_fh = fopen($temp, "w");
         if (!$url_fh) {
@@ -402,11 +410,39 @@ class TripalImporter {
         while (!feof($url_fh)) {
           fwrite($tmp_fh, fread($url_fh, 255), 255);
         }
-
         // Set the path to the file for the importer to use.
         $this->arguments['file']['file_path'] = $temp;
         $this->is_prepared = TRUE;
       }
+
+      // Is this file compressed?  If so, then uncompress it
+      $matches = array();
+      if (preg_match('/^(.*?)\.gz$/', $this->arguments['file']['file_path'], $matches)) {
+        $this->logMessage("Uncompressing: !file", array('!file' => $this->arguments['file']['file_path']));
+        $buffer_size = 4096;
+        $new_file_path = $matches[1];
+        $gzfile = gzopen($this->arguments['file']['file_path'], 'rb');
+        $out_file = fopen($new_file_path, 'wb');
+        if (!$out_file) {
+          throw new Exception("Cannot uncompress file: new temporary file, '$new_file_path', cannot be created.");
+        }
+
+        // Keep repeating until the end of the input file
+        while (!gzeof($gzfile)) {
+          // Read buffer-size bytes
+          // Both fwrite and gzread and binary-safe
+          fwrite($out_file, gzread($gzfile, $buffer_size));
+        }
+
+        // Files are done, close files
+        fclose($out_file);
+        gzclose($gzfile);
+
+        // Now remove the .gz file and reset the file_path to the new
+        // uncompressed version.
+        unlink($this->arguments['file']['file_path']);
+        $this->arguments['file']['file_path'] = $new_file_path;
+      }
     }
     catch (Exception $e){
       throw new Exception('Cannot prepare the importer: ' .  $e->getMessage());

+ 14 - 3
tripal/includes/TripalJob.inc

@@ -115,8 +115,19 @@ class TripalJob {
     }
 
     $includes = $details['includes'];
-    foreach ($includes as $include) {
-      require_once($include);
+    foreach ($includes as $path) {
+      $full_path = $_SERVER['DOCUMENT_ROOT'] . base_path() . $path;
+      if (!empty($path)) {
+        if (file_exists($path)) {
+          require_once($path);
+        }
+        elseif (file_exists($full_path)) {
+          require_once($path);
+        }
+        elseif (!empty($path)) {
+          throw new Exception("Included files for Tripal Job must exist. This path ($full_path) doesn't exist.");
+        }
+      }
     }
     if (!function_exists($details['callback'])) {
       throw new Exception("Must provide a valid callback function to the tripal_add_job() function.");
@@ -253,7 +264,7 @@ class TripalJob {
       $record->job_id = $this->job->job_id;
       $record->end_time = time();
       $record->error_msg = $this->job->error_msg;
-      $record->progress = $this->job->progress;
+      $record->progress = 100;
       $record->status = 'Completed';
       $record->pid = '';
 

+ 4 - 0
tripal/includes/tripal.entity.inc

@@ -275,6 +275,10 @@ function tripal_entity_info_alter(&$entity_info){
  */
 function tripal_entity_property_info_alter(&$info) {
 
+  // Sometimes this function is called when there are no Tripal Entities.
+  // Don't bother to do anything in this case.
+  if (!isset($info['TripalEntity']['bundles'])) { return TRUE; }
+
   // For each Tripal Content Type, we want to ensure all attached fields
   // are added to the bundle properties.
   foreach ($info['TripalEntity']['bundles'] as $bundle_name => $bundle) {

+ 5 - 2
tripal/includes/tripal.fields.inc

@@ -108,6 +108,7 @@ function tripal_field_views_data($field) {
       $data += $tfield->viewsData($view_base_id);
     }
   }
+
   return $data;
 }
 
@@ -694,9 +695,11 @@ function tripal_field_instance_settings_form_submit($form, &$form_state) {
 function tripal_field_widget_form_validate($element, &$form_state, $form) {
   $field = $element['#field'];
   $instance = $element['#instance'];
-  $widget_class = $element['#field_name'] . '_widget';
+
   $langcode = $element['#language'];
   $delta = $element['#delta'];
+
+  $widget_class = $instance['widget']['type'];
   tripal_load_include_field_class($widget_class);
   if (class_exists($widget_class)) {
     $widget = new $widget_class($field, $instance);
@@ -810,7 +813,7 @@ function tripal_field_validate($entity_type, $entity, $field, $instance,
    $field_type = $field['type'];
    if (tripal_load_include_field_class($field_type)) {
      $tfield = new $field_type($field, $instance);
-     $tfield->validate($entity_type, $entity, $langcode,$items, $errors);
+     $tfield->validate($entity_type, $entity, $langcode, $items, $errors);
    }
 }
 

+ 1 - 0
tripal/tripal.info

@@ -19,6 +19,7 @@ files[] = views_handlers/tripal_views_handler_field_image.inc
 files[] = views_handlers/tripal_views_handler_field_boolean.inc
 files[] = views_handlers/tripal_views_handler_filter.inc
 files[] = views_handlers/tripal_views_handler_filter_string.inc
+files[] = views_handlers/tripal_views_handler_filter_numeric.inc
 files[] = views_handlers/tripal_views_handler_filter_element_string.inc
 files[] = views_handlers/tripal_views_handler_filter_boolean_operator.inc
 files[] = views_handlers/tripal_views_handler_filter_entity_string.inc

+ 37 - 1
tripal/tripal.module

@@ -640,6 +640,42 @@ function tripal_form_alter(&$form, $form_state, $form_id) {
   if ($form_id == 'field_ui_field_edit_form' and $form['#instance']['entity_type'] == 'TripalEntity') {
     tripal_field_instance_settings_form_alter($form, $form_state);
   }
+
+  // Remove fields that have no form. It's just a bit too confusing to have
+  // widgets appear in the form but without and form elements inside them.
+  if ($form_id == 'tripal_entity_form') {
+    $children = element_children($form);
+    foreach ($children as $child) {
+      // count the number of form elements
+      if (array_key_exists('und', $form[$child])) {
+        $total_widgets = 0;
+        foreach ($form[$child]['und'] as $delta => $element) {
+          if (is_numeric($delta)) {
+            $total_widgets += count(element_children($element));
+            // Ignore the weight column
+            if (array_key_exists('_weight', $element)) {
+              $total_widgets--;
+            }
+            // Ignore a hidden value column
+            if (array_key_exists('value', $element) and $element['value']['#type'] == 'value') {
+              $total_widgets--;
+            }
+            // Some form elements don't have a 'value' and they don't have any
+            // widgets (i.e image field and description field. We don't
+            // want to loose those, so add one to the widget count.
+            if (!array_key_exists('value', $element)) {
+              $total_widgets++;
+            }
+          }
+        }
+        // If we have no widgets then here's not a form for this field so just
+        // remove it.
+        if ($total_widgets == 0) {
+          unset($form[$child]);
+        }
+      }
+    }
+  }
 }
 
 function tripal_check_new_fields($bundle_name) {
@@ -1096,7 +1132,7 @@ function tripal_field_display_TripalEntity_alter(&$display, $context){
  * @param $element
  *   The field group object.
  * @param $children
- *   An array of field names that are cpntained in the field group
+ *   An array of field names that are contained in the field group.
  */
 function tripal_field_group_table_rows_alter(&$element, &$children) {
 

+ 24 - 6
tripal/tripal.views_default.inc

@@ -145,6 +145,12 @@ function tripal_bundle_default_views(&$views) {
         $handler->display->display_options['fields']['schema__description']['alter']['trim'] = TRUE;
       }
 
+      // Add a filter to show published content only.
+      $handler->display->display_options['filters']['status']['id'] = 'status';
+      $handler->display->display_options['filters']['status']['table'] = $base_table;
+      $handler->display->display_options['filters']['status']['field'] = 'status';
+      $handler->display->display_options['filters']['status']['value'] = '1';
+
       // Filter criterion.
       if (in_array($field_name, array('data__identifier', 'schema__name',
           'data__accession', 'rdfs__label', 'taxrank__genus',
@@ -172,22 +178,34 @@ function tripal_bundle_default_views(&$views) {
 
     // Add the default sorted column.
     if (in_array('data__identifier', $selected_fields)) {
-      $handler->display->display_options['style_options']['default'] = 'data__identifier';
+      $handler->display->display_options['sorts']['priority']['id'] = $field_name;
+      $handler->display->display_options['sorts']['priority']['table'] = $base_table;
+      $handler->display->display_options['sorts']['priority']['field'] = 'data__identifier';
     }
     else if (in_array('schema__name', $selected_fields)) {
-      $handler->display->display_options['style_options']['default'] = 'schema__name';
+      $handler->display->display_options['sorts']['priority']['id'] = $field_name;
+      $handler->display->display_options['sorts']['priority']['table'] = $base_table;
+      $handler->display->display_options['sorts']['priority']['field'] = 'schema__name';
     }
     else if (in_array('obi__organism', $selected_fields)) {
-      $handler->display->display_options['style_options']['default'] = 'obi__organism';
+      $handler->display->display_options['sorts']['priority']['id'] = $field_name;
+      $handler->display->display_options['sorts']['priority']['table'] = $base_table;
+      $handler->display->display_options['sorts']['priority']['field'] = 'obi__organism';
     }
     else if (in_array('rdfs_label', $selected_fields)) {
-      $handler->display->display_options['style_options']['default'] = 'rdfs_label';
+      $handler->display->display_options['sorts']['priority']['id'] = $field_name;
+      $handler->display->display_options['sorts']['priority']['table'] = $base_table;
+      $handler->display->display_options['sorts']['priority']['field'] = 'rdfs_label';
     }
     else if (in_array('taxrank__genus', $selected_fields)) {
-      $handler->display->display_options['style_options']['default'] = 'taxrank__genus';
+      $handler->display->display_options['sorts']['priority']['id'] = $field_name;
+      $handler->display->display_options['sorts']['priority']['table'] = $base_table;
+      $handler->display->display_options['sorts']['priority']['field'] = 'taxrank__genus';
     }
     else if (in_array('taxrank__species', $selected_fields)) {
-      $handler->display->display_options['style_options']['default'] = 'taxrank__species';
+      $handler->display->display_options['sorts']['priority']['id'] = $field_name;
+      $handler->display->display_options['sorts']['priority']['table'] = $base_table;
+      $handler->display->display_options['sorts']['priority']['field'] = 'taxrank__species';
     }
 
     // No results behavior: Global: Text area.

+ 39 - 62
tripal/tripal_views_query.inc

@@ -2,35 +2,6 @@
 
 class tripal_views_query extends views_plugin_query {
 
-  /**
-   * The EntityFieldQuery object used to perform the query
-   */
-  var $query;
-
-  /**
-   * The EntityFieldQuery object used to perform the count query.
-   * It will not include the order by and only fields that are used in
-   * filters.
-   */
-  var $cquery;
-
-  /**
-   * The fields that are to be included in the query.
-   */
-  var $fields;
-
-  /**
-   * The filters that are to be included in the query.
-   */
-  var $filters;
-
-  /**
-   * The sort item that are to be included in the query.
-   */
-  var $order;
-
-
-
   /**
    * Ensure a table exists in the queue.
    *
@@ -116,14 +87,16 @@ class tripal_views_query extends views_plugin_query {
    *   here.
    */
   public function add_where($group, $field_name, $value = NULL, $operator = NULL) {
-    $this->filters[] = array(
-      'group' => $group,
-      'field_name' => $field_name,
-      'value' => $value,
-      'op' => $operator
-    );
 
     if ($value) {
+
+      $this->filters[] = array(
+        'group' => $group,
+        'field_name' => $field_name,
+        'value' => $value,
+        'op' => $operator
+      );
+
       // Handle the bundle properties separate from real fields.
       if ($field_name == 'entity_id' or $field_name == 'status') {
         $this->query->propertyCondition($field_name, $value, $operator);
@@ -131,32 +104,32 @@ class tripal_views_query extends views_plugin_query {
         return;
       }
 
-      // 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'.");
-         }
+      // For Tripal create fields the name of the field is an encoded
+      // string that contains the bundle term, field name and any
+      // sub elements. We need to extract them.
+      $elements = explode('.', $field_name);
+      $bundle_term = array_shift($elements);
+      $field_name = array_shift($elements);
+      $element_name = implode(',', $elements);
+
+      // Get the field and instance.
+      $field = field_info_field($field_name);
+      $instance = field_info_instance('TripalEntity', $field_name, $this->query->entityConditions['bundle']['value']);
+
+      // Construct the field term.
+      $field_term = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
+
+      // Let's add add on the $field_term to the element_name and add the
+      // query condition.
+      if ($element_name) {
+        $element_name = $field_term . ',' . $element_name;
       }
       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->fieldCondition($field_name, $field_term, $value, $operator);
-        $this->cquery->fieldCondition($field_name, $field_term, $value, $operator);
+        $element_name = $field_term;
       }
+
+      $this->query->fieldCondition($field_name, $element_name, $value, $operator);
+      $this->cquery->fieldCondition($field_name, $element_name, $value, $operator);
     }
   }
 
@@ -164,12 +137,13 @@ class tripal_views_query extends views_plugin_query {
    * Overrides add_orderby().
    */
   public function add_orderby($table, $field_name = NULL, $order = 'ASC', $alias = '', $params = array()) {
-
     if ($field_name) {
-      // Make sure we don't put the orderby in more than once.
+      // If we already have an orderBy for this field then remove it so
+      // we can reset it.
       foreach ($this->order as $index => $order_details) {
         if ($order_details['field'] == $field_name) {
-          return;
+          $this->order[$index]['direction'] = $order;
+          unset($this->order[$index]);
         }
       }
       $this->order[] = array(
@@ -252,11 +226,14 @@ class tripal_views_query extends views_plugin_query {
         $this->pager->pre_execute($query);
         $num_items_per_page = $this->pager->get_items_per_page();
         $offset = $this->pager->get_current_page() * $num_items_per_page;
+        // I'm not sure why an offset would come back as -1 but it has happened
+        // with Drupal Views.  This is a quick band-aid fix.
+        $offset = ($offset < 0) ? 0 : $offset;
         $query->range($offset, $num_items_per_page);
 
         // Get the IDs
         $results = $query->execute();
-        $entity_ids = array_keys($results['TripalEntity']);
+        $entity_ids = (isset($results['TripalEntity']) AND is_array($results['TripalEntity'])) ? array_keys($results['TripalEntity']) : array();
 
         $this->pager->post_execute($view->result);
         if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {

+ 187 - 0
tripal/views_handlers/tripal_views_handler_filter_numeric.inc

@@ -0,0 +1,187 @@
+<?php
+
+class tripal_views_handler_filter_numeric extends tripal_views_handler_filter {
+
+  var $always_multiple = TRUE;
+
+  function operators() {
+    $operators = array(
+      '<' => array(
+        'title' => t('Is less than'),
+        'method' => 'op_simple',
+        'short' => t('<'),
+        'values' => 1,
+      ),
+      '<=' => array(
+        'title' => t('Is less than or equal to'),
+        'method' => 'op_simple',
+        'short' => t('<='),
+        'values' => 1,
+      ),
+      '=' => array(
+        'title' => t('Is equal to'),
+        'method' => 'op_simple',
+        'short' => t('='),
+        'values' => 1,
+      ),
+      '!=' => array(
+        'title' => t('Is not equal to'),
+        'method' => 'op_simple',
+        'short' => t('!='),
+        'values' => 1,
+      ),
+      '>=' => array(
+        'title' => t('Is greater than or equal to'),
+        'method' => 'op_simple',
+        'short' => t('>='),
+        'values' => 1,
+      ),
+      '>' => array(
+        'title' => t('Is greater than'),
+        'method' => 'op_simple',
+        'short' => t('>'),
+        'values' => 1,
+      ),
+    );
+
+    return $operators;
+  }
+
+
+  function op_simple($field) {
+    $this->query->add_where($this->options['group'], $field, $this->value, $this->operator);
+  }
+
+  function op_empty($field) {
+    if ($this->operator == 'empty') {
+      $operator = "IS NULL";
+    }
+    else {
+      $operator = "IS NOT NULL";
+    }
+
+    $this->query->add_where($this->options['group'], $field, NULL, $operator);
+  }
+
+
+
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['expose']['contains']['required'] = array('default' => FALSE, 'bool' => TRUE);
+
+    return $options;
+  }
+
+  /**
+   * Build strings from the operators() for 'select' options
+   */
+  function operator_options($which = 'title') {
+    $options = array();
+    foreach ($this->operators() as $id => $info) {
+      $options[$id] = $info[$which];
+    }
+
+    return $options;
+  }
+
+  function admin_summary() {
+    if ($this->is_a_group()) {
+      return t('grouped');
+    }
+    if (!empty($this->options['exposed'])) {
+      return t('exposed');
+    }
+
+    $options = $this->operator_options('short');
+    $output = '';
+    if (!empty($options[$this->operator])) {
+      $output = check_plain($options[$this->operator]);
+    }
+    if (in_array($this->operator, $this->operator_values(1))) {
+      $output .= ' ' . check_plain($this->value);
+    }
+    return $output;
+  }
+
+  function operator_values($values = 1) {
+    $options = array();
+    foreach ($this->operators() as $id => $info) {
+      if (isset($info['values']) && $info['values'] == $values) {
+        $options[] = $id;
+      }
+    }
+
+    return $options;
+  }
+
+  /**
+   * Provide a simple textfield for equality
+   */
+  function value_form(&$form, &$form_state) {
+
+    // We have to make some choices when creating this as an exposed
+    // filter form. For example, if the operator is locked and thus
+    // not rendered, we can't render dependencies; instead we only
+    // render the form items we need.
+    $which = 'all';
+    if (!empty($form['operator'])) {
+      $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
+    }
+    if (!empty($form_state['exposed'])) {
+      $identifier = $this->options['expose']['identifier'];
+
+      if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
+        // exposed and locked.
+        $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none';
+      }
+      else {
+        $source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
+      }
+    }
+
+    if ($which == 'all' || $which == 'value') {
+      $form['value'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Value'),
+        '#size' => 30,
+        '#default_value' => $this->value,
+      );
+      if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) {
+        $form_state['input'][$identifier] = $this->value;
+      }
+
+      if ($which == 'all') {
+        $form['value'] += array(
+          '#dependency' => array($source => $this->operator_values(1)),
+        );
+      }
+    }
+
+    if (!isset($form['value'])) {
+      // Ensure there is something in the 'value'.
+      $form['value'] = array(
+        '#type' => 'value',
+        '#value' => NULL
+      );
+    }
+  }
+
+  function operator() {
+    return $this->operator == '=' ? 'LIKE' : 'NOT LIKE';
+  }
+
+  /**
+   * Add this filter to the query.
+   */
+  function query() {
+    $this->ensure_my_table();
+    $field = "$this->table_alias.$this->real_field";
+
+    $info = $this->operators();
+    if (!empty($info[$this->operator]['method'])) {
+      $op_function = $info[$this->operator]['method'];
+      $this->{$op_function}($field);
+    }
+  }
+}

+ 2 - 6
tripal/views_handlers/tripal_views_handler_filter_string.inc

@@ -1,6 +1,6 @@
 <?php
 class tripal_views_handler_filter_string extends tripal_views_handler_filter {
-  // exposed filter options
+
   var $always_multiple = TRUE;
 
   function option_definition() {
@@ -223,14 +223,10 @@ class tripal_views_handler_filter_string extends tripal_views_handler_filter {
 
   /**
    * Add this filter to the query.
-   *
-   * Due to the nature of fapi, the value and the operator have an unintended
-   * level of indirection. You will find them in $this->operator
-   * and $this->value respectively.
    */
   function query() {
     $this->ensure_my_table();
-    $field = $this->real_field;
+    $field = "$this->table_alias.$this->real_field";
 
     $info = $this->operators();
     if (!empty($info[$this->operator]['method'])) {

+ 14 - 8
tripal_chado/api/modules/tripal_chado.cv.api.inc

@@ -1331,7 +1331,7 @@ function tripal_submit_obo_job($obo) {
   $obo['file']   = (isset($obo['file']))   ? $obo['file']   : NULL;
 
   $includes = array(
-    module_load_include('inc', 'tripal_chado', 'includes/loaders/tripal_chado.obo_loader'),
+    drupal_get_path('module', 'tripal_chado') . '/includes/tripal_chado.cv.inc',
   );
 
   if ($obo['obo_id']) {
@@ -1340,18 +1340,24 @@ function tripal_submit_obo_job($obo) {
 
     $args = array($result->obo_id);
     return tripal_add_job("Load OBO " . $result->name, 'tripal_chado',
-       "tripal_chado_load_obo_v1_2_id", $args, $user->uid, 10, $includes);
+       "tripal_cv_load_obo", $args, $user->uid, 10, $includes);
   }
   else {
     if ($obo['url']) {
-      $args = array($obo['name'], $obo['url']);
-      return tripal_add_job("Load OBO " . $obo['name'], 'tripal_chado',
-        "tripal_chado_load_obo_v1_2_url", $args, $user->uid, 10, $includes);
+      $sql = "SELECT * FROM {tripal_cv_obo} WHERE path = :url";
+      $result = db_query($sql, array(':url' => $obo['url']))->fetchObject();
+
+      $args = array($result->obo_id);
+      return tripal_add_job("Load OBO " . $result->name, 'tripal_chado',
+        "tripal_cv_load_obo", $args, $user->uid, 10, $includes);
     }
     elseif ($obo['file']) {
-      $args = array($obo['name'], $obo['file']);
-      return tripal_add_job("Load OBO " . $obo['name'], 'tripal_chado',
-        "tripal_chado_load_obo_v1_2_file", $args, $user->uid, 10, $includes);
+      $sql = "SELECT * FROM {tripal_cv_obo} WHERE path = :file";
+      $result = db_query($sql, array(':url' => $obo['file']))->fetchObject();
+
+      $args = array($result->obo_id);
+      return tripal_add_job("Load OBO " . $result->name, 'tripal_chado',
+        "tripal_cv_load_obo", $args, $user->uid, 10, $includes);
     }
   }
   return FALSE;

+ 4 - 0
tripal_chado/api/modules/tripal_chado.db.api.inc

@@ -393,6 +393,10 @@ function tripal_insert_db($values, $options = array()) {
  *    - version: (Optional) The version of the database reference
  *    - description: (Optional) A description of the database reference
  *
+ * @return
+ *   The newly inserted dbxref as an object, similar to that returned by
+ *   the chado_select_record() function.
+ *
  * @ingroup tripal_chado_api
  */
 function tripal_insert_dbxref($values) {

+ 32 - 16
tripal_chado/api/modules/tripal_chado.pub.api.inc

@@ -302,7 +302,8 @@ function tripal_autocomplete_pub($string = '') {
   ";
   $pubs = chado_query($sql, array(':str' => $string . '%'));
   while ($pub = $pubs->fetchObject()) {
-    $items[$pub->uniquename] = $pub->uniquename;
+    $val = $pub->title  . " [id:" . $pub->pub_id . "]";
+    $items[$val] = $pub->title;
   }
 
   drupal_json_output($items);
@@ -1025,21 +1026,6 @@ function tripal_get_minimal_pub_info($pub) {
   $pub = chado_expand_var($pub, 'field', 'pub.title');
   $pub = chado_expand_var($pub, 'field', 'pub.volumetitle');
 
-  // get the citation
-  $values = array(
-    'pub_id' => $pub->pub_id,
-    'type_id' => array(
-      'name' => 'Citation',
-    ),
-  );
-  $options = array(
-    'include_fk' => array(
-    ),
-  );
-  $citation = chado_generate_var('pubprop', $values, $options);
-  $citation = chado_expand_var($citation, 'field', 'pubprop.value');
-  $citation = $citation->value;
-
   // get the abstract
   $values = array(
     'pub_id' => $pub->pub_id,
@@ -1121,6 +1107,36 @@ function tripal_get_minimal_pub_info($pub) {
     $dbxrefs[] = $pub_dbxref->dbxref_id->db_id->name . ':' . $pub_dbxref->dbxref_id->accession;
   }
 
+  // get the citation
+  $values = array(
+    'pub_id' => $pub->pub_id,
+    'type_id' => array(
+      'name' => 'Citation',
+    ),
+  );
+  $options = array(
+    'include_fk' => array(
+    ),
+  );
+  $citation = chado_generate_var('pubprop', $values, $options);
+  if ($citation) {
+    $citation = chado_expand_var($citation, 'field', 'pubprop.value');
+    $citation = $citation->value;
+  }
+  else {
+    $pub_info = array(
+      'Title' => $pub->title,
+      'Publication Type' => $pub->type_id->name,
+      'Authors' => $authors_list,
+      'Series Name' => $pub->series_name,
+      'Volume' => $pub->volume,
+      'Issue' => $pub->issue,
+      'Pages' => $pub->pages,
+      'Publication Date' => $pub->pyear,
+    );
+    $citation = tripal_pub_create_citation($pub_info);
+  }
+
   return array(
     'TPUB:0000039' => $pub->title,
     'TPUB:0000003' => $citation,

+ 1 - 1
tripal_chado/api/tripal_chado.mviews.api.inc

@@ -376,8 +376,8 @@ function tripal_populate_mview($mview_id) {
       chado_set_active($previous_db);
     }
     catch (Exception $e) {
-      chado_set_active($previous_db);
       $transaction->rollback();
+      chado_set_active($previous_db);
       // print and save the error message
       $record = new stdClass();
       $record->mview_id = $mview_id;

+ 12 - 5
tripal_chado/api/tripal_chado.query.api.inc

@@ -1321,8 +1321,12 @@ function chado_select_record($table, $columns, $values, $options = NULL) {
     // Require the field be in the table description.
     if (!array_key_exists($field, $table_desc['fields'])) {
       tripal_report_error('tripal_chado', TRIPAL_ERROR,
-        'chado_select_record: The field "%field" does not exist for the table "%table".  Cannot perform query. Values: %array',
-        array('%field' => $field, '%table' => $table, '%array' => print_r($values, 1)),
+        'chado_select_record: The field "%field" does not exist in the table "%table".  Cannot perform query. Values: %array. Fields: %fields',
+        array(
+          '%field' => $field,
+          '%table' => $table,
+          '%array' => print_r($values, 1),
+          '%fields' => print_r($table_desc['fields'], 1)),
         array('print' => $print_errors)
       );
       return array();
@@ -1662,10 +1666,13 @@ function chado_query($sql, $args = array()) {
         chado_set_active($previous_db);
       }
       catch (Exception $e) {
-        chado_set_active($previous_db);
-        throw $e;
+        try {
+          chado_set_active($previous_db);
+        }
+        catch (Exception $e2) {
+          throw new Exception($e->getMessage() . $e2->getMessage());
+        }
       }
-
     }
     // for all other tables we should have everything in scope so just run the query
     else {

+ 21 - 0
tripal_chado/includes/TripalFields/ChadoField.inc

@@ -85,6 +85,27 @@ class ChadoField extends TripalField {
    */
   public function queryOrder($query, $order) {
 
+
+    // If we are here it is because the child class did not implement the
+    // queryOrder function.  So, we will do our best to make the query work.
+    $chado_table = $this->instance['settings']['chado_table'];
+    $base_table = $this->instance['settings']['base_table'];
+    $bschema = chado_get_schema($base_table);
+    $bpkey = $bschema['primary key'][0];
+    $alias = 'dbx_linker';
+    $operator = $condition['operator'];
+
+    // If the chado_table and the base_table are the same then this is easy.
+    if ($chado_table == $base_table) {
+      // Get the base table column that is associated with the term
+      // passed as $condition['column'].
+      $base_field = tripal_get_chado_semweb_column($chado_table, $order['column']);
+      $query->orderBy('base.' . $base_field, $order['direction']);
+    }
+    else {
+      // If the two are not the same then we expect that the child class
+      // will implement a query() function.
+    }
   }
 
   /**

+ 75 - 17
tripal_chado/includes/TripalFields/chado_linker__contact/chado_linker__contact.inc

@@ -61,8 +61,8 @@ class chado_linker__contact extends ChadoField {
    * @see TripalField::elements()
    */
   public function elementInfo() {
-    $field_term = $this->getFieldTermID();
 
+    $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');
@@ -76,7 +76,6 @@ class chado_linker__contact extends ChadoField {
         'elements' => array(
           $type_term => array(
             'searchable' => TRUE,
-            'name' => 'type',
             'label' => 'Contact Type',
             'help' => 'The type of contact',
             'operations' => array('eq', 'ne', 'contains', 'starts'),
@@ -84,7 +83,6 @@ class chado_linker__contact extends ChadoField {
           ),
           $name_term => array(
             'searchable' => TRUE,
-            'name' => 'name',
             'label' => 'Contact Name',
             'help' => 'The name of the contact.',
             'operations' => array('eq', 'ne', 'contains', 'starts'),
@@ -92,11 +90,10 @@ class chado_linker__contact extends ChadoField {
           ),
           $description_term => array(
             'searchable' => TRUE,
-            'name' => 'description',
             'label' => 'Contact Description',
             'help' => 'A descriptoin of the contact.',
             'operations' => array('contains'),
-            'sortable' => FALSE,
+            'sortable' => TRUE,
           ),
           'entity' => array(
             'searchable' => FALSE,
@@ -116,6 +113,7 @@ class chado_linker__contact extends ChadoField {
     $field_type = $this->field['type'];
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
 
     $type_term = tripal_get_chado_semweb_term('contact', 'type_id');
     $name_term = tripal_get_chado_semweb_term('contact', 'name');
@@ -123,7 +121,6 @@ class chado_linker__contact extends ChadoField {
 
     // Get the FK that links to the base record.
     $schema = chado_get_schema($field_table);
-    $base_table = $entity->chado_table;
     $pkey = $schema['primary key'][0];
     if (!isset($schema['foreign keys'][$base_table]['columns'])) {
       return;
@@ -184,28 +181,89 @@ class chado_linker__contact extends ChadoField {
    * @see ChadoField::query()
    */
   public function query($query, $condition) {
-    $contact_linker = $this->instance['settings']['chado_table'];
-    $base_table = $this->instance['settings']['base_table'];
-    $bschema = chado_get_schema($base_table);
-    $bpkey = $bschema['primary key'][0];
-    $alias = 'contact_linker';
+    $alias = $this->field['field_name'];
     $operator = $condition['operator'];
 
-    $type_term = tripal_get_chado_semweb_term('contact', 'type');
+    $field_term_id = $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');
 
-    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);
+    $contact_linker = $this->instance['settings']['chado_table'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($contact_linker);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    // Join the contact linker table and then join the contact table.
+    $calias = $contact_linker . '_contact';
+    $this->queryJoinOnce($query, $contact_linker, $alias, "base.$fkey_rcolumn = $alias.$fkey_lcolumn");
+    $this->queryJoinOnce($query, 'contact', $calias, "$calias.contact_id = $alias.contact_id");
+
+    // Search by the contact name
+    if ($condition['column'] == $field_term_id or
+        $condition['column'] == $field_term_id . ',' . $name_term) {
+      $query->condition("$calias.name", $condition['value'], $operator);
+    }
+    // Search on the contact description.
+    if ($condition['column'] == $field_term_id . ',' . $description_term) {
+      $query->condition("$calias.description", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'contact.type') {
+    // Search on the contact type.
+    if ($condition['column'] == $field_term_id . ',' . $type_term) {
+      $talias = $contact_linker . '_contact_type';
+      $this->queryJoinOnce($query, 'cvterm', $talias, "$calias.type_id = $talias.cvterm_id");
+      $query->condition("$talias.name", $condition['value'], $operator);
+    }
+  }
 
+  /**
+   * @see ChadoField::queryOrder()
+   */
+  public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
+    $field_term_id = $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');
+
+    $contact_linker = $this->instance['settings']['chado_table'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($contact_linker);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    // Join the contact linker table and then join the contact table.
+    $calias = $contact_linker . '_contact';
+    $this->queryJoinOnce($query, $contact_linker, $alias, "base.$fkey_rcolumn = $alias.$fkey_lcolumn", "LEFT OUTER");
+    $this->queryJoinOnce($query, 'contact', $calias, "$calias.contact_id = $alias.contact_id", "LEFT OUTER");
+
+    // Search by the contact name
+    if ($order['column'] == $field_term_id or
+        $order['column'] == $field_term_id . ',' . $name_term) {
+      $query->orderBy("$calias.name", $order['direction']);
+    }
+    // Search on the contact description.
+    if ($order['column'] == $field_term_id . ',' . $description_term) {
+      $query->orderBy("$calias.description", $order['direction']);
+    }
+    // Search on the contact type.
+    if ($order['column'] == $field_term_id . ',' . $type_term) {
+      $talias = $contact_linker . '_contact_type';
+      $this->queryJoinOnce($query, 'cvterm', $talias, "$calias.type_id = $talias.cvterm_id", "LEFT OUTER");
+      $query->orderBy("$talias.name", $order['direction']);
     }
   }
 }
 
+
+
 /**
  * An Ajax callback for the pub widget.
  */

+ 31 - 34
tripal_chado/includes/TripalFields/chado_linker__contact/chado_linker__contact_widget.inc

@@ -17,39 +17,38 @@ class chado_linker__contact_widget extends ChadoFieldWidget {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $base_table = $this->instance['settings']['base_table'];
-    $chado_table = $this->instance['settings']['chado_table'];
+    $field_table = $this->instance['settings']['chado_table'];
     $chado_column = $this->instance['settings']['chado_column'];
     $instance = $this->instance;
 
     // Get the FK column that links to the base table.
-    $schema = chado_get_schema($chado_table);
+    $schema = chado_get_schema($field_table);
     $pkey = $schema['primary key'][0];
-    $lfkey_field = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
 
     // Get the field defaults.
     $record_id = '';
-    $fk_value = array_key_exists('#entity', $element) and $element['#entity'] ? $element['#entity']->chado_record_id : NULL;
+    $fk_value = (array_key_exists('#entity', $element) and $element['#entity']) ? $element['#entity']->chado_record_id : NULL;
     $contact_id = '';
     $name = '';
     $value = '';
 
+    $name_term = tripal_get_chado_semweb_term('contact', 'name');
 
     // If the field already has a value then it will come through the $items
     // array.  This happens when editing an existing record.
-    if (count($items) > 0) {
-      // Check for element values that correspond to fields in the Chado table.
-      $fk_value = tripal_get_field_item_keyval($items, 0, 'chado-' . $chado_table . '__' . $lfkey_field, $fk_value);
-      if (array_key_exists($delta, $items)) {
-        $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $chado_table . '__' . $pkey, $record_id);
-        $contact_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $chado_table . '__contact_id', $contact_id);
-      }
+    if (count($items) > 0 and array_key_exists($delta, $items)) {
+      $name = array_key_exists($name_term, $items[$delta]['value']) ? $items[$delta]['value'][$name_term] : $name;
+      $fk_value = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__' . $fkey_lcolumn, $fk_value);
+      $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__' . $pkey, $record_id);
+      $contact_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__contact_id', $contact_id);
     }
 
     // Check $form_state['values'] to see if an AJAX call set the values.
-    if (array_key_exists('values', $form_state) and array_key_exists($delta, $form_state['values'])) {
-      $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $pkey];
-      $fk_value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $lfkey_field];
-      $contact_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__contact_id'];
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
+      $name = $form_state['values'][$field_name]['und'][$delta]['name'];
     }
 
 
@@ -60,15 +59,15 @@ class chado_linker__contact_widget extends ChadoFieldWidget {
       '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
     );
 
-    $widget['chado-' . $chado_table . '__' . $pkey] = array(
+    $widget['chado-' . $field_table . '__' . $pkey] = array(
       '#type' => 'value',
       '#default_value' => $record_id,
     );
-    $widget['chado-' . $chado_table . '__' . $lfkey_field] = array(
+    $widget['chado-' . $field_table . '__' . $fkey_lcolumn] = array(
       '#type' => 'value',
       '#default_value' => $fk_value,
     );
-    $widget['chado-' . $chado_table . '__contact_id'] = array(
+    $widget['chado-' . $field_table . '__contact_id'] = array(
       '#type' => 'value',
       '#default_value' => $contact_id,
     );
@@ -78,12 +77,6 @@ class chado_linker__contact_widget extends ChadoFieldWidget {
       '#title' => t('Contact'),
       '#default_value' => $name,
       '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/contact',
-      '#ajax' => array(
-        'callback' => "chado_linker__contact_widget_form_ajax_callback",
-        'wrapper' => "$chado_table-$delta",
-        'effect' => 'fade',
-        'method' => 'replace'
-      ),
       '#maxlength' => 100000,
     );
   }
@@ -93,31 +86,35 @@ class chado_linker__contact_widget extends ChadoFieldWidget {
    *
    * @see TripalFieldWidget::submit()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $base_table = $this->instance['settings']['base_table'];
-    $chado_table = $this->instance['settings']['chado_table'];
+    $field_table = $this->instance['settings']['chado_table'];
     $chado_column = $this->instance['settings']['chado_column'];
     $instance = $this->instance;
-    $schema = chado_get_schema($chado_table);
 
+    // Get information about this contact linke rtable.
+    $schema = chado_get_schema($field_table);
     $pkey = $schema['primary key'][0];
     $lfkey_field = key($schema['foreign keys'][$base_table]['columns']);
 
-    // Get the field values.
-    $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $pkey];
-    $fk_value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $lfkey_field];
-    $contact_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__contact_id'];
+    // Get the name from the form state.
     $name = $form_state['values'][$field_name]['und'][$delta]['name'];
 
     // If the user provided a name then we want to set the foreign key
     // value to be the chado_record_id
     if ($name) {
       $contact = chado_generate_var('contact', array('name' => $name));
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $chado_table . '__contact_id'] = $contact->contact_id;
-      $form_state['values'][$field_name][$langcode][$delta]['value'] = $name;
+      if ($contact) {
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__contact_id'] = $contact->contact_id;
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $name;
+      }
+    }
+    // If no name is provided then we want to set the field for deletion.
+    else {
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $lfkey_field] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__contact_id'] = '';
     }
-    dpm($form_state['values']);
   }
 }

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

@@ -1,178 +0,0 @@
-<?php
-
-class chado_linker__cvterm extends ChadoField {
-
-
-  // --------------------------------------------------------------------------
-  //                     EDITABLE STATIC CONSTANTS
-  //
-  // The following constants SHOULD be set for each descendent class.  They are
-  // used by the static functions to provide information to Drupal about
-  // the field and it's default widget and formatter.
-  // --------------------------------------------------------------------------
-
-  // The default lable for this field.
-  public static $default_label = 'Chado Annotation';
-
-  // The default description for this field.
-  public static $description = 'This record can be annotated with terms from other
-              vocabularies.';
-
-  // Provide a list of instance specific settings. These can be access within
-  // the instanceSettingsForm.  When the instanceSettingsForm is submitted
-  // then Drupal with automatically change these settings for the instnace.
-  // It is recommended to put settings at the instance level whenever possible.
-  // If you override this variable in a child class be sure to replicate the
-  // term_name, term_vocab, term_accession and term_fixed keys as these are
-  // required for all TripalFields.
-  public static $default_instance_settings  = array(
-    // The short name for the vocabulary (e.g. shcema, SO, GO, PATO, etc.).
-    'term_vocabulary' => 'local',
-    // The name of the term.
-    'term_name' => 'cvterm',
-    // The unique ID (i.e. accession) of the term.
-    'term_accession' => 'cvterm',
-    // Set to TRUE if the site admin is allowed to change the term
-    // type. This will create form elements when editing the field instance
-    // to allow the site admin to change the term settings above.
-    'term_fixed' => FALSE,
-  );
-
-  // The default widget for this field.
-  public static $default_widget = 'chado_linker__cvterm_widget';
-
-  // The default formatter for this field.
-  public static $default_formatter = 'chado_linker__cvterm_formatter';
-
-  // A boolean specifying that users should not be allowed to create
-  // fields and instances of this field type through the UI. Such
-  // fields can only be created programmatically with field_create_field()
-  // and field_create_instance().
-  public static $no_ui = FALSE;
-
-
-
-  /**
-   *
-   * @see TripalField::load()
-   */
-  public function load($entity) {
-    $field_name = $this->field['field_name'];
-    $field_type = $this->field['type'];
-    $field_table = $this->instance['settings']['chado_table'];
-    $field_column = $this->instance['settings']['chado_column'];
-    $base_table = $this->instance['settings']['base_table'];
-
-    $vocabulary = $this->instance['settings']['term_vocabulary'];
-    $accession = $this->instance['settings']['term_accession'];
-    $cvterm = tripal_get_cvterm(array(
-      'dbxref_id' => array(
-        'db_id' => array(
-          'name' => $vocabulary,
-        ),
-        'accession' => $accession,
-      ),
-    ));
-    $cvterm_id = $cvterm->cvterm_id;
-
-    // Get the FK that links to the base record.
-    $schema = chado_get_schema($field_table);
-    $pkey = $schema['primary key'][0];
-    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
-    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
-
-    // Set some defaults for the empty record.
-    $chado_record = $entity->chado_record;
-    $entity->{$field_name}['und'][0] = array(
-      'value' => '',
-      'chado-' . $field_table . '__' . $fkey_lcolumn => '',
-      'chado-' . $field_table . '__' . 'cvterm_id' => '',
-      // The pub column is present in the cell_line_cvterm, feature_cvterm,
-      // library_cvterm, phenotype_comparision_cvterm, phenotype_cvterm,
-      // stock_cvterm, and stock_relationship_cvterm.
-      'chado-' . $field_table . '__' . 'pub_id' => '',
-      // The is_not column is present in feature_cvterm and stock_cvterm tables.
-      'chado-' . $field_table . '__' . 'is_not' => '',
-      // The rank column is present in the cell_line_cvterm, expression_cvterm,
-      // feature_cvterm, phenotype_comparision_cvterm, phenotype_cvterm,
-      // and stock_cvterm tables.
-      'chado-' . $field_table . '__' . 'rank' => '',
-      // The cvterm_type_id is present in the expression_cvterm table.
-      'cvterm_type_id' => '',
-      // The following field are to help link the cvterm.
-      'cv__cv_id' => '',
-      'cvterm__name' => '',
-    );
-
-    // Get the annotations associated with this base record for this fields type.
-    $columns = array('*');
-    $match = array(
-      $fkey_lcolumn => $chado_record->$fkey_rcolumn,
-      'cvterm_id' => $cvterm_id,
-    );
-    $options = array(
-      'return_array' => TRUE,
-      'order_by' => array('rank' => 'ASC')
-    );
-    $fcvterms = chado_select_record($field_table, $columns, $match, $options);
-    for ($i = 0; $i < count($fcvterms); $i++) {
-      $linker = $fcvterms[$i];
-      $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $linker->cvterm_id));
-      $entity->{$field_name}['und'][$i] = array(
-        'value' => $linker->$pkey,
-        'chado-' . $field_table . '__' . $fkey_lcolumn => $linker->$fkey_lcolumn,
-        'chado-' . $field_table . '__' . 'cvterm_id' => $linker->cvterm_id,
-        'chado-' . $field_table . '__' . 'pub_id' => property_exists($linker, 'pub_id') ? $linker->pub_id : '',
-        'chado-' . $field_table . '__' . 'is_not' => property_exists($linker, 'is_not') ? $linker->is_not : '',
-        'chado-' . $field_table . '__' . 'rank' => property_exists($linker, 'rank') ? $linker->rank : '',
-        'chado-' . $field_table . '__' . 'cvterm_type_id' => property_exists($linker, 'cvterm_type_id') ? $linker->cvterm_type_id : '',
-        'cv__cv_id' => $cvterm->cv_id->cv_id,
-        'cvterm__name' => $cvterm->name,
-      );
-    }
-  }
-}
-
-/**
- * Theme function for the dbxref_id_widget.
- *
- * @param $variables
- */
-function theme_chado_linker__cvterm_widget($variables) {
-  $element = $variables['element'];
-
-  // These two fields were added to the widget to help identify the fields
-  // for layout.
-  $table_name = $element['#table_name'];
-  $fkey = $element['#fkey_field'];
-
-  $layout = "
-    <div class=\"annotation-cvterm-widget\">
-      <div class=\"annotation-cvterm-widget-item\">" .
-      drupal_render($element['cv__cv_id']) . "
-      </div>
-      <div class=\"annotation-cvterm-widget-item\">" .
-      drupal_render($element['cvterm__name']) . "
-      </div>
-      <div class=\"annotation-cvterm-widget-item\">" .
-      drupal_render($element['pub']) . "
-      </div>
-      <div class=\"annotation-cvterm-widget-item\">" .
-      drupal_render($element['chado-' . $table_name . '__is_not']) . "
-      </div>
-    </div>
-  ";
-  return $layout;
-}
-
-/**
- * An Ajax callback for the dbxref widget.
- */
-function chado_linker__cvterm_widget_form_ajax_callback($form, $form_state) {
-
-  $field_name = $form_state['triggering_element']['#parents'][0];
-  $delta = $form_state['triggering_element']['#parents'][2];
-
-
-  return $form[$field_name]['und'][$delta];
-}

+ 0 - 132
tripal_chado/includes/TripalFields/chado_linker__cvterm/chado_linker__cvterm_formatter.inc

@@ -1,132 +0,0 @@
-<?php
-
-class chado_linker__cvterm_formatter extends ChadoFieldFormatter {
-  // The default lable for this field.
-  public static $default_label = 'Chado Annotation';
-
-  // The list of field types for which this formatter is appropriate.
-  public static $field_types = array('chado_linker__cvterm');
-
-
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-    $headers = array('Term', 'Definition', 'Is Not', 'Reference');
-    $rows = array();
-
-    $chado_table = $this->instance['settings']['chado_table'];
-    foreach ($items as $delta => $item) {
-      if ($item['chado-' . $chado_table . '__cvterm_id']) {
-        $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $item['chado-' . $chado_table . '__cvterm_id']));
-        $dbxref = $cvterm->dbxref_id;
-
-        // Build the accession.
-        $accession = $dbxref->db_id->name . ':' . $dbxref->accession;
-        if ($dbxref->db_id->urlprefix) {
-          $accession = l($accession, tripal_get_dbxref_url($dbxref), array('attributes' => array('target' => '_blank')));
-        }
-
-        // Build the publication reference.
-        $pub_ref = '';
-        $pub_id = $item['chado-' . $chado_table . '__pub_id'];
-        if ($pub_id) {
-          $pub = chado_generate_var('pub', array('pub_id' => $pub_id));
-          $pub_ref = $pub->title;
-        }
-        $rows[] = array(
-          $accession,
-          $cvterm->definition,
-          $item['chado-' . $chado_table . '__is_not'] ? 'Yes' : '',
-          '',
-        );
-      }
-    }
-
-    // the $table array contains the headers and rows array as well as other
-    // options for controlling the display of the table.  Additional
-    // documentation can be found here:
-    // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
-    $table = array(
-      'header' => $headers,
-      'rows' => $rows,
-      'attributes' => array(
-        'id' => "$chado_table-table-terms",
-        'class' => 'tripal-data-table'
-      ),
-      'caption' => '',
-      'sticky' => FALSE,
-      'colgroups' => array(),
-      'empty' => 'There are no annotations of this type',
-    );
-
-    if (count($items) > 0) {
-      $element[0] = array(
-        '#type' => 'markup',
-        '#markup' => theme_table($table),
-      );
-    }
-  }
-
-  /**
-   *
-   * @see TripalFieldFormatter::view()
-   */
-  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
-    $headers = array('Term', 'Definition', 'Is Not', 'Publication');
-    $rows = array();
-
-    $chado_table = $this->instance['settings']['chado_table'];
-    foreach ($items as $delta => $item) {
-      if ($item['chado-' . $chado_table . '__cvterm_id']) {
-        $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $item['chado-' . $chado_table . '__cvterm_id']));
-        $dbxref = $cvterm->dbxref_id;
-
-        // Build the accession.
-        $accession = $dbxref->db_id->name . ':' . $dbxref->accession;
-        if ($dbxref->db_id->urlprefix) {
-          $accession = l($accession, tripal_get_dbxref_url($dbxref), array('attributes' => array('target' => '_blank')));
-        }
-
-        // Build the publication reference.
-        $pub_ref = '';
-        $pub_id = $item['chado-' . $chado_table . '__pub_id'];
-        if ($pub_id) {
-          $pub = chado_generate_var('pub', array('pub_id' => $pub_id));
-          $pub_ref = $pub->uniquename;
-        }
-        $rows[] = array(
-          $accession,
-          $cvterm->definition,
-          $item['chado-' . $chado_table . '__is_not'] ? 'Yes' : '',
-          $pub_ref,
-        );
-      }
-    }
-
-    // the $table array contains the headers and rows array as well as other
-    // options for controlling the display of the table.  Additional
-    // documentation can be found here:
-    // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
-    $table = array(
-      'header' => $headers,
-      'rows' => $rows,
-      'attributes' => array(
-        'id' => "$chado_table-table-terms",
-        'class' => 'tripal-data-table'
-      ),
-      'caption' => '',
-      'sticky' => FALSE,
-      'colgroups' => array(),
-      'empty' => 'There are no annotations of this type',
-    );
-
-    if (count($items) > 0) {
-      $element[0] = array(
-        '#type' => 'markup',
-        '#markup' => theme_table($table),
-      );
-    }
-  }
-}

+ 0 - 176
tripal_chado/includes/TripalFields/chado_linker__cvterm/chado_linker__cvterm_widget.inc

@@ -1,176 +0,0 @@
-<?php
-
-class chado_linker__cvterm_widget extends ChadoFieldWidget {
-  // The default lable for this field.
-  public static $default_label = 'Chado Annotation';
-
-  // The list of field types for which this formatter is appropriate.
-  public static $field_types = array('chado_linker__cvterm');
-
-  /**
-   *
-   * @see TripalFieldWidget::form()
-   */
-  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
-    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
-    $field_name = $this->field['field_name'];
-
-    $matches = array();
-    preg_match('/(.*?)__(\d+)/', $field_name, $matches);
-    // If the field name is not properly formatted then we can't tell what
-    // table and type this is.  So just return.
-    if (count($matches) != 3) {
-      return $widget;
-    }
-    $table_name = $matches[1];
-    $cv_id = $matches[2];
-
-    // Get the FK column that links to the base table.
-    $chado_table = $this->instance['settings']['chado_table'];
-    $base_table = $this->instance['settings']['base_table'];
-    $schema = chado_get_schema($chado_table);
-    $pkey = $schema['primary key'][0];
-    $fkeys = array_values($schema['foreign keys'][$base_table]['columns']);
-    $fkey = $fkeys[0];
-
-    // Get the field defaults.
-    $record_id = '';
-    $fkey_value = $element['#entity']->chado_record_id;
-    $cvterm_name = '';
-    $cvterm_id = '';
-    $pub_id = '';
-    $uname = '';
-    $is_not = '';
-    $cvterm = NULL;
-
-    // If the field already has a value then it will come through the $items
-    // array.  This happens when editing an existing record.
-    if (array_key_exists($delta, $items)) {
-      $record_id = $items[$delta]['value'];
-      $cvterm_name = $items[$delta]['cvterm__name'];
-      $pub_id =$items[$delta]['chado-' . $table_name . '__pub_id'];
-      if ($pub_id && $pub_id != 1) {
-        $pub = chado_generate_var('pub', array('pub_id' => $pub_id));
-        $uname = $pub->uniquename;
-      }
-      $is_not = $items[$delta]['chado-' . $table_name . '__is_not'];
-      $cvterm_id = $items[$delta]['chado-' . $table_name . '__cvterm_id'];
-    }
-
-    // Check $form_state['values'] to see if an AJAX call set the values.
-    if (array_key_exists('values', $form_state) and array_key_exists($delta, $form_state['values'])) {
-      // See example in chado_linker_contact.inc
-      //       $record_id = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name);
-      //       $fkey_value = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__' . $fkey);
-      //       $is_not = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '__is_not');
-      //       $cvterm_name = tripal_chado_get_field_form_values($table_name, $form_state, $delta, $table_name . '--cvterm__name');
-    }
-
-    if ($cvterm_name) {
-      $cvterm = chado_generate_var('cvterm', array('cv_id' => $cv_id, 'name' => $cvterm_name));
-    }
-
-    $schema = chado_get_schema('cvterm');
-    $options = tripal_get_cv_select_options();
-
-    $widget['#table_name'] = $chado_table;
-    $widget['#fkey_field'] = $fkey;
-    $widget['#prefix'] =  "<span id='$table_name-$delta'>";
-    $widget['#suffix'] =  "</span>";
-
-    $widget['value'] = array(
-      '#type' => 'value',
-      '#value' => key_exists($delta, $items) ? $items[$delta]['value'] : '',
-    );
-
-    $widget['chado-' . $table_name . '__' . $pkey] = array(
-      '#type' => 'value',
-      '#default_value' => $record_id,
-    );
-    $widget['cv__cv_id'] = array(
-      '#type' => 'value',
-      '#default_value' => $cv_id,
-    );
-    $widget['chado-' . $table_name . '__cvterm_id'] = array(
-      '#type' => 'value',
-      '#default_value' => $cvterm ? $cvterm->cvterm_id : '',
-    );
-    $widget['chado-' . $table_name . '__' . $fkey] = array(
-      '#type' => 'value',
-      '#default_value' => $fkey_value,
-    );
-
-    $widget['cvterm__name'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Term Name'),
-      '#default_value' => $cvterm_name,
-      '#required' => $element['#required'],
-      '#maxlength' => array_key_exists('length', $schema['fields']['name']) ? $schema['fields']['name']['length'] : 255,
-      '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/cvterm/' . $cv_id,
-      '#size' => 30
-    );
-
-    $widget['pub'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Publication'),
-      '#default_value' => $uname,
-      '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/pub',
-      '#ajax' => array(
-        'callback' => "schema__publication_widget_form_ajax_callback",
-        'wrapper' => "$table_name-$delta",
-        'effect' => 'fade',
-        'method' => 'replace'
-      ),
-      '#maxlength' => 100000,
-    );
-
-    $widget['chado-' . $table_name . '__pub_id'] = array(
-      '#type' => 'value',
-      '#default_value' => $pub_id ? $pub_id : 1,
-    );
-
-    $widget['chado-' . $table_name . '__is_not'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Is Not'),
-      '#default_value' => $is_not,
-      '#required' => $element['#required'],
-    );
-
-    $widget['cvterm__definition'] = array(
-      '#type' => 'item',
-      '#markup' => '',
-    );
-  }
-
-  /**
-   *
-   * @see TripalFieldWidget::submit()
-   */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
-    $field_name = $this->field['field_name'];
-    $table_name = $this->instance['settings']['chado_table'];
-    $schema = chado_get_schema($table_name);
-    $pkey = $schema['primary key'][0];
-    $base_table = $this->instance['settings']['base_table'];
-    $lfkey_field = key($schema['foreign keys'][$base_table]['columns']);
-    $rfkey_field = $schema['foreign keys'][$base_table]['columns'][$lfkey_field];
-
-
-    // If the form ID is field_ui_field_edit_form, then the user is editing the
-    // field's values in the manage fields form of Drupal.  We don't want
-    // to validate it as if it were being used in a data entry form.
-    if ($form_state['build_info']['form_id'] =='field_ui_field_edit_form') {
-      return;
-    }
-
-    // If the user provided a cv_id and a name then we want to set the
-    // foreign key value to be the chado_record_idd
-    $cvterm_name = isset($form_state['values'][$field_name][$langcode][$delta]['cvterm__name']) ? $form_state['values'][$field_name][$langcode][$delta]['cvterm__name'] : '';
-
-    if (!$cvterm_name) {
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__cvterm_id'] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $lfkey_field] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id'] = '';
-    }
-  }
-}

+ 23 - 0
tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop.inc

@@ -41,6 +41,8 @@ class chado_linker__prop extends ChadoField {
     'chado_column' => '',
     // The base table.
     'base_table' => '',
+    // The number of default rows to show. The default is 1.
+    'rows' => 1,
   );
 
   // The default widget for this field.
@@ -130,6 +132,7 @@ class chado_linker__prop extends ChadoField {
   public function query($query, $condition) {
     $prop_linker = $this->instance['settings']['chado_table'];
     $base_table = $this->instance['settings']['base_table'];
+
     $bschema = chado_get_schema($base_table);
     $bpkey = $bschema['primary key'][0];
     $alias = $this->field['field_name'];
@@ -145,4 +148,24 @@ class chado_linker__prop extends ChadoField {
     $query->condition("$alias.value", $condition['value'], $operator);
 
   }
+
+  /**
+   * @see ChadoField::query()
+   */
+  public function queryOrder($query, $order) {
+    $prop_linker = $this->instance['settings']['chado_table'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    $bschema = chado_get_schema($base_table);
+    $bpkey = $bschema['primary key'][0];
+    $alias = $this->field['field_name'];
+
+    $vocab = $this->instance['settings']['term_vocabulary'];
+    $accession = $this->instance['settings']['term_accession'];
+
+    $cvterm = tripal_get_cvterm(array('id' => $vocab . ':' . $accession));
+
+    $this->queryJoinOnce($query, $prop_linker, $alias, "base.$bpkey = $alias.$bpkey AND $alias.type_id = $cvterm->cvterm_id", "LEFT OUTER");
+    $query->orderBy("$alias.value", $order['direction']);
+  }
 }

+ 38 - 28
tripal_chado/includes/TripalFields/chado_linker__prop/chado_linker__prop_widget.inc

@@ -17,13 +17,14 @@ class chado_linker__prop_widget extends ChadoFieldWidget {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $base_table = $this->instance['settings']['base_table'];
-    $chado_table = $this->instance['settings']['chado_table'];
+    $field_table = $this->instance['settings']['chado_table'];
     $chado_column = $this->instance['settings']['chado_column'];
+    $rows = array_key_exists('rows', $this->instance['settings']) ? $this->instance['settings']['rows'] : 1;
     $instance = $this->instance;
 
     // Get the name of the pkey field for this property table and the name
     // of the FK field that links to the base table.
-    $schema = chado_get_schema($chado_table);
+    $schema = chado_get_schema($field_table);
     $pkey = $schema['primary key'][0];
     $lfkey_field = key($schema['foreign keys'][$base_table]['columns']);
     $rfkey_field = $schema['foreign keys'][$base_table]['columns'][$lfkey_field];
@@ -39,22 +40,23 @@ class chado_linker__prop_widget extends ChadoFieldWidget {
     // array.  This happens when editing an existing record.
     if (count($items) > 0) {
       // Check for element values that correspond to fields in the Chado table.
-      $fk_value = tripal_get_field_item_keyval($items, 0, 'chado-' . $chado_table . '__' . $lfkey_field, $fk_value);
-      $type_id = tripal_get_field_item_keyval($items, 0, 'chado-' . $chado_table . '__type_id', $type_id);
+      $fk_value = tripal_get_field_item_keyval($items, 0, 'chado-' . $field_table . '__' . $lfkey_field, $fk_value);
+      $type_id = tripal_get_field_item_keyval($items, 0, 'chado-' . $field_table . '__type_id', $type_id);
       if (array_key_exists($delta, $items)) {
-        $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $chado_table . '__' . $pkey, $record_id);
-        $value = tripal_get_field_item_keyval($items, $delta, 'chado-' . $chado_table . '__value', $value);
-        $rank = tripal_get_field_item_keyval($items, $delta, 'chado-' . $chado_table . '__rank', $rank);
+        $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__' . $pkey, $record_id);
+        $value = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__value', $value);
+        $rank = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__rank', $rank);
       }
     }
 
     // Check $form_state['values'] to see if an AJAX call set the values.
-    if (array_key_exists('values', $form_state) and array_key_exists($delta, $form_state['values'])) {
-      $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $pkey];
-      $fk_value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $lfkey_field];
-      $type_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__type_id'];
-      $value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__value'];
-      $rank = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__rank'];
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
+      $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $pkey];
+      $fk_value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $lfkey_field];
+      $type_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__type_id'];
+      $value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__value'];
+      $rank = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__rank'];
     }
 
     // Use default value for the field if it's not already set
@@ -80,25 +82,27 @@ class chado_linker__prop_widget extends ChadoFieldWidget {
       '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
     );
 
-    $widget['chado-' . $chado_table . '__' . $pkey] = array(
+    $widget['chado-' . $field_table . '__' . $pkey] = array(
       '#type' => 'hidden',
       '#default_value' => $record_id,
     );
-    $widget['chado-' . $chado_table . '__' . $lfkey_field] = array(
+    $widget['chado-' . $field_table . '__' . $lfkey_field] = array(
       '#type' => 'hidden',
       '#value' => $fk_value,
     );
-    $widget['chado-' . $chado_table . '__value'] = array(
+    $widget['chado-' . $field_table . '__value'] = array(
       '#type' => 'textarea',
       '#default_value' => $value,
       '#title' => $instance['label'],
       '#description' => $instance['description'],
+      '#rows' => $rows,
+      '#required' => $instance['required'],
     );
-    $widget['chado-' . $chado_table . '__type_id'] = array(
+    $widget['chado-' . $field_table . '__type_id'] = array(
       '#type' => 'hidden',
       '#value' => $type_id,
     );
-    $widget['chado-' . $chado_table . '__rank'] = array(
+    $widget['chado-' . $field_table . '__rank'] = array(
       '#type' => 'hidden',
       '#value' => $rank,
     );
@@ -108,26 +112,32 @@ class chado_linker__prop_widget extends ChadoFieldWidget {
    *
    * @see TripalFieldWidget::submit()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
     $field_name = $this->field['field_name'];
-    $chado_table = $this->instance['settings']['chado_table'];
-    $schema = chado_get_schema($chado_table);
+    $field_type = $this->field['type'];
+    $base_table = $this->instance['settings']['base_table'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $chado_column = $this->instance['settings']['chado_column'];
+    $instance = $this->instance;
+
+    $schema = chado_get_schema($field_table);
     $pkey = $schema['primary key'][0];
     $base_table = $this->instance['settings']['base_table'];
     $lfkey_field = key($schema['foreign keys'][$base_table]['columns']);
     $rfkey_field = $schema['foreign keys'][$base_table]['columns'][$lfkey_field];
 
-    $value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__value'];
+    $value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__value'];
     $form_state['values'][$field_name]['und'][$delta]['value'] = $value;
 
     // If the user removed the property then we want to clear out the other
-    // fields so there is no insert.
+    // fields except the pkey value. If the pkey field is present and the value
+    // is present then the chado storage backend will delete the record.
     if (!$value) {
-      $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $pkey] = '';
-      $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__' . $lfkey_field] = '';
-      $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__type_id'] = '';
-      $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__value'] = '';
-      $form_state['values'][$field_name]['und'][$delta]['chado-' . $chado_table . '__rank'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['value'] = 'delete_me';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $lfkey_field] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__type_id'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__value'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__rank'] = '';
     }
   }
 }

+ 29 - 17
tripal_chado/includes/TripalFields/data__accession/data__accession.inc

@@ -61,8 +61,6 @@ class data__accession extends ChadoField {
       'chado-' . $field_table . '__' . $field_column => '',
       'db_id' => '',
       'accession' => '',
-      'version' => '',
-      'description' => '',
     );
 
     // Get the primary dbxref record (if it's not NULL).  Because we have a
@@ -73,10 +71,8 @@ class data__accession extends ChadoField {
       $entity->{$field_name}['und'][0] = array(
         'value' => $dbxref->accession,
         'chado-' . $field_table . '__' . $field_column => $record->$field_column->$field_column,
-        'db_id'       => $dbxref->db_id->db_id,
-        'accession'   => $dbxref->accession,
-        'version'     => $dbxref->version,
-        'description' => $dbxref->description,
+        'db_id' => $dbxref->db_id->db_id,
+        'accession' => $dbxref->accession,
       );
     }
   }
@@ -85,25 +81,44 @@ class data__accession extends ChadoField {
    * @see ChadoField::query()
    */
   public function query($query, $condition) {
-    $chado_table = $this->instance['settings']['chado_table'];
-    $base_table = $this->instance['settings']['base_table'];
-    $bschema = chado_get_schema($base_table);
-    $bpkey = $bschema['primary key'][0];
-    $alias = 'dbx_linker';
+    $alias = $this->field['field_name'];
     $operator = $condition['operator'];
 
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    $field_term_id = $this->getFieldTermID();
+    $accession_term = tripal_get_chado_semweb_term($field_table, $field_column);
+
     // 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'] == $accession_term) {
       $this->queryJoinOnce($query, 'dbxref', 'DBX', "DBX.dbxref_id = base.dbxref_id");
       $query->condition("DBX.accession", $condition['value'], $operator);
     }
   }
 
+  public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
+    $operator = $condition['operator'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    $field_term_id = $this->getFieldTermID();
+    $accession_term = tripal_get_chado_semweb_term($field_table, $field_column);
+
+    // We don't offer any sub elements so the value coming in should
+    // always be the field_name.
+    if ($order['column'] == $accession_term) {
+      $this->queryJoinOnce($query, 'dbxref', 'DBX', "DBX.dbxref_id = base.dbxref_id", "LEFT OUTER");
+      $query->orderBy("DBX.accession", $order['direction']);
+    }
+  }
+
   /**
    * @see TripalField::validate()
    */
-  public function validate($entity_type, $entity, $field, $items, &$errors) {
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
 
     $field_name = $this->field['field_name'];
     $settings = $this->field['settings'];
@@ -113,11 +128,8 @@ class data__accession extends ChadoField {
 
     // Get the field values.
     foreach ($items as $delta => $values) {
-      $fk_val = $values['chado-' . $field_table . '__' . $field_column];
       $db_id = $values['db_id'];
       $accession = $values['accession'];
-      $version = $values['version'];
-      $description = $values['description'];
 
       // Make sure that if a database ID is provided that an accession is also
       // provided.  Here we use the form_set_error function rather than the
@@ -138,7 +150,7 @@ class data__accession extends ChadoField {
           'error' => 'chado_base__dbxref',
         );
       }
-      if (!$db_id and !$accession and ($version or $description)) {
+      if (!$db_id and !$accession) {
         $errors[$field_name][$delta]['und'][] = array(
           'message' => t("A database and the accession must both be provided for the primary cross reference."),
           'error' => 'chado_base__dbxref',

+ 58 - 73
tripal_chado/includes/TripalFields/data__accession/data__accession_widget.inc

@@ -18,29 +18,24 @@ class data__accession_widget extends ChadoFieldWidget {
     $field_column = $this->instance['settings']['chado_column'];
 
     // Get the field defaults.
-    $fk_val = '';
+    $dbxref_id = '';
     $db_id = '';
     $accession = '';
-    $version = '';
-    $description = '';
 
     // If the field already has a value then it will come through the $items
     // array.  This happens when editing an existing record.
     if (count($items) > 0 and array_key_exists($delta, $items)) {
-      $fk_val = $items[$delta]['chado-' . $field_table . '__' . $field_column];
+      $dbxref_id = $items[$delta]['chado-' . $field_table . '__' . $field_column];
       $db_id = $items[$delta]['db_id'];
       $accession = $items[$delta]['accession'];
-      $version = $items[$delta]['version'];
-      $description = $items[$delta]['description'];
     }
 
     // Check $form_state['values'] to see if an AJAX call set the values.
-    if (array_key_exists('values', $form_state)) {
-      $fk_val = isset($form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $field_column]) ? $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $field_column] : '';
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
+      $dbxref_id = isset($form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $field_column]) ? $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $field_column] : '';
       $db_id = isset($form_state['values'][$field_name]['und'][$delta]['db_id']) ? $form_state['values'][$field_name]['und'][$delta]['db_id'] : '';
       $accession = isset($form_state['values'][$field_name]['und'][$delta]['accession']) ? $form_state['values'][$field_name]['und'][$delta]['accession'] : '';
-      $version = isset($form_state['values'][$field_name]['und'][$delta]['version']) ? $form_state['values'][$field_name]['und'][$delta]['version'] : '';
-      $description = isset($form_state['values'][$field_name]['und'][$delta]['description']) ? $form_state['values'][$field_name]['und'][$delta]['description'] : '';
     }
 
     $schema = chado_get_schema('dbxref');
@@ -52,12 +47,12 @@ class data__accession_widget extends ChadoFieldWidget {
 
     $widget['value'] = array(
       '#type' => 'value',
-      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+      '#value' => $dbxref_id,
     );
 
     $widget['chado-' . $field_table . '__' . $field_column] = array(
       '#type' => 'value',
-      '#default_value' => $fk_val,
+      '#default_value' => $dbxref_id,
     );
 
     $widget['db_id'] = array(
@@ -81,39 +76,14 @@ class data__accession_widget extends ChadoFieldWidget {
       '#maxlength' => array_key_exists('length', $schema['fields']['accession']) ? $schema['fields']['accession']['length'] : 255,
       '#size' => 15,
       '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/dbxref/' . $db_id,
-      '#ajax' => array(
-        'callback' => "tripal_chado_dbxref_widget_form_ajax_callback",
-        'wrapper' => "$field_name-dbxref--db-id",
-        'effect' => 'fade',
-        'method' => 'replace'
-      ),
       '#disabled' => $db_id ? FALSE : TRUE,
     );
-    $widget['version'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Version'),
-      '#default_value' => $version,
-      '#maxlength' => array_key_exists('length', $schema['fields']['version']) ? $schema['fields']['version']['length'] : 255,
-      '#size' => 5,
-      '#disabled' => $db_id ? FALSE : TRUE,
-    );
-    $widget['description'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Description'),
-      '#default_value' => $description,
-      '#size' => 20,
-      '#disabled' => $db_id ? FALSE : TRUE,
-    );
-    $widget['links'] = array(
-      '#type' => 'item',
-      '#markup' => l('Add a new database', 'admin/tripal/loaders/chado_db/add', array('attributes' => array('target' => '_blank')))
-    );
   }
 
   /**
    * @see TripalFieldWidget::submit()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
     $field_name = $this->field['field_name'];
     $settings = $this->field['settings'];
     $field_name = $this->field['field_name'];
@@ -121,26 +91,55 @@ class data__accession_widget extends ChadoFieldWidget {
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
-    $fk_val = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__' . $field_column]) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__' . $field_column] : '';
-    $db_id = isset($form_state['values'][$field_name][$langcode][$delta]['db_id']) ? $form_state['values'][$field_name][$langcode][$delta]['db_id'] : '';
-    $accession = isset($form_state['values'][$field_name][$langcode][$delta]['accession']) ? $form_state['values'][$field_name][$langcode][$delta]['accession'] : '';
-    if (!$accession) {
-      $accession = $form_state['values'][$field_name][$langcode][$delta]['value'];
-    }
-    $version = isset($form_state['values'][$field_name][$langcode][$delta]['version']) ? $form_state['values'][$field_name][$langcode][$delta]['version'] : '';
+    $dbxref_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'];
+    $db_id = $form_state['values'][$field_name]['und'][$delta]['db_id'];
+    $accession = $form_state['values'][$field_name]['und'][$delta]['accession'];
 
     // If user did not select a database, we want to remove dbxref_id from the
-    // field.
+    // field. We use '__NULL__' because this field is part of the base table
+    // and this tells the Chado backend to insert a null rather than an empty
+    // string.
     if (!$db_id) {
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__dbxref_id'] = '__NULL__';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'] = '__NULL__';
     }
     // If the dbxref_id does not match the db_id + accession then the user
     // has selected a new dbxref record and we need to update the hidden
     // value accordingly.
     if ($db_id and $accession) {
       $dbxref = chado_generate_var('dbxref', array('db_id' => $db_id, 'accession' => $accession));
-      if ($dbxref and $dbxref->dbxref_id != $fk_val) {
-        $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__dbxref_id'] = $dbxref->dbxref_id;
+      if ($dbxref and $dbxref->dbxref_id != $dbxref_id) {
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'] = $dbxref->dbxref_id;
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $dbxref->dbxref_id;
+      }
+    }
+  }
+
+  /**
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $settings = $this->field['settings'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    $dbxref_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'];
+    $db_id = $form_state['values'][$field_name]['und'][$delta]['db_id'];
+    $accession = $form_state['values'][$field_name]['und'][$delta]['accession'];
+
+    // If the accession doesn't exist then add it.
+    if ($db_id and $accession) {
+      $dbxref = chado_generate_var('dbxref', array('db_id' => $db_id, 'accession' => $accession));
+      if (!$dbxref) {
+        $values = array(
+          'db_id' => $db_id,
+          'accession' => $accession,
+        );
+        $dbxref = tripal_insert_dbxref($values);
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'] = $dbxref->dbxref_id;
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $dbxref->dbxref_id;
       }
     }
   }
@@ -157,13 +156,6 @@ class data__accession_widget extends ChadoFieldWidget {
         <div class=\"primary-dbxref-widget-item\">" .
           drupal_render($element['accession']) . "
         </div>
-        <div class=\"primary-dbxref-widget-item\">" .
-          drupal_render($element['version']) . "
-        </div>
-        <div class=\"primary-dbxref-widget-item\">" .
-          drupal_render($element['description']) . "
-        </div>
-        <div class=\"primary-dbxref-widget-links\">" . drupal_render($element['links']) . "</div>
       </div>
     ";
 
@@ -172,7 +164,6 @@ class data__accession_widget extends ChadoFieldWidget {
       '#value' => '',
       '#description' => $element['#description'],
       '#children' => $layout,
-      //    '#attributes' => array('class' => $classes),
     );
 
     return theme('fieldset', array('element' => $fieldset));
@@ -186,23 +177,17 @@ function data__accession_widget_form_ajax_callback($form, $form_state) {
 
   $instance = $form['#instance'];
   $field_name = $form_state['triggering_element']['#parents'][0];
-  $field = field_info_field($field_name);
-  $field_type = $field['type'];
-  $field_table = $instance['settings']['chado_table'];
-  $field_column = $instance['settings']['chado_column'];
-  $field_prefix = 'chado-' . $field_table . '__' . $field_column;
-
-  //   $db_id = tripal_chado_get_field_form_values($field_name, $form_state, 0, $field_prefix . '--db_id');
-  //   $accession = tripal_chado_get_field_form_values($field_name, $form_state, 0, $field_prefix . '--accession');
+  $dbxref_id = $form_state['input'][$field_name]['und'][0]['chado-' . $field_table . '__dbxref_id'];
+  $db_id = $form_state['input'][$field_name]['und'][0]['db_id'];
+  $accession = $form_state['input'][$field_name]['und'][0]['accession'];
+
+  // If we don't have a match then this must be new accession. Because
+  // this is a database defined access we will automatically add the
+  // accession.
   if ($db_id and $accession) {
-    $values = array(
-      'db_id' => $db_id,
-      'accession' => $accession,
-    );
-    $options = array('is_duplicate' => TRUE);
-    $has_duplicate = chado_select_record('dbxref', array('*'), $values, $options);
-    if (!$has_duplicate) {
-      drupal_set_message('The selected cross reference is new and will be added for future auto completions.', 'warning');
+    $dbxref = chado_generate_var('dbxref', array('db_id' => $db_id, 'accession' => $accession));
+    if (!$dbxref) {
+      drupal_set_message('The accession provided does not exist in the database and will be added.', 'warning');
     }
   }
 

+ 11 - 4
tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence.inc

@@ -49,13 +49,15 @@ class data__protein_sequence extends ChadoField {
    */
   public function elementInfo() {
     $field_term = $this->getFieldTermID();
-    return array(
+    $info = array(
       $field_term => array(
-        'operations' => array(),
+        'label' => 'Protein sequence',
+        'help' => 'The polypeptide sequence derived from mRNA',
         'sortable' => FALSE,
         'searchable' => FALSE,
       ),
     );
+    return $info;
   }
   /**
    * @see TripalField::load()
@@ -85,9 +87,14 @@ class data__protein_sequence extends ChadoField {
     ";
     $proteins = chado_query($sql, array(':feature_id' => $feature->feature_id));
     while ($protein = $proteins->fetchObject()) {
-      if ($protein->residues) {
-        $entity->{$field_name}['und'][$num_seqs++]['value'] = $protein->residues;
+      $entity->{$field_name}['und'][$num_seqs]['value'] = $protein->residues;
+      // Because we'll be saving a feature we need to maintain all of it's
+      // columns in the feature table. The following will add them all.
+      $columns = get_object_vars($protein);
+      foreach ($columns as $colname => $value) {
+        $entity->{$field_name}['und'][$num_seqs]['chado-feature__' . $colname] = $value;
       }
+      $num_seqs++;
     }
   }
 }

+ 1 - 8
tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence_formatter.inc

@@ -7,15 +7,8 @@ class data__protein_sequence_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('data__protein_sequence');
 
-
   /**
-   *
-   * @param unknown $element
-   * @param unknown $entity_type
-   * @param unknown $entity
-   * @param unknown $langcode
-   * @param unknown $items
-   * @param unknown $display
+   * @see TripalFieldFormatter::view()
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
     $content = 'There is no protein sequence.';

+ 4 - 10
tripal_chado/includes/TripalFields/data__protein_sequence/data__protein_sequence_widget.inc

@@ -14,15 +14,9 @@ class data__protein_sequence_widget extends ChadoFieldWidget {
   public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
     parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
 
-    // TODO: add the form for setting a protein sequence.
-  }
-
-  /**
-   *
-   * @see TripalFieldWidget::submit()
-   */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
-    $field_name = $this->field['field_name'];
-
+    // TODO: before we can create a widget we must have a way to
+    // save another record whose table matches the same as the base base
+    // table with the chado_storage backend getting confused as to what value
+    // belongs to which feature record.
   }
 }

+ 15 - 3
tripal_chado/includes/TripalFields/data__sequence/data__sequence_widget.inc

@@ -31,6 +31,11 @@ class data__sequence_widget extends ChadoFieldWidget {
     }
 
     $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => $residues,
+    );
+
+    $widget['chado-' . $field_table . '__' . $field_column] = array(
       '#type' => 'textarea',
       '#title' => $element['#title'],
       '#description' => $element['#description'],
@@ -46,14 +51,21 @@ class data__sequence_widget extends ChadoFieldWidget {
    *
    * @see TripalFieldWidget::submit()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
     $field_name = $this->field['field_name'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
 
     // Remove any white spaces.
-    $residues = isset($form_state['values'][$field_name][$langcode][$delta]['value']) ? $form_state['values'][$field_name][$langcode][$delta]['value'] : '';
+    $residues = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $field_column];
     if ($residues) {
       $residues = preg_replace('/\s/', '', $residues);
-      $form_state['values'][$field_name][$langcode][$delta]['value'] = $residues;
+      $form_state['values'][$field_name]['und'][$delta]['value'] = $residues;
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $field_column] = $residues;
+    }
+    else {
+      $form_state['values'][$field_name]['und'][$delta]['value'] = 'delete_me';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $field_column] = '';
     }
   }
 }

+ 9 - 9
tripal_chado/includes/TripalFields/data__sequence_checksum/data__sequence_checksum_widget.inc

@@ -30,7 +30,7 @@ class data__sequence_checksum_widget extends ChadoFieldWidget {
       '#type' => 'value',
       '#value' => $md5checksum,
     );
-    $widget['chado-feature__md5checksum'] = array(
+    $widget['chado-' . $field_table . '__md5checksum'] = array(
       '#type' => 'value',
       '#value' => $md5checksum,
     );
@@ -40,8 +40,8 @@ class data__sequence_checksum_widget extends ChadoFieldWidget {
    *
    * @see TripalFieldWidget::submit()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
-    $field = $this->field;
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+  $field = $this->field;
     $settings = $field['settings'];
     $field_name = $field['field_name'];
     $field_type = $field['type'];
@@ -49,17 +49,17 @@ class data__sequence_checksum_widget extends ChadoFieldWidget {
     $field_column = $this->instance['settings']['chado_column'];
 
     // Get the residues so we can calculate the length.
-    $residues = isset($form_state['values']['data__sequence'][$langcode][0]['chado-feature__residues']) ? $form_state['values']['data__sequence'][$langcode][0]['chado-feature__residues'] : '';
-
-    if ($residues) {
+    if ($form_state['values']['data__sequence']['und'][0]['chado-feature__residues']){
       // Remove spaces and new lines from the residues string.
+      $residues =  $form_state['values']['data__sequence']['und'][0]['chado-feature__residues'];
       $residues = preg_replace('/\s/', '', $residues);
-      $form_state['values']['data__sequence'][$langcode][0]['chado-feature__residues'] = $residues;
-      $form_state['values'][$field_name][$langcode][$delta]['chado-feature__md5checksum'] = md5($residues);
+      $checksum = md5($residues);
+      $form_state['values'][$field_name]['und'][0]['chado-feature__md5checksum'] = $checksum;
+      $form_state['values'][$field_name]['und'][0]['value'] = $checksum;
     }
     else {
       // Otherwise, remove the md5 value
-      $form_state['values'][$field_name][$langcode][$delta]['chado-feature__md5checksum'] = '__NULL__';
+      $form_state['values'][$field_name]['und'][0]['chado-feature__md5checksum'] = '__NULL__';
     }
   }
 }

+ 106 - 30
tripal_chado/includes/TripalFields/data__sequence_coordinates/data__sequence_coordinates.inc

@@ -60,61 +60,136 @@ class data__sequence_coordinates extends ChadoField {
    */
   public function elementInfo() {
     $field_term = $this->getFieldTermID();
+
+    $reference_term = 'data:3002';
+    $fmin_term = tripal_get_chado_semweb_term('featureloc', 'fmin');
+    $fmax_term = tripal_get_chado_semweb_term('featureloc', 'fmax');
+    $strand_term = tripal_get_chado_semweb_term('featureloc', 'strand');
+    $phase_term = tripal_get_chado_semweb_term('featureloc', 'phase');
+
     return array(
       $field_term => array(
         'operations' => array(),
         'sortable' => FALSE,
         'searchable' => FALSE,
+        'label' => 'Location Coordinates',
+        'help' => 'The locations on other genomic sequences where this record has been aligned.',
         'elements' => array(
-          'data:3002' => array(
+          $reference_term => 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(
+          $fmin_term => 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(
+          $fmax_term => 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(
+          $phase_term => 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(
+          $strand_term => 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,
+            'sortable' => FALSE,
           ),
         ),
       ),
     );
   }
 
+  /**
+   * @see ChadoField::query()
+   */
+  public function query($query, $condition) {
+    $alias = $this->field['field_name'];
+    $operator = $condition['operator'];
+
+    $field_term_id = $this->getFieldTermID();
+    $reference_term = $field_term_id . ',' . 'data:3002';
+    $fmin_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'fmin');
+    $fmax_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'fmax');
+    $strand_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'strand');
+    $phase_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'phase');
+
+    // Join to the organism table for this field.
+    $this->queryJoinOnce($query, 'featureloc', $alias, "base.feature_id = $alias.feature_id");
+    if ($condition['column'] == $reference_term) {
+      $salias = $alias . '_src';
+      $this->queryJoinOnce($query, 'feature', $salias, "$alias.srcfeature_id = $salias.feature_id");
+      $query->condition("$salias.name", $condition['value'], $operator);
+    }
+    if ($condition['column'] == $strand_term) {
+      $strand = '';
+      if ($condition['value'] == '-') {
+        $strand = -1;
+      }
+      if ($condition['value'] == '+') {
+        $strand = 1;
+      }
+      $query->condition("$alias.strand", $strand, $operator);
+    }
+    if ($condition['column'] == $fmin_term) {
+      $query->condition("$alias.fmin", $condition['value'] - 1, $operator);
+    }
+    if ($condition['column'] == $fmax_term) {
+      $query->condition("$alias.fmax", $condition['value'], $operator);
+    }
+    if ($condition['column'] == $phase_term) {
+      $query->condition("$alias.phase", $condition['value'], $operator);
+    }
+  }
+
+  /**
+   * @see ChadoField::queryOrder()
+   */
+  public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
+
+    $field_term_id = $this->getFieldTermID();
+    $reference_term = $field_term_id . ',' . 'data:3002';
+    $fmin_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'fmin');
+    $fmax_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'fmax');
+    $strand_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'strand');
+    $phase_term = $field_term_id . ',' . tripal_get_chado_semweb_term('featureloc', 'phase');
+
+    $this->queryJoinOnce($query, 'featureloc', $alias, "base.feature_id = $alias.feature_id", "LEFT OUTER");
+    if ($order['column'] == $reference_term) {
+      $salias = $alias . '_src';
+      $this->queryJoinOnce($query, 'feature', $salias, "$alias.srcfeature_id = $salias.feature_id", "LEFT OUTER");
+      $query->orderBy("$salias.name", $order['direction']);
+    }
+    if ($order['column'] == $fmin_term) {
+      $query->orderBy("$alias.fmin", $order['direction']);
+    }
+    if ($order['column'] == $fmax_term) {
+      $query->orderBy("$alias.fmax", $order['direction']);
+    }
+    if ($order['column'] == $phase_term) {
+      $query->orderBy("$alias.phase", $order['direction']);
+    }
+  }
+
   /**
    *
    * @see TripalField::load()
@@ -124,6 +199,12 @@ class data__sequence_coordinates extends ChadoField {
     $feature = $entity->chado_record;
     $num_seqs = 0;
 
+    $reference_term = 'data:3002';
+    $fmin_term = tripal_get_chado_semweb_term('featureloc', 'fmin');
+    $fmax_term = tripal_get_chado_semweb_term('featureloc', 'fmax');
+    $strand_term = tripal_get_chado_semweb_term('featureloc', 'strand');
+    $phase_term = tripal_get_chado_semweb_term('featureloc', 'phase');
+
     $options = array(
       'return_array' => TRUE,
       'order_by' => array('rank' => 'ASC'),
@@ -132,19 +213,7 @@ class data__sequence_coordinates extends ChadoField {
 
     // Set some defauls for the empty record
     $entity->{$field_name}['und'][0] = array(
-      'value' => array(
-        /*
-        // Types of elements that will appear in the value array.
-        // Annotation track
-        'data:3002' => '',
-        'local:fmin' => '',
-        'local:fmax' => '',
-        // phase
-        'data:2336' => '',
-        // strand
-        'data:0853' => '',
-        */
-      ),
+      'value' => '',
     );
 
     // Get the featureloc records that this feature is aligned to.
@@ -152,13 +221,20 @@ class data__sequence_coordinates extends ChadoField {
     if ($aligned) {
       foreach ($aligned as $index => $featureloc) {
         $srcfeature = $featureloc->srcfeature_id->name;
+        $strand = '';
+        if ($featureloc->strand == 1) {
+          $strand = '+';
+        }
+        else {
+          $strand = '-';
+        }
         $entity->{$field_name}['und'][0] = array(
           'value' => array(
-            'data:3002' => $srcfeature,
-            'local:fmin' => $featureloc->fmin + 1,
-            'local:fmax' => $featureloc->fmax,
-            'data:2336' => $featureloc->phase,
-            'data:0853' => $featureloc->strand,
+            $reference_term => $srcfeature,
+            $fmin_term => $featureloc->fmin + 1,
+            $fmax_term => $featureloc->fmax,
+            $strand_term => $strand,
+            $phase_term => $featureloc->phase,
           ),
         );
         $sentity_id = chado_get_record_entity_by_table('feature_id', $featureloc->srcfeature_id->feature_id);

+ 11 - 18
tripal_chado/includes/TripalFields/data__sequence_coordinates/data__sequence_coordinates_formatter.inc

@@ -7,13 +7,6 @@ class data__sequence_coordinates_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('data__sequence_coordinates');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
 
   /**
    *
@@ -21,20 +14,20 @@ class data__sequence_coordinates_formatter extends ChadoFieldFormatter {
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
 
+    $reference_term = 'data:3002';
+    $fmin_term = tripal_get_chado_semweb_term('featureloc', 'fmin');
+    $fmax_term = tripal_get_chado_semweb_term('featureloc', 'fmax');
+    $strand_term = tripal_get_chado_semweb_term('featureloc', 'strand');
+    $phase_term = tripal_get_chado_semweb_term('featureloc', 'phase');
+
     $content = '';
     foreach ($items as $item) {
       if (!empty($item['value'])) {
-        $srcfeature = $item['value']['data:3002'];
-        $fmin = $item['value']['local:fmin'];
-        $fmax = $item['value']['local:fmax'];
-        $phase = $item['value']['data:2336'];
-        $strand = $item['value']['data:0853'];
-        if ($strand == 1) {
-          $strand = '+';
-        }
-        else {
-          $strand = '-';
-        }
+        $srcfeature = $item['value'][$reference_term];
+        $fmin = $item['value'][$fmin_term];
+        $fmax = $item['value'][$fmax_term];
+        $phase = $item['value'][$phase_term];
+        $strand = $item['value'][$strand_term];
         $content .= $srcfeature . ':' . $fmin . '..' . $fmax . $strand;
       }
     }

+ 0 - 7
tripal_chado/includes/TripalFields/data__sequence_length/data__sequence_length_formatter.inc

@@ -7,13 +7,6 @@ class data__sequence_length_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('data__sequence_length');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
 
   /**
    *

+ 9 - 7
tripal_chado/includes/TripalFields/data__sequence_length/data__sequence_length_widget.inc

@@ -43,16 +43,18 @@ class data__sequence_length_widget extends ChadoFieldWidget {
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
-    // Get the residues so we can calculate teh length.
-    $residues = $form_state['values']['data__sequence']['und'][0]['value'];
-    // Remove any white spaces.
-    if ($residues) {
+    // Get the residues so we can calculate the length.
+    if ($form_state['values']['data__sequence']['und'][0]['chado-feature__residues']){
+      $residues =  $form_state['values']['data__sequence']['und'][0]['chado-feature__residues'];
+      // Remove spaces and new lines from the residues string.
       $residues = preg_replace('/\s/', '', $residues);
-      $form_state['values']['data__sequence']['und'][0]['value'] = $residues;
-      $form_state['values']['data__sequence_length']['und'][0]['chado-feature__seqlen'] = strlen($residues);
+      $length = strlen($residues);
+      $form_state['values'][$field_name]['und'][0]['chado-feature__seqlen'] = $length;
+      $form_state['values'][$field_name]['und'][0]['value'] = $length;
     }
     else {
-      $form_state['values']['data__sequence_length']['und'][0]['chado-feature__seqlen'] = '__NULL__';
+      // Otherwise, remove the md5 value
+      $form_state['values'][$field_name]['und'][0]['chado-feature__seqlen'] = '__NULL__';
     }
   }
 }

+ 0 - 7
tripal_chado/includes/TripalFields/go__gene_expression/go__gene_expression_formatter.inc

@@ -7,13 +7,6 @@ class go__gene_expression_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('go__gene_expression');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
 
   /**
    *

+ 37 - 8
tripal_chado/includes/TripalFields/local__source_data/local__source_data.inc

@@ -73,26 +73,20 @@ class local__source_data extends ChadoField {
         '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',
+            'searchable' => TRUE,
             '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,
+            'sortable' => TRUE,
           ),
           $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,
           ),
         ),
@@ -100,6 +94,41 @@ class local__source_data extends ChadoField {
     );
   }
 
+  /**
+   * @see ChadoField::query()
+   */
+  public function query($query, $condition) {
+    $operator = $condition['operator'];
+
+    $field_term_id = $this->getFieldTermID();
+    $sourcename_term = $field_term_id . ',' . tripal_get_chado_semweb_term('analysis', 'sourcename');
+    $sourceversion_term = $field_term_id . ',' . tripal_get_chado_semweb_term('analysis', 'sourceversion');
+    $sourceuri_term = $field_term_id . ',' . tripal_get_chado_semweb_term('analysis', 'sourceuri');
+
+    if ($condition['column'] == $sourcename_term) {
+      $query->condition("base.sourcename", $condition['value'], $operator);
+    }
+    if ($condition['column'] == $sourceversion_term) {
+      $query->condition("base.sourceversion", $condition['value'], $operator);
+    }
+  }
+  /**
+   * @see ChadoField::queryOrder()
+   */
+  public function queryOrder($query, $order) {
+    $field_term_id = $this->getFieldTermID();
+    $sourcename_term = $field_term_id . ',' . tripal_get_chado_semweb_term('analysis', 'sourcename');
+    $sourceversion_term = $field_term_id . ',' . tripal_get_chado_semweb_term('analysis', 'sourceversion');
+    $sourceuri_term = $field_term_id . ',' . tripal_get_chado_semweb_term('analysis', 'sourceuri');
+
+    if ($order['column'] == $sourcename_term) {
+      $query->orderBy("base.sourcename", $order['direction']);
+    }
+    if ($order['column'] == $sourceversion_term) {
+      $query->orderBy("base.sourceversion", $order['direction']);
+    }
+  }
+
   /**
    *
    * @see TripalField::load()

+ 7 - 3
tripal_chado/includes/TripalFields/local__source_data/local__source_data_formatter.inc

@@ -13,16 +13,20 @@ class local__source_data_formatter extends ChadoFieldFormatter {
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
 
+    $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');
+
     $content = 'The data source is not provided.';
     if ($items[0]['value']) {
       $content = "<dl class=\"tripal-dl\">";
-      if (!empty($items[0]['value']['schema:name'])) {
+      if (!empty($items[0]['value'][$sourcename_term])) {
         $content .= "<dt>Source Name</dt><dd>: " . $items[0]['value']['schema:name'] . " </dd>";
       }
-      if (!empty($items[0]['value']['IAO:0000129'])) {
+      if (!empty($items[0]['value'][$sourceversion_term])) {
         $content .= "<dt>Source Version</dt><dd>: " . $items[0]['value']['IAO:0000129'] . " </dd>";
       }
-      if (!empty($items[0]['value']['data:1047'])) {
+      if (!empty($items[0]['value'][$sourceuri_term])) {
         $content .= "<dt>Source URI</dt><dd>: " . l($items[0]['value']['data:1047'], $items[0]['value']['data:1047'], array('attributes' => array('target' => '_blank'))) . " </dd>";
       }
       $content .= "</dl>";

+ 40 - 15
tripal_chado/includes/TripalFields/local__source_data/local__source_data_widget.inc

@@ -36,15 +36,28 @@ class local__source_data_widget extends ChadoFieldWidget {
     // submit).
     if (array_key_exists('values', $form_state) and
         array_key_exists($field_name, $form_state['values'])) {
-      $sourcename = $form_state['values'][$field_name][$langcode][$delta]['chado-analysis__sourcename'];
-      $sourceversion = $form_state['values'][$field_name][$langcode][$delta]['chado-analysis__sourceversion'];
-      $sourceuri = $form_state['values'][$field_name][$langcode][$delta]['chado-analysis__sourceuri'];
+      $sourcename = $form_state['values'][$field_name]['und'][$delta]['source_data']['sourcename'];
+      $sourceversion = $form_state['values'][$field_name]['und'][$delta]['source_data']['sourceversion'];
+      $sourceuri = $form_state['values'][$field_name]['und'][$delta]['source_data']['sourceuri'];
     }
 
     $widget['value'] = array(
       '#type' => 'value',
       '#value' => $sourcename,
     );
+    $widget['chado-analysis__sourcename'] = array(
+      '#type' => 'value',
+      '#value' => $sourcename,
+    );
+    $widget['chado-analysis__sourceversion'] = array(
+      '#type' => 'value',
+      '#value' => $sourceversion,
+    );
+    $widget['chado-analysis__sourceuri'] = array(
+      '#type' => 'value',
+      '#value' => $sourceuri,
+    );
+
     $widget['source_data'] = array(
       '#type' => 'fieldset',
       '#title' => $this->instance['label'],
@@ -52,30 +65,29 @@ class local__source_data_widget extends ChadoFieldWidget {
       '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
       '#delta' => $delta,
     );
-    $widget['source_data']['chado-analysis__sourcename'] = array(
+    $widget['source_data']['sourcename'] = array(
       '#type' => 'textfield',
       '#title' => 'Data Source Name',
       '#description' => 'The name of the source where data was obtained for this analysis.',
       '#default_value' => $sourcename,
       '#required' => TRUE,
     );
-    $widget['source_data']['chado-analysis__sourceversion'] = array(
+    $widget['source_data']['sourceversion'] = array(
       '#type' => 'textfield',
       '#title' => 'Data Source Version',
       '#description' => 'The version number of the data source (if applicable).',
       '#default_value' => $sourceversion,
     );
-    $widget['source_data']['chado-analysis__sourceuri'] = array(
+    $widget['source_data']['sourceuri'] = array(
       '#type' => 'textfield',
       '#title' => 'Data Source URI',
       '#description' => 'The URI (e.g. web URL) where the source data can be retreived.',
       '#default_value' => $sourceuri,
     );
+
   }
-  /**
-   * @see TripalFieldWidget::submit()
-   */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $table_name = $this->instance['settings']['chado_table'];
@@ -83,10 +95,23 @@ class local__source_data_widget extends ChadoFieldWidget {
     $field_column = $this->instance['settings']['chado_column'];
     $base_table = $this->instance['settings']['base_table'];
 
-    // Using a fieldset in our widget above throws off the structure
-    // of the values array.  So we need to reorganize it a bit:
-    $value = $form_state['values'][$field_name][$langcode][$delta]['value'];
-    $form_state['values'][$field_name][$langcode][$delta] = $form_state['values'][$field_name][$langcode][$delta]['source_data'];
-    $form_state['values'][$field_name][$langcode][$delta]['value'] = $value;
+    $source_name = $form_state['values'][$field_name]['und'][$delta]['source_data']['sourcename'];
+    $sourceversion = $form_state['values'][$field_name]['und'][$delta]['source_data']['sourceversion'];
+    $sourceuri = $form_state['values'][$field_name]['und'][$delta]['source_data']['sourceuri'];
+
+    // Set the value so the form element won't be ignored.
+    $form_state['values'][$field_name]['und'][$delta]['value'] = $source_name;
+
+    if (!$sourceversion) {
+      $sourceversion = '__NULL__';
+    }
+    if (!$sourceuri) {
+      $sourceuri = '__NULL__';
+    }
+
+    // And set the values for our Chado insert/update
+    $form_state['values'][$field_name]['und'][$delta]['chado-analysis__sourcename'] = $source_name;
+    $form_state['values'][$field_name]['und'][$delta]['chado-analysis__sourceversion'] = $sourceversion;
+    $form_state['values'][$field_name]['und'][$delta]['chado-analysis__sourceuri'] = $sourceuri;
   }
 }

+ 2 - 9
tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc

@@ -40,7 +40,7 @@ class obi__organism extends ChadoField {
   /**
    * @see TripalField::validate()
    */
-  public function validate($entity_type, $entity, $field, $items, &$errors) {
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
 
     $settings = $this->field['settings'];
     $field_name = $this->field['field_name'];
@@ -84,14 +84,7 @@ class obi__organism extends ChadoField {
 
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
-      'value' => array(
-        /*
-        // Types of elements that will appear in the value array.
-        $label_term => '',
-        $genus_term => '',
-        $species_term => '',
-        */
-      ),
+      'value' => array(),
     );
 
     if ($record) {

+ 2 - 2
tripal_chado/includes/TripalFields/obi__organism/obi__organism_widget.inc

@@ -53,7 +53,7 @@ class obi__organism_widget extends ChadoFieldWidget {
     $field_table = $this->instance['settings']['chado_table'];
 
     // Make sure the value is set to the organism_id
-    $organism_id = $form_state['values'][$field_name][$langcode][0]['chado-' . $field_table . '__organism_id'];
-    $form_state['values'][$field_name][$langcode][0]['value'] = $organism_id;
+    $organism_id = $form_state['values'][$field_name]['und'][0]['chado-' . $field_table . '__organism_id'];
+    $form_state['values'][$field_name]['und'][0]['value'] = $organism_id;
   }
 }

+ 0 - 8
tripal_chado/includes/TripalFields/ogi__location_on_map/ogi__location_on_map_formatter.inc

@@ -7,14 +7,6 @@ class ogi__location_on_map_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('ogi__location_on_map');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()

+ 58 - 43
tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference.inc

@@ -62,36 +62,33 @@ class sbo__database_cross_reference extends ChadoField {
    */
   public function elementInfo() {
 
+    $field_term = $this->getFieldTermID();
     $dbname_term = tripal_get_chado_semweb_term('db', 'name');
     $accession_term = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $dburl_term = tripal_get_chado_semweb_term('db', 'url');
 
-    $field_term = $this->getFieldTermID();
     return array(
       $field_term => array(
         'operations' => array(),
+        'label' => 'Cross Reference',
         'sortable' => FALSE,
         'searchable' => FALSE,
         'elements' => array(
           $dbname_term => array(
             'searchable' => TRUE,
-            'name' => 'db_name',
-            'label' => 'Database Name',
+            'label' => 'Cross Reference 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',
+            'label' => 'Cross Reference 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(
+          $dburl_term => array(
             'searchable' => FALSE,
-            'name' => 'URL',
-            'label' => 'Database URL',
+            'label' => 'Cross Reference Database URL',
             'help' => 'The URL of the database that houses the cross reference.',
             'sortable' => FALSE,
           ),
@@ -117,25 +114,18 @@ class sbo__database_cross_reference extends ChadoField {
     $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
     $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
 
+    $dbname_term = tripal_get_chado_semweb_term('db', 'name');
+    $accession_term = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $dburl_term = tripal_get_chado_semweb_term('db', 'url');
+
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
-      'value' => array(
-        /* sub elements that may be present in the values array
-        // Database name
-        'data:1048' => '',
-        // Accession
-        'data:2091' => '',
-        'schema:url' => '',
-        */
-      ),
+      'value' => '',
       'chado-' . $field_table . '__' . $pkey => '',
-      'chado-' . $field_table . '__' . $fkey_lcolumn => '',
+      'chado-' . $field_table . '__' . $fkey_lcolumn => $record->$fkey_rcolumn,
       'chado-' . $field_table . '__dbxref_id' => '',
-      'dbxref_id' => '',
       'db_id' => '',
       'accession' => '',
-      'version' => '',
-      'description' => '',
     );
 
     $linker_table = $base_table . '_dbxref';
@@ -155,18 +145,15 @@ class sbo__database_cross_reference extends ChadoField {
         $URL = tripal_get_dbxref_url($dbxref);
         $entity->{$field_name}['und'][$i] = array(
           'value' => array(
-            'data:1048' => $dbxref->db_id->name,
-            'data:2091' => $dbxref->accession,
-            'schema:url' => $URL,
+            $dbname_term => $dbxref->db_id->name,
+            $accession_term => $dbxref->accession,
+            $dburl_term => $URL,
           ),
           'chado-' . $field_table . '__' . $pkey => $linker->$pkey,
           'chado-' . $field_table . '__' . $fkey_lcolumn => $linker->$fkey_lcolumn->$fkey_lcolumn,
           'chado-' . $field_table . '__dbxref_id' => $dbxref->dbxref_id,
-          'dbxref_id' => $dbxref->dbxref_id,
           'db_id' => $dbxref->db_id->db_id,
           'accession' => $dbxref->accession,
-          'version' => $dbxref->version,
-          'description' => $dbxref->description,
         );
         $i++;
       }
@@ -179,21 +166,28 @@ class sbo__database_cross_reference extends ChadoField {
   public function query($query, $condition) {
     $dbxref_linker = $this->instance['settings']['chado_table'];
     $base_table = $this->instance['settings']['base_table'];
+    $field_table = $this->instance['settings']['chado_table'];
+
     $bschema = chado_get_schema($base_table);
     $bpkey = $bschema['primary key'][0];
-    $alias = 'dbx_linker';
+
+    $alias = $this->field['field_name'];
     $operator = $condition['operator'];
 
-    if ($condition['column'] == 'database_cross_reference.database_name') {
-      $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);
+    $field_term_id = $this->getFieldTermID();
+    $dbname_term = $field_term_id . ',' . tripal_get_chado_semweb_term('db', 'name');
+    $accession_term = $field_term_id . ',' . tripal_get_chado_semweb_term('dbxref', 'accession');
+    $dburl_term = $field_term_id . ',' . tripal_get_chado_semweb_term('db', 'url');
+
+    $this->queryJoinOnce($query, $field_table, $alias, "base.$bpkey = $alias.$bpkey");
+    $this->queryJoinOnce($query, 'dbxref', $alias . '_DBX', $alias . "_DBX.dbxref_id = $alias.dbxref_id");
+
+    if ($condition['column'] == $dbname_term) {
+      $this->queryJoinOnce($query, 'db', $alias . '_DB', $alias . "_DB.db_id = " . $alias . "_DBX.db_id");
+      $query->condition($alias . "_DB.name", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'database_cross_reference.accession') {
-      $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);
+    if ($condition['column'] == $accession_term) {
+      $query->condition($alias . "_DBX.accession", $condition['value'], $operator);
     }
   }
 
@@ -201,13 +195,36 @@ class sbo__database_cross_reference extends ChadoField {
    * @see ChadoField::queryOrder()
    */
   public function queryOrder($query, $order) {
+    $dbxref_linker = $this->instance['settings']['chado_table'];
+    $base_table = $this->instance['settings']['base_table'];
+    $field_table = $this->instance['settings']['chado_table'];
+
+    $bschema = chado_get_schema($base_table);
+    $bpkey = $bschema['primary key'][0];
 
+    $alias = $this->field['field_name'];
+
+    $field_term_id = $this->getFieldTermID();
+    $dbname_term = $field_term_id . ',' . tripal_get_chado_semweb_term('db', 'name');
+    $accession_term = $field_term_id . ',' . tripal_get_chado_semweb_term('dbxref', 'accession');
+    $dburl_term = $field_term_id . ',' . tripal_get_chado_semweb_term('db', 'url');
+
+    $this->queryJoinOnce($query, $field_table, $alias, "base.$bpkey = $alias.$bpkey", "LEFT OUTER");
+    $this->queryJoinOnce($query, 'dbxref', $alias . '_DBX', $alias . "_DBX.dbxref_id = $alias.dbxref_id", "LEFT OUTER");
+
+    if ($order['column'] == $dbname_term) {
+      $this->queryJoinOnce($query, 'db', $alias . '_DB', $alias . "_DB.db_id = " . $alias . "_DBX.db_id", "LEFT OUTER");
+      $query->orderBy($alias . "_DB.name", $order['direction']);
+    }
+    if ($order['column'] == $accession_term) {
+      $query->orderBy($alias . "_DBX.accession", $order['direction']);
+    }
   }
 
   /**
    * @see TripalField::validate()
    */
-  public function validate($entity_type, $entity, $field, $items, &$errors) {
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
 
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
@@ -228,8 +245,6 @@ class sbo__database_cross_reference extends ChadoField {
       $dbxref_id = $values['chado-' . $field_table . '__dbxref_id'];
       $db_id = $values['db_id'];
       $accession = $values['accession'];
-      $version = $values['version'];
-      $description = $values['description'];
 
       // Make sure that if a database ID is provided that an accession is also
       // provided.  Here we use the form_set_error function rather than the
@@ -250,7 +265,7 @@ class sbo__database_cross_reference extends ChadoField {
           'error' => 'sbo__database_cross_reference',
         );
       }
-      if (!$db_id and !$accession and ($version or $description)) {
+      if (!$db_id and !$accession) {
         $errors[$field_name][$delta]['und'][] = array(
           'message' => t("A database and the accession must both be provided."),
           'error' => 'sbo__database_cross_reference',

+ 17 - 12
tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference_formatter.inc

@@ -7,28 +7,33 @@ class sbo__database_cross_reference_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('sbo__database_cross_reference');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
-    $chado_table = $this->instance['settings']['chado_table'];
     $content = '';
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
+    $linker_table = $base_table . '_dbxref';
+
+    $dbname_term = tripal_get_chado_semweb_term('db', 'name');
+    $accession_term = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $dburl_term = tripal_get_chado_semweb_term('db', 'url');
+
     foreach ($items as $delta => $item) {
       if (!$item['value']) {
         continue;
       }
-      $content = $item['value']['data:1048'] . ':' . $item['value']['data:2091'];
-      if ($item['value']['schema:url']) {
-        $content = l($content, $item['value']['schema:url'], array('attributes' => array('target' => '_blank')));
+      $content = $item['value'][$dbname_term] . ':' . $item['value'][$accession_term];
+      if ($item['value'][$dburl_term]) {
+        $dbxref = tripal_get_dbxref(array('dbxref_id' => $item['chado-' . $linker_table . '__dbxref_id']));
+        $url = tripal_get_dbxref_url($dbxref);
+        $content = l($content, $url, array('attributes' => array('target' => '_blank')));
       }
       $element[$delta] = array(
         '#type' => 'markup',

+ 39 - 123
tripal_chado/includes/TripalFields/sbo__database_cross_reference/sbo__database_cross_reference_widget.inc

@@ -13,15 +13,14 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
    */
   public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
     parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
-
-    // Get the FK column that links to the base table.
-    $chado_table = $this->instance['settings']['chado_table'];
     $base_table = $this->instance['settings']['base_table'];
-    $schema = chado_get_schema($chado_table);
+
+    $schema = chado_get_schema($field_table);
     $pkey = $schema['primary key'][0];
     $fkeys = array_values($schema['foreign keys'][$base_table]['columns']);
     $fkey = $fkeys[0];
@@ -32,8 +31,6 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
     $dbxref_id = '';
     $db_id = '';
     $accession = '';
-    $version = '';
-    $description = '';
 
     // If the field already has a value then it will come through the $items
     // array.  This happens when editing an existing record.
@@ -43,27 +40,18 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
       $dbxref_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__dbxref_id', $dbxref_id);
       $db_id = tripal_get_field_item_keyval($items, $delta, 'db_id', $db_id);
       $accession = tripal_get_field_item_keyval($items, $delta, 'accession', $accession);
-      $version = tripal_get_field_item_keyval($items, $delta, 'version', $version);
-      $description = tripal_get_field_item_keyval($items, $delta, 'description', $description);
     }
 
     // Check $form_state['values'] to see if an AJAX call set the values.
-    if (array_key_exists('values', $form_state) and array_key_exists($delta, $form_state['values'])) {
-      $record_id = $form_state['values'][$field_name]['und'][$delta][$field_table . '__' . $pkey];
-      $fkey_value = $form_state['values'][$field_name]['und'][$delta][$field_table . '__' . $fkey];
-      $dbxref_id = $form_state['values'][$field_name]['und'][$delta][$field_table . '__dbxref_id'];
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
+      $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $pkey];
+      $fkey_value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $fkey];
+      $dbxref_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'];
       $db_id = $form_state['values'][$field_name]['und'][$delta]['db_id'];
       $accession = $form_state['values'][$field_name]['und'][$delta]['accession'];
-      $version = $form_state['values'][$field_name]['und'][$delta]['version'];
-      $description = $form_state['values'][$field_name]['und'][$delta]['description'];
     }
 
-    $schema = chado_get_schema('dbxref');
-    $options = tripal_get_db_select_options();
-
-    $widget['#table_name'] = $chado_table;
-    $widget['#fkey_field'] = $fkey;
-    //$widget['#element_validate'] = array('sbo__database_cross_reference_widget_validate');
     $widget['#prefix'] =  "<span id='$field_name-dbxref--db-id-$delta'>";
     $widget['#suffix'] =  "</span>";
 
@@ -85,10 +73,7 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
       '#type' => 'value',
       '#default_value' => $dbxref_id,
     );
-    $widget['dbxref_id'] = array(
-      '#type' => 'value',
-      '#default_value' => $dbxref_id,
-    );
+    $options = tripal_get_db_select_options();
     $widget['db_id'] = array(
       '#type' => 'select',
       '#title' => t('Database'),
@@ -102,6 +87,7 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
         'method' => 'replace'
       ),
     );
+    $schema = chado_get_schema('dbxref');
     $widget['accession'] = array(
       '#type' => 'textfield',
       '#title' => t('Accession'),
@@ -110,93 +96,47 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
       '#maxlength' => array_key_exists('length', $schema['fields']['accession']) ? $schema['fields']['accession']['length'] : 255,
       '#size' => 15,
       '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/dbxref/' . $db_id,
-      '#ajax' => array(
-        'callback' => "sbo__database_cross_reference_widget_form_ajax_callback",
-        'wrapper' => "$field_name-dbxref--db-id-$delta",
-        'effect' => 'fade',
-        'method' => 'replace'
-      ),
-      '#disabled' => $db_id ? FALSE : TRUE,
-    );
-    $widget['version'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Version'),
-      '#default_value' => $version,
-      '#maxlength' => array_key_exists('length', $schema['fields']['version']) ? $schema['fields']['version']['length'] : 255,
-      '#size' => 5,
-      '#disabled' => $db_id ? FALSE : TRUE,
-    );
-    $widget['description'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Description'),
-      '#default_value' => $description,
-      '#size' => 20,
       '#disabled' => $db_id ? FALSE : TRUE,
     );
-    if (!$db_id) {
-      $widget['links'] = array(
-        '#type' => 'item',
-        '#markup' => l('Add a database', 'admin/tripal/loaders/chado_db/add', array('attributes' => array('target' => '_blank')))
-      );
-    }
   }
 
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
 
-  /**
-   *
-   * @see TripalFieldWidget::submit()
-   */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $table_name = $this->instance['settings']['chado_table'];
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
     $base_table = $this->instance['settings']['base_table'];
+
     $schema = chado_get_schema($table_name);
     $pkey = $schema['primary key'][0];
     $fkeys = array_values($schema['foreign keys'][$base_table]['columns']);
     $fkey = $fkeys[0];
 
     // Get the field values.
-    $dbxref_id = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__dbxref_id']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__dbxref_id'] : '';
-    $db_id = isset($form_state['values'][$field_name][$langcode][$delta]['db_id']) ? $form_state['values'][$field_name][$langcode][$delta]['db_id'] : '';
-    $accession = isset($form_state['values'][$field_name][$langcode][$delta]['accession']) ? $form_state['values'][$field_name][$langcode][$delta]['accession'] : '';
-    $version = isset($form_state['values'][$field_name][$langcode][$delta]['version']) ? $form_state['values'][$field_name][$langcode][$delta]['version'] : '';
-    $description = isset($form_state['values'][$field_name][$langcode][$delta]['description']) ? $form_state['values'][$field_name][$langcode][$delta]['description'] : '';
-
+    $dbxref_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'];
+    $db_id = $form_state['values'][$field_name]['und'][$delta]['db_id'];
+    $accession = $form_state['values'][$field_name]['und'][$delta]['accession'];
 
+    // If user did not select a database, we want to remove the dbxref record.
+    // We do this by setting all values to empty except the value and the
+    // primary key.
+    if (!$db_id) {
+      $form_state['values'][$field_name]['und'][$delta]['value'] = 'delete_me';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $fkey] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'] = '';
+    }
     // If the dbxref_id does not match the db_id + accession then the user
     // has selected a new dbxref record and we need to update the hidden
     // value accordingly.
     if ($db_id and $accession) {
-      // If the dbxref doesn't exist then let's add it.
-      $values = array(
-        'db_id' => $db_id,
-        'accession' => $accession,
-      );
-      $options = array('is_duplicate' => TRUE);
-      $has_duplicate = chado_select_record('dbxref', array('*'), $values, $options);
-      if (!$has_duplicate) {
-        tripal_insert_dbxref(array(
-          'db_id' => $db_id,
-          'accession' => $accession,
-        ));
-      }
       $dbxref = chado_generate_var('dbxref', array('db_id' => $db_id, 'accession' => $accession));
       if ($dbxref and $dbxref->dbxref_id != $dbxref_id) {
-        $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__dbxref_id'] = $dbxref->dbxref_id;
-        $form_state['values'][$field_name][$langcode][$delta]['dbxref_id'] = $dbxref->dbxref_id;
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__dbxref_id'] = $dbxref->dbxref_id;
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $dbxref->dbxref_id;
       }
     }
-    // If the db_id and accession are not set, then remove the linker FK
-    // value to the base table, but leave the primary key so the record
-    // can be deleted.
-    else {
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__dbxref_id'] = '';
-    }
-
   }
 
   /**
@@ -204,11 +144,6 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
    */
   public function theme($element) {
 
-    // These two fields were added to the widget to help identify the fields
-    // for layout.
-    $table_name = $element['#table_name'];
-    $fkey = $element['#fkey_field'];
-
     $layout = "
       <div class=\"secondary-dbxref-widget\">
         <div class=\"secondary-dbxref-widget-item\">" .
@@ -217,13 +152,6 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
         <div class=\"secondary-dbxref-widget-item\">" .
           drupal_render($element['accession']) . "
         </div>
-        <div class=\"secondary-dbxref-widget-item\">" .
-          drupal_render($element['version']) . "
-        </div>
-        <div class=\"secondary-dbxref-widget-item\">" .
-          drupal_render($element['description']) . "
-        </div>
-        <div class=\"secondary-dbxref-widget-links\">" . drupal_render($element['links']) . "</div>
       </div>
     ";
 
@@ -236,31 +164,19 @@ class sbo__database_cross_reference_widget extends ChadoFieldWidget {
  * An Ajax callback for the dbxref widget.
  */
 function sbo__database_cross_reference_widget_form_ajax_callback($form, $form_state) {
-
-  $field_name = $form_state['triggering_element']['#parents'][0];
-  $delta = $form_state['triggering_element']['#parents'][2];
-  $field = field_info_field($field_name);
-  $field_type = $field['type'];
-  $instance = $form[$field_name]['und'][$delta]['#instance'];
-  $field_table = $instance['settings']['chado_table'];
-  $field_column = $instance['settings']['chado_column'];
-  $field_prefix = 'chado-' . $field_table . '__dbxref_id';
-
-  // Check to see if this dbxref already exists. If not then
-  // give a notice to the user that the dbxref will be added.
-  $db_id = $form_state['values'][$field_name]['und'][$delta]['db_id'];
-  $accession = $form_state['values'][$field_name]['und'][$delta]['accession'];
-  if ($db_id and $accession) {
-    $values = array(
-      'db_id' => $db_id,
-      'accession' => $accession,
-    );
-    $options = array('is_duplicate' => TRUE);
-    $has_duplicate = chado_select_record('dbxref', array('*'), $values, $options);
-    if (!$has_duplicate) {
-      drupal_set_message('The selected cross reference is new and will be added.');
-    }
+  // Get the triggering element
+  $form_element_name = $form_state['triggering_element']['#name'];
+  preg_match('/(.+?)\[(.+?)\]\[(.+?)\]/', $form_element_name, $matches);
+  $field = $matches[1];
+  $lang = $matches[2];
+  $delta = $matches[3];
+
+  // Return the widget that triggered the AJAX call
+  if (isset($form[$field][$lang][$delta])) {
+    return $form[$field][$lang][$delta];
+  }
+  // Alternatively, return the default value widget for the widget setting form
+  else {
+    return $form['instance']['default_value_widget'][$field];
   }
-
-  return $form[$field_name]['und'][$delta];
 }

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

@@ -65,8 +65,8 @@ class sbo__phenotype extends ChadoField {
     return array(
       $field_term => array(
         'operations' => array('eq', 'ne', 'contains', 'starts'),
-        'sortable' => TRUE,
-        'searchable' => TRUE,
+        'sortable' => FALSE,
+        'searchable' => FALSE,
       ),
     );
   }

+ 0 - 8
tripal_chado/includes/TripalFields/sbo__phenotype/sbo__phenotype_formatter.inc

@@ -7,14 +7,6 @@ class sbo__phenotype_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('sbo__phenotype');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()

+ 274 - 369
tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship.inc

@@ -84,6 +84,12 @@ class sbo__relationship extends ChadoField {
         'searchable' => FALSE,
         'type' => 'string',
         'elements' => array(
+          'SIO:000493' => array(
+            'searchable' => FALSE,
+            'label' => 'Relationship Clause',
+            'help' => 'An English phrase describing the relationships.',
+            'sortable' => FALSE,
+          ),
           'local:relationship_subject' => array(
             'searchable' => FALSE,
             'name' => 'relationship_subject',
@@ -92,7 +98,6 @@ class sbo__relationship extends ChadoField {
             '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'),
@@ -100,7 +105,6 @@ class sbo__relationship extends ChadoField {
               ),
               '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'),
@@ -150,29 +154,28 @@ class sbo__relationship extends ChadoField {
       )
     );
   }
-  /**
-   *
-   * @see TripalField::load()
-   */
-  public function load($entity) {
-    $settings = $this->field['settings'];
 
-    $record = $entity->chado_record;
-    $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle));
+  private function loadRelationship($relationship, &$entity, $delta) {
 
     $field_name = $this->field['field_name'];
-    $field_type = $this->field['type'];
     $field_table = $this->instance['settings']['chado_table'];
-    $field_column = $this->instance['settings']['chado_column'];
     $base_table = $this->instance['settings']['base_table'];
 
-    // Get the PKey for this table
-    $schema = chado_get_schema($field_table);
-    $pkey = $schema['primary key'][0];
+    $rel_acc = $relationship->type_id->dbxref_id->db_id->name . ':' . $relationship->type_id->dbxref_id->accession;
+    $rel_type = $relationship->type_id->name;
+    $verb = $this->get_rel_verb($rel_type);
 
     // Get the foreign keys for the subject and object tables
     $subject_fkey_table = '';
     $object_fkey_table = '';
+
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    // Not all tables have the columns named 'subject_id' and 'object_id'.
+    // some have variations on that name and we need to determine what they are.
     $fkeys = $schema['foreign keys'];
     $subject_id_key = 'subject_id';
     $object_id_key = 'object_id';
@@ -188,37 +191,172 @@ class sbo__relationship extends ChadoField {
         }
       }
     }
+
+    // Get the schemas for the subject and object table.  These should
+    // be the same as the base table but just to be safe we'll get them
+    // separately.
     $subject_schema = chado_get_schema($subject_fkey_table);
-    $object_schema = chado_get_schema($object_fkey_table);
     $subject_pkey = $subject_schema['primary key'][0];
+    $object_schema = chado_get_schema($object_fkey_table);
     $object_pkey = $object_schema['primary key'][0];
 
-    // Get the FK that links to the base record.
+    // Not all realtionshp tables have a name field (e.g. organism_relationship)
+    // threfore in some cases we need to dig a bit deeper to get the entity
+    // name and the entity type name.
+    $subject_name = '';
+    $subject_type = '';
+    $object_name = '';
+    $object_type = '';
+
+    // The linked to table of a relationship linker table may not always
+    // have a type_id or name field.  So we have to be a bit more
+    // specific about how we set some variables.
+    switch ($relationship->tablename) {
+      case 'acquisition_relationship':
+        $subject_type = 'acquisition';
+        $object_type = 'acquisition';
+        break;
+      case 'analysis_relationship':
+        $subject_type = 'analysis';
+        $object_type = 'analysis';
+        break;
+      case 'biomaterial_relationship':
+        $subject_type = 'biomaterial';
+        $object_type = 'biomaterial';
+        break;
+      case 'cell_line_relationship':
+        $subject_type = 'cell_line';
+        $object_type = 'cell_line';
+        break;
+      case 'element_relationship':
+        $subject_name = $relationship->$subject_id_key->feature_id->name;
+        $object_name = $relationship->$object_id_key->feature_id->name;
+        break;
+      case 'organism_relationship':
+        $subject_name = $relationship->$subject_id_key->genus . ' ' . $relationship->$subject_id_key->species;
+        $object_name = $relationship->$object_id_key->genus . ' ' . $relationship->$object_id_key->species;
+        $subject_type = 'organism';
+        $object_type = 'organism';
+        break;
+      case 'project_relationship':
+        $subject_type = 'project';
+        $object_type = 'project';
+        break;
+      case 'phylonode_relationship':
+        $subject_name = $relationship->$subject_id_key->label;
+        $object_name = $relationship->$object_id_key->label;
+        break;
+      case 'pub_relationship':
+        $subject_name = $relationship->$subject_id_key->uniquename;
+        $object_name = $relationship->$object_id_key->uniquename;
+        break;
+      case 'quantification_relationship':
+        $subject_type = 'quantification';
+        $object_type = 'quantification';
+        break;
+      default:
+        $subject_name = isset($relationship->$subject_id_key->name) ? $relationship->$subject_id_key->name : '';
+        $subject_type = isset($relationship->$subject_id_key->type_id) ? $relationship->$subject_id_key->type_id->name : '';
+        $object_name = isset($relationship->$object_id_key->name) ? $relationship->$object_id_key->name : '';
+        $object_type = isset($relationship->$object_id_key->type_id) ? $relationship->$object_id_key->type_id->name : '';
+    }
+
+    $entity->{$field_name}['und'][$delta]['value'] = array(
+      'local:relationship_subject' => array(
+        'rdfs:type' => $subject_type,
+        'schema:name' => $subject_name,
+      ),
+      'local:relationship_type' => $relationship->type_id->name,
+      'local:relationship_object' => array(
+        'rdfs:type' => $object_type,
+        'schema:name' => $object_name,
+        'entity' => 'TripalEntity:' . $entity->id,
+      )
+    );
+
+    // If the subject or object have a unqiuename then add that in for refernce.
+    if (property_exists($relationship->$subject_id_key, 'uniquename')) {
+      $entity->{$field_name}['und'][$delta]['value']['local:relationship_subject']['data:0842'] = $relationship->$subject_id_key->uniquename;;
+    }
+    if (property_exists($relationship->$object_id_key, 'uniquename')) {
+      $entity->{$field_name}['und'][$delta]['value']['local:relationship_object']['data:0842'] = $relationship->$object_id_key->uniquename;
+    }
+
+    // Add in the TripalEntity ids if these base records in the relationship
+    // are published.
+    if (property_exists($relationship->$subject_id_key, 'entity_id')) {
+      $entity_id = $relationship->$subject_id_key->entity_id;
+      $entity->{$field_name}['und'][$delta]['value']['local:relationship_subject']['entity'] = 'TripalEntity:' . $entity_id;
+    }
+    if (property_exists($relationship->$object_id_key, 'entity_id')) {
+      $entity_id = $relationship->$object_id_key->entity_id;
+      $entity->{$field_name}['und'][$delta]['value']['local:relationship_object']['entity'] = 'TripalEntity:' . $entity_id;
+    }
+
+    // Add the clause to the values array.  The clause is a written version
+    // of the relationships.
+    $rel_type_clean = lcfirst(preg_replace('/_/', ' ', $rel_type));
+    $entity->{$field_name}['und'][$delta]['value']['SIO:000493'] = 'The ' . $subject_type . ', ' .
+      $subject_name . ', ' . $verb . ' '  . $rel_type_clean . ' this '  .
+      $object_type . '.';
+
+    $entity->{$field_name}['und'][$delta]['chado-' . $field_table . '__' . $pkey] = $relationship->$pkey;
+    $entity->{$field_name}['und'][$delta]['chado-' . $field_table . '__' . $subject_id_key] = $relationship->$subject_id_key->$subject_pkey;
+    $entity->{$field_name}['und'][$delta]['chado-' . $field_table . '__type_id'] = $relationship->type_id->cvterm_id;
+    $entity->{$field_name}['und'][$delta]['chado-' . $field_table . '__' . $object_id_key] = $relationship->$object_id_key->$object_pkey;
+
+    // For the widget to work properly we will preform values.
+    $entity->{$field_name}['und'][$delta]['type_name'] = $relationship->type_id->name;
+    $entity->{$field_name}['und'][$delta]['subject_name'] = $subject_name . ' [id: ' . $relationship->$subject_id_key->$subject_pkey . ']';
+    $entity->{$field_name}['und'][$delta]['object_name'] = $object_name  . ' [id: ' . $relationship->$object_id_key->$object_pkey . ']';
+    if (array_key_exists('value', $schema['fields'])) {
+      $entity->{$field_name}['und'][$delta]['chado-' . $field_table . '__value'] = $relationship->value;
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $entity->{$field_name}['und'][$delta]['chado-' . $field_table . '__rank'] = $relationship->rank;
+    }
+  }
+ /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity) {
+    $settings = $this->field['settings'];
+
+    $record = $entity->chado_record;
+    $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle));
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    // Get the PKey for this table
     $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
     $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
     $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
 
+    // Not all tables have the columns named 'subject_id' and 'object_id'.
+    // some have variations on that name and we need to determine what they are.
+    $fkeys = $schema['foreign keys'];
+    $subject_id_key = 'subject_id';
+    $object_id_key = 'object_id';
+    foreach ($fkeys as $fktable => $details) {
+      foreach ($details['columns'] as $fkey_lcolumn => $fkey_rcolumn) {
+        if (preg_match('/^subject_.*id/', $fkey_lcolumn)) {
+          $subject_id_key = $fkey_lcolumn;
+        }
+        if (preg_match('/^object_.*id/', $fkey_lcolumn)) {
+          $object_id_key = $fkey_lcolumn;
+        }
+      }
+    }
+
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
-      'value' => array(
-      /* The following shows what may be present in the value array
-        // Clause
-        'SIO:000493' => '',
-        'local:relationship_subject' => array(
-          // Identifier
-          'data:0842' => '',
-          'schema:name' => '',
-          'rdfs:type' => ''
-        ),
-        'local:relationship_object' => array(
-          // Identifier
-          'data:0842' => '',
-          'schema:name' => '',
-          'rdfs:type' => '',
-        ),
-        'local:relationship_type' => '',
-      */
-      ),
+      'value' => '',
       'chado-' . $field_table . '__' . $pkey => '',
       'chado-' . $field_table . '__' . $subject_id_key => '',
       'chado-' . $field_table . '__' . $object_id_key => '',
@@ -269,244 +407,22 @@ class sbo__relationship extends ChadoField {
     if (!$record->$rel_table) {
       return;
     }
-    $srelationships = null;
-    $orelationships = null;
 
+    // Load the subject relationships
+    $i = 0;
     if (isset($record->$rel_table->$subject_id_key)) {
       $srelationships = $record->$rel_table->$subject_id_key;
-    }
-    if (isset($record->$rel_table->$object_id_key)) {
-      $orelationships = $record->$rel_table->$object_id_key;
-    }
-
-    $i = 0;
-    if ($orelationships) {
-      foreach ($orelationships as $relationship) {
-        $rel_acc = $relationship->type_id->dbxref_id->db_id->name . ':' . $relationship->type_id->dbxref_id->accession;
-        $rel_type = $relationship->type_id->name;
-        $verb = $this->get_rel_verb($rel_type);
-        $subject_id = $relationship->$subject_id_key->$subject_pkey;
-
-        // The linked to table of a relationship linker table may not always
-        // have a type_id or name field.  So we have to be a bit more
-        // specific about how we set some variables.
-        switch ($relationship->tablename) {
-          case 'acquisition_relationship':
-            $subject_type = 'acquisition';
-            $object_type = 'acquisition';
-            break;
-          case 'analysis_relationship':
-            $subject_type = 'analysis';
-            $object_type = 'analysis';
-            break;
-          case 'biomaterial_relationship':
-            $subject_type = 'biomaterial';
-            $object_type = 'biomaterial';
-            break;
-          case 'cell_line_relationship':
-            $subject_type = 'cell_line';
-            $object_type = 'cell_line';
-            break;
-          case 'element_relationship':
-            $subject_name = $relationship->$subject_id_key->feature_id->name;
-            $object_name = $relationship->$object_id_key->feature_id->name;
-            break;
-          case 'organism_relationship':
-            $subject_name = $relationship->$subject_id_key->genus . ' ' . $relationship->$subject_id_key->species;
-            $object_name = $relationship->$object_id_key->genus . ' ' . $relationship->$object_id_key->species;
-            $subject_type = 'organism';
-            $object_type = 'organism';
-            break;
-          case 'project_relationship':
-            $subject_type = 'project';
-            $object_type = 'project';
-            break;
-          case 'phylonode_relationship':
-            $subject_name = $relationship->$subject_id_key->label;
-            $object_name = $relationship->$object_id_key->label;
-            break;
-          case 'pub_relationship':
-            $subject_name = $relationship->$subject_id_key->uniquename;
-            $object_name = $relationship->$object_id_key->uniquename;
-            break;
-          case 'quantification_relationship':
-            $subject_type = 'quantification';
-            $object_type = 'quantification';
-            break;
-          default:
-            $subject_name = isset($relationship->$subject_id_key->name) ? $relationship->$subject_id_key->name : '';
-            $subject_type = isset($relationship->$subject_id_key->type_id) ? $relationship->$subject_id_key->type_id->name : '';
-            $object_name = isset($relationship->$object_id_key->name) ? $relationship->$object_id_key->name : '';
-            $object_type = isset($relationship->$object_id_key->type_id) ? $relationship->$object_id_key->type_id->name : '';
-        }
-
-        $entity->{$field_name}['und'][$i]['value'] = array(
-          'local:relationship_subject' => array(
-            'rdfs:type' => $subject_type,
-            'schema:name' => $subject_name,
-          ),
-          'local:relationship_type' => $relationship->type_id->name,
-          'local:relationship_object' => array(
-            'rdfs:type' => $object_type,
-            'schema:name' => $object_name,
-            'entity' => 'TripalEntity:' . $entity->id,
-          )
-        );
-
-        // See if an entity exists for the subject.
-        $data_table = preg_replace('/_relationship/', '', $relationship->tablename);
-        $sentity_id = chado_get_record_entity_by_table($data_table, $subject_id);
-        if ($sentity_id) {
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_subject']['entity'] = 'TripalEntity:' . $sentity_id;
-        }
-
-        if (property_exists($relationship->$subject_id_key, 'uniquename')) {
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_subject']['data:0842'] =  $relationship->$subject_id_key->uniquename;;
-        }
-        if (property_exists($relationship->$object_id_key, 'uniquename')) {
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_object']['data:0842'] = $relationship->$object_id_key->uniquename;
-        }
-        if (property_exists($relationship->$subject_id_key, 'entity_id')) {
-          $entity_id = $relationship->$subject_id_key->entity_id;
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_subject']['entity'] = 'TripalEntity:' . $entity_id;
-        }
-
-        // Add the clause to the values array.
-        $rel_type_clean = lcfirst(preg_replace('/_/', ' ', $rel_type));
-        $entity->{$field_name}['und'][$i]['value']['SIO:000493'] = 'The ' . $subject_type . ', ' .
-          $subject_name . ', ' . $verb . ' '  . $rel_type_clean . ' this '  .
-          $object_type . '.';
-
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $pkey] = $relationship->$pkey;
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $subject_id_key] = $relationship->$subject_id_key->$subject_pkey;
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__type_id'] = $relationship->type_id->cvterm_id;
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $object_id_key] = $relationship->$object_id_key->$object_pkey;
-
-        $entity->{$field_name}['und'][$i]['type_name'] = $relationship->type_id->name;
-        $entity->{$field_name}['und'][$i]['subject_name'] = $subject_name . ' [id: ' . $relationship->$subject_id_key->$fkey_rcolumn . ']';
-        $entity->{$field_name}['und'][$i]['object_name'] = $object_name  . ' [id: ' . $relationship->$object_id_key->$fkey_rcolumn . ']';
-        if (array_key_exists('value', $schema['fields'])) {
-          $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__value'] = $relationship->value;
-        }
-        if (array_key_exists('rank', $schema['fields'])) {
-          $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__rank'] = $relationship->rank;
-        }
+      foreach ($srelationships as $relationship) {
+        $this->loadRelationship($relationship, $entity, $i);
         $i++;
       }
     }
 
-    if ($srelationships) {
-      foreach ($srelationships as $relationship) {
-        $rel_acc = $relationship->type_id->dbxref_id->db_id->name . ':' . $relationship->type_id->dbxref_id->accession;
-        $rel_type = $relationship->type_id->name;
-        $verb = $this->get_rel_verb($rel_type);
-        $object_id = $relationship->$object_id_key->$object_pkey;
-
-        // The linked to table of a relationship linker table may not always
-        // have a type_id or name field.  So we have to be a bit more
-        // specific about how we set some variables.
-        switch ($relationship->tablename) {
-          case 'acquisition_relationship':
-            $subject_type = 'acquisition';
-            $object_type = 'acquisition';
-            break;
-          case 'analysis_relationship':
-            $subject_type = 'analysis';
-            $object_type = 'analysis';
-            break;
-          case 'biomaterial_relationship':
-            $subject_type = 'biomaterial';
-            $object_type = 'biomaterial';
-            break;
-          case 'cell_line_relationship':
-            $subject_type = 'cell_line';
-            $object_type = 'cell_line';
-            break;
-          case 'element_relationship':
-            $subject_name = $relationship->$subject_id_key->feature_id->name;
-            $object_name = $relationship->$object_id_key->feature_id->name;
-            break;
-          case 'organism_relationship':
-            $subject_name = $relationship->$subject_id_key->genus . ' ' . $relationship->$subject_id_key->species;
-            $object_name = $relationship->$object_id_key->genus . ' ' . $relationship->$object_id_key->species;
-            $subject_type = 'organism';
-            $object_type = 'organism';
-            break;
-          case 'project_relationship':
-            $subject_type = 'project';
-            $object_type = 'project';
-            break;
-          case 'phylonode_relationship':
-            $subject_name = $relationship->$subject_id_key->label;
-            $object_name = $relationship->$object_id_key->label;
-            break;
-          case 'pub_relationship':
-            $subject_name = $relationship->$subject_id_key->uniquename;
-            $object_name = $relationship->$object_id_key->uniquename;
-            break;
-          case 'quantification_relationship':
-            $subject_type = 'quantification';
-            $object_type = 'quantification';
-            break;
-          default:
-            $subject_name = isset($relationship->$subject_id_key->name) ? $relationship->$subject_id_key->name : '';
-            $subject_type = isset($relationship->$subject_id_key->type_id) ? $relationship->$subject_id_key->type_id->name : '';
-            $object_name = isset($relationship->$object_id_key->name) ? $relationship->$object_id_key->name : '';
-            $object_type = isset($relationship->$object_id_key->type_id) ? $relationship->$object_id_key->type_id->name : '';
-        }
-
-        $entity->{$field_name}['und'][$i]['value'] = array(
-          'local:relationship_subject' => array(
-            'rdfs:type' => $subject_type,
-            'schema:name' => $subject_name,
-            'entity' => 'TripalEntity:' . $entity->id,
-          ),
-          'local:relationship_type' => $relationship->type_id->name,
-          'local:relationship_object' => array(
-            'rdfs:type' =>  $object_type,
-            'schema:name' => $object_name,
-          )
-        );
-
-        // See if an entity exists for the object.
-        $data_table = preg_replace('/_relationship/', '', $relationship->tablename);
-        $oentity_id = chado_get_record_entity_by_table($data_table, $object_id);
-        if ($oentity_id) {
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_object']['entity'] = 'TripalEntity:' . $oentity_id;
-        }
-
-        if (property_exists($relationship->$subject_id_key, 'uniquename')) {
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_subject']['data:0842'] = $relationship->$subject_id_key->uniquename;
-        }
-        if (property_exists($relationship->$object_id_key, 'uniquename')) {
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_object']['data:0842'] = $relationship->$object_id_key->uniquename;
-        }
-        if (property_exists($relationship->$object_id_key, 'entity_id')) {
-          $entity_id = $relationship->$object_id_key->entity_id;
-          $entity->{$field_name}['und'][$i]['value']['local:relationship_object']['entity'] = 'TripalEntity:' . $entity_id;
-        }
-
-        // Add the clause to the value array.
-        $rel_type_clean = lcfirst(preg_replace('/_/', ' ', $rel_type));
-        $entity->{$field_name}['und'][$i]['value']['SIO:000493'] = 'This ' .
-          $subject_type . ' ' . $verb . ' '  . $rel_type_clean . ' the '  .
-          $object_type . ' ' . $object_name . '.';
-
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $pkey] = $relationship->$pkey;
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $subject_id_key] = $relationship->$subject_id_key->$subject_pkey;
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__type_id'] = $relationship->type_id->cvterm_id;
-        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $object_id_key] = $relationship->$object_id_key->$object_pkey;
-
-        $entity->{$field_name}['und'][$i]['type_name'] = $relationship->type_id->name;
-        $entity->{$field_name}['und'][$i]['subject_name'] = $subject_name . ' [id: ' . $relationship->$subject_id_key->$fkey_rcolumn . ']';
-        $entity->{$field_name}['und'][$i]['object_name'] = $object_name . ' [id: ' . $relationship->$object_id_key->$fkey_rcolumn . ']';
-
-        if (array_key_exists('value', $schema['fields'])) {
-          $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__value'] = $relationship->value;
-        }
-        if (array_key_exists('rank', $schema['fields'])) {
-          $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__rank'] = $relationship->rank;
-        }
+    // Load the object relationships
+    if (isset($record->$rel_table->$object_id_key)) {
+      $orelationships = $record->$rel_table->$object_id_key;
+      foreach ($orelationships as $relationship) {
+        $this->loadRelationship($relationship, $entity, $i);
         $i++;
       }
     }
@@ -743,123 +659,112 @@ class sbo__relationship extends ChadoField {
     $option3 = isset($settings['relationship_types']) && trim($settings['relationship_types']);
     if ($option1 && ($option2 || $option3) == 1 ||
         $option2 && ($option1 || $option3) == 1 ||
-        $option3 && ($option1 || $option2) == 1
-        ) {
-          form_set_error(
-              "instance][settings][relationships",
-              t("Only one option is allowed to limit the relationship types.")
-              );
-          return;
-        }
+        $option3 && ($option1 || $option2) == 1) {
+      form_set_error("instance][settings][relationships", t("Only one option is allowed to limit the relationship types."));
+      return;
+    }
 
-        // For option3, make sure the supplied types are valid cvterms
-        if ($option3) {
-          $rel_types = explode(PHP_EOL, $settings['relationship_types']);
-          foreach($rel_types AS $type) {
-            $type =  trim($type);
-            // Ignore empty lines
-            if ($type == '') {
-              continue;
-            }
-            // Find the matching cvterm
-            $sql = "SELECT cvterm_id FROM {cvterm} WHERE name = :name";
-            $results = chado_query($sql, array(':name' => $type));
-            $terms = array();
-            while ($obj = $results->fetchObject()) {
-              $terms[] = $obj;
-            }
-            // Don't save the form  if a term can not be found or it matches more than one cvterm
-            $cv = '';
-            if (count($terms) == 0) {
-              // If a term can not be found, maybe the type contains '|', parse it as 'vocabulary|cvterm'
-              if (strpos($type, '|')) {
-                $tmp = explode('|', $type, 2);
-                $type = trim($tmp[1]);
-                $cv = tripal_get_cv(array('name' => trim($tmp[0])));
-                if($cv) {
-                  $sql = "SELECT cvterm_id FROM {cvterm} WHERE name = :name AND cv_id = :cv_id";
-                  $results = chado_query($sql, array(':name' => $type, ':cv_id' => $cv->cv_id));
-                  while ($obj = $results->fetchObject()) {
-                    $terms[] = $obj;
-                  }
-                }
-                else {
-                  $cv = $tmp[0];
-                }
-              }
-              if (count($terms) != 1) {
-                $message = "The term '@type' can not be found.";
-                $token = array('@type' => $type);
-                if ($cv) {
-                  $message =  "The term '@type' can not be found within the vocabulary '@vocab'.";
-                  $token['@vocab'] = $cv;
-                }
-                form_set_error(
-                    "instance][settings][relationships][relationship_types",
-                    t($message, $token)
-                    );
+    // For option3, make sure the supplied types are valid cvterms
+    if ($option3) {
+      $rel_types = explode(PHP_EOL, $settings['relationship_types']);
+      foreach($rel_types AS $type) {
+        $type =  trim($type);
+        // Ignore empty lines
+        if ($type == '') {
+          continue;
+        }
+        // Find the matching cvterm
+        $sql = "SELECT cvterm_id FROM {cvterm} WHERE name = :name";
+        $results = chado_query($sql, array(':name' => $type));
+        $terms = array();
+        while ($obj = $results->fetchObject()) {
+          $terms[] = $obj;
+        }
+        // Don't save the form  if a term can not be found or it matches more than one cvterm
+        $cv = '';
+        if (count($terms) == 0) {
+          // If a term can not be found, maybe the type contains '|', parse it as 'vocabulary|cvterm'
+          if (strpos($type, '|')) {
+            $tmp = explode('|', $type, 2);
+            $type = trim($tmp[1]);
+            $cv = tripal_get_cv(array('name' => trim($tmp[0])));
+            if($cv) {
+              $sql = "SELECT cvterm_id FROM {cvterm} WHERE name = :name AND cv_id = :cv_id";
+              $results = chado_query($sql, array(':name' => $type, ':cv_id' => $cv->cv_id));
+              while ($obj = $results->fetchObject()) {
+                $terms[] = $obj;
               }
             }
-            else if (count($terms) > 1) {
-              // If a type matches more than one term, parse it as 'vocabulary|cvterm' and try again
-              if (strpos($type, '|')) {
-                $tmp = explode('|', $type, 2);
-                $type = trim($tmp[1]);
-                $cv = tripal_get_cv(array('name' => trim($tmp[0])));
-                if ($cv) {
-                  $sql = "SELECT cvterm_id FROM {cvterm} WHERE name = :name AND cv_id = :cv_id";
-                  $results = chado_query($sql, array(':name' => $type, ':cv_id' => $cv->cv_id));
-                  while ($obj = $results->fetchObject()) {
-                    $terms[] = $obj;
-                  }
-                }
-              }
-              if(count($terms) != 1) {
-                form_set_error(
-                    "instance][settings][relationships][relationship_types",
-                    t("The term '@type' matches more than one term. Please specify its vocabulary in
-                  the format of 'vocabulary|@type'.", array('@type' => $type))
-                    );
-              }
+            else {
+              $cv = $tmp[0];
+            }
+          }
+          if (count($terms) != 1) {
+            $message = "The term '@type' can not be found.";
+            $token = array('@type' => $type);
+            if ($cv) {
+              $message =  "The term '@type' can not be found within the vocabulary '@vocab'.";
+              $token['@vocab'] = $cv;
             }
+            form_set_error("instance][settings][relationships][relationship_types",
+                t($message, $token));
           }
         }
-
-        // For option2: Make sure the parent term is a valid cvterm
-        if ($option2) {
-          $cv_id = $settings['option2_vocab'];
-          $supertype = $settings['option2_parent'];
-          $term = tripal_get_cvterm(array(
-            'name' => trim($supertype),
-            'cv_id' => $cv_id,
-          ));
-          // Tripal cv autocomplete also allow cvterm synonyms, if the parent term doesn't match
-          // a cvterm, try cvtermsynonym
-          if (!$term) {
-            $synonym = tripal_get_cvterm(
-                array(
-                  'synonym' => array(
-                    'name' => trim($supertype),
-                  )
-                )
-                );
-            if ($synonym && $synonym->cv_id->cv_id == $cv_id) {
-              $term = $synonym;
+        else if (count($terms) > 1) {
+          // If a type matches more than one term, parse it as 'vocabulary|cvterm' and try again
+          if (strpos($type, '|')) {
+            $tmp = explode('|', $type, 2);
+            $type = trim($tmp[1]);
+            $cv = tripal_get_cv(array('name' => trim($tmp[0])));
+            if ($cv) {
+              $sql = "SELECT cvterm_id FROM {cvterm} WHERE name = :name AND cv_id = :cv_id";
+              $results = chado_query($sql, array(':name' => $type, ':cv_id' => $cv->cv_id));
+              while ($obj = $results->fetchObject()) {
+                $terms[] = $obj;
+              }
             }
           }
-          if (!isset($term->cvterm_id)) {
-            form_set_error(
-                "instance][settings][relationships][option2_parent",
-                t("The term '@type' is not a valid term for the vocabulary selected.", array('@type' => $supertype))
-                );
+          if(count($terms) != 1) {
+            form_set_error("instance][settings][relationships][relationship_types",
+              t("The term '@type' matches more than one term. Please specify its vocabulary in the format of 'vocabulary|@type'.", array('@type' => $type)));
           }
         }
+      }
+    }
+
+    // For option2: Make sure the parent term is a valid cvterm
+    if ($option2) {
+      $cv_id = $settings['option2_vocab'];
+      $supertype = $settings['option2_parent'];
+      $term = tripal_get_cvterm(array(
+        'name' => trim($supertype),
+        'cv_id' => $cv_id,
+      ));
+      // Tripal cv autocomplete also allow cvterm synonyms, if the parent term doesn't match
+      // a cvterm, try cvtermsynonym
+      if (!$term) {
+        $synonym = tripal_get_cvterm(
+            array(
+              'synonym' => array(
+                'name' => trim($supertype),
+              )
+            )
+            );
+        if ($synonym && $synonym->cv_id->cv_id == $cv_id) {
+          $term = $synonym;
+        }
+      }
+      if (!isset($term->cvterm_id)) {
+        form_set_error("instance][settings][relationships][option2_parent",
+            t("The term '@type' is not a valid term for the vocabulary selected.", array('@type' => $supertype)));
+      }
+    }
   }
 
   /**
    * @see TripalField::validate()
    */
-  public function validate($entity_type, $entity, $field, $items, &$errors) {
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $field_table = $this->instance['settings']['chado_table'];

+ 10 - 4
tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship_widget.inc

@@ -14,6 +14,10 @@ class sbo__relationship_widget extends ChadoFieldWidget {
   public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
     parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
 
+    // TODO: make this widget deal better with the various relationship
+    // tables. See the load function as it does a better job of this.
+    return;
+
     // Get the field settings.
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
@@ -99,7 +103,8 @@ class sbo__relationship_widget extends ChadoFieldWidget {
     }
 
     // Check $form_state['values'] to see if an AJAX call set the values.
-    if (array_key_exists('values', $form_state) and array_key_exists($delta, $form_state['values'])) {
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
       $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $pkey];
       $subject_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $subject_id_key];
       $object_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $object_id_key];
@@ -329,15 +334,16 @@ class sbo__relationship_widget extends ChadoFieldWidget {
 
   /**
    *
-   * @see TripalFieldWidget::submit()
+   * @see TripalFieldWidget::validate()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
     $base_table = $this->instance['settings']['base_table'];
-    $chado_record_id = $entity->chado_record_id;
+    $chado_record_id = array_key_exists('#entity', $element)? $element['#entity']->chado_record_id : NULL;
 
     $schema = chado_get_schema($field_table);
     $fkeys = $schema['foreign keys'];

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

@@ -35,6 +35,14 @@ class schema__additional_type extends ChadoField {
     // type. This will create form elements when editing the field instance
     // to allow the site admin to change the term settings above.
     'term_fixed' => FALSE,
+    // The name of the vocabulary that should be used to provide a list
+    // of terms to select form when providing the additional type.
+    // This should be the name of the controlled vocabulary from the cv table.
+    'vocabulary' => '',
+    // If the entire vocabulary is not to be used for the types and if the
+    // vocabulary is heirarchical then this will be the parent term.
+    // This should be of the format {db.name}:{dbxref.accession}
+    'parent_term' => '',
   );
 
   // The default widget for this field.
@@ -43,6 +51,36 @@ class schema__additional_type extends ChadoField {
   // The default formatter for this field.
   public static $default_formatter = 'schema__additional_type_formatter';
 
+  /**
+   * @see ChadoField::query()
+   */
+  public function query($query, $condition) {
+    $alias = $this->field['field_name'];
+    $operator = $condition['operator'];
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    $this->queryJoinOnce($query, 'cvterm', $alias, "base.$field_column = $alias.cvterm_id");
+    $query->condition($alias . '.name' , $condition['value'], $operator);
+  }
+
+  /**
+   * @see ChadoField::query()
+   */
+  public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    $this->queryJoinOnce($query, 'cvterm', $alias, "base.$field_column = $alias.cvterm_id", "LEFT OUTER");
+    $query->orderBy($alias . '.name' , $order['direction']);
+  }
 
   /**
    *
@@ -57,7 +95,6 @@ class schema__additional_type extends ChadoField {
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
-
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
       'value' => $record->type_id->name,

+ 8 - 13
tripal_chado/includes/TripalFields/schema__additional_type/schema__additional_type_formatter.inc

@@ -7,24 +7,19 @@ class schema__additional_type_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('schema__additional_type');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
-    foreach ($items as $delta => $item) {
-      $element[$delta] = array(
-        '#type' => 'markup',
-        '#markup' => $item['value'],
-      );
+    $content = '';
+    if (count($items) > 0) {
+      $content = $items[0]['value'];
     }
+
+    $element[0] = array(
+      '#type' => 'markup',
+      '#markup' => $content,
+    );
   }
 }

+ 83 - 1
tripal_chado/includes/TripalFields/schema__additional_type/schema__additional_type_widget.inc

@@ -14,13 +14,95 @@ class schema__additional_type_widget extends ChadoFieldWidget {
   public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
     parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
 
+    $settings = $this->field['settings'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $vocabulary = $this->instance['settings']['vocabulary'];
+    $parent_term = $this->instance['settings']['parent_term'];
+
+    $record_id = $element['#entity']->chado_record_id;
+    $value = '';
+    $type_id = '';
+
+    if (count($items) > 0) {
+      $type_id = $items[0]['chado-' . $field_table . '__type_id'];
+      $value = $items[0]['value'];
+    }
+
+    if (array_key_exists('values', $form_state) and array_key_exists($field_name, $form_state['values'])) {
+      $type_id = $form_state['values'][$field_name][$langcode][$delta]['value'];
+    }
+
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => $value,
+    );
+
+    If ($field_table == 'pub' && empty($vocabulary)){
+      $vocabulary = 'tripal_pub';
+    };
+    // If a parent_term is provided then use that to get the options
+    $options = array();
+    $options[] = 'Select a type';
+    if ($parent_term) {
+      list ($vocabulary, $accession) = explode(':', $parent_term);
+      if ($vocabulary and $accession) {
+        $sql = "
+          SELECT
+            CVTS.cvterm_id, CVTS.name
+          FROM {cvtermpath} CVTP
+            INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
+            INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
+            INNER JOIN {dbxref} DBXO ON DBXO.dbxref_id = CVTO.dbxref_id
+            INNER JOIN {db} DBO      ON DBO.db_id = DBXO.db_id
+          WHERE
+            DBO.name = :vocabulary AND DBXO.accession = :accession AND
+            NOT CVTS.is_obsolete = 1
+          ORDER BY CVTS.name ASC
+       ";
+        $results = chado_query($sql, array(':vocabulary' => $vocabulary, ':accession' => $accession));
+        while ($term = $results->fetchObject()) {
+          $options[$term->cvterm_id] = $term->name;
+        }
+      }
+    }
+    elseif ($vocabulary) {
+      $cv = tripal_get_cv(array('name' => $vocabulary));
+      $options = tripal_get_cvterm_select_options($cv->cv_id);
+    }
+    $widget['chado-' . $field_table . '__type_id'] = array(
+      '#type' => 'select',
+      '#options' => $options,
+      '#title' => $element['#title'],
+      '#description' => $element['#description'],
+      '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
+      '#default_value' => $type_id,
+      '#delta' => $delta,
+    );
   }
 
   /**
    *
    * @see TripalFieldWidget::submit()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+    $settings = $this->field['settings'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $vocabulary = $this->instance['settings']['vocabulary'];
+    $parent_term = $this->instance['settings']['parent_term'];
+
+    $type_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__type_id'];
 
+    if (!$type_id) {
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__type_id'] = '__NULL__';
+    }
+    else {
+      $form_state['values'][$field_name]['und'][$delta]['value'] = $type_id;
+    }
   }
 }

+ 36 - 13
tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name.inc

@@ -59,9 +59,6 @@ class schema__alternate_name extends ChadoField {
     // Get the PKey for this table
     $schema = chado_get_schema($field_table);
     $pkey = $schema['primary key'][0];
-
-    // Get the FK that links to the base record.
-    $schema = chado_get_schema($field_table);
     $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
     $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
 
@@ -106,18 +103,44 @@ class schema__alternate_name extends ChadoField {
    * @see ChadoField::query()
    */
   public function query($query, $condition) {
-    $syn_linker = $this->instance['settings']['chado_table'];
-    $base_table = $this->instance['settings']['base_table'];
-    $bschema = chado_get_schema($base_table);
-    $bpkey = $bschema['primary key'][0];
-    $alias = 'syn_linker';
+    $alias = $this->field['field_name'];
     $operator = $condition['operator'];
 
-    if ($condition['column'] == 'alternatename') {
-      $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']);
-    }
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    $this->queryJoinOnce($query, $field_table, $alias, "base.$fkey_rcolumn = $alias.$fkey_lcolumn");
+    $this->queryJoinOnce($query, 'synonym', $alias . '_SYN', $alias . "_SYN.synonym_id = $alias.synonym_id");
+    $query->condition($alias . "_SYN.name", $condition['value']);
   }
 
+  /**
+   * @see ChadoField::query()
+   */
+  public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    $this->queryJoinOnce($query, $field_table, $alias, "base.$fkey_rcolumn = $alias.$fkey_lcolumn", "LEFT OUTER");
+    $this->queryJoinOnce($query, 'synonym', $alias . '_SYN', $alias . "_SYN.synonym_id = $alias.synonym_id", "LEFT OUTER");
+    $query->orderBy($alias . "_SYN.name", $order['direction']);
+  }
 }

+ 3 - 10
tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name_formatter.inc

@@ -7,29 +7,22 @@ class schema__alternate_name_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('schema__alternate_name');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
     $list_items = array();
+
     foreach ($items as $delta => $item) {
       $list_items[] = $item['value'];
     }
     $list = 'There are no synonyms.';
-    if (count($list_items) > 1) {
+    if (count($list_items) > 0) {
       $list = array(
         'title' => '',
         'items' => $list_items,
-        'type' => 'ol',
+        'type' => 'ul',
         'attributes' => array(),
       );
       $list = theme_item_list($list);

+ 55 - 35
tripal_chado/includes/TripalFields/schema__alternate_name/schema__alternate_name_widget.inc

@@ -13,7 +13,6 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
    */
   public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
     parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
-
     $field_name = $this->field['field_name'];
 
     // Get the FK column that links to the base table.
@@ -26,7 +25,7 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
 
     // Get the field defaults.
     $record_id = '';
-    $fkey_value = array_key_exists('#entity', $element) and $element['#entity'] ? $element['#entity']->chado_record_id : NULL;
+    $fkey_value = (array_key_exists('#entity', $element) and $element['#entity']) ? $element['#entity']->chado_record_id : NULL;
     $synonym_id = '';
     $pub_id = '';
     $is_current = TRUE;
@@ -47,8 +46,8 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
     }
 
     // Check $form_state['values'] to see if an AJAX call set the values.
-    if (array_key_exists('values', $form_state) and array_key_exists($delta, $form_state['values'])) {
-      $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__' . $pkey];
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
       $synonym_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__synonym_id'];
       $pub_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__pub_id'];
       $is_current = $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__is_current'];
@@ -71,10 +70,9 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
     // size of the name field to the proper size.
     $schema = chado_get_schema('synonym');
 
-    $widget['#table_name'] = $table_name;
-    $widget['#fkey_field'] = $fkey;
     $widget['#prefix'] =  "<span id='$table_name-$delta'>";
     $widget['#suffix'] =  "</span>";
+    $widget['#table_name'] = $table_name;
 
     $widget['value'] = array(
       '#type' => 'value',
@@ -125,12 +123,10 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
       '#required' => $element['#required'],
     );
   }
-
   /**
-   *
-   * @see TripalFieldWidget::submit()
+   * @see TripalFieldWidget::validate()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $table_name = $this->instance['settings']['chado_table'];
@@ -142,14 +138,53 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
     $fkeys = array_values($schema['foreign keys'][$base_table]['columns']);
     $fkey = $fkeys[0];
 
-    $record_id = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $pkey]) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $pkey] : '';
-    $fkey_value = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey]) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey] : '';
-    $synonym_id = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__synonym_id']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__synonym_id'] : '';
-    $pub_id = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id'] : '';
-    $is_current = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__is_current']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__is_current'] : '';
-    $is_internal = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__is_internal']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__is_internal'] : '';
-    $syn_name = isset($form_state['values'][$field_name][$langcode][$delta]['name']) ? $form_state['values'][$field_name][$langcode][$delta]['name'] : '';
-    $syn_type = isset($form_state['values'][$field_name][$langcode][$delta]['type_id']) ? $form_state['values'][$field_name][$langcode][$delta]['type_id'] : '';
+
+    $pub_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__pub_id'];
+    $syn_name = $form_state['values'][$field_name]['und'][$delta]['name'];
+    $syn_type = $form_state['values'][$field_name]['und'][$delta]['type_id'];
+
+    // If the user provided a $syn_name and a $syn_type then we want to set
+    // the foreign key value to be the chado_record_id.
+    if ($syn_name and $syn_type) {
+      // Set the synonym_id and FK value
+      $synonym = chado_generate_var('synonym', array('name' => $syn_name, 'type_id' => $syn_type));
+      if ($synonym) {
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $synonym->name;
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__synonym_id'] = $synonym->synonym_id;
+      }
+      else {
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $syn_name;
+      }
+      // If a publication is not provided then use the null pub.
+      if (!$pub_id) {
+        $pub = chado_generate_var('pub', array('uniquename' => 'null'));
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__pub_id'] = $pub->pub_id;
+      }
+    }
+    // If no snynonym name is provided then we want to delete the record.
+    // To do this we have to set the value to something (here we just use
+    // the arbitrary 'delete_me', and set every other element to empty other
+    // than the pkey element.
+    else {
+      $form_state['values'][$field_name]['und'][$delta]['value'] = 'delete_me';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__' . $fkey] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__synonym_id'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__is_internal'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__is_current'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__pub_id'] = '';
+    }
+  }
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $table_name = $this->instance['settings']['chado_table'];
+
+    $syn_name = $form_state['values'][$field_name]['und'][$delta]['name'];
+    $syn_type = $form_state['values'][$field_name]['und'][$delta]['type_id'];
 
     // If the user provided a $syn_name and a $syn_type then we want to set
     // the foreign key value to be the chado_record_id.
@@ -165,23 +200,9 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
           'synonym_sgml' => '',
         ));
         $synonym = (object) $synonym;
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $synonym->name;
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__synonym_id'] = $synonym->synonym_id;
       }
-
-      // Set the synonym_id and FK value
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__synonym_id'] = $synonym->synonym_id;
-
-      if (!$pub_id) {
-        $pub = chado_generate_var('pub', array('uniquename' => 'null'));
-        $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id'] = $pub->pub_id;
-      }
-    }
-    else {
-      // If the $syn_name is not set, then remove the linker FK value to the base table.
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__synonym_id'] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__is_internal'] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__is_current'] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id'] = '';
     }
   }
 
@@ -193,7 +214,6 @@ class schema__alternate_name_widget extends ChadoFieldWidget {
     // These two fields were added to the widget to help identify the fields
     // for layout.
     $table_name = $element['#table_name'];
-    $fkey = $element['#fkey_field'];
 
     $layout = "
       <div class=\"synonym-widget\">

+ 0 - 43
tripal_chado/includes/TripalFields/schema__publication/schema__publication.inc

@@ -94,7 +94,6 @@ class schema__publication extends ChadoField {
       'chado-' . $field_table . '__' . $pkey => '',
       'chado-' . $field_table . '__' . $fkey_lcolumn => '',
       'chado-' . $field_table . '__' . 'pub_id' => '',
-      'uniquename' => '',
     );
 
     $linker_table = $base_table . '_pub';
@@ -112,7 +111,6 @@ class schema__publication extends ChadoField {
         $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $pkey] = $linker->$pkey;
         $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . $fkey_lcolumn] = $linker->$fkey_lcolumn->$fkey_lcolumn;
         $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__' . 'pub_id'] = $pub->pub_id;
-        $entity->{$field_name}['und'][$i]['uniquename'] =  $pub->uniquename;
 
         if (property_exists($pub, 'entity_id')) {
           $entity->{$field_name}['und'][$i]['value']['entity'] = 'TripalEntity:' . $pub->entity_id;
@@ -121,45 +119,4 @@ class schema__publication extends ChadoField {
       }
     }
   }
-
-  /**
-   * @see ChadoField::query()
-   */
-  public function query($query, $condition) {
-    $pub_linker = $this->instance['settings']['chado_table'];
-    $base_table = $this->instance['settings']['base_table'];
-    $bschema = chado_get_schema($base_table);
-    $bpkey = $bschema['primary key'][0];
-    $alias = 'pub_linker';
-    $operator = $condition['operator'];
-
-    if ($condition['column'] == 'publication.database_cross_reference') {
-      list($db_name, $accession) = explode(':', $condition['value']);
-
-      $this->queryJoinOnce($query, $pub_linker, $alias, "base.$bpkey = $alias.$bpkey");
-      $this->queryJoinOnce($query, 'pub_dbxref', 'PDBX', "PDBX.pub_id = $alias.pub_id");
-      $this->queryJoinOnce($query, 'dbxref', 'DBX', "DBX.dbxref_id = PDBX.dbxref_id");
-      $this->queryJoinOnce($query, 'db', 'DB', "DB.db_id = DBX.db_id");
-      $query->condition("DB.name", $db_name);
-      $query->condition("DBX.accession", $accession);
-    }
-
-    if ($condition['column'] == 'publication.title') {
-      $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);
-    }
-
-    if ($condition['column'] == 'publication.citation') {
-
-    }
-
-    if ($condition['column'] == 'publication.authors') {
-
-    }
-    if ($condition['column'] == 'publication.abstract') {
-
-    }
-  }
-
 }

+ 2 - 15
tripal_chado/includes/TripalFields/schema__publication/schema__publication_formatter.inc

@@ -7,14 +7,6 @@ class schema__publication_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('schema__publication');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()
@@ -25,19 +17,14 @@ class schema__publication_formatter extends ChadoFieldFormatter {
 
     foreach ($items as $delta => $item) {
 
-      if (empty($item['value'])) {
-        continue;
-      }
       $title = isset($item['value']['TPUB:0000039']) ? $item['value']['TPUB:0000039'] : '';
       $citation = isset($item['value']['TPUB:0000003']) ? $item['value']['TPUB:0000003'] : '';
       $entity = array_key_exists('entity', $item['value']) ? $item['value']['entity'] : '';
       if ($entity) {
         list($entity_type, $entity_id) = explode(':', $entity);
         $new_title = l($title, 'bio_data/' . $entity_id);
-        // If a title has parens we need to escape them for the
-        // regular expression to work.
-        $title = preg_replace('/\(/', '\(', $title);
-        $title = preg_replace('/\)/', '\)', $title);
+        // Escape anything that isn't alphanumeric
+        $title = preg_replace('/([^\w])/', '\\\\$1', $title);
         $citation = preg_replace("/$title/", $new_title, $citation);
       }
       $list_items[] = $citation;

+ 37 - 31
tripal_chado/includes/TripalFields/schema__publication/schema__publication_widget.inc

@@ -25,15 +25,28 @@ class schema__publication_widget extends ChadoFieldWidget {
     $fkey = $fkeys[0];
 
     // Get the field defaults.
-    $fkey_value = (array_key_exists('#entity', $element) and is_object($element['#entity'])) ? $element['#entity']->chado_record_id : NULL;
+    $pkey_val = '';
+    $fkey_value = '';
     $pub_id = '';
-    $uname = '';
+    $title = '';
 
     // If the field already has a value then it will come through the $items
     // array.  This happens when editing an existing record.
     if (count($items) > 0 and array_key_exists($delta, $items)) {
+      $pkey_val =  tripal_get_field_item_keyval($items, $delta, 'chado-' . $table_name . '__' . $pkey, $pkey_val);
+      $fkey_value = tripal_get_field_item_keyval($items, $delta, 'chado-' . $table_name . '__' . $fkey, $fkey_value);
       $pub_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $table_name . '__pub_id', $pub_id);
-      $uname = tripal_get_field_item_keyval($items, $delta, 'uniquename', $uname);
+      if ($pub_id) {
+        $pub = tripal_get_publication(array('pub_id' => $pub_id));
+        $title =  $pub->title . ' [id:' . $pub->pub_id . ']';
+      }
+    }
+
+    // Check $form_state['values'] to see if an AJAX call set the values.
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
+      $title = $form_state['values'][$field_name]['und'][$delta]['pub_title'];
+      $pub_id = $form_state['values'][$field_name]['und'][$delta]['accession'];
     }
 
     $schema = chado_get_schema('pub');
@@ -50,7 +63,7 @@ class schema__publication_widget extends ChadoFieldWidget {
 
     $widget['chado-' . $table_name . '__' . $pkey] = array(
       '#type' => 'value',
-      '#default_value' => '',
+      '#default_value' => $pkey_val,
     );
     $widget['chado-' . $table_name . '__' . $fkey] = array(
       '#type' => 'value',
@@ -61,17 +74,11 @@ class schema__publication_widget extends ChadoFieldWidget {
       '#default_value' => $pub_id,
     );
 
-    $widget['uniquename'] = array(
+    $widget['pub_title'] = array(
       '#type' => 'textfield',
       '#title' => t('Publication'),
-      '#default_value' => $uname,
+      '#default_value' => $title,
       '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/pub',
-      '#ajax' => array(
-        'callback' => "schema__publication_widget_form_ajax_callback",
-        'wrapper' => "$table_name-$delta",
-        'effect' => 'fade',
-        'method' => 'replace'
-      ),
       '#maxlength' => 100000,
     );
   }
@@ -81,7 +88,7 @@ class schema__publication_widget extends ChadoFieldWidget {
    *
    * @see TripalFieldWidget::submit()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
     // Get the FK column that links to the base table.
     $table_name = $this->instance['settings']['chado_table'];
     $base_table = $this->instance['settings']['base_table'];
@@ -92,31 +99,30 @@ class schema__publication_widget extends ChadoFieldWidget {
     $field_name = $this->field['field_name'];
 
     // Get the field values.
-    $fkey_value = isset($form_state['values'][$field_name][$langcode][$delta]['value']) ? $form_state['values'][$field_name][$langcode][$delta]['value'] : '';
-    $pub_id = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id'] : '';
-    $uname = isset($form_state['values'][$field_name][$langcode][$delta]['uniquename']) ? $form_state['values'][$field_name][$langcode][$delta]['uniquename'] : '';
+    $fkey_value = $form_state['values'][$field_name]['und'][$delta]['value'];
+    $pub_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__pub_id'];
+    $title = $form_state['values'][$field_name]['und'][$delta]['pub_title'];
 
-    // If the user provided a uniquename then we want to set the foreign key
+    // If the user provided a pub_title then we want to set the foreign key
     // value to be the chado_record_id
-    if ($uname and !$pub_id) {
-      $pub = chado_generate_var('pub', array('uniquename' => $uname));
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id'] = $pub->pub_id;
+    if ($title) {
+      $matches = array();
+      if (preg_match('/^.*\[id:(\d+)]$/', $title, $matches)) {
+        $pub_id = $matches[1];
+        $pub = chado_generate_var('pub', array('pub_id' => $pub_id));
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__pub_id'] = $pub->pub_id;
+        $form_state['values'][$field_name]['und'][$delta]['value'] = $pub->pub_id;
+      }
     }
 
     // In the widgetFrom function we automatically add the foreign key
     // record.  But if the user did not provide a publication we want to take
     // it out so that the Chado field_storage infrastructure won't try to
     // write a record.
-    if (!$uname and !$pub_id) {
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey] = '';
-    }
-
-    // If the user removed the publication from the pub_uniquename field
-    // then we want to clear out the rest of the hidden values.
-    // Leave the primary key so the record can be deleted.
-    if (!$uname and $pub_id) {
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey] = '';
-      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__pub_id'] = '';
+    if (!$title) {
+      $form_state['values'][$field_name]['und'][$delta]['value'] = 'delete_me';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__' . $fkey] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__pub_id'] = '';
     }
   }
 
@@ -128,7 +134,7 @@ class schema__publication_widget extends ChadoFieldWidget {
     $layout = "
       <div class=\"pub-widget\">
         <div class=\"pub-widget-item\">" .
-          drupal_render($element['uniquename']) . "
+          drupal_render($element['pub_title']) . "
         </div>
       </div>
     ";

+ 346 - 0
tripal_chado/includes/TripalFields/sio__annotation/sio__annotation.inc

@@ -0,0 +1,346 @@
+<?php
+
+class sio__annotation extends ChadoField {
+
+
+  // --------------------------------------------------------------------------
+  //                     EDITABLE STATIC CONSTANTS
+  //
+  // The following constants SHOULD be set for each descendent class.  They are
+  // used by the static functions to provide information to Drupal about
+  // the field and it's default widget and formatter.
+  // --------------------------------------------------------------------------
+
+  // The default lable for this field.
+  public static $default_label = 'Annotations';
+
+  // The default description for this field.
+  public static $description = 'This record is annotated with controlled vocabulary terms.';
+
+  // Provide a list of instance specific settings. These can be access within
+  // the instanceSettingsForm.  When the instanceSettingsForm is submitted
+  // then Drupal with automatically change these settings for the instnace.
+  // It is recommended to put settings at the instance level whenever possible.
+  // If you override this variable in a child class be sure to replicate the
+  // term_name, term_vocab, term_accession and term_fixed keys as these are
+  // required for all TripalFields.
+  public static $default_instance_settings  = array(
+    // The short name for the vocabulary (e.g. shcema, SO, GO, PATO, etc.).
+    'term_vocabulary' => 'SIO',
+    // The name of the term.
+    'term_name' => 'annotation',
+    // The unique ID (i.e. accession) of the term.
+    'term_accession' => '001166',
+    // Set to TRUE if the site admin is allowed to change the term
+    // type. This will create form elements when editing the field instance
+    // to allow the site admin to change the term settings above.
+    'term_fixed' => FALSE,
+  );
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__cvterm_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__cvterm_formatter';
+
+  // A boolean specifying that users should not be allowed to create
+  // fields and instances of this field type through the UI. Such
+  // fields can only be created programmatically with field_create_field()
+  // and field_create_instance().
+  public static $no_ui = FALSE;
+
+
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
+
+    $field_name = $this->field['field_name'];
+
+    foreach ($items as $delta => $item) {
+      // Get the term that matches.
+      $cvterm_name = $item['cvterm_name'];
+      $cv_id = $item['cv_id'];
+      if($cvterm_name and $cv_id) {
+        $cvterm = chado_generate_var('cvterm', array(
+          'cv_id' => $cv_id,
+          'name' => $cvterm_name,
+        ));
+        if (!$cvterm) {
+          $errors[$field_name][$langcode][$delta][] = array(
+            'message' =>  t("Cannot find a term that matches the term name and vocabulary."),
+            'error' => 'cvterm_name'
+          );
+        }
+      }
+    }
+  }
+
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_table = $this->instance['settings']['chado_table'];
+    $schema = chado_get_schema($field_table);
+
+    $vocabulary_term = tripal_get_chado_semweb_term('cvterm', 'cv_id');
+    $accession_term = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $definition_term = tripal_get_chado_semweb_term('cvterm', 'definition');
+
+    $field_term = $this->getFieldTermID();
+    $info = array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'type' => 'array',
+        'elements' => array(
+          $vocabulary_term => array(
+            'sortable' => TRUE,
+            'searchable' => TRUE,
+            'label' => 'Annotation Term Vocabulary',
+          ),
+          $accession_term => array(
+            'sortable' => TRUE,
+            'searchable' => TRUE,
+            'label' => 'Annotation Term Accession',
+          ),
+          $definition_term => array(
+            'sortable' => TRUE,
+            'searchable' => TRUE,
+            'label' => 'Annotation Term Description',
+          ),
+        ),
+      ),
+    );
+
+    if (array_key_exists('is_not', $schema['fields'])) {
+      $negation_term = tripal_get_chado_semweb_term($field_table, 'is_not');
+      $info[$field_term]['elements'][$negation_term] = array(
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'label' => 'Annotation Term Negates',
+      );
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $rank_term = tripal_get_chado_semweb_term($field_table, 'rank');
+      $info[$field_term]['elements'][$rank_term] = array(
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'label' => 'Annotation Term Rank',
+        'type' => 'numeric'
+      );
+    }
+    if (array_key_exists('pub_id', $schema['fields'])) {
+    }
+    return $info;
+  }
+
+  /**
+   * @see ChadoField::query()
+   */
+  public function query($query, $condition) {
+    $alias = $this->field['field_name'];
+    $operator = $condition['operator'];
+
+    $field_table = $this->instance['settings']['chado_table'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    $field_term_id = $this->getFieldTermID();
+
+    $vocabulary_term = $field_term_id . ',' . tripal_get_chado_semweb_term('cvterm', 'cv_id');
+    $accession_term = $field_term_id . ',' . tripal_get_chado_semweb_term('dbxref', 'accession');
+    $definition_term = $field_term_id . ',' . tripal_get_chado_semweb_term('cvterm', 'definition');
+
+
+    // Join to the xxx_cvterm table for this field.
+    $this->queryJoinOnce($query, $field_table, $alias, "base.$fkey_rcolumn = $alias.$fkey_lcolumn");
+
+    if ($condition['column'] == $vocabulary_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . ".cvterm_id = " . $alias . "_cvterm.cvterm_id");
+      $this->queryJoinOnce($query, 'dbxref', $alias . '_dbxref', $alias . "_cvterm.dbxref_id = " . $alias . "_dbxref.dbxref_id");
+      $this->queryJoinOnce($query, 'db', $alias . '_db', $alias . "_db.db_id = " . $alias . "_dbxref.db_id");
+      $query->condition($alias . '_db.name', $condition['value'], $operator);
+    }
+    if ($condition['column'] == $accession_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . ".cvterm_id = " . $alias . "_cvterm.cvterm_id");
+      $this->queryJoinOnce($query, 'dbxref', $alias . '_dbxref', $alias . "_cvterm.dbxref_id = " . $alias . "_dbxref.dbxref_id");
+      $query->condition($alias . '_dbxref.accession', $condition['value'], $operator);
+    }
+    if ($condition['column'] == $definition_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . ".cvterm_id = " . $alias . "_cvterm.cvterm_id");
+      $query->condition($alias . '_cvterm.definition', $condition['value'], $operator);
+    }
+  }
+
+  /**
+   * @see ChadoField::queryOrder()
+   */
+  public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
+
+    $field_table = $this->instance['settings']['chado_table'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    $field_term_id = $this->getFieldTermID();
+
+    $vocabulary_term = $field_term_id . ',' . tripal_get_chado_semweb_term('cvterm', 'cv_id');
+    $accession_term = $field_term_id . ',' . tripal_get_chado_semweb_term('dbxref', 'accession');
+    $definition_term = $field_term_id . ',' . tripal_get_chado_semweb_term('cvterm', 'definition');
+
+    // Join to the xxx_cvterm table for this field.
+    $this->queryJoinOnce($query, $field_table, $alias, "base.$fkey_rcolumn = $alias.$fkey_lcolumn");
+
+    if ($order['column'] == $vocabulary_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . ".cvterm_id = " . $alias . "_cvterm.cvterm_id", "LEFT OUTER");
+      $this->queryJoinOnce($query, 'dbxref', $alias . '_dbxref', $alias . "_cvterm.dbxref_id = " . $alias . "_dbxref.dbxref_id", "LEFT OUTER");
+      $this->queryJoinOnce($query, 'db', $alias . '_db', $alias . "_db.db_id = " . $alias . "_dbxref.db_id", "LEFT OUTER");
+      $query->orderBy($alias . "_db.name", $order['direction']);
+    }
+    if ($order['column'] == $accession_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . ".cvterm_id = " . $alias . "_cvterm.cvterm_id", "LEFT OUTER");
+      $this->queryJoinOnce($query, 'dbxref', $alias . '_dbxref', $alias . "_cvterm.dbxref_id = " . $alias . "_dbxref.dbxref_id", "LEFT OUTER");
+      $query->orderBy($alias . "_dbxref.accession", $order['direction']);
+    }
+    if ($order['column'] == $definition_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . ".cvterm_id = " . $alias . "_cvterm.cvterm_id");
+      $query->orderBy($alias . "_cvterm.definition", $order['direction']);
+    }
+  }
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity) {
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    $vocabulary = tripal_get_chado_semweb_term('cvterm', 'cv_id');
+    $accession = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $definition = tripal_get_chado_semweb_term('cvterm', 'definition');
+    if (array_key_exists('is_not', $schema['fields'])) {
+      $negation = tripal_get_chado_semweb_term($field_table, 'is_not');
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $rank_term = tripal_get_chado_semweb_term($field_table, 'rank');
+    }
+
+    // Set some defaults for the empty record.
+    $chado_record = $entity->chado_record;
+    $entity->{$field_name}['und'][0] = array(
+      'value' => '',
+      'chado-' . $field_table . '__' . $pkey => '',
+      'chado-' . $field_table . '__' . $fkey_lcolumn => $chado_record->$fkey_rcolumn,
+      'chado-' . $field_table . '__cvterm_id' => '',
+    );
+    if (array_key_exists('is_not', $schema['fields'])) {
+      $entity->{$field_name}['und'][0]['chado-' . $field_table . '__is_not'] = '';
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $entity->{$field_name}['und'][0]['chado-' . $field_table . '__rank'] = '';
+    }
+    if (array_key_exists('pub_id', $schema['fields'])) {
+      $entity->{$field_name}['und'][0]['chado-' . $field_table . '__pub_id'] = '';
+    }
+
+    // Get the annotations associated with this base record for this fields type.
+    $columns = array('*');
+    $match = array(
+      $fkey_lcolumn => $chado_record->$fkey_rcolumn,
+    );
+    $order_by = array($pkey => 'ASC');
+    if (array_key_exists('rank', $schema['fields'])) {
+      $order_by = array('rank' => 'ASC');
+    }
+    $options = array(
+      'return_array' => TRUE,
+      'order_by' => $order_by
+    );
+    $fcvterms = chado_select_record($field_table, $columns, $match, $options);
+    for ($i = 0; $i < count($fcvterms); $i++) {
+      $linker = $fcvterms[$i];
+      $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $linker->cvterm_id));
+      $entity->{$field_name}['und'][$i] = array(
+        'value' => array(
+          $vocabulary => $cvterm->dbxref_id->db_id->name,
+          $accession => $cvterm->dbxref_id->accession,
+          $definition => $cvterm->definition
+        ),
+        'chado-' . $field_table . '__' . $pkey => $linker->$pkey,
+        'chado-' . $field_table . '__' . $fkey_lcolumn => $linker->$fkey_lcolumn,
+        'chado-' . $field_table . '__' . 'cvterm_id' => $linker->cvterm_id,
+      );
+      if (array_key_exists('is_not', $schema['fields'])) {
+        $entity->{$field_name}['und'][$i]['value'][$negation] = $linker->is_not;
+        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__is_not'] = $linker->is_not;
+      }
+      if (array_key_exists('rank', $schema['fields'])) {
+        $entity->{$field_name}['und'][$i]['value'][$rank_term] = $linker->rank;
+        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__rank'] = $linker->rank;
+      }
+      if (array_key_exists('pub_id', $schema['fields'])) {
+        $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__pub_id'] = $linker->pub_id;
+      }
+    }
+  }
+}
+
+/**
+ * Theme function for the dbxref_id_widget.
+ *
+ * @param $variables
+ */
+function theme_chado_linker__cvterm_widget($variables) {
+  $element = $variables['element'];
+
+  // These two fields were added to the widget to help identify the fields
+  // for layout.
+  $table_name = $element['#table_name'];
+  $fkey = $element['#fkey_field'];
+
+  $layout = "
+    <div class=\"annotation-cvterm-widget\">
+      <div class=\"annotation-cvterm-widget-item\">" .
+      drupal_render($element['cv__cv_id']) . "
+      </div>
+      <div class=\"annotation-cvterm-widget-item\">" .
+      drupal_render($element['cvterm__name']) . "
+      </div>
+      <div class=\"annotation-cvterm-widget-item\">" .
+      drupal_render($element['pub']) . "
+      </div>
+      <div class=\"annotation-cvterm-widget-item\">" .
+      drupal_render($element['chado-' . $table_name . '__is_not']) . "
+      </div>
+    </div>
+  ";
+  return $layout;
+}
+
+/**
+ * An Ajax callback for the dbxref widget.
+ */
+function chado_linker__cvterm_widget_form_ajax_callback($form, $form_state) {
+
+  $field_name = $form_state['triggering_element']['#parents'][0];
+  $delta = $form_state['triggering_element']['#parents'][2];
+
+
+  return $form[$field_name]['und'][$delta];
+}

+ 78 - 0
tripal_chado/includes/TripalFields/sio__annotation/sio__annotation_formatter.inc

@@ -0,0 +1,78 @@
+<?php
+
+class sio__annotation_formatter extends ChadoFieldFormatter {
+  // The default lable for this field.
+  public static $default_label = 'Chado Annotation';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__cvterm');
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    $headers = array('Term', 'Definition');
+    $rows = array();
+
+    $field_table = $this->instance['settings']['chado_table'];
+    $schema = chado_get_schema($field_table);
+
+    $vocabulary_term = tripal_get_chado_semweb_term('cvterm', 'cv_id');
+    $accession_term = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $definition_term = tripal_get_chado_semweb_term('cvterm', 'definition');
+    if (array_key_exists('is_not', $schema['fields'])) {
+      $negation_term = tripal_get_chado_semweb_term($field_table, 'is_not');
+    }
+
+    $chado_table = $this->instance['settings']['chado_table'];
+    foreach ($items as $delta => $item) {
+
+      if ($item['chado-' . $chado_table . '__cvterm_id']) {
+        $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $item['chado-' . $chado_table . '__cvterm_id']));
+        $dbxref = $cvterm->dbxref_id;
+
+        // Build the accession.
+        $accession = $dbxref->db_id->name . ':' . $dbxref->accession;
+        if ($dbxref->db_id->urlprefix) {
+          $accession = l($accession, tripal_get_dbxref_url($dbxref), array('attributes' => array('target' => '_blank')));
+        }
+
+        $row = array(
+          $item['value'][$vocabulary_term] . ':' . $item['value'][$accession_term],
+          $item['value'][$definition_term],
+        );
+
+        if (array_key_exists('is_not', $schema['fields'])) {
+          if ($negation_term == FALSE) {
+            $row[1] = 'NOT ' . $row[1];
+          }
+        }
+
+        $rows[] = $row;
+      }
+    }
+
+    // Theme the results in a talbe.
+    $caption = 'This record is associated with the following annotations.';
+    $table = array(
+      'header' => $headers,
+      'rows' => $rows,
+      'attributes' => array(
+        'id' => "$chado_table-table-terms",
+        'class' => 'tripal-data-table'
+      ),
+      'caption' => $caption,
+      'sticky' => FALSE,
+      'colgroups' => array(),
+      'empty' => 'There are no annotations of this type',
+    );
+
+    if (count($items) > 0) {
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => theme_table($table),
+      );
+    }
+  }
+}

+ 293 - 0
tripal_chado/includes/TripalFields/sio__annotation/sio__annotation_widget.inc

@@ -0,0 +1,293 @@
+<?php
+
+class sio__annotation_widget extends ChadoFieldWidget {
+  // The default lable for this field.
+  public static $default_label = 'Chado Annotation';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__cvterm');
+
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    $vocabulary = tripal_get_chado_semweb_term('cvterm', 'cv_id');
+    $accession = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $definition = tripal_get_chado_semweb_term('cvterm', 'definition');
+    if (array_key_exists('is_not', $schema['fields'])) {
+      $negation = tripal_get_chado_semweb_term($field_table, 'is_not');
+    }
+
+    // Get the field defaults.
+    $record_id = '';
+    $fk_value = '';
+    $cvterm_id = '';
+    $pub_id = '';
+    $is_not = FALSE;
+    $cvterm_name = '';
+    $cv_id = '';
+    $cvterm = NULL;
+    $pub_name = '';
+
+    // If the field already has a value then it will come through the $items
+    // array.  This happens when editing an existing record.
+    if (array_key_exists($delta, $items)) {
+      // Check for element values that correspond to fields in the Chado table.
+      $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__' . $pkey, $record_id);
+      $fk_value = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__' . $fkey_lcolumn, $fk_value);
+      $cvterm_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__cvterm_id', $cvterm_id);
+
+      if (array_key_exists('pub_id', $schema['fields'])) {
+        $pub_name = tripal_get_field_item_keyval($items, $delta, 'pub', $pub_name);
+        $pub_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__pub_id', $pub_id);
+      }
+      if (array_key_exists('is_not', $schema['fields'])) {
+        $is_not = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__is_not', $is_not);
+      }
+
+      if ($cvterm_id) {
+        $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
+        $cv_id = $cvterm->cv_id->cv_id;
+        $cvterm_name = $cvterm->name;
+      }
+    }
+
+    // Check $form_state['values'] to see if an AJAX call set the values.
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
+
+      $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $pkey];
+      $fk_value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $fkey_lcolumn];
+      $cvterm_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__cvterm_id'];
+
+      if (array_key_exists('pub_id', $schema['fields'])) {
+        $pub_name = $form_state['values'][$field_name]['und'][$delta]['pub'];
+        $pub_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__pub_id'];
+      }
+      if (array_key_exists('is_not', $schema['fields'])) {
+        $is_not = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__is_not'];
+      }
+      $cvterm_name = $form_state['values'][$field_name]['und'][$delta]['cvterm_name'];
+      $cv_id = $form_state['values'][$field_name]['und'][$delta]['cv_id'];
+      $cvterm = chado_generate_var('cvterm', array(
+        'cv_id' => $cv_id,
+        'name' => $cvterm_name,
+      ));
+      if (!$cvterm) {
+        $cvterm_name = '';
+      }
+    }
+
+    $widget['#prefix'] =  "<span id='$field_name-sio--annotation-$delta'>";
+    $widget['#suffix'] =  "</span>";
+
+    // The value field isn't really used but it's needed because if
+    // it doesn't have a value the element won't be considered for
+    // insert/update.
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => $cvterm_id,
+    );
+
+    $widget['chado-' . $field_table . '__' . $pkey] = array(
+      '#type' => 'value',
+      '#default_value' => $record_id,
+    );
+    $widget['chado-' . $field_table . '__cvterm_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $cvterm_id,
+    );
+    $widget['chado-' . $field_table . '__' . $fkey_lcolumn] = array(
+      '#type' => 'value',
+      '#default_value' => $fk_value,
+    );
+
+    $cvs = tripal_get_cv_select_options();
+    $widget['cv_id'] = array(
+      '#type' => 'select',
+      '#title' => t('Vocabulary'),
+      '#options' => $cvs,
+      '#default_value' => $cv_id,
+      '#required' => $element['#required'],
+      '#attributes' => array('style' => 'width: 200px;'),
+      '#ajax' => array(
+        'callback' => "sio__annotation_widget_form_ajax_callback",
+        'wrapper' => "$field_name-sio--annotation-$delta",
+        'effect' => 'fade',
+        'method' => 'replace'
+      ),
+    );
+    $cv_schema = chado_get_schema('cvterm');
+    $widget['cvterm_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Term Name'),
+      '#default_value' => $cvterm_name,
+      '#maxlength' => array_key_exists('length', $cv_schema['fields']['name']) ? $cv_schema['fields']['name']['length'] : 255,
+      '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/cvterm/' . $cv_id,
+      '#disabled' => $cv_id ? FALSE : TRUE,
+    );
+
+    if (array_key_exists('pub_id', $schema['fields'])) {
+      $widget['pub'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Publication'),
+        '#default_value' => $pub_name,
+        '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/pub',
+        '#maxlength' => 100000,
+        '#disabled' => $cv_id ? FALSE : TRUE,
+      );
+      $widget['chado-' . $field_table . '__pub_id'] = array(
+        '#type' => 'value',
+        '#default_value' => $pub_id ? $pub_id : '',
+      );
+    }
+
+    if (array_key_exists('is_not', $schema['fields'])) {
+      $widget['chado-' . $field_table . '__is_not'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Negate this term (NOT)'),
+        '#default_value' => $is_not,
+        '#required' => $element['#required'],
+        '#disabled' => $cv_id ? FALSE : TRUE,
+      );
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $widget['chado-' . $field_table . '__rank'] = array(
+        '#type' => 'value',
+        '#default_value' => $delta,
+      );
+    }
+  }
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+    $base_table = $this->instance['settings']['base_table'];
+
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkey_lcolumn = key($schema['foreign keys'][$base_table]['columns']);
+    $fkey_rcolumn = $schema['foreign keys'][$base_table]['columns'][$fkey_lcolumn];
+
+    $vocabulary = tripal_get_chado_semweb_term('cvterm', 'cv_id');
+    $accession = tripal_get_chado_semweb_term('dbxref', 'accession');
+    $definition = tripal_get_chado_semweb_term('cvterm', 'definition');
+    if (array_key_exists('is_not', $schema['fields'])) {
+      $negation = tripal_get_chado_semweb_term($field_table, 'is_not');
+    }
+
+    $record_id = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $pkey];
+    $fk_value = $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $fkey_lcolumn];
+
+    // If a publication ID was provided then make sure the form_state
+    // value for the pub_id is set correctly.
+    if (array_key_exists('pub_id', $schema['fields'])) {
+      $pub_name = $form_state['values'][$field_name]['und'][$delta]['pub'];
+      if ($pub_name) {
+        $pub = chado_generate_var('pub', array('uniquename' => $pub_name));
+        if ($pub) {
+          $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__pub_id'] = $pub->pub_id;
+        }
+      }
+      // Use the NULL pub.
+      else {
+        $pub = tripal_get_publication(array('uniquename' => 'null'));
+        $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__pub_id'] = $pub->pub_id;
+      }
+    }
+
+    // Make sure the rank is set.
+    if (array_key_exists('rank', $schema['fields'])) {
+      $rank = $form_state['values'][$field_name]['und'][$delta]['_weight'];
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__rank'] = $rank;
+    }
+
+    // Get the term that matches.
+    $cvterm_name = $form_state['values'][$field_name]['und'][$delta]['cvterm_name'];
+    $cv_id = $form_state['values'][$field_name]['und'][$delta]['cv_id'];
+    $cvterm = chado_generate_var('cvterm', array(
+      'cv_id' => $cv_id,
+      'name' => $cvterm_name,
+    ));
+    if ($cvterm) {
+      $form_state['values'][$field_name]['und'][$delta]['cvterm_name'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__cvterm_id'] = $cvterm->cvterm_id;
+      $form_state['values'][$field_name]['und'][$delta]['value'] = $cvterm->cvterm_id;
+    }
+    // Remove all values so we can delete this record if there is no
+    // cvterm.
+    else {
+      // There must be some value set.
+      $form_state['values'][$field_name]['und'][$delta]['value'] = 'delete_me';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__cvterm_id'] = '';
+      $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__' . $fkey_lcolumn] = '';
+      if (array_key_exists('rank', $schema['fields'])) {
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__rank'] = '';
+      }
+      if (array_key_exists('pub_id', $schema['fields'])) {
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '__pub_id'] = '';
+      }
+      if (array_key_exists('is_not', $schema['fields'])) {
+        $form_state['values'][$field_name]['und'][$delta]['chado-' . $field_table . '_is_not'] = '';
+      }
+    }
+  }
+
+  /**
+   * @see TripalFieldWidget::theme()
+   */
+  public function theme($element) {
+    $field_table = $this->instance['settings']['chado_table'];
+    $layout = "
+      <div class=\"sio--annotation-widget\">
+        <div class=\"sio--annotation-item\">" .
+          drupal_render($element['cv_id']) . "
+        </div>
+        <div class=\"sio--annotation-item\">" .
+          drupal_render($element['cvterm_name']) . "
+        </div>
+        <div class=\"sio--annotation-item\">" .
+          drupal_render($element['pub']) . "
+        </div>
+        <div class=\"sio--annotation-item\">" .
+          drupal_render($element['chado-' . $field_table . '__is_not']) . "
+        </div>
+      </div>
+    ";
+
+    return $layout;
+  }
+}
+
+/**
+ * An Ajax callback for the tripal_chado_admin_publish_form..
+ */
+function sio__annotation_widget_form_ajax_callback($form, $form_state) {
+  $field_name = $form_state['triggering_element']['#parents'][0];
+  $delta = $form_state['triggering_element']['#parents'][2];
+
+  return $form[$field_name]['und'][$delta];
+}

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

@@ -66,36 +66,6 @@ class sio__references extends ChadoField {
         '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,
-          ),
-        ),
       ),
     );
   }

+ 0 - 9
tripal_chado/includes/TripalFields/sio__references/sio__references_formatter.inc

@@ -7,15 +7,6 @@ class sio__references_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('sio__references');
 
-
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()

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

@@ -38,7 +38,7 @@ class sio__vocabulary extends ChadoField {
   /**
    * @see TripalField::validate()
    */
-  public function validate($entity_type, $entity, $field, $items, &$errors) {
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
 
     $settings = $this->field['settings'];
     $field_name = $this->field['field_name'];

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

@@ -22,7 +22,34 @@ class sio__vocabulary_widget extends ChadoFieldWidget {
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
-    $cv_id = 0;
+    // If the items array is empty then we are creating a new entity.
+    // Since this is set when the entity type is created, we don't want to allow
+    // content managers to change it. Thus we need to look up the value for the
+    // entity type and use it here.
+    if (empty($items)) {
+      // Use the bundle to get the cv_id choosen for this cvterm-based entity.
+      // ASSUMPTION: the cv_id is saved as the "type_value" of the bundle.
+      $bundle = tripal_load_bundle_entity(array('name' => $widget['#bundle']));
+      $cv = tripal_get_cv(array('cv_id' => $bundle->type_value));
+
+      // Now populate the items array with defaults based on the cv.
+      if ($cv) {
+        $items[$delta] = array(
+          'value' => $cv->name,
+          'chado-' . $field_table . '__cv_id' => $cv->cv_id,
+        );
+      }
+      else {
+        tripal_report_error(
+          $field_name,
+          TRIPAL_ERROR,
+          'Unable to determine default vocabulary for :name Tripal Content Type',
+          array(':name' => $bundle->label)
+        );
+        drupal_set_message(t('Unable to determine default vocabulary for :name Tripal Content Type',
+          array(':name' => $bundle->label)), 'error');
+      }
+    }
 
     $widget['value'] = array(
       '#type' => 'value',
@@ -30,7 +57,7 @@ class sio__vocabulary_widget extends ChadoFieldWidget {
     );
     $widget['chado-' . $field_table . '__cv_id'] = array(
       '#type' => 'value',
-      '#value' => $items[$delta]['chado-' . $field_table . '__cv_id'],
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['chado-' . $field_table . '__cv_id'] : '',
     );
     $widget['vocabulary_name'] = array(
       '#type' => 'item',

+ 0 - 7
tripal_chado/includes/TripalFields/so__genotype/so__genotype.inc

@@ -63,32 +63,25 @@ class so__genotype extends ChadoField {
     $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,
           ),
         )

+ 0 - 8
tripal_chado/includes/TripalFields/so__genotype/so__genotype_formatter.inc

@@ -7,14 +7,6 @@ class so__genotype_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('so__genotype');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()

+ 0 - 8
tripal_chado/includes/TripalFields/so__transcript/so__transcript_formatter.inc

@@ -7,14 +7,6 @@ class so__transcript_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('so__transcript');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()

+ 92 - 6
tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon.inc

@@ -59,15 +59,83 @@ class taxrank__infraspecific_taxon extends ChadoField {
    */
   public function elementInfo() {
     $field_term = $this->getFieldTermID();
+
+    $label_term = 'rdfs:label';
+    $infraspecific_name_term = tripal_get_chado_semweb_term('organism', 'infraspecific_name');
+    $infraspecific_type_term = tripal_get_chado_semweb_term('organism', 'type_id');
+
     return array(
       $field_term => array(
-        'operations' => array(),
         'sortable' => FALSE,
-        'searchable' => FALSE,
+        'searchable' => TRUE,
+        'elements' => array(
+          $label_term => array(
+            'sortable' => FALSE,
+            'searchable' => TRUE,
+            'label' => 'Infraspecific Full Name',
+            'help' => 'The full infraspecific name including the rank and name.',
+          ),
+          $infraspecific_name_term => array(
+            'sortable' => TRUE,
+            'searchable' => TRUE,
+            'label' => 'Infrasepcies Name',
+            'help' => 'The infraspecific name of the organism below the rank of species.',
+          ),
+          $infraspecific_type_term => array(
+            'sortable' => TRUE,
+            'searchable' => TRUE,
+            'label' => 'Infraspecific Rank',
+            'help' => 'The infraspecific rank of the organism below the rank of species.',
+          ),
+        )
       ),
     );
   }
 
+  /**
+   * @see ChadoField::query()
+   */
+  public function query($query, $condition){
+    $alias = $this->field['field_name'];
+    $operator = $condition['operator'];
+
+    $field_term_id = $this->getFieldTermID();
+    $label_term = $field_term_id . ',' . 'rdfs:label';
+    $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');
+
+    if ($condition['column'] == $label_term or $condition['column'] == $field_term_id) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . "_cvterm.cvterm_id = base.type_id");
+      $query->where("CONCAT(" . $alias . "_cvterm.name, ' ', base.infraspecific_name) $operator :full_name",  array(':full_name' => $condition['value']));
+    }
+    if ($condition['column'] == $infraspecific_name_term) {
+      $query->condition('base.infraspecific_name', $condition['value'], $operator);
+    }
+    if ($condition['column'] == $infraspecific_type_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . "_cvterm.cvterm_id = base.type_id");
+      $query->condition($alias . '_cvterm.name', $condition['value'], $operator);
+    }
+  }
+  /**
+   * @see ChadoField::queryOrder()
+   */
+  public function queryOrder($query, $order) {
+    $alias = $this->field['field_name'];
+
+    $field_term_id = $this->getFieldTermID();
+    $label_term = $field_term_id . ',' . 'rdfs:label';
+    $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');
+
+    if ($order['column'] == $infraspecific_name_term) {
+      $query->orderBy('base.infraspecific_name', $order['direction']);
+    }
+    if ($order['column'] == $infraspecific_type_term) {
+      $this->queryJoinOnce($query, 'cvterm', $alias . '_cvterm', $alias . "_cvterm.cvterm_id = base.type_id", "LEFT OUTER");
+      $query->orderBy($alias . '_cvterm.name', $order['direction']);
+    }
+  }
+
   /**
    *
    * @see TripalField::load()
@@ -81,15 +149,33 @@ class taxrank__infraspecific_taxon extends ChadoField {
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
+    $entity->{$field_name}['und'][0]['value'] = '';
+
+    if (chado_get_version() < 1.3) {
+      return;
+    }
+
+    $label_term = 'rdfs:label';
+    $infraspecific_name_term = tripal_get_chado_semweb_term('organism', 'infraspecific_name');
+    $infraspecific_type_term = tripal_get_chado_semweb_term('organism', 'type_id');
+
     // Set some defaults for the empty record.
     $entity->{$field_name}['und'][0] = array(
-      'value' => '',
-      'organism__type_id' => '',
+      'value' => array(),
+      'chado-organism__infraspecific_name' => '',
+      'chado-organism__type_id' => '',
     );
 
+    if ($record->infraspecific_name) {
+      $label = $record->type_id->name . ' ' . $record->infraspecific_name;
+      $entity->{$field_name}['und'][0]['value'][$label_term] = $label;
+      $entity->{$field_name}['und'][0]['value'][$infraspecific_name_term] = $record->infraspecific_name;
+      $entity->{$field_name}['und'][0]['chado-organism__infraspecific_name'] = $record->infraspecific_name;
+    }
+
     if ($record->type_id) {
-      $entity->{$field_name}['und'][0]['value'] = $record->type_id->name;
-      $entity->{$field_name}['und'][0]['organism__type_id'] = $record->type_id->cvterm_id;
+      $entity->{$field_name}['und'][0]['value'][$infraspecific_type_term] = $record->type_id->name;
+      $entity->{$field_name}['und'][0]['chado-organism__type_id'] = $record->type_id->cvterm_id;
     }
   }
 }

+ 16 - 8
tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon_formatter.inc

@@ -7,19 +7,27 @@ class taxrank__infraspecific_taxon_formatter extends ChadoFieldFormatter {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('taxrank__infraspecific_taxon');
 
-  /**
-   *
-   * @see TripalFieldFormatter::settingsForm()
-   */
-  public function settingsForm($view_mode, $form, &$form_state) {
-
-  }
-
   /**
    *
    * @see TripalFieldFormatter::view()
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
 
+    $label_term = 'rdfs:label';
+    $infraspecific_name_term = tripal_get_chado_semweb_term('organism', 'infraspecific_name');
+    $infraspecific_type_term = tripal_get_chado_semweb_term('organism', 'type_id');
+
+    if (is_array($items[0]['value']) and array_key_exists($infraspecific_name_term, $items[0]['value'])) {
+      $infraspecific_name = $items[0]['value'][$infraspecific_name_term];
+      $infraspecific_type = $items[0]['value'][$infraspecific_type_term];
+      $content = $items[0]['value'][$label_term];
+
+      // The cardinality of this field is 1 so we don't have to
+      // iterate through the items array, as there will never be more than 1.
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => $content,
+      );
+    }
   }
 }

+ 74 - 14
tripal_chado/includes/TripalFields/taxrank__infraspecific_taxon/taxrank__infraspecific_taxon_widget.inc

@@ -7,50 +7,110 @@ class taxrank__infraspecific_taxon_widget extends ChadoFieldWidget {
   // The list of field types for which this formatter is appropriate.
   public static $field_types = array('taxrank__infraspecific_taxon');
 
+
   /**
    *
    * @see TripalFieldWidget::form()
    */
   public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
     parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+
     $settings = $this->field['settings'];
     $field_name = $this->field['field_name'];
     $field_type = $this->field['type'];
     $field_table = $this->instance['settings']['chado_table'];
     $field_column = $this->instance['settings']['chado_column'];
 
+    $infra_name = '';
     $type_id = 0;
-    if (count($items) > 0 and array_key_exists('organism__type_id', $items[0])) {
-      $type_id = $items[0]['organism__type_id'];
+
+    if (count($items) > 0) {
+      $type_id = array_key_exists('chado-organism__type_id', $items[0]) ? $items[0]['chado-organism__type_id'] : $type_id;
+      $infra_name = array_key_exists('chado-organism__infraspecific_name', $items[0]) ? $items[0]['chado-organism__infraspecific_name'] : $infra_name;
     }
 
-    $form['value'] = array(
+    if (array_key_exists('values', $form_state) and
+        array_key_exists($field_name, $form_state['values'])) {
+      $type_id = $form_state['values'][$field_name][$langcode][$delta]['infraname']['specific_epithet'];
+      $infra_name = $form_state['values'][$field_name][$langcode][$delta]['infraname']['infrapsecific_epithet'];
+    }
+
+    $widget['value'] = array(
       '#type' => 'value',
       '#value' =>  array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
     );
+    $widget['chado-organism__type_id'] = array(
+      '#type' => 'value',
+      '#value' =>  $type_id,
+    );
+    $widget['chado-organism__infraspecific_name'] = array(
+      '#type' => 'value',
+      '#value' =>  $infra_name,
+    );
 
-    $cv = tripal_get_default_cv($field_table, $field_column);
+    $cv = tripal_get_cv(array('name' => 'taxonomic_rank'));
+    $terms = tripal_get_cvterm_select_options($cv->cv_id);
+
+    // Unfortunately the taxonomic_rank vocabulary is not properly organized
+    // such that we an only include terms below 'species'. Therefore we will
+    // just list them here and hope we haven't missed one.
+    $valid_terms = array('cultivar', 'subspecies', 'varietas', 'subvariety', 'forma', 'subforma');
     $options = array();
-    if ($cv) {
-      $options = tripal_get_cvterm_select_options($cv->cv_id);
+    $options[] = '--Select a rank--';
+    foreach  ($terms as $cvterm_id => $name) {
+      if (in_array($name, $valid_terms)) {
+        $options[$cvterm_id] = $name;
+      }
     }
-    $widget['organism__type_id'] = array(
-      '#type' => 'select',
+
+    $widget['infraname'] = array(
+      '#type' => 'fieldset',
       '#title' => $element['#title'],
-      '#description' => $element['#description'],
+      '#collapsible' => FALSE,
+      '#collapsed' => FALSE,
+      '#description' => $element['#description']
+    );
+    $widget['infraname']['specific_epithet'] = array(
+      '#type' => 'select',
+      '#title' => 'Specific Epithet',
+      '#description' => 'The rank below species. This field may be left blank if not appropriate.',
       '#options' => $options,
       '#default_value' => $type_id,
+    );
+    $widget['infraname']['infrapsecific_epithet'] = array(
+      '#type' => 'textfield',
+      '#title' => 'Infrapsecific Epithet',
+      '#description' => 'The name below species.',
+      '#default_value' => $infra_name,
       '#required' => $element['#required'],
-      '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
-      '#delta' => $delta,
     );
   }
 
   /**
-   *
-   * @see TripalFieldWidget::submit()
+   * @see TripalFieldWidget::validate()
    */
-  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    $type_id = $form_state['values'][$field_name]['und'][0]['infraname']['specific_epithet'];
+    $infra_name = $form_state['values'][$field_name]['und'][0]['infraname']['infrapsecific_epithet'];
+
+    if ($infra_name and $type_id) {
+      $form_state['values'][$field_name]['und'][0]['value'] = $infra_name;
+      $form_state['values'][$field_name]['und'][0]['chado-organism__type_id'] = $type_id;
+      $form_state['values'][$field_name]['und'][0]['chado-organism__infraspecific_name'] = $infra_name;
+    }
+    if ($infra_name and !$type_id) {
+      $form_state['values'][$field_name]['und'][0]['value'] = $infra_name;
+      $form_state['values'][$field_name]['und'][0]['chado-organism__type_id'] = '__NULL__';
+      $form_state['values'][$field_name]['und'][0]['chado-organism__infraspecific_name'] = $infra_name;
+    }
+    else {
+      $form_state['values'][$field_name]['und'][0]['chado-organism__type_id'] = '__NULL__';
+      $form_state['values'][$field_name]['und'][0]['chado-organism__infraspecific_name'] = '__NULL__';
+    }
 
   }
 }

+ 5 - 0
tripal_chado/includes/TripalFields/uo__unit/uo__unit.inc

@@ -56,6 +56,11 @@ class uo__unit extends ChadoField {
 
     if ($record) {
       $entity->{$field_name}['und'][0]['value'] = $record->unittype_id->name;
+      $entity->{$field_name}['und'][0]['chado-' . $field_table . '__unittype_id'] = $record->unittype_id->cvterm_id;
+      $entity->{$field_name}['und'][0]['chado-cvterm__name'] = $record->unittype_id->name;
+      $entity->{$field_name}['und'][0]['chado-cvterm__definition'] = $record->unittype_id->definition;
+      $entity->{$field_name}['und'][0]['chado-cvterm__cv_id'] = $record->unittype_id->cv_id->cv_id;
+      $entity->{$field_name}['und'][0]['chado-cv__name'] = $record->unittype_id->cv_id->name;
     }
   }
 }

+ 42 - 1
tripal_chado/includes/TripalFields/uo__unit/uo__unit_widget.inc

@@ -16,6 +16,47 @@ class uo__unit_widget extends ChadoFieldWidget {
 
     parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
 
-    // TODO: add a form for changing the unit types.
+    $settings = $this->field['settings'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    $unittype_id = 0;
+    if (count($items) > 0 and array_key_exists('chado-' . $field_table . '__unittype_id', $items[0])) {
+      $unittype_id = $items[0]['chado-' . $field_table . '__unittype_id'];
+    }
+
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+    $cv = tripal_get_cv(array('name' => 'featuremap_units'));
+    $options = tripal_get_cvterm_select_options($cv->cv_id);
+    unset($options[0]);
+    $widget['chado-' . $field_table . '__unittype_id'] = array(
+      '#type' => 'select',
+      '#title' => $element['#title'],
+      '#description' => $element['#description'],
+      '#options' => $options,
+      '#default_value' => $unittype_id,
+      '#empty_option' => '- Select a Unit -',
+      '#required' => $element['#required'],
+      '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
+      '#delta' => $delta,
+    );
+  }
+
+  /**
+   * @see TripalFieldWidget::validate()
+   */
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+
+    $field_name = $this->field['field_name'];
+    $field_table = $this->instance['settings']['chado_table'];
+
+    // Make sure the value is set to the organism_id
+    $unittype_id = $form_state['values'][$field_name]['und'][0]['chado-' . $field_table . '__unittype_id'];
+    $form_state['values'][$field_name]['und'][0]['value'] = $unittype_id;
   }
 }

+ 19 - 25
tripal_chado/includes/TripalImporter/OBOImporter.inc

@@ -553,11 +553,9 @@ class OBOImporter extends TripalImporter {
   private function loadOBO_v1_2($file, &$newcvs) {
 
     $header = array();
-
-    // make sure our temporary table exists
     $ret = array();
 
-    // empty the temp table
+    // Empty the temp table.
     $sql = "DELETE FROM {tripal_obo_temp}";
     chado_query($sql);
 
@@ -566,7 +564,7 @@ class OBOImporter extends TripalImporter {
     // parse the obo file
     $default_db = $this->parse($file, $header);
 
-    // add the CV for this ontology to the database.  The v1.2 definition
+    // 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.
@@ -577,10 +575,10 @@ class OBOImporter extends TripalImporter {
       }
       $newcvs[$header['default-namespace'][0]] = $defaultcv->cv_id;
     }
-    // if the 'default-namespace' is missing
+    // If the 'default-namespace' is missing.
     else {
 
-      // look to see if an 'ontology' key is present.  It is part of the v1.4
+      // 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]), '');
@@ -597,11 +595,11 @@ class OBOImporter extends TripalImporter {
           "should go.  Instead, those terms will be placed in the '!vocab' vocabulary.",
           array('!vocab' => $defaultcv->name), TRIPAL_WARNING);
     }!
-    // add any typedefs to the vocabulary first
+    // Add any typedefs to the vocabulary first.
     $this->logMessage("Step 2: Loading type defs...");
     $this->loadTypeDefs($defaultcv, $newcvs, $default_db);
 
-    // next add terms to the vocabulary
+    // Next add terms to the vocabulary.
     $this->logMessage("Step 3: Loading terms...");
     if (!$this->processTerms($defaultcv, $newcvs, $default_db)) {
       throw new Exception('Cannot add terms from this ontology');
@@ -668,7 +666,7 @@ class OBOImporter extends TripalImporter {
 
     $i = 0;
 
-    // iterate through each term from the OBO file and add it
+    // Iterate through each term from the OBO file and add it.
     $sql = "
       SELECT * FROM {tripal_obo_temp}
       WHERE type = 'Term'
@@ -691,7 +689,7 @@ class OBOImporter extends TripalImporter {
       $term = unserialize(base64_decode($t->stanza));
       $this->setItemsHandled($i);
 
-      // add/update this term
+      // Add/update this term.
       if (!$this->processTerm($term, $defaultcv->name, 0, $newcvs, $default_db)) {
         throw new Exception("Failed to process terms from the ontology");
       }
@@ -752,6 +750,14 @@ class OBOImporter extends TripalImporter {
       throw new Exception("Cannot add the term " . $term['id'][0]);
     }
 
+    // Remove any relationships that this term already has (in case it was
+    // updated) and we'll re-add them.
+    $sql = "
+      DELETE FROM {cvterm_relationship} CVTR
+      WHERE CVTR.subject_id = :cvterm_id
+    ";
+    chado_query($sql, array(':cvterm_id' => $cvterm->cvterm_id));
+
     if (array_key_exists('namespace', $term)) {
       $newcvs[$term['namespace'][0]] = $cvterm->cv_id;
     }
@@ -863,18 +869,6 @@ class OBOImporter extends TripalImporter {
       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 (!$this->addRelationship($cvterm, $defaultcv, $rel, $object, $is_relationship, $default_db)) {
           throw new Exception("Cannot add relationship $rel: $object");
         }
@@ -917,7 +911,7 @@ class OBOImporter extends TripalImporter {
   private function addRelationship($cvterm, $defaultcv, $rel,
       $objname, $object_is_relationship = 0, $default_db = 'OBO_REL') {
 
-    // make sure the relationship cvterm exists
+    // Make sure the relationship cvterm exists.
     $term = array(
       'name' => $rel,
       'id' => "$default_db:$rel",
@@ -930,7 +924,7 @@ class OBOImporter extends TripalImporter {
     $relcvterm = tripal_insert_cvterm($term, array('update_existing' => FALSE));
 
     if (!$relcvterm) {
-      // if the relationship term couldn't be found in the default_db provided
+      // 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,
@@ -947,7 +941,7 @@ class OBOImporter extends TripalImporter {
       }
     }
 
-    // get the object term
+    // Get the object term.
     $oterm = $this->getTerm($objname);
     if (!$oterm) {
       throw new Exception("Could not find object term $objname\n");

+ 25 - 2
tripal_chado/includes/setup/tripal_chado.setup.inc

@@ -52,7 +52,6 @@ function tripal_chado_prepare_form_submit($form, $form_state) {
      $args = array();
      $includes = array(
         module_load_include('inc', 'tripal_chado', 'includes/setup/tripal_chado.setup'),
-        module_load_include('inc', 'tripal_chado', 'includes/loaders/tripal_chado.obo_loader'),
      );
      tripal_add_job('Prepare Chado', 'tripal_chado',
        'tripal_chado_prepare_chado', $args,
@@ -137,7 +136,15 @@ function tripal_chado_load_ontologies() {
 /**
  * Prepares Chado for use by Tripal.
  */
-function tripal_chado_prepare_chado() {
+function tripal_chado_prepare_chado($job = NULL) {
+
+  // Retrieve the job arguement in order to report progress.
+  if (is_int($job)) {
+    $job = new TripalJob();
+    $job->load($job_id);
+  }
+  $report_progress = TRUE;
+  if (!is_object($job)) { $report_progress = FALSE; }
 
   try {
 
@@ -171,20 +178,28 @@ function tripal_chado_prepare_chado() {
       tripal_chado_fix_v1_3_custom_tables();
     }
 
+    if ($report_progress) { $job->setProgress(5); }
+
     // Import commonly used ontologies if needed.
     drush_print("Loading Ontologies...");
     tripal_chado_load_ontologies();
 
+    if ($report_progress) { $job->setProgress(50); }
+
     // Populate the semantic web associations for Chado tables/fields.
     drush_print("Making semantic connections for Chado tables/fields...");
     tripal_chado_populate_chado_semweb_table();
 
+    if ($report_progress) { $job->setProgress(60); }
+
     // Initialize the population of the chado_cvterm_mapping table.  This will
     // map existing data types already in Chado so that when users want to
     // add new content types it simplifies the form for them.
     drush_print("Map Chado Controlled vocabularies to Tripal Terms...");
     tripal_chado_map_cvterms();
 
+    if ($report_progress) { $job->setProgress(70); }
+
     drush_print("Creating common Tripal Content Types...");
 
     // Create the 'Organism' entity type. This uses the obi:organism term.
@@ -204,6 +219,7 @@ function tripal_chado_prepare_chado() {
         throw new Exception($error['!message']);
       }
     }
+    if ($report_progress) { $job->setProgress(74); }
 
     // Create the 'Analysis' entity type. This uses the local:analysis term.
     $error = '';
@@ -222,6 +238,7 @@ function tripal_chado_prepare_chado() {
         throw new Exception($error['!message']);
       }
     }
+    if ($report_progress) { $job->setProgress(78); }
 
     // Create the 'Project' entity type. This uses the local:project term.
     $error = '';
@@ -240,6 +257,7 @@ function tripal_chado_prepare_chado() {
         throw new Exception($error['!message']);
       }
     }
+    if ($report_progress) { $job->setProgress(82); }
 
     // Create the 'Map' entity type. This uses the local:project term.
     $error = '';
@@ -265,6 +283,7 @@ function tripal_chado_prepare_chado() {
     );
     $cvterm = tripal_get_cvterm($identifier);
     tripal_chado_add_cvterm_mapping($cvterm->cvterm_id, 'featuremap', NULL);
+    if ($report_progress) { $job->setProgress(86); }
 
     // Import a publication so we get all of the properties before
     // creating the content type.
@@ -306,6 +325,7 @@ function tripal_chado_prepare_chado() {
     );
     $result = chado_select_record('pub_dbxref', array('pub_id'), $values);
     chado_delete_record('pub', array('pub_id' => $result[0]->pub_id));
+    if ($report_progress) { $job->setProgress(90); }
 
     // Create the 'Gene' entity type.
     $error = '';
@@ -325,6 +345,7 @@ function tripal_chado_prepare_chado() {
         throw new Exception($error['!message']);
       }
     }
+    if ($report_progress) { $job->setProgress(94); }
 
     // Create the 'mRNA' entity type.
     $error = '';
@@ -344,6 +365,7 @@ function tripal_chado_prepare_chado() {
         throw new Exception($error['!message']);
       }
     }
+    if ($report_progress) { $job->setProgress(98); }
 
     // Add the supported loaders
     variable_set('tripal_pub_supported_dbs', array('PMID', 'AGL'));
@@ -352,6 +374,7 @@ function tripal_chado_prepare_chado() {
     variable_set('tripal_chado_is_prepared', TRUE);
   }
   catch (Exception $e) {
+    $job->logMessage($e);
     throw new Exception($e);
   }
 }

+ 13 - 0
tripal_chado/includes/tripal_chado.cv.inc

@@ -1,4 +1,17 @@
 <?php
+
+/**
+ * Loads an OBO File using the new TripalImporter. Expected to be run by a Tripal Job.
+ */
+function tripal_cv_load_obo($obo_id) {
+
+  module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/OBOImporter');
+  $obo_importer = new OBOImporter();
+  $obo_importer->create(array('obo_id' => $obo_id));
+  $obo_importer->run();
+
+}
+
 /**
  * Provide landing page to the new admin pages
  *

+ 5 - 0
tripal_chado/includes/tripal_chado.db.inc

@@ -151,6 +151,11 @@ function tripal_chado_add_db_form_fields(&$form, $form_state, $dbid = NULL) {
     $default_desc = $db->description;
     $default_url = $db->url;
     $default_urlprefix = $db->urlprefix;
+
+    $form['dbid'] = array(
+      '#type' => 'value',
+      '#value' => $dbid,
+    );
   }
 
   // add a fieldset for the Drupal Schema API

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

@@ -43,7 +43,8 @@ function tripal_chado_field_storage_write($entity_type, $entity, $op, $fields) {
 
   // Convert the fields into a key/value list of fields and their values.
   $field_vals = tripal_chado_field_storage_write_merge_fields($fields, $entity_type, $entity);
-dpm($field_vals);
+//dpm($field_vals);
+
   // First, write the record for the base table.  If we have a record id then
   // this is an update and we need to set the primary key.  If not, then this
   // is an insert and we need to set the type_id if the table supports it.
@@ -387,7 +388,8 @@ function tripal_chado_field_storage_write_merge_fields($fields, $entity_type, $e
     $field = field_info_field_by_id($field_id);
     $field_name = $field['field_name'];
     $instance = field_info_instance('TripalEntity', $field['field_name'], $entity->bundle);
-    // Some fields (e.g. chado_linker_cvterm_adder) don't add data to
+
+    // Some fields don't add data to
     // Chado so they don't have a table, but they are still attached to the
     // entity. Just skip these.
     if (!array_key_exists('chado_table', $instance['settings'])) {
@@ -553,6 +555,12 @@ function tripal_chado_field_storage_query($query) {
   // Make sure we only get records of the correct entity type
   $cquery->condition('TE.bundle', $query->entityConditions['bundle']['value']);
 
+  // Iterate through all the property conditions. These will be filters
+  // on the tripal_entity table.
+  foreach ($query->propertyConditions as $index => $condition) {
+    $cquery->condition('TE.' . $condition['column'], $condition['value']);
+  }
+
   // Now set any ordering.
   foreach ($query->order as $index => $sort) {
     // Add in property ordering.
@@ -620,8 +628,8 @@ function tripal_chado_field_storage_query($query) {
     } // end if ($sort['type'] == 'field') {
   } // end foreach ($query->order as $index => $sort) {
 
-  //dpm($cquery->__toString());
-  //dpm($cquery->getArguments());
+//        dpm($cquery->__toString());
+//        dpm($cquery->getArguments());
 
   $records = $cquery->execute();
 

+ 331 - 200
tripal_chado/includes/tripal_chado.fields.inc

@@ -70,6 +70,13 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
       continue;
     }
 
+    // Skip the infraspecific type_id and name from the organism table as we
+    // have a special field for those.
+    if ($table_name == 'organism' and ($column_name == 'type_id' or
+        $column_name == 'infraspecific_name')) {
+      continue;
+    }
+
     // Skip the cvterm.is_relationshptype.
     if ($table_name == 'cvterm' and $column_name == 'is_relationshiptype') {
       continue;
@@ -88,7 +95,7 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
         array('%table_name' => $table_name, '%column_name' => $column_name)), 'error');
       continue;
     }
-    $field_name = strtolower($cvterm->dbxref_id->db_id->name . '__' . preg_replace('/ /', '_', $cvterm->name));
+    $field_name = strtolower($cvterm->dbxref_id->db_id->name . '__' . preg_replace('/[^\w]/', '_', $cvterm->name));
     $field_name = substr($field_name, 0, 32);
 
     // Skip the primary key field.
@@ -96,9 +103,9 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
       continue;
     }
 
-    // Skip the type field that will always be custom
-    if (($table_name == $type_table and $column_name == $type_column) or
-         $column_name == 'type_id') {
+    // If the type_id defines the content type and it's part of the
+    // base table and this column is the type_id then skip it.
+    if (!$type_table and $type_column and $column_name == $type_column) {
       continue;
     }
 
@@ -165,7 +172,9 @@ function tripal_chado_bundle_fields_info_base(&$info, $details, $entity_type, $b
     //
     // PUB TABLE
     //
-    elseif ($table_name == 'pub' and $column_name == 'uniquename') {
+    if ($table_name == 'pub' and (
+        $column_name == 'uniquename' or $column_name == 'title' or
+        $column_name == 'volumetitle')) {
       $base_info['type'] = 'text';
       $base_info['settings']['text_processing'] = 0;
     }
@@ -187,12 +196,8 @@ function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type,
 
   $schema = chado_get_schema($table_name);
 
-  // BASE TYPE_ID
-  // Exclude the type_id field on base tables where content types are mapped
-  // by the type_id field or on the organism table where the type_id is mean
-  // as an infraspecific name type.
-  if (array_key_exists('type_id', $schema['fields']) and
-      $table_name != 'organism' and $type_column != 'type_id') {
+  // An additional type for the publication
+  if ($table_name == 'pub') {
     $field_name = 'schema__additional_type';
     $field_type = 'schema__additional_type';
     $info[$field_name] = array(
@@ -297,7 +302,7 @@ function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type,
     );
   }
 
-  // PROTEIN & CDS SEQUENCE
+  // PROTEIN & CDS
   if ($table_name == 'feature' and
       ($bundle->label == 'mRNA' or $bundle->label == 'transcript'))  {
     $field_name = 'data__protein_sequence';
@@ -325,39 +330,37 @@ function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type,
 //     );
   }
 
-  // GENE TRANSCRIPTS
-  $rel_table = $table_name . '_relationship';
-  if (chado_table_exists($rel_table) and $bundle->label == 'gene') {
-    $field_name = 'so__transcript';
-    $field_type = 'so__transcript';
-    $info[$field_name] = array(
-      'field_name' => $field_name,
-      'type' => $field_type,
-      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
-      'locked' => FALSE,
-      'storage' => array(
-        'type' => 'field_chado_storage',
-      ),
-    );
-  }
-
-  // ORGANISM TYPE_ID
-//   if ($table_name == 'organism' and array_key_exists('type_id', $schema['fields'])) {
-//     $field_name = 'taxarank__infraspecific_taxon';
-//     $field_type = 'taxarank__infraspecific_taxon';
+//   // GENE TRANSCRIPTS
+//   $rel_table = $table_name . '_relationship';
+//   if (chado_table_exists($rel_table) and $bundle->label == 'gene') {
+//     $field_name = 'so__transcript';
+//     $field_type = 'so__transcript';
 //     $info[$field_name] = array(
 //       'field_name' => $field_name,
 //       'type' => $field_type,
-//       'cardinality' => 1,
+//       'cardinality' => FIELD_CARDINALITY_UNLIMITED,
 //       'locked' => FALSE,
 //       'storage' => array(
 //         'type' => 'field_chado_storage',
 //       ),
-//       'settings' => array(
-//       ),
 //     );
 //   }
 
+  // ORGANISM TYPE_ID
+  if ($table_name == 'organism' and array_key_exists('type_id', $schema['fields'])) {
+    $field_name = 'taxrank__infraspecific_taxon';
+    $field_type = 'taxrank__infraspecific_taxon';
+    $info[$field_name] = array(
+      'field_name' => $field_name,
+      'type' => $field_type,
+      'cardinality' => 1,
+      'locked' => FALSE,
+      'storage' => array(
+        'type' => 'field_chado_storage',
+      ),
+    );
+  }
+
   // FEATUREMAP UNITTYPE_ID
   if ($table_name == 'featuremap') {
     $field_name = 'uo__unit';
@@ -497,37 +500,37 @@ function tripal_chado_bundle_fields_info_linker(&$info, $details, $entity_type,
     );
   }
 
-  // GENOTYPE
-  $genotype_table = $table_name . '_genotype';
-  if (chado_table_exists($genotype_table)) {
-    $field_name = 'so__genotype';
-    $field_type = 'so__genotype';
-    $info[$field_name] = array(
-      'field_name' => $field_name,
-      'type' => $field_type,
-      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
-      'locked' => FALSE,
-      'storage' => array(
-        'type' => 'field_chado_storage',
-      ),
-    );
-  }
+//   // GENOTYPE
+//   $genotype_table = $table_name . '_genotype';
+//   if (chado_table_exists($genotype_table)) {
+//     $field_name = 'so__genotype';
+//     $field_type = 'so__genotype';
+//     $info[$field_name] = array(
+//       'field_name' => $field_name,
+//       'type' => $field_type,
+//       'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+//       'locked' => FALSE,
+//       'storage' => array(
+//         'type' => 'field_chado_storage',
+//       ),
+//     );
+//   }
 
-  // PHENOTYPE
-  $phenotype_table = $table_name . '_phenotype';
-  if (chado_table_exists($phenotype_table)) {
-    $field_name = 'sbo__phenotype';
-    $field_type = 'sbo__phenotype';
-    $info[$field_name] = array(
-      'field_name' => $field_name,
-      'type' => $field_type,
-      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
-      'locked' => FALSE,
-      'storage' => array(
-        'type' => 'field_chado_storage',
-      ),
-    );
-  }
+//   // PHENOTYPE
+//   $phenotype_table = $table_name . '_phenotype';
+//   if (chado_table_exists($phenotype_table)) {
+//     $field_name = 'sbo__phenotype';
+//     $field_type = 'sbo__phenotype';
+//     $info[$field_name] = array(
+//       'field_name' => $field_name,
+//       'type' => $field_type,
+//       'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+//       'locked' => FALSE,
+//       'storage' => array(
+//         'type' => 'field_chado_storage',
+//       ),
+//     );
+//   }
 
   // PROPERTIES
   $prop_table = $table_name . 'prop';
@@ -563,6 +566,27 @@ function tripal_chado_bundle_fields_info_linker(&$info, $details, $entity_type,
     }
   }
 
+  // CVTERMS
+  $term_table = $table_name . '_cvterm';
+  if (chado_table_exists($term_table)) {
+    $schema = chado_get_schema($term_table);
+    $pkey = $schema['primary key'][0];
+    $lkey = key($schema['foreign keys'][$table_name]['columns']);
+    $rkey = $schema['foreign keys'][$table_name]['columns'][$lkey];
+
+    $field_name = 'sio__annotation';
+    $field_type = 'sio__annotation';
+    $info[$field_name] = array(
+      'field_name' => $field_name,
+      'type' => $field_type,
+      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+      'locked' => FALSE,
+      'storage' => array(
+        'type' => 'field_chado_storage',
+      ),
+    );
+  }
+
   // PUBLICATIONS
   $pub_table = $table_name . '_pub';
   if (chado_table_exists($pub_table)) {
@@ -702,6 +726,13 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
       continue;
     }
 
+    // Skip the infraspecific type_id and name from the organism table as we
+    // have a special field for those.
+    if ($table_name == 'organism' and ($column_name == 'type_id' or
+        $column_name == 'infraspecific_name')) {
+      continue;
+    }
+
     // Don't create base fields for the primary key and the type_id field.
     if ($column_name == $pkey or $column_name == $type_column) {
       continue;
@@ -720,7 +751,7 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
       continue;
     }
 
-    $field_name = strtolower($cvterm->dbxref_id->db_id->name . '__' . preg_replace('/ /', '_', $cvterm->name));
+    $field_name = strtolower($cvterm->dbxref_id->db_id->name . '__' . preg_replace('/[^\w]/', '_', $cvterm->name));
     $field_name = substr($field_name, 0, 32);
 
     // Skip the primary key field.
@@ -728,8 +759,9 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
       continue;
     }
 
-    // Skip the type field.
-    if ($table_name == $type_table and $column_name == $type_column) {
+    // If the type_id defines the content type and it's part of the
+    // base table and this column is the type_id then skip it.
+    if (!$type_table and $type_column and $column_name == $type_column) {
       continue;
     }
 
@@ -823,46 +855,87 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
         unset($base_info['settings']['format']);
       }
     }
-    elseif ($base_info['label'] == 'Timeaccessioned') {
+    if ($base_info['label'] == 'Timeaccessioned') {
       $base_info['label'] = 'Time Accessioned';
       $base_info['description'] = 'Please enter the time that this record was first added to the database.';
     }
-    elseif ($base_info['label'] == 'Timelastmodified') {
+    if ($base_info['label'] == 'Timelastmodified') {
       $base_info['label'] = 'Time Last Modified';
       $base_info['description'] = 'Please enter the time that this record was last modified. The default is the current time.';
     }
     //
     // ORGANISM TABLE
     //
-    elseif ($table_name == 'organism' and $column_name == 'comment') {
+    if ($table_name == 'organism' and $column_name == 'comment') {
       $base_info['label'] = 'Description';
     }
     //
     // PUB TABLE
     //
-    elseif ($table_name == 'pub' and $column_name == 'uniquename') {
-      $base_info['widget_type'] = 'text_textfield';
+    if ($table_name == 'pub') {
+      if ($column_name == 'title' or $column_name == 'type_id') {
+        $base_info['required'] = TRUE;
+      }
+      if($column_name == 'uniquename' or $column_name == 'title' or $column_name == 'volumetitle') {
+        $base_info['widget']['type'] = 'text_textfield';
+        $base_info['settings']['text_processing'] = '0';
+      }
+      if($column_name == 'uniquename') {
+        $base_info['label'] = 'Unique Local Identifier';
+        $base_info['required'] = TRUE;
+        $base_info['description'] = 'This publication is housed in Chado whic requires a unique identifer (or name) be provided for every publication. This identifier need not be shown to end-users but it is required. Each site must decide on a format for this unique name.';
+      }
+      if($column_name == 'title') {
+        $base_info['description'] = 'The title of the published work.';
+      }
+      if($column_name == 'series_name') {
+        $base_info['description'] = 'The name media that produces a series of publications (e.g. journal, conference proceedings, etc.).';
+      }
+      if($column_name == 'issue') {
+        $base_info['description'] = 'The issue of the series (e.g. journal) where the publication was printed.';
+      }
+      if($column_name == 'volume') {
+        $base_info['description'] = 'The volume of the series (e.g. journal) where the publication was printed.';
+      }
+      if($column_name == 'pyear') {
+        $base_info['label'] = 'Publication Year';
+        $base_info['required'] = TRUE;
+      }
+      if($column_name == 'pages') {
+        $base_info['label'] = 'Page Numbers';
+      }
+      if($column_name == 'pubplace') {
+        $base_info['label'] = 'Publication Location';
+      }
+      if($column_name == 'volumetitle') {
+        $base_info['label'] = 'Volumn Title';
+        $base_info['label'] = 'The title of the volume (if applicable).';
+      }
+      if($column_name == 'miniref') {
+        $base_info['label'] = 'Mini Local identifier';
+        $base_info['description'] = 'Some sites use small identifiers for each publication. if your site uses this please provide the proper miniref for this publication.';
+      }
     }
 
     //
     // ANALYSIS TABLE
     //
-    elseif ($table_name == 'analysis' and $column_name == 'name') {
+    if ($table_name == 'analysis' and $column_name == 'name') {
       $base_info['required'] = TRUE;
     }
-    elseif ($table_name == 'analysis' and $column_name == 'program') {
+    if ($table_name == 'analysis' and $column_name == 'program') {
       $base_info['description'] = 'The program name (e.g. blastx, blastp, sim4, genscan. If the analysis was not derived from a software package then provide a very brief description of the pipeline, workflow or method.';
       $base_info['label'] = 'Program, Pipeline, Workflow or Method Name';
     }
-    elseif ($table_name == 'analysis' and $column_name == 'algorithm') {
+    if ($table_name == 'analysis' and $column_name == 'algorithm') {
       $base_info['label'] = 'Source Version';
       $base_info['description'] = 'The name of the algorithm used to produce the dataset if different from the program.';
     }
-    elseif ($table_name == 'analysis' and $column_name == 'programversion') {
+    if ($table_name == 'analysis' and $column_name == 'programversion') {
       $base_info['label'] = 'Program Version';
       $base_info['description'] = 'The version of the program used to perform this analysis. (e.g. TBLASTX 2.0MP-WashU [09-Nov-2000]. Enter "n/a" if no version is available or applicable.';
     }
-    elseif ($table_name == 'analysis' and $column_name == 'timeexecuted') {
+    if ($table_name == 'analysis' and $column_name == 'timeexecuted') {
       $base_info['label'] = 'Date Performed';
       $base_info['description'] = 'The date and time when the analysis was performed.';
     }
@@ -876,7 +949,7 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
     //
     // PROJECT TABLE
     //
-    elseif ($table_name == 'project' and $column_name == 'description') {
+    if ($table_name == 'project' and $column_name == 'description') {
       $base_info['label'] = 'Short Description';
     }
     $info[$field_name] = $base_info;
@@ -901,12 +974,8 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
   $type_value = $details['chado_type_value'];
   $schema = chado_get_schema($table_name);
 
-  // BASE TYPE_ID
-  // Exclude the type_id field on base tables where content types are mapped
-  // by the type_id field or on the organism table where the type_id is mean
-  // as an infraspecific name type.
-  if (array_key_exists('type_id', $schema['fields']) and
-      $table_name != 'organism' and $type_column != 'type_id') {
+  // An additional type for publications
+  if ($table_name == 'pub') {
     $field_name = 'schema__additional_type';
     $is_required = FALSE;
     if (array_key_exists('not null', $schema['fields']['type_id']) and
@@ -917,14 +986,16 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
       'field_name' => $field_name,
       'entity_type' => $entity_type,
       'bundle' => $bundle->name,
-      'label' => 'Type',
-      'description' => 'Select a type for this record.',
+      'label' => 'Publication Type',
+      'description' => 'Select the publication type.',
       'required' => $is_required,
       'settings' => array(
         'auto_attach' => TRUE,
         'chado_table' => $table_name,
         'chado_column' => 'type_id',
         'base_table' => $table_name,
+        'vocabulary' => 'tripal_pub',
+        'parent_term' => 'TPUB:0000015',
       ),
       'widget' => array(
         'type' => 'schema__additional_type_widget',
@@ -1212,71 +1283,71 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
   }
 
 
-  // GENE TRANSCRIPTS
-  $rel_table = $table_name . '_relationship';
-  if (chado_table_exists($rel_table) and $bundle->label == 'gene') {
-    $field_name = 'so__transcript';
-    $info[$field_name] = array(
-      'field_name' => $field_name,
-      'entity_type' => $entity_type,
-      'bundle' => $bundle->name,
-      'label' => 'Transcripts',
-      'description' => 'Transcripts that are part of this gene.',
-      'required' => FALSE,
-      'settings' => array(
-        'auto_attach' => FALSE,
-        'chado_table' => $rel_table,
-        'chado_column' => '',
-        'base_table' => $table_name,
-      ),
-      'widget' => array(
-        'type' => 'so__transcript_widget',
-        'settings' => array(
-          'display_label' => 1,
-        ),
-      ),
-      'display' => array(
-        'default' => array(
-          'label' => 'above',
-          'type' => 'so__transcript_formatter',
-          'settings' => array(),
-        ),
-      ),
-    );
-  }
-
-  // ORGANISM TYPE_ID
-//   if ($table_name == 'organism' and array_key_exists('type_id', $schema['fields'])) {
-//     $field_name = 'taxarank__infraspecific_taxon';
+//   // GENE TRANSCRIPTS
+//   $rel_table = $table_name . '_relationship';
+//   if (chado_table_exists($rel_table) and $bundle->label == 'gene') {
+//     $field_name = 'so__transcript';
 //     $info[$field_name] = array(
 //       'field_name' => $field_name,
 //       'entity_type' => $entity_type,
 //       'bundle' => $bundle->name,
-//       'label' => 'Infraspecific Taxon',
-//       'description' => 'The Infraspecific Taxon.',
+//       'label' => 'Transcripts',
+//       'description' => 'Transcripts that are part of this gene.',
 //       'required' => FALSE,
 //       'settings' => array(
-//         'auto_attach' => TRUE,
-//         'chado_table' => 'organism',
-//         'chado_column' => 'type_id',
-//         'base_table' => 'organism',
+//         'auto_attach' => FALSE,
+//         'chado_table' => $rel_table,
+//         'chado_column' => '',
+//         'base_table' => $table_name,
 //       ),
 //       'widget' => array(
-//         'type' => 'taxarank__infraspecific_taxon_widget',
+//         'type' => 'so__transcript_widget',
 //         'settings' => array(
 //           'display_label' => 1,
 //         ),
 //       ),
 //       'display' => array(
 //         'default' => array(
-//           'label' => 'inline',
-//           'type' => 'taxarank__infraspecific_taxon_formatter',
+//           'label' => 'above',
+//           'type' => 'so__transcript_formatter',
 //           'settings' => array(),
 //         ),
 //       ),
 //     );
 //   }
 
+  // ORGANISM TYPE_ID
+  if ($table_name == 'organism' and array_key_exists('type_id', $schema['fields'])) {
+    $field_name = 'taxrank__infraspecific_taxon';
+    $info[$field_name] = array(
+      'field_name' => $field_name,
+      'entity_type' => $entity_type,
+      'bundle' => $bundle->name,
+      'label' => 'Infraspecific Taxon',
+      'description' => 'The Infraspecific Taxon.',
+      'required' => FALSE,
+      'settings' => array(
+        'auto_attach' => TRUE,
+        'chado_table' => 'organism',
+        'chado_column' => 'type_id',
+        'base_table' => 'organism',
+      ),
+      'widget' => array(
+        'type' => 'taxrank__infraspecific_taxon_widget',
+        'settings' => array(
+          'display_label' => 1,
+        ),
+      ),
+      'display' => array(
+        'default' => array(
+          'label' => 'inline',
+          'type' => 'taxrank__infraspecific_taxon_formatter',
+          'settings' => array(),
+        ),
+      ),
+    );
+  }
+
 
   // FEATURE SEQLEN
   if ($table_name == 'featuremap') {
@@ -1555,75 +1626,75 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
     );
   }
 
-  // GENOTYPE
-  $genotype_table = $table_name . '_genotype';
-  if (chado_table_exists($genotype_table)) {
-    $field_name = 'so__genotype';
-    $schema = chado_get_schema($genotype_table);
-    $pkey = $schema['primary key'][0];
-    $info[$field_name] = array(
-      'field_name' => $field_name,
-      'entity_type' => $entity_type,
-      'bundle' => $bundle->name,
-      'label' => 'Genotype',
-      'description' => 'The genotypes associated with this record.',
-      'required' => FALSE,
-      'settings' => array(
-        'auto_attach' => FALSE,
-        'chado_table' => $genotype_table,
-        'chado_column' => $pkey,
-        'base_table' => $table_name,
-      ),
-      'widget' => array(
-        'type' => 'so__genotype_widget',
-        'settings' => array(
-          'display_label' => 1,
-        ),
-      ),
-      'display' => array(
-        'default' => array(
-          'label' => 'hidden',
-          'type' => 'so__genotype_formatter',
-          'settings' => array(),
-        ),
-      ),
-    );
-  }
+//   // GENOTYPE
+//   $genotype_table = $table_name . '_genotype';
+//   if (chado_table_exists($genotype_table)) {
+//     $field_name = 'so__genotype';
+//     $schema = chado_get_schema($genotype_table);
+//     $pkey = $schema['primary key'][0];
+//     $info[$field_name] = array(
+//       'field_name' => $field_name,
+//       'entity_type' => $entity_type,
+//       'bundle' => $bundle->name,
+//       'label' => 'Genotype',
+//       'description' => 'The genotypes associated with this record.',
+//       'required' => FALSE,
+//       'settings' => array(
+//         'auto_attach' => FALSE,
+//         'chado_table' => $genotype_table,
+//         'chado_column' => $pkey,
+//         'base_table' => $table_name,
+//       ),
+//       'widget' => array(
+//         'type' => 'so__genotype_widget',
+//         'settings' => array(
+//           'display_label' => 1,
+//         ),
+//       ),
+//       'display' => array(
+//         'default' => array(
+//           'label' => 'hidden',
+//           'type' => 'so__genotype_formatter',
+//           'settings' => array(),
+//         ),
+//       ),
+//     );
+//   }
 
-  // PHENOTYPE
-  $phenotype_table = $table_name . '_phenotype';
-  if (chado_table_exists($phenotype_table)) {
-    $field_name = 'sbo__phenotype';
-    $schema = chado_get_schema($phenotype_table);
-    $pkey = $schema['primary key'][0];
-    $info[$field_name] = array(
-      'field_name' => $field_name,
-      'entity_type' => $entity_type,
-      'bundle' => $bundle->name,
-      'label' => 'Phenotype',
-      'description' => 'The phenotypes associated with this record.',
-      'required' => FALSE,
-      'settings' => array(
-        'auto_attach' => FALSE,
-        'chado_table' => $phenotype_table,
-        'chado_column' => $pkey,
-        'base_table' => $table_name,
-      ),
-      'widget' => array(
-        'type' => 'sbo__phenotype_widget',
-        'settings' => array(
-          'display_label' => 1,
-        ),
-      ),
-      'display' => array(
-        'default' => array(
-          'label' => 'hidden',
-          'type' => 'sbo__phenotype_formatter',
-          'settings' => array(),
-        ),
-      ),
-    );
-  }
+//   // PHENOTYPE
+//   $phenotype_table = $table_name . '_phenotype';
+//   if (chado_table_exists($phenotype_table)) {
+//     $field_name = 'sbo__phenotype';
+//     $schema = chado_get_schema($phenotype_table);
+//     $pkey = $schema['primary key'][0];
+//     $info[$field_name] = array(
+//       'field_name' => $field_name,
+//       'entity_type' => $entity_type,
+//       'bundle' => $bundle->name,
+//       'label' => 'Phenotype',
+//       'description' => 'The phenotypes associated with this record.',
+//       'required' => FALSE,
+//       'settings' => array(
+//         'auto_attach' => FALSE,
+//         'chado_table' => $phenotype_table,
+//         'chado_column' => $pkey,
+//         'base_table' => $table_name,
+//       ),
+//       'widget' => array(
+//         'type' => 'sbo__phenotype_widget',
+//         'settings' => array(
+//           'display_label' => 1,
+//         ),
+//       ),
+//       'display' => array(
+//         'default' => array(
+//           'label' => 'hidden',
+//           'type' => 'sbo__phenotype_formatter',
+//           'settings' => array(),
+//         ),
+//       ),
+//     );
+//   }
 
   // PROPERTIES
   $prop_table = $table_name . 'prop';
@@ -1695,6 +1766,13 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
        while ($prop = $props->fetchObject()) {
 
          $term = chado_generate_var('cvterm', array('cvterm_id' => $prop->type_id));
+         $term = chado_expand_var($term, 'field', 'cvterm.definition');
+
+         // Skip the Publiation Type property for pubs as this is
+         // already handled by the pub.type_id field.
+         if ($term->name == 'Publication Type' and $term->cv_id->name == 'tripal_pub') {
+           continue;
+         }
 
          // The tripal_analysis_KEGG, tripal_analysis_blast, and
          // tripal_analysis_interpro modules store results in the analysisprop
@@ -1738,10 +1816,63 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
              ),
            ),
          );
+
+         // Make some customizations to some fields
+         if ($term->name == 'Citation' and $term->cv_id->name == 'tripal_pub') {
+           $info[$field_name]['required'] = TRUE;
+           $info[$field_name]['description'] = t('All publications must have a unique citation.
+            Please enter the full citation for this publication.  For PubMed style citations list
+            the last name of the author followed by initials. Each author should be separated by a comma. Next comes
+            the title, followed by the series title (e.g. journal name), publication date (4 digit year, 3 character Month, day), volume, issue and page numbers. You may also use HTML to provide a link in the citation.
+            Below is an example: <pre>Medeiros PM, Ladio AH, Santos AM, Albuquerque UP. <a href="http://www.ncbi.nlm.nih.gov/pubmed/23462414" target="_blank">Does the selection of medicinal plants by Brazilian local populations
+              suffer taxonomic influence?</a> J Ethnopharmacol. 2013 Apr 19; 146(3):842-52.</pre>');
+           $info[$field_name]['settings']['rows'] = 3;
+         }
+         if ($term->name == 'Abstract' and $term->cv_id->name == 'tripal_pub') {
+           $info[$field_name]['settings']['rows'] = 5;
+         }
        }
      }
    }
 
+  // CVTERMS
+  $term_table = $table_name . '_cvterm';
+  if (chado_table_exists($term_table)) {
+    $schema = chado_get_schema($term_table);
+    $pkey = $schema['primary key'][0];
+    $lkey = key($schema['foreign keys'][$table_name]['columns']);
+    $rkey = $schema['foreign keys'][$table_name]['columns'][$lkey];
+
+    $field_name = 'sio__annotation';
+    $info[$field_name] = array(
+      'field_name' => $field_name,
+      'entity_type' => $entity_type,
+      'bundle' => $bundle->name,
+      'label' => 'Annotations',
+      'description' => 'Annotations that are associated with this record.',
+      'required' => FALSE,
+      'settings' => array(
+        'auto_attach' => FALSE,
+        'chado_table' => $term_table,
+        'chado_column' => $pkey,
+        'base_table' => $table_name,
+      ),
+      'widget' => array(
+        'type' => 'sio__annotation_widget',
+        'settings' => array(
+          'display_label' => 1,
+        ),
+      ),
+      'display' => array(
+        'default' => array(
+          'label' => 'hidden',
+          'type' => 'sio__annotation_formatter',
+          'settings' => array(),
+        ),
+      ),
+    );
+  }
+
 
   // PUBLICATIONS
   $pub_table = $table_name . '_pub';

+ 2 - 4
tripal_chado/includes/tripal_chado.publish.inc

@@ -2,8 +2,7 @@
 
 function tripal_chado_publish_form($form, &$form_state) {
 
-  global $language;
-  $langcode = $language->language;
+  $langcode = 'und';
 
   $bundle_name = '';
   if (array_key_exists('values', $form_state)) {
@@ -141,9 +140,8 @@ function tripal_chado_publish_form_validate($form, &$form_state) {
 function tripal_chado_publish_form_submit($form, &$form_state) {
   if ($form_state['clicked_button']['#name'] == 'publish_btn') {
     global $user;
-    global $language;
 
-    $langcode = $language->language;
+    $langcode = 'und';
 
     $bundle_name = $form_state['values']['bundle_name'];
     $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));

+ 61 - 13
tripal_chado/includes/tripal_chado.semweb.inc

@@ -27,6 +27,7 @@ function tripal_chado_populate_chado_semweb_table() {
   tripal_chado_populate_vocab_IAO();
   tripal_chado_populate_vocab_LOCAL();
   tripal_chado_populate_vocab_NCBITAXON();
+  tripal_chado_populate_vocab_OBCS();
   tripal_chado_populate_vocab_OBI();
   tripal_chado_populate_vocab_OGI();
   tripal_chado_populate_vocab_RDFS();
@@ -201,7 +202,7 @@ function tripal_chado_populate_vocab_SCHEMA() {
     'cv_name' => 'schema',
     'definition' => 'URL of the item.',
   ));
-  tripal_associate_chado_semweb_term('db', 'URL', $term);
+  tripal_associate_chado_semweb_term('db', 'url', $term);
 
   // Typically the type_id field is used for distinguishing between records
   // but in the case that it isn't then we need to associate a term with it
@@ -270,6 +271,34 @@ function tripal_chado_populate_vocab_SIO() {
   ));
   tripal_associate_chado_semweb_term('featurepos', 'mappos', $term);
 
+  $term = tripal_insert_cvterm(array(
+    'id' => 'SIO:001166',
+    'name' => 'annotation',
+    'cv_name' => 'SIO',
+    'definition' => 'An annotation is a written explanatory or critical description, or other in-context information (e.g., pattern, motif, link), that has been associated with data or other types of information.',
+  ));
+  tripal_associate_chado_semweb_term('feature_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('analysis_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('cell_line_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('environment_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('expression_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('library_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('organism_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('phenotype_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('stock_cvterm', 'cvterm_id', $term);
+  tripal_associate_chado_semweb_term('stock_relationship_cvterm', 'cvterm_id', $term);
+
+
+  $term = tripal_insert_cvterm(array(
+    'id' => 'SIO:000281',
+    'name' => 'negation',
+    'cv_name' => 'SIO',
+    'definition' => 'NOT is a logical operator in that has the value true if its operand is false.',
+  ));
+  tripal_associate_chado_semweb_term('feature_cvterm', 'is_not', $term);
+  tripal_associate_chado_semweb_term('analysis_cvterm', 'is_not', $term);
+  tripal_associate_chado_semweb_term('organism_cvterm', 'is_not', $term);
+  tripal_associate_chado_semweb_term('stock_cvterm', 'is_not', $term);
 
   $term = tripal_insert_cvterm(array(
     'id' => 'SIO:001080',
@@ -513,6 +542,29 @@ function tripal_chado_populate_vocab_ERO() {
   ));
 }
 
+/**
+ * Adds the Information Artifact Ontology database and terms.
+ */
+function tripal_chado_populate_vocab_OBCS() {
+  tripal_insert_db(array(
+    'name' => 'OBCS',
+    'description' => 'Ontology of Biological and Clinical Statistics.',
+    'url' => 'https://github.com/obcs/obcs',
+    'urlprefix' => 'http://purl.obolibrary.org/obo/{db}_{accession}',
+  ));
+  tripal_insert_cv(
+    'OBCS',
+    'Ontology of Biological and Clinical Statistics.'
+  );
+
+  $term = tripal_insert_cvterm(array(
+    'id' => 'OBCS:0000117',
+    'name' => 'rank order',
+    'cv_name' => 'OBCS',
+    'definition' => 'A data item that represents an arrangement according to a rank, i.e., the position of a particular case relative to other cases on a defined scale.',
+  ));
+  tripal_associate_chado_semweb_term(NULL, 'rank', $term);
+}
 /**
  * Adds the Information Artifact Ontology database and terms.
  */
@@ -569,7 +621,7 @@ function tripal_chado_populate_vocab_IAO() {
     'name' => 'IAO',
     'description' => 'The Information Artifact Ontology (IAO).',
     'url' => 'https://github.com/information-artifact-ontology/IAO/',
-    'urlprefix' => 'http://purl.obolibrary.org/obo/IAO_',
+    'urlprefix' => 'http://purl.obolibrary.org/obo/{db}_{accession}',
   ));
   tripal_insert_cv(
     'IAO',
@@ -838,6 +890,13 @@ function tripal_chado_populate_vocab_LOCAL() {
   ));
   tripal_associate_chado_semweb_term(NULL, 'is_obsolete', $term);
 
+  $term = tripal_insert_cvterm(array(
+    'id' => 'local:miniref',
+    'name' => 'Mini-ref',
+    'definition' => 'A small in-house unique identifier for a publication.',
+    'cv_name' => 'local',
+  ));
+  tripal_associate_chado_semweb_term('pub', 'miniref', $term);
 
   //-----------------------------
   // Relationship Terms
@@ -1197,17 +1256,6 @@ function tripal_chado_populate_vocab_LOCAL() {
   ));
   tripal_associate_chado_semweb_term('featureloc', 'fmax', $term);
 
-  $term = tripal_insert_cvterm(array(
-    'id' => 'local:unknown_fmin',
-    'name' => ' minimal boundary',
-    'definition' => 'The leftmost, minimal boundary in the linear range ' .
-    'represented by the feature location. Sometimes this is called ' .
-    'start although this is confusing because it does not necessarily ' .
-    'represent the 5-prime coordinate.',
-    'cv_name' => 'local',
-  ));
-  tripal_associate_chado_semweb_term('featureloc', 'fmin', $term);
-
 
   //--------------
   // Analysis Terms

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

@@ -2,6 +2,7 @@
 
 .synonym-widget-item,
 .primary-dbxref-widget-item,
+.sio--annotation-item,
 .secondary-dbxref-widget-item,
 .kvproperty-adder-widget-item,
 .chado-linker--relationship-widget-item,

+ 130 - 0
tripal_chado/tripal_chado.install

@@ -1071,4 +1071,134 @@ function tripal_chado_update_7308() {
   }
 }
 
+/**
+ * Add cvterm 'annotation' and maps to cvterm linking tables.
+ */
+function tripal_chado_update_7309() {
+  try {
+    $term = tripal_insert_cvterm(array(
+      'id' => 'SIO:001166',
+      'name' => 'annotation',
+      'cv_name' => 'SIO',
+      'definition' => 'An annotation is a written explanatory or critical description, or other in-context information (e.g., pattern, motif, link), that has been associated with data or other types of information.',
+    ));
+    tripal_associate_chado_semweb_term('feature_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('analysis_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('cell_line_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('environment_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('expression_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('library_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('organism_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('phenotype_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('stock_cvterm', 'cvterm_id', $term);
+    tripal_associate_chado_semweb_term('stock_relationship_cvterm', 'cvterm_id', $term);
+
+
+    $term = tripal_insert_cvterm(array(
+      'id' => 'SIO:000281',
+      'name' => 'negation',
+      'cv_name' => 'SIO',
+      'definition' => 'NOT is a logical operator in that has the value true if its operand is false.',
+    ));
+    tripal_associate_chado_semweb_term('feature_cvterm', 'is_not', $term);
+    tripal_associate_chado_semweb_term('analysis_cvterm', 'is_not', $term);
+    tripal_associate_chado_semweb_term('organism_cvterm', 'is_not', $term);
+    tripal_associate_chado_semweb_term('stock_cvterm', 'is_not', $term);
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+/**
+ * Adds the 'OBCS' and rank order term.
+ */
+function tripal_chado_update_7310() {
+  try {
+    tripal_insert_db(array(
+      'name' => 'OBCS',
+      'description' => 'Ontology of Biological and Clinical Statistics.',
+      'url' => 'https://github.com/obcs/obcs',
+      'urlprefix' => 'http://purl.obolibrary.org/obo/{db}_{accession}',
+    ));
+    tripal_insert_cv(
+        'OBCS',
+        'Ontology of Biological and Clinical Statistics.'
+    );
+
+    $term = tripal_insert_cvterm(array(
+      'id' => 'OBCS:0000117',
+      'name' => 'rank order',
+      'cv_name' => 'OBCS',
+      'definition' => 'A data item that represents an arrangement according to a rank, i.e., the position of a particular case relative to other cases on a defined scale.',
+    ));
+    tripal_associate_chado_semweb_term(NULL, 'rank', $term);
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+/**
+ * Fix a mistake with the association of the term with featureloc.fmin.
+ */
+function tripal_chado_update_7311() {
+  try {
+    $term = tripal_insert_cvterm(array(
+      'id' => 'local:fmin',
+      'name' => 'minimal boundary',
+      'definition' => 'The leftmost, minimal boundary in the linear range ' .
+        'represented by the feature location. Sometimes this is called ' .
+        'start although this is confusing because it does not necessarily ' .
+        'represent the 5-prime coordinate.',
+      'cv_name' => 'local',
+    ));
+    tripal_associate_chado_semweb_term('featureloc', 'fmin', $term);
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+/**
+ * Associates a local term with the pub.miniref column.
+ */
+function tripal_chado_update_7312() {
+  try {
 
+    $term = tripal_insert_cvterm(array(
+      'id' => 'local:miniref',
+      'name' => 'Mini-ref',
+      'definition' => 'A small in-house unique identifier for a publication.',
+      'cv_name' => 'local',
+    ));
+    tripal_associate_chado_semweb_term('pub', 'miniref', $term);
+    $term = tripal_insert_cvterm(array(
+      'id' => 'schema:url',
+      'name' => 'url',
+      'cv_name' => 'schema',
+      'definition' => 'URL of the item.',
+    ));
+    tripal_associate_chado_semweb_term('db', 'url', $term);
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+
+/**
+ * Run the cvtermpath for the Tripal Pub and Contact ontologies.
+ */
+function tripal_chado_update_7313() {
+  try {
+    $cv = tripal_get_cv(array('name' => 'tripal_pub'));
+    tripal_update_cvtermpath($cv->cv_id);
+    $cv = tripal_get_cv(array('name' => 'tripal_contact'));
+    tripal_update_cvtermpath($cv->cv_id);
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}

+ 1 - 1
tripal_chado/tripal_chado.module

@@ -644,7 +644,7 @@ function tripal_chado_menu() {
     'title' => 'Edit a Database Reference',
     'description' => 'Edit existing Database References.',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('tripal_chado_db_edit_form',6),
+    'page arguments' => array('tripal_chado_db_edit_form', 5),
     'access callback' => 'user_access',
     'access arguments' => array('administer db cross-references'),
     'file' => 'includes/tripal_chado.db.inc',

+ 0 - 4
tripal_chado/tripal_chado.views_default.inc

@@ -506,10 +506,6 @@ function tripal_chado_defaultview_admin_cvs_listing() {
     'label-1' => 'Add Vocabulary',
     'path-1' => 'admin/tripal/loaders/chado_cv/add',
   );
-  $handler->display->display_options['header']['action_links_area']['link-2'] = array(
-    'label-2' => 'Load Ontology',
-    'path-2' => 'admin/tripal/vocab/obo_loader',
-  );
   /* No results behavior: Global: Text area */
   $handler->display->display_options['empty']['text']['id'] = 'area';
   $handler->display->display_options['empty']['text']['table'] = 'views';

+ 11 - 3
tripal_ds/includes/tripal_ds.ds.inc

@@ -300,10 +300,10 @@ function _ds_layout_pub_settings_info($bundle_name, $instances) {
         field_update_instance($instance);
       }
       elseif($instance_name == 'tpub__publication_type' || $instance_name == 'tpub__doi' || $instance_name == 'tpub__publication_date') {
-        array_push($properties, $instance);
+        array_push($properties, $instance_name);
       }
       else {
-        array_push($disabled_instances, $instance);
+        array_push($disabled_instances, $instance_name);
       }
 
     }
@@ -317,7 +317,7 @@ function _ds_layout_pub_settings_info($bundle_name, $instances) {
       // updating fields here to ensure name consistency.
       $group_field_name = substr($group_field_name, 0, 27);
 
-      // Add randomm numbers to ensure the field name is unique within the 32
+      // Add random numbers to ensure the field name is unique within the 32
       // character limit of the field.
       $group_field_name = $group_field_name.rand(0, 99999);
 
@@ -337,6 +337,11 @@ function _ds_layout_pub_settings_info($bundle_name, $instances) {
       $region_right = array_merge($region_right, $properties);
       $all_fields+= [ 'group_prop_tripalpane' => 'right', 'group_prop_table' => 'right' ];
     }
+    if(!empty($all_fields)){
+      foreach ($disabled_instances as $disabled_field) {
+        $all_fields += [$disabled_field => 'disabled'];
+      }
+    }
 
     // Add blocks to $region_left and build the toc field that is placed within.
     _ds_fields_info_write($bundle_name);
@@ -372,6 +377,9 @@ function _ds_layout_pub_settings_info($bundle_name, $instances) {
     );
     $record->settings = $settings;
     drupal_write_record('ds_layout_settings', $record);
+    // Clear the Drpual chace
+    cache_clear_all();
+    watchdog('debug', '<pre>$record: '. print_r($record, TRUE) .'</pre>');
   }
   catch (Exception $e) {
     watchdog_exception('tripal_ds', $e);

+ 0 - 1
tripal_ds/tripal_ds.install

@@ -79,7 +79,6 @@ function tripal_ds_disable(){
   $output .= '</ul>';
   if(!empty($entities_with_tripal_panes)){
     drupal_set_message($output, 'warning');
-
   }
 }
 

+ 16 - 5
tripal_ws/includes/TripalWebService.inc

@@ -62,7 +62,7 @@ class TripalWebService {
    */
   public function __construct($base_path) {
     if (!$base_path) {
-      throw new Exception('Pleaes provide a $base_path argument when creating a new TripalWebService.');
+      throw new Exception('Please provide a $base_path argument when creating a new TripalWebService.');
     }
 
     // Create a default resource so that the service always some something.
@@ -190,11 +190,7 @@ class TripalWebService {
       '@id' => '',
       '@type' => $type,
     );
-
-    // Get the data array and set the IRIs fore each ID.
     $data = $this->getData();
-    //$this->setIDs($data);
-
     return array_merge($json_ld, $data);
   }
 
@@ -255,4 +251,19 @@ class TripalWebService {
     $this->resource->addContextItem('error', 'rdfs:error');
     $this->resource->addProperty('error', $message);
   }
+
+  /**
+   * Retrieves an array contining the supported classes.
+   *
+   * Supported classe are resources provided by this web services and the
+   * operations supported by those classes.
+   *
+   * @return
+   *   An array of TripalWebServiceResource objects that follow the Hydra
+   *   documentation for documenting supported classes.
+   */
+  public function getSupportedClasses() {
+     $supported_classes = array();
+     return $supported_classes;
+  }
 }

+ 0 - 19
tripal_ws/includes/TripalWebService/TripalDocService_V0_1.inc

@@ -1,19 +0,0 @@
-<?php
-
-class TripalDocService_v0_1 extends TripalWebService {
-
-  /**
-   * The human-readable label for this web service.
-   */
-  public static $label = 'API Documentation';
-  /**
-   * A bit of text to describe what this service provides.
-   */
-  public static $description = 'Provides documentation for the use of this web services.';
-  /**
-   * A machine-readable type for this service. This name must be unique
-   * among all Tripal web services and is used to form the URL to access
-   * this service.
-   */
-  public static $type = 'doc';
-}

+ 546 - 116
tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc

@@ -6,13 +6,15 @@ class TripalEntityService_v0_1 extends TripalWebService {
    * The human-readable label for this web service.
    */
   public static $label = 'Content Types';
+
   /**
    * A bit of text to describe what this service provides.
    */
-  public static $description = 'Provides acesss to the biological and ' .
-    'ancilliary data available on this site. Each content type represents ' .
-    'biological data that is defined in a controlled vocabulary (e.g. ' .
-    'Sequence Ontology term: gene (SO:0000704)).';
+  public static $description = 'Provides acesss to the biological and
+    ancilliary data available on this site. Each content type represents
+    biological data that is defined in a controlled vocabulary (e.g.
+    Sequence Ontology term: gene (SO:0000704)).';
+
   /**
    * A machine-readable type for this service. This name must be unique
    * among all Tripal web services and is used to form the URL to access
@@ -61,7 +63,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
    */
   private function doExpandedField($ctype, $entity_id, $expfield) {
     $service_path = $this->getServicePath() . '/' . urlencode($ctype) . '/' . $entity_id;
-    $this->resource = new TripalWebServiceResource($service_path);
+    $this->resource = new TripalWebServiceResource($service_path, $this->doc_path);
 
     // Get the TripalBundle, TripalTerm and TripalVocab for this type.
     $bundle = tripal_load_bundle_entity(array('label' => $ctype));
@@ -75,9 +77,13 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
     // If we couldn't match this field argument to a field and entity then return
     if (!$entity) {
-      throw new Exception("Cannot find this entity.");
+      throw new Exception("Cannot find this record.");
     }
 
+    // Check that the user has access to this entity.  If not then the
+    // function call will throw an error.
+    $this->checkAccess($entity);
+
     list($field, $instance, $term) = $this->findField($bundle, $expfield);
 
     // Next add in the ID and Type for this resources.
@@ -129,10 +135,19 @@ class TripalEntityService_v0_1 extends TripalWebService {
     // Add the vocabulary to the context.
     $this->resource->addContextItem($term->name, $term->url);
 
-    // Get the TripalEntity
+    // Get the TripalEntity.
     $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
     $entity = reset($entity);
 
+    // If we couldn't match this field argument to a field and entity then return
+    if (!$entity) {
+      throw new Exception("Cannot find this record.");
+    }
+
+    // Check that the user has access to this entity.  If not then the
+    // function call will throw an error.
+    $this->checkAccess($entity);
+
     $itemPage = tripal_get_term_details('schema', 'ItemPage');
     $label = tripal_get_term_details('rdfs', 'label');
     $this->resource->setID($entity_id);
@@ -148,6 +163,29 @@ class TripalEntityService_v0_1 extends TripalWebService {
 //    tripal_ws_services_v0_1_write_context($response, $ctype);
   }
 
+  /**
+   * Ensures that user's only have access to content they should see.
+   *
+   * Denies access to an entity if it is unpublished or if the user does
+   * not have permission to see it.
+   *
+   * @param $entity
+   *   The full entity object.
+   *
+   * @throws Exception
+   */
+  private function checkAccess($entity) {
+    global $user;
+
+    if (!tripal_entity_access('view', $entity, $user, 'TripalEntity')) {
+      throw new Exception("Permission Denied.");
+    }
+    // Don't show entities that aren't published
+    if ($entity->status == 0) {
+      throw new Exception("This record is currently unavailable.");
+    }
+  }
+
   /**
    * Adds the fields as properties of an entity resource.
    */
@@ -286,7 +324,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
       // If this is the expanded field page then we need to swap out
       // the resource for a collection.
-      $response = new TripalWebServiceCollection($service_path . '/' . urlencode($expfield));
+      $response = new TripalWebServiceCollection($service_path . '/' . urlencode($expfield), $this->params);
       $label = tripal_get_term_details('rdfs', 'label');
       $response->addContextItem('label', $label['url']);
       $response->addProperty('label', $instance['label']);
@@ -327,10 +365,12 @@ class TripalEntityService_v0_1 extends TripalWebService {
     if (is_array($value)) {
       $temp = array();
       foreach ($value as $k => $v) {
+
         // exclude fields that have no values so we can hide them
-        if (empty($v) and $hide_fields == 'hide') {
+        if (!isset($v) and $hide_fields == 'hide') {
           continue;
         }
+
         $matches = array();
         if (preg_match('/^(.+):(.+)$/', $k, $matches)) {
           $vocabulary = $matches[1];
@@ -342,7 +382,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
             $temp[$key_adj] = $this->sanitizeFieldKeys($v, $bundle, $service_path);
           }
           else {
-            $temp[$key_adj] = $v !== "" ? $v : NULL;
+            $temp[$key_adj] = $v;
           }
           $this->resource->addContextItem($key_adj, $term['url']);
 
@@ -360,7 +400,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
     }
     else {
-      $new_value = $value !== "" ? $value : NULL;
+      $new_value = $value;
     }
 
     return $new_value;
@@ -394,22 +434,16 @@ class TripalEntityService_v0_1 extends TripalWebService {
   }
 
   /**
-   * Creates a collection of resources for a given type.
+   * A helper function to make it easy to map between keys and their fields.
+   *
+   * @bundle
+   *   The bundle object.  Fields attached to this bundle will be included
+   *   in the mapping array.
+   * @return
+   *   An associative arrray that maps web servcies keys to fields and
+   *   fields to web services keys (reciprocol).
    */
-  private function doContentTypeList($ctype) {
-    $service_path = $this->getServicePath() . '/' . urlencode($ctype);
-    $label = tripal_get_term_details('rdfs', 'label');
-    $this->resource = new TripalWebServiceCollection($service_path);
-    $this->resource->addContextItem('label', $label['url']);
-
-    // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
-    $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-    $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
-    $term = reset($term);
-
-    // Set the label for this collection.
-    $this->resource->addProperty('label', $bundle->label . " collection");
-
+  private function getFieldMapping($bundle) {
     // Iterate through the fields and create a $field_mapping array that makes
     // it easier to determine which filter criteria belongs to which field. The
     // key is the label for the field and the value is the field name. This way
@@ -422,8 +456,8 @@ class TripalEntityService_v0_1 extends TripalWebService {
           if ($bundle_name == $bundle->name) {
             $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle_name);
             if (array_key_exists('term_accession', $instance['settings'])){
-              $vocabulary = $instance['settings']['term_vocabulary'];
-              $accession = $instance['settings']['term_accession'];
+                $vocabulary = $instance['settings']['term_vocabulary'];
+                $accession = $instance['settings']['term_accession'];
               $fterm = tripal_get_term_details($vocabulary, $accession);
               $key = $fterm['name'];
               $key = strtolower(preg_replace('/ /', '_', $key));
@@ -434,45 +468,171 @@ class TripalEntityService_v0_1 extends TripalWebService {
         }
       }
     }
+    return $field_mapping;
+  }
 
-    // Convert the filters to their field names
-    $new_params = array();
-    $order = array();
-    $order_dir = array();
-    $URL_add = array();
-    foreach ($this->params as $param => $value) {
-      $URL_add[] = "$param=$value";
+  /**
+   * Gets any order by statements provided by the user.
+   *
+   * @field_mapping
+   *   An array that maps WS keys to field names. As provided by the
+   *   getFieldMapping() function.
+   * @return
+   *   An array of fields for ordering.
+   *
+   * @throws Exception
+   */
+  private function getOrderBy($field_mapping,  $bundle) {
+    $order_by = array();
 
-      // Ignore non filter parameters
-      if ($param == 'page' or $param == 'limit') {
-        continue;
-      }
+    // Handle order separately.
+    if (array_key_exists('order', $this->params)) {
+      $order_params = $this->params['order'];
+      $dir = 'ASC';
 
-      // Handle order separately
-      if ($param == 'order') {
-        $temp = explode(',', $value);
-        foreach ($temp as $key) {
-          $matches = array();
-          $dir = 'ASC';
-          // The user can provide a direction by separating the field key and the
-          // direction with a '|' character.
-          if (preg_match('/^(.*)\|(.*)$/', $key, $matches)) {
-            $key = $matches[1];
-            if ($matches[2] == 'ASC' or $matches[2] == 'DESC') {
-              $dir = $matches[2];
+      // If the user provided more than one order statement then those are
+      // separated by a semicolong.
+      $items = explode(';', $order_params);
+      foreach ($items as $key) {
+
+        // The user can provide a direction by separating the field key and the
+        // direction with a '|' character.
+        $matches = array();
+        if (preg_match('/^(.*)\|(.*)$/', $key, $matches)) {
+          $key = $matches[1];
+          if ($matches[2] == 'ASC' or $matches[2] == 'DESC') {
+            $dir = $matches[2];
+          }
+          else {
+            throw new Exception('Please provide "ASC" or "DESC" for the ordering direction');
+          }
+        }
+
+        // Break apart any subkeys and pull the first one as this is the parent
+        // field.
+        $subkeys = explode(',', $key);
+        if (count($subkeys) > 0) {
+          $key = $subkeys[0];
+        }
+
+        if (array_key_exists($key, $field_mapping)) {
+          $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.
+          $field_class = $key_field['type'];
+          if (tripal_load_include_field_class($field_class)) {
+            // To find out which fields are sortable we'll call the
+            // webServicesData() function.
+            $key_field = new $field_class($key_field, $key_instance);
+            $ws_data = $key_field->webServicesData();
+            $sortable_keys = $ws_data['sortable'];
+            $criteria = implode('.', $subkeys);
+            if (array_key_exists($criteria, $sortable_keys)) {
+              $order_by[$key_field_name][] = array(
+                'column' => $sortable_keys[$criteria],
+                'dir' => $dir,
+              );
             }
             else {
-              // TODO: handle error of providing an incorrect direction.
+              throw new Exception("The value, '$criteria', is not available for sorting.");
             }
           }
-          if (array_key_exists($key, $field_mapping)) {
-            $order[$field_mapping[$key]] = $key;
-            $order_dir[] = $dir;
-          }
+          // If this field is not a TripalField then it should just have
+          // a simple value and we can query for that.
           else {
-            // TODO: handle error of providing a non existing field name.
+            $key_field_id = $key_instance['settings']['term_vocabulary'] . ':' . $key_instance['settings']['term_accession'];
+
+            $order_by[$key_field_name][] = array(
+              'column' => $key_field_id,
+              'dir' => $dir,
+            );
+          }
+
+        }
+        else {
+          throw new Exception("The value, '$key', is not available for sorting.");
+        }
+      }
+    }
+
+    // If there is no ordering that is set then set a default order.
+    if (count(array_keys($order_by)) == 0) {
+      $key_field_names = array();
+      if (in_array('data__identifier', $field_mapping)) {
+        $key_field_names['data__identifier'][] = 'identifier';
+      }
+      else if (in_array('schema__name', $field_mapping)) {
+        $key_field_names['schema__name'][] = 'name';
+      }
+      else if (in_array('rdfs_label', $field_mapping)) {
+        $key_field_names['rdfs_label'][] = 'label';
+      }
+      else if (in_array('taxrank__genus', $field_mapping)) {
+        $key_field_names['taxrank__genus'][] = 'genus';
+        $key_field_names['taxrank__species'][] = 'species';
+      }
+      foreach ($key_field_names as $key_field_name => $criteria) {
+        $key_field = field_info_field($key_field_name);
+        $key_instance = field_info_instance('TripalEntity', $key_field_name, $bundle->name);
+        $key_field_id = $key_instance['settings']['term_vocabulary'] . ':' . $key_instance['settings']['term_accession'];
+        $field_class = $key_field['type'];
+        if (tripal_load_include_field_class($field_class)) {
+          // To find out which fields are sortable we'll call the
+          // webServicesData() function.
+          $key_field = new $field_class($key_field, $key_instance);
+          $ws_data = $key_field->webServicesData();
+          $sortable_keys = $ws_data['sortable'];
+          if (array_key_exists($criteria, $sortable_keys)) {
+            $order_by[$key_field_name][] = array(
+              'column' => $sortable_keys[$criteria],
+              'dir' => $dir,
+            );
           }
         }
+       // If this field is not a TripalField then it should just have
+          // a simple value and we can query for that.
+        else {
+          $order_by[$key_field_name][] = array(
+            'column' => $key_field_id,
+            'dir' => 'ASC',
+          );
+        }
+      }
+    }
+
+    return $order_by;
+  }
+
+  /**
+   * Gets any filter by statements provided by the user.
+   *
+   * @field_mapping
+   *   An array that maps WS keys to field names. As provided by the
+   *   getFieldMapping() function.
+   *
+   * @return
+   *   An array of fields for filtering.
+   *
+   * @throws Exception
+   */
+  private function getFilters($field_mapping, $bundle) {
+    $filters = array();
+
+    // Iterate through the paramter list provided by user.
+    foreach ($this->params as $param => $value) {
+
+      // Ignore non filter parameters.
+      if ($param == 'page' or $param == 'limit') {
+        continue;
+      }
+
+      // Ignore the order parameter as that is handled by the getOrderBy()
+      // function
+      if ($param == 'order') {
         continue;
       }
 
@@ -501,16 +661,20 @@ class TripalEntityService_v0_1 extends TripalWebService {
         // 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)) {
+        $field_class = $key_field['type'];
+        if (tripal_load_include_field_class($field_class)) {
           // 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();
+          $key_field = new $field_class($key_field, $key_instance);
+          $ws_data = $key_field->webServicesData();
+          $searchable_keys = $ws_data['searchable'];
           $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];
+            $filters[$key_field_name][] = array(
+              'value' => $value,
+              'op' => $op,
+              'column' => $searchable_keys[$criteria]
+            );
           }
           else {
             throw new Exception("The filter term, '$criteria', is not available for use.");
@@ -521,9 +685,11 @@ class TripalEntityService_v0_1 extends TripalWebService {
         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;
+          $filters[$key_field_name][] = array(
+            'value' => $value,
+            'op' => $op,
+            'column' => $key_field_id,
+          );
         }
       }
       else {
@@ -531,44 +697,93 @@ class TripalEntityService_v0_1 extends TripalWebService {
       }
     }
 
-    // Get the list of entities for this bundle.
+    // Now convert the operation for each filter to one that is compatible
+    // with TripalFieldQuery.
+    foreach ($filters as $key_field_name => $key_filters) {
+      foreach ($key_filters as $i => $filter) {
+        $op = '=';
+        switch ($filters[$key_field_name][$i]['op']) {
+          case 'eq':
+            $op = '=';
+            break;
+          case 'gt':
+            $op = '>';
+            break;
+          case 'gte':
+            $op = '>=';
+            break;
+          case 'lt':
+            $op = '<';
+            break;
+          case 'lte':
+            $op = '<=';
+            break;
+          case 'ne':
+            $op = '<>';
+            break;
+          case 'contains':
+            $op = 'CONTAINS';
+            break;
+          case 'starts':
+            $op = 'STARTS WITH';
+            break;
+          default:
+            $op = '=';
+        }
+        $filters[$key_field_name][$i]['op'] = $op;
+      }
+    }
+    return $filters;
+  }
+
+  /**
+   * Creates a collection of resources for a given type.
+   */
+  private function doContentTypeList($ctype) {
+    $service_path = $this->getServicePath() . '/' . urlencode($ctype);
+    $label = tripal_get_term_details('rdfs', 'label');
+    $this->resource = new TripalWebServiceCollection($service_path, $this->params);
+    $this->resource->addContextItem('label', $label['url']);
+
+    // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
+    $bundle = tripal_load_bundle_entity(array('label' => $ctype));
+    $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
+    $term = reset($term);
+
+    // Set the label for this collection.
+    $this->resource->addProperty('label', $bundle->label . " collection");
+
+    // For quick lookup, get the mapping of WS keys to their appropriate fields.
+    $field_mapping = $this->getFieldMapping($bundle);
+
+    // Get arrays for filters and order by statements.
+    $filters = $this->getFilters($field_mapping, $bundle);
+    $order_by = $this->getOrderBy($field_mapping, $bundle);
+
+    // Initialize the query to search for records for out bundle type
+    // that are published.
     $query = new TripalFieldQuery();
     $query->entityCondition('entity_type', 'TripalEntity');
     $query->entityCondition('bundle', $bundle->name);
-    foreach($new_params as $field_name => $details) {
-      $value = $details['value'];
-      $column_name = $details['column'];
-      switch ($details['op']) {
-        case 'eq':
-          $op = '=';
-          break;
-        case 'gt':
-          $op = '>';
-          break;
-        case 'gte':
-          $op = '>=';
-          break;
-        case 'lt':
-          $op = '<';
-          break;
-        case 'lte':
-          $op = '<=';
-          break;
-        case 'ne':
-          $op = '<>';
-          break;
-        case 'contains':
-          $op = 'CONTAINS';
-          break;
-        case 'starts':
-          $op = 'STARTS WITH';
-          break;
-        default:
-          $op = '=';
-      }
-      // We pass in the $column_name as an identifier for any sub fields
-      // that are present for the fields.
-      $query->fieldCondition($field_name, $column_name, $value, $op);
+    $query->propertyCondition('status', 1);
+
+    // Now iterate through the filters and add those.
+    foreach ($filters as $key_field_name => $key_filters) {
+      foreach ($key_filters as $i => $filter) {
+         $column_name = $filter['column'];
+         $value = $filter['value'];
+         $op = $filter['op'];
+        $query->fieldCondition($key_field_name, $column_name, $value, $op);
+      }
+    }
+
+    // Now set the order by.
+    foreach ($order_by as $key_field_name => $key_order) {
+      foreach ($key_order as $i => $order) {
+        $column_name = $order['column'];
+        $dir = $order['dir'];
+        $query->fieldOrderBy($key_field_name, $column_name, $dir);
+      }
     }
 
     // Perform the query just as a count first to get the number of records.
@@ -587,12 +802,6 @@ class TripalEntityService_v0_1 extends TripalWebService {
     $total_pages = ceil($num_records / $limit);
     $page = array_key_exists('page', $this->params) ? $this->params['page'] : 1;
 
-    // Set the query order
-    $order_keys = array_keys($order);
-    for($i = 0; $i < count($order_keys); $i++) {
-      $query->fieldOrderBy($order_keys[$i], $order[$order_keys[$i]], $order_dir[$i]);
-    }
-
     // Set the query range
     $start = ($page - 1) * $limit;
     $query->range($start, $limit);
@@ -602,8 +811,14 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
     $this->resource->initPager($num_records, $limit, $page);
 
-    // Iterate through the entities and add them to the list.
-    foreach ($results['TripalEntity'] as $entity_id => $stub) {
+    // Check to make sure there are results.
+    $entity_ids = array();
+    if (isset($results['TripalEntity']) AND is_array($results['TripalEntity'])) {
+      $entity_ids = $results['TripalEntity'];
+    }
+
+    // Iterate through the entities and add them to the output list.
+    foreach ($entity_ids as $entity_id => $stub) {
       // We don't need all of the attached fields for an entity so, we'll
       // not use the entity_load() function.  Instead just pull it from the
       // database table.
@@ -634,7 +849,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
   private function doAllTypesList() {
     $service_path = $this->getServicePath();
     $label = tripal_get_term_details('rdfs', 'label');
-    $this->resource = new TripalWebServiceCollection($service_path);
+    $this->resource = new TripalWebServiceCollection($service_path, $this->params);
     $this->resource->addContextItem('label', $label['url']);
     $this->resource->addProperty('label', 'Content Types');
 
@@ -651,22 +866,237 @@ class TripalEntityService_v0_1 extends TripalWebService {
       $term = reset($entity);
       $vocab = $term->vocab;
 
+      $member = new TripalWebServiceResource($service_path);
+      // If the term has no URL then we'll default to using thie site's
+      // term lookup service.
+      $url = $term->url;
+      if (!$url) {
+        $url = url('cv/lookup/' . $term->vocab->vocabulary . '/' . $term->accession , array('absolute' => TRUE));
+      }
+      $member->addContextItem($term->name, $url);
+      $member->addContextItem('label', $label['url']);
+      $member->addProperty('label', $bundle->label);
+      $member->addContextItem('description', 'hydra:description');
       // Get the bundle description. If no description is provided then
       // use the term definition
       $description = tripal_get_bundle_variable('description', $bundle->id);
       if (!$description) {
         $description = $term->definition;
       }
-      $member = new TripalWebServiceResource($service_path);
-      $member->addContextItem($term->name, $term->url);
-      $member->addContextItem('label', $label['url']);
-      $member->addContextItem('description', 'hydra:description');
+      if (!$description) {
+        $description = '';
+      }
+      $member->addProperty('description', $description);
       $member->setID(urlencode($bundle->label));
       $member->setType($term->name);
-      $member->addProperty('label', $bundle->label);
-      $member->addProperty('description', $description);
+
       $this->resource->addMember($member);
 
     }
   }
+
+  /**
+   * @see TripalWebService::getSupportedClasses()
+   */
+  public function getSupportedClasses() {
+
+    global $user;
+
+    // An array of TripalWebServiceResources containing a
+    // description of the supported classes.
+    $supported_classes = array();
+
+    // Get the list of published terms (these are the bundle IDs)
+    $bundles = db_select('tripal_bundle', 'tb')
+      ->fields('tb')
+      ->orderBy('tb.label', 'ASC')
+      ->execute();
+
+    // Iterate through the terms and add an entry in the collection.
+    $i = 0;
+    while ($bundle = $bundles->fetchObject()) {
+      $entity =  entity_load('TripalTerm', array('id' => $bundle->term_id));
+      $term = reset($entity);
+      $vocab = $term->vocab;
+
+      // Get the bundle description. If no description is provided then
+      // use the term definition
+      $description = tripal_get_bundle_variable('description', $bundle->id);
+      if (!$description) {
+        $description = $term->definition;
+      }
+
+      $supported = new TripalWebServiceResource($this->getServicePath());
+      $supported->addContextItem('supportedOperation', 'hydra:supportedOperation');
+      $supported->addContextItem('supportedProperty', 'hydra:supportedProperty');
+      $supported->setID(urlencode($bundle->label));
+      $supported->setType('hydra:Class');
+      $supported->addProperty('hydra:title', $bundle->label);
+      $supported->addProperty('hydra:description', $description);
+
+      // Add in the supported operations for this content type.
+      $operations = array();
+
+      // If the user can view this content type.
+      if (user_access('view ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $get_op = new TripalWebServiceResource($this->getServicePath());
+        $get_op->addContextItem('supportedOperation', 'hydra:supportedOperation');
+        $get_op->addContextItem('method', 'hydra:method');
+        $get_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $get_op->addContextItem('label', 'rdfs:label');
+        $get_op->addContextItem('description', 'rdfs:comment');
+        $get_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $get_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $get_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_retrieve');
+        $get_op->setType('hydra:Operation');
+        $get_op->addProperty('method', 'GET');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $get_op->addProperty('label', "Retrieves an " . $bundle->label . " entity.");
+        }
+        else {
+          $get_op->addProperty('label', "Retrieves a " . $bundle->label . " entity.");
+        }
+        $get_op->addProperty('description', NULL);
+        $get_op->addProperty('expects', NULL);
+        $get_op->addProperty('returns', $term->url);
+        $get_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be found using the provided ID.'
+          ),
+        ));
+        $operations[] = $get_op;
+      }
+
+      // If the user can create this content type.
+      if (user_access('create ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $create_op = new TripalWebServiceResource($this->getServicePath());
+        $create_op->addContextItem('method', 'hydra:method');
+        $create_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $create_op->addContextItem('label', 'rdfs:label');
+        $create_op->addContextItem('description', 'rdfs:comment');
+        $create_op->addContextItem('code', 'hydra:statusCode');
+        $create_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $create_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $create_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_create');
+        $create_op->setType('http://schema.org/CreateAction');
+        $create_op->addProperty('method', 'POST');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $create_op->addProperty('label', "Creates an " . $bundle->label . " entity.");
+        }
+        else {
+          $create_op->addProperty('label', "Creates a " . $bundle->label . " entity.");
+        }
+        $create_op->addProperty('description', NULL);
+        $create_op->addProperty('expects', $term->url);
+        $create_op->addProperty('returns', $term->url);
+        $create_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be created.'
+          ),
+          array(
+            'code' => 409,
+            'description' => 'The ' . $bundle->label . ' already exists.'
+          ),
+        ));
+        $operations[] = $create_op;
+      }
+
+      // If the user can edit this content type.
+      if (user_access('edit ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $edit_op = new TripalWebServiceResource($this->getServicePath());
+        $edit_op->addContextItem('method', 'hydra:method');
+        $edit_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $edit_op->addContextItem('label', 'rdfs:label');
+        $edit_op->addContextItem('description', 'rdfs:comment');
+        $edit_op->addContextItem('code', 'hydra:statusCode');
+        $edit_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $edit_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $edit_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_update');
+        $edit_op->setType('http://schema.org/UpdateAction');
+        $edit_op->addProperty('method', 'PUT');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $edit_op->addProperty('label', "Update and replace an " . $bundle->label . " entity.");
+        }
+        else {
+          $edit_op->addProperty('label', "Update and replace a " . $bundle->label . " entity.");
+        }
+        $edit_op->addProperty('description', NULL);
+        $edit_op->addProperty('expects', $term->url);
+        $edit_op->addProperty('returns', $term->url);
+        $edit_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be updated using the provided ID.'
+          ),
+        ));
+        $operations[] = $edit_op;
+      }
+
+      // If the user can edit this content type.
+      if (user_access('delete ' . $bundle->name)) {
+        // All content types allow GET (if the user has view permission).
+        $delete_op = new TripalWebServiceResource($this->getServicePath());
+        $delete_op->addContextItem('method', 'hydra:method');
+        $delete_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $delete_op->addContextItem('label', 'rdfs:label');
+        $delete_op->addContextItem('description', 'rdfs:comment');
+        $delete_op->addContextItem('code', 'hydra:statusCode');
+        $delete_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $delete_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $delete_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_delete');
+        $delete_op->setType('http://schema.org/DeleteAction');
+        $delete_op->addProperty('method', 'DELETE');
+        if (preg_match('/^[aeiou]/i', $bundle->label)) {
+          $delete_op->addProperty('label', "Deletes an " . $bundle->label . " entity.");
+        }
+        else {
+          $delete_op->addProperty('label', "Deletes a " . $bundle->label . " entity.");
+        }
+        $delete_op->addProperty('description', NULL);
+        $delete_op->addProperty('expects', $term->url);
+        $delete_op->addProperty('returns', $term->url);
+        $delete_op->addProperty('statusCodes', array(
+          array(
+            'code' => 404,
+            'description' => 'The ' . $bundle->label . ' could not be deleted using the provided ID.'
+          ),
+        ));
+        $operations[] = $delete_op;
+      }
+
+      $supported->addProperty('supportedOperation', $operations);
+      $supported_classes[] = $supported;
+    }
+
+    return $supported_classes;
+  }
 }

+ 138 - 1
tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc

@@ -15,5 +15,142 @@ class TripalVocabService_v0_1 extends TripalWebService {
    * among all Tripal web services and is used to form the URL to access
    * this service.
    */
-  public static $type = 'vocabulary';
+  public static $type = 'vocab';
+
+
+  /**
+   * @see TripalWebService::handleRequest()
+   */
+  public function handleRequest() {
+    $this->resource->addContextItem('vocab', $this->getServicePath() . '#');
+
+    $this->resource->addContextItem('ApiDocumentation', 'hydra:ApiDocumentation');
+    $this->resource->addContextItem('supportedClass', 'hydra:supportedClass');
+    $this->resource->setType('ApiDocumentation');
+    $this->resource->setID('vocab/' . $this->getVersion());
+
+    // We need to list the content types as properties of the EntryPoint.
+    $properties = array();
+
+
+    // Iterate through all of the web services and get their documentation
+    $services = tripal_get_web_services();
+    foreach ($services as $service_class) {
+      tripal_load_include_web_service_class($service_class);
+      $service = new $service_class($this->base_path);
+      $supported_classes = $service->getSupportedClasses();
+      foreach ($supported_classes as $supported) {
+        $this->resource->addProperty('supportedClass', $supported);
+      }
+      if ($service_class::$type != 'vocab') {
+        $event_prop = new TripalWebServiceResource($this->getServicePath());
+        $event_prop->addProperty('hydra:title', $service_class::$type);
+        $event_prop->addProperty('hydra:description', $service_class::$description);
+        $event_prop->addContextItem('required', 'hydra:reqiured');
+        $event_prop->addProperty('required', NULL);
+        $event_prop->addContextItem('readonly', 'hydra:readonly');
+        $event_prop->addProperty('readonly', TRUE);
+        $event_prop->addContextItem('writeonly', 'hydra:writeonly');
+        $event_prop->addProperty('writeonly', FALSE);
+        $event_prop->addContextItem('property', array(
+          "@id" => "hydra:property",
+          "@type" => "@id"
+        ));
+        //$event_prop->setID('');
+        //$event_prop->setType('');
+        $prop = new TripalWebServiceResource($this->getServicePath());
+        $prop->setID('vocab:EntryPoint/' . $service_class::$type);
+        $prop->setType('hydra:Link');
+        $prop->addContextItem('label', 'rdfs:label');
+        $prop->addProperty('label', $service_class::$label);
+        $prop->addContextItem('description', 'rdfs:comment');
+        $prop->addProperty('description', $service_class::$description);
+        $prop->addContextItem('domain', array(
+          "@id" => "rdfs:domain",
+          "@type" => "@id"
+        ));
+        $prop->addProperty('domain', 'vocab:EntryPoint');
+        $prop->addContextItem('range', array(
+          "@id" => "rdfs:range",
+          "@type" => "@id"
+        ));
+        $prop->addProperty('range', 'hydra:Collection');
+
+        $prop_ops = array();
+        $prop_op = new TripalWebServiceResource($this->getServicePath());
+        $prop_op->setID('_:event_collectionretrieve');
+        $prop_op->setType('hydra:Operation');
+        $prop_op->addContextItem('method', 'hydra:method');
+        $prop_op->addProperty('method', 'GET');
+        $prop_op->addContextItem('label', 'rdfs:label');
+        $prop_op->addProperty('label', 'Retrieves all ' . $service_class::$label . ' entities.');
+        $prop_op->addContextItem('description', 'rdfs:comment');
+        $prop_op->addProperty('description', NULL);
+        $prop_op->addContextItem('expects', array(
+          "@id" => "hydra:expects",
+          "@type" => "@id"
+        ));
+        $prop_op->addProperty('expects', NULL);
+        $prop_op->addContextItem('returns', array(
+          "@id" => "hydra:returns",
+          "@type" => "@id"
+        ));
+        $prop_op->addProperty('returns', 'hydra:Collection');
+        $prop_op->addContextItem('statusCodes', 'hydra:statusCodes');
+        $prop_op->addProperty('statusCodes', array());
+        $prop_ops[] = $prop_op;
+
+        $prop->addContextItem('supportedOperation', 'hydra:supportedOperation');
+        $prop->addProperty('supportedOperation', $prop_ops);
+        $event_prop->addProperty('property', $prop);
+        $properties[] = $event_prop;
+      }
+    }
+
+    // Add in the generic supported classes.
+    $entry_class = new TripalWebServiceResource($this->base_path);
+    $entry_class->addContextItem('label', 'rdfs:label');
+    $entry_class->setID($this->getServicePath() . '#EntryPoint');
+    $entry_class->setType('hydra:Class');
+    $entry_class->addProperty('label', 'EntryPoint');
+    $entry_class->addContextItem('description', 'rdfs:comment');
+    $entry_class->addProperty('description', 'The main entry point or homepage of the API');
+    $entry_class->addContextItem('subClassOf', array(
+      "@id" => "rdfs:subClassOf",
+      "@type" => "@id"
+    ));
+    $entry_class->addProperty('subClassOf', NULL);
+    $entry_class->addContextItem('supportedOperation', 'hydra:supportedOperation');
+    $entry_class->addContextItem('supportedProperty', 'hydra:supportedProperty');
+
+
+    $operations = array();
+
+    $get_op = new TripalWebServiceResource($this->getServicePath());
+    $get_op->addContextItem('method', 'hydra:method');
+    $get_op->addProperty('method', 'GET');
+    $get_op->addContextItem('statusCodes', 'hydra:statusCodes');
+    $get_op->addProperty('statusCodes', array());
+    $get_op->addContextItem('label', 'rdfs:label');
+    $get_op->addProperty('label', "The APIs main entry point.");
+    $get_op->addContextItem('description', 'rdfs:comment');
+    $get_op->addProperty('description', NULL);
+    $get_op->addContextItem('expects', array(
+      "@id" => "hydra:expects",
+      "@type" => "@id"
+    ));
+    $get_op->addProperty('expects', NULL);
+    $get_op->addContextItem('returns', array(
+      "@id" => "hydra:returns",
+      "@type" => "@id"
+    ));
+    $get_op->addProperty('returns', "vocab:EntryPoint");
+    $get_op->setID('_:entry_point');
+    $get_op->setType('hydra:Operation');
+
+    $operations[] = $get_op;
+    $entry_class->addProperty('supportedOperation', $operations);
+    $entry_class->addProperty('supportedProperty', $properties);
+    $this->resource->addProperty('supportedClass', $entry_class);
+  }
 }

+ 28 - 6
tripal_ws/includes/TripalWebServiceCollection.inc

@@ -32,15 +32,28 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
   protected $page;
 
 
+  /**
+   * The parameters as collected by the parent TripalWebService.  We need
+   * the parameters because this Resource generates first, next, prev, and last
+   * links and we need to maintain the parameters a user may have
+   * provided in those links.
+   */
+  protected $params;
+
   /**
    * Implements the constructor.
    *
    * @param TripalWebService $service
    *   An instance of a TripalWebService or class that extends it.
    */
-  public function __construct($service_path) {
+  public function __construct($service_path, $params) {
     parent::__construct($service_path);
+
+    // Initialize private member variables.
+    $this->params = $params;
     $this->members = array();
+
+    // Add some terms to the context.
     $term = tripal_get_term_details('hydra', 'Collection');
     $this->addContextItem('Collection', $term['url']);
     $term = tripal_get_term_details('hydra', 'totalItems');
@@ -111,6 +124,15 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
 
     if ($this->doPaging == TRUE) {
 
+      // Save any parameters provided by the user
+      $saved_params = '';
+      foreach ($this->params as $pkey => $pval) {
+        if (in_array($pkey, array('page', 'limit', 'first', 'last', 'next', 'prev'))) {
+          continue;
+        }
+        $saved_params .= '&' . $pkey . '=' . $pval;
+      }
+
       $data['totalItems'] = $this->totalItems;
       $total_pages = ceil($this->totalItems / $this->itemsPerPage);
       $page = $this->page;
@@ -118,18 +140,18 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
 
       if ($this->totalItems > 0) {
         $data['view'] = array(
-          '@id' => $this->service_path . '?' . implode('&', array_merge(array("page=$page", "limit=$limit"))),
+          '@id' => $this->service_path . '?' . implode('&', array_merge(array("page=$page", "limit=$limit"))) . $saved_params,
           '@type' => 'PartialCollectionView',
-          'first' => $this->service_path . '?' . implode('&', array_merge(array("page=1", "limit=$limit"))),
-          'last' => $this->service_path . '?' . implode('&', array_merge(array("page=$total_pages", "limit=$limit"))),
+          'first' => $this->service_path . '?' . implode('&', array_merge(array("page=1", "limit=$limit"))) . $saved_params,
+          'last' => $this->service_path . '?' . implode('&', array_merge(array("page=$total_pages", "limit=$limit"))) . $saved_params,
         );
         $prev = $page - 1;
         $next = $page + 1;
         if ($prev > 0) {
-          $data['view']['previous'] = $this->service_path .'?' . implode('&', array("page=$prev", "limit=$limit"));
+          $data['view']['previous'] = $this->service_path .'?' . implode('&', array("page=$prev", "limit=$limit")) . $saved_params;
         }
         if ($next < $total_pages) {
-          $data['view']['next'] = $this->service_path . '?' . implode('&', array("page=$next", "limit=$limit"));
+          $data['view']['next'] = $this->service_path . '?' . implode('&', array("page=$next", "limit=$limit")) . $saved_params;
         }
       }
     }

+ 116 - 52
tripal_ws/includes/TripalWebServiceResource.inc

@@ -32,21 +32,21 @@ class TripalWebServiceResource {
   /**
    * Implements the constructor.
    *
-   * @param TripalWebService $service
-   *   An instance of a TripalWebService or class that extends it.
+   * @param  $service_path
+   *
    */
   public function __construct($service_path) {
+
     $this->context = array();
     $this->data = array();
     $this->service_path = $service_path;
 
     // First, add the RDFS and Hydra vocabularies to the context.  All Tripal
     // web services should use these.
-    $vocab = tripal_get_vocabulary_details('rdfs');
-    $this->addContextItem('rdfs', $vocab['url']);
+    $this->addContextItem('rdf', "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+    $this->addContextItem('rdfs', 'http://www.w3.org/2000/01/rdf-schema#');
 
-    $vocab = tripal_get_vocabulary_details('hydra');
-    $this->addContextItem('hydra', $vocab['url']);
+    $this->addContextItem('hydra', "http://www.w3.org/ns/hydra/core#");
 
     $vocab = tripal_get_vocabulary_details('dc');
     $this->addContextItem('dc', $vocab['url']);
@@ -55,7 +55,8 @@ class TripalWebServiceResource {
     $this->addContextItem('schema', $vocab['url']);
 
     $vocab = tripal_get_vocabulary_details('local');
-    $this->addContextItem('local', $vocab['url']);
+    $this->addContextItem('local', url($vocab['url'], array('absolute' => TRUE)). '/');
+
 
     $this->data['@id'] = $service_path;
     $this->data['@type'] = '';
@@ -96,16 +97,59 @@ class TripalWebServiceResource {
    *   The type
    */
   public function setType($type) {
-    $keys = array_keys($this->context);
-    if (!in_array($type, $keys)) {
-      throw new Exception("The resource type, '$type', has not yet been added to the " .
-          "context of the web service. Use the addContextItem() function of the web service " .
-          "to add this term.");
-    }
+    $this->checkKey($type);
     $this->type = $type;
     $this->data['@type'] = $type;
   }
 
+  /**
+   * Checks a key to ensure it is in the Context before being used.
+   *
+   * This function should be used before adding a property or type to this
+   * resource.  It ensures that the key for the property is already in the
+   * context.
+   *
+   * @param $key
+   *   The key to check.
+   * @throws Exception
+   */
+  private function checkKey($key) {
+    // Make sure the key is already present in the context.
+    $keys = array_keys($this->context);
+
+    // Keys that are full HTML are acceptable
+    if (preg_match('/^(http|https):\/\/.*/', $key)) {
+      return;
+    }
+
+    // If the key has a colon separating the vocabulary and the term then we
+    // just need to make sure that the vocabulary is present.
+    $matches = array();
+    if (preg_match('/^(.*?):(.*?)$/', $key, $matches)) {
+      $vocab = $matches[1];
+      $accession = $matches[2];
+
+      // The underscore represents the blank node. So, these keys are okay.
+      if ($vocab == '_') {
+        return;
+      }
+
+      // If the vocabulary is not in the.
+      if (!in_array($vocab, $keys)) {
+        throw new Exception("The key, '$key', has a vocabulary that has not yet been added to the " .
+            "context. Use the addContextItem() function to add the vocabulary prior to adding a value for it.");
+      }
+    }
+    else {
+      // If the key is not in the context then throw an error.
+      if (!in_array($key, $keys)) {
+        throw new Exception("The key, '$key', has not yet been added to the " .
+            "context. Use the addContextItem() function to add this key prior to adding a value for it.");
+      }
+    }
+  }
+
+
   /**
    * Set's the unique identifier for the resource.
    *
@@ -119,7 +163,30 @@ class TripalWebServiceResource {
    */
   public function setID($id) {
     $this->id = $id;
-    $this->data['@id'] = $this->service_path . '/' . $id;
+    $this->data['@id'] = $this->getURI($id);
+  }
+
+  /**
+   * Retreives the IRI for an entity of a given ID in this web service.
+   *
+   * @param $id
+   *   The unique identifier for the resource.
+   */
+  public function getURI($id) {
+    // If this is a key/value pair for an id and if the vocab portion is
+    // an underscore then this represents a blank node and we don't want
+    // to add the full path.
+    $matches = array();
+    if (preg_match('/^(.*?):(.*?)$/', $id, $matches)) {
+      $vocab = $matches[1];
+      if ($vocab == '_') {
+        return $id;
+      }
+      return $id;
+    }
+    else {
+      return $this->service_path . '/' . $id;
+    }
   }
 
 
@@ -152,7 +219,8 @@ class TripalWebServiceResource {
    * Adds a new key/value pair to the web serivces response.
    *
    * The value must either be a scalar or another TripalWebServiceResource
-   * object.
+   * object.  If the same key is usesd multiple times then the resulting
+   * resource will be presented as an array of elements.
    *
    * @param unknown $key
    *   The name of the $key to add. This key must already be present in the
@@ -164,49 +232,45 @@ class TripalWebServiceResource {
    */
   public function addProperty($key, $value) {
 
-    // Make sure the key is already present in the context.
-    $keys = array_keys($this->context);
-    if (!in_array($key, $keys)) {
-      throw new Exception("The key, '$key', has not yet been added to the " .
-          "context. Use the addContextItem() function to add this key prior to adding a value for it.");
+    $this->checkKey($key);
+
+    // If this is a numeric array then go through each element and add them.
+    if (is_array($value) and count(array_filter(array_keys($value), 'is_int')) == count(array_keys($value))) {
+      if (!array_key_exists($key, $this->data)) {
+        $this->data[$key] = array();
+      }
+      foreach ($value as $item) {
+        $this->addProperty($key, $item);
+      }
+      return;
     }
-    if (is_scalar($value)) {
-      $this->data[$key] = $value;
+
+    // If this is a TripalWebServiceResource then get it's data and use that
+    // as the new value.
+    if (is_a($value, 'TripalWebServiceResource') or is_subclass_of($value, 'TripalWebServiceResource')) {
+      // Add in the context items to this resource.
+      $context = $value->getContext();
+      foreach ($context as $k => $v) {
+        $this->addContextItem($k, $v);
+      }
+      $value = $value->getData();
     }
-    else if (!is_subclass_of($value, 'TripalWebServiceResource')) {
+
+    // If this key doesn't exist then add the value directly to the key.
+    if (!array_key_exists($key, $this->data)) {
       $this->data[$key] = $value;
     }
+    // Else if it does exist then we need to make sure that the element is
+    // an array and add it.
     else {
-      throw new Exception("The value must either be a scalar or a TripalWebServiceResource");
-    }
-  }
-
-  /**
-   * A recursive function that ensures all keys in an item are in the context.
-   *
-   * @param $key
-   *   The name of the current key.
-   * @param $value
-   *   The avlue assigned to the current key.
-   *
-   * @throws Exception
-   *   Throws an exception of a key is not present in the context.
-   */
-  private function checkDataItem($key, $value) {
-    // Make sure the key is already present in the context.
-    $keys = array_keys($this->context);
-    if (!in_array($key, $keys)) {
-      throw new Exception("The key, '$key', has not yet been added to the " .
-        "context. Use the addContextItem() function to add this key prior to adding a value for it.");
-    }
-    // If the value is an associative array then recurse
-    if (is_array($value)) {
-      // Check if this is an associatave array (non-integer keys).
-      if (count(array_filter(array_keys($array), 'is_string')) > 0) {
-        foreach ($value as $sub_key => $sub_value) {
-          $this->checkDataItem($sub_key, $sub_value);
-        }
+      // If this is the second element, then convert it to an array.  The
+      // second test in the condition below is for for a numeric array.
+      if (!is_array($this->data[$key]) or count(array_filter(array_keys($this->data[$key]), 'is_string')) > 0) {
+        $element = $this->data[$key];
+        $this->data[$key] = array();
+        $this->data[$key][] = $element;
       }
+      $this->data[$key][] = $value;
     }
   }
 

+ 27 - 19
tripal_ws/tripal_ws.module

@@ -104,7 +104,17 @@ function tripal_ws_get_services() {
   global $base_url;
   $service_path = $base_url . '/web-services';
 
-  drupal_add_http_header('Content-Type', 'application/json');
+
+  // This should go out as ld+json
+  drupal_add_http_header('Content-Type', 'application/ld+json');
+
+  // Add a link header for the vocabulary service so that clients
+  // know where to find the docs.
+  tripal_load_include_web_service_class('TripalVocabService_v0_1');
+  $service = new TripalVocabService_v0_1($service_path);
+  $vocab = tripal_get_vocabulary_details('hydra');
+  drupal_add_http_header('Link', '<' . $service->getServicePath() . '>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"');
+  drupal_add_http_header('Cache-Control', "no-cache");
 
   try {
     $ws_path = func_get_args();
@@ -196,19 +206,28 @@ function tripal_ws_list_services() {
 
   // Create the parent resource which is a collection.
   $resource = new TripalWebServiceResource($base_path);
-  $resource->addContextItem('entrypoint', 'hydra:entrypoint');
-  $resource->setType('entrypoint');
 
-  // Now add the member to the collection
+  // Add the vocabulary to the context.
+  tripal_load_include_web_service_class('TripalVocabService_v0_1');
+  $service = new TripalVocabService_v0_1($base_path);
+  $resource->addContextItem('vocab', $service->getServicePath() . '#');
+  $resource->addContextItem('EntryPoint', 'vocab:EntryPoint');
+  $resource->setType('EntryPoint');
+
+  // Now add the services as properties.
   foreach ($services as $service_class) {
     tripal_load_include_web_service_class($service_class);
+    if ($service_class == 'TripalVocabService_v0_1') {
+      continue;
+    }
     $service = new $service_class($base_path);
-    $version = $service->getVersion();
-    $resource->addContextItem($service_class::$type, '');
+    $resource->addContextItem($service_class::$type, array(
+      '@id' => 'vocab:EntryPoint/' . $service_class::$type,
+      '@type' => '@id',
+    ));
     $resource->addProperty($service_class::$type, $service->getServicePath());
   }
 
-  // For discoverability add the document webservice.
   $service->setResource($resource);
   $response = $service->getResponse();
   print drupal_json_encode($response);
@@ -248,21 +267,10 @@ function tripal_ws_services() {
     // TODO: What do we do if no version is provided?
   }
 
-  drupal_add_http_header('Content-Type', 'application/json');
+  drupal_add_http_header('Content-Type', 'application/ld+json');
   print drupal_json_encode($response);
 }
 
-/**
- *
- * @param $entities
- * @param $type
- */
-function tripal_ws_entity_load($entities, $type) {
-  foreach ($entities as $entity) {
-
-  }
-}
-
 /**
  *
  * @param $site_id