Browse Source

Split linker fileds into three files each

Chun-Huai Cheng 8 years ago
parent
commit
f73bc67954
43 changed files with 5932 additions and 15 deletions
  1. 222 0
      tripal_chado/includes/TripalFields/chado_linker__contact.inc
  2. 72 0
      tripal_chado/includes/TripalFields/chado_linker__contact_formatter.inc
  3. 142 0
      tripal_chado/includes/TripalFields/chado_linker__contact_widget.inc
  4. 226 0
      tripal_chado/includes/TripalFields/chado_linker__cvterm.inc
  5. 114 0
      tripal_chado/includes/TripalFields/chado_linker__cvterm_adder.inc
  6. 27 0
      tripal_chado/includes/TripalFields/chado_linker__cvterm_adder_formatter.inc
  7. 178 0
      tripal_chado/includes/TripalFields/chado_linker__cvterm_adder_widget.inc
  8. 80 0
      tripal_chado/includes/TripalFields/chado_linker__cvterm_formatter.inc
  9. 192 0
      tripal_chado/includes/TripalFields/chado_linker__cvterm_widget.inc
  10. 232 0
      tripal_chado/includes/TripalFields/chado_linker__dbxref.inc
  11. 48 0
      tripal_chado/includes/TripalFields/chado_linker__dbxref_formatter.inc
  12. 248 0
      tripal_chado/includes/TripalFields/chado_linker__dbxref_widget.inc
  13. 186 0
      tripal_chado/includes/TripalFields/chado_linker__expression.inc
  14. 92 0
      tripal_chado/includes/TripalFields/chado_linker__expression_formatter.inc
  15. 41 0
      tripal_chado/includes/TripalFields/chado_linker__expression_widget.inc
  16. 113 0
      tripal_chado/includes/TripalFields/chado_linker__featureloc.inc
  17. 27 0
      tripal_chado/includes/TripalFields/chado_linker__featureloc_formatter.inc
  18. 40 0
      tripal_chado/includes/TripalFields/chado_linker__featureloc_widget.inc
  19. 113 0
      tripal_chado/includes/TripalFields/chado_linker__featurepos.inc
  20. 27 0
      tripal_chado/includes/TripalFields/chado_linker__featurepos_formatter.inc
  21. 40 0
      tripal_chado/includes/TripalFields/chado_linker__featurepos_widget.inc
  22. 172 0
      tripal_chado/includes/TripalFields/chado_linker__genotype.inc
  23. 72 0
      tripal_chado/includes/TripalFields/chado_linker__genotype_formatter.inc
  24. 40 0
      tripal_chado/includes/TripalFields/chado_linker__genotype_widget.inc
  25. 171 0
      tripal_chado/includes/TripalFields/chado_linker__phenotype.inc
  26. 72 0
      tripal_chado/includes/TripalFields/chado_linker__phenotype_formatter.inc
  27. 40 0
      tripal_chado/includes/TripalFields/chado_linker__phenotype_widget.inc
  28. 164 0
      tripal_chado/includes/TripalFields/chado_linker__prop.inc
  29. 114 0
      tripal_chado/includes/TripalFields/chado_linker__prop_adder.inc
  30. 27 0
      tripal_chado/includes/TripalFields/chado_linker__prop_adder_formatter.inc
  31. 282 0
      tripal_chado/includes/TripalFields/chado_linker__prop_adder_widget.inc
  32. 27 0
      tripal_chado/includes/TripalFields/chado_linker__prop_formatter.inc
  33. 117 0
      tripal_chado/includes/TripalFields/chado_linker__prop_widget.inc
  34. 162 0
      tripal_chado/includes/TripalFields/chado_linker__pub.inc
  35. 27 0
      tripal_chado/includes/TripalFields/chado_linker__pub_formatter.inc
  36. 173 0
      tripal_chado/includes/TripalFields/chado_linker__pub_widget.inc
  37. 596 0
      tripal_chado/includes/TripalFields/chado_linker__relationship.inc
  38. 100 0
      tripal_chado/includes/TripalFields/chado_linker__relationship_formatter.inc
  39. 643 0
      tripal_chado/includes/TripalFields/chado_linker__relationship_widget.inc
  40. 168 0
      tripal_chado/includes/TripalFields/chado_linker__synonym.inc
  41. 41 0
      tripal_chado/includes/TripalFields/chado_linker__synonym_formatter.inc
  42. 249 0
      tripal_chado/includes/TripalFields/chado_linker__synonym_widget.inc
  43. 15 15
      tripal_chado/includes/tripal_chado.fields.inc

+ 222 - 0
tripal_chado/includes/TripalFields/chado_linker__contact.inc

@@ -0,0 +1,222 @@
+<?php
+
+class chado_linker__contact extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'local:contact';
+
+  // The default lable for this field.
+  public static $label = 'Contacts';
+
+  // The default description for this field.
+  public static $description = 'Associates an indviddual or organization with
+          this record';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__contact_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__contact_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $record = $details['record'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $base_table = $details['record']->tablename;
+    $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.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      'chado-' . $field_table . '__' . $pkey => '',
+      'chado-' . $field_table . '__' . $fkey_lcolumn => '',
+      'chado-' . $field_table . '__' . 'contact_id' => '',
+      // Ignore the synonym_sgml column for now.
+    );
+    
+    $linker_table = $base_table . '_contact';
+    $options = array(
+      'return_array' => 1,
+      'include_fk' => array(
+        'contact_id' => array(
+          'type_id' => array(
+            'dbxref_id' => array(
+              'db_id' => TRUE,
+            ),
+          ),
+        ),
+        $fkey_lcolumn => TRUE,
+      ),
+    );
+    $record = chado_expand_var($record, 'table', $linker_table, $options);
+    $contact_linkers = $record->$linker_table;
+    if ($contact_linkers) {
+      foreach ($contact_linkers as $i => $contact_linker) {
+        $contact = $contact_linker->contact_id;
+        $entity->{$field_name}['und'][$i] = array(
+          'value' => array(
+            'type' => $contact->type_id ? $contact->type_id->name : '',
+            'name' => $contact->name,
+            'description' => $contact->description,
+          ),
+          // Add in the semantic web settings.  This array is expected by
+          // other Tripal modules that handle semantic web for fields.
+          'semantic_web' => array(
+            'type' => $contact->type_id ? $contact->type_id->dbxref_id->db_id->name . ':' . $contact->type_id->dbxref_id->accession : '',
+            'name' => tripal_get_chado_semweb_term('contact', 'name'),
+            'description' => tripal_get_chado_semweb_term('contact', 'description'),
+          ),
+          // Add in subfield mapping to Chado tables. This is used by the
+          // chado_field_storage for performing queries on sub element values.
+          // It should be a comma-separated list (no spacing) of the field names
+          // as foreign keys are followed starting from the Chado table to which
+          // this field maps.
+          'chado_mapping' => array(
+            'type' => 'type_id,name',
+            'name' => 'contact_id,name',
+            'description' => 'contact_id,name'
+          ),
+          'chado-' . $field_table . '__' . $pkey => $contact_linker->$pkey,
+          'chado-' . $field_table . '__' . $fkey_lcolumn => $contact_linker->$fkey_lcolumn->$fkey_lcolumn,
+          'chado-' . $field_table . '__' . 'contact_id' => $contact->contact_id
+        );
+    
+        if (property_exists($contact, 'entity_id')) {
+          $entity->{$field_name}['und'][$i]['value']['entity'] = 'TripalEntity:' . $contact->entity_id;
+        }
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}
+
+/**
+ * An Ajax callback for the pub widget.
+ */
+function chado_linker__contact_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];
+}
+/**
+ * Theme function for the pub widget.
+ *
+ * @param $variables
+ */
+function theme_chado_linker__contact_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=\"pub-widget\">
+        <div class=\"pub-widget-item\">" .
+        drupal_render($element['name']) . "
+        </div>
+      </div>
+    ";
+
+        return $layout;
+}

+ 72 - 0
tripal_chado/includes/TripalFields/chado_linker__contact_formatter.inc

@@ -0,0 +1,72 @@
+<?php
+
+class chado_linker__contact_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Contacts';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__contact');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    // Get the settings
+    $settings = $display['settings'];
+    
+    $headers = array('Name', 'Description', 'Type');
+    $rows = array();
+    
+    foreach ($items as $delta => $item) {
+      $contact = $item['value'];
+      if (!$contact) {
+        continue;
+      }
+    
+      // Get the field values
+      $contact_name = $contact['name'];
+      $description = $contact['description'];
+      $type = $contact['type'];
+    
+      // Add a link i there is an entity.
+      if (array_key_exists('entity', $item['value']) and $item['value']['entity']) {
+        list($entity_type, $entity_id) = explode(':', $item['value']['entity']);
+        $contact_name = l($contact_name, "bio_data/" . $entity_id, array('attributes' => array('target' => "_blank")));
+      }
+      $rows[] = array($contact_name, $description, $type);
+    }
+    $table = array(
+      'header' => $headers,
+      'rows' => $rows,
+      'attributes' => array(
+        'id' => 'tripal_linker-table-contact-object',
+        'class' => 'tripal-data-table'
+      ),
+      'sticky' => FALSE,
+      'caption' => "",
+      'colgroups' => array(),
+      'empty' => 'No contacts available',
+    );
+    $content = theme_table($table);
+    
+    if (count($items) > 0) {
+      // once we have our table array structure defined, we call Drupal's theme_table()
+      // function to generate the table.
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => $content,
+      );
+    }
+  }
+}

+ 142 - 0
tripal_chado/includes/TripalFields/chado_linker__contact_widget.inc

@@ -0,0 +1,142 @@
+<?php
+
+class chado_linker__contact_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Contacts';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__contact');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+    $entity = $form['#entity'];
+    $field_name = $this->field['field_name'];
+    
+    // Get the FK column that links to the base table.
+    $table_name = $this->field['settings']['chado_table'];
+    $base_table = $this->field['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 defaults.
+    $record_id = '';
+    $fkey_value = $element['#entity']->chado_record_id;
+    $contact_id = '';
+    $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 and array_key_exists($delta, $items)) {
+      $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $table_name . '__' . $pkey, $record_id);
+      $contact_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $table_name . '__contact_id', $contact_id);
+      if ($contact_id) {
+        $contact = chado_generate_var('contact', array('contact_id' => $contact_id));
+        $name = $contact->name;
+      }
+    }
+    
+    $schema = chado_get_schema('contact');
+    
+    $widget['#table_name'] = $table_name;
+    $widget['#fkey_field'] = $fkey;
+    $widget['#theme'] = 'chado_linker__contact_widget';
+    $widget['#prefix'] =  "<span id='$table_name-$delta'>";
+    $widget['#suffix'] =  "</span>";
+    
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+    
+    $widget['chado-' . $table_name . '__' . $pkey] = array(
+      '#type' => 'value',
+      '#default_value' => $record_id,
+    );
+    $widget['chado-' . $table_name . '__' . $fkey] = array(
+      '#type' => 'value',
+      '#default_value' => $fkey_value,
+    );
+    $widget['chado-' . $table_name . '__contact_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $contact_id,
+    );
+    
+    $widget['name'] = array(
+      '#type' => 'textfield',
+      '#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' => "$table_name-$delta",
+        'effect' => 'fade',
+        'method' => 'replace'
+      ),
+      '#maxlength' => 100000,
+    );
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    // Get the FK column that links to the base table.
+    $table_name = $this->field['settings']['chado_table'];
+    $base_table = $this->field['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];
+    $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'] : '';
+    $contact_id = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__contact_id']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__contact_id'] : '';
+    $name = isset($form_state['values'][$field_name][$langcode][$delta]['name']) ? $form_state['values'][$field_name][$langcode][$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 and !$contact_id) {
+      $contact = chado_generate_var('contact', array('name' => $name));
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__contact_id'] = $contact->contact_id;
+    }
+    
+    // In the widgetForm function we automatically add the foreign key
+    // record.  But if the user did not provide a contact we want to take
+    // it out so that the Chado field_storage infrastructure won't try to
+    // write a record.
+    if (!$name and !$contact_id) {
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey] = '';
+    }
+    
+    // If the user removed the contact from the contact_name field
+    // then we want to clear out the rest of the hidden values.
+    // Leave the primary key so the record can be deleted.
+    if (!$name and $contact_id) {
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $fkey] = '';
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__contact_id'] = '';
+    }
+  }
+}

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

