Browse Source

Added support of sub elements of TripalFields for Drpual Views

Stephen Ficklin 7 years ago
parent
commit
ae29c0c9e5

+ 21 - 1
tripal/api/tripal.fields.api.inc

@@ -224,4 +224,24 @@ function tripal_get_field_item_keyval($items, $delta, $key, $default='') {
   return $items[$delta][$key];
 }
 
-
+/**
+ * Formats an element of a TripalField for use by Drupal Views.
+ *
+ * Sometimes the value of TripalField can be more than just a single scalar. In
+ * this case the value is an array of key value pairs where each key is a
+ * controlled vocabulary term.  In order to support fields, filtering and
+ * sorting by these sub elements using Drupal Views, the TripalField
+ * implementation must provide some help to Views by describing these elements,
+ * and then implementing a query() function to support them.  However, the
+ * naming of sub elements must follow a set convention. This function
+ * guarantees proper naming for sub elements.
+ *
+ * @param $field_name
+ *   The name of the field to which the element belongs.
+ * @param $term
+ *   The term object as returned by tripal_get_term_details();
+ */
+function tripal_format_views_field_element($field_name, $term) {
+  $element_name = $term['vocabulary']['short_name'] . '__' . $term['accession'];
+  return $field_name . '.' . $element_name;
+}

+ 25 - 46
tripal/includes/TripalFields/TripalField.inc

@@ -79,29 +79,6 @@ class TripalField {
   // the user but otherwise provides no data.
   public static $no_data = FALSE;
 
-  // In order for this field to integrate with Drupal Views, a set of
-  // handlers must be specififed.  These include handlers for
-  // the field, for the filter, and the sort.  Within this variable,
-  // the key must be one of: field, filter, sort; and the value
-  // is the settings for those handlers as would be provided by
-  // a hook_views_data().  The following defaults make a field visible
-  // using the default formatter of the field, allow for filtering using
-  // a string value and sortable.  in order for filters to work you
-  // must implement the query() function.
-  public static $default_view_handlers = array(
-    'field' => array(
-      'handler' => 'tripal_views_handler_field',
-      'click sortable' => TRUE,
-    ),
-    'filter' => array(
-      'handler' => 'tripal_views_handler_filter_string',
-    ),
-    'sort' => array(
-      'handler' => 'views_handler_sort',
-    ),
-  );
-
-
   // --------------------------------------------------------------------------
   //              PROTECTED CLASS MEMBERS -- DO NOT OVERRIDE
   // --------------------------------------------------------------------------
@@ -299,36 +276,38 @@ class TripalField {
    * An array of views data, in the same format as the return value of
    * hook_views_data().
    *
+   * @param $view_base_id
+   *   Views was originally designed to integrate with SQL tables. And
+   *   each field is associated with a table.  Because these are TripalFields
+   *   and views is not directly querying the tables it doesn't make sense to
+   *   associate fields with a table, but we must associate the fields with
+   *   the bundle.  Each bundle is uniquely identified with the $view_base_id
+   *   that is passed here.
+   *
+   * @return
+   *   An associative array describing the data structure. Primary key is the
+   *   name used internally by Views for the bundle that is provided by
+   *   the $view_base_id. The returned array should be compatible with the
+   *   instructions provided by the hook_views_data() function.
    */
-  public function viewsData() {
+  public function viewsData($view_base_id) {
     $data = array();
-
-    $class = get_called_class();
-
-    $bundle_name = $this->instance['bundle'];
     $field_name = $this->field['field_name'];
 
-    // Fields should be associated with the bundle's term identifier
-    // (i.e. [vocab]__[accession].
-    $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));
-    $term = tripal_load_term_entity(array('term_id' => $bundle->term_id));
-    $table = $term->vocab->vocabulary . '__' . $term->accession;
-
-    $handlers = $class::$default_view_handlers;
-    $data[$table][$field_name] = array(
+    $data[$view_base_id][$field_name] = array(
       'title' => $this->instance['label'],
       'help' => $this->instance['description'],
+      'field' => array(
+        'handler' => 'tripal_views_handler_field',
+        'click sortable' => TRUE,
+      ),
+      'filter' => array(
+        'handler' => 'tripal_views_handler_filter_string',
+      ),
+      'sort' => array(
+        'handler' => 'views_handler_sort',
+      )
     );
-    if (array_key_exists('field', $handlers)) {
-      $data[$table][$field_name]['field'] = $handlers['field'];
-    }
-    if (array_key_exists('sort', $handlers)) {
-      $data[$table][$field_name]['sort'] = $handlers['sort'];
-    }
-    if (array_key_exists('filter', $handlers)) {
-      $data[$table][$field_name]['filter'] = $handlers['filter'];
-    }
-
     return $data;
   }
 

+ 8 - 1
tripal/includes/tripal.fields.inc

@@ -98,7 +98,14 @@ function tripal_field_views_data($field) {
     $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle_name);
     if (in_array($field_type, $field_types)) {
       $tfield = new $field_type($field, $instance);
-      $data += $tfield->viewsData();
+
+      // Fields should be associated with the bundle's term identifier
+      // (i.e. [vocab]__[accession].
+      $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));
+      $term = tripal_load_term_entity(array('term_id' => $bundle->term_id));
+      $view_base_id = $term->vocab->vocabulary . '__' . $term->accession;
+
+      $data += $tfield->viewsData($view_base_id);
     }
   }
   return $data;

