Sfoglia il codice sorgente

Contact module now syncs/insert/updates. Templates converted except relationships/publications haven't been tested. Added new Properties API to tripal_core to allow for easy addition of properties to a node form

Stephen Ficklin 11 anni fa
parent
commit
7e6e3a3f21

+ 11 - 13
tripal_analysis/includes/tripal_analysis.form.inc

@@ -188,7 +188,7 @@ function chado_analysis_form($node, &$form_state) {
     '#title' => t('Analysis Details'),
     '#description' => t('You may add additional properties by
       selecting a property type from the dropdown and adding text.  You may add 
-      as many properties as desired by clicking the plus button on the right.  To 
+      as many properties as desired by clicking the add button on the right.  To 
       remove a property, click the minus button.  If a property is not available
       you may add it by ' . l('adding the term', 'admin/tripal/tripal_cv/cvterm/add') . '
       to the <b>analysis_property</b> vocabulary within the <b>tripal</b> database'),
@@ -203,7 +203,6 @@ function chado_analysis_form($node, &$form_state) {
   // get the analysis properties
   $properties_select = array();
   $properties_select[] = 'Select a Property';
-  $properties_list = array();
   $sql = "
     SELECT DISTINCT CVT.cvterm_id, CVT.name, CVT.definition
     FROM  {cvterm} CVT
@@ -216,7 +215,6 @@ function chado_analysis_form($node, &$form_state) {
   $prop_types = chado_query($sql);
   while ($prop = $prop_types->fetchObject()) {
     $properties_select[$prop->cvterm_id] = $prop->name;
-    $properties_list[$prop->cvterm_id] = $prop;
   }
   
   // this array keeps track of all properties we have and allows the functions
@@ -303,8 +301,8 @@ function chado_analysis_node_form_add_new_empty_props(&$form, &$form_state, $pro
     // 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. 
-    '#submit'   => array('chado_anslysis_node_form_props_button_submit'),
-    '#validate' => array('chado_anslysis_node_form_props_button_validate'),
+    '#submit'   => array('chado_analysis_node_form_props_button_submit'),
+    '#validate' => array('chado_analysis_node_form_props_button_validate'),
     '#limit_validation_errors' => array(array('new_id')),
   );
 }
@@ -314,7 +312,7 @@ function chado_analysis_node_form_add_new_empty_props(&$form, &$form_state, $pro
  * the validate and submit routines on the form button. Therefore, this function
  * only needs to tell Drupal to rebuild the form
  */
-function chado_anslysis_node_form_props_button_validate($form, &$form_state){
+function chado_analysis_node_form_props_button_validate($form, &$form_state){
   if (array_key_exists('triggering_element', $form_state) and 
       $form_state['triggering_element']['#name'] == 'add' and
       $form_state['input']['new_id'] == 0 ){
@@ -326,7 +324,7 @@ function chado_anslysis_node_form_props_button_validate($form, &$form_state){
 /**
  * This function is just a dummy to override the default form submit on ajax calls for buttons
  */
-function chado_anslysis_node_form_props_button_submit($form, &$form_state){
+function chado_analysis_node_form_props_button_submit($form, &$form_state){
   // do nothing
 }
 /**
@@ -407,8 +405,8 @@ function chado_analysis_node_form_add_new_props(&$form, &$form_state, &$ranks, &
         // 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.
-        '#submit'   => array('chado_anslysis_node_form_props_button_submit'),
-        '#validate' => array('chado_anslysis_node_form_props_button_validate'),
+        '#submit'   => array('chado_analysis_node_form_props_button_submit'),
+        '#validate' => array('chado_analysis_node_form_props_button_validate'),
         '#limit_validation_errors' => array(),
       );
     }
@@ -468,8 +466,8 @@ function chado_analysis_node_form_add_new_props(&$form, &$form_state, &$ranks, &
       // 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.
-      '#submit'   => array('chado_anslysis_node_form_props_button_submit'),
-      '#validate' => array('chado_anslysis_node_form_props_button_validate'),
+      '#submit'   => array('chado_analysis_node_form_props_button_submit'),
+      '#validate' => array('chado_analysis_node_form_props_button_validate'),
       '#limit_validation_errors' => array(),
     );
 
@@ -554,8 +552,8 @@ function chado_analysis_node_form_add_analysisprop_table_props(&$form, $form_sta
       // 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.
-      '#submit'   => array('chado_anslysis_node_form_props_button_submit'),
-      '#validate' => array('chado_anslysis_node_form_props_button_validate'),
+      '#submit'   => array('chado_analysis_node_form_props_button_submit'),
+      '#validate' => array('chado_analysis_node_form_props_button_validate'),
       '#limit_validation_errors' => array(),
     );
   }

+ 1 - 1
tripal_analysis/includes/tripal_analysis.sync.inc

@@ -243,7 +243,7 @@ function tripal_analysis_sync_analyses($analysis_id = NULL, $job_id = NULL) {
         }
       }
       else {
-        print "Failed to insert organism $organism->common_name\n";
+        print "Failed to insert analysis $analysis->name\n";
       }
     }
     else {

+ 0 - 3
tripal_analysis/tripal_analysis.module

@@ -484,9 +484,6 @@ function chado_analysis_update($node) {
   $node->sourceversion = trim($node->sourceversion);
   $node->sourceuri = trim($node->sourceuri);
 
-  if ($node->revision) {
-    // TODO -- decide what to do about revisions
-  }
   // Create a timestamp so we can insert it into the chado database
   $time = $node->timeexecuted;
   $month = $time['month'];

+ 71 - 42
tripal_contact/includes/contact_sync.inc

@@ -4,16 +4,36 @@
  */
 function tripal_contact_sync_form() {
 
-  $form['sync_all'] = array(
-    '#type' => 'item',
-    '#value' => t('Syncing a contact will create a Drupal page for every contact record in the Chado database. Click the button below to sync all contacts in Chado that currently are not already synced with Drupal.'),
+  $form['sync'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Sync Contacts')
+  );
+  $form['sync']['sync_all'] = array(
+    '#markup' => t('<p>Syncing a contact will create a Drupal page for every contact 
+      record in the Chado database. Click the button below to sync all contacts in 
+      Chado that currently are not already synced with Drupal.</p>'),
   );
 
-  $form['submit'] = array(
+  $form['sync']['submit'] = array(
     '#type' => 'submit',
-    '#weight' => 10,
     '#value' => t('Sync contacts')
   );
+  
+  $form['cleanup'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Clean Up')
+  );
+  $form['cleanup']['description'] = array(
+    '#markup' => t("<p>With Drupal and chado residing in different databases ".
+      "it is possible that nodes in Drupal and contacts in Chado become ".
+      "\"orphaned\".  This can occur if an contact node in Drupal is ".
+      "deleted but the corresponding chado contact is not and/or vice ".
+      "versa. Click the button below to resolve these discrepancies.</p>"),
+  );
+  $form['cleanup']['button'] = array(
+    '#type' => 'submit',
+    '#value' => t('Clean up orphaned contacts'),
+  );
 
   return $form;
 }
@@ -24,9 +44,19 @@ function tripal_contact_sync_form() {
  */
 function tripal_contact_sync_form_submit($form, $form_state) {
   global $user;    //needed to make the current users details available so access of user id is available
-  $job_args = array();
-  $job_id = tripal_add_job('Sync contacts', 'tripal_contact', 'tripal_contact_sync_contacts', $job_args, $user->uid);
+  
+  if ($form_state['values']['op'] == t('Sync contacts')) {
+    $job_args = array();
+    $job_id = tripal_add_job('Sync contacts', 'tripal_contact', 'tripal_contact_sync_contacts', 
+      $job_args, $user->uid);
+  }
 
+  // Submit the Cleanup Job if selected
+  if ($form_state['values']['op'] == t('Clean up orphaned contacts')) {
+    $job_args = array();
+    tripal_add_job('Cleanup orphaned contacts', 'tripal_contact',
+     'tripal_contact_cleanup', $job_args, $user->uid);
+  }
 }
 /**
  *
@@ -34,6 +64,7 @@ function tripal_contact_sync_form_submit($form, $form_state) {
  * @ingroup tripal_contact
  */
 function tripal_contact_sync_contacts($job_id = NULL) {
+  global $user;
   
   // get the list of contacts that have not yet been synced
   // and ignore the default 'NULL' contact. we don't want
@@ -47,44 +78,42 @@ function tripal_contact_sync_contacts($job_id = NULL) {
   $results = db_query($sql);
 
   while ($contact = $results->fetchObject()) {
-    $node = tripal_contact_sync_contact($contact);
-  }
-}
-/**
- * @param $contact
- *   A contactlication object
- *   
- * @return
- *   A new Drupal node object on success. FALSE on failure
- *   
- * @ingroup tripal_contact
- */
-function tripal_contact_sync_contact($contact) {
-  global $user;
+    $new_node = new stdClass();
+    $new_node->contact_id = $contact->contact_id;
+    $new_node->type = 'chado_contact';
+    $new_node->title = $contact->name;
+    $new_node->description = $contact->description;
+    $new_node->type_id = $contact->type_id;
   
-  $new_node = new stdClass();
-  $new_node->contact_id = $contact->contact_id;
-  $new_node->type = 'chado_contact';
-  $new_node->title = $contact->name;
-  $new_node->description = $contact->description;
-  $new_node->type_id = $contact->type_id;
-
-  node_validate($new_node);
-  $errors = form_get_errors();
-  if (!$errors) {
-    $node = node_submit($new_node);
-    node_save($node);
-    if ($node->nid) {
-      print "Added " . $contact->contact_id . "\n";      
+    $form = array(); // dummy variable
+    $form_state = array(); // dummy variable
+    node_validate($new_node, $form, $form_state);
+    if (!form_get_errors()) {
+      $node = node_submit($new_node);
+      node_save($node);
+      if ($node->nid) {
+        print "Added contact: $new_node->title\n";
+      }
     }
     else {
-      print "ERROR: Unable to create " . $contact->name . "\n";
+      print "ERROR: Unable to create " . $contact->name . "\n" . print_r($errors, TRUE) . "\n";
       return FALSE;
     }
-  }
-  else {
-    print "ERROR: Unable to create " . $contact->name . "\n" . print_r($errors, TRUE) . "\n";
-    return FALSE;
-  }
-  return $node; 
+  } 
 }
+
+/**
+ * Remove orphaned drupal nodes
+ *
+ * @param $dummy
+ *   Not Used -kept for backwards compatibility
+ * @param $job_id
+ *   The id of the tripal job executing this function
+ *
+ * @ingroup tripal_contact
+ */
+function tripal_contact_cleanup($dummy = NULL, $job_id = NULL) {
+
+  return tripal_core_clean_orphaned_nodes('contact', $job_id);
+
+}

+ 5 - 131
tripal_contact/includes/tripal_contact.admin.inc

@@ -42,100 +42,21 @@ function tripal_contact_admin_contact_view() {
 function tripal_contact_admin() {
   $form = array();
 
-  // before proceeding check to see if we have any
-  // currently processing jobs. If so, we don't want
-  // to give the opportunity to sync maps
-  $active_jobs = FALSE;
-  if (tripal_get_module_active_jobs('tripal_contact')) {
-    $active_jobs = TRUE;
-  }
-
-  // add the field set for syncing maps
-  if (!$active_jobs) {
-    get_tripal_contact_admin_form_cleanup_set($form);
-// TODO: complete coding of indexing and taxonomy assignment to features.
-//    get_tripal_contact_admin_form_reindex_set($form);
-//    get_tripal_contact_admin_form_taxonomy_set($form);
-  }
-  else {
-    $form['notice'] = array(
-     '#type' => 'fieldset',
-     '#title' => t('Feature Map Management Temporarily Unavailable')
-    );
-    $form['notice']['message'] = array(
-        '#value' => t('Currently, feature map management jobs are waiting or are running. Managemment features have been hidden until these jobs complete.  Please check back later once these jobs have finished.  You can view the status of pending jobs in the Tripal jobs page.'),
-    );
-  }
+  $form['nothing'] = array(
+    '#markup' => t('There are currently no settings to configure. .')
+  );
 
   return system_settings_form($form);
 }
-/**
- *
- *
- * @ingroup tripal_contact
- */
-function get_tripal_contact_admin_form_cleanup_set(&$form) {
-  $form['cleanup'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Clean Up')
-  );
-  $form['cleanup']['description'] = array(
-     '#type' => 'item',
-     '#value' => t("With Drupal and chado residing in different databases ".
-        "it is possible that nodes in Drupal and maps in Chado become ".
-        "\"orphaned\".  This can occur if an map node in Drupal is ".
-        "deleted but the corresponding chado map is not and/or vice ".
-        "versa. Click the button below to resolve these discrepancies."),
-     '#weight' => 1,
-  );
-  $form['cleanup']['button'] = array(
-    '#type' => 'submit',
-    '#value' => t('Clean up orphaned maps'),
-    '#weight' => 2,
-  );
-}
+
 
 /**
  *
  * @ingroup tripal_contact
  */
 function get_tripal_contact_admin_form_reindex_set(&$form) {
-   // define the fieldsets
-  $form['reindex'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Reindex Map Features')
-  );
-
-  // get the list of maps
-  $sql = "SELECT * FROM {contact} ORDER BY name";
-  $lib_rset = chado_query($sql);
-
-  // iterate through all of the maps
-  $lib_boxes = array();
-  while ($contact = $lib_rset->fetchObject()) {
-    $lib_boxes[$contact->contact_id] = "$contact->name";
-  }
-  $form['reindex']['description'] = array(
-     '#type' => 'item',
-     '#value' => t("This option allows for reindexing of only those features that belong to the selected maps below. All other features will be unaffected.  To reindex all features in the site see the Feature Administration page."),
-   '#weight' => 1,
-  );
 
-  $form['reindex']['re-maps'] = array(
-   '#title'       => t('Contacts'),
-   '#type'        => t('checkboxes'),
-   '#description' => t("Check the maps whoee features you want to reindex. Note: this list contains all maps, even those that may not be synced."),
-   '#required'    => FALSE,
-   '#prefix'      => '<div id="lib_boxes">',
-   '#suffix'      => '</div>',
-   '#options'     => $lib_boxes,
-   '#weight' => 2,
-  );
-  $form['reindex']['re-button'] = array(
-    '#type' => 'submit',
-    '#value' => t('Reindex Features'),
-    '#weight' => 3,
-  );
+ 
 }
 /**
  *
@@ -144,52 +65,5 @@ function get_tripal_contact_admin_form_reindex_set(&$form) {
 function tripal_contact_admin_validate($form, &$form_state) {
   global $user;  // we need access to the user info
   $job_args = array();
-
-  // -------------------------------------
-  // Submit the Reindex Job if selected
-  if ($form_state['values']['op'] == t('Reindex Features')) {
-    $contacts = $form_state['values']['re-maps'];
-    foreach ($contacts as $contact_id) {
-      if ($contact_id and preg_match("/^\d+$/i", $contact_id)) {
-        // get the map info
-        $sql = "SELECT * FROM {contact} WHERE contact_id = :contact_id";
-        $contact = chado_query($sql, array(':contact_id' => $contact_id))->fetchObject();
-        $job_args[0] = $contact_id;
-        tripal_add_job("Reindex features for map: $contact->name", 'tripal_contact',
-         'tripal_contact_reindex_features', $job_args, $user->uid);
-      }
-    }
-  }
-
-  // -------------------------------------
-  // Submit the Cleanup Job if selected
-  if ($form_state['values']['op'] == t('Clean up orphaned maps')) {
-    tripal_add_job('Cleanup orphaned maps', 'tripal_contact',
-       'tripal_contact_cleanup', $job_args, $user->uid);
-  }
-}
-
-
-/**
- * Remove orphaned drupal nodes
- *
- * @param $dummy
- *   Not Used -kept for backwards compatibility
- * @param $job_id
- *   The id of the tripal job executing this function
- *
- * @ingroup tripal_contact
- */
-function tripal_contact_cleanup($dummy = NULL, $job_id = NULL) {
-
-  return tripal_core_clean_orphaned_nodes('contact', $job_id);
-
 }
-/**
- * Add the map as a taxonomy term for associating with map_features
- *
- * @ingroup tripal_contact
- */
-function tripal_contact_add_taxonomy($node, $contact_id) {
 
-}

+ 128 - 486
tripal_contact/includes/tripal_contact.form.inc

@@ -16,55 +16,76 @@
  *
  */
 function chado_contact_form(&$node, $form_state) {
-  tripal_core_ahah_init_form();
   $form = array();
-
-  $contact = $node->contact;
-  $contact_id = $contact->contact_id;
-    
-  $d_title        = $form_state['values']['title']       ? $form_state['values']['title']       : $contact->name;
-  $d_description  = $form_state['values']['description'] ? $form_state['values']['description'] : $contact->description;
-  $d_type_id      = $form_state['values']['type_id']     ? $form_state['values']['type_id']     : $contact->type_id->cvterm_id;
+  // Default values can come in the following ways:
+  //
+  // 1) as elements of the $node object.  This occurs when editing an existing contact
+  // 2) in the $form_state['values'] array which occurs on a failed validation or
+  //    ajax callbacks from non submit form elements
+  // 3) in the $form_state['input'[ array which occurs on ajax callbacks from submit
+  //    form elements and the form is being rebuilt
+  //
+  // set form field defaults
+  $contact_id  = null;
+  $type_id     = 0;
+  $title       = '';
+  $description = '';
   
-  // on AHAH callbacks we want to keep a list of all the properties that have been removed
-  // we'll store this info in a hidden field and retrieve it here
-  $d_removed = $form_state['values']['removed'];
-
-  // get the number of new fields that have been aded via AHAH callbacks
-  $num_new = $form_state['values']['num_new'] ? $form_state['values']['num_new'] : 0;
-
-  // initialze default properties array. This is where we store the property defaults
-  $d_properties = array();
-
-  // get the contact default values.  When this module was first created
-  // the contact description was incorrectly stored in the $node->body field.
-  // It is better to store it in the Chado tables.  However, the 'description'
-  // field of the contact table is only 255 characters.  So, we are going
-  // to follow the same as the contact module and store the description in
-  // the contactprop table and leave the contact.description field blank.
-  // however, for backwards compatibitily, we check to see if the description
-  // is in the $node->body field. If it is we'll use that.  When the node is
-  // edited the text will be moved out of the body and into the contactprop
-  // table where it should belong.
-  if ($node->body) {
-    $description = $node->body;
+  // if we are editing an existing node then the contact is already part of the node
+  if (property_exists($node, 'contact')) {
+    $contact = $node->contact;
+    $contact_id = $contact->contact_id;
+    
+    // get form defaults
+    $type_id     = $contact->type_id->cvterm_id;
+    $title       = $contact->name;
+    
+    // get the contact default values.  When this module was first created
+    // the contact description was incorrectly stored in the $node->body field.
+    // It is better to store it in the Chado tables.  However, the 'description'
+    // field of the contact table is only 255 characters.  So, we are going
+    // to follow the same as the analysis module and store the description in
+    // the contactprop table and leave the contact.description field blank.
+    // however, for backwards compatibitily, we check to see if the description
+    // is in the $node->body field. If it is we'll use that.  When the node is
+    // edited the text will be moved out of the body and into the contactprop
+    // table where it should belong.
+    $description = '';
+    if (property_exists($node, 'body')) {
+      $description = $node->body;
+    }
+    else {
+      $description = $contact->description;
+    }
+    if (!$description) {
+      $contactprop = tripal_contact_get_property($contact->contact_id, 'contact_description');
+      $description = $contactprop->value;
+    }
+     
+    // set the contact_id in the form
+    $form['contact_id'] = array(
+      '#type' => 'value',
+      '#value' => $contact->contact_id,
+    );
   }
-  else {
-    $description = $node->description;
+  // if we are re constructing the form from a failed validation or ajax callback
+  // then use the $form_state['values'] values
+  if (array_key_exists('values', $form_state)) {
+    $type_id     = $form_state['values']['type_id'];
+    $title       = $form_state['values']['title'];
+    $description = $form_state['values']['description'];
   }
-  if (!$description) {
-    $contactprop = tripal_contact_get_property($contact->contact_id, 'contact_description');
-    $description = $contactprop->value;
+  // if we are re building the form from after submission (from ajax call) then
+  // the values are in the $form_state['input'] array
+  if (array_key_exists('input', $form_state) and !empty($form_state['input'])) {
+    $type_id     = $form_state['input']['type_id'];
+    $title       = $form_state['input']['title'];
+    $description = $form_state['input']['description'];
   }
 
-  // keep track of the contact id if we have.  If we do have one then
-  // this is an update as opposed to an insert.
-  $form['contact_id'] = array(
-    '#type' => 'hidden',
-    '#value' => $contact_id,
-  );;
-  
-  // get the contact type
+  // get the contact types. These are those that are part of the tripal_contact
+  // vocabulary and are children of the term 'Contact Type', so we need
+  // to join on the cvtermpath table and select those with a distance of 1
   $sql = "
     SELECT CVTS.cvterm_id, CVTS.name
     FROM {cvtermpath} CVTP
@@ -74,45 +95,23 @@ function chado_contact_form(&$node, $form_state) {
     WHERE 
       CV.name = 'tripal_contact' AND 
       CVTO.name = 'Contact Type' AND
-      CVTP.pathdistance = 1      
+      CVTP.pathdistance = 1
     ORDER BY CVTS.name ASC 
   ";
   $results = chado_query($sql);
   $contact_types = array();
   while ($contact_type = $results->fetchObject()) {
     $contact_types[$contact_type->cvterm_id] = $contact_type->name;
-    if (strcmp($contact_type->name,"Person") == 0 and !$d_type_id) {
-      $d_type_id = $contact_type->cvterm_id;
+    if (strcmp($contact_type->name, "Person") == 0 and !$type_id) {
+      $type_id = $contact_type->cvterm_id;
     }
   }
-  
-  // get the contact properties 
-  $properties_select = array();
-  $properties_select[] = 'Select a Property';
-  $properties_list = array();
-  $sql = "
-    SELECT CVTS.cvterm_id, CVTS.name
-    FROM {cvtermpath} CVTP
-      INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
-      INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
-      INNER JOIN {cv} CV       ON CVTO.cv_id = CV.cv_id
-    WHERE 
-      CV.name = 'tripal_contact' AND 
-      NOT CVTO.name = 'Contact Type' 
-    ORDER BY CVTS.name ASC 
-  ";
-  $prop_types = chado_query($sql);
-  while ($prop = $prop_types->fetchObject()) {
-    $properties_select[$prop->cvterm_id] = $prop->name;
-    $properties_list[$prop->cvterm_id] = $prop;
-  }
-  
   $form['type_id'] = array(
     '#type' => 'select',
     '#title' => t('Contact Type'),
     '#options' => $contact_types,
     '#required' => TRUE,
-    '#default_value' => $d_type_id,
+    '#default_value' => $type_id,
   );
   
   $form['title']= array(
@@ -120,8 +119,7 @@ function chado_contact_form(&$node, $form_state) {
     '#title'         => t('Contact Name'),
     '#description'   => t('Enter the name of this contact'),
     '#required'      => TRUE,
-    '#default_value' => $d_title,
-    '#weight'        => 1,
+    '#default_value' => $title,
     '#maxlength'     => 255,
   );
 
@@ -130,22 +128,33 @@ function chado_contact_form(&$node, $form_state) {
     '#title'         => t('Contact Description'),
     '#description'   => t('A brief description of the contact'),
     '#required'      => TRUE,
-    '#default_value' => $d_description,
-    '#weight'        => 5
+    '#default_value' => $description,
   );
-  
-  // add in the properties from the contactprop table
-  $num_properties += chado_contact_node_form_add_contactprop_table_props($form, $form_state, $contact_id, $d_properties, $d_removed);
 
-  // add in any new properties that have been added by the user through an AHAH callback
-  $num_new = chado_contact_node_form_add_new_props($form, $form_state, $d_properties, $d_removed);
-
-  // add an empty row of field to allow for addition of a new property
-  chado_contact_node_form_add_new_empty_props($form, $properties_select);
+  // get the contact properties that the user can use for this form
+  $properties = array();
+  $properties[] = 'Select a Property';
+  $sql = "
+    SELECT CVTS.cvterm_id, CVTS.name
+    FROM {cvtermpath} CVTP
+      INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
+      INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
+      INNER JOIN {cv} CV       ON CVTO.cv_id = CV.cv_id
+    WHERE
+      CV.name = 'tripal_contact' AND
+      NOT CVTO.name = 'Contact Type'
+    ORDER BY CVTS.name ASC
+  ";
+  $prop_types = chado_query($sql);
+  while ($prop = $prop_types->fetchObject()) {
+    $properties[$prop->cvterm_id] = $prop->name;
+  }
   
-
+  $exclude = array('contact_description');
+  tripal_core_properties_form($form, $form_state, 'contactprop', 'contact_id', 'tripal_contact',
+    $properties, $contact_id, $exclude, '');
+   
   return $form;
-
 }
 
 /**
@@ -154,414 +163,47 @@ function chado_contact_form(&$node, $form_state) {
  * @ingroup tripal_contact
  */
 function chado_contact_validate($node, &$form) {
-  $title          = trim($node->title);
-  $contact_id     = trim($node->contact_id);
-  $unittype_id    = trim($node->unittype_id);
-  $description    = trim($node->description);
-  $num_properties = $node->num_properties;
-  $num_new = $node->num_new;
-  
-  $contact = 0;
-  // check to make sure the name on the contact is unique
-  // before we try to insert into chado. If this is an update then we will
-  // have a contact_id, therefore we want to look for another contact with this
-  // name but with a different contact_id. If this is an insert, just look
-  // for a case where the name already exists.
-  if ($node->contact_id) {
-    $sql = "
-      SELECT * FROM {contact} 
-      WHERE name = :name AND NOT contact_id = :contact_id
-    ";
-    $contact = chado_query($sql, array(':name' => $title, ':contact_id' => $contact_id))->fetchObject();
-  }
-  else {
-    $sql = "SELECT * FROM {contact} WHERE name = :name";
-    $contact = chado_query($sql, array(':name' => $title))->fetchObject();
-  }
-  if ($contact) {
-    form_set_error('title', t('The contact name already exists. Please choose another'));
-  }
-  
-}
-/*
- *
- */
-function chado_contact_node_form_add_new_empty_props(&$form, $properties_select) {
-
-  // add one more blank set of property fields
-  $form['properties']['new']["new_id"] = array(
-    '#type'          => 'select',
-    '#options'       => $properties_select,
-    '#ahah' => array(
-      'path'    => "tripal_contact/properties/description",
-      'wrapper' => 'tripal-contact-new_value-desc',
-      'event'   => 'change',
-      'method'  => 'replace',          
-  ),
-  );
-  $form['properties']['new']["new_value"] = array(
-    '#type'          => 'textarea',
-    '#default_value' => '',
-    '#cols'          => 5,
-    '#rows'          => $rows,
-    '#description'   => '<div id="tripal-contact-new_value-desc"></div>'
-    );
-    $form['properties']['new']["add"] = array(
-    '#type'         => 'image_button',      
-    '#value'        => t('Add'),
-    '#src'          => drupal_get_path('theme', 'tripal') . '/images/add.png',
-    '#ahah' => array(
-      'path'    => "tripal_contact/properties/add",
-      'wrapper' => 'tripal-contact-edit-properties-table',
-      'event'   => 'click',
-      'method'  => 'replace',          
-    ),
-    '#attributes' => array('onClick' => 'return false;'),
-    );
-}
-/*
- *
- */
-function chado_contact_node_form_add_new_props(&$form, $form_state, &$d_properties, &$d_removed) {
-   
-  // first, add in all of the new properties that were added through a previous AHAH callback
-  $j = 0;
-  $num_properties++;
-
-  // we need to find the
-  if ($form_state['values']) {
-    foreach ($form_state['values'] as $element_name => $value) {
-      if (preg_match('/new_value-(\d+)-(\d+)/', $element_name, $matches)) {
-        $new_id = $matches[1];
-        $rank = $matches[2];
-
-        // skip any properties that the user requested to delete through a previous
-        // AHAH callback or through the current AHAH callback
-        if($d_removed["$new_id-$rank"]) {
-          continue;
-        }
-        if($form_state['post']['remove-' . $new_id . '-' . $rank]) {
-          $d_removed["$new_id-$rank"] = 1;
-          continue;
-        }
-
-        // get this new_id information
-        $cvterm = tripal_core_chado_select('cvterm', array('name', 'definition'), array('cvterm_id' => $new_id));
-
-        // add it to the $d_properties array
-        $d_properties[$new_id][$rank]['name']  = $cvterm->name;
-        $d_properties[$new_id][$rank]['id']    = $new_id;
-        $d_properties[$new_id][$rank]['value'] = $value;
-        $d_properties[$new_id][$rank]['definition']  = $cvterm->definition;
-        $num_properties++;
-
-        // determine how many rows we need in the textarea
-        $rows = 1;
-
-
-        // add the new fields
-        $form['properties']['new'][$new_id][$rank]["new_id-$new_id-$rank"] = array(
-          '#type'          => 'item',
-          '#value'         => $cvterm[0]->name
-        );
-        $form['properties']['new'][$new_id][$rank]["new_value-$new_id-$rank"] = array(
-          '#type'          => 'textarea',
-          '#default_value' => $value,
-          '#cols'          => 50,
-          '#rows'          => $rows,
-          '#description'   => $cvterm->definition,
-        );
-
-        $form['properties']['new'][$new_id][$rank]["remove-$new_id-$rank"] = array(
-          '#type'         => 'image_button',
-          '#value'        => t('Remove'),
-          '#src'          => drupal_get_path('theme', 'tripal') . '/images/minus.png',
-          '#ahah' => array(
-            'path'    => "tripal_contact/properties/minus/$new_id/$rank",
-            'wrapper' => 'tripal-contact-edit-properties-table',
-            'event'   => 'click',
-            'method'  => 'replace',
-        ),
-          '#attributes' => array('onClick' => 'return false;'),
-        );
-      }
-    }
-  }
-
-
-  // second add in any new properties added during this callback
-  if($form_state['post']['add']) {
-    $new_id = $form_state['values']['new_id'];
-    $new_value = $form_state['values']['new_value'];
-
-    // get the rank by counting the number of entries
-    $rank = count($d_properties[$new_id]);
-
-    // get this new_id information
-    $cvterm = tripal_core_chado_select('cvterm', array('name', 'definition'), array('cvterm_id' => $new_id));
-
-    // add it to the $d_properties array
-    $d_properties[$new_id][$rank]['name']  = $cvterm->name;
-    $d_properties[$new_id][$rank]['id']    = $new_id;
-    $d_properties[$new_id][$rank]['value'] = $value;
-    $d_properties[$new_id][$rank]['definition']  = $cvterm->definition;
-    $num_properties++;
-
-    // determine how many rows we need in the textarea
-    $rows = 1;
-    if (preg_match('/Abstract/', $cvterm[0]->name)) {
-      $rows = 10;
-    }
-    if ($cvterm[0]->name == 'Authors') {
-      $rows = 2;
-    }
-
-    // add the new fields
-    $form['properties']['new'][$new_id][$rank]["new_id-$new_id-$rank"] = array(
-      '#type'          => 'item',
-      '#value'         => $cvterm[0]->name
-    );
-    $form['properties']['new'][$new_id][$rank]["new_value-$new_id-$rank"] = array(
-      '#type'          => 'textarea',
-      '#default_value' => $new_value,
-      '#cols'          => 50,
-      '#rows'          => $rows,
-      '#description'   => $cvterm->definition,
-    );
-
-    $form['properties']['new'][$new_id][$rank]["remove-$new_id-$rank"] = array(
-      '#type'         => 'image_button',
-      '#value'        => t('Remove'),
-      '#src'          => drupal_get_path('theme', 'tripal') . '/images/minus.png',
-      '#ahah' => array(
-        'path'    => "tripal_contact/properties/minus/$new_id/$rank",
-        'wrapper' => 'tripal-contact-edit-properties-table',
-        'event'   => 'click',
-        'method'  => 'replace',
-    ),
-      '#attributes' => array('onClick' => 'return false;'),
-    );
-
-  }
-
-  return $num_properties;
-}
-/*
- *
- */
-function chado_contact_node_form_add_contactprop_table_props(&$form, $form_state, $contact_id, &$d_properties, &$d_removed) {
-
-  // get the properties for this contact
-  $num_properties = 0;
-
-  if(!$contact_id) {
-    return $num_properties;
-  }
-
-  $sql = "
-    SELECT CVT.cvterm_id, CVT.name, CVT.definition, PP.value, PP.rank
-    FROM {contactprop} PP
-      INNER JOIN {cvterm} CVT on CVT.cvterm_id = PP.type_id
-    WHERE PP.contact_id = :contact_id
-    ORDER BY CVT.name, PP.rank
-  ";
-  $contact_props = chado_query($sql, array(':contact_id' => $contact_id));
-  while ($prop = $contact_props->fetchObject()) {
-
-    $type_id = $prop->cvterm_id;
-    $rank = count($d_properties[$type_id]);
+  // remove surrounding white-space on submitted values
+  $node->title          = trim($node->title);
+  $node->description    = trim($node->description);
+ 
+  // Only nodes being updated will have an nid already
+  if (!property_exists($node,'nid')) {    
+    // CASE A: We are validating a form for updating an existing node
     
-    // skip the description as we have a separate form element for that
-    if($prop->name == "contact_description") {
-      continue;
+    // get the existing node    
+    $values = array('contact_id' => $node->contact_id);      
+    $result = tripal_core_chado_select('contact', array('*'), $values);
+    $contact = $result[0];
+      
+    // if the name has changed make sure it doesn't conflict with an existing name
+    if ($contact->name != $node->title) {
+      $values = array('name' => $node->title);
+      $result = tripal_core_chado_select('contact', array('contact_id'), $values);
+      if ($result and count($result) > 0) {
+        form_set_error('title', 'Cannot update the contact with this contact name. An contact with this name already exists.');
+        return;
+      }  
     }
-
-    // skip any properties that the user requested to delete through a previous
-    // AHAH callback or through the current AHAH callback
-    if($d_removed["$type_id-$rank"]) {
-      continue;
-    }
-    if($form_state['post']['remove-' . $type_id . '-' . $rank]) {
-      $d_removed["$type_id-$rank"] = 1;
-      continue;
-    }
-
-    $d_properties[$type_id][$rank]['name']  = $prop->name;
-    $d_properties[$type_id][$rank]['id']    = $type_id;
-    $d_properties[$type_id][$rank]['value'] = $prop->value;
-    $d_properties[$type_id][$rank]['definition']  = $prop->definition;
-    $num_properties++;
-
-    $form['properties'][$type_id][$rank]["prop_id-$type_id-$rank"] = array(
-      '#type'          => 'item',
-      '#value'         => $prop->name,
-    );
-    $form['properties'][$type_id][$rank]["prop_value-$type_id-$rank"] = array(
-      '#type'          => 'textarea',
-      '#default_value' => $prop->value,
-      '#cols'          => 50,
-      '#rows'          => $rows,
-      '#description'   => $prop->definition,
-    );
-
-    $form['properties'][$type_id][$rank]["remove-$type_id-$rank"] = array(
-      '#type'         => 'image_button',
-      '#value'        => t('Remove'),
-      '#src'          => drupal_get_path('theme', 'tripal') . '/images/minus.png',
-      '#ahah' => array(
-        'path'    => "tripal_contact/properties/minus/$type_id/$rank",
-        'wrapper' => 'tripal-contact-edit-properties-table',
-        'event'   => 'click',
-        'method'  => 'replace',
-    ),
-      '#attributes' => array('onClick' => 'return false;'),
-    );
   }
-  return $num_properties;
-}
-/*
- *
- */
-function tripal_contact_theme_node_form_properties($form) {
-  $rows = array();
-
-  if ($form['properties']) {
-
-    // first add in the properties derived from the contactprop table
-    // the array tree for these properties looks like this:
-    // $form['properties'][$type_id][$rank]["prop_id-$type_id-$rank"]
-    foreach ($form['properties'] as $type_id => $elements) {
-      // there are other fields in the properties array so we only
-      // want the numeric ones those are our type_id
-      if (is_numeric($type_id)) {
-        foreach ($elements as $rank => $element) {
-          if (is_numeric($rank)) {
-            $rows[] = array(
-            drupal_render($element["prop_id-$type_id-$rank"]),
-            drupal_render($element["prop_value-$type_id-$rank"]),
-            drupal_render($element["remove-$type_id-$rank"]),
-            );
-          }
-        }
-      }
+  else {
+    // To differentiate if we are syncing or creating a new contact altogther, see if an
+    // contact_id already exists
+    if (property_exists($node, 'contact_id') and $node->contact_id != 0) {
+      // CASE B: Synchronizing a node from chado to drupal
+      // we don't need to do anything.
     }
-
-    // second, add in any new properties added by the user through AHAH callbacks
-    // the array tree for these properties looks like this:
-    // $form['properties']['new'][$type_id][$rank]["new_id-$new_id-$rank"]
-    foreach ($form['properties']['new'] as $type_id => $elements) {
-      if (is_numeric($type_id)) {
-        foreach ($elements as $rank => $element) {
-          if (is_numeric($rank)) {
-            $rows[] = array(
-            drupal_render($element["new_id-$type_id-$rank"]),
-            drupal_render($element["new_value-$type_id-$rank"]),
-            drupal_render($element["remove-$type_id-$rank"]),
-            );
-          }
-        }
+    else {
+      // CASE C: We are validating a form for inserting a new node
+      // The unique constraint for the chado contact table is: name
+      $values = array(
+        'name' => $node->title,
+      );
+      $contact = tripal_core_chado_select('contact', array('contact_id'), $values);
+      if ($contact and count($contact) > 0) {
+        form_set_error('title', 'Cannot add the contact with this name. An contact with these values already exists.');
+        return;
       }
     }
-
-    // finally add in a set of blank field for adding a new property
-    $rows[] = array(
-    drupal_render($form['properties']['new']['new_id']),
-    drupal_render($form['properties']['new']['new_value']),
-    drupal_render($form['properties']['new']['add']),
-    );
   }
-
-  $headers = array('Property Type','Value', '');
-  return theme('table', $headers, $rows, array('id'=> "tripal-contact-edit-properties-table"));
-}
-
-/*
- *
- */
-function tripal_contact_property_add() {
-  $status = TRUE;
-
-  // prepare and render the form
-  $form = tripal_core_ahah_prepare_form();
-
-  // we only want to return the properties as that's all we'll replace with this AHAh callback
-  $data = tripal_contact_theme_node_form_properties($form);
-
-  // bind javascript events to the new objects that will be returned
-  // so that AHAH enabled elements will work.
-  $settings = tripal_core_ahah_bind_events();
-
-  // return the updated JSON
-  drupal_json(
-  array(
-      'status'   => $status, 
-      'data'     => $data,
-      'settings' => $settings,
-  )
-  );
-}
-/*
- *
- */
-function tripal_contact_property_delete() {
-  $status = TRUE;
-
-  // prepare and render the form
-  $form = tripal_core_ahah_prepare_form();
-
-  // we only want to return the properties as that's all we'll replace with this AHAh callback
-  $data = tripal_contact_theme_node_form_properties($form);
-
-  // bind javascript events to the new objects that will be returned
-  // so that AHAH enabled elements will work.
-  $settings = tripal_core_ahah_bind_events();
-
-  // return the updated JSON
-  drupal_json(
-    array(
-      'status'   => $status, 
-      'data'     => $data,
-      'settings' => $settings,
-    )
-  );
-}
-/*
- *
- */
-function tripal_contact_property_get_description() {
-  $new_id = $_POST['new_id'];
-
-  $values = array('cvterm_id' => $new_id);
-  $cvterm = tripal_core_chado_select('cvterm', array('definition'), $values);
-
-  $description = '&nbsp;';
-  if ($cvterm[0]->definition) {
-    $description = $cvterm[0]->definition;
-  }
-  drupal_json(
-    array(
-      'status' => TRUE,
-      'data'   => '<div id="tripal-contact-new_value-desc">' . $description . '</div>',
-    )
-  );
-}
-/*
- *
- */
-function theme_chado_contact_node_form($form) {
-
-  $properties_table = tripal_contact_theme_node_form_properties($form);
-
-  $markup  = drupal_render($form['contact_id']);
-  $markup .= drupal_render($form['title']);
-  $markup .= drupal_render($form['type_id']);
-  $markup .= drupal_render($form['description']);
-  $markup .= "<b>Include Additional Details</b><br>You may add additional properties to this contact by scrolling to the bottom of this table, selecting a property type from the dropdown and adding text.  You may add as many properties as desired by clicking the plus button on the right.  To remove a property, click the minus button";
-  $markup .= $properties_table;
-
-  $form['properties'] = array(
-    '#type' => 'markup',
-    '#value' =>  $markup,
-  );
-  return drupal_render($form);
 }

+ 0 - 139
tripal_contact/theme/node--chado-contact.tpl.php

@@ -1,139 +0,0 @@
-<?php
-// Purpose: This template provides the layout of the contact node (page)
-//   using the same templates used for the various contact content blocks.
-//
-// To Customize the Libray Node Page:
-//   - This Template: customize basic layout and which elements are included
-//   - Using Panels: Override the node page using Panels3 and place the blocks
-//       of content as you please. This method requires no programming. See
-//       the Tripal User Guide for more details
-//   - Block Templates: customize the content/layout of each block of stock 
-//       content. These templates are found in the tripal_stock subdirectory
-//
-// Variables Available:
-//   - $node: a standard object which contains all the fields associated with
-//       nodes including nid, type, title, taxonomy. It also includes stock
-//       specific fields such as stock_name, uniquename, stock_type, synonyms,
-//       properties, db_references, object_relationships, subject_relationships,
-//       organism, etc.
-//   NOTE: For a full listing of fields available in the node object the
-//       print_r $node line below or install the Drupal Devel module which 
-//       provides an extra tab at the top of the node page labelled Devel
- 
-$contact  = $variables['node']->contact;
-
-// get the template settings
-$template_settings = theme_get_setting('tripal');
-
-// toggle the sidebar if desired
-$no_sidebar = 0;
-if (is_array($template_settings['tripal_no_sidebar']) and 
-   $template_settings['tripal_no_sidebar']['contact']) {
-  $no_sidebar = 1;
-}
-
-if ($teaser) { 
-  print theme('tripal_contact_teaser', $variables); 
-} 
-else { ?>
-
-<script type="text/javascript">
-(function ($) {
-  Drupal.behaviors.contactBehavior = {
-    attach: function (context, settings){ <?php 
-      if($no_sidebar){ ?>    
-        // hide the resource side bar and strech the details section    
-        $(".tripal_toc").hide();
-        $(".tripal_details").addClass("tripal_details_full");
-        $(".tripal_details_full").removeClass("tripal_details"); <?php
-      } else { ?>
-        // use default resource sidebar
-        $(".tripal-info-box").hide(); <?php
-      } ?>
- 
-      // iterate through all of the info boxes and add their titles
-      // to the table of contents
-      $(".tripal-info-box-title").each(function(){
-        var parent = $(this).parent();
-        var id = $(parent).attr('id');
-        var title = $(this).text();
-        $('#tripal_contact_toc_list').append('<li><a href="#'+id+'" class="tripal_contact_toc_item">'+title+'</a></li>');
-      });
-
-      // when a title in the table of contents is clicked, then
-      // show the corresponding item in the details box
-      $(".tripal_contact_toc_item").click(function(){
-         $(".tripal-info-box").hide();
-         href = $(this).attr('href');
-         $(href).fadeIn('slow');
-         // we want to make sure our table of contents and the details
-         // box stay the same height
-         $("#tripal_contact_toc").height($(href).parent().height());
-         return false;
-      }); 
-
-      // we want the base details to show up when the page is first shown 
-      // unless the user specified a specific block
-      var block = window.location.href.match(/[\?|\&]block=(.+?)\&/)
-      if(block == null){
-         block = window.location.href.match(/[\?|\&]block=(.+)/)
-      }
-      if(block != null){
-         $("#tripal_contact-"+block[1]+"-box").show();
-      } else {
-         $("#tripal_contact-base-box").show();
-      }
-
-      $("#tripal_contact_toc").height($("#tripal_contact-base-box").parent().height());
-    }     
-  };
-})(jQuery);
-</script>
-
-<div id="tripal_contact_details" class="tripal_details">
-
-   <!-- Basic Details Theme -->
-   <?php print theme('tripal_contact_base',$node); ?>
-   
-   <!-- Properties Theme -->
-   <?php print theme('tripal_contact_properties',$node); ?>
-   
-   <!-- Relationships Theme -->
-   <?php print theme('tripal_contact_relationships',$node); ?>
-   
-   <!-- Relationships Theme -->
-   <?php print theme('tripal_contact_publications',$node); ?>
-   
-   <!-- Resource Blocks CCK elements --><?php
-   for($i = 0; $i < count($node->field_resource_titles); $i++){
-     if($node->field_resource_titles[$i]['value']){ ?>
-       <div id="tripal_contact-resource_<?php print $i?>-box" class="tripal_contact-info-box tripal-info-box">
-         <div class="tripal_contact-info-box-title tripal-info-box-title"><?php print $node->field_resource_titles[$i]['value'] ?></div>
-         <?php print $node->field_resource_blocks[$i]['value']; ?>
-       </div><?php
-     }
-   }?>
-   
-   <!-- Let modules add more content -->
-
-   <?php print $content ?>
-</div>
-
-<!-- Table of contents -->
-<div id="tripal_contact_toc" class="tripal_toc">
-   <div id="tripal_contact_toc_title" class="tripal_toc_title">Resources</div>
-   <ul id="tripal_contact_toc_list" class="tripal_toc_list">
-   
-     <!-- Resource Links CCK elements --><?php
-     for($i = 0; $i < count($node->field_resource_links); $i++){
-       if($node->field_resource_links[$i]['value']){
-         $matches = preg_split("/\|/",$node->field_resource_links[$i]['value']);?>
-         <li><a href="<?php print $matches[1] ?>" target="_blank"><?php print $matches[0] ?></a></li><?php
-       }
-     }?>
-     
-     <?php // ADD CUSTOMIZED <li> LINKS HERE ?>
-   </ul>
-</div>
-
-<?php } ?>

+ 54 - 18
tripal_contact/theme/tripal_contact/tripal_contact_base.tpl.php

@@ -1,24 +1,60 @@
 <?php
-$node = $variables['node'];
-$contact = $variables['node']->contact;
+$contact = $variables['node']->contact; ?>
 
-?>
 <div id="tripal_contact-base-box" class="tripal_contact-info-box tripal-info-box">
   <div class="tripal_contact-info-box-title tripal-info-box-title">Details</div>
-  <div class="tripal_contact-info-box-desc tripal-info-box-desc"></div>   
+  <div class="tripal_contact-info-box-desc tripal-info-box-desc"></div> <?php
 
-  <table id="tripal_contact-table-base" class="tripal_contact-table tripal-table tripal-table-vert">
-    <tr class="tripal_contact-table-even-row tripal-table-even-row">
-      <th>Name</th>
-      <td><?php print $contact->name; ?></td>
-    </tr>
-    <tr class="tripal_contact-table-odd-row tripal-table-odd-row">
-      <th>Contact Type</th>
-      <td><?php print $contact->type_id->name; ?></td>
-    </tr>
-    <tr class="tripal_contact-table-even-row tripal-table-even-row">
-      <th>Description</th>
-      <td><?php print $contact->description; ?></td>
-    </tr>
-  </table> 
+  // the $headers array is an array of fields to use as the colum headers. 
+  // additional documentation can be found here 
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+  // This table for the contact has a vertical header (down the first column)
+  // so we do not provide headers here, but specify them in the $rows array below.
+  $headers = array();
+  
+  // the $rows array contains an array of rows where each row is an array
+  // of values for each column of the table in that row.  Additional documentation
+  // can be found here:
+  // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7 
+  $rows = array();
+
+  // Contact Name row
+  $rows[] = array(
+    array(
+      'data' => 'Name',
+      'header' => TRUE
+    ),
+    $contact->name,
+  );
+  // Contact Type row
+  $rows[] = array(
+    array(
+      'data' => 'Type',
+      'header' => TRUE
+    ),
+    $contact->type_id->name,
+  );
+  
+  // 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' => 'tripal_contact-table-base',
+    ),
+    'sticky' => FALSE,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => '',
+  );
+  
+  // once we have our table array structure defined, we call Drupal's theme_table()
+  // function to generate the table.
+  print theme_table($table);
+  if (property_exists($contact, 'description')) {
+    print $contact->description;
+  } ?>
 </div>

+ 50 - 30
tripal_contact/theme/tripal_contact/tripal_contact_properties.tpl.php

@@ -1,43 +1,63 @@
 <?php
-$contact = $node->contact;
 
 // expand the contact to include the properties.
+$contact = $variables['node']->contact;
 $contact = tripal_core_expand_chado_vars($contact,'table', 'contactprop', array('return_array' => 1));
 $contactprops = $contact->contactprop;
+
+// put the properties in an array for easier access
 $properties = array();
-if (is_array($contactprops)) {
-  foreach ($contactprops as $property) {
-    // we want to keep all properties but the contact_description as that
-    // property is shown on the base template page.
-    if($property->type_id->name != 'contact_description') {
-      $property = tripal_core_expand_chado_vars($property,'field','contactprop.value');
-      $properties[] = $property;
-    }
+foreach ($contactprops as $property) {
+  // we want to keep all properties but the contact_description as that
+  // property is shown on the base template page.
+  if($property->type_id->name != 'contact_description') {
+    $property = tripal_core_expand_chado_vars($property,'field','contactprop.value');
+    $properties[] = $property;
   }
 }
 
+
 if (count($properties) > 0) { ?>
   <div id="tripal_contact-properties-box" class="tripal_contact-info-box tripal-info-box">
     <div class="tripal_contact-info-box-title tripal-info-box-title">More Details</div>
-    <div class="tripal_contact-info-box-desc tripal-info-box-desc">Additional information about this contact:</div>
-    <table class="tripal_contact-table tripal-table tripal-table-horz">
-      <tr>
-        <th>Property Name</th>
-        <th>Value</th>
-      </tr> <?php
-      $i = 0;
-      foreach ($properties as $property) {
-        $class = 'tripal_contact-table-odd-row tripal-table-odd-row';
-        if ($i % 2 == 0 ) {
-           $class = 'tripal_contact-table-odd-row tripal-table-even-row';
-        }
-        $i++; 
-        ?>
-        <tr class="<?php print $class ?>">
-          <td><?php print ucfirst(preg_replace('/_/', ' ', $property->type_id->name)) ?></td>
-          <td><?php print $property->value ?></td>
-        </tr><?php 
-      } ?>
-    </table>
+    <div class="tripal_contact-info-box-desc tripal-info-box-desc">Additional information about this contact:</div><?php
+    // the $headers array is an array of fields to use as the colum headers.
+    // additional documentation can be found here
+    // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+    $headers = array('Property Name', 'Value');
+    
+    // the $rows array contains an array of rows where each row is an array
+    // of values for each column of the table in that row.  Additional documentation
+    // can be found here:
+    // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+    $rows = array();
+    
+    // add the properties as individual rows
+    foreach ($properties as $property) {
+      $rows[] = array(
+        ucfirst(preg_replace('/_/', ' ', $property->type_id->name)),
+        $property->value
+      );
+    } 
+    
+    // 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' => 'tripal_contact-table-properties',
+      ),
+      'sticky' => FALSE,
+      'caption' => '',
+      'colgroups' => array(),
+      'empty' => '',
+    );
+    
+    // once we have our table array structure defined, we call Drupal's theme_table()
+    // function to generate the table.
+    print theme_table($table); ?>
   </div> <?php
-}
+}

+ 71 - 51
tripal_contact/theme/tripal_contact/tripal_contact_publications.tpl.php

@@ -1,58 +1,78 @@
 <?php
-$contact  = $variables['node']->contact;
+$contact = $variables['node']->contact;
 
 // expand contact to include pubs 
 $options = array('return_array' => 1);
 $contact = tripal_core_expand_chado_vars($contact, 'table', 'pubauthor_contact', $options);
+$pubauthor_contacts = $contact->pubauthor_contact; 
 
-$pubauthor_contacts = $contact->pubauthor_contact;
-?>
 
-<div id="tripal_pubauthor_contact-pub-box" class="tripal_pubauthor_contact-info-box tripal-info-box">
-  <div class="tripal_pubauthor_contact-info-box-title tripal-info-box-title">Publications</div>
-  <div class="tripal_pubauthor_contact-info-box-desc tripal-info-box-desc"></div>
-
-  <table id="tripal_pubauthor_contact-pub-table" class="tripal_pubauthor_contact-table tripal-table tripal-table-vert" style="border-bottom:solid 2px #999999">
-    <tr>
-      <th>Year</th>
-      <th>Publication</th></tr> <?php
-      $i = 0;
-      foreach ($pubauthor_contacts AS $pubauthor_contact) {
-        $pub = $pubauthor_contact->pubauthor_id->pub_id;
-        $pub = tripal_core_expand_chado_vars($pub, 'field', 'pub.title');
-        $citation = $pub->title;  // use the title as the default citation
-        
-        // get the citation for this pub if it exists
-        $values = array(
-          'pub_id' => $pub->pub_id, 
-          'type_id' => array(
-            'name' => 'Citation',
-          ),
-        );
-        $options = array('return_array' => 1);
-        $citation_prop = tripal_core_generate_chado_var('pubprop', $values, $options); 
-        if (count($citation_prop) == 1) {
-          $citation_prop = tripal_core_expand_chado_vars($citation_prop, 'field', 'pubprop.value');
-          $citation = $citation_prop[0]->value;
-        }
-        
-        // if the publicatio is synced then link to it
-        if ($pub->nid) {
-          // replace the title with a link
-          $link = l($pub->title, 'node/' . $pub->nid ,array('attributes' => array('target' => '_blank')));
-          $citation = preg_replace('/' . $pub->title . '/', $link, $citation);
-        }
-          
-        
-        $class = 'tripal_pubauthor_contact-table-odd-row tripal-table-odd-row';
-        if($i % 2 == 0 ){
-           $class = 'tripal_pubauthor_contact-table-odd-row tripal-table-even-row';
-        } ?>
-        <tr class="<?php print $class ?>">
-          <td><?php print $pub->pyear ?></td>
-          <td><?php print $citation ?></td>
-        </tr><?php 
-        $i++;
-      }  ?>
-  </table>
-</div>
+if (count($pubauthor_contacts) > 0) { ?>
+  <div id="tripal_pubauthor_contact-pub-box" class="tripal_pubauthor_contact-info-box tripal-info-box">
+    <div class="tripal_pubauthor_contact-info-box-title tripal-info-box-title">Publications</div>
+    <div class="tripal_pubauthor_contact-info-box-desc tripal-info-box-desc"></div> <?php 
+  
+    // the $headers array is an array of fields to use as the colum headers.
+    // additional documentation can be found here
+    // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+    $headers = array('Year', 'Publication');
+    
+    // the $rows array contains an array of rows where each row is an array
+    // of values for each column of the table in that row.  Additional documentation
+    // can be found here:
+    // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+    $rows = array();
+    
+    foreach ($pubauthor_contacts as $pubauthor_contact) {
+      $pub = $pubauthor_contact->pubauthor_id->pub_id;
+      $pub = tripal_core_expand_chado_vars($pub, 'field', 'pub.title');
+      $citation = $pub->title;  // use the title as the default citation
+      
+      // get the citation for this pub if it exists
+      $values = array(
+        'pub_id' => $pub->pub_id, 
+        'type_id' => array(
+          'name' => 'Citation',
+        ),
+      );
+      $options = array('return_array' => 1);
+      $citation_prop = tripal_core_generate_chado_var('pubprop', $values, $options); 
+      if (count($citation_prop) == 1) {
+        $citation_prop = tripal_core_expand_chado_vars($citation_prop, 'field', 'pubprop.value');
+        $citation = $citation_prop[0]->value;
+      }
+      
+      // if the publication is synced then link to it
+      if ($pub->nid) {
+        // replace the title with a link
+        $link = l($pub->title, 'node/' . $pub->nid ,array('attributes' => array('target' => '_blank')));
+        $citation = preg_replace('/' . $pub->title . '/', $link, $citation);
+      }
+      
+      $rows[] = array(
+        $pub->pyear,
+        $citation,
+      );
+    }
+    
+    // the $table array contains the headers and rows array as well as other
+    // options for controlling the display of the table.  Additional
+    // documentation can be found here:
+    // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+    $table = array(
+      'header' => $headers,
+      'rows' => $rows,
+      'attributes' => array(
+        'id' => 'tripal_contact-table-publications',
+      ),
+      'sticky' => FALSE,
+      'caption' => '',
+      'colgroups' => array(),
+      'empty' => '',
+    );
+    
+    // once we have our table array structure defined, we call Drupal's theme_table()
+    // function to generate the table.
+    print theme_table($table); ?>
+  </div> <?php 
+}

+ 117 - 69
tripal_contact/theme/tripal_contact/tripal_contact_relationships.tpl.php

@@ -1,81 +1,129 @@
 <?php
-// this template does not follow the typical Tripal API. Normally
-// variables are expanded using the tripal_core_expand_chado_vars API
-// function call, but expanding the relationships table does not yeild
-// a meaningful order to the data.  Therefore, relationships are preprocessed
-// into an array named 'all_relationships', which is used in the template below.
-
+/* Typically in a Tripal template, the data needed is retrieved using a call to
+ * tripal_core_expand_chado_vars function.  For example, to retrieve all 
+ * of the contact relationships for this node, the following function call would be made:
+ * 
+ *   $contact = tripal_core_expand_chado_vars($contact,'table','contact_relationship');
+ * 
+ * However, this function call can be extremely slow when there are numerous relationships.
+ * This is because the tripal_core_expand_chado_vars function is recursive and expands 
+ * all data following the foreign key relationships tree.  Therefore, to speed retrieval
+ * of data, a special variable is provided to this template:
+ * 
+ *   $contact->all_relationships;
+ *   
+ * This variable is an array with two sub arrays with the keys 'object' and 'subject'.  The array with
+ * key 'object' contains relationships where the contact is the object, and the array with
+ * the key 'subject' contains relationships where the contact is the subject
+ */
 $contact = $variables['node']->contact;
 
 $all_relationships = $contact->all_relationships;
 $object_rels = $all_relationships['object'];
 $subject_rels = $all_relationships['subject'];
 
-// make the contact type a bit more human readable
-$contact_type =  preg_replace("/_/", ' ', $contact->type_id->name);
-
-if (count($object_rels) > 0 or count($subject_rels) > 0) {
-?>
+if (count($object_rels) > 0 or count($subject_rels) > 0) { ?>
   <div id="tripal_contact-relationships-box" class="tripal_contact-info-box tripal-info-box">
     <div class="tripal_contact-info-box-title tripal-info-box-title">Relationships</div>
-    <!--  <div class="tripal_contact-info-box-desc tripal-info-box-desc"></div> --><?php
+    <div class="tripal_contact-info-box-desc tripal-info-box-desc"></div> <?php
+    // first add in the subject relationships.  
+    foreach ($subject_rels as $rel_type => $rels){
+      foreach ($rels as $obj_type => $objects){ ?>
+        <p>This <?php print $contact->type_id->name;?> is <?php print $rel_type ?> the following <b><?php print $obj_type ?></b> contact(s): <?php
+         
+        // the $headers array is an array of fields to use as the colum headers.
+        // additional documentation can be found here
+        // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+        $headers = array('Name');
+        
+        // the $rows array contains an array of rows where each row is an array
+        // of values for each column of the table in that row.  Additional documentation
+        // can be found here:
+        // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+        $rows = array();
+        
+        foreach ($objects as $object){
+          // link the contact to it's node
+          $contact_name = $object->record->object_id->name;
+          if (property_exists($object->record, 'nid')) {
+            $contact_name = "<a href=\"" . url("node/" . $object->record->nid) . "\" target=\"_blank\">" . $object->record->object_id->name . "</a>";
+          }
+
+          $rows[] = array(
+            $contact_name, 
+          ); 
+         } 
+         // 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' => 'tripal_contact-table-relationship-object',
+           ),
+           'sticky' => FALSE,
+           'caption' => '',
+           'colgroups' => array(),
+           'empty' => '',
+         );
+         
+         // once we have our table array structure defined, we call Drupal's theme_table()
+         // function to generate the table.
+         print theme_table($table); ?>
+         </p>
+         <br><?php
+       }
+    }
     
-      // first add in the subject relationships.  
-      foreach ($subject_rels as $rel_type => $rels){
-         // make the type a bit more human readable
-         $rel_type = preg_replace("/_/", ' ', $rel_type);
-         $rel_type = preg_replace("/^is/", '', $rel_type);
-         // iterate through each parent   
-         foreach ($rels as $obj_type => $objects){?>
-           <p>This contact is a <b><?php print $rel_type ?></b> of the following contact(s):
-           <table id="tripal_contact-relationships_as_object-table" class="tripal_contact-table tripal-table tripal-table-horz">
-             <tr>
-               <th>contact Name</th>
-             </tr> <?php
-             foreach ($objects as $object){ ?>
-               <tr>
-                 <td><?php 
-                    if ($object->nid) {
-                      print "<a href=\"" . url("node/" . $object->nid) . "\" target=\"_blank\">" . $object->name . "</a>";
-                    }
-                    else {
-                      print $object->name;
-                    } ?>
-                 </td>
-               </tr> <?php
-             } ?>
-             </table>
-             </p><br><?php
-         }
-      }
-      
-      // second add in the object relationships.  
-      foreach ($object_rels as $rel_type => $rels){
-         // make the type more human readable
-         $rel_type = preg_replace('/_/', ' ', $rel_type);
-         $rel_type = preg_replace("/^is/", '', $rel_type);
-         // iterate through the children         
-         foreach ($rels as $subject_type => $subjects){?>
-           <p>The following contacts are a <b><?php print $rel_type ?></b> of this contact:
-           <table id="tripal_contact-relationships_as_object-table" class="tripal_contact-table tripal-table tripal-table-horz">
-             <tr>
-               <th>Name</th>
-             </tr> <?php
-             foreach ($subjects as $subject){ ?>
-               <tr>
-                 <td><?php 
-                    if ($subject->nid) {
-                      print "<a href=\"" . url("node/" . $subject->nid) . "\" target=\"_blank\">" . $subject->name . "</a>";
-                    }
-                    else {
-                      print $subject->name;
-                    } ?>
-                 </td>
-               </tr> <?php
-             } ?>
-             </table>
-             </p><br><?php
-         }
-      } ?>
+    // second add in the object relationships.  
+    foreach ($object_rels as $rel_type => $rels){
+      foreach ($rels as $subject_type => $subjects){?>
+        <p>The following <b><?php print $subjects[0]->record->subject_id->type_id->name ?></b> contact(s) are <?php print $rel_type ?> this <?php print $contact->type_id->name;?>: <?php 
+        // the $headers array is an array of fields to use as the colum headers.
+        // additional documentation can be found here
+        // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+        $headers = array('Name');
+        
+        // the $rows array contains an array of rows where each row is an array
+        // of values for each column of the table in that row.  Additional documentation
+        // can be found here:
+        // https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_table/7
+        $rows = array();
+        
+        foreach ($subjects as $subject){
+          // link the contact to it's node
+          $contact_name = $subject->record->subject_id->name;
+          if (property_exists($subject->record, 'nid')) {
+            $contact_name = "<a href=\"" . url("node/" . $subject->record->nid) . "\" target=\"_blank\">" . $subject->record->subject_id->name . "</a>";
+          }
+          $rows[] = array(
+            $contact_name, 
+          ); 
+         } 
+         // 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' => 'tripal_contact-table-relationship-subject',
+           ),
+           'sticky' => FALSE,
+           'caption' => '',
+           'colgroups' => array(),
+           'empty' => '',
+         );
+         
+         // once we have our table array structure defined, we call Drupal's theme_table()
+         // function to generate the table.
+         print theme_table($table); ?>
+         </p>
+         <br><?php
+       }
+    }?>
   </div> <?php
 }

+ 132 - 187
tripal_contact/tripal_contact.module

@@ -154,39 +154,46 @@ function tripal_contact_menu() {
  *   An array of themeing functions to register
  *
  */
-function tripal_contact_theme() {
-
-  return array(
+function tripal_contact_theme($existing, $type, $theme, $path) {
+  $core_path = drupal_get_path('module', 'tripal_core');
+  
+  $items = array(
+    'node__chado_contact' => array(
+      'template' => 'node--chado-generic',
+      'render element' => 'node',
+      'base hook' => 'node',
+      'path' => "$core_path/theme",
+    ),
     'tripal_contact_base' => array(
-      'arguments' => array('node' => NULL),
+      'variables' => array('node' => NULL),
       'template' => 'tripal_contact_base',
+      'path' => "$path/theme/tripal_contact",
     ),
     'tripal_contact_properties' => array(
-      'arguments' => array('node' => NULL),
+      'variables' => array('node' => NULL),
       'template' => 'tripal_contact_properties',
+      'path' => "$path/theme/tripal_contact",
     ),
     'tripal_contact_relationships' => array(
-      'arguments' => array('node' => NULL),
+      'variables' => array('node' => NULL),
       'template' => 'tripal_contact_relationships',
+      'path' => "$path/theme/tripal_contact",
     ),
     'tripal_contact_publications' => array(
-      'arguments' => array('node' => NULL),
+      'variables' => array('node' => NULL),
       'template' => 'tripal_contact_publications',
+      'path' => "$path/theme/tripal_contact",
     ),
     'tripal_contact_help' => array(
       'template' => 'tripal_contact_help',
-      'arguments' =>  array(NULL),
-      'path' => drupal_get_path('module', 'tripal_contact') . '/theme'
-    ),
-
-    // Themed Forms
-    'chado_contact_node_form' => array(
-      'arguments' => array('form'),
+      'variables' =>  array(NULL),
+      'path' => "$path/theme",
     ),
   );
+  return $items;
 }
 /**
- * @ingroup tripal_library
+ * @ingroup tripal_contact
  */
 function tripal_contact_block_info() {
 
@@ -205,11 +212,11 @@ function tripal_contact_block_info() {
   return $blocks;
 }
 /**
- * @ingroup tripal_library
+ * @ingroup tripal_contact
  */
 function tripal_contact_block_view($delta = '') {
 
-  if (user_access('access chado_library content') and arg(0) == 'node' and is_numeric(arg(1))) {
+  if (user_access('access chado_contact content') and arg(0) == 'node' and is_numeric(arg(1))) {
     $nid = arg(1);
     $node = node_load($nid);
 
@@ -328,71 +335,35 @@ function chado_contact_node_access($node, $op, $account ) {
  */
 function chado_contact_insert($node) {
 
-  // if a contact_id already exists for this node then it already exists in Chado and
-  // we get here because we are syncing the node.  Therefore, we can skip the insert
-  if ($node->contact_id) {
-    $contact['contact_id'] = $node->contact_id;
-  }
-  else {
-    // we don't want to store the description in the description field as it may be longer than
-    // 255 characters, so we'll use a property to store this value.
-    $values =  array(
-      'name' => $node->title,
-      'description' => '',
-      'type_id' => $node->type_id
+  // remove surrounding white-space on submitted values
+  $node->title          = trim($node->title);
+  $node->description    = trim($node->description);
+  
+  // if there is a contact_id in the $node object then this must be a sync so
+  // we can skip adding the contact as it is already there, although
+  // we do need to proceed with the rest of the insert
+  if (!property_exists($node, 'contact_id')) {
+    // insert and then get the newly inserted contact record
+    $values = array(
+      'name'           => $node->title,
+      'description'    => '',
+      'type_id'        => $node->type_id,
     );
-    $options = array('statement_name' => 'ins_contact_nadety');
-    $contact = tripal_core_chado_insert('contact', $values, $options);
+    $contact = tripal_core_chado_insert('contact', $values);
     if (!$contact) {
-      drupal_set_message(t('Could not add the contact'), 'error');
-      watchdog('tripal_contact','Could not add the contact', array(), WATCHDOG_ERROR);
-      return FALSE;
-    }
-
-    // now add the properties
-    $properties = array(); // stores all of the properties we need to add
-
-    // get the list of properties for easy lookup (without doing lots of database queries
-    $properties_list = array();
-    $sql = "
-      SELECT CVTS.cvterm_id, CVTS.name
-      FROM {cvtermpath} CVTP
-        INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
-        INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
-        INNER JOIN {cv} CV       ON CVTO.cv_id = CV.cv_id
-      WHERE
-        CV.name = 'tripal_contact' AND
-        NOT CVTO.name = 'Contact Type'
-      ORDER BY CVTS.name ASC
-    ";
-    $prop_types = chado_query($sql);
-    while ($prop = $prop_types->fetchObject()) {
-      $properties_list[$prop->cvterm_id] = $prop->name;
-    }
-
-    // get the properties that should be added. Properties are in one of two forms:
-    //  1) prop_value-[type id]-[index]
-    //  2) new_value-[type id]-[index]
-    //  3) new_id, new_value
-    foreach ($node as $name => $value) {
-      if (preg_match('/^new_value-(\d+)-(\d+)/', $name, $matches)) {
-        $type_id = $matches[1];
-        $index = $matches[2];
-        $name = $properties_list[$type_id];
-        $properties[$name][$index] = trim($value);
-      }
-    }
-    if ($node->new_id and $node->new_value) {
-      $type_id = $node->new_id;
-      $index = count($properties[$name]);
-      $name = $properties_list[$type_id];
-      $properties[$name][$index] = trim($node->new_value);
+      drupal_set_message(t('Unable to add contact.', 'warning'));
+      watchdog('tripal_contact', 'Insert contact: Unable to create contact where values: %values',
+      array('%values' => print_r($values, TRUE)), WATCHDOG_ERROR);
+      return;
     }
+    $contact_id = $contact['contact_id'];
+    
     // now add in the properties
+    $properties = tripal_core_properties_form_retreive($node, 'tripal_contact');
     foreach ($properties as $property => $elements) {
       foreach ($elements as $rank => $value) {
-
-        $status = tripal_contact_insert_property($contact['contact_id'], $property, $value, FALSE);
+    
+        $status = tripal_contact_insert_property($contact_id, $property, $value, FALSE);
         if (!$status) {
           drupal_set_message("Error cannot add property: $property", "error");
           watchdog('t_contact', "Error cannot add property: %prop",
@@ -400,29 +371,23 @@ function chado_contact_insert($node) {
         }
       }
     }
-  }
-
-  // add the record to the chado_contact table in Drupal
-  if ($contact) {
-
-    // add the description property
-    tripal_contact_insert_property($contact['contact_id'], 'contact_description',
-      $node->description, TRUE);
-
-    // make sure the entry for this contact doesn't already exist in the chado_contact table
-    // if it doesn't exist then we want to add it.
-    $contact_id = chado_get_id_for_node('contact', $node->nid) ;
-    if (!$contact_id) {
-       // next add the item to the drupal table
-      $sql = "INSERT INTO {chado_contact} (nid, vid, contact_id) ".
-             "VALUES (:nid, :vid, :contact_id)";
-      db_query($sql, array(':nid'=> $node->nid, ':vid' => $node->vid, ':contact_id' => $contact['contact_id']));
-    }
+    
+    // add in the description as a separate property
+    tripal_contact_insert_property($contact_id, 'contact_description', $node->description, FALSE);
   }
   else {
-    drupal_set_message(t('Unable to add contact.', 'warning'));
-    watchdog('tripal_contact', 'Insert contact: Unable to create contact where values: %values',
-      array('%values' => print_r($values, TRUE)), WATCHDOG_WARNING);
+    $contact_id = $node->contact_id;
+  }
+  
+  // Make sure the entry for this contact doesn't already exist in the
+  // chado_contact table if it doesn't exist then we want to add it.
+  $check_org_id = chado_get_id_for_node('contact', $node->nid);
+  if (!$check_org_id) {
+    $record = new stdClass();
+    $record->nid = $node->nid;
+    $record->vid = $node->vid;
+    $record->contact_id = $contact_id;
+    drupal_write_record('chado_contact', $record);
   }
   return TRUE;
 }
@@ -441,24 +406,15 @@ function chado_contact_insert($node) {
  * @ingroup tripal_contact
  */
 function chado_contact_update($node) {
-  if ($node->revision) {
-    // there is no way to handle revisions in Chado but leave
-    // this here just to make not we've addressed it.
-  }
+  // remove surrounding white-space on submitted values
+  $node->title          = trim($node->title);
+  $node->description    = trim($node->description);
 
   $contact_id = chado_get_id_for_node('contact', $node->nid) ;
 
-  // check to see if this contact name doens't already exists.
-  $sql = "SELECT contact_id FROM {contact} WHERE NOT contact_id = :contact_id AND name = :name";
-  $contact = chado_query($sql, array(':contact_id' => $contact_id, ':name' => $node->contact_name))->fetchObject();
-  if ($contact) {
-    drupal_set_message(t('A contact with this name already exists. Cannot perform update.'), 'warning');
-    return;
-  }
-
   // update the contact record
   $match = array(
-     'contact_id' => $contact_id,
+    'contact_id' => $contact_id,
   );
   $values = array(
     'name' => $node->title,
@@ -472,56 +428,11 @@ function chado_contact_update($node) {
     return;
   }
 
-  // now update the properties
-  $properties = array(); // stores all of the properties we need to add
-
-  // get the list of properties for easy lookup (without doing lots of database queries
-  $properties_list = array();
-  $sql = "
-    SELECT CVTS.cvterm_id, CVTS.name
-    FROM {cvtermpath} CVTP
-      INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
-      INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
-      INNER JOIN {cv} CV       ON CVTO.cv_id = CV.cv_id
-    WHERE
-      CV.name = 'tripal_contact' AND
-      NOT CVTO.name = 'Contact Type'
-    ORDER BY CVTS.name ASC
-  ";
-  $prop_types = chado_query($sql);
-  while ($prop = $prop_types->fetchObject()) {
-    $properties_list[$prop->cvterm_id] = $prop->name;
-  }
-
-  // get the properties that should be added. Properties are in one of three forms:
-  //  1) prop_value-[type id]-[index]
-  //  2) new_value-[type id]-[index]
-  //  3) new_id, new_value
-  //  dpm($node);
-  foreach ($node as $key => $value) {
-    if (preg_match('/^prop_value-(\d+)-(\d+)/', $key, $matches)) {
-      $type_id = $matches[1];
-      $index = $matches[2];
-      $name = $properties_list[$type_id];
-      $properties[$name][$index] = trim($value);
-    }
-    if (preg_match('/^new_value-(\d+)-(\d+)/', $key, $matches)) {
-      $type_id = $matches[1];
-      $index = $matches[2];
-      $name = $properties_list[$type_id];
-      $properties[$name][$index] = trim($value);
-    }
-  }
-  if ($node->new_id and $node->new_value) {
-    $type_id = $node->new_id;
-    $name = $properties_list[$type_id];
-    $index = count($properties[$name]);
-    $properties[$name][$index] = trim($node->new_value);
-  }
-
   // now add in the properties by first removing any the contact
   // already has and adding the ones we have
   tripal_core_chado_delete('contactprop', array('contact_id' => $contact_id));
+  $properties = tripal_core_properties_form_retreive($node, 'tripal_contact');
+  
   foreach ($properties as $property => $elements) {
     foreach ($elements as $rank => $value) {
       $status = tripal_contact_insert_property($contact_id, $property, $value, FALSE);
@@ -533,6 +444,7 @@ function chado_contact_update($node) {
     }
   }
 
+  // add in the description as a separate property
   tripal_contact_update_property($contact_id, 'contact_description', $node->description, 1);
 }
 
@@ -548,35 +460,37 @@ function chado_contact_update($node) {
  *   The node with the information to be loaded into the database
  *
  */
-function chado_contact_load($node) {
-  // get the feature details from chado
-  $contact_id = chado_get_id_for_node('contact', $node->nid);
-
-  $values = array('contact_id' => $contact_id);
-  $contact = tripal_core_generate_chado_var('contact', $values);
-
-  // get the contact description and replace the contact.description field with this one
-  $values = array(
-    'contact_id' => $contact->contact_id,
-    'type_id' => array(
-      'name' => 'contact_description',
-    ),
-  );
-  $options = array(
-    'return_array' => 1,
-    'include_fk' => array('type_id' => 1),
-  );
-  $description = tripal_core_generate_chado_var('contactprop', $values, $options);
-  if (count($description) == 1) {
-    $description = tripal_core_expand_chado_vars($description, 'field', 'contactprop.value');
-    $contact->description = $description[0]->value;
+function chado_contact_load($nodes) {
+  
+  foreach ($nodes as $nid => $node) {
+    // find the contact and add in the details
+    $contact_id = chado_get_id_for_node('contact', $nid);
+  
+    // get the contact 
+    $values = array('contact_id' => $contact_id);
+    $contact = tripal_core_generate_chado_var('contact', $values);
+    
+    // get the contact description from the contactprop table and replace 
+    // the contact.description field with this one (we don't use the contact.description
+    // field because it is only 255 characters (too small)).
+    $values = array(
+      'contact_id' => $contact->contact_id,
+      'type_id' => array(
+        'name' => 'contact_description',
+      ),
+    );
+    $options = array(
+      'return_array' => 1,
+      'include_fk' => array('type_id' => 1),
+    );
+    $description = tripal_core_generate_chado_var('contactprop', $values, $options);
+    if (count($description) == 1) {
+      $description = tripal_core_expand_chado_vars($description, 'field', 'contactprop.value');
+      $contact->description = $description[0]->value;
+    }
+    
+    $nodes[$nid]->contact = $contact;
   }
-
-
-  $additions = new stdClass();
-  $additions->contact = $contact;
-  return $additions;
-
 }
 
 /**
@@ -628,7 +542,7 @@ function tripal_contact_preprocess_tripal_contact_relationships(&$variables) {
   // query, therefore we will manually perform the query
   $sql = "
     SELECT C.name, C.contact_id, CP.nid, CVT.name as rel_type
-    FROM contact_relationship PR
+    FROM {contact_relationship} PR
       INNER JOIN {contact} C              ON PR.object_id  = C.contact_id
       INNER JOIN {cvterm} CVT             ON PR.type_id    = CVT.cvterm_id
       LEFT  JOIN public.chado_contact CP  ON C.contact_id  = CP.contact_id
@@ -637,7 +551,7 @@ function tripal_contact_preprocess_tripal_contact_relationships(&$variables) {
   $as_subject = chado_query($sql, array(':contact_id' => $contact->contact_id));
   $sql = "
     SELECT C.name, C.contact_id, CP.nid, CVT.name as rel_type
-    FROM contact_relationship PR
+    FROM {contact_relationship} PR
       INNER JOIN {contact} C              ON PR.subject_id = C.contact_id
       INNER JOIN {cvterm} CVT             ON PR.type_id    = CVT.cvterm_id
       LEFT  JOIN public.chado_contact CP  ON C.contact_id  = CP.contact_id
@@ -697,4 +611,35 @@ function tripal_contact_form_alter(&$form, &$form_state, $form_id) {
   if ($form_id == "chado_contact_node_form") {
     $form['actions']['preview']['#access'] = FALSE;
   }
+}
+
+/**
+ *
+ * @ingroup tripal_feature
+ */
+function tripal_contact_node_view($node, $view_mode, $langcode) {
+  switch ($node->type) {
+    case 'chado_contact':
+      // Show feature browser and counts
+      if ($view_mode == 'full') {
+        $node->content['tripal_contact_base'] = array(
+          '#value' => theme('tripal_contact_base', array('node' => $node)),
+        );
+        $node->content['tripal_contact_properties'] = array(
+          '#value' => theme('tripal_contact_properties', array('node' => $node)),
+        );
+        $node->content['tripal_contact_publications'] = array(
+          '#value' => theme('tripal_contact_publications', array('node' => $node)),
+        );
+        $node->content['tripal_contact_relationships'] = array(
+          '#value' => theme('tripal_contact_relationships', array('node' => $node)),
+        );
+      }
+      if ($view_mode == 'teaser') {
+        $node->content['tripal_contact_teaser'] = array(
+          '#value' => theme('tripal_contact_teaser', array('node' => $node)),
+        );
+      }
+      break;
+  }
 }

+ 0 - 140
tripal_core/api/tripal_core_ahah.api.inc

@@ -1,140 +0,0 @@
-<?php
-/**
- * @file
- * The Tripal AJAX/AHAH API
- *
- * This file provides the API to help Tripal modules more easily use 
- * Drupal's AHAH functionality with forms.
- */
-
-/**
- * @defgroup tripal_ahah_api AHAH API
- * @ingroup tripal_core_api
- * @{
- * Provides an application programming interface (API) for improved 
- * support of Drupal's AHAH functionality.
- *
- * @} 
- */
-
-/**
- * This function should be called to initialize the page
- * for use of AHAH elements in a form.
- * 
- * See http://tripal.info/documentation/ahah_api for example usage
- *
- * @returns
- *   nothing
- *
- * @ingroup tripal_ahah_api
- */
-function tripal_core_ahah_init_form() {
-  // If form elements have autocomplete elements returned in
-  // an ahah call they won't work because the following JS file
-  // doesn't get included. If we include it first before any 
-  // ahah calls it will be there when we need it.
-  drupal_add_js('misc/autocomplete.js');
-}
-
-/**
- * This function simply gets the posted form ID, builds the form
- * and retrieves the specified element.  This function should be
- * called in an AHAH callback function to retrieve the form
- *
- * See http://tripal.info/documentation/ahah_api for example usage
- *
- * @returns
- *   A Drupal form array.  If no form is available then FALSE is returned
- *
- * @ingroup tripal_ahah_api
- */
-function tripal_core_ahah_prepare_form(&$form_state = array()) {
-  
-  // Retrieve the form from the cache
-  $form_state['storage'] = NULL;
-  $form_build_id = filter_xss($_POST['form_build_id']);
-  if (!$form_build_id) {
-    return FALSE;
-  }
-  $form = form_get_cache($form_build_id, $form_state);
-
-  // Preparing to process the form
-  $args = $form['#parameters']; 
-  if (!is_array($args)) {
-    // if there is no form #parameters as an array then the form was not built property
-    return FALSE;
-  }  
-  $form_id = array_shift($args);
-  $form_state['post'] = $form['#post'] = $_POST;
-  $form['#programmed'] = $form['#redirect'] = FALSE;
-
-  // we don't want to submit the form or have required fields validated on
-  // an ahah callback.
-  $form_state['submitted'] = TRUE;
-  $form['#validate'] = NULL;
-  $form['#submit'] = NULL;
-  $form_state['submit_handlers'] = NULL;
-  $form_state['validate_handlers'] = NULL;
-  tripal_core_ahah_form_element_disable_validation($form);
-
-  // Sets the form_state so that the validate and submit handlers can tell
-  // when the form is submitted via AHAH
-  $form_state['ahah_submission'] = TRUE;
-
-  // Process the form with drupal_process_form. This function calls the submit
-  // handlers, which put whatever was worthy of keeping into $form_state.
-  drupal_process_form($form_id, $form, $form_state);
-  
-  // You call drupal_rebuild_form which destroys $_POST.
-  // The form generator function is called and creates the form again but since
-  // it knows to use $form_state, the form will be different.
-  // The new form gets cached and processed again, but because $_POST is
-  // destroyed, the submit handlers will not be called again.
-  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
-
-  return $form;
-}
-
-/**
- * This function rebuilds the $settings array needed by the 
- * javascript that handles AHAH data returns.  This function should be
- * called in an AHAH callback function prior to returning.
- *
- * See http://tripal.info/documentation/ahah_api for example usage
- *
- * @returns
- *   an associative array with an 'ahah' key.
- *
- * @ingroup tripal_ahah_api
- */
-function tripal_core_ahah_bind_events() {
-  
-  // Get the JS settings so we can merge them.
-  $options = array('scope' => 'header'); 
-  $javascript = drupal_add_js(NULL, $options);
-  $settings = call_user_func_array('array_merge_recursive', $javascript['setting']);
-  
-  return array('ahah' => $settings['ahah']);
-}
-
-/**
- * This function is a helperfunction of the 
- * tripal_core_ahah_prepare_form() function.  It simply
- * disables field validations for all fields (recursively) so that
- * when the form is rebuilt it doesn't try to validate and submit the form.
- *
- * See http://tripal.info/documentation/ahah_api for example usage
- *
- * @returns
- *   nothing 
- *
- * @ingroup tripal_ahah_api
- */
-function tripal_core_ahah_form_element_disable_validation(&$form) {
-  // --START code borrowed from ahah_helper module
-  foreach (element_children($form) as $child) {
-    $form[$child]['#validated'] = TRUE;
-    tripal_core_ahah_form_element_disable_validation($form[$child]);
-  }
-  // --END code borrowed from ahah_helper module
-}

+ 1 - 329
tripal_core/api/tripal_core_chado.api.inc

@@ -1482,7 +1482,7 @@ function tripal_core_generate_chado_var($table, $values, $base_options = array()
 
   // Expandable fields without value needed for criteria--------------------------------------------
   $all->expandable_fields = array();
-  if ($table_desc['referring_tables']) {
+  if (array_key_exists('referring_tables', $table_desc)) {
     $all->expandable_tables = $table_desc['referring_tables'];
   }
   else {
@@ -2153,334 +2153,6 @@ function chado_get_node_id($table, $id) {
   return db_query($sql, array(":" . $table . "_id" => $id))->fetchField();
 }
 
-/**
- * Retrieve a property for a given base table record
- *
- * @param $basetable
- *   The base table for which the property should be retrieved. Thus to retrieve a property
- *   for a feature the basetable=feature and property is retrieved from featureprop
- * @param $record_id
- *   The foriegn key field of the base table. This should be in integer.
- * @param $property
- *   The cvterm name describing the type of properties to be retrieved
- * @param $cv_name
- *   The name of the cv that the above cvterm is part of
- *
- * @return
- *   An array in the same format as that generated by the function
- *   tripal_core_generate_chado_var().  If only one record is returned it
- *   is a single object.  If more than one record is returned then it is an array
- *   of objects
- *
- * @ingroup tripal_chado_api
- */
-function tripal_core_get_property($basetable, $record_id, $property, $cv_name) {
-  // get the foreign key for this property table
-  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
-  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
-
-  // construct the array of values to be selected
-  $values = array(
-    $fkcol => $record_id,
-    'type_id' => array(
-      'cv_id' => array(
-        'name' => $cv_name,
-      ),
-      'name' => $property,
-      'is_obsolete' => 0
-    ),
-  );
-  $results = tripal_core_generate_chado_var($basetable . 'prop', $values);
-  if ($results) {
-    $results = tripal_core_expand_chado_vars($results, 'field', $basetable . 'prop.value');
-  }
-
-  return $results;
-}
-
-/**
- * Insert a property for a given base table.  By default if the property already
- * exists a new property is added with the next available rank.  If
- * $update_if_present argument is specified then the record will be updated if it
- * exists rather than adding a new property.
- *
- * @param $basetable
- *   The base table for which the property should be inserted. Thus to insert a property
- *   for a feature the basetable=feature and property is inserted into featureprop
- * @param $record_id
- *   The foriegn key value of the base table. This should be in integer.
- * @param $property
- *   The cvterm name describing the type of properties to be inserted
- * @param $cv_name
- *   The name of the cv that the above cvterm is part of
- * @param $value
- *   The value of the property to be inserted (can be empty)
- * @param $update_if_present
- *   A boolean indicating whether an existing record should be updated. If the
- *   property already exists and this value is not specified or is zero then
- *   a new property will be added with the next largest rank.
- *
- * @return
- *   Return True on Insert/Update and False otherwise
- *
- * @ingroup tripal_chado_api
- */
-function tripal_core_insert_property($basetable, $record_id, $property,
-$cv_name, $value, $update_if_present = 0) {
-
-  // first see if the property already exists, if the user want's to update
-  // then we can do that, but otherwise we want to increment the rank and
-  // insert
-  $props = tripal_core_get_property($basetable, $record_id, $property, $cv_name);
-  if (!is_array($props) and $props) {
-    $props = array($props);
-  }
-
-  $rank = 0;
-  if (count($props) > 0) {
-    if ($update_if_present) {
-      return tripal_core_update_property($basetable, $record_id, $property, $cv_name, $value);
-    }
-    else {
-      // iterate through the properties returned and check to see if the
-      // property with this value already exists if not, get the largest rank
-      // and insert the same property but with this new value
-      foreach ($props as $p) {
-        if ($p->rank > $rank) {
-          $rank = $p->rank;
-        }
-        if (strcmp($p->value, $value) == 0) {
-          return TRUE;
-        }
-      }
-      // now add 1 to the rank
-      $rank++;
-    }
-  }
-
-  // make sure the cvterm exists.  Otherwise we'll get an error with
-  // prepared statements not matching
-  $values = array(
-    'cv_id' => array(
-      'name' => $cv_name,
-    ),
-    'name' => $property,
-  );
-
-  $options = array();
-  $term = tripal_core_chado_select('cvterm', array('cvterm_id'), $values, $options);
-  if (!$term or count($term) == 0) {
-    watchdog('tripal_core', "Cannot find property '%prop_name'.",
-      array('%prop_name' => $property), WATCHDOG_ERROR);
-    return FALSE;
-  }
-
-  // get the foreign key for this property table
-  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
-  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
-
-  // construct the array of values to be inserted
-  $values = array(
-    $fkcol => $record_id,
-    'type_id' => array(
-      'cv_id' => array(
-        'name' => $cv_name,
-      ),
-      'name' => $property,
-    ),
-    'value' => $value,
-    'rank' => $rank,
-  );
-
-  $options = array();
-  $result = tripal_core_chado_insert($basetable . 'prop', $values, $options);
-  return $result;
-}
-
-/**
- * Update a property for a given base table record and property name.  This
- * function should be used only if one record of the property will be present.
- * If the property name can have multiple entries (with increasing rank) then
- * use the function named tripal_core_update_property_by_id
- *
- * @param $basetable
- *   The base table for which the property should be updated. The property table
- *   is constructed using  a combination of the base table name and the suffix
- *   'prop' (e.g. basetable = feature then property tabie is featureprop).
- * @param $record_id
- *   The foreign key of the basetable to update a property for. This should be in integer.
- *   For example, if the basetable is 'feature' then the $record_id should be the feature_id
- * @param $property
- *   The cvterm name of property to be updated
- * @param $cv_name
- *   The name of the cv that the above cvterm is part of
- * @param $value
- *   The value of the property to be inserted (can be empty)
- * @param $insert_if_missing
- *   A boolean indicating whether a record should be inserted if one doesn't exist to update
- *
- * Note: The property to be updated is select via the unique combination of $record_id and
- * $property and then it is updated with the supplied value
- *
- * @return
- *   Return True on Update/Insert and False otherwise
- *
- * @ingroup tripal_chado_api
- */
-function tripal_core_update_property($basetable, $record_id, $property,
-    $cv_name, $value, $insert_if_missing = 0) {
-
-  // first see if the property is missing (we can't update a missing property
-  $prop = tripal_core_get_property($basetable, $record_id, $property, $cv_name);
-  if (count($prop)==0) {
-    if ($insert_if_missing) {
-      return tripal_core_insert_property($basetable, $record_id, $property, $cv_name, $value);
-    }
-    else {
-      return FALSE;
-    }
-  }
-
-  // get the foreign key for this property table
-  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
-  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
-
-  // construct the array that will match the exact record to update
-  $match = array(
-  $fkcol => $record_id,
-    'type_id' => array(
-      'cv_id' => array(
-        'name' => $cv_name,
-      ),
-      'name' => $property,
-    ),
-  );
-
-  // construct the array of values to be updated
-  $values = array(
-    'value' => $value,
-  );
-
-  return tripal_core_chado_update($basetable . 'prop', $match, $values);
-}
-
-/**
- * Update a property for a given base table record.  This function should be
- * used if multiple records of the same property will be present. Also, use this
- * function to change the property name of an existing property.
- *
- * @param $basetable
- *   The base table for which the property should be updated. The property table
- *   is constructed using  a combination of the base table name and the suffix
- *   'prop' (e.g. basetable = feature then property tabie is featureprop).
- * @param $record_id
- *   The primary key of the base table. This should be in integer.
- *   For example, if the basetable is 'feature' then the $record_id should be the featureprop_id
- * @param $property
- *   The cvterm name of property to be updated
- * @param $cv_name
- *   The name of the cv that the above cvterm is part of
- * @param $value
- *   The value of the property to be inserted (can be empty)
- *
- * @return
- *   Return True on Update/Insert and False otherwise
- *
- * @ingroup tripal_chado_api
- */
-function tripal_core_update_property_by_id($basetable, $record_id, $property,
-$cv_name, $value) {
-
-  // get the primary key for this property table
-  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
-  $pkcol = $table_desc['primary key'][0];
-
-  // construct the array that will match the exact record to update
-  $match = array(
-  $pkcol => $record_id,
-  );
-
-  // construct the array of values to be updated
-  $values = array(
-    'type_id' => array(
-      'cv_id' => array(
-        'name' => $cv_name,
-  ),
-      'name' => $property,
-  ),
-    'value' => $value,
-  );
-
-  return tripal_core_chado_update($basetable . 'prop', $match, $values);
-}
-
-/**
- * Deletes a property for a given base table record using the property name
- *
- * @param $basetable
- *   The base table for which the property should be deleted. Thus to deleted a property
- *   for a feature the basetable=feature and property is deleted from featureprop
- * @param $record_id
- *   The primary key of the basetable to delete a property for. This should be in integer.
- * @param $property
- *   The cvterm name describing the type of property to be deleted
- * @param $cv_name
- *   The name of the cv that the above cvterm is part of
- *
- * Note: The property to be deleted is select via the unique combination of $record_id and $property
- *
- * @return
- *   Return True on Delete and False otherwise
- *
- * @ingroup tripal_chado_api
- */
-function tripal_core_delete_property($basetable, $record_id, $property, $cv_name) {
-
-  // get the foreign key for this property table
-  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
-  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
-
-  // construct the array that will match the exact record to update
-  $match = array(
-  $fkcol => $record_id,
-    'type_id' => array(
-      'cv_id' => array(
-        'name' => $cv_name,
-  ),
-      'name' => $property,
-  ),
-  );
-
-  return tripal_core_chado_delete($basetable . 'prop', $match);
-}
-
-/**
- * Deletes a property using the property ID
- *
- * @param $basetable
- *   The base table for which the property should be deleted. Thus to deleted a property
- *   for a feature the basetable=feature and property is deleted from featureprop
- * @param $record_id
- *   The primary key of the basetable to delete a property for. This should be in integer.
- *
- * @return
- *   Return True on Delete and False otherwise
- *
- * @ingroup tripal_chado_api
- */
-function tripal_core_delete_property_by_id($basetable, $record_id) {
-
-  // get the foreign key for this property table
-  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
-  $pkcol = $table_desc['primary key'][0];
-
-  // construct the array that will match the exact record to update
-  $match = array(
-  $pkcol => $record_id,
-  );
-
-  return tripal_core_chado_delete($basetable . 'prop', $match);
-}
 
 
 /**

+ 930 - 0
tripal_core/api/tripal_core_properties.api.inc

@@ -0,0 +1,930 @@
+<?php
+/**
+ * Retrieve a property for a given base table record
+ *
+ * @param $basetable
+ *   The base table for which the property should be retrieved. Thus to retrieve a property
+ *   for a feature the basetable=feature and property is retrieved from featureprop
+ * @param $record_id
+ *   The foriegn key field of the base table. This should be in integer.
+ * @param $property
+ *   The cvterm name describing the type of properties to be retrieved
+ * @param $cv_name
+ *   The name of the cv that the above cvterm is part of
+ *
+ * @return
+ *   An array in the same format as that generated by the function
+ *   tripal_core_generate_chado_var().  If only one record is returned it
+ *   is a single object.  If more than one record is returned then it is an array
+ *   of objects
+ *
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_get_property($basetable, $record_id, $property, $cv_name) {
+  // get the foreign key for this property table
+  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
+  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
+
+  // construct the array of values to be selected
+  $values = array(
+    $fkcol => $record_id,
+    'type_id' => array(
+      'cv_id' => array(
+        'name' => $cv_name,
+      ),
+      'name' => $property,
+      'is_obsolete' => 0
+    ),
+  );
+  $results = tripal_core_generate_chado_var($basetable . 'prop', $values);
+  if ($results) {
+    $results = tripal_core_expand_chado_vars($results, 'field', $basetable . 'prop.value');
+  }
+
+  return $results;
+}
+
+/**
+ * Insert a property for a given base table.  By default if the property already
+ * exists a new property is added with the next available rank.  If
+ * $update_if_present argument is specified then the record will be updated if it
+ * exists rather than adding a new property.
+ *
+ * @param $basetable
+ *   The base table for which the property should be inserted. Thus to insert a property
+ *   for a feature the basetable=feature and property is inserted into featureprop
+ * @param $record_id
+ *   The foriegn key value of the base table. This should be in integer.
+ * @param $property
+ *   The cvterm name describing the type of properties to be inserted
+ * @param $cv_name
+ *   The name of the cv that the above cvterm is part of
+ * @param $value
+ *   The value of the property to be inserted (can be empty)
+ * @param $update_if_present
+ *   A boolean indicating whether an existing record should be updated. If the
+ *   property already exists and this value is not specified or is zero then
+ *   a new property will be added with the next largest rank.
+ *
+ * @return
+ *   Return True on Insert/Update and False otherwise
+ *
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_insert_property($basetable, $record_id, $property,
+$cv_name, $value, $update_if_present = 0) {
+
+  // first see if the property already exists, if the user want's to update
+  // then we can do that, but otherwise we want to increment the rank and
+  // insert
+  $props = tripal_core_get_property($basetable, $record_id, $property, $cv_name);
+  if (!is_array($props) and $props) {
+    $props = array($props);
+  }
+
+  $rank = 0;
+  if (count($props) > 0) {
+    if ($update_if_present) {
+      return tripal_core_update_property($basetable, $record_id, $property, $cv_name, $value);
+    }
+    else {
+      // iterate through the properties returned and check to see if the
+      // property with this value already exists if not, get the largest rank
+      // and insert the same property but with this new value
+      foreach ($props as $p) {
+        if ($p->rank > $rank) {
+          $rank = $p->rank;
+        }
+        if (strcmp($p->value, $value) == 0) {
+          return TRUE;
+        }
+      }
+      // now add 1 to the rank
+      $rank++;
+    }
+  }
+
+  // make sure the cvterm exists.  Otherwise we'll get an error with
+  // prepared statements not matching
+  $values = array(
+    'cv_id' => array(
+      'name' => $cv_name,
+    ),
+    'name' => $property,
+  );
+
+  $options = array();
+  $term = tripal_core_chado_select('cvterm', array('cvterm_id'), $values, $options);
+  if (!$term or count($term) == 0) {
+    watchdog('tripal_core', "Cannot find property '%prop_name'.",
+    array('%prop_name' => $property), WATCHDOG_ERROR);
+    return FALSE;
+  }
+
+  // get the foreign key for this property table
+  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
+  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
+
+  // construct the array of values to be inserted
+  $values = array(
+    $fkcol => $record_id,
+    'type_id' => array(
+      'cv_id' => array(
+        'name' => $cv_name,
+      ),
+      'name' => $property,
+    ),
+    'value' => $value,
+    'rank' => $rank,
+  );
+
+  $options = array();
+  $result = tripal_core_chado_insert($basetable . 'prop', $values, $options);
+  return $result;
+}
+
+/**
+ * Update a property for a given base table record and property name.  This
+ * function should be used only if one record of the property will be present.
+ * If the property name can have multiple entries (with increasing rank) then
+ * use the function named tripal_core_update_property_by_id
+ *
+ * @param $basetable
+ *   The base table for which the property should be updated. The property table
+ *   is constructed using  a combination of the base table name and the suffix
+ *   'prop' (e.g. basetable = feature then property tabie is featureprop).
+ * @param $record_id
+ *   The foreign key of the basetable to update a property for. This should be in integer.
+ *   For example, if the basetable is 'feature' then the $record_id should be the feature_id
+ * @param $property
+ *   The cvterm name of property to be updated
+ * @param $cv_name
+ *   The name of the cv that the above cvterm is part of
+ * @param $value
+ *   The value of the property to be inserted (can be empty)
+ * @param $insert_if_missing
+ *   A boolean indicating whether a record should be inserted if one doesn't exist to update
+ *
+ * Note: The property to be updated is select via the unique combination of $record_id and
+ * $property and then it is updated with the supplied value
+ *
+ * @return
+ *   Return True on Update/Insert and False otherwise
+ *
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_update_property($basetable, $record_id, $property,
+$cv_name, $value, $insert_if_missing = 0) {
+
+  // first see if the property is missing (we can't update a missing property
+  $prop = tripal_core_get_property($basetable, $record_id, $property, $cv_name);
+  if (count($prop)==0) {
+    if ($insert_if_missing) {
+      return tripal_core_insert_property($basetable, $record_id, $property, $cv_name, $value);
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  // get the foreign key for this property table
+  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
+  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
+
+  // construct the array that will match the exact record to update
+  $match = array(
+    $fkcol => $record_id,
+    'type_id' => array(
+      'cv_id' => array(
+        'name' => $cv_name,
+      ),
+      'name' => $property,
+    ),
+  );
+
+  // construct the array of values to be updated
+  $values = array(
+    'value' => $value,
+  );
+
+  return tripal_core_chado_update($basetable . 'prop', $match, $values);
+}
+
+/**
+ * Update a property for a given base table record.  This function should be
+ * used if multiple records of the same property will be present. Also, use this
+ * function to change the property name of an existing property.
+ *
+ * @param $basetable
+ *   The base table for which the property should be updated. The property table
+ *   is constructed using  a combination of the base table name and the suffix
+ *   'prop' (e.g. basetable = feature then property tabie is featureprop).
+ * @param $record_id
+ *   The primary key of the base table. This should be in integer.
+ *   For example, if the basetable is 'feature' then the $record_id should be the featureprop_id
+ * @param $property
+ *   The cvterm name of property to be updated
+ * @param $cv_name
+ *   The name of the cv that the above cvterm is part of
+ * @param $value
+ *   The value of the property to be inserted (can be empty)
+ *
+ * @return
+ *   Return True on Update/Insert and False otherwise
+ *
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_update_property_by_id($basetable, $record_id, $property,
+$cv_name, $value) {
+
+  // get the primary key for this property table
+  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
+  $pkcol = $table_desc['primary key'][0];
+
+  // construct the array that will match the exact record to update
+  $match = array(
+    $pkcol => $record_id,
+  );
+
+  // construct the array of values to be updated
+  $values = array(
+    'type_id' => array(
+      'cv_id' => array(
+        'name' => $cv_name,
+      ),
+      'name' => $property,
+    ),
+    'value' => $value,
+  );
+
+  return tripal_core_chado_update($basetable . 'prop', $match, $values);
+}
+
+/**
+ * Deletes a property for a given base table record using the property name
+ *
+ * @param $basetable
+ *   The base table for which the property should be deleted. Thus to deleted a property
+ *   for a feature the basetable=feature and property is deleted from featureprop
+ * @param $record_id
+ *   The primary key of the basetable to delete a property for. This should be in integer.
+ * @param $property
+ *   The cvterm name describing the type of property to be deleted
+ * @param $cv_name
+ *   The name of the cv that the above cvterm is part of
+ *
+ * Note: The property to be deleted is select via the unique combination of $record_id and $property
+ *
+ * @return
+ *   Return True on Delete and False otherwise
+ *
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_delete_property($basetable, $record_id, $property, $cv_name) {
+
+  // get the foreign key for this property table
+  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
+  $fkcol = key($table_desc['foreign keys'][$basetable]['columns']);
+
+  // construct the array that will match the exact record to update
+  $match = array(
+    $fkcol => $record_id,
+    'type_id' => array(
+      'cv_id' => array(
+        'name' => $cv_name,
+      ),
+      'name' => $property,
+    ),
+  );
+
+  return tripal_core_chado_delete($basetable . 'prop', $match);
+}
+
+/**
+ * Deletes a property using the property ID
+ *
+ * @param $basetable
+ *   The base table for which the property should be deleted. Thus to deleted a property
+ *   for a feature the basetable=feature and property is deleted from featureprop
+ * @param $record_id
+ *   The primary key of the basetable to delete a property for. This should be in integer.
+ *
+ * @return
+ *   Return True on Delete and False otherwise
+ *
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_delete_property_by_id($basetable, $record_id) {
+
+  // get the foreign key for this property table
+  $table_desc = tripal_core_get_chado_table_schema($basetable . 'prop');
+  $pkcol = $table_desc['primary key'][0];
+
+  // construct the array that will match the exact record to update
+  $match = array(
+    $pkcol => $record_id,
+  );
+
+  return tripal_core_chado_delete($basetable . 'prop', $match);
+}
+
+/**
+ * This function is a wrapper for adding fields to an existing form for managing properties.  
+ * Many of the chado tables have a corresponding 'prop' table (e.g. analysisprop, contactprop,
+ * organismprop, etc) and those prop tables all have the same schema.  Use this function
+ * to add all the necessary components to a form for allowing the user to add/edit properties to
+ * a given record.  To retreive properties in hook_insert or hook_update of a node form use
+ * use the function tripal_core_properties_form_retreive().
+ * 
+ * @param $form
+ *   The Drupal form array into which the properties elements will be added
+ * @param $form_state
+ *   The corresponding form_state array for the form
+ * @param $prop_table
+ *   The name of the property table (e.g. anlaysisprop, contactprop)
+ * @param $id_field
+ *   The name of the ID field in the property table (e.g. analysis_id, contact_id)
+ * @param $cv_name
+ *   The name of the controlled vocabulary that these properties are derived from
+ * @param $available_props
+ *   An array of properties to inclde in the properties drop down.  This array should
+ *   have cvterm_id's as the key and the cvterm name as the value
+ * @param $id
+ *   The current base table ID.  For example, if the property table is analysisprop then the
+ *   value should be that of the analysis_id.  If the property table is contactprop then the
+ *   value should be that of the contact_id.  This is the record from which currently assigned
+ *   properties will be retrieved.
+ * @param $exclude
+ *   An array of cvterms to exclude when retreiving terms already saved in the database.
+ *   Use this array when properties are present but should be handled elsewhere.
+ *   For example, for contacts, the description field is stored as a property because 
+ *   the actual field is only 255 characters. The 'contact_description' therefore should
+ *   not be shown in the list of properties, even if present, because it is handled by
+ *   a different form element.
+ * @param $instructions
+ *   An additional set of instructions for the form properties.
+ *   
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_properties_form(&$form, &$form_state, $prop_table, $id_field, $cv_name,
+    $available_props, $id = NULL, $exclude = array(), $instructions = '') {
+  
+  $d_removed      = array(); // lists removed properties
+  $num_new        = 0;  // the number of new rows
+  
+  // if we are re constructing the form from a failed validation or ajax callback
+  // then use the $form_state['values'] values
+  if (array_key_exists('values', $form_state)) {
+    $d_removed      = $form_state['values']['removed'];
+    $num_new        = $form_state['values']['num_new'] ? $form_state['values']['num_new'] : 0;
+  }
+  // if we are re building the form from after submission (from ajax call) then
+  // the values are in the $form_state['input'] array
+  if (array_key_exists('input', $form_state) and !empty($form_state['input'])) {
+    $d_removed      = $form_state['input']['removed'];
+    $num_new        = $form_state['input']['num_new'] ? $form_state['input']['num_new'] : 0;
+  }
+  
+  $form['properties'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Additional Details'),
+    '#description' => t('You may add additional properties by
+      selecting a property type from the dropdown and adding text.  You may add
+      as many properties as desired by clicking the add button on the right.  To
+      remove a property, click the remove button. ' . $instructions),
+  );
+  $form['properties']['table'] = array(
+    '#type' => 'markup',
+    '#value' =>  '',
+    '#prefix' => '<div id="tripal-generic-edit-properties-table">',
+    '#suffix' => '</div>',
+  );
+  
+  // this array keeps track of all properties we have and allows the functions
+  // below to select the next rank if a property is dupliated
+  $ranks = array();
+  
+  // add in the properties from the Chado prop table (only pertains to existing analyses)
+  if ($id) {
+    tripal_core_properties_form_add_prop_table_props($prop_table, $id_field, $cv_name,
+      $form, $form_state, $id, $ranks, $d_removed, $exclude);
+  }
+  
+  // add in any new properties that have been added by the user through an AHAH callback
+  tripal_core_properties_form_add_new_props($form, $form_state, $ranks, $d_removed);
+  
+  // add an empty row of field to allow for addition of a new property
+  tripal_core_properties_form_add_new_empty_props($form, $form_state, $available_props);
+  
+  $form['properties']['table']['#theme'] = 'tripal_core_properties_form';
+}
+
+/**
+ * This function is responsible for adding a blank row to the properties table for
+ * adding a new property.
+ */
+function  tripal_core_properties_form_add_new_empty_props(&$form, &$form_state, $properties_select) {
+
+  // get the field defaults either from $form_state['values'] or $form_state['input']
+  $description = '';
+  $text = '';
+  $id = 0;
+  if (array_key_exists('values', $form_state)) {
+    $id = $form_state['values']['new_id'];
+    $text = $form_state['values']['new_value'];
+  }
+  // if we have a property ID then get it's definition to display to the user
+  if($id) {
+    $values = array('cvterm_id' => $id);
+    $cvterm = tripal_core_chado_select('cvterm', array('definition'), $values);
+
+    if ($cvterm[0]->definition) {
+      $description = $cvterm[0]->definition;
+    }
+  }
+  $rows = 1;
+
+  // add one more blank set of property fields
+  $form['properties']['table']['new']["new_id"] = array(
+    '#type'          => 'select',
+    '#options'       => $properties_select,
+    '#default_value' => $id,
+    '#ajax' => array(
+      'callback' => "tripal_core_props_property_ajax_get_description",
+      'wrapper'  => 'tripal-properties-new_value',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+  );
+  $form['properties']['table']['new']["new_value"] = array(
+    '#type'           => 'textarea',
+    '#default_value'  => $text,
+    '#cols'           => 50,
+    '#rows'           => $rows,
+    '#prefix'         => '<div id="tripal-properties-new_value">',
+    '#description'    => $description,
+    '#suffix'         => '</div>',
+  );
+
+  $form['properties']['table']['new']["add"] = array(
+    '#type'    => 'button',
+    '#value'   => t('Add'),
+    '#name'    => 'add',
+    '#ajax'      => array(
+      'callback' => "tripal_core_props_property_ajax_update",
+      'wrapper'  => 'tripal-properties-edit-properties-table',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+      'prevent'  => 'click'
+    ),
+    // 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.
+    '#submit'   => array('tripal_core_props_form_props_button_submit'),
+    '#validate' => array('tripal_core_props_form_props_button_validate'),
+    '#limit_validation_errors' => array(array('new_id')),
+  );
+}
+
+/**
+ * This function is used to rebuild the form if an ajax call is made vai a button.
+ * The button causes the form to be submitted. We don't want this so we override
+ * the validate and submit routines on the form button. Therefore, this function
+ * only needs to tell Drupal to rebuild the form
+ */
+function  tripal_core_props_form_props_button_validate($form, &$form_state){
+  if (array_key_exists('triggering_element', $form_state) and
+  $form_state['triggering_element']['#name'] == 'add' and
+  $form_state['input']['new_id'] == 0 ){
+    form_set_error('new_id', "Please specify a property type");
+    return;
+  }
+  $form_state['rebuild'] = TRUE;
+}
+/**
+ * This function is just a dummy to override the default form submit on ajax calls for buttons
+ */
+function tripal_core_props_form_props_button_submit($form, &$form_state){
+  // do nothing
+}
+/**
+ * This adds
+ */
+function tripal_core_properties_form_add_new_props(&$form, &$form_state, &$ranks, &$d_removed) {
+   
+  // set some default values
+  $j = 0;
+  $num_properties = 0;
+
+  $values = array();
+  if (array_key_exists('values', $form_state)) {
+    $values = $form_state['values'];
+  }
+  if (array_key_exists('input', $form_state) and !empty($form_state['input'])) {
+    $values = $form_state['input'];
+  }
+
+  // first, add in all of the new properties that were added previously via this form
+  foreach ($values as $element_name => $value) {
+    if (preg_match('/new_value-(\d+)-(\d+)/', $element_name, $matches)) {
+      $new_id = $matches[1];
+      $rank = $matches[2];
+
+      // skip any properties that the user requested to delete through a previous
+      // ajax callback or through the current ajax callback
+      if (array_key_exists("$new_id-$rank", $d_removed)) {
+        continue;
+      }
+      if (array_key_exists('triggering_element', $form_state) and
+      $form_state['triggering_element']['#name'] == 'remove-' . $new_id . '-' . $rank) {
+        $d_removed["$new_id-$rank"] = 1;
+        continue;
+      }
+
+      // get this new_id information
+      $args = array('cvterm_id' => $new_id);
+      $cvterm = tripal_core_chado_select('cvterm', array('name', 'definition'), $args);
+
+      // add it to the $ranks array
+      $ranks[$new_id][$rank]['name']  = $cvterm[0]->name;
+      $ranks[$new_id][$rank]['id']    = $new_id;
+      $ranks[$new_id][$rank]['value'] = $value;
+      $ranks[$new_id][$rank]['definition']  = $cvterm[0]->definition;
+      $num_properties++;
+
+      // determine how many rows we need in the textarea
+      $rows = 1;
+      // add the new fields
+      $form['properties']['table']['new'][$new_id][$rank]["new_id-$new_id-$rank"] = array(
+        '#markup' => $cvterm[0]->name
+      );
+      $form['properties']['table']['new'][$new_id][$rank]["new_value-$new_id-$rank"] = array(
+        '#type'          => 'textarea',
+        '#default_value' => $value,
+        '#cols'          => 50,
+        '#rows'          => $rows,
+        '#description'   => $cvterm[0]->definition,
+      );
+
+      $form['properties']['table']['new'][$new_id][$rank]["remove-$new_id-$rank"] = array(
+        '#type'         => 'button',
+        '#value'        => t('Remove'),
+        '#name'         => "remove-$new_id-$rank",
+        '#ajax' => array(
+          'callback' => "tripal_core_props_property_ajax_update",
+          'wrapper'  => 'tripal-properties-edit-properties-table',
+          'effect'   => 'fade',
+          'event'    => 'mousedown',
+          'method'   => 'replace',
+          'prevent'  => 'click'
+        ),
+        // 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.
+        '#submit'   => array('tripal_core_props_form_props_button_submit'),
+        '#validate' => array('tripal_core_props_form_props_button_validate'),
+        '#limit_validation_errors' => array(),
+      );
+    }
+  }
+
+  // second add in any new properties added during this callback
+  if (array_key_exists('triggering_element', $form_state) and
+  $form_state['triggering_element']['#name'] == 'add' and
+  $form_state['input']['new_id'] != 0) {
+    $new_id    = $form_state['input']['new_id'];
+    $new_value = $form_state['input']['new_value'];
+
+    // get the rank by counting the number of entries
+    $rank = count($ranks[$new_id]);
+
+    // get this new_id information
+    $cvterm = tripal_core_chado_select('cvterm', array('name', 'definition'), array('cvterm_id' => $new_id));
+
+    // add it to the $ranks array
+    $ranks[$new_id][$rank]['name']  = $cvterm[0]->name;
+    $ranks[$new_id][$rank]['id']    = $new_id;
+    $ranks[$new_id][$rank]['value'] = $value;
+    $ranks[$new_id][$rank]['definition']  = $cvterm[0]->definition;
+    $num_properties++;
+
+    // determine how many rows we need in the textarea
+    $rows = 1;
+
+    // add the new fields
+    $form['properties']['table']['new'][$new_id][$rank]["new_id-$new_id-$rank"] = array(
+      '#markup' => $cvterm[0]->name
+    );
+    $form['properties']['table']['new'][$new_id][$rank]["new_value-$new_id-$rank"] = array(
+      '#type'          => 'textarea',
+      '#default_value' => $new_value,
+      '#cols'          => 50,
+      '#rows'          => $rows,
+      '#description'   => $cvterm[0]->definition,
+    );
+
+    $form['properties']['table']['new'][$new_id][$rank]["remove-$new_id-$rank"] = array(
+      '#type'         => 'button',
+      '#value'        => t('Remove'),
+      '#name'         => "remove-$new_id-$rank",
+      '#ajax' => array(
+        'callback' => "tripal_core_props_property_ajax_update",
+        'wrapper'  => 'tripal-properties-edit-properties-table',
+        'effect'   => 'fade',
+        'event'    => 'mousedown',
+        'method'   => 'replace',
+        'prevent'  => 'click'
+      ),
+      // 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.
+      '#submit'   => array('tripal_core_props_form_props_button_submit'),
+      '#validate' => array('tripal_core_props_form_props_button_validate'),
+      '#limit_validation_errors' => array(),
+    );
+
+  }
+
+  return $num_properties;
+}
+/*
+ *
+*/
+function  tripal_core_properties_form_add_prop_table_props($prop_table, $id_field, $cv_name, 
+    &$form, $form_state, $id, &$ranks, &$d_removed, $exclude = array()) {
+
+  // get the existing properties
+  $num_properties = 0;
+
+  if (!$id) {
+    return;
+  }
+
+  $sql = "
+    SELECT CVT.cvterm_id, CVT.name, CVT.definition, PP.value, PP.rank
+    FROM {" . $prop_table . "} PP
+      INNER JOIN {cvterm} CVT ON CVT.cvterm_id = PP.type_id
+      INNER JOIN {cv} CV      ON CVT.cv_id     = CV.cv_id
+    WHERE 
+      PP.$id_field = :id AND 
+      CV.name = '$cv_name'
+    ORDER BY CVT.name, PP.rank
+  ";
+  $props = chado_query($sql, array(':id' => $id));
+  while ($prop = $props->fetchObject()) {
+
+    $type_id = $prop->cvterm_id;
+    $rank = 0;
+    if(array_key_exists($type_id, $ranks)) {
+      $rank = count($ranks[$type_id]);
+    }
+
+    // skip any properties that the user requested to delete through a previous
+    // AHAH callback or through the current AHAH callback
+    if (array_key_exists("$type_id-$rank", $d_removed)) {
+      continue;
+    }
+    // skip any properties that should be excluded
+    if (count(array_intersect(array($prop->name), $exclude)) == 1) {
+      continue;
+    }
+    if (array_key_exists('triggering_element', $form_state) and
+    $form_state['triggering_element']['#name'] == 'remove-' . $type_id . '-' . $rank) {
+      $d_removed["$type_id-$rank"] = 1;
+      continue;
+    }
+
+    $ranks[$type_id][$rank]['name']  = $prop->name;
+    $ranks[$type_id][$rank]['id']    = $type_id;
+    $ranks[$type_id][$rank]['value'] = $prop->value;
+    $ranks[$type_id][$rank]['definition']  = $prop->definition;
+    $num_properties++;
+    $rows = 1;
+
+    $form['properties']['table'][$type_id][$rank]["prop_id-$type_id-$rank"] = array(
+      '#markup'        => $prop->name,
+    );
+    $form['properties']['table'][$type_id][$rank]["prop_value-$type_id-$rank"] = array(
+      '#type'          => 'textarea',
+      '#default_value' => $prop->value,
+      '#cols'          => 50,
+      '#rows'          => $rows,
+      '#description'   => $prop->definition,
+    );
+
+    $form['properties']['table'][$type_id][$rank]["remove-$type_id-$rank"] = array(
+      '#type'         => 'button',
+      '#value'        => t('Remove'),
+      '#name'         => "remove-$type_id-$rank",
+      '#ajax' => array(
+        'callback' => "tripal_core_props_property_ajax_update",
+        'wrapper'  => 'tripal-properties-edit-properties-table',
+        'effect'   => 'fade',
+        'event'    => 'mousedown',
+        'method'   => 'replace',
+        'prevent'  => 'click'
+      ),
+      // 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.
+      '#submit'   => array('tripal_core_props_form_props_button_submit'),
+      '#validate' => array('tripal_core_props_form_props_button_validate'),
+      '#limit_validation_errors' => array(),
+    );
+  }
+  return $num_properties;
+}
+
+/**
+ * Form AJAX callback for adding a blank property row
+ *
+ * We only want to return the properties as that's all we'll replace with this callback
+ */
+function tripal_core_props_property_ajax_update($form, $form_state) {
+  $properties_html = tripal_core_props_theme_node_form_properties($form['properties']['table']);
+  $form['properties']['table'] = array(
+    '#markup' => $properties_html,
+    '#prefix' => '<div id="tripal-properties-edit-properties-table">',
+    '#suffix' => '</div>',
+  );
+  return $form['properties']['table'];
+}
+/**
+ * Form AJAX callback for updating a property description. This
+ * function only gets called when the property drop down is changed
+ * on the bottom (empty) row of properties
+ */
+function tripal_core_props_property_ajax_get_description($form, $form_state) {
+  return $form['properties']['table']['new']["new_value"];
+}
+
+/**
+ * We need to theme the form so that the properties fields look good
+ */
+function theme_tripal_core_properties_form($variables) {
+  $form = $variables['form'];
+
+  $properties_table = tripal_core_props_theme_node_form_properties($form);
+  $markup = $properties_table;
+
+  $form['properties']['table'] = array(
+    '#markup' => $markup,
+    '#prefix' => '<div id="tripal-properties-edit-properties-table">',
+    '#suffix' => '</div>',
+  );
+  $form['buttons']['#weight'] = 50;
+  return drupal_render($form['properties']['table']);
+}
+/**
+ *
+ */
+function tripal_core_props_theme_node_form_properties($form) {
+  $rows = array(); 
+
+  // first add in the properties derived from the prop table
+  // the array tree for these properties looks like this:
+  // $form['properties']['table'][$type_id][$rank]["prop_id-$type_id-$rank"]
+  foreach ($form as $type_id => $elements) {
+    // there are other fields in the properties array so we only
+    // want the numeric ones those are our type_id
+    if (is_numeric($type_id)) {
+      foreach ($elements as $rank => $element) {
+        if (is_numeric($rank)) {
+          $rows[] = array(
+            drupal_render($element["prop_id-$type_id-$rank"]),
+            drupal_render($element["prop_value-$type_id-$rank"]),
+            drupal_render($element["remove-$type_id-$rank"]),
+          );
+        }
+      }
+    }
+  }
+
+  // second, add in any new properties added by the user through AHAH callbacks
+  // the array tree for these properties looks like this:
+  // $form['properties']['table']['new'][$type_id][$rank]["new_id-$new_id-$rank"]
+  foreach ($form['new'] as $type_id => $elements) {
+    if (is_numeric($type_id)) {
+      foreach ($elements as $rank => $element) {
+        if (is_numeric($rank)) {
+          $rows[] = array(
+            drupal_render($element["new_id-$type_id-$rank"]),
+            drupal_render($element["new_value-$type_id-$rank"]),
+            drupal_render($element["remove-$type_id-$rank"]),
+          );
+        }
+      }
+    }
+  }
+
+  // finally add in a set of blank field for adding a new property
+  $rows[] = array(
+    drupal_render($form['new']['new_id']),
+    array(
+      'data' => drupal_render($form['new']['new_value']),
+      'width' => '60%',
+    ),
+    drupal_render($form['new']['add']),
+  );
+
+  $headers = array('Property Type', 'Value', 'Actions');
+  $table = array(
+    'header' => $headers,
+    'rows' => $rows,
+    'attributes' => array(),
+    'sticky' => TRUE,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => '',
+  );
+
+  return theme_table($table);
+}
+
+/**
+ * This function is used in a hook_insert, hook_update for a node form
+ * when the properties form has been added to the form.  It retrieves all of the properties
+ * and returns them in an array of the format:
+ *   
+ *   $properties[<property name>][<rank>] = <value
+ * 
+ * This array can then be used for inserting or updating properties using the API call
+ * tripal_hook_insert_property()
+ * 
+ * @param $node
+ * @param $cvname
+ *   The name of the controlled vocabulary that the properties belong to
+ * 
+ * @return
+ *   A properties array
+ *   
+ * @ingroup tripal_properties_api
+ */
+function tripal_core_properties_form_retreive($node, $cv_name) {
+  
+  // now add the properties
+  $properties = array(); // stores all of the properties we need to add
+  
+  // get the list of properties for easy lookup (without doing lots of database queries
+  $properties_list = array();
+  $sql = "
+      SELECT DISTINCT CVT.cvterm_id, CVT.name, CVT.definition
+      FROM  {cvterm} CVT
+        INNER JOIN {cv} ON CVT.cv_id = CV.cv_id
+      WHERE
+        CV.name = '$cv_name' AND
+        NOT CVT.is_obsolete = 1
+      ORDER BY CVT.name ASC
+  ";
+  $prop_types = chado_query($sql);
+  while ($prop = $prop_types->fetchObject()) {
+    $properties_list[$prop->cvterm_id] = $prop->name;
+  }
+  
+  // get the properties that should be added. Properties are in one of two forms:
+  //  1) prop_value-[type id]-[index]
+  //  2) new_value-[type id]-[index]
+  //  3) new_id, new_value
+  
+  foreach ($node as $name => $value) {
+    if (preg_match('/^new_value-(\d+)-(\d+)/', $name, $matches)) {
+      $type_id = $matches[1];
+      $index = $matches[2];
+      $name = $properties_list[$type_id];
+      $properties[$name][$index] = trim($value);
+    }
+    if (preg_match('/^prop_value-(\d+)-(\d+)/', $name, $matches)) {
+      $type_id = $matches[1];
+      $index = $matches[2];
+      $name = $properties_list[$type_id];
+      $properties[$name][$index] = trim($value);
+    }
+  }
+  if (property_exists($node, 'new_id') and $node->new_id and property_exists($node, 'new_value') and $node->new_value) {
+    $type_id = $node->new_id;
+    $name = $properties_list[$type_id];
+    $index = 0;
+    if (array_key_exists($name, $properties)) {
+      $index = count($properties[$name]);
+    }
+    $properties[$name][$index] = trim($node->new_value);
+  }
+
+  return $properties;
+}

+ 10 - 5
tripal_core/tripal_core.module

@@ -38,11 +38,11 @@
 
 require_once "api/tripal_core_chado.api.inc";
 require_once "api/tripal_core_files.api.inc";
-require_once "api/tripal_core_ahah.api.inc";
 require_once "api/tripal_core_custom_tables.api.inc";
 require_once "api/tripal_core_jobs.api.inc";
 require_once "api/tripal_core_mviews.api.inc";
 require_once "api/tripal_core_misc.api.inc";
+require_once "api/tripal_core_properties.api.inc";
 require_once "includes/jobs.inc";
 require_once "includes/mviews.inc";
 require_once "includes/custom_tables.inc";
@@ -388,12 +388,12 @@ function tripal_core_permission() {
  *
  * @ingroup tripal_core
  */
-function tripal_core_theme() {
+function tripal_core_theme($existing, $type, $theme, $path) {
   return array(
     'tripal_core_customize' => array(
       'arguments' => array('job_id' => NULL),
       'template' => 'tripal_core_customize',
-      'path' => drupal_get_path('module', 'tripal_core') . '/theme'
+      'path' => "$path/theme"
     ),
     'theme_file_upload_combo' => array(
       'render element' => 'element',
@@ -404,12 +404,17 @@ function tripal_core_theme() {
     'tripal_core_jobs_help' => array(
       'template' => 'tripal_core_jobs_help',
       'variables' =>  array(NULL),
-      'path' => drupal_get_path('module', 'tripal_core') . '/theme'
+      'path' => "$path/theme"
     ),
     'tripal_core_customtables_help' => array(
       'template' => 'tripal_core_customtables_help',
       'variables' =>  array(NULL),
-      'path' => drupal_get_path('module', 'tripal_core') . '/theme'
+      'path' => "$path/theme"
+    ),
+    
+    // form theme
+    'tripal_core_properties_form' => array(
+      'render element' => 'form',
     ),
   );
 }

+ 19 - 20
tripal_pub/tripal_pub.module

@@ -772,28 +772,26 @@ function chado_pub_update($node) {
  *   The node with the information to be loaded into the database
  *
  */
-function chado_pub_load($node) {
-  // get the feature details from chado
-  $pub_id = chado_get_id_for_node('pub', $node->nid);
-
-  $values = array('pub_id' => $pub_id);
-  $pub = tripal_core_generate_chado_var('pub', $values);
-
-  // expand the 'text' fields as those aren't included by default
-  // and they really shouldn't be so large to cause problems
-  if (is_array($pub) or is_object($pub)) {
+function chado_pub_load($nodes) {
+  
+  foreach ($nodes as $nid => $node) {
+    // find the pub and add in the details
+    $pub_id = chado_get_id_for_node('pub', $nid);
+  
+    // get the pub
+    $values = array('pub_id' => $pub_id);
+    $pub = tripal_core_generate_chado_var('pub', $values);
+    
+    // expand the 'text' fields as those aren't included by default
+    // and they really shouldn't be so large to cause problems
     $pub = tripal_core_expand_chado_vars($pub, 'field', 'pub.title');
     $pub = tripal_core_expand_chado_vars($pub, 'field', 'pub.volumetitle');
     $pub = tripal_core_expand_chado_vars($pub, 'field', 'pub.uniquename');
+    
+    // set the URL path
+    $nodes[$nid]->path = "pub/$pub_id";
+    $nodes[$nid]->pub = $pug;
   }
-
-  // set the URL path
-  $path = "pub/$pub_id";
-
-  $additions = new stdClass();
-  $additions->pub = $pub;
-  return $additions;
-
 }
 
 /**
@@ -946,14 +944,15 @@ function tripal_pub_node_insert($node) {
  * @param $node
  * @param $types
  */
-function tripal_pub_node_load($node, $types) {
+function tripal_pub_node_load($nodes, $types) {
+
   // we want the publications to always have a URL of http://[base url]/pub/[pub id]
   // where [pub id] is the Chado publication ID.  This will allow for easy linking
   // into the publication without needing to know the node.  Of course if you know the
   // node that will still work too (e.g. http://[base url]/node/[node id]
   // so the nodeapi function ensures that the URL path is set after insert or update
   // of the node and when the node is loaded if it hasn't yet been set.
-  if ($node->type == 'chado_pub') {
+  if (count(array_intersect(array('chado_pub'), $types))) {
     if (!$node->path) {
       $pub_id = chado_get_id_for_node('pub', $node->nid);
       $path = tripal_pub_set_pub_url($node, $pub_id);