@@ -0,0 +1,226 @@
+<?php
+
+class chado_linker__cvterm extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'local:cvterm';
+
+  // The default lable for this field.
+  public static $label = 'Annotations';
+
+  // The default description for this field.
+  public static $description = 'This record can be annotated with terms from other
+              vocabularies.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // 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';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['settings']['base_table'];
+    
+    $matches = array();
+    preg_match('/(.*?)__(\d+)/', $field_name, $matches);
+    $table_name = $matches[1];
+    $cv_id = $matches[2];
+    
+    // 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' => array(
+        'cv_id' => $cv_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,
+      );
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}
+
+/**
+ * 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];
+}

+ 114 - 0
tripal_chado/includes/TripalFields/chado_linker__cvterm_adder.inc

@@ -0,0 +1,114 @@
+<?php
+
+class chado_linker__cvterm_adder extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'local:cvterm';
+
+  // The default lable for this field.
+  public static $label = 'Add an Annotation Type';
+
+  // The default description for this field.
+  public static $description = 'This record may have any number of types of
+        annotations. Use this field to first add the type.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__cvterm_adder_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__cvterm_adder_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 27 - 0
tripal_chado/includes/TripalFields/chado_linker__cvterm_adder_formatter.inc

@@ -0,0 +1,27 @@
+<?php
+
+class chado_linker__cvterm_adder_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Add an Annotation Type';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__cvterm_adder');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+  }
+}

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

@@ -0,0 +1,178 @@
+<?php
+
+class chado_linker__cvterm_adder_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Add an Annotation Type';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__cvterm_adder');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+    // This field has no value field.  Just a fieldset for adding new annotation types.
+    $widget['#type'] = 'fieldset';
+    $widget['#title'] = $element['#title'];
+    $widget['#description'] = $element['#description'];
+    $widget['#group'] = 'entity_form_vtabs';
+    
+    $widget['cvterm_class_adder_instructions'] = array(
+      '#type' => 'item',
+      '#markup' => t('You may add annotation types to this form by
+          providing a vocabulary name in the field above
+          and clicking the "Add Annotation Type" button.  This will add a
+          new field to the form above for the vocabulary you entered which
+          will allow users to associate terms from that vocabulary to
+          this record.'),
+    );
+    
+    $options = tripal_get_cv_select_options();
+    $widget['value'] = array(
+      '#type' => 'select',
+      '#title' => t('Vocabulary'),
+      '#options' => $options,
+      '#description' => t("Please enter the vocabulary that contains terms
+          you want to allow users to use for annotations."),
+    );
+    
+    //    $widget['#element_validate'] = array('chado_linker__cvterm_adder_widget_validate');
+    
+    // When this button is clicked, the form will be validated and submitted.
+    // Therefore, we set custom submit and validate functions to override the
+    // default form submit.  In the validate function we set the form_state
+    // to rebuild the form so the submit function never actually gets called,
+    // but we need it or Drupal will run the default validate anyway.
+    // we also set #limit_validation_errors to empty so fields that
+    // are required that don't have values won't generate warnings.
+    $widget['cvterm_class_adder_button'] = array(
+      '#value' => t('Add Annotation Type'),
+      '#type' => 'submit',
+      '#name' => 'cvterm_class_adder_button',
+      //      '#submit' => array('chado_linker__cvterm_adder_widget_submit'),
+      '#limit_validation_errors' => array(array($this->field['field_name'])),
+    );
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    if (array_key_exists('triggering_element', $form_state) and
+        $form_state['triggering_element']['#name'] == 'cvterm_class_adder_button') {
+          $this_field = $this->field;
+          $field_name = $this_field['field_name'];
+          $bundle = $entity->bundle;
+    
+          // Get the base table name from the field annotations.
+          $base_table = $entity->chado_table;
+          $cvterm_class_adder = $form_state['values'][$base_table . '_cvterm'][$langcode][$delta]['value'];
+          $cv = chado_generate_var('cv', array('cv_id' => $cvterm_class_adder));
+    
+          // Make sure a valid vocabulary is selected
+          if (!$cv) {
+            form_set_error("$field_name][$langcode][$delta][value", "Please select a vocabulary.");
+          }
+          else {
+            // Make sure this vocabulary doesn't already have a field
+            if (key_exists($field_name . '__' . $cv->cv_id, $form_state['values'])) {
+              form_set_error("$field_name][$langcode][$delta][wrapper][terms_name", "Field for this vocabulary already exists. Please select another vocabulary.");
+            }
+          }
+        }
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    // Add the new field to the entity
+    if (array_key_exists('triggering_element', $form_state) and
+        $form_state['triggering_element']['#name'] == 'cvterm_class_adder_button') {
+    
+          $form_state['rebuild'] = TRUE;
+          $this_field = $this->field;
+          $field_name = $this_field['field_name'];
+          $bundle = $entity->bundle;
+    
+          // Get the base table name from the field annotations.
+          $base_table = $entity->chado_table;
+          $cvterm_class_adder = $form_state['values'][$base_table . '_cvterm'][$langcode][$delta]['value'];
+    
+          // Get the vocabulary.
+          //$cvterm_class_adder = tripal_chado_get_field_form_values($field_name, $form_state);
+          $cv = chado_generate_var('cv', array('cv_id' => $cvterm_class_adder));
+    
+          if (!$cv) {
+            return;
+          }
+    
+          $type_field_name = $field_name . '__' . $cv->cv_id;
+    
+          // The field name is the table name in this case. We want to get the
+          // primary key as this should be the field that maps th the value.
+          $schema = chado_get_schema($field_name);
+          $pkey = $schema['primary key'][0];
+    
+          // Add the field if it doesn't already exists.
+          $field = field_info_field($type_field_name);
+          if (!$field) {
+            $create_info = array(
+              'field_name' => $type_field_name,
+              'type' => 'chado_linker__cvterm',
+              'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+              'locked' => FALSE,
+              'storage' => array(
+                'type' => 'field_chado_storage',
+              ),
+              'settings' => array(
+                'chado_table' => $field_name,
+                'chado_column' => $pkey,
+                'base_table' => $base_table,
+              ),
+            );
+            $field = field_create_field($create_info);
+          }
+    
+          // Attach the field to the bundle if it isn't already.
+          if (!$field or !array_key_exists('bundles', $field) or
+              !array_key_exists('TripalEntity', $field['bundles']) or
+              !in_array($bundle, $field['bundles']['TripalEntity'])) {
+                $createInstanceInfo = array(
+                  'field_name' => $type_field_name,
+                  'entity_type' => 'TripalEntity',
+                  'bundle' => $bundle,
+                  'label' => ucfirst(preg_replace('/_/', ' ', $cv->name)),
+                  'description' => "Annotations from the $cv->name vocabulary",
+                  'required' => FALSE,
+                  'settings' => array(),
+                  'widget' => array(
+                    'type' => 'chado_linker__cvterm_widget',
+                    'settings' => array(
+                      'display_label' => 1,
+                    ),
+                  ),
+                  'display' => array(
+                    'default' => array(
+                      'label' => 'above',
+                      'type' => 'chado_linker__cvterm_formatter',
+                      'settings' => array(),
+                    ),
+                  ),
+                );
+                $instance = field_create_instance($createInstanceInfo);
+              }
+        }
+  }
+}

+ 80 - 0
tripal_chado/includes/TripalFields/chado_linker__cvterm_formatter.inc

@@ -0,0 +1,80 @@
+<?php
+
+class chado_linker__cvterm_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Annotations';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__cvterm');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+    $headers = array('Term', 'Definition', 'Is Not', 'Reference');
+    $rows = array();
+    
+    $chado_table = $this->field['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) {
+
+  }
+}

+ 192 - 0
tripal_chado/includes/TripalFields/chado_linker__cvterm_widget.inc

@@ -0,0 +1,192 @@
+<?php
+
+class chado_linker__cvterm_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Annotations';
+
+  // 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->field['settings']['chado_table'];
+    $base_table = $this->field['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['#theme'] = 'chado_linker__cvterm_widget';
+    $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' => "chado_linker__pub_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' => '',
+    );
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $table_name = $this->field['settings']['chado_table'];
+    $schema = chado_get_schema($table_name);
+    $pkey = $schema['primary key'][0];
+    $base_table = $this->field['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'] = '';
+    }
+  }
+}

+ 232 - 0
tripal_chado/includes/TripalFields/chado_linker__dbxref.inc

@@ -0,0 +1,232 @@
+<?php
+
+class chado_linker__dbxref extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'SBO:0000554';
+
+  // The default lable for this field.
+  public static $label = 'Cross references';
+
+  // The default description for this field.
+  public static $description = 'This record can be cross referenced with a record in
+          another online database. This field is intended for one or more
+          references.  At a minimum, the database and accession must be provided.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__dbxref_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__dbxref_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $record = $details['record'];
+    
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $record->tablename;
+    
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    $fkeys = array_values($schema['foreign keys'][$base_table]['columns']);
+    $fkey = $fkeys[0];
+    
+    // Set some defaults for the empty record.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      'chado-' . $field_table . '__' . $pkey => '',
+      'chado-' . $field_table . '__' . $fkey => '',
+      'chado-' . $field_table . '__dbxref_id' => '',
+      'dbxref_id' => '',
+      'db_id' => '',
+      'accession' => '',
+      'version' => '',
+      'description' => '',
+    );
+    
+    $linker_table = $base_table . '_dbxref';
+    $options = array('return_array' => 1);
+    $record = chado_expand_var($record, 'table', $linker_table, $options);
+    if (count($record->$linker_table) > 0) {
+      $i = 0;
+      foreach ($record->$linker_table as $index => $linker) {
+        $dbxref = $linker->dbxref_id;
+        $URL = tripal_get_dbxref_url($dbxref);
+        $entity->{$field_name}['und'][$i] = array(
+          'value' => array(
+            'vocabulary' => $dbxref->db_id->name,
+            'accession' => $dbxref->accession,
+            'URL' => $URL,
+          ),
+          'chado-' . $field_table . '__' . $pkey => $linker->$pkey,
+          'chado-' . $field_table . '__' . $fkey => $linker->$fkey->$fkey,
+          '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++;
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}
+
+/**
+ * Theme function for the dbxref_id_widget.
+ *
+ * @param $variables
+ */
+function theme_chado_linker__dbxref_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=\"secondary-dbxref-widget\">
+        <div class=\"secondary-dbxref-widget-item\">" .
+        drupal_render($element['db_id']) . "
+        </div>
+        <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>
+    ";
+
+        return $layout;
+}
+
+/**
+ * An Ajax callback for the dbxref widget.
+ */
+function chado_linker__dbxref_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'];
+  $field_table = $field['settings']['chado_table'];
+  $field_column = $field['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'];
+  $db_id = $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 for future auto completions.');
+    }
+  }
+
+  return $form[$field_name]['und'][$delta];
+}

+ 48 - 0
tripal_chado/includes/TripalFields/chado_linker__dbxref_formatter.inc

@@ -0,0 +1,48 @@
+<?php
+
+class chado_linker__dbxref_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Cross references';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__dbxref');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @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->field['settings']['chado_table'];
+    $content = '';
+    foreach ($items as $delta => $item) {
+      if (!$item['value']) {
+        continue;
+      }
+      $content = $item['value']['vocabulary'] . ':' . $item['value']['accession'];
+      if ($item['value']['URL']) {
+        $content = l($content, $item['value']['URL'], array('attributes' => array('target' => '_blank')));
+      }
+      $element[$delta] = array(
+        '#type' => 'markup',
+        '#markup' => $content,
+      );
+    }
+    
+    if (count($element) == 0) {
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => '',
+      );
+    }
+  }
+}

+ 248 - 0
tripal_chado/includes/TripalFields/chado_linker__dbxref_widget.inc

