Browse Source

Merge branch '7.x-2.x' of git.drupal.org:sandbox/spficklin/1337878 into 7.x-2.x

Stephen Ficklin 11 years ago
parent
commit
eb1989add1

+ 634 - 0
tripal_core/api/tripal_core.relationships.api.inc

@@ -0,0 +1,634 @@
+<?php
+
+/**
+ * @file
+ * API to manage the chado _relationship table for various Tripal Node Types
+ *
+ * How To Use:
+ * @code
+
+  function chado_example_form($form, $form_state) {
+
+    // Default values for form elements can come in the following ways:
+    //
+    // 1) as elements of the $node object.  This occurs when editing an existing node
+    // 2) in the $form_state['values'] array which occurs on a failed validation or
+    //    ajax callbacks when the ajax call originates from non-submit fields other
+    //    than button
+    // 3) in the $form_state['input'] array which occurs on ajax callbacks from submit
+    //    form elements (e.g. buttons) and the form is being rebuilt but has not yet
+    //    been validated
+    //
+    // The reference elements added by this function do use AJAX calls from buttons,
+    // therefore, it is important to check for form values in the $form_state['values']
+    // for case #2 above, and in the $form_state['input'] for case #3.
+    // See the chado analysis node form for an example.
+
+
+    // Next, add in all the form array definition particular to your node type
+
+    // To add in the relationship form elements, you first need to prepare the arguments
+    // for the function call.
+
+    $details = array(
+      'relationship_table' => 'example_relationship',    // the name of the table linking additional dbxrefs to this node
+      'base_table' => 'example',                         // the name of the chado table this node links to
+      'base_foreign_key' => 'example_id',                // key to link to the chado content created by this node
+      'base_key_value' => $example_id,                   // the value of the above key
+      'fieldset_title' => 'Relationships',               // the non-translated title for this fieldset
+      'additional_instructions' => ''                    // a non-stranslated string providing additional instructions
+    );
+
+    // Finally, and add the additional form elements to the form
+    tripal_core_relationships_form($form, $form_state, $details);
+
+    return $form;
+  }
+
+  function chado_example_insert($node) {
+
+    // if there is an example_id in the $node object then this must be a sync so
+    // we can skip adding the chado_example as it is already there, although
+    // we do need to proceed with the rest of the insert
+    if (!property_exists($node, 'example_id')) {
+
+      // Add record to chado example table
+
+      // Add to any other tables needed
+
+      // Add all relationships
+      // Existing _relationship links with the current example as either the subject_id
+      // or object_id will be cleared and then re-added
+      tripal_core_relationships_form_update_relationships(
+        $node,
+        'example_relationship',
+        $node->example_id
+      );
+    }
+
+    // Add record to chado_example linking example_id to new node
+
+  }
+
+  function chado_example_update($node) {
+
+
+      // Update record in chado example table
+
+      // Update any other tables needed
+
+      // Update all additional database references
+      // Existing _relationship links with the current example as either the subject_id
+      // or object_id will be cleared and then re-added
+      tripal_core_relationships_form_update_relationships(
+        $node,
+        'example_relationship',
+        $node->example_id
+      );
+
+    // Don't need to update chado_example linking table since niether example_id or nid can be changed in update
+
+  }
+
+ * @endcode
+ */
+
+/**
+ * Provides a form for adding to BASE_relationship and relationship tables
+ *
+ * @param $form
+ *   The Drupal form array into which the relationship elements will be added
+ * @param $form_state
+ *   The corresponding form_state array for the form
+ * @param $details
+ *   An array defining details needed by this form. Required Keys are:
+ *     - relationship_table: the name of the relationship table (ie: feature_relationship)
+ *     - base_table: the name of the base table (ie: feature)
+ *     - base_foreign_key: the name of the foreign key linking this table to the non-relationship table (ie: feature_id)
+ *     - base_key_value: the value of the base_foreign_key for the current form (ie: 999 if the feature_id=999)
+ *     - nodetype: the non-translated singular title of this node type
+ *   One of the following:
+ *     - cv_id: the id of the ontology to supply terms for the type dropdown
+ *     - cv_name: the name of the ontology to supply terms for the type dropdown
+ *   Optional keys include:
+ *     - fieldset_title: the non-translated title for this fieldset
+ *     - additional_instructions: a non-translated string providing additional instructions
+ *     - nodetype_plural: the non-translated plural title of this node type
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form(&$form, &$form_state, $details) {
+
+  $form_state['rebuild'] = TRUE;
+
+  // Set Defaults for optional fields
+  $details['fieldset_title'] = (isset($details['fieldset_title'])) ? $details['fieldset_title'] : 'Relationships';
+  $details['additional_instructions'] = (isset($details['additional_instructions'])) ? $details['additional_instructions'] : '';
+  $details['nodetype_plural']  = (isset($details['nodetype_plural'])) ? $details['nodetype_plural'] : $details['nodetype'] . 's';
+
+  // Add defaults into form_state to be used elsewhere
+  $form['rel_details'] = array(
+    '#type' => 'hidden',
+    '#value' => serialize($details)
+  );
+
+  // Get relationship type options
+  if (isset($details['cv_id'])) {
+    $query = "SELECT cvterm_id, name FROM {cvterm} cvterm WHERE cv_id = :cv_id";
+    $result = chado_query($query, array(':cv_id' => $details['cv_id']));
+  } elseif (isset($details['cv_name'])) {
+    $query = "SELECT cvterm_id, name FROM {cvterm} cvterm WHERE cv_id IN (SELECT cv_id FROM cv WHERE name = :cv_name)";
+    $result = chado_query($query, array(':cv_name' => $details['cv_name']));
+  }
+  $type_options = array(0 => 'Select a Type');
+  foreach ($result as $cvterm) {
+    $type_options[ $cvterm->cvterm_id ] = $cvterm->name;
+  }
+
+  $form['relationships'] = array(
+    '#type' => 'fieldset',
+    '#title' => t($details['fieldset_title']),
+    '#description' => t('You may add relationships between this %nodetype and other
+      %nodetype_plural by entering the details below.  You may add
+      as many relationships as desired by clicking the add button on the right.  To
+      remove a relationship, click the remove button. ' . $details['additional_instructions'],
+      array('%nodetype' => $details['nodetype'], '%nodetype_plural' => $details['nodetype_plural'])),
+    '#prefix' => "<div id='relationships-fieldset'>",
+    '#suffix' => '</div>'
+  );
+
+  $form['relationships']['rel_table'] = array(
+    '#type' => 'markup',
+    '#tree' => TRUE,
+    '#prefix' => '<div id="tripal-generic-edit-relationships-table">',
+    '#suffix' => '</div>',
+    '#theme' => 'tripal_core_relationships_form_table'
+  );
+
+  // Add relationships already attached to the node
+  //---------------------------------------------
+  if (isset($form_state['chado_relationships'])) {
+    $existing_rels = $form_state['chado_relationships'];
+  }
+  else {
+    $existing_rels = chado_query(
+      "SELECT rel.*, base1.uniquename as object_name, base2.uniquename as subject_name, cvterm.name as type_name
+        FROM {".$details['relationship_table']."} rel
+        LEFT JOIN {".$details['base_table']."} base1 ON base1.".$details['base_foreign_key']." = rel.object_id
+        LEFT JOIN {".$details['base_table']."} base2 ON base2.".$details['base_foreign_key']." = rel.subject_id
+        LEFT JOIN {cvterm} cvterm ON cvterm.cvterm_id = rel.type_id
+        WHERE rel.object_id = :base_key_value OR rel.subject_id = :base_key_value",
+        array(':base_key_value' => $details['base_key_value'])
+    );
+  }
+  foreach ($existing_rels as $relationship) {
+
+    $form['relationships']['rel_table'][$relationship->type_id]['#type'] = 'markup';
+    $form['relationships']['rel_table'][$relationship->type_id]['#type'] = '';
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['#type'] = 'markup';
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['#value'] = '';
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['object_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $relationship->object_id
+    );
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['subject_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $relationship->subject_id
+    );
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['type_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $relationship->type_id
+    );
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['object_name'] = array(
+      '#type' => 'markup',
+      '#markup' => $relationship->object_name
+    );
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['type_name'] = array(
+      '#type' => 'markup',
+      '#markup' => $relationship->type_name
+    );
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['subject_name'] = array(
+      '#type' => 'markup',
+      '#markup' => $relationship->subject_name
+    );
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['rank'] = array(
+      '#type' => 'markup',
+      '#markup' => $relationship->rank
+    );
+
+    $form['relationships']['rel_table'][$relationship->type_id][$relationship->rank]['rel_action'] = array(
+      '#type' => 'submit',
+      '#value' => t('Remove'),
+      '#name' => "rel_remove-".$relationship->type_id.'-'.$relationship->rank,
+      '#ajax' => array(
+        'callback' => 'tripal_core_relationships_form_ajax_update',
+        'wrapper' => 'tripal-generic-edit-relationships-table',
+        'effect'   => 'fade',
+        'method'   => 'replace',
+        'prevent'  => 'click'
+      ),
+      '#validate' => array('tripal_core_relationships_form_remove_button_validate'),
+      '#submit' => array('tripal_core_relationships_form_remove_button_submit'),
+    );
+  }
+
+  $form['relationships']['rel_table']['new']['object_name'] = array(
+    '#type' => 'textfield',
+  );
+
+  $form['relationships']['rel_table']['new']['object_is_current'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Current '.$details['nodetype']),
+  );
+
+  $form['relationships']['rel_table']['new']['type_name'] = array(
+    '#type' => 'select',
+    '#options' => $type_options,
+  );
+
+  $form['relationships']['rel_table']['new']['subject_name'] = array(
+    '#type' => 'textfield',
+  );
+
+  $form['relationships']['rel_table']['new']['subject_is_current'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Current '.$details['nodetype']),
+  );
+
+  $form['relationships']['rel_table']['new']['rank'] = array(
+    '#type' => 'markup',
+    '#markup' => ''
+  );
+
+  $form['relationships']['rel_table']['new']['rel_action'] = array(
+    '#type' => 'submit',
+    '#value' => t('Add'),
+    '#name' => 'rel_add',
+    '#ajax' => array(
+      'callback' => 'tripal_core_relationships_form_ajax_update',
+      'wrapper' => 'tripal-generic-edit-relationships-table',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+      'prevent'  => 'click'
+    ),
+    '#validate' => array('tripal_core_relationships_form_add_button_validate'),
+    '#submit' => array('tripal_core_relationships_form_add_button_submit'),
+  );
+
+}
+
+/**
+ * Validate the user input for creating a new relationship
+ * Called by the add button in tripal_core_relationships_form
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_add_button_validate($form, &$form_state) {
+
+  $details = unserialize($form_state['values']['rel_details']);
+
+  // At least one of the participants must be the current node
+  if (!($form_state['values']['rel_table']['new']['subject_is_current'] OR $form_state['values']['rel_table']['new']['object_is_current'])) {
+    // If the checkbox isn't set then check to see if either has the same uniquename as the node
+    if ($form_state['values']['rel_table']['new']['subject_name'] == $form_state['values']['uniquename']) {
+      $form_state['values']['rel_table']['new']['subject_is_current'] = 1;
+      form_set_error('subject_is_current', 'To set the current '.$details['nodetype'].', select the
+        checkbox. You entered the unique name of the current '.$details['nodetype'].' as the subject,
+        is this what you meant to do?');
+    }
+    elseif ($form_state['values']['rel_table']['new']['subject_name'] == $form_state['values']['uniquename']) {
+      $form_state['values']['rel_table']['new']['object_is_current'] = 1;
+      form_set_error('subject_is_current', 'To set the current '.$details['nodetype'].', select the
+        checkbox. You entered the unique name of the current '.$details['nodetype'].' as the subject,
+        is this what you meant to do?');
+    }
+    else {
+      form_set_error('object_is_current', 'At least one member of the relationship must be
+        the current '.$details['nodetype'].'. This is specified by checking the "Current '.$details['nodetype'].'"
+        checkbox for either the subject or object.');
+    }
+  }
+
+  // The non-current uniquename must be exist in the base table (subject)
+  if (!($form_state['values']['rel_table']['new']['subject_is_current'])) {
+    $result = tripal_core_chado_select(
+      $details['base_table'],
+      array($details['base_foreign_key']),
+      array('uniquename' => $form_state['values']['rel_table']['new']['subject_name'])
+    );
+    if (!isset($result[0])) {
+      form_set_error('subject_name', 'The subject must be the unique name of an
+        existing '.$details['nodetype'].' unless the "Current '.$details['nodetype'].'" checkbox is selected');
+    }
+    else {
+      $form_state['values']['rel_table']['new']['subject_id'] = $result[0]->{$details['base_foreign_key']};
+    }
+  }
+
+  // The non-current uniquename must exist in the base table (object)
+  if (!($form_state['values']['rel_table']['new']['object_is_current'])) {
+    $result = tripal_core_chado_select(
+      $details['base_table'],
+      array($details['base_foreign_key']),
+      array('uniquename' => $form_state['values']['rel_table']['new']['object_name'])
+    );
+    if (!isset($result[0])) {
+      form_set_error('object_name', 'The object must be the unique name of an
+        existing '.$details['nodetype'].' unless the "Current '.$details['nodetype'].'" checkbox is selected');
+    }
+    else {
+      $form_state['values']['rel_table']['new']['object_id'] = $result[0]->{$details['base_foreign_key']};
+    }
+  }
+
+  // The type must be a valid cvterm
+  if ($form_state['values']['rel_table']['new']['type_name']) {
+    $form_state['values']['rel_table']['new']['type_id'] = $form_state['values']['rel_table']['new']['type_name'];
+    $result = tripal_core_chado_select(
+      'cvterm',
+      array('name'),
+      array('cvterm_id' => $form_state['values']['rel_table']['new']['type_id'])
+    );
+    if (!isset($result[0])) {
+      form_set_error('type_id', 'The select type is not a valid controlled vocabulary term.');
+    }
+    else {
+      $form_state['values']['rel_table']['new']['type_name'] = $result[0]->name;
+    }
+  }
+  else {
+    form_set_error('type_id', 'Please select a type of relationship');
+  }
+}
+
+/**
+ * Called by the add button in tripal_core_relationships_form
+ *
+ * Create an array of additional relationships in the form state. This array will then be
+ * used to rebuild the form in subsequent builds
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_add_button_submit(&$form, &$form_state) {
+
+  $details = unserialize($form_state['values']['rel_details']);
+
+  // if the chado_relationships array is not set then this is the first time modifying the
+  // relationship table. this means we need to include all the relationships from the db
+  if (!isset($form_state['chado_relationships'])) {
+    tripal_core_relationships_form_create_relationship_formstate_array($form, $form_state);
+  }
+
+  // get details for the new relationship
+  if ($form_state['values']['rel_table']['new']['subject_is_current']) {
+
+    $relationship = array(
+      'type_id' => $form_state['values']['rel_table']['new']['type_id'],
+      'object_id' => $form_state['values']['rel_table']['new']['object_id'],
+      'subject_id' => $form_state['values'][ $details['base_foreign_key'] ],
+      'type_name' => $form_state['values']['rel_table']['new']['type_name'],
+      'object_name' => $form_state['values']['rel_table']['new']['object_name'],
+      'subject_name' => $form_state['values']['uniquename'],
+      'rank' => '0'
+    );
+  }
+  else {
+    $relationship = array(
+      'type_id' => $form_state['values']['rel_table']['new']['type_id'],
+      'object_id' => $form_state['values'][ $details['base_foreign_key'] ],
+      'subject_id' => $form_state['values']['rel_table']['new']['subject_id'],
+      'type_name' => $form_state['values']['rel_table']['new']['type_name'],
+      'object_name' => $form_state['values']['uniquename'],
+      'subject_name' => $form_state['values']['rel_table']['new']['subject_name'],
+      'rank' => '0'
+    );
+  }
+
+  // get max rank
+  $rank = tripal_core_get_max_chado_rank(
+    $details['relationship_table'],
+    array(
+      'subject_id' => $relationship['subject_id'],
+      'type_id' => $relationship['type_id'],
+      'object_id' => $relationship['object_id'],
+    )
+  );
+  $relationship['rank'] = strval($rank + 1);
+
+  $key = $relationship['type_id'] . '-' . $relationship['rank'];
+  $form_state['chado_relationships'][$key] = (object) $relationship;
+
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * There is no user input for the remove buttons so there is no need to validate
+ * However, both a submit & validate need to be specified so this is just a placeholder
+ *
+ * Called by the many remove buttons in tripal_core_relationships_form
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_remove_button_validate($form, $form_state) {
+  // No Validation needed for remove
+}
+
+/**
+ * Remove the correct relationship from the form
+ * Called by the many remove buttons in tripal_core_relationships_form
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_remove_button_submit(&$form, &$form_state) {
+
+  // if the chado_relationships array is not set then this is the first time modifying the
+  // relationship table. this means we need to include all the relationships from the db
+  if (!isset($form_state['chado_relationships'])) {
+    tripal_core_relationships_form_create_relationship_formstate_array($form, $form_state);
+  }
+
+  // @TODO: Test that this actually works
+
+  // remove the specified relationship from the form relationship table
+  if(preg_match('/rel_remove-([^-]+-[^-]+)/',$form_state['triggering_element']['#name'],$match)) {
+    $key = $match[1];
+    if (array_key_exists($key, $form_state['chado_relationships'])) {
+      unset($form_state['chado_relationships'][$key]);
+    }
+  }
+
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Ajax function which returns the section of the form to be re-rendered
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_ajax_update($form, $form_state) {
+  return $form['relationships']['rel_table'];
+}
+
+/**
+ * Creates an array in form_state containing the existing relationships. This array is
+ * then modified by the add/remove buttons and used as a source for rebuilding the form.
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_create_relationship_formstate_array($form, &$form_state) {
+
+  $form_state['chado_relationships'] = array();
+
+  foreach (element_children($form['relationships']['rel_table']) as $type_id) {
+    if ($type_id != 'new') {
+      foreach (element_children($form['relationships']['rel_table'][$type_id]) as $rank) {
+          $element = $form['relationships']['rel_table'][$type_id][$rank];
+          $rel = array(
+            'type_id' => $element['type_id']['#value'],
+            'object_id' => $element['object_id']['#value'],
+            'subject_id' => $element['subject_id']['#value'],
+            'type_name' => $element['type_name']['#markup'],
+            'object_name' => $element['object_name']['#markup'],
+            'subject_name' => $element['subject_name']['#markup'],
+            'rank' => $element['rank']['#markup']
+          );
+          $key = $rel['type_id'] . '-' . $rel['rank'];
+          $form_state['chado_relationships'][$key] = (object) $rel;
+      }
+    }
+  }
+}
+
+/**
+ * Function to theme the add/remove relationships form into a table
+ *
+ * @ingroup tripal_relationships_api
+ */
+function theme_tripal_core_relationships_form_table($variables) {
+  $element = $variables['element'];
+
+  $header = array(
+    'object_name' => t('Object Unique Name'),
+    'type_name' => t('Type'),
+    'subject_name' => t('Subject Unique Name'),
+    'rank' => t('Rank'),
+    'rel_action' => t('Action')
+  );
+
+  $rows = array();
+  foreach (element_children($element) as $type_id) {
+    if ($type_id == 'new') {
+      $row = array();
+
+        $row['data'] = array();
+        foreach ($header as $fieldname => $title) {
+          if ($fieldname == 'subject_name') {
+            $row['data'][] = drupal_render($element[$type_id][$fieldname]) . drupal_render($element[$type_id]['subject_is_current']);
+          }
+          elseif ($fieldname == 'object_name') {
+            $row['data'][] = drupal_render($element[$type_id][$fieldname]) . drupal_render($element[$type_id]['object_is_current']);
+          }
+          else {
+            $row['data'][] = drupal_render($element[$type_id][$fieldname]);
+          }
+        }
+        $rows[] = $row;
+    }
+    else {
+      foreach (element_children($element[$type_id]) as $rank) {
+        $row = array();
+
+        $row['data'] = array();
+        foreach ($header as $fieldname => $title) {
+          $row['data'][] = drupal_render($element[$type_id][$rank][$fieldname]);
+        }
+        $rows[] = $row;
+      }
+    }
+  }
+
+  return theme('table', array(
+    'header' => $header,
+    'rows' => $rows
+  ));
+}
+
+/**
+ * This function is used in a hook_insert, hook_update for a node form
+ * when the relationships form has been added to the form.  It retrieves all of the relationships
+ * and returns them in an array of the format:
+ *
+ *   $relationships[<type_id>][<rank>] = array(
+ *         'subject_id' => <subject_id>,
+ *         'object_id'  => <object_id>,
+ *   );
+ *
+ * This array can then be used for inserting or updating relationships manually
+ *
+ * @param $node
+ *
+ * @return
+ *   A relationship array
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_retreive($node) {
+  $rels = array();
+  foreach ($node->rel_table as $type_id => $elements) {
+    if ($type_id != 'new') {
+      foreach ($elements as $rank => $relationships) {
+        $rels[$type_id][$rank]['subject_id'] = $relationships['subject_id'];
+        $rels[$type_id][$rank]['object_id'] = $relationships['object_id'];
+      }
+    }
+  }
+
+  return $rels;
+}
+
+/**
+ * This function is used in hook_insert or hook_update and handles inserting of
+ * relationships between the current nodetype and other memebers of the same nodetype
+ *
+ * @param $node
+ *    The node passed into hook_insert & hook_update
+ * @param $relationship_table
+ *    The name of the _relationship linking table (ie: feature_relationship)
+ * @param $current_id
+ *    The value of the foreign key (ie: 445, if there exists a feature where feature_id=445)
+ *
+ * @ingroup tripal_relationships_api
+ */
+function tripal_core_relationships_form_update_relationships($node, $relationship_table, $current_id) {
+
+  // First remove existing relationships links
+  tripal_core_chado_delete($relationship_table, array('subject_id' => $current_id));
+  tripal_core_chado_delete($relationship_table, array('object_id' => $current_id));
+
+  // Add back in dbxref links and insert dbxrefs as needed
+  $relationships = tripal_core_relationships_form_retreive($node);
+  foreach ($relationships as $type_id => $ranks) {
+    foreach ($ranks as $rank => $element) {
+
+      // add relationship
+      $success_link = tripal_core_chado_insert(
+        $relationship_table,
+        array(
+          'subject_id' => $element['subject_id'],
+          'type_id' => $type_id,
+          'object_id' => $element['object_id'],
+          'rank' => $rank
+        )
+      );
+
+    }
+  }
+}