+ 2 - 0
tripal/tripal.info

@@ -10,6 +10,7 @@ stylesheets[all][] = theme/css/tripal.css
 scripts[]          = theme/js/tripal.js
 
 files[] = views_handlers/tripal_views_handler_field.inc
+files[] = views_handlers/tripal_views_handler_field_element.inc
 files[] = views_handlers/tripal_views_handler_field_entity.inc
 files[] = views_handlers/tripal_views_handler_field_entity_link.inc
 files[] = views_handlers/tripal_views_handler_field_entity_link_edit.inc
@@ -18,6 +19,7 @@ files[] = views_handlers/tripal_views_handler_field_image.inc
 files[] = views_handlers/tripal_views_handler_field_boolean.inc
 files[] = views_handlers/tripal_views_handler_filter.inc
 files[] = views_handlers/tripal_views_handler_filter_string.inc
+files[] = views_handlers/tripal_views_handler_filter_element_string.inc
 files[] = views_handlers/tripal_views_handler_filter_boolean_operator.inc
 files[] = views_handlers/tripal_views_handler_filter_entity_string.inc
 files[] = views_handlers/tripal_views_handler_filter_string_selectbox.inc

+ 9 - 1
tripal/tripal.views.inc

@@ -62,9 +62,17 @@ function tripal_views_data_fields(&$data) {
       $bundles = $field['bundles']['TripalEntity'];
       $result = array();
       foreach ($bundles as $bundle_name) {
+        $field_name = $field['field_name'];
+
+        // Fields should be associated with the bundle's term identifier
+        // (i.e. [vocab]__[accession].
+        $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));
+        $term = tripal_load_term_entity(array('term_id' => $bundle->term_id));
+        $view_base_id = $term->vocab->vocabulary . '__' . $term->accession;
+
         $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle_name);
         $tfield = new TripalField($field, $instance);
-        $result += $tfield->viewsData();
+        $result += $tfield->viewsData($view_base_id);
       }
     }
 

+ 14 - 3
tripal/tripal_views_query.inc

@@ -105,9 +105,20 @@ class tripal_views_query extends views_plugin_query {
         // TODO: not sure how to handle these just yet.
       }
       else {
-        // Unforutnately, we don't know the column for this field, so leave it
-        // as NULL and the let the field_storage implementation handle it.
-        $this->query->fieldCondition($field_name, $field_name, $value, $operator);
+        // If the field_name comes to us with a period in it then it means that
+        // we need to separate the field name from sub-element names.
+        $matches = array();
+        if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
+           $field_name = $matches[1];
+           $element_name = $matches[2];
+           // Remove the double underscore from the $element_name and put
+           // back the colon
+           $element_name = preg_replace('/__/', ':', $element_name);
+          $this->query->fieldCondition($field_name, $element_name, $value, $operator);
+        }
+        else {
+          $this->query->fieldCondition($field_name, $field_name, $value, $operator);
+        }
       }
     }
   }