@@ -0,0 +1,248 @@
+<?php
+
+class chado_linker__dbxref_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Cross references';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__dbxref');
+
+  /**
+   *
+   * @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->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    
+    // Get the FK column that links to the base table.
+    $chado_table = $this->field['settings']['chado_table'];
+    $base_table = $this->field['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;
+    $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)) {
+      $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__' . $pkey, $record_id);
+      $fkey_value = tripal_get_field_item_keyval($items, $delta, 'chado-' . $field_table . '__' . $fkey, $fkey_value);
+      $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'];
+      $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('chado_linker__dbxref_widget_validate');
+    $widget['#theme'] = 'chado_linker__dbxref_widget';
+    $widget['#prefix'] =  "<span id='$field_name-dbxref--db-id-$delta'>";
+    $widget['#suffix'] =  "</span>";
+    
+    
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+    
+    $widget['chado-' . $field_table . '__' . $pkey] = array(
+      '#type' => 'value',
+      '#default_value' => $record_id,
+    );
+    $widget['chado-' . $field_table . '__' . $fkey] = array(
+      '#type' => 'value',
+      '#default_value' => $fkey_value,
+    );
+    $widget['chado-' . $field_table . '__dbxref_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $dbxref_id,
+    );
+    $widget['dbxref_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $dbxref_id,
+    );
+    $widget['db_id'] = array(
+      '#type' => 'select',
+      '#title' => t('Database'),
+      '#options' => $options,
+      '#required' => $element['#required'],
+      '#default_value' => $db_id,
+      '#ajax' => array(
+        'callback' => "chado_linker__dbxref_widget_form_ajax_callback",
+        'wrapper' => "$field_name-dbxref--db-id-$delta",
+        'effect' => 'fade',
+        'method' => 'replace'
+      ),
+    );
+    $widget['accession'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Accession'),
+      '#default_value' => $accession,
+      '#required' => $element['#required'],
+      '#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' => "chado_linker__dbxref_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/legacy/tripal_db/add', array('attributes' => array('target' => '_blank')))
+      );
+    }
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $table_name = $this->field['settings']['chado_table'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['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.
+    foreach ($items as $delta => $values) {
+    
+      // Get the field values.
+      $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
+      // form_error function because the form_error will add a red_highlight
+      // around all of the fields in the fieldset which is confusing as it's not
+      // clear to the user what field is required and which isn't. Therefore,
+      // we borrow the code from the 'form_error' function and append the field
+      // so that the proper field is highlighted on error.
+      if (!$db_id and $accession) {
+        $errors[$field_name][$delta]['und'][] = array(
+          'message' => t("A database and the accession must both be provided."),
+          'error' => 'chado_linker__dbxref',
+        );
+      }
+      if ($db_id and !$accession) {
+        $errors[$field_name][$delta]['und'][] = array(
+          'message' => t("A database and the accession must both be provided."),
+          'error' => 'chado_linker__dbxref',
+        );
+      }
+      if (!$db_id and !$accession and ($version or $description)) {
+        $errors[$field_name][$delta]['und'][] = array(
+          'message' => t("A database and the accession must both be provided."),
+          'error' => 'chado_linker__dbxref',
+        );
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @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->field['settings']['chado_table'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['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'] : '';
+    
+    // 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 != $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;
+      }
+    }
+    // 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'] = '';
+    }
+  }
+}

+ 186 - 0
tripal_chado/includes/TripalFields/chado_linker__expression.inc

@@ -0,0 +1,186 @@
+<?php
+
+class chado_linker__expression extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'local:expression';
+
+  // The default lable for this field.
+  public static $label = 'Expression';
+
+  // The default description for this field.
+  public static $description = 'Associates an expression with
+          this record.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__expression_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__expression_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $record = $details['record'];
+    
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $base_table = $details['record']->tablename;
+    $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.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+    );
+    
+    $linker_table = $base_table . '_expression';
+    $options = array(
+      'return_array' => 1,
+    );
+    $record = chado_expand_var($record, 'table', $linker_table, $options);
+    $exp_linkers = $record->$linker_table;
+    if ($exp_linkers) {
+      foreach ($exp_linkers as $i => $exp_linker) {
+    
+        // Because the unqiuename is a text field we must expand it.
+        $expression = $exp_linker->expression_id;
+        $expression = chado_expand_var($expression, 'field', 'expression.uniquename', $options);
+    
+        // Set the values that will be seen by the user on the page and in
+        // web services, or anwhere this field is viewed.
+        $entity->{$field_name}['und'][$i]['value'] = array(
+          'name' => $expression->uniquename,
+          'description' => $expression->description,
+          //'md5checksum' => $expression->md5checksum,
+        );
+    
+        // Add the pub information if a real pub is associated with the record.
+        $pub = $exp_linker->pub_id;
+        if ($pub->uniquename != 'null') {
+          $pub_details = tripal_get_minimal_pub_info($pub);
+    
+          $entity->{$field_name}['und'][$i]['value']['publication'] = $pub_details;
+          $entity->{$field_name}['und'][$i]['value']['publication']['type'] = $pub->type_id->name;
+          if (property_exists($pub, 'entity_id')) {
+            $entity->{$field_name}['und'][$i]['publication'][0]['value']['entity'] = 'TripalEntity:' . $pub->entity_id;
+          }
+        }
+    
+        // Add the linker_expressionprop
+        $linkerprop_table =  $linker_table . 'prop';
+        if (db_table_exists('chado.' . $linkerprop_table)) {
+          $exp_linker = chado_expand_var($exp_linker, 'table', $linkerprop_table, $options);
+          $exp_linkerprops = $exp_linker->feature_expressionprop;
+          if ($exp_linkerprops) {
+            foreach ($exp_linkerprops AS $linkerprop) {
+              $entity->{$field_name}['und'][$i]['value'][$linkerprop->type_id->name] = $linkerprop->value;
+            }
+          }
+        }
+        // Add the fields for the widget form.  The name requres the following
+        // format if the fields should be used as values for insertint/updating
+        // into the chado table:  [table_name]__[field_name]
+        $entity->{$field_name}['und'][$i][$linker_table . '__expression_id'] = $expression->expression_id;
+        $entity->{$field_name}['und'][$i][$linker_table . '__uniquename'] = $expression->uniquename;
+        //$entity->{$field_name}['und'][$i][$linker_table . '__md5checksum'] = $expression->md5checksum;
+        $entity->{$field_name}['und'][$i][$linker_table . '__description'] = $expression->description;
+    
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 92 - 0
tripal_chado/includes/TripalFields/chado_linker__expression_formatter.inc

@@ -0,0 +1,92 @@
+<?php
+
+class chado_linker__expression_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Expression';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__expression');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    // Get the settings
+    $settings = $display['settings'];
+    
+    $content = '';
+    $rows = array();
+    foreach ($items as $delta => $item) {
+      if (!$item['value']) {
+        continue;
+      }
+      // Iterate through all of the children of the $item['value']. Add each
+      // one as an independent row in the table.
+    
+      foreach ($item['value'] as $key => $value) {
+    
+        // If this key is the name, then we want to link to the entity if one
+        // exists.
+        if ($key == 'name') {
+          if (array_key_exists('entity', $item['value']) and $item['value']['$entity_id']) {
+            list($entity_type, $entity_id) = explode(':', $item['value']['entity']);
+            $value = l($value, "bio_data/" . $entity_id, array('attributes' => array('target' => "_blank")));
+          }
+        }
+        // If this key is the publication then we want to get the citation
+        // and link to the pub if an entity exits.
+        if ($key == 'publication') {
+          $pub = $value['Citation'];
+          if (array_key_exists('publication', $item) and array_key_exists('entity', $item['publication'][0])) {
+            $entity_id = $item['publication'][0]['entity_id'];
+            $title =  $item['value']['publication']['Title'];
+            $link = l($title, 'bio_data/' . $entity_id);
+            $pub = preg_replace("/$title/", $link, $pub);
+          }
+          $value = $pub;
+        }
+        // Add the item as a new row.
+        $rows[] = array(
+          array(
+            'data' => ucfirst(str_replace('_', ' ', $key)),
+            'header' => TRUE,
+            'width' => '20%',
+          ),
+          $value
+        );
+      }
+    }
+    $table = array(
+      'header' => array(),
+      'rows' => $rows,
+      'attributes' => array(
+        'id' => 'tripal_linker-table-expression-object',
+        'class' => 'tripal-data-table'
+      ),
+      'sticky' => FALSE,
+      'caption' => "",
+      'colgroups' => array(),
+      'empty' => 'There is no curated expression data.',
+    );
+    $content = theme_table($table);
+    if (count($items) > 0) {
+      // once we have our table array structure defined, we call Drupal's theme_table()
+      // function to generate the table.
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => $content,
+      );
+    }
+  }
+}

+ 41 - 0
tripal_chado/includes/TripalFields/chado_linker__expression_widget.inc

@@ -0,0 +1,41 @@
+<?php
+
+class chado_linker__expression_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Expression';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__expression');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+  
+}

+ 113 - 0
tripal_chado/includes/TripalFields/chado_linker__featureloc.inc

@@ -0,0 +1,113 @@
+<?php
+
+class chado_linker__featureloc extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'SO:position_of';
+
+  // The default lable for this field.
+  public static $label = 'Aligned Locations';
+
+  // The default description for this field.
+  public static $description = 'Locations on landmark sequences where the feature is aligned.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__featureloc_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__featureloc_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 27 - 0
tripal_chado/includes/TripalFields/chado_linker__featureloc_formatter.inc

@@ -0,0 +1,27 @@
+<?php
+
+class chado_linker__featureloc_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Aligned Locations';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__featureloc');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+  }
+}

+ 40 - 0
tripal_chado/includes/TripalFields/chado_linker__featureloc_widget.inc

@@ -0,0 +1,40 @@
+<?php
+
+class chado_linker__featureloc_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Aligned Locations';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__featureloc');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+}

+ 113 - 0
tripal_chado/includes/TripalFields/chado_linker__featurepos.inc

@@ -0,0 +1,113 @@
+<?php
+
+class chado_linker__featurepos extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'SO:position_of';
+
+  // The default lable for this field.
+  public static $label = 'Map positions';
+
+  // The default description for this field.
+  public static $description = 'Map position of a sequence.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__featurepos_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__featurepos_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 27 - 0
tripal_chado/includes/TripalFields/chado_linker__featurepos_formatter.inc

@@ -0,0 +1,27 @@
+<?php
+
+class chado_linker__featurepos_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Map positions';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__featurepos');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+  }
+}

+ 40 - 0
tripal_chado/includes/TripalFields/chado_linker__featurepos_widget.inc

@@ -0,0 +1,40 @@
+<?php
+
+class chado_linker__featurepos_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Map positions';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__featurepos');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+}

+ 172 - 0
tripal_chado/includes/TripalFields/chado_linker__genotype.inc

@@ -0,0 +1,172 @@
+<?php
+
+class chado_linker__genotype extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'SO:0001027';
+
+  // The default lable for this field.
+  public static $label = 'Genotypes';
+
+  // The default description for this field.
+  public static $description = 'Associates an indviddual or organization with
+          this record.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__genotype_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__genotype_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $record = $details['record'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $base_table = $details['record']->tablename;
+    $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.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      $field_table . '__' . $pkey => '',
+      $field_table . '__' . $fkey_lcolumn => '',
+      $field_table . '__' . 'genotype_id' => '',
+      // Ignore the synonym_sgml column for now.
+    );
+    
+    $linker_table = $base_table . '_genotype';
+    $options = array(
+      'return_array' => 1,
+      'include_fk' => array(
+        'genotype_id' => array(
+          'type_id' => array(
+            'dbxref_id' => array(
+              'db_id' => TRUE,
+            ),
+          ),
+        ),
+        $fkey_lcolumn => TRUE,
+      ),
+    );
+    $record = chado_expand_var($record, 'table', $linker_table, $options);
+    $genotype_linkers = $record->$linker_table->$fkey_rcolumn;
+    if ($genotype_linkers) {
+      foreach ($genotype_linkers as $i => $genotype_linker) {
+        $genotype = $genotype_linker->genotype_id;
+        $entity->{$field_name}['und'][$i] = array(
+          'value' => array(
+            '@type' => $genotype->type_id->dbxref_id->db_id->name . ':' . $genotype->type_id->dbxref_id->accession,
+            'type' => $genotype->type_id->name,
+            'name' => $genotype->name,
+            'description' => $genotype->description,
+          ),
+          $field_table . '__' . $pkey => $genotype_linker->$pkey,
+          $field_table . '__' . $fkey_lcolumn => $genotype_linker->$fkey_lcolumn->$fkey_lcolumn,
+          $field_table . '__' . 'genotype_id' => $genotype->genotype_id
+        );
+    
+        if ($genotype && property_exists($genotype, 'entity_id')) {
+          $entity->{$field_name}['und'][$i]['value']['entity'] = 'TripalEntity:' . $genotype->entity_id;
+        }
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 72 - 0
tripal_chado/includes/TripalFields/chado_linker__genotype_formatter.inc

@@ -0,0 +1,72 @@
+<?php
+
+class chado_linker__genotype_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Genotypes';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__genotype');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    // Get the settings
+    $settings = $display['settings'];
+    
+    $headers = array('Name', 'Description', 'Type');
+    $rows = array();
+    
+    foreach ($items as $delta => $item) {
+      $genotype = $item['value'];
+      if (!$genotype) {
+        continue;
+      }
+    
+      // Get the field values
+      $genotype_name = $genotype['name'];
+      $description = $genotype['description'];
+      $type = $genotype['type'];
+    
+      // Add a link i there is an entity.
+      if (array_key_exists('entity', $item['value']) and $item['value']['entity']) {
+        list($entity_type, $entity_id) = explode(':', $item['value']['entity']);
+        $genotype_name = l($genotype_name, "bio_data/" . $entity_id, array('attributes' => array('target' => "_blank")));
+      }
+      $rows[] = array($genotype_name, $description, $type);
+    }
+    
+    $table = array(
+      'header' => $headers,
+      'rows' => $rows,
+      'attributes' => array(
+        'id' => 'tripal_linker-table-genotype-object',
+        'class' => 'tripal-data-table'
+      ),
+      'sticky' => FALSE,
+      'caption' => "",
+      'colgroups' => array(),
+      'empty' => 'No genotypes available.',
+    );
+    $content = theme_table($table);
+    if (count($items) > 0) {
+      // once we have our table array structure defined, we call Drupal's theme_table()
+      // function to generate the table.
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => $content,
+      );
+    }
+  }
+}

+ 40 - 0
tripal_chado/includes/TripalFields/chado_linker__genotype_widget.inc

@@ -0,0 +1,40 @@
+<?php
+
+class chado_linker__genotype_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Genotypes';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__genotype');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+}

+ 171 - 0
tripal_chado/includes/TripalFields/chado_linker__phenotype.inc

@@ -0,0 +1,171 @@
+<?php
+
+class chado_linker__phenotype extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'SBO:0000358';
+
+  // The default lable for this field.
+  public static $label = 'Phenotypes';
+
+  // The default description for this field.
+  public static $description = 'Associates phenotypes with this record.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__phenotype_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__phenotype_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $record = $details['record'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    
+    // Get the FK that links to the base record.
+    $schema = chado_get_schema($field_table);
+    $base_table = $details['record']->tablename;
+    $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.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      $field_table . '__' . $pkey => '',
+      $field_table . '__' . $fkey_lcolumn => '',
+      $field_table . '__' . 'phenotype_id' => '',
+      // Ignore the synonym_sgml column for now.
+    );
+    
+    $linker_table = $base_table . '_phenotype';
+    $options = array(
+      'return_array' => 1,
+      'include_fk' => array(
+        'phenotype_id' => array(
+          'attr_id' => array(
+            'dbxref_id' => array(
+              'db_id' => TRUE,
+            ),
+          ),
+        ),
+        $fkey_lcolumn => TRUE,
+      ),
+    );
+    $record = chado_expand_var($record, 'table', $linker_table, $options);
+    $phenotype_linkers = $record->$linker_table;
+    if ($phenotype_linkers) {
+      foreach ($phenotype_linkers as $i => $phenotype_linker) {
+        $phenotype = $phenotype_linker->phenotype_id;
+        $entity->{$field_name}['und'][$i] = array(
+          'value' => array(
+            '@type' => $phenotype->attr_id->dbxref_id->db_id->name . ':' . $phenotype->attr_id->dbxref_id->accession,
+            'type' => $phenotype->attr_id->name,
+            'name' => $phenotype->name,
+            'value' => $phenotype->value,
+          ),
+          $field_table . '__' . $pkey => $phenotype_linker->$pkey,
+          $field_table . '__' . $fkey_lcolumn => $phenotype_linker->$fkey_lcolumn->$fkey_lcolumn,
+          $field_table . '__' . 'phenotype_id' => $phenotype->phenotype_id
+        );
+    
+        if ($phenotype && property_exists($phenotype, 'entity_id')) {
+          $entity->{$field_name}['und'][$i]['value']['entity'] = 'TripalEntity:' . $phenotype->entity_id;
+        }
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 72 - 0
tripal_chado/includes/TripalFields/chado_linker__phenotype_formatter.inc

@@ -0,0 +1,72 @@
+<?php
+
+class chado_linker__phenotype_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Phenotypes';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__phenotype');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    // Get the settings
+    $settings = $display['settings'];
+    
+    $headers = array('Name', 'Value', 'Type');
+    $rows = array();
+    
+    foreach ($items as $delta => $item) {
+      $phenotype = $item['value'];
+      if (!$phenotype) {
+        continue;
+      }
+    
+      // Get the field values
+      $phenotype_name = $phenotype['name'];
+      $value = $phenotype['value'];
+      $type = $phenotype['type'];
+    
+      // Add a link i there is an entity.
+      if (array_key_exists('entity', $item['value']) and $item['value']['entity']) {
+        list($entity_type, $entity_id) = explode(':', $item['value']['entity']);
+        $phenotype_name = l($phenotype_name, "bio_data/" . $entity_id, array('attributes' => array('target' => "_blank")));
+      }
+      $rows[] = array($phenotype_name, $value, $type);
+    }
+    $table = array(
+      'header' => $headers,
+      'rows' => $rows,
+      'attributes' => array(
+        'id' => 'tripal_linker-table-phenotype-object',
+        'class' => 'tripal-data-table'
+      ),
+      'sticky' => FALSE,
+      'caption' => "",
+      'colgroups' => array(),
+      'empty' => 'No phenotypes available',
+    );
+    $content = theme_table($table);
+    
+    // once we have our table array structure defined, we call Drupal's theme_table()
+    // function to generate the table.
+    if (count($items) > 0) {
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => $content,
+      );
+    }
+  }
+}

+ 40 - 0
tripal_chado/includes/TripalFields/chado_linker__phenotype_widget.inc

@@ -0,0 +1,40 @@
+<?php
+
+class chado_linker__phenotype_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Phenotypes';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__phenotype');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+}

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

@@ -0,0 +1,164 @@
+<?php
+
+class chado_linker__prop extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'local:property';
+
+  // The default lable for this field.
+  public static $label = 'Property';
+
+  // The default description for this field.
+  public static $description = 'Add details about this property.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__prop_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__prop_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['settings']['base_table'];
+    
+    $matches = array();
+    preg_match('/(.*?)__(\d+)/', $field_name, $matches);
+    $table_name = $matches[1];
+    $cvterm_id = $matches[2];
+    // 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 . '__' . $pkey => '',
+      'chado-' . $field_table . '__' . $fkey_lcolumn => $chado_record->{$fkey_lcolumn},
+      'chado-' . $field_table . '__value' => '',
+      'chado-' . $field_table . '__type_id' => '',
+      'chado-' . $field_table . '__rank' => '',
+    );
+    
+    // Get the properties associated with this base record for this fields
+    // given type.
+    $columns = array('*');
+    $match = array(
+      $fkey_lcolumn => $chado_record->{$fkey_lcolumn},
+      'type_id' => $cvterm_id,
+      );
+    $options = array(
+      'return_array' => TRUE,
+      'order_by' => array('rank' => 'ASC')
+    );
+    $properties = chado_select_record($field_table, $columns, $match, $options);
+    for ($i = 0; $i < count($properties); $i++) {
+      $property = $properties[$i];
+      foreach ($schema['fields'] as $fname => $details) {
+        $entity->{$field_name}['und'][$i] = array(
+          'value' => array(),
+          'chado-' . $field_table . '__' . $pkey => $property->$pkey,
+          'chado-' . $field_table . '__' . $fkey_lcolumn => $property->$fkey_lcolumn,
+          'chado-' . $field_table . '__value' => $property->value,
+          'chado-' . $field_table . '__type_id' => $property->type_id,
+          'chado-' . $field_table . '__rank' => $property->rank,
+        );
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 114 - 0
tripal_chado/includes/TripalFields/chado_linker__prop_adder.inc

@@ -0,0 +1,114 @@
+<?php
+
+class chado_linker__prop_adder extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'local:property';
+
+  // The default lable for this field.
+  public static $label = 'Add a Property Type';
+
+  // The default description for this field.
+  public static $description = 'This record may have any number of properties. Use
+            this field to first add the type.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__prop_adder_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__prop_adder_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 27 - 0
tripal_chado/includes/TripalFields/chado_linker__prop_adder_formatter.inc

@@ -0,0 +1,27 @@
+<?php
+
+class chado_linker__prop_adder_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Add a Property Type';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__prop_adder');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+  }
+}

+ 282 - 0
tripal_chado/includes/TripalFields/chado_linker__prop_adder_widget.inc

@@ -0,0 +1,282 @@
+<?php
+
+class chado_linker__prop_adder_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Add a Property Type';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__prop_adder');
+
+  /**
+   *
+   * @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 = $widget['#field_name'];
+    
+    $widget['#type'] = 'fieldset';
+    $widget['#title'] = $element['#title'];
+    $widget['#description'] = $element['#description'];
+    $widget['#group'] = 'entity_form_vtabs';
+    
+    
+    $widget['kvproperty_instructions'] = array(
+      '#type' => 'item',
+      '#markup' => t('You may add additional properties to this form by
+          providing a property name (from a vocabulary) in the field below
+          and clicking the "Lookup Term" button.  Terms that match the value
+          entered will be displayed for selection.  After selecting the
+          appropriate term click the "Use this term" button and a
+          new field will be added to the form above for the property you selected.
+          In the future, this field will be present for all records
+          of this type.'),
+    );
+    
+    $term_name = isset($form_state['values'][$field_name]['und'][0]['wrapper']['term_name']) ? $form_state['values'][$field_name]['und'][0]['wrapper']['term_name'] : '';
+    
+    // Drupal's vertical feild set is a bit quirky in that we can't just
+    // add a prefix and suffix to the weidget.  If we do, then the
+    // field doesn't show up on the page after an AJAX call.  We have to add
+    // an internal wrapper (below) for AJAX calls.
+    $widget['wrapper'] = array(
+      '#prefix' =>  "<span id='$field_name-lookup-form'>",
+      '#suffix' => '</span>',
+    );
+    
+    // If no term has been selected yet then provide the auto complete field.
+    $widget['wrapper']['term_name'] = array(
+      '#title'       => t('Term'),
+      '#type'        => 'textfield',
+      '#description' => t("The content type must be the name of a term in
+        a controlled vocabulary and the controlled vocabulary should
+        already be loaded into Tripal.  For example, to create a content
+        type for storing 'genes', use the 'gene' term from the
+        Sequence Ontology (SO)."),
+      '#default_value' => $term_name,
+      '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/cvterm/",
+    );
+    
+    $widget['wrapper']['select_button'] = array(
+      '#type' => 'button',
+      '#value' => t('Lookup Term'),
+      '#name' => 'select_cvterm',
+      '#ajax' => array(
+        'callback' => "tripal_chado_prop_adder_form_ajax_callback",
+        'wrapper' => "$field_name-lookup-form",
+        'effect' => 'fade',
+        'method' => 'replace'
+      ),
+    );
+    if ($term_name) {
+      $widget['wrapper']['terms_list'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Matching Terms'),
+        '#description' => t('Please select the term the best matches the
+          content type you want to create. If the same term exists in
+          multiple vocabularies you will see more than one option below.')
+      );
+      $match = array(
+        'name' => $term_name,
+      );
+      $terms = chado_generate_var('cvterm', $match, array('return_array' => TRUE));
+      $terms = chado_expand_var($terms, 'field', 'cvterm.definition');
+      $num_terms = 0;
+      foreach ($terms as $term) {
+        // Save the user a click by setting the default value as 1 if there's
+        // only one matching term.
+        $default = FALSE;
+        $attrs = array();
+        if ($num_terms == 0 and count($terms) == 1) {
+          $default = TRUE;
+          $attrs = array('checked' => 'checked');
+        }
+        $widget['wrapper']['terms_list']['term-' . $term->cvterm_id] = array(
+          '#type' => 'checkbox',
+          '#title' =>  $term->name,
+          '#default_value' => $default,
+          '#attributes' => $attrs,
+          '#description' => '<b>Vocabulary:</b> ' . $term->cv_id->name . ' (' . $term->dbxref_id->db_id->name . ') ' . $term->cv_id->definition .
+          '<br><b>Term: </b> ' . $term->dbxref_id->db_id->name . ':' . $term->dbxref_id->accession . '.  ' .
+          '<br><b>Definition:</b>  ' . $term->definition,
+        );
+        $num_terms++;
+      }
+      if ($num_terms == 0) {
+        $widget['wrapper']['terms_list']['none'] = array(
+          '#type' => 'item',
+          '#markup' => '<i>' . t('There is no term that matches the entered text.') . '</i>'
+        );
+      }
+      else {
+        $widget['wrapper']['cardinality'] = array(
+          '#title'       => t('Number of Values'),
+          '#type'        => 'textfield',
+          '#description' => t("A number of 1 or more indicating the number of values allowed for this property. Enter -1 for unlimited values"),
+          '#size' => 10,
+          '#default_value' => 1,
+        );
+        // Add in the button for the cases of no terms or too many.
+        $widget['wrapper']['submit_button'] = array(
+          '#type' => 'submit',
+          '#value' => t('Use this term'),
+          '#name' => 'use_term_button',
+        );
+      }
+    }
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field = $this->field;
+    $field_name = $field['field_name'];
+    
+    if ($form_state['triggering_element']['#name'] == 'use_term_button') {
+      // If the user has clicked the 'user_term_button' then we need to makes
+      // sure that the cardinality textbox contains a positive integer.
+      $cardinality = $form_state['values'][$field_name][$langcode][$delta]['wrapper']['cardinality'];
+      if (!preg_match('/^-*\d+$/', $cardinality) or $cardinality < -2 or $cardinality == 0) {
+        form_set_error("$field_name][$langcode][$delta][wrapper][cardinality", "Please provide positive number for the number of values, or -1 for unlimited.");
+      }
+    
+      // Get selected terms
+      $terms_list = $form_state['values'][$field_name][$langcode][$delta]['wrapper']['terms_list'];
+      $counter = 0;
+      $selected_term_id = '';
+      foreach ($terms_list AS $term => $selected) {
+        if ($selected) {
+          $selected_term_id = str_replace('term-', '', $term);
+          $counter ++;
+        }
+      }
+      // Make sure at least a term is selected
+      if ($counter == 0) {
+        form_set_error("$field_name][$langcode][$delta][wrapper][terms_list", "Please select a term.");
+      }
+      // Make sure only one term is selected
+      if ($counter > 1) {
+        form_set_error("$field_name][$langcode][$delta][wrapper][terms_list", "Please select only one term.");
+      }
+      // Make sure this property doesn't already have a field
+      if (key_exists($field_name . '__' . $selected_term_id, $form_state['values'])) {
+        form_set_error("$field_name][$langcode][$delta][wrapper][terms_name", "Property already exists. Please select another term.");
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    // Add the new field to the entity but only if the property adder button
+    // was clicked
+    if (!array_key_exists('triggering_element', $form_state) or
+        $form_state['triggering_element']['#name'] != 'use_term_button') {
+          return;
+        }
+        else {
+          // Because we're going to add a new property we want to rebuild the form
+          // rather than have it fully submit.
+          $form_state['rebuild'] = TRUE;
+        }
+    
+        // Get the table and base table.
+        $base_table = $this->field['settings']['base_table'];
+    
+        // Get the term for the property
+        $field = $this->field;
+        $fname = $field['field_name'];
+        $cardinality = $form_state['values'][$fname][$langcode][$delta]['wrapper']['cardinality'];
+        // Get selected terms
+        $terms_list = $form_state['values'][$fname][$langcode][$delta]['wrapper']['terms_list'];
+        $selected_term_id = '';
+        foreach ($terms_list AS $term => $selected) {
+          if ($selected) {
+            $selected_term_id = str_replace('term-', '', $term);
+          }
+        }
+        $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $selected_term_id));
+    
+        // Generate the name for the property table and the field name that we'll
+        // be creating.
+        $prop_table = $base_table . 'prop';
+        $field_name = $prop_table . '__' . $cvterm->cvterm_id;
+    
+        // The field name is the table name in this case. We want to get the
+        // primary key as this should be the field that maps th the value.
+        $schema = chado_get_schema($prop_table);
+        $pkey = $schema['primary key'][0];
+    
+        // Add the field if it doesn't already exists.
+        $field = field_info_field($field_name);
+        if (!$field) {
+          $field = field_create_field(array(
+            'field_name' => $field_name,
+            'type' => 'chado_linker__prop',
+            'cardinality' => $cardinality,
+            'locked' => FALSE,
+            'storage' => array(
+              'type' => 'field_chado_storage',
+            ),
+            'settings' => array(
+              'chado_table' => $prop_table,
+              'chado_column' => $pkey,
+              'base_table' => $base_table,
+              'semantic_web' => $cvterm->dbxref_id->db_id->name . ':' . $cvterm->dbxref_id->accession,
+            ),
+          ));
+        }
+    
+        // Create an instance of the field.
+        $instance = field_info_instance($entity_type, $field_name,  $entity->bundle);
+        if (!$instance) {
+          $instance = field_create_instance(array(
+            'field_name' => $field_name,
+            'entity_type' => 'TripalEntity',
+            'bundle' => $entity->bundle,
+            'label' => ucfirst(preg_replace('/_/', ' ', $cvterm->name)),
+            'description' => $cvterm->definition ? $cvterm->definition : '',
+            'required' => FALSE,
+            'settings' => array(
+              'auto_attach' => FALSE,
+            ),
+            'widget' => array(
+              'type' => 'chado_linker__prop_widget',
+              'settings' => array(
+                'display_label' => 1,
+              ),
+            ),
+            'display' => array(
+              'default' => array(
+                'label' => 'inline',
+                'type' => 'chado_linker__prop_formatter',
+                'settings' => array(),
+              ),
+            ),
+          ));
+        }
+  }
+}
+
+/**
+ *
+ */
+function tripal_chado_prop_adder_form_ajax_callback($form, $form_state) {
+  $field_name = $form_state['triggering_element']['#parents'][0];
+
+  // Because this field is inside a vertical fieldset we can't just
+  // return $form[$field_name]. We have set the AJAX call to replace
+  // everything inside of the 'wrapper' element, so we must return that.
+  return $form[$field_name]['und'][0]['wrapper'];
+}