+ 75 - 27
tripal_core/api/tripal_core_chado.api.inc

@@ -892,12 +892,12 @@ function tripal_core_chado_delete($table, $match, $options = NULL) {
  *     should specify the number of records to return and 'element' is a
  *     unique integer to differentiate between pagers when more than one
  *     appear on a page.  The 'element' should start with zero and increment by
- *     one for each pager.    
+ *     one for each pager.
  *
  * @return
  *  An array of results, FALSE if the query was not executed
  *  correctly, an empty array if no records were matched, or the number of records
- *  in the dataset if $has_record is set. 
+ *  in the dataset if $has_record is set.
  *  If the option 'is_duplicate' is provided and the record is a duplicate it
  *  will return the duplicated record.  If the 'has_record' option is provided
  *  a value of TRUE will be returned if a record exists and FALSE will bee
@@ -1454,7 +1454,7 @@ function tripal_core_chado_get_foreign_key($table_desc, $field, $values, $option
  */
 function tripal_core_generate_chado_var($table, $values, $base_options = array()) {
   $all = new stdClass();
-  
+
   $return_array = 0;
   if (array_key_exists('return_array', $base_options)) {
     $return_array = 1;
@@ -1617,11 +1617,11 @@ function tripal_core_generate_chado_var($table, $values, $base_options = array()
             }
 
             $foreign_object = tripal_core_generate_chado_var($foreign_table, $foreign_values, $options);
-            
+
             // add the foreign record to the current object in a nested manner
             $object->{$foreign_key} = $foreign_object;
             // Flatten expandable_x arrays so only in the bottom object
-            if (property_exists($object->{$foreign_key}, 'expandable_fields') and 
+            if (property_exists($object->{$foreign_key}, 'expandable_fields') and
                 is_array($object->{$foreign_key}->expandable_fields)) {
               $object->expandable_fields = array_merge(
                 $object->expandable_fields,
@@ -1629,7 +1629,7 @@ function tripal_core_generate_chado_var($table, $values, $base_options = array()
               );
               unset($object->{$foreign_key}->expandable_fields);
             }
-            if (property_exists($object->{$foreign_key}, 'expandable_tables') and 
+            if (property_exists($object->{$foreign_key}, 'expandable_tables') and
                 is_array($object->{$foreign_key}->expandable_tables)) {
               $object->expandable_tables = array_merge(
                 $object->expandable_tables,
@@ -1637,7 +1637,7 @@ function tripal_core_generate_chado_var($table, $values, $base_options = array()
               );
               unset($object->{$foreign_key}->expandable_tables);
             }
-            if (property_exists($object->{$foreign_key}, 'expandable_nodes') and 
+            if (property_exists($object->{$foreign_key}, 'expandable_nodes') and
                 is_array($object->{$foreign_key}->expandable_nodes)) {
               $object->expandable_nodes = array_merge(
                 $object->expandable_nodes,
@@ -1830,7 +1830,7 @@ function tripal_core_expand_chado_vars($object, $type, $to_expand, $table_option
           // generate a new object for this table using the FK values in the base table.
           $new_options = $table_options;
           $foreign_object = tripal_core_generate_chado_var($foreign_table, array($left => $object->{$right}), $new_options);
-          
+
           // if the generation of the object was successful, update the base object to include it.
           if ($foreign_object) {
             // in the case where the foreign key relationship exists more
@@ -1842,7 +1842,7 @@ function tripal_core_expand_chado_vars($object, $type, $to_expand, $table_option
               }
               $object->{$foreign_table}->{$left} = $foreign_object;
               $object->expanded = $to_expand;
-              
+
             }
             else {
               if (!property_exists($object, $foreign_table)) {
@@ -1876,8 +1876,8 @@ function tripal_core_expand_chado_vars($object, $type, $to_expand, $table_option
         foreach ((array) $object as $field_name => $field_value) {
           // if we have a nested object ->expand the table in it
           // check to see if the $field_name is a valid chado table, we don't need
-          // to call tripal_core_expand_chado_vars on fields that aren't tables          
-          $check = tripal_core_get_chado_table_schema($field_name);          
+          // to call tripal_core_expand_chado_vars on fields that aren't tables
+          $check = tripal_core_get_chado_table_schema($field_name);
           if ($check) {
             $did_expansion = 1;
             $object->{$field_name} = tripal_core_expand_chado_vars($field_value, 'table', $foreign_table);
@@ -2048,14 +2048,14 @@ function tripal_core_exclude_field_from_feature_by_default() {
  * @ingroup tripal_chado_api
  */
 function chado_pager_query($query, $args, $limit, $element, $count_query = '') {
-  
+
   // get the page and offset for the pager
   $page = isset($_GET['page']) ? $_GET['page'] : '0';
   $offset = $limit * $page;
 
   // Construct a count query if none was given.
   if (!isset($count_query)) {
-    $count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'), 
+    $count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'),
       array('SELECT COUNT(*) FROM ', ''), $query);
   }
 
@@ -2070,9 +2070,9 @@ function chado_pager_query($query, $args, $limit, $element, $count_query = '') {
 
   // set a session variable for storing the total number of records
   $_SESSION['chado_pager'][$element]['total_records'] = $total_records;
-  
+
   pager_default_initialize($total_records, $limit, $element);
-  
+
   $query .= ' LIMIT ' . (int) $limit . ' OFFSET ' . (int) $offset;
   $results = chado_query($query, $args);
   return $results;
@@ -2117,7 +2117,7 @@ function chado_query($sql, $args = array()) {
 
     // the featureloc table has some indexes that use function that call other functions
     // and those calls do not reference a schema, therefore, any tables with featureloc
-    // must automaticaly have the chado schema set as active to find 
+    // must automaticaly have the chado schema set as active to find
     if(preg_match('/chado.featureloc/i', $sql)) {
       $previous_db = tripal_db_set_active('chado') ;
       $results = db_query($sql, $args);
@@ -2274,7 +2274,7 @@ function tripal_core_schema_exists($schema) {
  * @ingroup tripal_core_api
  */
 function tripal_core_get_chado_tables($include_custom = NULL) {
- 
+
   // first get the chado version that is installed
   $v = $GLOBALS["chado_version"];
 
@@ -2309,7 +2309,7 @@ function tripal_core_get_chado_tables($include_custom = NULL) {
  * Returns the version number of the currently installed Chado instance.
  * It can return the real or effective version.  Note, this function
  * is executed in the hook_init() of the tripal_core module which then
- * sets the $GLOBAL['exact_chado_version'] and $GLOBAL['chado_version'] 
+ * sets the $GLOBAL['exact_chado_version'] and $GLOBAL['chado_version']
  * variable.  You can access these variables rather than calling this function.
  *
  * @param $exact
@@ -2329,11 +2329,11 @@ function tripal_core_get_chado_tables($include_custom = NULL) {
  * @ingroup tripal_core_api
  */
 function tripal_core_get_chado_version($exact = FALSE, $warn_if_unsupported = FALSE) {
-  
+
   global $databases;
   $version = '';
   $is_local = 0;
-  
+
   // check that Chado is installed if not return 'uninstalled as the version'
   $chado_exists = tripal_core_chado_schema_exists();
   if (!$chado_exists) {
@@ -2353,13 +2353,13 @@ function tripal_core_get_chado_version($exact = FALSE, $warn_if_unsupported = FA
     $is_local = 1;
     $prop_exists = db_table_exists('chado.chadoprop');
   }
-  
+
   // if the table doesn't exist then we don't know what version but we know
   // it must be 1.11 or older.
   if (!$prop_exists) {
     $version = "1.11 or older";
   }
-  else {  
+  else {
     $sql = "
       SELECT value
       FROM {chadoprop} CP
@@ -2376,15 +2376,15 @@ function tripal_core_get_chado_version($exact = FALSE, $warn_if_unsupported = FA
       $results = chado_query($sql);
     }
     $v = $results->fetchObject();
-    
+
     // if we don't have a version in the chadoprop table then it must be
     // v1.11 or older
     if (!$v) {
       $version =  "1.11 or older";
-    }  
+    }
     $version =  $v->value;
   }
-  
+
   // next get the exact Chado version that is installed
   $exact_version = $version;
 
@@ -2429,7 +2429,7 @@ function tripal_core_get_chado_version($exact = FALSE, $warn_if_unsupported = FA
  * @ingroup tripal_core_api
  */
 function tripal_core_get_chado_table_schema($table) {
-  
+
   // first get the chado version that is installed
   $v = $GLOBALS["chado_version"];
 
@@ -2509,7 +2509,7 @@ function tripal_core_is_chado_local() {
   if (array_key_exists('chado', $databases)) {
     return FALSE;
   }
-  
+
   // check to make sure the chado schema exists
   return tripal_core_chado_schema_exists();
 }
@@ -2563,3 +2563,51 @@ function tripal_db_set_active($dbname  = 'default') {
     return db_set_active($dbname);
   }
 }
+
+/**
+ * Get max rank for a given set of criteria
+ * This function was developed with the many property tables in chado in mind but will
+ * work for any table with a rank
+ *
+ * @params tablename: the name of the chado table you want to select the max rank from
+ *    this table must contain a rank column of type integer
+ * @params where_options: array(
+ *                          <column_name> => array(
+ *                            'type' => <type of column: INT/STRING>,
+ *                            'value' => <the value you want to filter on>,
+ *                            'exact' => <if TRUE use =; if FALSE use ~>,
+ *                          )
+ *        )
+ *     where options should include the id and type for that table to correctly
+ *     group a set of records together where the only difference are the value and rank
+ * @return the maximum rank
+ *
+ */
+function tripal_core_get_max_chado_rank($tablename, $where_options) {
+
+  $where_clauses = array();
+  $where_args = array();
+
+  //generate the where clause from supplied options
+  // the key is the column name
+  $i = 0;
+  $sql = "
+    SELECT max(rank) as max_rank, count(rank) as count
+    FROM {".$tablename."}
+    WHERE
+  ";
+  foreach ($where_options as $key => $value) {
+    $where_clauses[] = "$key = :$key";
+    $where_args[":$key"] = $value;
+  }
+  $sql .= implode($where_clauses, ' AND ');
+
+  $result = chado_query($sql, $where_args)->fetchObject();
+  if ($result->count > 0) {
+    return $result->max_rank;
+  }
+  else {
+    return -1;
+  }
+
+}

+ 7 - 0
tripal_core/tripal_core.module

@@ -44,6 +44,7 @@ require_once "api/tripal_core_jobs.api.inc";
 require_once "api/tripal_core_mviews.api.inc";
 require_once "api/tripal_core_properties.api.inc";
 require_once "api/tripal_core.database_references.api.inc";
+require_once "api/tripal_core.relationships.api.inc";
 
 require_once "includes/jobs.inc";
 require_once "includes/mviews.inc";
@@ -424,6 +425,12 @@ function tripal_core_theme($existing, $type, $theme, $path) {
       'function' => 'theme_tripal_core_additional_dbxrefs_form_table',
       'render element' => 'element',
     ),
+
+    // relationships form theme
+    'tripal_core_relationships_form_table' => array(
+      'function' => 'theme_tripal_core_relationships_form_table',
+      'render element' => 'element',
+    ),
   );
 }
 

+ 33 - 4
tripal_stock/includes/tripal_stock.chado_node.inc

@@ -344,6 +344,19 @@ function chado_stock_form($node, $form_state) {
   );
   tripal_core_additional_dbxrefs_form($form, $form_state, $details);
 
+  // RELATIONSHIPS FORM
+  //---------------------------------------------
+
+  $details = array(
+    'relationship_table' => 'stock_relationship',
+    'base_table' => 'stock',
+    'base_foreign_key' => 'stock_id',
+    'base_key_value' => $stock_id,
+    'nodetype' => 'stock',
+    'cv_id' => variable_get('chado_stock_relationship_cv', 0)
+  );
+  tripal_core_relationships_form($form, $form_state, $details);
+
   return $form;
 }
 
@@ -593,13 +606,22 @@ function chado_stock_insert($node) {
 
     // Now add the additional references
     if ($stock_added) {
-      tripal_core_additional_dbxrefs_form_update_dbxrefs(
+      tripal_core_relationships_form_update_relationships(
         $node,
         'stock_dbxref',
         'stock_id',
         $stock_id
       );
     }
+
+    // Now add in relationships
+    if ($stock_added) {
+      tripal_core_relationships_form_update_dbxrefs(
+        $node,
+        'stock_relationship',
+        $stock_id
+      );
+    }
   } //end of adding stock to chado
   else {
     // stock already exists since this is a sync
@@ -780,13 +802,20 @@ function chado_stock_update($node) {
 
   // now update the additional dbxrefs
   if ($node->stock_id > 0) {
-    $stock_id = $node->stock_id;
-
     tripal_core_additional_dbxrefs_form_update_dbxrefs(
       $node,
       'stock_dbxref',
       'stock_id',
-      $stock_id
+      $node->stock_id
+    );
+  }
+
+  // now update relationships
+  if ($node->stock_id > 0) {
+    tripal_core_relationships_form_update_relationships(
+      $node,
+      'stock_relationship',
+      $node->stock_id
     );
   }
 }