+ 73 - 0
tripal/views_handlers/tripal_views_handler_field_element.inc

@@ -0,0 +1,73 @@
+<?php
+/**
+ * @file
+ *   Views field handler for basic TripalFields fields.
+ */
+
+/**
+ * Views field handler for basic TripalFields fields.
+ */
+class tripal_views_handler_field_element extends tripal_views_handler_field {
+  /**
+   *
+   */
+  function query() {
+    parent::query();
+    // We need to add an alias to our TripalFields so that the
+    // views can find the results.  With SQL it sets the alias for each
+    // field and expects to find that alias in the results array.  Without
+    // setting this alias Views can't find our results from our
+    // tripal_views_query plugin.
+    $this->field_alias = $this->real_field;
+  }
+
+  /**
+   * Get the value that's supposed to be rendered.
+   *
+   * This api exists so that other modules can easy set the values of the field
+   * without having the need to change the render method as well.
+   *
+   * @param $values
+   *   An object containing all retrieved values.
+   * @param $field
+   *   Optional name of the field where the value is stored.
+   */
+  function get_value($values, $field = NULL) {
+
+    $field_name = $this->field_alias;
+
+    if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
+      $field_name = $matches[1];
+      $element_name = $matches[2];
+    }
+    if (isset($values->{$field_name})) {
+      return $values->{$field_name};
+    }
+  }
+
+  /**
+   * Render the field.
+   *
+   * @param $values
+   *   The values retrieved from the database.
+   */
+  function render($values) {
+    $field_name = $this->field_alias;
+    $element_name = $field_name;
+
+    if (preg_match('/^(.+?)\.(.*)$/', $field_name, $matches)) {
+      $field_name = $matches[1];
+      $element_name = $matches[2];
+      // Conver the element name back to it's correct format with the colon.
+      $element_name = preg_replace('/__/', ':', $element_name);
+    }
+
+    $value = $this->get_value($values);
+
+    // Handle single value fields:
+    if (count($value == 1)) {
+      return $this->sanitize_value($value[0]['value'][$element_name], 'xss');
+    }
+    return '';
+  }
+}

+ 13 - 0
tripal/views_handlers/tripal_views_handler_filter_element_string.inc

@@ -0,0 +1,13 @@
+<?php
+
+class tripal_views_handler_filter_element_string extends tripal_views_handler_filter_string {
+
+  function init(&$view, &$options) {
+    parent::init($view, $options);
+
+    // Fix identifier names that don't match what will be in the form state.
+    $this->options['expose']['identifier'] = preg_replace('/\./', '_', $this->options['expose']['identifier']);
+    $this->options['expose']['operator'] = preg_replace('/\./', '_', $this->options['expose']['operator']);
+    $this->options['expose']['operator_id'] = preg_replace('/\./', '_', $this->options['expose']['operator_id']);
+  }
+}

+ 3 - 1
tripal/views_handlers/tripal_views_handler_filter_string.inc

@@ -169,6 +169,7 @@ class tripal_views_handler_filter_string extends tripal_views_handler_filter {
    * Provide a simple textfield for equality
    */
   function value_form(&$form, &$form_state) {
+
     // We have to make some choices when creating this as an exposed
     // filter form. For example, if the operator is locked and thus
     // not rendered, we can't render dependencies; instead we only
@@ -233,7 +234,8 @@ class tripal_views_handler_filter_string extends tripal_views_handler_filter {
 
     $info = $this->operators();
     if (!empty($info[$this->operator]['method'])) {
-      $this->{$info[$this->operator]['method']}($field);
+      $op_function = $info[$this->operator]['method'];
+      $this->{$op_function}($field);
     }
   }
 

+ 13 - 1
tripal_chado/api/tripal_chado.semweb.api.inc

@@ -189,9 +189,21 @@ function tripal_get_chado_semweb_term($chado_table, $chado_column, $options = ar
       return $cvterm;
     }
 
-    return $cvterm->dbxref_id->db_id->name . ':' . $cvterm->dbxref_id->accession;
+    return tripal_format_chado_semweb_term($cvterm);
   }
 }