+ 27 - 0
tripal_chado/includes/TripalFields/chado_linker__prop_formatter.inc

@@ -0,0 +1,27 @@
+<?php
+
+class chado_linker__prop_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Property';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__prop');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+  }
+}

+ 117 - 0
tripal_chado/includes/TripalFields/chado_linker__prop_widget.inc

@@ -0,0 +1,117 @@
+<?php
+
+class chado_linker__prop_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Property';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__prop');
+
+  /**
+   *
+   * @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->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $instance = $this->instance;
+    
+    // Get the table name and cvterm that this field maps to.
+    $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];
+    $cvterm_id = $matches[2];
+    
+    // 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($table_name);
+    $pkey = $schema['primary key'][0];
+    $base_table = $this->field['settings']['base_table'];
+    $lfkey_field = key($schema['foreign keys'][$base_table]['columns']);
+    $rfkey_field = $schema['foreign keys'][$base_table]['columns'][$lfkey_field];
+    
+    // Get the field defaults.
+    $fk_value =key_exists(0, $items) ? $items[0]['chado-' . $field_table . '__' . $lfkey_field] : '';
+    $propval = '';
+    if (array_key_exists($delta, $items)) {
+      $propval = tripal_get_field_item_keyval($items, $delta, 'chado-' . $table_name . '__value', $propval);
+    }
+    
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+    
+    $widget['chado-' . $table_name . '__' . $pkey] = array(
+      '#type' => 'hidden',
+      '#default_value' => !empty($items[$delta]['chado-' . $field_table . '__' . $pkey]) ? $items[$delta]['chado-' . $field_table . '__' . $pkey] : '',
+    );
+    $widget['chado-' . $table_name . '__' . $lfkey_field] = array(
+      '#type' => 'hidden',
+      '#value' => $fk_value,
+    );
+    $widget['chado-' . $table_name . '__value'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $propval,
+      '#title' => $instance['label'] . ' value',
+      '#description' => $instance['description'],
+    );
+    $widget['chado-' . $table_name . '__type_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $cvterm_id,
+    );
+    $widget['chado-' . $table_name . '__rank'] = array(
+      '#type' => 'hidden',
+      '#value' => $delta,
+    );
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $table_name = $this->field['settings']['chado_table'];
+    $schema = chado_get_schema($table_name);
+    $pkey = $schema['primary key'][0];
+    $base_table = $this->field['settings']['base_table'];
+    $lfkey_field = key($schema['foreign keys'][$base_table]['columns']);
+    $rfkey_field = $schema['foreign keys'][$base_table]['columns'][$lfkey_field];
+    
+    $prop_value = isset($form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__value']) ? $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__value'] : '';
+    
+    // If the user removed the contact from the contact_name field
+    // then we want to clear out the rest of the hidden values.
+    // Leave the primary key so the record can be deleted.
+    if (!$prop_value) {
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $pkey] = '';
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__rank'] = '';
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__type_id'] = '';
+      $form_state['values'][$field_name][$langcode][$delta]['chado-' . $table_name . '__' . $lfkey_field] = '';
+    }
+  }
+}

+ 162 - 0
tripal_chado/includes/TripalFields/chado_linker__pub.inc

@@ -0,0 +1,162 @@
+<?php
+
+class chado_linker__pub extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'schema:publication';
+
+  // The default lable for this field.
+  public static $label = 'Publications';
+
+  // The default description for this field.
+  public static $description = 'Associates a publication (e.g. journal article,
+            conference proceedings, book chapter, etc.) with this record.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__pub_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__pub_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $record = $details['record'];
+    
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['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];
+    
+    // Set some defaults for the empty record.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      'chado-' . $field_table . '__' . $pkey => '',
+      'chado-' . $field_table . '__' . $fkey_lcolumn => '',
+      'chado-' . $field_table . '__' . 'pub_id' => '',
+      'uniquename' => '',
+    );
+    
+    $linker_table = $base_table . '_pub';
+    $options = array(
+      'return_array' => 1,
+    );
+    $record = chado_expand_var($record, 'table', $linker_table, $options);
+    
+    if (count($record->$linker_table) > 0) {
+      $i = 0;
+      foreach ($record->$linker_table as $index => $linker) {
+        $pub = $linker->pub_id;
+        $pub_details = tripal_get_minimal_pub_info($pub);
+        $pub_details['@type'] = $pub->type_id->dbxref_id->db_id->name . ':' . $pub->type_id->dbxref_id->accession;
+        $pub_details['publication']['type'] = $pub->type_id->name;
+    
+        $entity->{$field_name}['und'][$i]['value'] = $pub_details;
+        $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;
+        }
+        $i++;
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 27 - 0
tripal_chado/includes/TripalFields/chado_linker__pub_formatter.inc

@@ -0,0 +1,27 @@
+<?php
+
+class chado_linker__pub_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Publications';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__pub');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+  }
+}

+ 173 - 0
tripal_chado/includes/TripalFields/chado_linker__pub_widget.inc

@@ -0,0 +1,173 @@
+<?php
+
+class chado_linker__pub_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Publications';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__pub');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+    $entity = $form['#entity'];
+    $field_name = $this->field['field_name'];
+    
+    // Get the FK column that links to the base table.
+    $table_name = $this->field['settings']['chado_table'];
+    $base_table = $this->field['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 defaults.
+    $record_id = '';
+    $fkey_value = $element['#entity']->chado_record_id;
+    $pub_id = '';
+    $uname = '';
+    
+    // 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)) {
+      $record_id = tripal_get_field_item_keyval($items, $delta, 'chado-' . $table_name . '__' . $pkey, $record_id);
+      $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);
+    }
+    
+    $schema = chado_get_schema('pub');
+    
+    $widget['#table_name'] = $table_name;
+    $widget['#fkey_field'] = $fkey;
+    $widget['#theme'] = 'chado_linker__pub_widget';
+    $widget['#prefix'] =  "<span id='$table_name-$delta'>";
+    $widget['#suffix'] =  "</span>";
+    
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+    
+    $widget['chado-' . $table_name . '__' . $pkey] = array(
+      '#type' => 'value',
+      '#default_value' => $record_id,
+    );
+    $widget['chado-' . $table_name . '__' . $fkey] = array(
+      '#type' => 'value',
+      '#default_value' => $fkey_value,
+    );
+    $widget['chado-' . $table_name . '__pub_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $pub_id,
+    );
+    
+    $widget['uniquename'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Publication'),
+      '#default_value' => $uname,
+      '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/pub',
+      '#ajax' => array(
+        'callback' => "chado_linker__pub_widget_form_ajax_callback",
+        'wrapper' => "$table_name-$delta",
+        'effect' => 'fade',
+        'method' => 'replace'
+      ),
+      '#maxlength' => 100000,
+    );
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalFieldWidget::submit()
+   */
+  public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    // Get the FK column that links to the base table.
+    $table_name = $this->field['settings']['chado_table'];
+    $base_table = $this->field['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];
+    $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'] : '';
+    
+    // If the user provided a uniquename 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;
+    }
+    
+    // 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'] = '';
+    }
+  }
+}
+
+/**
+ * An Ajax callback for the pub widget.
+ */
+function chado_linker__pub_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];
+}
+/**
+ * Theme function for the pub widget.
+ *
+ * @param $variables
+ */
+function theme_chado_linker__pub_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=\"pub-widget\">
+        <div class=\"pub-widget-item\">" .
+        drupal_render($element['uniquename']) . "
+        </div>
+      </div>
+    ";
+
+        return $layout;
+}

