Browse Source

Merge pull request #616 from tripal/427-Tv3-prop_content_types

Overhaul of content creation for prop-based types.
Bradford Condon 6 years ago
parent
commit
8c4e57c0f0

BIN
docs/user_guide/creating_content.create1.png


BIN
docs/user_guide/creating_content.create2.png


BIN
docs/user_guide/creating_content.create3.png


BIN
docs/user_guide/creating_content.create4.png


BIN
docs/user_guide/creating_content.create5.png


BIN
docs/user_guide/creating_content.create6.png


BIN
docs/user_guide/creating_content.create7.png


+ 51 - 12
docs/user_guide/creating_content.rst

@@ -38,26 +38,65 @@ Alternatively, you can add terms one at a time. To add a single term either from
 Create a Tripal Content Type
 ----------------------------
 
-Creation of a new content type requires familiarity with Chado.  This is because data records used by content types must be mapped to actual data and the data lives in Chado.  Tripal's interface for creating content types allows you to provide the CV term for the type and then indicate where in Chado the data is/will be stored.  Chado is a flexible relational database schema.  Thus, it is possible for different sites to store data in different ways.  It is best practice however to follow community standards when storing data.  Therefore, please review the online documentation for Chado. If you are unclear how data for your content type should be stored in Chado please consider emailing the `Chado mailing list <http://gmod.org/wiki/GMOD_Mailing_Lists>`_ to ask for help or add a request for help on the Tripal issue queue.
+Creation of a new content type requires familiarity with Chado.  This is because data records used by content types must be mapped to actual data and the data lives in Chado.  Tripal's interface for creating content types allows you to provide the CV term for the type and then indicate where in Chado the data is/will be stored.  Chado is a flexible relational database schema.  Thus, it is possible for different sites to store data in different ways.  It is best practice however to follow community standards when storing data.  Therefore, please review the online documentation for Chado. If you are unclear how data for your content type should be stored in Chado please consider emailing the `Chado mailing list <http://gmod.org/wiki/GMOD_Mailing_Lists>`_ or posting an issue on the `Chado GitHub issue queue <https://github.com/GMOD/Chado>`_ to ask for help or add a request for help on the Tripal issue queue.
 
-To add a new content type, start by navigating to **Structure → Tripal Content Types** and  click on the **Add Tripal Content Type** link at the top. This will take you to a web form that leads you through the process of creating a custom Tripal Content Type. First, enter for the name of the term you would like to use to describe your content in the Content Type autocomplete textbox (e.g. genetic_marker). Then, click **Lookup Term**. This should bring up a list of matching terms from which you can select the specific term you would like to use.  Sometimes the same term exists in multiple vocabularies and you can select the proper one.
+To add a new content type, start by navigating to **Structure → Tripal Content Types** and  click on the **Add Tripal Content Type** link at the top. This will take you to a web form that leads you through the process of creating a custom Tripal Content Type.
+
+Genetic Marker Example
+^^^^^^^^^^^^^^^^^^^^^^
+To demonstrate how to create a new content type we will use the example of a genetic marker. First, enter for the name of the term you would like to use to describe your content in the Content Type autocomplete textbox. Then, click **Lookup Term**. This should bring up a list of matching terms from which you can select the specific term you would like to use.  Sometimes the same term exists in multiple vocabularies and you can select the proper one.
 
 .. image:: creating_content.create1.png
 
-During content type creation there is as a section to specify which Chado tables will store your data. Chado is typically structured with primary **base** tables (e.g. organism, feature, stock, project, etc) and a set of linker and property tables that contain ancillary data related to the base records.  Here you must first choose the base table where primary records for your data type are stored.  For our example, because genetic markers are sequence features, they are stored in the Chado feature table. Once you select the Chado table, the form will ask additional questions to determine exactly how records of the content type can be found. Specifically, the following options are supported if the appropriate fields/tables are available:
+During content type creation there is as a section to specify which Chado tables will store your data. Chado is typically structured with primary **base** tables (e.g. organism, feature, stock, project, etc) and a set of linker and property tables that contain ancillary data related to the base records.  Here you must first choose the base table where primary records for your data type are stored.  For our example, because genetic markers are sequence features, they are stored in the Chado **feature** table.
 
-1. All records in the **base** table belong to the content type (e.g. tables: organism, analysis, etc.)
-2. The **base** table has a **type_id** that stores the CV term and this differentiates the records. (e.g. tables: feature, stock, library, etc.).
-3. The records can be differentiated by way of a property table which contains a **type_id** column for the CV term. (e.g. tables: featureprop, stockprop, libraryprop, etc.)
-4. The records can be differentiated by way of a linking table that associates records in the **base** table with the CV term (e.g. tables: feature_cvterm, stock_cvterm, etc.)
+.. image:: creating_content.create2.png
 
-For our genetic marker example, we can use the Chado **feature** table and **type_id** column to differentiate which records in the feature table are genetic markers. Thus we
+Next, you will be asked if all of the records in the selected table are of the desired content type.  Usually the answer to this is "No", especially if the Chado table has a **type_id** column.  In our case. The **feature** table does have a **type_id** column so we must select "No".
 
-- Select "No" not all records in the feature table are genetic markers
-- Type Column: type_id
+.. image:: creating_content.create3.png
 
-Then click Create Content Type to create a custom genetic marker content type.
+If all of the records in the selected table do not belong to the content type then the form knows enough about each Chado table to offer you appropriate options for how to store data for your content type.  The form knows that the **feature** table has a **type_id** column so it asks if we can differentiate records for our content type using the **type_id** field.  For our example genetic marker we can do so.
 
-.. image:: creating_content.create2.png
+.. image:: creating_content.create4.png
+
+Just prior to creating our content type we are provided a summary of our selection to review:
+
+
+.. image:: creating_content.create5.png
+
+Finally, click the **Create Content Type** to create a custom genetic marker content type.  We are provided with a message indicating that a job has been added for creation of the content type.  Depending how large the Chado database is, creation of a contnet type may take awhile, hence we must run it as a job.
+
+.. image:: creating_content.create6.png
 
 Once the content type is created, you can create pages for site visitors. This will be described later in this User's Guide. In short, you can manually create new records through brand new web forms that are created automatically for your content type, or you can use a data loader to import your data directly to Chado, then **Publish** those records through the Tripal interface.
+
+.. note::
+
+  Each time you create a new content type, you get several new things:
+
+  - A new search tool will be created automatically for the content type.
+  - A new set of permissions to help you control access is created.
+
+SNP Example
+^^^^^^^^^^^
+Perhaps we want to be more specific with our genetic marker pages and create pages for each type of genetic marker (e.g. SNP, RFLP, etc. pages).  Suppose for this example that we continue to store genetic markes in the feature table and use the genetic_marker term in  **type_id** as in the previous example.  To differentiate between different markers, we store a record in the **featureprop** table where the **featureprop.type_id** indicates the that the property provides the marker type and the **featureprop.value** column houses the string for the marker type (e.g. "SNP").  Thus, any genetic marker that has a property with this type of featureprop should form part of our SNP content type.
+
+To accomplish this we can walk through the content type creation form and set the following values:
+
+.. csv-table::
+  :header: 'Field', 'Value'
+
+  "Content Type", "SNP (SO:0000694)"
+  "Storage Backend", "Chado"
+  "Chado Table", "feature"
+  "Are all records in the 'feature' table of type 'genetic_marker'?", "No"
+  "Type column", "--None--",
+  "Do you want to use the 'featureprop' table to distinguish between content types?", "Yes"
+  "Base Type", "genetic_marker (SO:0001645)"
+  "Property Type", "type (rdfs:type)"
+  "Property Value", "SNP"
+
+.. image:: creating_content.create7.png
+
+After clicking the **Create content type** button a job will be submitted and we will have a new SNP content type whose data is saved to both the feature and featureprop tables.