+
+/**
+ * Formats a controlled vocabulary term from Chado for use with Tripal.
+ *
+ * @param $cvterm
+ *   A cvterm object.
+ * @return
+ *   The semantic web name for the term.
+ */
+function tripal_format_chado_semweb_term($cvterm) {
+  return $cvterm->dbxref_id->db_id->name . ':' . $cvterm->dbxref_id->accession;
+}
 /**
  * Retreive the column name in a Chado table that matches a given term.
  *

+ 20 - 10
tripal_chado/includes/TripalFields/ChadoField.inc

@@ -44,16 +44,6 @@ class ChadoField extends TripalField {
     'base_table' => '',
   );
 
-  // By default our Chado fields are complicated and we only want to support
-  // viewing them, and not filtering by them.  Each field cand override as
-  // needed.
-  public static $default_view_handlers = array(
-    'field' => array(
-      'handler' => 'tripal_views_handler_field',
-      'click sortable' => TRUE,
-    ),
-  );
-
   // The module that manages this field.
   public static $module = 'tripal_chado';
 
@@ -120,4 +110,24 @@ class ChadoField extends TripalField {
     return $element;
   }
 
+  /**
+   * @see TripalField::viewsData()
+   */
+  public function viewsData($view_base_id) {
+    $data = array();
+    $field_name = $this->field['field_name'];
+
+    $data[$view_base_id][$field_name] = array(
+      'title' => $this->instance['label'],
+      'help' => $this->instance['description'],
+      // By default our Chado fields are complicated and we only want to support
+      // viewing them, and not filtering by them.  Each field cand override as
+      // needed.
+      'field' => array(
+        'handler' => 'tripal_views_handler_field',
+        'click sortable' => FALSE,
+      )
+    );
+    return $data;
+  }
 }

+ 1 - 2
tripal_chado/includes/TripalFields/data__accession/data__accession.inc