+ 596 - 0
tripal_chado/includes/TripalFields/chado_linker__relationship.inc

@@ -0,0 +1,596 @@
+<?php
+
+class chado_linker__relationship extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'SBO:0000374';
+
+  // The default lable for this field.
+  public static $label = 'Relationships';
+
+  // The default description for this field.
+  public static $description = 'Relationships between records.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__relationship_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__relationship_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $settings = $this->field['settings'];
+    
+    $record = $details['record'];
+    
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['settings']['base_table'];
+    
+    // Get the PKey for this table
+    $schema = chado_get_schema($field_table);
+    $pkey = $schema['primary key'][0];
+    
+    // Get the Pkeys for the subject and object tables
+    $subject_fkey_table = '';
+    $object_fkey_table = '';
+    $fkeys = $schema['foreign keys'];
+    foreach ($fkeys as $fktable => $details) {
+      foreach ($details['columns'] as $fkey_lcolumn => $fkey_rcolumn) {
+        if ($fkey_lcolumn == 'subject_id') {
+          $subject_fkey_table = $fktable;
+        }
+        if ($fkey_lcolumn == 'object_id') {
+          $object_fkey_table = $fktable;
+        }
+      }
+    }
+    $subject_schema = chado_get_schema($subject_fkey_table);
+    $object_schema = chado_get_schema($object_fkey_table);
+    $subject_pkey = $subject_schema['primary key'][0];
+    $object_pkey = $object_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];
+    
+    // Set some defaults for the empty record.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      $field_table . '__' . $pkey => '',
+      $field_table . '__subject_id' => '',
+      $field_table . '__object_id' => '',
+      $field_table . '__type_id' => '',
+      // These elements don't need to follow the naming scheme above
+      // becasue we don't need the chado_field_storage to try and
+      // save these values.
+      'object_name' => '',
+      'subject_name' => '',
+      'type_name' => '',
+    );
+    
+    // If the table has rank and value fields then add those to the default
+    // value array.
+    if (array_key_exists('value', $schema['fields'])) {
+      $entity->{$field_name}['und'][0][$field_table . '__value'] = '';
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $entity->{$field_name}['und'][0][$field_table . '__rank'] = '';
+    }
+    
+    // If we have no record then just return.
+    if (!$record) {
+      return;
+    }
+    
+    // Expand the object to include the relationships.
+    $options = array(
+      'return_array' => 1,
+      // we don't want to fully recurse we only need information about the
+      // relationship type and the object and subject
+      'include_fk' => array(
+        'type_id' => 1,
+        'object_id' => array(
+          'type_id' => 1,
+        ),
+        'subject_id'  => array(
+          'type_id' => 1,
+        ),
+      ),
+    );
+    $rel_table = $base_table . '_relationship';
+    $schema = chado_get_schema($rel_table);
+    if (array_key_exists('rank', $schema['fields'])) {
+      $options['order_by'] = array('rank' => 'ASC');
+    }
+    $record = chado_expand_var($record, 'table', $rel_table, $options);
+    if (!$record->$rel_table) {
+      return;
+    }
+    $srelationships = null;
+    $orelationships = null;
+    if ($rel_table == 'organism_relationship') {
+      $srelationships = $record->$rel_table->subject_organism_id;
+      $orelationships = $record->$rel_table->object_organism_id;
+    }
+    else if ($rel_table == 'nd_reagent_relationship') {
+      $srelationships = $record->$rel_table->subject_reagent_id;
+      $orelationships = $record->$rel_table->object_reagent_id;
+    }
+    else if ($rel_table == 'project_relationship') {
+      $srelationships = $record->$rel_table->subject_project_id;
+      $orelationships = $record->$rel_table->object_project_id;
+    }
+    else {
+      $srelationships = $record->$rel_table->subject_id;
+      $orelationships = $record->$rel_table->object_id;
+    }
+    
+    $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 = self::get_rel_verb($rel_type);
+        $subject_name = $relationship->subject_id->name;
+        $subject_type = $relationship->subject_id->type_id->name;
+        $object_name = $relationship->object_id->name;
+        $object_type = $relationship->object_id->type_id->name;
+        $entity->{$field_name}['und'][$i]['value'] = array(
+          'type' => $relationship->type_id->name,
+          'subject' => array(
+            'type' => $subject_type,
+            'name' => $subject_name,
+          ),
+          'type' => $relationship->type_id->name,
+          'object' => array(
+            'type' => $object_type,
+            'name' => $object_name,
+            'entity' => 'TripalEntity:' . $entity->id,
+          )
+        );
+        if (property_exists($relationship->subject_id, 'uniquename')) {
+          $entity->{$field_name}['und'][$i]['value']['subject']['identifier'] =  $relationship->subject_id->uniquename;;
+        }
+        if (property_exists($relationship->object_id, 'uniquename')) {
+          $entity->{$field_name}['und'][$i]['value']['object']['identifier'] = $relationship->object_id->uniquename;
+        }
+        if (property_exists($relationship->subject_id, 'entity_id')) {
+          $entity_id = $relationship->subject_id->entity_id;
+          $entity->{$field_name}['und'][$i]['value']['subject']['entity'] = 'TripalEntity:' . $entity_id;
+        }
+        $rel_type_clean = lcfirst(preg_replace('/_/', ' ', $rel_type));
+        $entity->{$field_name}['und'][$i]['value']['phrase'] = 'The ' . $subject_type . ', ' .
+            $subject_name . ', ' . $verb . ' '  . $rel_type_clean . ' this '  .
+            $object_type . '.';
+    
+            $entity->{$field_name}['und'][$i]['semantic_web'] = array(
+              'type' => $rel_acc,
+              'subject' => $relationship->subject_id->type_id->dbxref_id->db_id->name . ':' . $relationship->subject_id->type_id->dbxref_id->accession,
+              'object' => $relationship->object_id->type_id->dbxref_id->db_id->name . ':' . $relationship->object_id->type_id->dbxref_id->accession,
+            );
+    
+            $entity->{$field_name}['und'][$i][$field_table . '__' . $pkey] = $relationship->$pkey;
+            $entity->{$field_name}['und'][$i][$field_table . '__subject_id'] = $relationship->subject_id->$subject_pkey;
+            $entity->{$field_name}['und'][$i][$field_table . '__type_id'] = $relationship->type_id->cvterm_id;
+            $entity->{$field_name}['und'][$i][$field_table . '__object_id'] = $relationship->object_id->$object_pkey;
+    
+            $entity->{$field_name}['und'][$i]['type_name'] = $relationship->type_id->name;
+            $entity->{$field_name}['und'][$i]['subject_name'] = $relationship->subject_id->name . ' [id: ' . $relationship->subject_id->$fkey_rcolumn . ']';
+            $entity->{$field_name}['und'][$i]['object_name'] = $relationship->object_id->name  . ' [id: ' . $relationship->object_id->$fkey_rcolumn . ']';
+            if (array_key_exists('value', $schema['fields'])) {
+              $entity->{$field_name}['und'][$i][$field_table . '__value'] = $relationship->value;
+            }
+            if (array_key_exists('rank', $schema['fields'])) {
+              $entity->{$field_name}['und'][$i][$field_table . '__rank'] = $relationship->rank;
+            }
+            $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 = self::get_rel_verb($rel_type);
+        $subject_name = $relationship->subject_id->name;
+        $subject_type = $relationship->subject_id->type_id->name;
+        $object_name = $relationship->object_id->name;
+        $object_type = $relationship->object_id->type_id->name;
+        $entity->{$field_name}['und'][$i]['value'] = array(
+          '@type' => $relationship->type_id->name,
+          'subject' => array(
+            'type' => $subject_type,
+            'name' => $subject_name,
+            'entity' => 'TripalEntity:' . $entity->id,
+          ),
+          'type' => $relationship->type_id->name,
+          'object' => array(
+            'type' =>  $object_type,
+            'name' => $object_name,
+          )
+        );
+        if (property_exists($relationship->subject_id, 'uniquename')) {
+          $entity->{$field_name}['und'][$i]['value']['subject']['identifier'] = $relationship->subject_id->uniquename;
+        }
+        if (property_exists($relationship->object_id, 'uniquename')) {
+          $entity->{$field_name}['und'][$i]['value']['object']['identifier'] = $relationship->object_id->uniquename;
+        }
+        if (property_exists($relationship->object_id, 'entity_id')) {
+          $entity_id = $relationship->object_id->entity_id;
+          $entity->{$field_name}['und'][$i]['value']['object']['entity'] = 'TripalEntity:' . $entity_id;
+        }
+        $rel_type_clean = lcfirst(preg_replace('/_/', ' ', $rel_type));
+        $entity->{$field_name}['und'][$i]['value']['phrase'] = 'This  ' .
+            $subject_type . ' ' . $verb . ' '  . $rel_type_clean . ' the '  .
+            $object_type . ', ' . $object_name . '.';
+    
+    
+            $entity->{$field_name}['und'][$i]['semantic_web'] = array(
+              'type' => $rel_acc,
+              'subject' => $relationship->subject_id->type_id->dbxref_id->db_id->name . ':' . $relationship->subject_id->type_id->dbxref_id->accession,
+              'object' => $relationship->object_id->type_id->dbxref_id->db_id->name . ':' . $relationship->object_id->type_id->dbxref_id->accession,
+            );
+    
+    
+            $entity->{$field_name}['und'][$i][$field_table . '__' . $pkey] = $relationship->$pkey;
+            $entity->{$field_name}['und'][$i][$field_table . '__subject_id'] = $relationship->subject_id->$subject_pkey;
+            $entity->{$field_name}['und'][$i][$field_table . '__type_id'] = $relationship->type_id->cvterm_id;
+            $entity->{$field_name}['und'][$i][$field_table . '__object_id'] = $relationship->object_id->$object_pkey;
+    
+            $entity->{$field_name}['und'][$i]['type_name'] = $relationship->type_id->name;
+            $entity->{$field_name}['und'][$i]['subject_name'] = $relationship->subject_id->name  . ' [id: ' . $relationship->subject_id->$fkey_rcolumn . ']';
+            $entity->{$field_name}['und'][$i]['object_name'] = $relationship->object_id->name  . ' [id: ' . $relationship->object_id->$fkey_rcolumn . ']';
+    
+            if (array_key_exists('value', $schema['fields'])) {
+              $entity->{$field_name}['und'][$i][$field_table . '__value'] = $relationship->value;
+            }
+            if (array_key_exists('rank', $schema['fields'])) {
+              $entity->{$field_name}['und'][$i][$field_table . '__rank'] = $relationship->rank;
+            }
+            $i++;
+      }
+    }
+  }
+
+  /**
+   * A helper function to define English verbs for relationship types.
+   *
+   * @param $rel_type
+   *   The vocabulary term name for the relationship.
+   *
+   * @return
+   *   The verb to use when creating a sentence of the relationship.
+   */
+  public static function get_rel_verb($rel_type) {
+    $rel_type_clean = lcfirst(preg_replace('/_/', ' ', $rel_type));
+    $verb = $rel_type_clean;
+    switch ($rel_type_clean) {
+      case 'integral part of':
+      case 'instance of':
+        $verb = 'is an';
+        break;
+      case 'proper part of':
+      case 'transformation of':
+      case 'genome of':
+      case 'part of':
+        $verb = 'is a';
+      case 'position of':
+      case 'sequence of':
+      case 'variant of':
+        $verb = 'is a';
+        break;
+      case 'derives from':
+      case 'connects on':
+      case 'contains':
+      case 'finishes':
+      case 'guides':
+      case 'has origin':
+      case 'has part':
+      case 'has quality':
+      case 'is consecutive sequence of':
+      case 'maximally overlaps':
+      case 'overlaps':
+      case 'starts':
+        break;
+      default:
+        $verb = 'is';
+    }
+  
+    return $verb;
+  }
+  
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+    $element = parent::instanceSettingsForm();
+    
+    //$element = parent::instanceSettingsForm();
+    $element['relationships'] = array(
+      '#type' => 'fieldset',
+      '#title' => 'Allowed Relationship Types',
+      '#description' => t('There are three ways that relationship types
+          can be limited for users who have permission to add new relationships.
+          Please select the most appropriate for you use case.  By default
+          all vocabularies are provided to the user which allows use of any
+          term for the relationship type.'),
+      '#collapsed' => TRUE,
+      '#collapsible' => TRUE,
+      '#theme' => 'chado_linker__relationship_instance_settings'
+    );
+    //     $element['instructions'] = array(
+    //       '#type' => 'item',
+    //       '#markup' => 'You may provide a list of terms that will be available in a select box
+    //         as the relationship types. This select box will replace the vocabulary select box if the
+    //         following value is set.'
+    //     );
+    $vocs = tripal_get_cv_select_options();
+    $element['relationships']['option1'] = array(
+      '#type' => 'item',
+      '#title' => 'Option #1',
+      '#description' => t('Use this option to limit the vocabularies that a user .
+        could use to specify relationship types. With this option any term in .
+        the vocabulary can be used for the relationship type. You may select
+        more than one vocabulary.'),
+    
+    );
+    $element['relationships']['option1_vocabs'] = array(
+      '#type' => 'select',
+      '#multiple' => TRUE,
+      '#options' => $vocs,
+      '#size' => 6,
+      '#default_value' => $this->instance['settings']['relationships']['option1_vocabs'],
+      // TODO add ajax here so that the relationship autocomplete below works
+    );
+    
+    $element['relationships']['option2'] = array(
+      '#type' => 'item',
+      '#title' => '<b>Option #2</b>',
+      '#description' => 'Some vocabularies are heirarchichal (an ontology). Within this
+         heirarchy groups of related terms typically fall under a common parent. If you
+         wish to limit the list of terms that a user can use for the relationship type,
+         you can provide the parent term here.  Then, only that term\'s children will
+         be avilable for use as a relationship type.',
+    );
+    $element['relationships']['option2_vocab'] = array(
+      '#type' => 'select',
+      '#description' => 'Specify Default Vocabulary',
+      '#multiple' => FALSE,
+      '#options' => $vocs,
+      '#default_value' => $this->instance['settings']['relationships']['option2_vocab'],
+      '#ajax' => array(
+        'callback' => "chado_linker__relationship_instance_settings_form_ajax_callback",
+        'wrapper' => 'relationships-option2-parent',
+        'effect' => 'fade',
+        'method' => 'replace'
+      ),
+    );
+    $element['relationships']['option2_parent'] = array(
+      '#type' => 'textfield',
+      '#description' => 'Specify a Heirarchical Parent Term',
+      '#default_value' => $this->instance['settings']['relationships']['option2_parent'],
+      '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/cvterm/",
+      '#prefix' => '<div id=relationships-option2-parent>',
+      '#suffix' => '</div>'
+    );
+    $element['relationships']['option3'] = array(
+      '#type' => 'item',
+      '#title' => 'Option #3',
+      '#description' => 'Provide terms separated by a new line. The term provided should be
+        unique and distinguishable by the name. You can use a bar | to separate a vocabulary
+        and a term to allow more specific assignment.',
+    );
+    $element['relationships']['relationship_types'] = array(
+      '#type' => 'textarea',
+      '#default_value' => $this->instance['settings']['relationships']['relationship_types'],
+    );
+    
+    return $element;
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+    // Get relationships settings
+    $settings = $form_state['values']['instance']['settings']['relationships'];
+    $form_state['values']['instance']['settings']['relationships']['relationship_types']= trim($settings['relationship_types']);
+    
+    // Make sure only one option is selected
+    $option1test = $settings['option1_vocabs'];
+    $option1 = isset($settings['option1_vocabs']) && array_pop($option1test);
+    $option2 = (isset($settings['option2_vocab']) && $settings['option2_vocab']) || $settings['option2_parent'];
+    $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;
+        }
+    
+        // 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)
+                    );
+              }
+            }
+            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))
+                    );
+              }
+            }
+          }
+        }
+    
+        // 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::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 100 - 0
tripal_chado/includes/TripalFields/chado_linker__relationship_formatter.inc