+ 12 - 13
tripal/api/tripal.entities.api.inc

@@ -380,15 +380,14 @@ function tripal_load_bundle_entity($values) {
  */
 function tripal_add_notification($title, $details, $type, $actions, $submitter_id) {
   $transaction = db_transaction();
-
-  // Check the notification isn't already in the admin notification table.
-  $dedup = db_select('tripal_admin_notfications', 'tan')
-    ->fields('tan')
-    ->condition('submitter_id', $submitter_id, '=')
-    ->execute()->fetchAll();
-
-  if (empty($dedup)) {
-    try {
+  try {
+    // Check the notification isn't already in the admin notification table.
+    $dedup = db_select('tripal_admin_notfications', 'tan')
+      ->fields('tan')
+      ->condition('submitter_id', $submitter_id, '=')
+      ->execute()->fetchAll();
+  
+    if (empty($dedup)) {
       $record = new stdClass;
       $record->details = $details;
       $record->title = $title;
@@ -398,10 +397,10 @@ function tripal_add_notification($title, $details, $type, $actions, $submitter_i
       $record->type = $type;
       $success = drupal_write_record('tripal_admin_notfications', $record);
     }
-    catch (Exception $e) {
-      $transaction->rollback();
-      watchdog('tripal_cron', 'Could not write notification to database.');
-    }
+  }
+  catch (Exception $e) {
+    $transaction->rollback();
+    watchdog('tripal_cron', 'Could not write notification to database.');
   }
 }
 

+ 5 - 6
tripal/api/tripal.importer.api.inc

@@ -210,10 +210,9 @@ function tripal_run_importer($import_id, TripalJob $job = NULL) {
  */
 function tripal_run_importer_run($loader, $job) {
 
+  // begin the transaction
+  $transaction = db_transaction();
   try {
-    // begin the transaction
-    $transaction = db_transaction();
-
     $loader->run();
 
     if ($job) {
@@ -244,9 +243,9 @@ function tripal_run_importer_run($loader, $job) {
  * @ingroup tripal_importer_api
  */
 function tripal_run_importer_post_run($loader, $job) {
-  try {
-    // the transaction
-    $transaction = db_transaction();
+  // the transaction
+  $transaction = db_transaction();
+  try {    
     $loader->postRun();
   }
   catch (Exception $e) {

+ 61 - 91
tripal/api/tripal.terms.api.inc

@@ -61,43 +61,6 @@ function hook_vocab_storage_info() {
   );
 }
 
-
-/**
- * Creates a form for specifying a term for TripalEntity creation.
- *
- * This hook allows the module that implements a vocabulary storage backend
- * to provide the form necessary to select a term that will then be used for
- * creating a new TripalEntity type.  Tripal will expect that a 'vocabulary' and
- * 'accession' are in the $form_state['storage'] array. The 'vocabulary' and
- * must be the abbreviated uppercase vocabulary for the vocabulary (e.g. 'RO',
- * 'SO', 'PATO', etc.).  The 'accession' must be the unique term ID (or
- * accession) for the term in the vocabulary.
- *
- * @param $form
- * @param $form_state
- *
- * @return
- *   A form object.
- *
- * @ingroup tripal_terms_api
- */
-function hook_vocab_select_term_form(&$form, &$form_state) {
-
-  return $form;
-}
-
-/**
- * Validates the hook_vocab_select_term_form().
- *
- * @param $form
- * @param $form_state
- *
- * @ingroup tripal_terms_api
- */
-function hook_vocab_select_term_form_validate($form, &$form_state) {
-
-}
-
 /**
  * Provides a form for importing vocabularies and their terms.
  *
@@ -618,19 +581,32 @@ function tripal_get_vocabularies() {
  */
 function tripal_get_term_lookup_form(&$form, &$form_state, $default_name = '',
     $title = 'Vocabulary Term', $description = '', $is_required,
-    $field_name = '', $delta = 0 ) {
+    $field_name = '', $delta = 0, $callback = '', $wrapper = '', $validate = [], 
+    $weight = 0) {
   
-  $ajax_wrapper_id = 'tripal-vocab-select-form-' . $delta;
-  if ($field_name) {
-    $ajax_wrapper_id = $field_name . '-' . $delta;
+  if (!$callback) {
+    $callback = 'tripal_get_term_lookup_form_ajax_callback';
   }
-
+  
+  if (!$wrapper) {
+    $ajax_wrapper_id = 'tripal-vocab-select-form-' . $delta;
+    if ($field_name) {
+      $ajax_wrapper_id = $field_name . '-' . $delta;
+    }
+  }
+  else {
+    $ajax_wrapper_id = $wrapper;
+  }
+  
   $term_name = $default_name;
-  if (array_key_exists('values', $form_state) and array_key_exists('term_name', $form_state['values'])) {
-    $term_name = $form_state['values']['term_name'];
+  if (array_key_exists('values', $form_state) and array_key_exists('term_name' . $delta, $form_state['values'])) {
+    $term_name = $form_state['values']['term_name' . $delta];
+  }
+  if (array_key_exists('input', $form_state) and array_key_exists('term_name' . $delta, $form_state['input'])) {
+    $term_name = $form_state['input']['term_name' . $delta];
   }
   if ($field_name and array_key_exists('input', $form_state) and array_key_exists($field_name, $form_state['input'])) {
-    $term_name = $form_state['input'][$field_name]['und'][$delta]['term_match']['term_name'];
+    $term_name = $form_state['input'][$field_name]['und'][$delta]['term_match' . $delta]['term_name' . $delta];
   }
 
   if (!$description) {
@@ -642,15 +618,16 @@ function tripal_get_term_lookup_form(&$form, &$form_state, $default_name = '',
   $form_state['storage'][$ajax_wrapper_id]['term_match_field'] = $field_name;
   $form_state['storage'][$ajax_wrapper_id]['term_match_delta'] = $delta;
 
-  $form['term_match'] = array(
+  $form['term_match' . $delta] = array(
     '#type' => 'fieldset',
     '#collapsible' => FALSE,
     '#collapsed' => FALSE,
     '#title' => t($title),
     '#prefix' => '<div id = "' . $ajax_wrapper_id . '">',
     '#suffix' => '</div>',
+    '#weight' => $weight,
   );
-  $form['term_match']['term_name'] = array(
+  $form['term_match' . $delta]['term_name' . $delta] = array(
     '#title'  => t('Type'),
     '#type' => 'textfield',
     '#description' => $description,
@@ -658,30 +635,33 @@ function tripal_get_term_lookup_form(&$form, &$form_state, $default_name = '',
     '#default_value' => $term_name,
     '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/cvterm/",
   );
-  $form['term_match']['select_button'] = array(
+  $form['term_match' . $delta]['select_button' . $delta] = array(
     '#type' => 'button',
     '#value' => t('Lookup Term'),
     '#name' => 'select_cvterm_' . $ajax_wrapper_id,
-    '#validate' => array(),
-    '#limit_validation_errors' => array(),
+    '#validate' => $validate,
     '#ajax' => array(
-      'callback' => "tripal_get_term_lookup_form_ajax_callback",
+      'callback' => $callback,
       'wrapper' => $ajax_wrapper_id,
       'effect' => 'fade',
       'method' => 'replace'
     ),
   );
+  if (empty($validate)) {
+    $form['term_match' . $delta]['select_button' . $delta]['#limit_validation_errors'] = [];
+  }
+  
 
   // If the term has been provided by the user then we want to search for
   // matching terms in the database and let them select among any matches.
   if ($term_name) {
     $submit_disabled = TRUE;
-    $form['term_match']['terms_list'] = array(
+    $form['term_match' . $delta]['terms_list' . $delta] = array(
       '#type' => 'fieldset',
       '#title' => t('Matching Terms'),
-      '#description' => t('Please select the term the best matches the
-          content type you want to create. If the same term exists in
-          multiple vocabularies you will see more than one option below.')
+      '#description' => t('Please select the best matching term. If the 
+        same term exists in multiple vocabularies you will see more than 
+        one option below.')
     );
     $match = array(
       'name' => $term_name,
@@ -700,27 +680,22 @@ function tripal_get_term_lookup_form(&$form, &$form_state, $default_name = '',
     foreach ($terms as $term) {
       // Save the user a click by setting the default value as 1 if there's
       // only one matching term.
-      $default = FALSE;
+      $checked = FALSE;
       $attrs = array();
       if ($num_terms == 0 and count($terms) == 1) {
-        $default = TRUE;
+        $checked = TRUE;
         $attrs = array('checked' => 'checked');
       }
-      $term_element_name = 'term-' . $term->cvterm_id;
-      $form['term_match']['terms_list'][$term_element_name] = array(
+      $term_element_name = 'term-' . $term->cvterm_id  . '-' . $delta;
+      $definition = property_exists($term, 'definition') ? $term->definition : '';
+      $form['term_match' . $delta]['terms_list' . $delta][$term_element_name] = array(
         '#type' => 'checkbox',
         '#title' =>  $term->name,
-        '#default_value' => $default,
+        '#default_value' => $checked,
         '#attributes' => $attrs,
         '#description' => '<b>Vocabulary:</b> ' . $term->cv_id->name . ' (' . $term->dbxref_id->db_id->name . ') ' . $term->cv_id->definition .
-        '<br><b>Term: </b> ' . $term->dbxref_id->db_id->name . ':' . $term->dbxref_id->accession . '.  ' .
-        '<br><b>Definition:</b>  ' . property_exists($term, 'definition') ? $term->definition : '',
-        '#ajax' => array(
-          'callback' => "tripal_get_term_lookup_form_ajax_callback",
-          'wrapper' => $ajax_wrapper_id,
-          'effect' => 'fade',
-          'method' => 'replace'
-        ),
+          '<br><b>Term ID: </b> ' . $term->dbxref_id->db_id->name . ':' . $term->dbxref_id->accession . '.  ' .
+          '<br><b>Definition:</b>  ' . $definition,
       );
 
       if (array_key_exists('values', $form_state) and array_key_exists($term_element_name, $form_state['values']) and
@@ -741,40 +716,35 @@ function tripal_get_term_lookup_form(&$form, &$form_state, $default_name = '',
       $term = $synonym->cvterm_id;
       // Save the user a click by setting the default value as 1 if there's
       // only one matching term.
-      $default = FALSE;
+      $checked = FALSE;
       $attrs = array();
       if ($num_terms == 0 and count($terms) == 1) {
-        $default = TRUE;
+        $checked = TRUE;
         $attrs = array('checked' => 'checked');
       }
-      $term_element_name = 'term-' . $term->cvterm_id;
-      $form['term_match']['terms_list'][$term_element_name] = array(
+      $term_element_name = 'term-' . $term->cvterm_id . '-' . $delta;
+      $definition = property_exists($term, 'definition') ? $term->definition : '';
+      $form['term_match' . $delta]['terms_list' . $delta][$term_element_name] = array(
         '#type' => 'checkbox',
         '#title' =>  $term->name,
-        '#default_value' => $default,
+        '#default_value' => $checked,
         '#attributes' => $attrs,
         '#description' => '<b>Vocabulary:</b> ' . $term->cv_id->name . ' (' . $term->dbxref_id->db_id->name . ') ' . $term->cv_id->definition .
         '<br><b>Term: </b> ' . $term->dbxref_id->db_id->name . ':' . $term->dbxref_id->accession . '.  ' .
-        '<br><b>Definition:</b>  ' . $term->definition . 
+        '<br><b>Definition:</b>  ' . $definition . 
         '<br><b>Synonym:</b> ' . $synonym->synonym,
-        '#ajax' => array(
-          'callback' => "tripal_get_term_lookup_form_ajax_callback",
-          'wrapper' => $ajax_wrapper_id,
-          'effect' => 'fade',
-          'method' => 'replace'
-        ),
       );
       
       if (array_key_exists('values', $form_state) and array_key_exists($term_element_name, $form_state['values']) and
         $form_state['values'][$term_element_name] == 1) {
-          $selected_term = $term;
-        }
-        $num_terms++;
+        $selected_term = $term;
+      }
+      $num_terms++;
     }
     
     
     if ($num_terms == 0) {
-      $form['term_match']['terms_list']['none'] = array(
+      $form['term_match'. $delta]['terms_list'. $delta]['none'. $delta] = array(
         '#type' => 'item',
         '#markup' => '<i>' . t('There is no term that matches the entered text.') . '</i>'
       );
@@ -804,19 +774,19 @@ function tripal_get_term_lookup_form_result($form, $form_state, $field_name = ''
   $values = array();
   $selected = array();
   if ($field_name) {
-    if (array_key_exists('term_match', $form_state['values'][$field_name]['und'][$delta]) and 
-      array_key_exists('terms_list', $form_state['values'][$field_name]['und'][$delta]['term_match'])) {
-      $values = $form_state['values'][$field_name]['und'][$delta]['term_match']['terms_list'];
+    if (array_key_exists('term_match' . $delta, $form_state['values'][$field_name]['und'][$delta]) and 
+        array_key_exists('terms_list' . $delta, $form_state['values'][$field_name]['und'][$delta]['term_match'. $delta])) {
+      $values = $form_state['values'][$field_name]['und'][$delta]['term_match'. $delta]['terms_list'. $delta];
     }
   }
   else {
-    $values = $form_state['values'];
+    $values = array_key_exists('values', $form_state) ? $form_state['values'] : [];
   }
 
   if (is_array($values)) {
     foreach ($values as $key => $value) {
       $matches = array();
-      if (preg_match("/^term-(\d+)$/", $key, $matches) and $values['term-' . $matches[1]]) {
+      if (preg_match("/^term-(\d+)-$delta$/", $key, $matches) and $values['term-' . $matches[1] . '-' . $delta]) {
         $cvterm_id = $matches[1];
         $selected[] = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
       }
@@ -839,9 +809,9 @@ function tripal_get_term_lookup_form_ajax_callback($form, $form_state) {
   // If this form is in a field then we need to dig a bit deeper to return
   // the form elements.
   if ($field_name) {
-    return $form[$field_name]['und'][$delta]['term_match'];
+    return $form[$field_name]['und'][$delta]['term_match'. $delta];
   }
   else {
-    return $form['term_match'];
+    return $form['term_match' . $delta];
   }
 }

+ 59 - 48
tripal/includes/TripalBundleController.inc

@@ -51,59 +51,70 @@ class TripalBundleController extends EntityAPIControllerExportable {
     if (!$transaction) {
       $transaction = db_transaction();
     }
-
-    if ($bundles) {
-
-      foreach ($bundles as $id => $bundle) {
-
-        // Allow modules to perform actions when the bundle is deleted.
-        $modules = module_implements('bundle_delete');
-        foreach ($modules as $module) {
-          $function = $module . '_bundle_delete';
-          $function($bundle);
+    
+    try {
+
+      if ($bundles) {
+  
+        foreach ($bundles as $id => $bundle) {
+  
+          // Allow modules to perform actions when the bundle is deleted.
+          $modules = module_implements('bundle_delete');
+          foreach ($modules as $module) {
+            $function = $module . '_bundle_delete';
+            $function($bundle);
+          }
+  
+          // Find any TripalEntity fields that are attached to this bundle and
+          // remove them.
+          $instances = field_info_instances('TripalEntity', $bundle->name);
+          foreach ($instances as $instance) {
+            // Mark the instance as deleted and purge it.
+            $field = field_info_field($instance['field_name']);
+            field_delete_instance($instance);
+            field_purge_instance($instance);
+  
+            // If the field has no more instances then purge it too.
+            if (count($field['bundles']) == 1 and
+              count($field['bundles']['TripalEntity']) == 1 and
+              in_array($bundle->name, $field['bundles']['TripalEntity'])
+            ) {
+              field_purge_field($field);
+            }
+          }
+  
+          // Remove any entities from the tripal_entity table.
+          db_delete('tripal_entity')
+            ->condition('bundle', $bundle->name)
+            ->execute();
+  
+          // Remove the terms for the bundles that are to be deleted.
+          db_delete('tripal_term')
+            ->condition('id', $bundle->term_id)
+            ->execute();
         }
-
-        // Find any TripalEntity fields that are attached to this bundle and
-        // remove them.
-        $instances = field_info_instances('TripalEntity', $bundle->name);
-        foreach ($instances as $instance) {
-          // Mark the instance as deleted and purge it.
-          $field = field_info_field($instance['field_name']);
-          field_delete_instance($instance);
-          field_purge_instance($instance);
-
-          // If the field has no more instances then purge it too.
-          if (count($field['bundles']) == 1 and
-            count($field['bundles']['TripalEntity']) == 1 and
-            in_array($bundle->name, $field['bundles']['TripalEntity'])
-          ) {
-            field_purge_field($field);
+  
+        // Use the parent function to delete the bundles.
+        parent::delete($ids, $transaction);
+  
+        // Not sure what this does, but copied from the
+        // EntityAPIControllerExportable->delete() function which this one
+        // overrides.
+        foreach ($bundles as $id => $bundle) {
+          if (entity_has_status($this->entityType, $bundle, ENTITY_IN_CODE)) {
+            entity_defaults_rebuild([$this->entityType]);
+            break;
           }
         }
-
-        // Remove any entities from the tripal_entity table.
-        db_delete('tripal_entity')
-          ->condition('bundle', $bundle->name)
-          ->execute();
-
-        // Remove the terms for the bundles that are to be deleted.
-        db_delete('tripal_term')
-          ->condition('id', $bundle->term_id)
-          ->execute();
       }
-
-      // Use the parent function to delete the bundles.
-      parent::delete($ids, $transaction);
-
-      // Not sure what this does, but copied from the
-      // EntityAPIControllerExportable->delete() function which this one
-      // overrides.
-      foreach ($bundles as $id => $bundle) {
-        if (entity_has_status($this->entityType, $bundle, ENTITY_IN_CODE)) {
-          entity_defaults_rebuild([$this->entityType]);
-          break;
-        }
+    }
+    catch (Exception $e) {
+      if ($transaction) {
+        $transaction->rollback();
       }
+      watchdog_exception('tripal', $e);
+      throw $e;
+      return FALSE;
     }
   }
 }

+ 238 - 224
tripal/includes/TripalBundleUIController.inc

@@ -537,6 +537,7 @@ function tripal_bundle_access($op, $type = NULL, $account = NULL) {
  */
 function tripal_admin_add_type_form($form, &$form_state) {
 
+  // Make sure we have a storage backend for managing content types.
   $stores = module_invoke_all('vocab_storage_info');
   if (!is_array($stores) or count($stores) == 0) {
     tripal_set_message('A storage backend is not enabled for managing
@@ -545,207 +546,218 @@ function tripal_admin_add_type_form($form, &$form_state) {
           and return to create new Tripal content types.', TRIPAL_NOTICE);
     return;
   }
-  $keys = array_keys($stores);
-  $module = $stores[$keys[0]]['module'];
-  $function = $module . '_vocab_select_term_form';
-  if (function_exists($function)) {
-    $form = $function($form, $form_state);
+  
+  // Set the stage to step1 if it isn't already set.
+  if (!isset($form_state['stage'])) $form_state['stage'] = 'step1';
+  $stage = $form_state['stage'];
+  
+  
+  // Get the selected term.
+  if (array_key_exists('values', $form_state) and
+      array_key_exists('term', $form_state['values'])) {
+    $selected_term = $form_state['values']['term'];
+  }
+  else {
+    $selected = tripal_get_term_lookup_form_result($form, $form_state);
+    if ($selected) {
+      $selected_term = $selected[0];
+    }
+  }
+    
+  // Get the selected storage element.
+  $default_store = 'term_chado_storage';
+  if (array_key_exists('values', $form_state) and
+      array_key_exists('store_select', $form_state['values'])) {
+    $default_store = $form_state['values']['store_select'];
   }
+  
 
-  $term_name = array_key_exists('values', $form_state) ? $form_state['values']['term_name'] : '';
+  // Handle the different stages:
+  if ($stage == 'step1') {
+    tripal_admin_add_type_form_step1($form, $form_state);
+  }
+  if ($form_state['stage'] == 'step2') {
+    tripal_admin_add_type_form_step1_summary($form, $form_state, $selected_term);
+    tripal_admin_add_type_form_step2($form, $form_state, $stores, $selected_term, $default_store);
+  }
+  if ($form_state['stage'] == 'step3') {
+    tripal_admin_add_type_form_step1_summary($form, $form_state, $selected_term);
+    tripal_admin_add_type_form_step2_summary($form, $form_state, $stores, $selected_term, $default_store);
+    tripal_admin_add_type_form_step3($form, $form_state, $stores, $selected_term, $default_store);
+  }
+  
+  $form['#prefix'] = '<div id = "tripal-add-type-form">';
+  $form['#suffix'] = '</div>';
+  
+  return $form;
+}
 
-  // If no term has been selected yet then provide the auto complete field.
-  $form['term_name'] = array(
-    '#title'       => t('Content Type'),
-    '#type'        => 'textfield',
-    '#description' => t("The content type must be the name of a term in
+/**
+ * Builds step1 of the tripal_admin_add_type_form()
+ */
+function tripal_admin_add_type_form_step1(&$form, &$form_state) {
+  
+  // Get the term name from the form_state.
+  $term_name = '';
+  if (array_key_exists('values', $form_state) and array_key_exists('term_name0', $form_state['values'])) {
+    $term_name = $form_state['values']['term_name0'];
+  }
+  if (array_key_exists('input', $form_state) and array_key_exists('term_name0', $form_state['input'])) {
+    $term_name = $form_state['input']['term_name0'];
+  }
+  
+  // Get the term lookup form.
+  $description = t("The content type must be the name of a term in
         a controlled vocabulary and the controlled vocabulary should
         already be loaded into Tripal.  For example, to create a content
         type for storing 'genes', use the 'gene' term from the
-        Sequence Ontology (SO)."),
-    '#required'    => TRUE,
-    '#default_value' => $term_name,
-    '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/cvterm/",
-  );
-  $form['select_button'] = array(
-    '#type' => 'submit',
-    '#value' => t('Lookup Term'),
-    '#name' => 'select_cvterm',
-    '#ajax' => array(
-      'callback' => "tripal_admin_add_type_form_ajax_callback",
-      'wrapper' => "tripal-vocab-select-form",
-      'effect' => 'fade',
-      'method' => 'replace'
-    ),
-  );
-  $form['#prefix'] = '<div id = "tripal-vocab-select-form">';
-  $form['#suffix'] = '</div>';
-
-  // If the term has been provided by the user then we want to search for
-  // matching terms in the database and let them select among any matches.
+        Sequence Ontology (SO).");
+  tripal_get_term_lookup_form($form, $form_state, $term_name,
+    'Step 1: Content Type', $description, TRUE, '', 0,
+    'tripal_admin_add_type_form_ajax_callback');
+  
   if ($term_name) {
-    $submit_disabled = TRUE;
-    $form['terms_list'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Matching Terms'),
-      '#description' => t('Please select the term the best matches the
-          content type you want to create. If the same term exists in
-          multiple vocabularies you will see more than one option below.')
-    );
-    $match = array(
-      'name' => $term_name,
+    $form['term_match']['step1-continue'] = array(
+      '#type' => 'submit',
+      '#value' => t('Continue'),
+      '#name' => 'step1-continue',
     );
-    $terms = chado_generate_var('cvterm', $match, array('return_array' => TRUE));
-    $terms = chado_expand_var($terms, 'field', 'cvterm.definition');
-    $num_terms = 0;
-    $selected_term = '';
-
-    // Let the user select from any matching terms. Sometimes there may be
-    // more than one that match.
-    foreach ($terms as $term) {
-      // Save the user a click by setting the default value as 1 if there's
-      // only one matching term. 
-      $default = FALSE;
-      $attrs = array();
-      if ($num_terms == 0 and count($terms) == 1) {
-        $default = TRUE;
-        $attrs = array('checked' => 'checked');
-      }
-      $term_element_name = 'term-' . $term->cvterm_id;
-      $form['terms_list'][$term_element_name] = array(
-        '#type' => 'checkbox',
-        '#title' =>  $term->name,
-        '#default_value' => $default,
-        '#attributes' => $attrs,
-        '#description' => '<b>Vocabulary:</b> ' . $term->cv_id->name . ' (' . $term->dbxref_id->db_id->name . ') ' . $term->cv_id->definition .
-        '<br><b>Term: </b> ' . $term->dbxref_id->db_id->name . ':' . $term->dbxref_id->accession . '.  ' .
-        '<br><b>Definition:</b>  ' . $term->definition,
-        '#ajax' => array(
-          'callback' => "tripal_admin_add_type_form_ajax_callback",
-          'wrapper' => "tripal-vocab-select-form",
-          'effect' => 'fade',
-          'method' => 'replace'
-        ),
-      );
-
-      if (array_key_exists('values', $form_state) and array_key_exists($term_element_name, $form_state['values']) and
-          $form_state['values'][$term_element_name] == 1) {
-        $selected_term = $term;
-      }
-      $num_terms++;
-    }
-    
-    
-    // Next find terms that are synonyms
-    $match = array(
-      'synonym' => $term_name,
-    );
-    $termsyn = chado_generate_var('cvtermsynonym', $match, array('return_array' => TRUE));
-    // Let the user select from any matching terms. Sometimes there may be
-    // more than one that match.
-    foreach ($termsyn as $synonym) {
-      $term = $synonym->cvterm_id;
-      // Save the user a click by setting the default value as 1 if there's
-      // only one matching term.
-      $default = FALSE;
-      $attrs = array();
-      if ($num_terms == 0 and count($terms) == 1) {
-        $default = TRUE;
-        $attrs = array('checked' => 'checked');
-      }
-      $term_element_name = 'term-' . $term->cvterm_id;
-      $form['term_match']['terms_list'][$term_element_name] = array(
-        '#type' => 'checkbox',
-        '#title' =>  $term->name,
-        '#default_value' => $default,
-        '#attributes' => $attrs,
-        '#description' => '<b>Vocabulary:</b> ' . $term->cv_id->name . ' (' . $term->dbxref_id->db_id->name . ') ' . $term->cv_id->definition .
-        '<br><b>Term: </b> ' . $term->dbxref_id->db_id->name . ':' . $term->dbxref_id->accession . '.  ' .
-        '<br><b>Definition:</b>  ' . $term->definition .
-        '<br><b>Synonym:</b> ' . $synonym->synonym,
-        '#ajax' => array(
-          'callback' => "tripal_admin_add_type_form_ajax_callback",
-          'wrapper' => "tripal-vocab-select-form",
-          'effect' => 'fade',
-          'method' => 'replace'
-        ),
-      );
-      
-      if (array_key_exists('values', $form_state) and array_key_exists($term_element_name, $form_state['values']) and
-        $form_state['values'][$term_element_name] == 1) {
-          $selected_term = $term;
-        }
-        $num_terms++;
-    }
-    if ($num_terms == 0) {
-      $form['terms_list']['none'] = array(
-        '#type' => 'item',
-        '#markup' => '<i>' . t('There is no term that matches the entered text.') . '</i>'
-      );
-      return $form;
-    }
+  }
+}
 
-    // Now let the user select where the data type will be stored.
-    $form['storage'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Storage Settings'),
-      '#description' => t('The primary record for each content of this type
+/**
+ * Provides a summary of values selected in Step 1. 
+ */
+function tripal_admin_add_type_form_step1_summary(&$form, &$form_state, $selected_term) {
+     
+  $form['term'] = [
+    '#type' => 'value',
+    '#value' => $selected_term,
+  ];
+  
+  $form['term_summary'] = [
+    '#type' => 'fieldset',
+    '#title' => t('Step 1: Content Type'),
+    '#description' => '',
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  ];
+  $definition = property_exists($selected_term, 'definition') ? $selected_term->definition : '';
+  $form['term_summary']['details'] = [
+    '#type' => 'item',
+    '#title' => t('Term'),
+    '#markup' => 'Name: ' . $selected_term->name . 
+      '<br>Vocabulary: ' . $selected_term->cv_id->name . ' (' . $selected_term->dbxref_id->db_id->name . ') ' .
+      '<br>Term ID: ' . $selected_term->dbxref_id->db_id->name . ':' . $selected_term->dbxref_id->accession . '.  ' .
+      '<br>Definition:  ' . $definition
+  ];
+  $form['term_summary']['step1-return'] = array(
+    '#type' => 'submit',
+    '#value' => t('Pick a different term'),
+    '#name' => 'step1-return',
+  );  
+}
+  
+/**
+ * Builds step1 of the tripal_admin_add_type_form()
+ */
+function tripal_admin_add_type_form_step2(&$form, &$form_state, $stores, $selected_term, $default_store) {
+  
+  // Now let the user select where the data type will be stored.
+  $form['storage'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Step 2: Storage'),
+    '#description' => t('The primary record for each content of this type
           must be stored in a single storage backend. Please select the
           storage method and settings for this content type.')
-    );
+  );
+    
+  $store_options = array(0 => '-- Select --');
+  foreach ($stores as $store_type => $store) {
+    $store_options[$store_type] = $store['label'];
+  }
 
-    // TODO: there should be a way for each storage backend to determine if
-    // it can handle the content type.  Maybe certain content types aren't
-    // yet supported by every storage backend.
-    $default_store = 'term_chado_storage';
-    $store_options = array(0 => '-- Select --');
-    foreach ($stores as $store_type => $store) {
-      $store_options[$store_type] = $store['label'];
-    }
-    if (array_key_exists('values', $form_state) and
-        array_key_exists('store_select', $form_state['values'])) {
-      $default_store = $form_state['values']['store_select'];
-    }
-    $form['storage']['store_select'] = array(
-      '#type' => 'select',
-      '#title' => 'Storage backend',
-      '#options' => $store_options,
-      '#default_value' => $default_store,
-      '#ajax' => array(
-        'callback' => "tripal_admin_add_type_form_ajax_callback",
-        'wrapper' => "tripal-vocab-select-form",
-        'effect' => 'fade',
-        'method' => 'replace'
-      ),
-      '#description' => 'Select a storage background for this content type.'
+  $form['storage']['store_select'] = array(
+    '#type' => 'select',
+    '#title' => 'Storage backend',
+    '#options' => $store_options,
+    '#default_value' => $default_store,
+    '#description' => 'Select a storage background for this content type.'
+  );
+  
+  if ($default_store) {
+    $form['term_match']['step2-continue'] = array(
+      '#type' => 'submit',
+      '#value' => t('Continue'),
+      '#name' => 'step2-continue',
     );
+  }
+}
 
-    if ($default_store) {
-      $selected_store_module = $stores[$store_type]['module'];
+/**
+ * Provides a summary of values selected in Step 1.
+ */
+function tripal_admin_add_type_form_step2_summary(&$form, &$form_state, $stores, $selected_term, $default_store) {
+  $default_store = $form_state['values']['store_select'];
+  $selected_store_module = $stores[$default_store]['module'];
+  
+  $form['store_select'] = [
+    '#type' => 'value',
+    '#value' => $default_store,
+  ];
+  
+  $form['store_summary'] = [
+    '#type' => 'fieldset',
+    '#title' => t('Step 2: Storage'),
+    '#description' => '',
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  ];
+  $form['store_summary']['details'] = [
+    '#type' => 'item',
+    '#title' => t('Storage backend'),
+    '#markup' => $stores[$default_store]['label']
+  ];
+  $form['term_summary']['step1-return'] = array(
+    '#type' => 'submit',
+    '#value' => t('Pick a different term'),
+    '#name' => 'step1-return',
+  );  
+}
 
-      $function = $selected_store_module . '_field_storage_bundle_mapping_form';
-      if (function_exists($function)) {
-        $store_form = $function($form, $form_state, $selected_term, $submit_disabled);
-        $form['storage'][$store_type] = $store_form;
-      }
+/**
+ * Builds step1 of the tripal_admin_add_type_form()
+ */
+function tripal_admin_add_type_form_step3(&$form, &$form_state, $stores, $selected_term, $default_store) {
+  $default_store = $form_state['values']['store_select'];
+  
+  $selected_store_module = $stores[$default_store]['module'];
+  
+  $form['store_settings'] = [
+    '#type' => 'fieldset',
+    '#title' => t('Step 3: Storage Settings'),
+    '#description' => '',
+  ];
 
-      // Add in the button for the cases of no terms or too many.
-      $form['submit_button'] = array(
-        '#type' => 'submit',
-        '#value' => t('Create content type'),
-        '#name' => 'use_cvterm',
-        '#disabled' => $submit_disabled,
-      );
-    }
+  $function = $selected_store_module . '_field_storage_bundle_mapping_form';
+  if (function_exists($function)) {
+    $store_form = $function($form, $form_state, $selected_term, $submit_disabled);
+    $form['store_settings'][$default_store] = $store_form;
   }
-
-
-  return $form;
+  
+  // Add in the button for the cases of no terms or too many.
+  $form['submit_button'] = array(
+    '#type' => 'submit',
+    '#value' => t('Create content type'),
+    '#name' => 'create-content',
+    '#disabled' => $submit_disabled,
+  );
 }
 /**
  * Implements an AJAX callback for the tripal_chado_vocab_select_term_form.
  */
 function tripal_admin_add_type_form_ajax_callback($form, $form_state) {
+
   return $form;
 }
 /**
@@ -755,50 +767,52 @@ function tripal_admin_add_type_form_ajax_callback($form, $form_state) {
 function tripal_admin_add_type_form_validate($form, &$form_state) {
   $stores = module_invoke_all('vocab_storage_info');
   $store_select = (isset($form_state['values']['store_select'])) ? $form_state['values']['store_select'] : NULL;
+  $clicked_button = $form_state['clicked_button']['#name'];
+  
+  // Don't do validation on an ajax callback.
+  if (array_key_exists('#ajax', $form_state['triggering_element'])) {
+    return;
+  }
 
-  if (array_key_exists('clicked_button', $form_state) and
-      $form_state['clicked_button']['#name'] =='use_cvterm') {
-
-    $cvterm_id = NULL;
-
-    // Make sure we have a cvterm selected
-    $num_selected = 0;
-    foreach ($form_state['values'] as $key => $value) {
-      $matches = array();
-      if (preg_match("/^term-(\d+)$/", $key, $matches) and
-          $form_state['values']['term-' . $matches[1]]) {
-        $cvterm_id = $matches[1];
-        $term = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
-        $num_selected++;
-      }
+  if ($clicked_button =='step1-continue') {
+    $form_state['rebuild'] = TRUE;
+    $form_state['stage'] = 'step2';   
+    
+    $selected = tripal_get_term_lookup_form_result($form, $form_state);
+    if (count($selected) == 0) {
+      form_set_error('term_match][term_name', 'Please select a vocabulary term.');
     }
-
-    if ($num_selected == 0) {
-      form_set_error('', 'Please select at least one term.');
+    if (count($selected) > 1) {
+      form_set_error('term_match][term_name', 'Please select only one vocabulary term.');
     }
-    else if ($num_selected > 1) {
-      form_set_error('term-' . $cvterm_id, 'Please select only one term from the list below.');
+    
+  }
+  if ($clicked_button =='step1-return') {
+    $form_state['rebuild'] = TRUE;
+    $form_state['stage'] = 'step1';
+  }
+  if ($clicked_button =='step2-continue') {
+    
+    if (!$store_select) {
+      form_set_error('store_select', 'Please select only one vocabulary term.');
     }
-    else {
-      // Add the term to the form state so we can access it later.
-      $form_state['term']['vocabulary'] = $term->dbxref_id->db_id->name;
-      $form_state['term']['accession'] = $term->dbxref_id->accession;
-      $form_state['term']['term_name'] = $term->name;
-
-      // Call the submit hook for this form for the storage method that
-      // will be responsible for this cotent type.
-      $stores = module_invoke_all('vocab_storage_info');
-      $selected_store_module = $stores[$store_select]['module'];
-      $selected_term = $form_state['term'];
-
-      $function = $selected_store_module . '_field_storage_bundle_mapping_form_validate';
-      if (function_exists($function)) {
-        $function($form, $form_state, $selected_term);
-      }
+    
+    $form_state['rebuild'] = TRUE;
+    $form_state['stage'] = 'step3';
+  }
+  
+  if ($clicked_button == 'create-content') {           
+    // Call the submit hook for this form for the storage method that
+    // will be responsible for this cotent type.
+    $stores = module_invoke_all('vocab_storage_info');
+    $selected_store_module = $stores[$store_select]['module'];
+    $selected_term = $form_state['values']['term'];
+
+    $function = $selected_store_module . '_field_storage_bundle_mapping_form_validate';
+    if (function_exists($function)) {
+      $function($form, $form_state, $selected_term);
     }
   }
-  // For any other button click it's an AJAX call and we just want to reubild
-  // the form.
   else {
     $form_state['rebuild'] = TRUE;
   }
@@ -809,16 +823,16 @@ function tripal_admin_add_type_form_validate($form, &$form_state) {
 function tripal_admin_add_type_form_submit($form, &$form_state) {
   $vocabulary = '';
   $accession = '';
-  if (array_key_exists('term', $form_state)) {
-    $selected_term = $form_state['term'];
+  if (array_key_exists('term', $form_state['values'])) {
+    $selected_term = $form_state['values']['term'];
     $store_select = $form_state['values']['store_select'];
-    $vocabulary = array_key_exists('vocabulary', $selected_term) ? $selected_term['vocabulary'] : '';
-    $accession = array_key_exists('accession', $selected_term) ? $selected_term['accession'] : '';
-    $term_name = array_key_exists('term_name', $selected_term) ? $selected_term['term_name'] : '';
+    $vocabulary = $selected_term->dbxref_id->db_id->name;
+    $accession = $selected_term->dbxref_id->accession;
+    $term_name = $selected_term->name;
 
     // Before we try to add this type, check to see if it already exists
     // as a bundle.
-    $term = tripal_load_term_entity(array('vocabulary' => $vocabulary, 'accession' => $accession));
+    $term = tripal_load_term_entity(['vocabulary' => $vocabulary, 'accession' => $accession]);
     if (!$term) {
 
       // Call the submit hook for this form for the storage method that
@@ -853,7 +867,7 @@ function tripal_admin_add_type_form_submit($form, &$form_state) {
       }
     }
     else {
-      drupal_set_message("The term '$accession' already exists as a content type.", 'warning');
+      drupal_set_message("The term ". $term->name . " (" . $vocabulary . ':' . $accession. ") already exists as a content type.", 'warning');
     }
   }
 }

+ 4 - 2
tripal/includes/TripalEntityController.inc

@@ -91,7 +91,9 @@ class TripalEntityController extends EntityAPIController {
       }
     }
     catch (Exception $e) {
-      $transaction->rollback();
+      if ($transaction) {
+        $transaction->rollback();
+      }
       watchdog_exception('tripal', $e);
       throw $e;
       return FALSE;
@@ -468,7 +470,7 @@ class TripalEntityController extends EntityAPIController {
     catch (Exception $e) {
       $transaction->rollback();
       watchdog_exception('tripal', $e);
-      drupal_set_message("Could not save the entity: " . $e->getMessage(), "error");
+      drupal_set_message("Could not save the TripalEntity: " . $e->getMessage(), "error");
       return FALSE;
     }
   }

+ 1 - 1
tripal/includes/TripalTermController.inc

@@ -105,7 +105,7 @@ class TripalTermController extends EntityAPIController {
     catch (Exception $e) {
       $transaction->rollback();
       watchdog_exception('tripal_entity', $e);
-      drupal_set_message("Could not save the entity:" . $e->getMessage(), "error");
+      drupal_set_message("Could not save the TripalTerm:" . $e->getMessage(), "error");
       return FALSE;
     }
   }

+ 1 - 1
tripal/includes/TripalVocabController.inc

@@ -106,7 +106,7 @@ class TripalVocabController extends EntityAPIController {
     catch (Exception $e) {
       $transaction->rollback();
       watchdog_exception('tripal_entity', $e);
-      drupal_set_message("Could not save the entity:" . $e->getMessage(), "error");
+      drupal_set_message("Could not save the TripalVocab:" . $e->getMessage(), "error");
       return FALSE;
     }
   }

+ 2 - 1
tripal_chado/api/modules/tripal_chado.cv.api.inc

@@ -414,7 +414,8 @@ function chado_update_cvtermpath($cv_id, $job_id = NULL){
     // Rollback any database changes
     $transaction->rollback();
     throw $e;
-  } finally {
+  } 
+  finally {
     // Set the database back
     chado_set_active($prev_db);
   }

+ 9 - 10
tripal_chado/api/tripal_chado.api.inc

@@ -98,10 +98,9 @@ function chado_publish_records($values, $job = NULL) {
     ->condition('bundle_id', $bundle->id)
     ->execute()
     ->fetchObject();
-  if(!$chado_bundle) {
-    tripal_report_error($message_type, TRIPAL_ERROR,
-      "Cannot find mapping of bundle to Chado tables. Could not publish record.",
-      [], $message_opts);
+  if (!$chado_bundle) {
+    tripal_report_error('tripal_chado', TRIPAL_ERROR,
+        "Cannot find mapping of bundle to Chado tables. Could not publish record.");
     return FALSE;
   }
 
@@ -236,13 +235,13 @@ function chado_publish_records($values, $job = NULL) {
         $total_published, $count, $complete * 3, number_format(memory_get_usage()), number_format((microtime(true) - $started_at)/60, 2));
     }
 
-    // There is no need to cache transactions since Drupal handles nested transactions
-    // "by performing no transactional operations (as far as the database sees) within
-    // the inner nesting layers". Effectively, Drupal ensures nested trasactions work the
-    // same as passing a transaction through to the deepest level and not starting a new
-    // transaction if we are already in one.
+    // There is no need to cache transactions since Drupal handles nested 
+    // transactions "by performing no transactional operations (as far as the 
+    // database sees) within the inner nesting layers". Effectively, Drupal 
+    // ensures nested trasactions work the same as passing a transaction 
+    // through to the deepest level and not starting a new transaction if we 
+    // are already in one.
     $transaction = db_transaction();
-
     try {
       $i = 0;
       while($record = $records->fetchObject()) {

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

@@ -32,7 +32,8 @@ class schema__additional_type_widget extends ChadoFieldWidget {
       $value = $items[0]['value'];
     }
 
-    if (array_key_exists('values', $form_state) and array_key_exists($field_name, $form_state['values'])) {
+    if (array_key_exists('values', $form_state) and array_key_exists($field_name, $form_state['values']) and 
+        array_key_exists('value', $form_state['values'][$field_name][$langcode][$delta])) {
       $type_id = $form_state['values'][$field_name][$langcode][$delta]['value'];
     }
 

+ 3 - 0
tripal_chado/includes/tripal_chado.bundle.inc

@@ -56,6 +56,9 @@ function tripal_chado_bundle_create($bundle, $storage_args) {
     if (array_key_exists('type_value', $storage_args)) {
       $record['type_value'] = $storage_args['type_value'];
     }
+    if (array_key_exists('base_type_id', $storage_args)) {
+      $record['base_type_id'] = $storage_args['base_type_id'];
+    }
     $success = drupal_write_record('chado_bundle', $record);
     if (!$success) {
       throw new Exception('Cannot create content type. Problem associating type with Chado.');

+ 7 - 2
tripal_chado/includes/tripal_chado.entity.inc

@@ -23,6 +23,9 @@ function tripal_chado_entity_create(&$entity, $type, $bundle = NULL) {
       $entity->chado_table =  NULL;
       $entity->chado_column = NULL;
       $entity->chado_linker = NULL;
+      $entity->chado_type_id = NULL;
+      $entity->chado_type_value = NULL;
+      $entity->chado_base_type_id = NULL;
 
       // Add in the Chado table information for this entity type.
       if (!$bundle) {
@@ -32,6 +35,9 @@ function tripal_chado_entity_create(&$entity, $type, $bundle = NULL) {
         $entity->chado_table = $bundle->data_table;
         $entity->chado_column = $bundle->type_column;
         $entity->chado_linker = $bundle->type_linker_table;
+        $entity->chado_type_id = $bundle->type_id;
+        $entity->chado_type_value = $bundle->type_value;
+        $entity->chado_base_type_id = $bundle->base_type_id;
       }
     }
     if (!property_exists($entity, 'chado_record')) {
@@ -74,6 +80,7 @@ function tripal_chado_entity_load($entities, $type) {
           $bundle->type_column = $chado_bundle->type_column;
           $bundle->type_id = $chado_bundle->type_id;
           $bundle->type_value = $chado_bundle->type_value;
+          $bundle->base_type_id = $chado_bundle->base_type_id;
         }
       }
     }
@@ -94,8 +101,6 @@ function tripal_chado_entity_load($entities, $type) {
         if (!$bundle) {
           continue;
         }
-        // TODO: this may need fixing. The chado_column may not always
-        // be the type_id of the base table. Is it expected to be so here?
         $entity->chado_table = $bundle->data_table;
         $entity->chado_column = $bundle->type_column;
 

File diff suppressed because it is too large
+ 811 - 385
tripal_chado/includes/tripal_chado.field_storage.inc


+ 23 - 9
tripal_chado/includes/tripal_chado.fields.inc

@@ -19,8 +19,10 @@ function tripal_chado_bundle_fields_info($entity_type, $bundle) {
     'chado_cvterm_id' => $chado_bundle->type_id,
     'chado_table' => $chado_bundle->data_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
+    'chado_type_id' => $chado_bundle->type_id,
     'chado_type_column' => $chado_bundle->type_column,
     'chado_type_value' => $chado_bundle->type_value,
+    'chado_base_type_id' => $chado_bundle->base_type_id,
   );
 
   $info = array();
@@ -220,13 +222,16 @@ function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type,
   $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
   $type_value = $details['chado_type_value'];
+  $base_type_id = $details['chado_base_type_id'];
 
   $schema = chado_get_schema($table_name);
 
-  // Handle type_id fields that are not the type_column, except for the
-  // organism type_id as that is handled by the taxrank__infraspecific_taxon
-  // field.
-  if (array_key_exists('type_id', $schema['fields']) and 'type_id' != $type_column and $table_name != 'organism') {
+  // Add the additional_type field to all tables with a type_id that is not used
+  // as the type column nor has a $base_type_id (i.e. the content type uses a 
+  // prop or linker table to resolve the type).
+  if (array_key_exists('type_id', $schema['fields']) and 'type_id' != $type_column and 
+      !$base_type_id and $table_name != 'organism') {    
+
     $field_name = 'schema__additional_type';
     $field_type = 'schema__additional_type';
     $info[$field_name] = array(
@@ -713,7 +718,7 @@ function tripal_chado_bundle_fields_info_linker(&$info, $details, $entity_type,
   $prop_table = $table_name . 'prop';
   if (chado_table_exists($prop_table)) {
     
-    $props = tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column);
+    $props = tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column, $cvterm_id);
     foreach ($props as $term) {
           
       $field_name = strtolower(preg_replace('/[^\w]/','_', $term->dbxref_id->db_id->name . '__' . $term->name));
@@ -855,8 +860,10 @@ function tripal_chado_bundle_instances_info($entity_type, $bundle) {
     'chado_cvterm_id' => $chado_bundle->type_id,
     'chado_table' => $chado_bundle->data_table,
     'chado_type_table' => $chado_bundle->type_linker_table,
+    'chado_type_id' => $chado_bundle->type_id,
     'chado_type_column' => $chado_bundle->type_column,
     'chado_type_value' => $chado_bundle->type_value,
+    'chado_base_type_id' => $chado_bundle->base_type_id,
   );
 
   $info = array();
@@ -1280,10 +1287,14 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
   $type_column = $details['chado_type_column'];
   $cvterm_id  = $details['chado_cvterm_id'];
   $type_value = $details['chado_type_value'];
+  $base_type_id = $details['chado_base_type_id'];  
   $schema = chado_get_schema($table_name);
 
-  // An additional type for publications
-  if (array_key_exists('type_id', $schema['fields']) and 'type_id' != $type_column and $table_name != 'organism') {
+  // Add the additional_type field to all tables with a type_id that is not used
+  // as the type column nor has a $base_type_id (i.e. the content type uses a
+  // prop or linker table to resolve the type).
+  if (array_key_exists('type_id', $schema['fields']) and 'type_id' != $type_column and
+      !$base_type_id and $table_name != 'organism') { 
     $field_name = 'schema__additional_type';
     $is_required = FALSE;
     if (array_key_exists('not null', $schema['fields']['type_id']) and
@@ -2478,7 +2489,7 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
   $prop_table = $table_name . 'prop';
   if (chado_table_exists($prop_table)) {
 
-    $props = tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column);
+    $props = tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column, $cvterm_id);
     foreach ($props as $term) {     
 
        $field_name = strtolower(preg_replace('/[^\w]/','_', $term->dbxref_id->db_id->name . '__' . $term->name));
@@ -2750,7 +2761,7 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
  * @return
  *   An array of cvterm objects for the properties to be added as fields.
  */
-function tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column) {
+function tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column, $cvterm_id) {
   
   $tschema = chado_get_schema($table_name);
   $schema = chado_get_schema($prop_table);
@@ -2817,6 +2828,9 @@ function tripal_chado_bundle_get_properties($table_name, $prop_table, $type_tabl
   // Iterate through all of the properties and do some final checks to see
   // which ones should be added.
   $prop_arr = [];
+  if (!$props) {
+    return $prop_arr;
+  }
   while ($prop = $props->fetchObject()) {
     $term = chado_generate_var('cvterm', array('cvterm_id' => $prop->type_id));
     $term = chado_expand_var($term, 'field', 'cvterm.definition');

+ 1 - 1
tripal_chado/includes/tripal_chado.vocab_storage.inc

@@ -368,4 +368,4 @@ function tripal_chado_vocab_import_form_validate($form, &$form_state) {
 function tripal_chado_vocab_import_form_submit($form, &$form_state) {
   module_load_include('inc', 'tripal_chado', 'includes/loaders/tripal_chado.obo_loader');
   return tripal_cv_obo_form_submit($form, $form_state);
-}
+}

+ 24 - 0
tripal_chado/tripal_chado.install

@@ -619,6 +619,11 @@ function tripal_chado_chado_bundle_schema() {
         'type' => 'text',
         'not null' => FALSE,
         'default' => '',
+      ),
+      'base_type_id' => array(
+        'description' => 'If a property table is used for a linker, and if the base table requires a type_id then this is the type that should be used on insert of new records in the base table.',
+        'size' => 'big',        
+        'type' => 'int',
       )
     ),
     'indexes' => array(
@@ -1595,4 +1600,23 @@ function tripal_chado_update_7329() {
   }
 }
 
+# tripal_chado_update_7330 is in PR #596 and adds categories for content types.
 
+/**
+ * Adds a base_type_id to the chado_bundle table.
+ */
+function tripal_chado_update_7331() {
+  try {
+    if (!db_field_exists('chado_bundle', 'base_type_id')) {
+      db_add_field('chado_bundle', 'base_type_id', [
+        'description' => 'If a property table is used for a linker, and if the base table requires a type_id then this is the type that should be used on insert of new records in the base table.',
+        'size' => 'big',
+        'type' => 'int',
+      ]);
+    }
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}

Some files were not shown because too many files changed in this diff