@@ -114,8 +114,7 @@ class data__accession extends ChadoField {
     $alias = 'dbx_linker';
     $operator = $condition['operator'];
 
-    if ($condition['column'] == 'accession' or
-        $condition['column'] == 'data__accession') {
+    if ($condition['column'] == 'data__accession') {
       $query->join('dbxref', 'DBX', "DBX.dbxref_id = base.dbxref_id");
       $query->condition("DBX.accession", $condition['value'], $operator);
     }

+ 114 - 4
tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc

@@ -209,6 +209,111 @@ class obi__organism extends ChadoField {
     return $element;
   }
 
+  /**
+   * @see ChadoField::viewsData()
+   */
+  public function viewsData($view_base_id) {
+    $data = array();
+    $options = array('return_object' => TRUE);
+
+    // Add a views field for the field.
+    $field_name = $this->field['field_name'];
+    $data[$view_base_id][$field_name] = array(
+      'title' => $this->instance['label'],
+      'help' => $this->instance['description'],
+      'field' => array(
+        'handler' => 'tripal_views_handler_field',
+        'click sortable' => TRUE,
+      ),
+      'filter' => array(
+        'handler' => 'tripal_views_handler_filter_string',
+      ),
+      'sort' => array(
+        'handler' => 'views_handler_sort',
+      )
+    );
+
+    // Add a views field for the genus.
+    $cvterm = tripal_get_chado_semweb_term('organism', 'genus', $options);
+    $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
+    $element_name = tripal_format_views_field_element($field_name, $term);
+    $data[$view_base_id][$element_name] = array(
+      'title' => ucfirst($term['name']),
+      'help' => $term['definition'],
+      'field' => array(
+        'handler' => 'tripal_views_handler_field_element',
+        'click sortable' => TRUE,
+      ),
+      'filter' => array(
+        'handler' => 'tripal_views_handler_filter_element_string',
+      ),
+      'sort' => array(
+        'handler' => 'views_handler_sort',
+      )
+    );
+
+    // Add a views field for the species.
+    $cvterm = tripal_get_chado_semweb_term('organism', 'species', $options);
+    $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
+    $element_name = tripal_format_views_field_element($field_name, $term);
+    $data[$view_base_id][$element_name] = array(
+      'title' => ucfirst($term['name']),
+      'help' => $term['definition'],
+      'field' => array(
+        'handler' => 'tripal_views_handler_field_element',
+        'click sortable' => TRUE,
+      ),
+      'filter' => array(
+        'handler' => 'tripal_views_handler_filter_element_string',
+      ),
+      'sort' => array(
+        'handler' => 'views_handler_sort',
+      )
+    );
+
+    // Suppor the Chado v1.3 fields.
+    if(chado_get_version() >= '1.3') {
+      // Add a views field for the infraspecific name.
+      $cvterm = tripal_get_chado_semweb_term('organism', 'infraspecific_name', $options);
+      $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
+      $element_name = tripal_format_views_field_element($field_name, $term);
+      $data[$view_base_id][$element_name] = array(
+        'title' => ucfirst($term['name']),
+        'help' => $term['definition'],
+        'field' => array(
+          'handler' => 'tripal_views_handler_field_element',
+          'click sortable' => TRUE,
+        ),
+        'filter' => array(
+          'handler' => 'tripal_views_handler_filter_element_string',
+        ),
+        'sort' => array(
+          'handler' => 'views_handler_sort',
+        )
+      );
+
+      // Add a views field for the infraspecific type.
+      $cvterm = tripal_get_chado_semweb_term('organism', 'type_id', $options);
+      $term = tripal_get_term_details($cvterm->dbxref_id->db_id->name, $cvterm->dbxref_id->accession);
+      $element_name = tripal_format_views_field_element($field_name, $term);
+      $data[$view_base_id][$element_name] = array(
+        'title' => ucfirst($term['name']),
+        'help' => $term['definition'],
+        'field' => array(
+          'handler' => 'tripal_views_handler_field_element',
+          'click sortable' => TRUE,
+        ),
+        'filter' => array(
+          'handler' => 'tripal_views_handler_filter_element_string',
+        ),
+        'sort' => array(
+          'handler' => 'views_handler_sort',
+        )
+      );
+    }
+    return $data;
+  }
+
   /**
    * @see ChadoField::query()
    */
@@ -216,6 +321,11 @@ class obi__organism extends ChadoField {
     $alias = $this->field['field_name'];
     $operator = $condition['operator'];
 
+    $genus_term = tripal_get_chado_semweb_term('organism', 'genus');
+    $species_term = tripal_get_chado_semweb_term('organism', 'species');
+    $infraspecific_name_term = tripal_get_chado_semweb_term('organism', 'infraspecific_name');
+    $infraspecific_type_term = tripal_get_chado_semweb_term('organism', 'type_id');
+
     $query->join('organism', $alias, "base.organism_id = $alias.organism_id");
 
     // If the column is the field name.
@@ -224,16 +334,16 @@ class obi__organism extends ChadoField {
     }
 
     // If the column is a subfield.
-    if ($condition['column'] == 'organism.species') {
+    if ($condition['column'] == $species_term) {
       $query->condition("$alias.species", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'organism.genus') {
+    if ($condition['column'] == $genus_term) {
       $query->condition("$alias.genus", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'organism.infraspecies') {
+    if ($condition['column'] == $infraspecific_name_term) {
       $query->condition("$alias.infraspecific_name", $condition['value'], $operator);
     }
-    if ($condition['column'] == 'organism.infraspecific_taxon') {
+    if ($condition['column'] == $infraspecific_type_term) {
       $query->join('cvterm', 'CVT', "base.type_id = CVT.cvterm_id");
       $query->condition("CVT.name", $condition['value'], $operator);
     }

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

@@ -948,7 +948,7 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
       'entity_type' => $entity_type,
       'bundle' => $bundle->name,
       'label' => 'Organism',
-      'description' => 'Select an organism.',
+      'description' => 'The full scientific name for a species..',
       'required' => $is_required,
       'settings' => array(
         'auto_attach' => TRUE,
@@ -972,7 +972,7 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
     );
   }
 
-  // BASE ORGANISM_ID
+  // BASE CVTERM
   if ($table_name == 'cvterm') {
     $field_name = 'sio__vocabulary';
     $is_required = TRUE;
@@ -981,7 +981,7 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
       'entity_type' => $entity_type,
       'bundle' => $bundle->name,
       'label' => 'Vocabulary',
-      'description' => 'Select a vocabulary.',
+      'description' => 'A controlled vocabulary.',
       'required' => $is_required,
       'settings' => array(
         'auto_attach' => TRUE,