@@ -0,0 +1,100 @@
+<?php
+
+class chado_linker__relationship_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Relationships';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__relationship');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @see TripalFieldFormatter::settingsForm()
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    // Get the settings
+    $settings = $display['settings'];
+    
+    $rows = array();
+    $headers = array('Subject' ,'Type', 'Object');
+    $headers = array('Relationship');
+    
+    foreach ($items as $delta => $item) {
+      if (!$item['value']) {
+        continue;
+      }
+      $subject_name = $item['value']['subject']['name'];
+      $subject_type = $item['value']['subject']['type'];
+      $object_name = $item['value']['object']['name'];
+      $object_type = $item['value']['object']['type'];
+      $phrase = $item['value']['phrase'];
+    
+      // Handle some special cases.
+      // For mRNA objects we don't want to show the CDS, exons, 5' UTR, etc.
+      // we want to show the parent gene and the protein.
+      if ($object_type == 'mRNA' and ($subject_type != 'polypeptide')) {
+        continue;
+      }
+      if ($subject_type == 'mRNA' and ($object_type != 'gene')) {
+        continue;
+      }
+    
+      $phrase = preg_replace("/$subject_type/", "<b>$subject_type</b>", $phrase);
+      $phrase = preg_replace("/$object_type/", "<b>$object_type</b>", $phrase);
+    
+      if (array_key_exists('entity', $item['value']['object'])) {
+        list($entity_type, $object_entity_id) = explode(':', $item['value']['object']['entity']);
+        if ($object_entity_id != $entity->id) {
+          $link = l($object_name, 'bio_data/' . $object_entity_id);
+          $phrase = preg_replace("/$object_name/", $link, $phrase);
+        }
+      }
+      if (array_key_exists('entity', $item['value']['subject'])) {
+        list($entity_type, $subject_entity_id) = explode(':', $item['value']['subject']['entity']);
+        if ($subject_entity_id != $entity->id) {
+          $link = l($subject_name, 'bio_data/' . $subject_entity_id);
+          $phrase = preg_replace("/$subject_name/", $link, $phrase);
+        }
+      }
+    
+      $rows[] = array($phrase);
+    }
+    
+    
+    // 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-linker--relationship-table',
+        'class' => 'tripal-data-table'
+      ),
+      'sticky' => FALSE,
+      'caption' => '',
+      'colgroups' => array(),
+      'empty' => 'There are no relationships',
+    );
+    
+    // once we have our table array structure defined, we call Drupal's theme_table()
+    // function to generate the table.
+    if (count($items) > 0) {
+      $element[0] = array(
+        '#type' => 'markup',
+        '#markup' => theme_table($table),
+      );
+    }
+  }
+}

+ 643 - 0
tripal_chado/includes/TripalFields/chado_linker__relationship_widget.inc

