Browse Source

Added dbxref custom field and updated field storage to handle nexted fields

Stephen Ficklin 9 years ago
parent
commit
c548b376ec

+ 7 - 0
tripal_entities/includes/tripal_entities.admin.inc

@@ -380,6 +380,8 @@ function tripal_entities_add_bundle_fields($entity_type_name, $bundle_name, $cvt
   // Iterate through the tables.
   foreach ($tables as $table) {
     $tablename = $table->data_table;
+    $type_table = $table->type_table;
+    $type_field = $table->field;
 
     // We only want to look at base tables.
     if ($tablename == 'cvterm_dbxref' || $tablename == 'cvterm_relationship' ||
@@ -402,6 +404,11 @@ function tripal_entities_add_bundle_fields($entity_type_name, $bundle_name, $cvt
         continue;
       }
 
+      // Skip the type field.
+      if ($tablename == $type_table and $column_name == $field_type) {
+        continue;
+      }
+
       // Determine if the field is required.
       $is_required = 0;
       if (array_key_exists('not null', $details)) {

+ 137 - 122
tripal_entities/includes/tripal_entities.field_storage.inc

@@ -17,44 +17,54 @@ function tripal_entities_field_storage_info() {
  */
 function tripal_entities_field_storage_write($entity_type, $entity, $op, $fields) {
 
-  switch ($op) {
-    case FIELD_STORAGE_INSERT:
-      // Use the cvterm_id to look up tables where this term is used
-      $sel_values = array(
-        'term_id' => array(
-          'cvterm_id' => $entity->cvterm_id,
-        ),
-      );
-      $term_usage = chado_generate_var('tripal_term_usage', $sel_values, array('return_array' => 1));
+  // Conver the fields into a key/value list of fields and their values.
+  $field_vals = tripal_entities_field_storage_reformat_fields($fields, $entity_type, $entity);
+  $transaction = db_transaction();
+  try {
+    switch ($op) {
+      case FIELD_STORAGE_INSERT:
+        // Use the cvterm_id to look up tables where this term is used
+        $sel_values = array(
+          'term_id' => array(
+            'cvterm_id' => $entity->cvterm_id,
+          ),
+        );
+        $term_usage = chado_generate_var('tripal_term_usage', $sel_values, array('return_array' => 1));
 
-      // For each table that uses this term, insert the field recursively
-      foreach ($term_usage as $usage) {
-        $data_table = $usage->data_table;
-        //$type_table = $usage->type_table;
-        $type_field = $usage->field;
-        tripal_entities_field_storage_write_recursive($entity_type, $entity,
-          $op, $fields, $data_table, $type_field);
-      }
-      break;
+        // For each table that uses this term, insert the field recursively
+        foreach ($term_usage as $usage) {
+          $data_table = $usage->data_table;
+          //$type_table = $usage->type_table;
+          $type_field = $usage->field;
+          tripal_entities_field_storage_write_recursive($entity_type, $entity,
+            $op, $field_vals, $data_table, $type_field);
+        }
+        break;
 
-    case FIELD_STORAGE_UPDATE :
+      case FIELD_STORAGE_UPDATE :
 
-      // Get the base table and record id for the fields of this entity.
-      $details = db_select('chado_entity', 'ce')
-        ->fields('ce')
-        ->condition('entity_id', $entity->id)
-        ->execute()
-        ->fetchObject();
-      $tablename = $details->data_table;
-      $type_field = $details->field;
-      $record_id = $details->record_id;
+        // Get the base table and record id for the fields of this entity.
+        $details = db_select('chado_entity', 'ce')
+          ->fields('ce')
+          ->condition('entity_id', $entity->id)
+          ->execute()
+          ->fetchObject();
+        $tablename = $details->data_table;
+        $type_field = $details->field;
+        $record_id = $details->record_id;
 
-      tripal_entities_field_storage_write_recursive($entity_type, $entity,
-        $op, $fields, $tablename, $type_field, $record_id);
-      if (!$details) {
-        // TODO: what to do if record is missing!
-      }
-      break;
+        tripal_entities_field_storage_write_recursive($entity_type, $entity,
+          $op, $field_vals, $tablename, $type_field, $record_id);
+        if (!$details) {
+          // TODO: what to do if record is missing!
+        }
+        break;
+    }
+  }
+  catch (Exception $e) {
+    $transaction->rollback();
+    watchdog_exception('tripal_feature', $e);
+    drupal_set_message('Could not write record to Chado.  See recent logs', 'error');
   }
 }
 
@@ -62,7 +72,7 @@ function tripal_entities_field_storage_write($entity_type, $entity, $op, $fields
  * Implements hook_field_storage_write_recursive().
  */
 function tripal_entities_field_storage_write_recursive($entity_type, $entity,
-    $op, $fields, $tablename, $type_field = NULL, $record_id = NULL, $depth = 0) {
+    $op, $field_vals, $tablename, $type_field = NULL, $record_id = NULL, $depth = 0) {
 
   // Intialize the values array and $record_id;
   $values = array();
@@ -89,24 +99,13 @@ function tripal_entities_field_storage_write_recursive($entity_type, $entity,
       // loop through the $fk_fields again.
       $fkey_fields_list[] = $local_id;
 
-      // Recurse differently if this is an insert or an update.
+      // Recurse on the FK field.  Pass in the ID for the FK field if one
+      // exists in the $field_vals;
       $fk_val = NULL;
-      switch ($op) {
-        case FIELD_STORAGE_INSERT:
-          // On an insert we do not pass in a $record_id because
-          // we don't have any.
-          $fk_val = tripal_entities_field_storage_write_recursive($entity_type,
-            $entity, $op, $fields, $fk_table, NULL, NULL, $depth + 1);
-          break;
-        case FIELD_STORAGE_UPDATE:
-          // On an update we must get the value of the FK field. This should
-          // be present as a field, so get it's value and pass it in as the
-          // $record_id field.
-          $fk_val = tripal_entities_get_field_value($tablename . '__' . $local_id, $entity, $entity_type);
-          $fk_val = tripal_entities_field_storage_write_recursive($entity_type,
-            $entity, $op, $fields, $fk_table, NULL, $fk_val, $depth + 1);
-          break;
-      }
+      $fk_field_name = $tablename . '__' . $local_id;
+      $fk_val = array_key_exists($fk_field_name, $field_vals) ? $field_vals[$fk_field_name] : NULL;
+      $fk_val = tripal_entities_field_storage_write_recursive($entity_type,
+        $entity, $op, $field_vals, $fk_table, NULL, $fk_val, $depth + 1);
       if ($fk_val) {
         $values[$local_id] = $fk_val;
       }
@@ -116,95 +115,71 @@ function tripal_entities_field_storage_write_recursive($entity_type, $entity,
   // STEP 2: Loop through the incoming fields.
   // Loop through the fields passed to the function and find any that
   // are for this table.  Then add their values to the $values array.
-  foreach ($fields as $field_id) {
-    $field = field_info_field_by_id($field_id);
-    $field_name = $field['field_name'];
-
+  foreach ($field_vals as $field_name => $field_val) {
+    // If the field value is empty then continue.
+    if (!$field_val) {
+      continue;
+    }
     if (preg_match('/^' . $tablename . '__(.*)/', $field_name, $matches)) {
       $chado_field = $matches[1];
 
+      // Skip the Pkey field. We won't ever insert a primary key and if
+      // one is provided in the fields then we use it for matching on an
+      // update.  We don't add it to the $values array in either case.
+      if ($chado_field == $pkey_field) {
+        continue;
+      }
+
       // Skip FK fields as those should already have been dealt with the
       // recursive code above.
-      if (in_array($field_id, $fkey_fields_list)) {
+      if (in_array($chado_field, $fkey_fields_list)) {
         continue;
       }
 
       // Add the value of the field to the $values arr for later insert/update.
-      $values[$chado_field] = tripal_entities_get_field_value($tablename . '__' . $chado_field, $entity, $entity_type);
+      $values[$chado_field] = $field_vals[$field_name];
     }
   }
 
   // STEP 3: Insert/Update the record.
   // If there are no values then return.
-  $entity->storage = array();
-  switch ($op) {
-    case FIELD_STORAGE_INSERT:
-      if (count($values) == 0) {
-        return NULL;
-      }
-      // Inser the values array as a new record in the table.
-      $record = chado_insert_record($tablename, $values);
-      if ($record === FALSE) {
-        drupal_set_message('Could not insert Chado record.', 'error');
-      }
-      $record_id = $record[$pkey_field];
-
-      // Add a record to the chado_entity table so that the data for the
-      // fields can be pulled from Chado when loaded the next time. Only do
-      // this for the base table record.
-      if ($depth == 0) {
-        $record = array(
-          'entity_id' => $entity->id,
-          'record_id' => $record_id,
-          'data_table' => $tablename,
-          'type_table' => $tablename, // TODO: this must be fixed.
-          'field' => $type_field,
-        );
-        $success = drupal_write_record('chado_entity', $record);
-        if (!$success) {
-          drupal_set_message('Unable to insert new data.', 'error');
-        }
-      }
-      break;
-    case FIELD_STORAGE_UPDATE:
-      // If we don't have any values we are not going to update the
-      // record because there is nothing to udpate, do just return the
-      // record_id.
-      if (count($values) == 0) {
-        return $record_id;
-      }
-      $match[$pkey_field] = $record_id;
-
-      chado_update_record($tablename, $match, $values);
-      break;
+  if (count($values) == 0) {
+    return $record_id;
   }
-  return $record_id;
-}
+  // If we don't have an incoming record ID then this is an insert.
+  if (!$record_id) {
+    // Insert the values array as a new record in the table.
+    $record = chado_insert_record($tablename, $values);
+    if ($record === FALSE) {
+      drupal_set_message('Could not insert Chado record into table, "$table".', 'error');
+    }
+    $record_id = $record[$pkey_field];
 
-/**
- *
- */
-function tripal_entities_get_field_value($field_name, $entity, $entity_type) {
-  $value = NULL;
-  $field = field_info_field($field_name);
-  if (!$field) {
-    return $value;
+    // Add a record to the chado_entity table so that the data for the
+    // fields can be pulled from Chado when loaded the next time. Only do
+    // this for the base table record.
+    if ($depth == 0) {
+      $record = array(
+        'entity_id' => $entity->id,
+        'record_id' => $record_id,
+        'data_table' => $tablename,
+        'type_table' => $tablename, // TODO: this must be fixed.
+        'field' => $type_field,
+      );
+      $success = drupal_write_record('chado_entity', $record);
+      if (!$success) {
+        drupal_set_message('Unable to insert new Chado entity.', 'error');
+      }
+    }
   }
-
-  // Currently, we only support one language, but for for the sake of
-  // thoroughness we'll iterate through all possible languages.
-  $all_languages = field_available_languages($entity_type, $field);
-  $field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name));
-  foreach ($field_languages as $langcode) {
-    $items = (array) $entity->{$field_name}[$langcode];
-    // The number of items is related to the cardinatily of the field.
-    // We should always only have one item because this is a field in a
-    // table. But we loop anyway.
-    foreach ($items as $delta => $item) {
-      $value = $item['value'];
+  // We have an incoming record_id so this is an update.
+  else {
+    $match[$pkey_field] = $record_id;
+    if (!chado_update_record($tablename, $match, $values)) {
+      drupal_set_message('Could not update Chado record for in table, "$table".', 'error');
     }
   }
-  return $value;
+  return $record_id;
 }
 
 /**
@@ -272,4 +247,44 @@ function tripal_entities_field_storage_load($entity_type, $entities, $age, $fiel
       }
     }
   }
+}
+
+/**
+ * Iterates through all of the fields reformats to a key/value array.
+ *
+ * @param $fields
+ */
+function tripal_entities_field_storage_reformat_fields($fields, $entity_type, $entity) {
+  $new_fields = array();
+  foreach ($fields as $field_id => $ids) {
+    $field = field_info_field_by_id($field_id);
+    $field_name = $field['field_name'];
+    // Currently, we only support one language, but for for the sake of
+    // thoroughness we'll iterate through all possible languages.
+    $all_languages = field_available_languages($entity_type, $field);
+    $field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name));
+    foreach ($field_languages as $langcode) {
+      $items = (array) $entity->{$field_name}[$langcode];
+      // The number of items is related to the cardinatily of the field.
+      // We should always only have one item because this is a field in a
+      // table. But we loop anyway.
+      foreach ($items as $delta => $item) {
+        // If the $value is an array then this field has nested fields. We
+        // want to add those to our $new_fields list.
+        if (is_array($item['value'])) {
+          foreach ($item['value'] as $children) {
+            foreach ($children as $child_field_name => $child_value) {
+              if (preg_match('/^.*?__.*?$/', $child_field_name)) {
+                $new_fields[$child_field_name] = $child_value;
+              }
+            }
+          }
+        }
+        else {
+          $new_fields[$field_name] = $item['value'];
+        }
+      }
+    }
+  }
+  return $new_fields;
 }

+ 146 - 40
tripal_entities/includes/tripal_entities.fields.inc

@@ -19,7 +19,7 @@ function tripal_entities_field_info() {
     ),
     'dbxref_id' => array(
       'label' => t('Primary Cross-reference'),
-      'description' => t('If this record is found in another online database you may link to it by providing a database cross reference.'),
+      'description' => t('This record can be cross-referenced with a record in another online database. This field is intended for the most prominent reference.  At a minimum, the database and accession must be provided.'),
       'default_widget' => 'tripal_entities_primary_dbxref_widget',
       'default_formatter' => 'tripal_entities_primary_dbxref_formatter',
       'settings' => array(),
@@ -44,7 +44,8 @@ function tripal_entities_field_widget_info() {
     ),
     'tripal_entities_primary_dbxref_widget' => array(
       'label' => t('Primary Cross-reference'),
-      'field types' => array('dbxref_id')
+      'field types' => array('dbxref_id'),
+      'description' => t('This record can be cross-referenced with a record in another online database. This field is intended for the most prominent reference.  At a minimum, the database and accession must be provided.'),
     ),
   );
 }
@@ -77,7 +78,7 @@ function tripal_entities_field_formatter_info() {
 function tripal_entities_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
   $element = array();
   switch ($display['type']) {
-    // This formatter simply outputs the field as text and with a color.
+
     case 'tripal_entities_organism_formatter':
       foreach ($items as $delta => $item) {
         $organism = chado_select_record('organism', array('genus', 'species'), array('organism_id' => $item['value']));
@@ -90,6 +91,23 @@ function tripal_entities_field_formatter_view($entity_type, $entity, $field, $in
         );
       }
       break;
+    case 'tripal_entities_primary_dbxref_formatter':
+      foreach ($items as $delta => $item) {
+        $accession = '';
+        if ($item['value']) {
+          $dbxref = chado_generate_var('dbxref', array('dbxref_id' => $item['value']));
+          $accession = $dbxref->db_id->name . ':' . $dbxref->accession;
+          if ($dbxref->db_id->urlprefix) {
+            $accession = l($accession, $dbxref->db_id->urlprefix . '/' . $dbxref->accession);
+          }
+        }
+        $element[$delta] = array(
+          // We create a render array to produce the desired markup,
+          '#type' => 'markup',
+          '#markup' => $accession,
+        );
+      }
+      break;
   }
   return $element;
 }
@@ -120,40 +138,63 @@ function tripal_entities_field_widget_form(&$form, &$form_state, $field,
       $element['value'] = $widget;
       break;
     case 'tripal_entities_primary_dbxref_widget':
+      // Get the field defaults from the database if a record exists.
+      $dbxref_id = '';
+      $db_id = '';
+      $accession = '';
+      $version = '';
+      $description = '';
+      if ($items[0]['value']) {
+        $dbxref = chado_generate_var('dbxref', array('dbxref_id' => $items[0]['value']));
+        $dbxref_id = $dbxref->dbxref_id;
+        $db_id = $dbxref->db_id->db_id;
+        $accession  = $dbxref->accession;
+        $version = $dbxref->version;
+        $description = $dbxref->description;
+      }
+
+      $schema = chado_get_schema('dbxref');
       $options = tripal_get_db_select_options();
       $widget += array(
-        'dbxref_id' => array(
-          '#type' => 'fieldset',
-          '#title' => $element['#title'],
-          '#description' => $element['#description'],
-          '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
-          '#delta' => $delta,
-          array(
-            'dbxref_id_db_id' => array(
-              '#type' => 'select',
-              '#title' => t('Database'),
-              '#options' => $options,
-              '#default_value' => count($items) > 0 ? $items[0]['value'] : 0,
-              '#required' => $element['#required'],
-            ),
-            'dbxref_id_accession' => array(
-              '#type' => 'textfield',
-              '#title' => t('Accession'),
-              '#default_value' => count($items) > 0 ? $items[0]['value'] : '',
-              '#required' => $element['#required'],
-            ),
-            'dbxref_id_version' => array(
-              '#type' => 'textfield',
-              '#title' => t('Version'),
-              '#default_value' => count($items) > 0 ? $items[0]['value'] : '',
-              '#required' => $element['#required'],
-            ),
-            'dbxref_id_description' => array(
-              '#type' => 'textfield',
-              '#title' => t('Description'),
-              '#default_value' => count($items) > 0 ? $items[0]['value'] : '',
-              '#required' => $element['#required'],
-            ),
+        '#element_validate' => array('tripal_entities_primary_dbxref_widget_validate'),
+        '#type' => 'fieldset',
+        '#title' => $element['#title'],
+        '#description' => 'Hi', // $element['#description'],
+        '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
+        '#delta' => $delta,
+        '#theme' => 'tripal_entities_primary_dbxref_widget',
+        array(
+          $element['#field_name'] => array(
+            '#type' => 'hidden',
+            '#default_value' => $dbxref_id,
+          ),
+          'dbxref__db_id' => array(
+            '#type' => 'select',
+            '#title' => t('Database'),
+            '#options' => $options,
+            '#required' => $element['#required'],
+            '#default_value' => $db_id,
+          ),
+          'dbxref__accession' => array(
+            '#type' => 'textfield',
+            '#title' => t('Accession'),
+            '#default_value' => $accession,
+            '#required' => $element['#required'],
+            '#maxlength' => array_key_exists('length', $schema['fields']['accession']) ? $schema['fields']['accession']['length'] : 255,
+            '#size' => 15,
+          ),
+          'dbxref__version' => array(
+            '#type' => 'textfield',
+            '#title' => t('Version'),
+            '#default_value' => $version,
+            '#maxlength' => array_key_exists('length', $schema['fields']['version']) ? $schema['fields']['version']['length'] : 255,
+            '#size' => 5,
+          ),
+          'dbxref__description' => array(
+            '#type' => 'textfield',
+            '#title' => t('Description'),
+            '#default_value' => $description,
+            '#size' => 20,
           ),
         ),
       );
@@ -177,16 +218,81 @@ function tripal_entities_field_is_empty($item, $field) {
  * Callback function for validating the tripal_entities_organism_select_widget.
  */
 function tripal_entities_organism_select_widget_validate($element, &$form_state) {
+  $field_name = $element['#field_name'];
+
+  $organism_id = tripal_entities_get_field_form_values($field_name, $form_state);
 
+  if (count($organism_id) == 0) {
+    form_error($element, t("Please specify an organism that already exists in the database."));
+  }
+}
+/**
+ * Callback function for validating the tripal_entities_organism_select_widget.
+ */
+function tripal_entities_primary_dbxref_widget_validate($element, &$form_state) {
   $field_name = $element['#field_name'];
-  // Make sure we have a valid organism
+
+  // Get the field values.
+  $db_id = tripal_entities_get_field_form_values($field_name, $form_state, "dbxref__db_id");
+  $accession = tripal_entities_get_field_form_values($field_name, $form_state, "dbxref__accession");
+  $version = tripal_entities_get_field_form_values($field_name, $form_state, "dbxref__version");
+  $description = tripal_entities_get_field_form_values($field_name, $form_state, "dbxref__description");
+
+  // Make sure that if a database ID is provided that an accession is also
+  // provided.  Here we use the form_set_error function rather than the
+  // form_error function because the form_error will add a red_highlight
+  // around all of the fields in the fieldset which is confusing as it's not
+  // clear to the user what field is required and which isn't. Therefore,
+  // we borrow the code from the 'form_error' function and append the field
+  // so that the proper field is highlighted on error.
+  if (count($db_id) == 0 and count($accession) > 0) {
+    form_set_error(implode('][', $element ['#parents']) . '][0][dbxref__db_id', t("A database and the accession must both be provided for the primary cross reference."));
+  }
+  if (count($db_id) > 0 and count($accession) == 0) {
+    form_set_error(implode('][', $element ['#parents']) . '][0][dbxref__accession', t("A database and the accession must both be provided for the primary cross reference."));
+  }
+}
+
+/**
+ * Theme function for the primary_dbxref_widget.
+ *
+ * @param $variables
+ */
+function theme_tripal_entities_primary_dbxref_widget($variables) {
+  $element = $variables['element'];
+  $layout = "
+    <div class=\"primary-dbxref-widget\">
+      <div class=\"primary-dbxref-widget-item\">" .
+        drupal_render($element[0]['dbxref__db_id']) . "
+      </div>
+      <div class=\"primary-dbxref-widget-item\">" .
+        drupal_render($element[0]['dbxref__accession']) . "
+      </div>
+      <div class=\"primary-dbxref-widget-item\">" .
+        drupal_render($element[0]['dbxref__version']) . "
+      </div>
+      <div class=\"primary-dbxref-widget-item\">" .
+        drupal_render($element[0]['dbxref__description']) . "
+      </div>
+    </div>
+  ";
+  return $layout;
+}
+
+/**
+ * Returns the values of the field from the $form_state.
+ */
+function tripal_entities_get_field_form_values($field_name, $form_state, $child = NULL) {
+  $values = array();
   foreach ($form_state['values'][$field_name] as $langcode => $items) {
     foreach ($items as $delta => $value) {
-      $organism_id = chado_select_record('organism', array('organism_id'),
-        array('organism_id' => $value['value']), array('has_record' => TRUE));
-      if (!$organism_id) {
-        form_error($element, t("Please specify an organism that already exists in the database."));
+      if ($child and array_key_exists($child, $value['value'][0]) and $value['value'][0][$child]) {
+        $values[] = $value['value'][0][$child];
+      }
+      else if (!$child and $value['value']) {
+        $values[] = $value['value'];
       }
     }
   }
+  return $values;
 }

+ 6 - 0
tripal_entities/theme/css/tripal_entities.css

@@ -0,0 +1,6 @@
+@CHARSET "UTF-8";
+
+.primary-dbxref-widget-item {
+   float: left;
+   margin-right: 10px;
+}

+ 2 - 0
tripal_entities/tripal_entities.info

@@ -6,6 +6,8 @@ package = Tripal
 version = 7.x-2.0
 configure = admin/tripal/chado/tripal_entities
 
+stylesheets[all][] = theme/css/tripal_entities.css
+
 dependencies[] = tripal_core
 dependencies[] = tripal_views
 dependencies[] = tripal_db

+ 4 - 0
tripal_entities/tripal_entities.module

@@ -99,6 +99,10 @@ function tripal_entities_theme($existing, $type, $theme, $path) {
       'template' => 'tripal_entity',
       'path' => "$path/theme/templates"
     ),
+    // Field themes.
+    'tripal_entities_primary_dbxref_widget' => array(
+      'render element' => 'element',
+    ),
   );
 }