@@ -0,0 +1,643 @@
+<?php
+
+class chado_linker__relationship_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Relationships';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__relationship');
+
+  /**
+   *
+   * @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->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    
+    // Get the instance settings
+    $instance = $this->instance;
+    $settings = $instance['settings']['relationships'];
+    
+    $option1_vocabs = $settings['option1_vocabs'];
+    $option2_vocab  = $settings['option2_vocab'];
+    $option2_parent = $settings['option2_parent'];
+    $option3_rtypes = $settings['relationship_types'];
+    
+    // For testing if there are selected vocabs for option1 we'll copy the
+    // contents in a special variable for later.
+    $option1_test   = $option1_vocabs;
+    
+    // Get the FK column that links to the base table.
+    $chado_table = $this->field['settings']['chado_table'];
+    $base_table = $this->field['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'] ? $element['#entity']->chado_record_id : '';
+    $subject_id = '';
+    $subject_uniquename = '';
+    $type_id = '';
+    $type = '';
+    $object_id = '';
+    $object_uniquename = '';
+    $value = '';
+    $rank = '';
+    
+    // Handle special cases
+    $subject_id_key = 'subject_id';
+    $object_id_key = 'object_id';
+    if ($chado_table == 'organism_relationship') {
+      $subject_id_key = 'subject_organism_id';
+      $object_id_key = 'object_organism_id';
+    }
+    else if ($chado_table == 'nd_reagent_relationship') {
+      $subject_id_key = 'subject_reagent_id';
+      $object_id_key = 'object_reagent_id';
+    }
+    else if ($chado_table == 'project_relationship') {
+      $subject_id_key = 'subject_project_id';
+      $object_id_key = 'object_project_id';
+    }
+    
+    // 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 = isset($items[$delta][$field_table . '__' . $pkey]) ? $items[$delta][$field_table . '__' . $pkey] : '';
+      $subject_id = isset($items[$delta][$field_table . '__' . $subject_id_key]) ? $items[$delta][$field_table . '__' . $subject_id_key] : '';
+      $type_id = isset($items[$delta][$field_table . '__type_id']) ? $items[$delta][$field_table . '__type_id'] : '';
+      $object_id = isset($items[$delta][$field_table . '__' . $object_id_key]) ? $items[$delta][$field_table . '__' . $object_id_key] : '';
+    
+      if (isset($items[$delta][$field_table . '__value'])) {
+        $value = $items[$delta][$field_table . '__value'];
+      }
+      if (isset($items[$delta][$field_table . '__rank'])) {
+        $rank = $items[$delta][$field_table . '__rank'];
+      }
+    
+      $object_uniquename = isset($items[$delta]['object_name']) ? $items[$delta]['object_name'] : '';
+      $subject_uniquename = isset($items[$delta]['subject_name']) ? $items[$delta]['subject_name'] : '';
+      $type = isset($items[$delta]['type_name']) ? $items[$delta]['type_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($delta, $form_state['values'])) {
+      //       $record_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_table . '__' . $pkey);
+      //       $subject_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_table . '__subject_id');
+      //       $subject_uniquename = tripal_chado_get_field_form_values($field_name, $form_state, $delta, 'subject_name');
+      //       $type_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_table . '__type_id');
+      //       $type = tripal_chado_get_field_form_values($field_name, $form_state, $delta, 'type_name');
+      //       $object_id = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_table . '__object_id');
+      //       $object_uniquename = tripal_chado_get_field_form_values($field_name, $form_state, $delta, 'object_name');
+      //       $value = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_table . '__value');
+      //       $rank = tripal_chado_get_field_form_values($field_name, $form_state, $delta, $field_table . '__rank');
+    }
+    
+    $widget['#table_name'] = $chado_table;
+    
+    $widget['#fkeys'] = $schema['foreign keys'];
+    $widget['#base_table'] = $base_table;
+    $widget['#chado_record_id'] = isset($form['#entity']) ? $form['#entity']->chado_record_id : '';
+    //$widget['#element_validate'] = array('chado_linker__relationship_validate');
+    $widget['#theme'] = 'chado_linker__relationship_widget';
+    $widget['#prefix'] =  "<span id='$chado_table-$delta'>";
+    $widget['#suffix'] =  "</span>";
+    
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+    $widget[$field_table . '__' . $pkey] = array(
+      '#type' => 'value',
+      '#default_value' => $record_id,
+    );
+    $widget[$field_table . '__' . $subject_id_key] = array(
+      '#type' => 'value',
+      '#default_value' => $subject_id,
+    );
+    $widget[$field_table . '__type_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $type_id,
+    );
+    $widget[$field_table . '__' . $object_id_key] = array(
+      '#type' => 'value',
+      '#default_value' => $object_id,
+    );
+    if (array_key_exists('value', $schema['fields'])) {
+      $widget[$field_table . '__value'] = array(
+        '#type' => 'value',
+        '#default_value' => $value,
+      );
+    }
+    if (array_key_exists('rank', $schema['fields'])) {
+      $widget[$field_table . '__rank'] = array(
+        '#type' => 'value',
+        '#default_value' => $rank,
+      );
+    }
+    $widget['subject_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Subject'),
+      '#default_value' => $subject_uniquename,
+      '#required' => $element['#required'],
+      '#maxlength' => array_key_exists('length', $schema['fields'][$subject_id_key]) ? $schema['fields'][$subject_id_key]['length'] : 255,
+      '#size' => 35,
+      '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/$base_table",
+    );
+    
+    // Getting default values
+    $default_voc = '';
+    if (isset($form_state['field'][$field_name]['und']['instance']['default_value'][0]['vocabulary'])) {
+      $default_voc = $form_state['field'][$field_name]['und']['instance']['default_value'][0]['vocabulary'];
+    }
+    $default_term = '';
+    if (isset($form_state['field'][$field_name]['und']['instance']['default_value'][0]['type_name'])) {
+      $default_term = $form_state['field'][$field_name]['und']['instance']['default_value'][0]['type_name'];
+    }
+    
+    $default_type_id = $type_id;
+    if (!$type_id && isset($form_state['field'][$field_name]['und']['instance']['default_value'][0]['type_id'])) {
+      $default_type_id = $form_state['field'][$field_name]['und']['instance']['default_value'][0]['type_id'];
+    }
+    // Option 3: Custom list of Relationship Types
+    $rtype_options = array();
+    $rtype_options[] = 'Select a Type';
+    if ($option3_rtypes) {
+      $rtypes = explode(PHP_EOL, $option3_rtypes);
+      foreach($rtypes AS $rtype) {
+        // Ignore empty lines
+        if (trim($rtype) == '') {
+          continue;
+        }
+        $term = tripal_get_cvterm(array('name' => trim($rtype)));
+        // Try to get term with vocabulary specified
+        if (!$term) {
+          $tmp = explode('|', trim($rtype), 2);
+          $cv = tripal_get_cv(array('name' => trim($tmp[0])));
+          $rtype = trim($tmp[1]);
+          $term = tripal_get_cvterm(array('name' => $rtype, 'cv_id' => $cv->cv_id));
+        }
+        $rtype_options[$term->cvterm_id] = $term->name;
+      }
+      $widget['type_id'] = array(
+        '#type' => 'select',
+        '#title' => t('Relationship Type'),
+        '#options' => $rtype_options,
+        '#default_value' => $default_type_id,
+      );
+      if ($type_id && !key_exists($type_id, $rtype_options)) {
+        form_set_error($this->field['field_name'] . '[' . $langcode . '][' . $delta . '][type_id]', 'Illegal option detected for Relationship Type. Please contact site administrator to fix the problem');
+      }
+    }
+    // Option 2: Child terms of a selected cvterm
+    else if ($option2_vocab) {
+      $values = array(
+        'cv_id' => $option2_vocab,
+        'name' => $option2_parent
+      );
+      $parent_term = tripal_get_cvterm($values);
+    
+      // If the term wasn't found then see if it's a synonym.
+      if(!$parent_term) {
+        $values = array(
+          'synonym' => array(
+            'name' => trim($option2_parent),
+          )
+        );
+        $synonym = tripal_get_cvterm($values);
+        if ($synonym && $synonym->cv_id->cv_id == $option2_vocab) {
+          $parent_term = $synonym;
+        }
+      }
+      // Get the child terms of the parent term found above.
+      $sql =
+      "SELECT
+           subject_id,
+           (SELECT name from {cvterm} where cvterm_id = subject_id) AS name
+         FROM {cvtermpath}
+         WHERE
+           object_id = :parent_cvterm_id
+         AND
+           cv_id = :parent_cv_id
+         ORDER BY name
+       ";
+      $args = array(
+        ':parent_cvterm_id' => $parent_term->cvterm_id,
+        ':parent_cv_id' => $parent_term->cv_id->cv_id
+      );
+      $results = chado_query($sql, $args);
+      while($child = $results->fetchObject()) {
+        $rtype_options[$child->subject_id] = $child->name;
+      }
+      $widget['type_id'] = array(
+        '#type' => 'select',
+        '#title' => t('Relationship Type'),
+        '#options' => $rtype_options,
+        '#default_value' => $default_type_id,
+      );
+      if ($type_id && !key_exists($type_id, $rtype_options)) {
+        form_set_error($this->field['field_name'] . '[' . $langcode . '][' . $delta . '][type_id]', 'Illegal option detected for Relationship Type. Please contact site administrator to fix the problem');
+      }
+    }
+    // Option 1: All terms of selected vocabularies
+    else if ($option1_test && array_pop($option1_test)) {
+      $sql = "SELECT cvterm_id, name FROM {cvterm} WHERE cv_id IN (:cv_id) ORDER BY name";
+      $results = chado_query($sql, array(':cv_id' => $option1_vocabs));
+      while ($obj = $results->fetchObject()) {
+        $rtype_options[$obj->cvterm_id] = $obj->name;
+      }
+      $widget['type_id'] = array(
+        '#type' => 'select',
+        '#title' => t('Relationship Type'),
+        '#options' => $rtype_options,
+        '#default_value' => $default_type_id,
+      );
+      if ($type_id && !key_exists($type_id, $rtype_options)) {
+        form_set_error($this->field['field_name'] . '[' . $langcode . '][' . $delta . '][type_id]', 'Illegal option detected for Relationship Type. Please contact site administrator to fix the problem');
+      }
+    }
+    // Default option:
+    else {
+      // Set up available cvterms for selection
+      $vocs = array(0 => 'Select a vocabulary');
+      $vocs = tripal_get_cv_select_options();
+      $cv_id = isset($form_state['values'][$field_name]['und'][0]['vocabulary']) ? $form_state['values'][$field_name]['und'][0]['vocabulary'] : 0;
+      // Try getting the cv_id from cvterm for existing records
+      if (!$cv_id && $type_id) {
+        $cvterm = tripal_get_cvterm(array('cvterm_id' => $type_id));
+        if (isset($cvterm->cv_id->cv_id)) {
+          $cv_id = $cvterm->cv_id->cv_id;
+          $default_term = $cvterm->name;
+        }
+      }
+      if (!$cv_id) {
+        $cv_id = $default_voc;
+      }
+      $widget['vocabulary'] = array(
+        '#type' => 'select',
+        '#title' => t('Vocabulary'),
+        '#options' => $vocs,
+        '#required' => $element['#required'],
+        '#default_value' => $cv_id,
+        '#ajax' => array(
+          'callback' => "chado_linker__relationship_widget_form_ajax_callback",
+          'wrapper' => "$chado_table-$delta",
+          'effect' => 'fade',
+          'method' => 'replace'
+        ),
+      );
+      if ($cv_id) {
+        $options = array();
+        $widget['type_name'] = array(
+          '#type' => 'textfield',
+          '#title' => t('Relationship Type'),
+          '#size' => 15,
+          '#default_value' => $default_term,
+          '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/cvterm/$cv_id"
+        );
+      }
+    }
+    
+    $widget['object_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Object'),
+      '#default_value' => $object_uniquename,
+      '#required' => $element['#required'],
+      '#maxlength' => array_key_exists('length', $schema['fields'][$object_id_key]) ? $schema['fields'][$object_id_key]['length'] : 255,
+      '#size' => 35,
+      '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/$base_table",
+    );
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $langcode, $delta) {
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['settings']['base_table'];
+    
+    $schema = chado_get_schema($field_table);
+    $fkeys = $schema['foreign keys'];
+    
+    // Handle special cases
+    $subject_id_key = 'subject_id';
+    $object_id_key = 'object_id';
+    if ($field_table == 'organism_relationship') {
+      $subject_id_key = 'subject_organism_id';
+      $object_id_key = 'object_organism_id';
+    }
+    else if ($field_table == 'nd_reagent_relationship') {
+      $subject_id_key = 'subject_reagent_id';
+      $object_id_key = 'object_reagent_id';
+    }
+    else if ($field_table == 'project_relationship') {
+      $subject_id_key = 'subject_project_id';
+      $object_id_key = 'object_project_id';
+    }
+    
+    foreach ($items as $delta => $item) {
+      $subject_id = $item[$field_table . '__' . $subject_id_key];
+      $object_id = $item[ $field_table . '__' . $object_id_key];
+      $type_id = $item[$field_table . '__type_id'];
+      $type_id = isset($item['type_id']) ? $item['type_id'] : $type_id;
+      $type_name = isset($item['type_name']) ? $item['type_name'] : '';
+      $subject_name = $item['subject_name'];
+      $object_name = $item['object_name'];
+    
+    
+      // If the row is empty then just continue, there's nothing to validate.
+      if (!$type_id and !$type_name and !$subject_name and !$object_name) {
+        continue;
+      }
+    
+      // Make sure we have values for all of the fields.
+      $form_error = FALSE;
+      if (!$type_name && !$type_id) {
+        $errors[$this->field['field_name']][$langcode][$delta][] = array(
+          'error' => 'chado_linker__relationship',
+          'message' => t("Please provide the type of relationship."),
+        );
+      }
+      if ($entity and !$subject_name) {
+        $errors[$this->field['field_name']][$langcode][$delta][] = array(
+          'error' => 'chado_linker__relationship',
+          'message' => t("Please provide the subject of the relationship."),
+        );
+      }
+      if ($entity and !$object_name) {
+        $errors[$this->field['field_name']][$langcode][$delta][] = array(
+          'error' => 'chado_linker__relationship',
+          'message' => t("Please provide the object of the relationship."),
+        );
+      }
+      if ($form_error) {
+        continue;
+      }
+    
+      // Before submitting this form we need to make sure that our subject_id and
+      // object_ids are real values.  There are two ways to get the value, either
+      // just with the text value or with an [id: \d+] string embedded.  If the
+      // later we will pull it out.
+      $subject_id = '';
+      $fkey_rcolumn = $fkeys[$base_table]['columns'][$subject_id_key];
+      $matches = array();
+      if ($entity) {
+        if(preg_match('/\[id: (\d+)\]/', $subject_name, $matches)) {
+          $subject_id =  $matches[1];
+          $values = array($fkey_rcolumn => $subject_id);
+          $subject = chado_select_record($base_table, array($fkey_rcolumn), $values);
+          if (count($subject) == 0) {
+            $errors[$this->field['field_name']][$langcode][$delta][] = array(
+              'error' => 'chado_linker__relationship',
+              'message' => t("The subject record cannot be found using the specified id (e.g. [id: xx])."),
+            );
+          }
+        }
+        else {
+          $values = array('uniquename' => $subject_name);
+          $subject = chado_select_record($base_table, array($fkey_rcolumn), $values);
+          if (count($subject) == 0) {
+            $errors[$this->field['field_name']][$langcode][$delta][] = array(
+              'error' => 'chado_linker__relationship',
+              'message' => t("The subject record cannot be found. Please check spelling."),
+            );
+          }
+          elseif (count($subject) > 1) {
+            $errors[$this->field['field_name']][$langcode][$delta][] = array(
+              'error' => 'chado_linker__relationship',
+              'message' => t("The subject is not unique and therefore the relationship cannot be made."),
+            );
+          }
+        }
+      }
+    
+      // Now check for a matching object.
+      $object_id = '';
+      $fkey_rcolumn = $fkeys[$base_table]['columns'][$object_id_key];
+      $matches = array();
+      if ($entity) {
+        if (preg_match('/\[id: (\d+)\]/', $object_name, $matches)) {
+          $object_id = $matches[1];
+          $values = array($fkey_rcolumn => $object_id);
+          $object = chado_select_record($base_table, array($fkey_rcolumn), $values);
+          if (count($subject) == 0) {
+            $errors[$this->field['field_name']][$langcode][$delta][] = array(
+              'error' => 'chado_linker__relationship',
+              'message' => t("The object record cannot be found using the specified id (e.g. [id: xx])."),
+            );
+          }
+        }
+        else {
+          $values = array('uniquename' => $object_name);
+          $object = chado_select_record($base_table, array($fkey_rcolumn), $values);
+          if (count($object) == 0) {
+            $errors[$this->field['field_name']][$langcode][$delta][] = array(
+              'error' => 'chado_linker__relationship',
+              'message' => t("The object record cannot be found. Please check spelling."),
+            );;
+          }
+          elseif (count($object) > 1) {
+            $errors[$this->field['field_name']][$langcode][$delta][] = array(
+              'error' => 'chado_linker__relationship',
+              'message' =>  t("The object is not unique and therefore the relationship cannot be made."),
+            );
+          }
+        }
+      }
+    
+      // Make sure that either our object or our subject refers to the base record.
+      if ($entity) {
+        $chado_record_id = $entity->chado_record_id;
+        if ($object_id != $chado_record_id  and $subject_id != $chado_record_id) {
+          $errors[$this->field['field_name']][$langcode][$delta][] = array(
+            'error' => 'chado_linker__relationship',
+            'message' =>  t("Either the subject or the object in the relationship must refer to this record."),
+          );
+        }
+    
+        // Make sure that the object and subject are not both the same thing.
+        if ($object_id == $subject_id) {
+          $errors[$this->field['field_name']][$langcode][$delta][] = array(
+            'error' => 'chado_linker__relationship',
+            'message' =>  t("The subject and the object in the relationship cannot both refer to the same record."),
+          );
+        }
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @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->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['settings']['base_table'];
+    $chado_record_id = $entity->chado_record_id;
+    
+    $schema = chado_get_schema($field_table);
+    $fkeys = $schema['foreign keys'];
+    
+    $type_name = array_key_exists('type_name', $item) ? $item['type_name'] : '';
+    $subject_id = $form_state['values'][$field_name][$langcode][$delta][$field_table . '__subject_id'];
+    $object_id = $form_state['values'][$field_name][$langcode][$delta][ $field_table . '__object_id'];
+    $type_id = $form_state['values'][$field_name][$langcode][$delta][$field_table . '__type_id'];
+    
+    $subject_name = $form_state['values'][$field_name][$langcode][$delta]['subject_name'];
+    $object_name = $form_state['values'][$field_name][$langcode][$delta]['object_name'];
+    
+    // If the row is empty then skip this one, there's nothing to validate.
+    if (!($type_id or !$type_name) and !$subject_name and !$object_name) {
+      return;
+    }
+    
+    // Get the subject ID.
+    $subject_id = '';
+    $fkey_rcolumn = $fkeys[$base_table]['columns']['subject_id'];
+    $matches = array();
+    if (preg_match('/\[id: (\d+)\]/', $subject_name, $matches)) {
+      $subject_id =  $matches[1];
+    }
+    else {
+      $values = array('uniquename' => $subject_name);
+      $subject = chado_select_record($base_table, array($fkey_rcolumn), $values);
+      $subject_id = $subject[0]->$fkey_rcolumn;
+    }
+    
+    // Get the object ID.
+    $object_id = '';
+    $fkey_rcolumn = $fkeys[$base_table]['columns']['object_id'];
+    $matches = array();
+    if (preg_match('/\[id: (\d+)\]/', $object_name, $matches)) {
+      $object_id = $matches[1];
+    }
+    else {
+      $values = array('uniquename' => $object_name);
+      $object = chado_select_record($base_table, array($fkey_rcolumn), $values);
+      $object_id = $object[0]->$fkey_rcolumn;
+    }
+    
+    // Set the IDs according to the values that were determined above.
+    $form_state['values'][$field_name][$langcode][$delta][$field_table . '__subject_id'] = $subject_id;
+    $form_state['values'][$field_name][$langcode][$delta][$field_table . '__object_id'] = $object_id;
+    $form_state['values'][$field_name][$langcode][$delta][$field_table . '__type_id'] = $type_name;
+    $form_state['values'][$field_name][$langcode][$delta][$field_table . '__rank'] = $item['_weight'];
+  }
+}
+
+
+/**
+ * Theme function for the chado_linker__relationship_widget.
+ */
+function theme_chado_linker__relationship_widget($variables) {
+  $element = $variables['element'];
+  $field_name = $element['#field_name'];
+  $field = field_info_field($field_name);
+  $field_type = $field['type'];
+  $field_table = $field['settings']['chado_table'];
+  $field_column = $field['settings']['chado_column'];
+  $layout = "
+      <div class=\"chado-linker--relationship-widget\">
+        <div class=\"chado-linker--relationship-widget-item\">" .
+        drupal_render($element['subject_name']) . "
+        </div>
+        <div class=\"chado-linker--relationship-widget-item\">" .
+        drupal_render($element['vocabulary']) . "
+        </div>
+        <div class=\"chado-linker--relationship-widget-item\">" .
+        drupal_render($element['type_name']) . "
+        </div>
+        <div class=\"chado-linker--relationship-widget-item\">" .
+        drupal_render($element['type_id']) . "
+        </div>
+        <div class=\"chado-linker--relationship-widget-item\">" .
+        drupal_render($element['object_name']) . "
+        </div>
+      </div>
+    ";
+        return $layout;
+}
+
+function theme_chado_linker__relationship_instance_settings ($variables) {
+  $element = $variables['element'];
+  $option1 = $element['option1'];
+  $option1_vocabs = $element['option1_vocabs'];
+  $option2 = $element['option2'];
+  $option2_vocab = $element['option2_vocab'];
+  $option2_parent = $element['option2_parent'];
+  $option3 = $element['option3'];
+  $rtype = $element['relationship_types'];
+  $layout = drupal_render($option1);
+  $layout .= drupal_render($option1_vocabs);
+  $layout .=
+  drupal_render($option2) .
+  "<div class=\"chado-linker--relationship-instance-settings-option2\">" .
+  "<div class=\"chado-linker--relationship-instance-settings-option2-item\">" .
+  drupal_render($option2_vocab) .
+  "</div>" .
+  "<div class=\"chado-linker--relationship-instance-settings-option2-item\">" .
+  drupal_render($option2_parent) .
+  "</div>" .
+  "</div>";
+  $layout .= drupal_render($option3);
+  $layout .= drupal_render($rtype);
+  return $layout;
+}
+
+/**
+ * An Ajax callback for the relationshp widget.
+ */
+function chado_linker__relationship_widget_form_ajax_callback(&$form, $form_state) {
+
+  // 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];
+  }
+}
+
+/**
+ * An Ajax callback for the relationshp instance setting form.
+ */
+function chado_linker__relationship_instance_settings_form_ajax_callback(&$form, &$form_state) {
+  $acpath  = $form['instance']['settings']['relationships']['option2_parent']['#autocomplete_path'];
+  $acpath .=  $form_state['values']['instance']['settings']['relationships']['option2_vocab'] . '/';
+  $urlval  = $form['instance']['settings']['relationships']['option2_parent']['#autocomplete_input']['#url_value'];
+  $urlval .=  $form_state['values']['instance']['settings']['relationships']['option2_vocab'];
+  // Reset value if a different vocabulary is selected
+  $form['instance']['settings']['relationships']['option2_parent']['#value'] = NULL;
+  $form_state['values']['instance']['settings']['relationships']['option2_parent'] = NULL;
+  $form['instance']['settings']['relationships']['option2_parent']['#autocomplete_path'] = $acpath;
+  $form['instance']['settings']['relationships']['option2_parent']['#autocomplete_input']['#url_value'] = $urlval;
+  return $form['instance']['settings']['relationships']['option2_parent'];
+}

+ 168 - 0
tripal_chado/includes/TripalFields/chado_linker__synonym.inc

@@ -0,0 +1,168 @@
+<?php
+
+class chado_linker__synonym extends TripalField {
+
+
+  // --------------------------------------------------------------------------
+  //                     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 term that this field maps to.  The format for the term should be:
+  // [vocab]:[accession] where [vocab] is the short name of the vocabulary
+  // and [acession] is the unique accession number for the term.  This term
+  // must already exist in the vocabulary storage backend. This
+  // value should never be changed once fields exist for this type.
+  public static $term = 'schema:alternateName';
+
+  // The default lable for this field.
+  public static $label = 'Synonyms';
+
+  // The default description for this field.
+  public static $description = 'Adds an alternative name (synonym or alias) to this record.';
+
+  // Provide a list of global settings. These can be accessed witihn the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  public static $settings = array(
+    'chado_table' => '',
+    'chado_column' => '',
+    'base_table' => '',
+  );
+
+  // 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.
+  public static $instance_settings  = array();
+
+  // Set this to the name of the storage backend that by default will support
+  // this field.
+  public static $storage = 'tripal_no_storage';
+
+  // The default widget for this field.
+  public static $default_widget = 'chado_linker__synonym_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'chado_linker__synonym_formatter';
+
+  // --------------------------------------------------------------------------
+  //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
+  // --------------------------------------------------------------------------
+  // An array containing details about the field. The format of this array
+  // is the same as that returned by field_info_fields()
+  protected $field;
+  // An array containing details about an instance of the field. A field does
+  // not have to have an instance.  But if dealing with an instance (such as
+  // when using the widgetForm, formatterSettingsForm, etc.) it should be set.
+  protected $instance;
+
+
+  /**
+   *
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $field, $items, &$errors) {
+
+  }
+
+
+  /**
+   *
+   * @see TripalField::load()
+   */
+  public function load($entity, $details = array()) {
+    $record = $details['record'];
+    $base_table = $this->field['settings']['base_table'];
+    
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    
+    // 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];
+    
+    // Set some defaults for the empty record.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      'chado-' . $field_table . '__' . $pkey => '',
+      'chado-' . $field_table . '__' . $fkey_lcolumn => '',
+      'chado-' . $field_table . '__' . 'synonym_id' => '',
+      'chado-' . $field_table . '__' . 'pub_id' => '',
+      'chado-' . $field_table . '__' . 'is_current' => TRUE,
+      'chado-' . $field_table . '__' . 'is_internal' => '',
+      'name' => '',
+      'type_id' => '',
+      // Ignore the synonym_sgml column for now.
+    );
+    
+    $linker_table = $base_table . '_synonym';
+    $options = array('return_array' => 1);
+    $record = chado_expand_var($record, 'table', $linker_table, $options);
+    if (count($record->$linker_table) > 0) {
+      $i = 0;
+      foreach ($record->$linker_table as $index => $linker) {
+        $synonym = $linker->synonym_id;
+        $entity->{$field_name}['und'][$i] = array(
+          'value' => array(
+            '@type' => $synonym->type_id->dbxref_id->db_id->name . ':' . $synonym->type_id->dbxref_id->accession,
+            'type' => $synonym->type_id->name,
+            'name' => $synonym->name,
+          ),
+          'chado-' . $field_table . '__' . $pkey => $linker->$pkey,
+          'chado-' . $field_table . '__' . $fkey_lcolumn => $linker->$fkey_lcolumn->$fkey_lcolumn,
+          'chado-' . $field_table . '__' . 'synonym_id' => $synonym->synonym_id,
+          'chado-' . $field_table . '__' . 'pub_id' => $linker->pub_id->pub_id,
+          'chado-' . $field_table . '__' . 'is_current' => $linker->is_current,
+          'chado-' . $field_table . '__' . 'is_internal' => $linker->is_internal,
+          'name' => $synonym->name,
+          'type_id' => $synonym->type_id->cvterm_id,
+        );
+        $i++;
+      }
+    }
+  }
+
+
+  /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function settingsForm($has_data) {
+
+  }
+  /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function settingsFormValidate($form, &$form_state) {
+
+  }
+
+  /**
+   *
+   * @see TripalField::instanceSettingsForm()
+   */
+  public function instanceSettingsForm() {
+
+  }
+  /**
+   *
+   * @see TripalField::instanceSettingsFormValidate()
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+
+  }
+
+}

+ 41 - 0
tripal_chado/includes/TripalFields/chado_linker__synonym_formatter.inc

@@ -0,0 +1,41 @@
+<?php
+
+class chado_linker__synonym_formatter extends TripalFieldFormatter {
+  // The default lable for this field.
+  public static $label = 'Synonyms';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__synonym');
+
+  // The list of default settings for this formatter.
+  public static $settings = array();
+  /**
+   *
+   * @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->field['settings']['chado_table'];
+    foreach ($items as $delta => $item) {
+      if (array_key_exists('chado-' . $chado_table . '__synonym_id', $item) and
+          $item['chado-' . $chado_table . '__synonym_id']) {
+            $synonym = chado_generate_var('synonym', array('synonym_id' => $item['chado-' . $chado_table . '__synonym_id']));
+            $name = $synonym->name;
+            if ($synonym->type_id->name != 'exact') {
+              $name .= ' (<i>' . $synonym->type_id->name . '</i>)';
+            }
+            $element[$delta] = array(
+              '#type' => 'markup',
+              '#markup' => $name,
+            );
+          }
+    }
+  }
+}

+ 249 - 0
tripal_chado/includes/TripalFields/chado_linker__synonym_widget.inc

@@ -0,0 +1,249 @@
+<?php
+
+class chado_linker__synonym_widget extends TripalFieldWidget {
+  // The default lable for this field.
+  public static $label = 'Synonyms';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('chado_linker__synonym');
+
+  /**
+   *
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+    $entity = $form['#entity'];
+    $field_name = $this->field['field_name'];
+    
+    // Get the FK column that links to the base table.
+    $table_name = $this->field['settings']['chado_table'];
+    $base_table = $this->field['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 defaults.
+    $record_id = '';
+    $fkey_value = $element['#entity']->chado_record_id;
+    $synonym_id = '';
+    $pub_id = '';
+    $is_current = TRUE;
+    $is_internal = FALSE;
+    $syn_name = '';
+    $syn_type = '';
+    
+    // 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]['chado-' . $table_name . '__' . $pkey];
+      $synonym_id = $items[$delta]['chado-' . $table_name . '__synonym_id'];
+      $pub_id = $items[$delta]['chado-' . $table_name . '__pub_id'];
+      $is_current = $items[$delta]['chado-' . $table_name . '__is_current'];
+      $is_internal = $items[$delta]['chado-' . $table_name . '__is_internal'];
+      $syn_name = $items[$delta]['name'];
+      $syn_type = $items[$delta]['type_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-' . $table_name . '__' . $pkey];
+      $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'];
+      $is_internal = $form_state['values'][$field_name]['und'][$delta]['chado-' . $table_name . '__is_internal'];
+      $syn_name = $form_state['values'][$field_name]['und'][$delta]['name'];
+      $syn_type = $form_state['values'][$field_name]['und'][$delta]['type_id'];
+    }
+    
+    $options = array();
+    $value = array('cv_id' => array('name' => 'synonym_type'));
+    $op = array('return_array' => 1);
+    $types = chado_generate_var('cvterm', $value, $op);
+    if ($types) {
+      foreach($types AS $type) {
+        $options[$type->cvterm_id] = $type->name;
+      }
+    }
+    
+    // Get the schema for the synonym table so we can make sure we limit the
+    // size of the name field to the proper size.
+    $schema = chado_get_schema('synonym');
+    
+    $widget['#table_name'] = $table_name;
+    $widget['#fkey_field'] = $fkey;
+    $widget['#theme'] = 'chado_linker__synonym_widget';
+    $widget['#prefix'] =  "<span id='$table_name-$delta'>";
+    $widget['#suffix'] =  "</span>";
+    
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+    
+    $widget['chado-' . $table_name . '__' . $pkey] = array(
+      '#type' => 'value',
+      '#default_value' => $record_id,
+    );
+    $widget['chado-' . $table_name . '__synonym_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $synonym_id,
+    );
+    $widget['chado-' . $table_name . '__' . $fkey] = array(
+      '#type' => 'value',
+      '#default_value' => $fkey_value,
+    );
+    // TODO: add a widget for selecting a publication.
+    $widget['chado-' . $table_name . '__pub_id'] = array(
+      '#type' => 'value',
+      '#default_value' => $pub_id,
+    );
+    $widget['type_id'] = array(
+      '#type' => 'select',
+      '#title' => t('Type'),
+      '#options' => $options,
+      '#default_value' => $syn_type,
+    );
+    $widget['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Synonym Name'),
+      '#default_value' => $syn_name,
+      '#size' => 25,
+    );
+    
+    $widget['chado-' . $table_name . '__is_current'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Is Current'),
+      '#default_value' => $is_current,
+      '#required' => $element['#required'],
+    );
+    
+    $widget['chado-' . $table_name . '__is_internal'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Is Internal'),
+      '#default_value' => $is_internal,
+      '#required' => $element['#required'],
+    );
+  }
+
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.  Note
+   * this is different from the validate() function which ensures that the
+   * field data meets expectations.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function validate($form, &$form_state, $entity_type, $entity, $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->field['settings']['chado_table'];
+    $field_table = $this->field['settings']['chado_table'];
+    $field_column = $this->field['settings']['chado_column'];
+    $base_table = $this->field['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];
+    
+    $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'] : '';
+    
+    // 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) {
+    
+      // Get the synonym. If one with the same name and type is already present
+      // then use that. Otherwise, insert a new one.
+      if (!$synonym_id) {
+        $synonym = chado_generate_var('synonym', array('name' => $syn_name, 'type_id' => $syn_type));
+        if (!$synonym) {
+          $synonym = chado_insert_record('synonym', array(
+            'name' => $syn_name,
+            'type_id' => $syn_type,
+            'synonym_sgml' => '',
+          ));
+          $synonym = (object) $synonym;
+        }
+    
+        // 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'] = '';
+    }
+  }
+}
+
+
+/**
+ * Theme function for the synonym widget.
+ *
+ * @param $variables
+ */
+function theme_chado_linker__synonym_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=\"synonym-widget\">
+        <div class=\"synonym-widget-item\">" .
+        drupal_render($element['name']) . "
+        </div>
+        <div>" .
+        drupal_render($element['type_id']) . "
+        </div>
+        <div class=\"synonym-widget-item\">" .
+        drupal_render($element['chado-' . $table_name . '__is_internal']) . "
+        </div>
+        <div>" .
+        drupal_render($element['chado-' . $table_name . '__is_current']) . "
+        </div>
+      </div>
+    ";
+
+        return $layout;
+}
+
+/**
+ * An Ajax callback for the synonym widget.
+ */
+function chado_linker__synonym_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];
+}

+ 15 - 15
tripal_chado/includes/tripal_chado.fields.inc

@@ -57,7 +57,7 @@ function tripal_chado_bundle_create_fields($entity_type, $bundle) {
   tripal_chado_bundle_create_fields_custom($info, $details, $entity_type, $bundle);
 
   // Create fields for linking tables.
-  //tripal_chado_bundle_create_fields_linker($info, $details, $entity_type, $bundle);
+  tripal_chado_bundle_create_fields_linker($info, $details, $entity_type, $bundle);
 
   foreach ($info as $field_name => $details) {
     $field_type = $details['type'];
@@ -383,16 +383,16 @@ function tripal_chado_bundle_create_fields_linker(&$info, $details, $entity_type
     $info[$field_name] = array(
       'field_name' => $field_name,
       'type' => $field_type,
-      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+      'cardinality' => 1,
       'locked' => FALSE,
       'storage' => array(
         'type' => 'field_chado_storage',
       ),
       'settings' => array(
         'chado_table' => $contact_table,
-        'chado_column' => 'contact_id',
         'base_table' => $table_name,
-        'semantic_web' => 'local:contact'
+        'chado_column' => 'contact_id',
+        'semantic_web' => tripal_get_chado_semweb_term($table_name, 'contact_id'),
       ),
     );
   }
@@ -439,6 +439,7 @@ function tripal_chado_bundle_create_fields_linker(&$info, $details, $entity_type
       ),
     );
   }
+
   // EXPRESSION
   $expression_table = $table_name . '_expression';
   if (chado_table_exists($expression_table)) {
@@ -508,6 +509,7 @@ function tripal_chado_bundle_create_fields_linker(&$info, $details, $entity_type
       ),
     );
   }
+
   // GENOTYPE
   $genotype_table = $table_name . '_genotype';
   if (chado_table_exists($genotype_table)) {
@@ -676,7 +678,7 @@ function tripal_chado_bundle_create_instances($entity_type, $bundle) {
 
   tripal_chado_bundle_create_instances_base($info, $entity_type, $bundle, $details);
   tripal_chado_bundle_create_instances_custom($info, $entity_type, $bundle, $details);
-  //tripal_chado_bundle_create_instances_linker($info, $entity_type, $bundle, $details);
+  tripal_chado_bundle_create_instances_linker($info, $entity_type, $bundle, $details);
 
   foreach ($info as $field_name => $details) {
     // If the field is already attached to this bundle then skip it.
@@ -1121,34 +1123,32 @@ function tripal_chado_bundle_create_instances_linker(&$info, $entity_type, $bund
   $contact_table = $table_name . '_contact';
   if (chado_table_exists($contact_table)) {
     $field_name = $table_name . '_contact';
-    $info[$field_name] = array(
+    $info[$field_name] =     $info[$field_name] = array(
       'field_name' => $field_name,
       'entity_type' => $entity_type,
       'bundle' => $bundle->name,
-      'label' => 'Contacts',
-      'description' => 'An individual, organization or entity that has had
-        some responsibility for the creation, delivery or maintenance of
-        the associated data.',
+      'label' => 'Contact',
+      'description' => 'Associates an indviddual or organization with this record',
       'required' => FALSE,
       'settings' => array(
-        'auto_attach' => FALSE,
+        'auto_attach' => TRUE,
       ),
       'widget' => array(
-        'type' => 'chado_linker__contact_widget',
+        'type' => 'local__contact_widget',
         'settings' => array(
           'display_label' => 1,
         ),
       ),
       'display' => array(
         'default' => array(
-          'label' => 'above',
-          'type' => 'chado_linker__contact_formatter',
+          'label' => 'inline',
+          'type' => 'local__contact_formatter',
           'settings' => array(),
         ),
       ),
     );
   }
-
+  
   // CVTERM
   $cvterm_table = $table_name . '_cvterm';
   if (chado_table_exists($cvterm_table)) {