#626704: basic Field API integration.

From: Damien Tournoud <damien@tournoud.net>


---
 handlers.inc                        |    2 
 field.views.inc                     |  259 +++++++++++++++++++++++++++++++++++
 field/views_handler_field_field.inc |  118 ++++++++++++++++
 views.info                          |    2 
 4 files changed, 381 insertions(+), 0 deletions(-)
 create mode 100644 field.views.inc
 create mode 100644 field/views_handler_field_field.inc

diff --git includes/handlers.inc includes/handlers.inc
index 23acc40..743bdcc 100644
--- includes/handlers.inc
+++ includes/handlers.inc
@@ -1377,6 +1377,8 @@ function comment_views_api() { return views_views_api(); }
 
 function locale_views_api() { return views_views_api(); }
 
+function field_views_api() { return views_views_api(); }
+
 function filter_views_api() { return views_views_api(); }
 
 function node_views_api() { return views_views_api(); }
diff --git modules/field.views.inc modules/field.views.inc
new file mode 100644
index 0000000..459d74c
--- /dev/null
+++ modules/field.views.inc
@@ -0,0 +1,259 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provide Views data and handlers for field.module
+ */
+
+/**
+ * @defgroup views_field_module field.module handlers
+ *
+ * @{
+ */
+
+/**
+ * Implements hook_views_data()
+ */
+function field_views_data() {
+  $data = array();
+  foreach (field_info_fields() as $field) {
+    if ($field['storage']['type'] != 'field_sql_storage') {
+      continue;
+    }
+
+    $module = $field['module'];
+    $result = (array) module_invoke($module, 'field_views_data', $field);
+    drupal_alter('field_views_data', $result, $field);
+
+    if (empty($result)) {
+      $result = field_views_field_default_views_data($field);
+    }
+    if (is_array($result)) {
+      $data = array_merge($data, $result);
+    }
+  }
+
+  return $data;
+}
+
+/**
+ * Default views data implementation for a field.
+ */
+function field_views_field_default_views_data($field) {
+  $field_types = field_info_field_types();
+
+  // Check the field module is available.
+  if (!isset($field_types[$field['type']])) {
+    return;
+  }
+
+  // Note: this should be in the $field object already, see http://drupal.org/node/645926.
+  $field_storage_details = field_sql_storage_field_storage_details($field, NULL);
+  $field += $field_storage_details;
+
+  $data = array();
+
+  $current_table = _field_sql_storage_tablename($field);
+  $revision_table = _field_sql_storage_revision_tablename($field);
+
+  // The list of entity:bundle that this field is used in.
+  $bundles_names = array();
+  $supports_revisions = FALSE;
+
+  // Build the relationships between the field table and the entity tables.
+  foreach ($field['bundles'] as $entity => $bundles) {
+    $entity_info = entity_get_info($entity);
+
+    $data[$current_table]['table']['join'][$entity_info['base table']] = array(
+      'left_field' => $entity_info['object keys']['id'],
+      'field' => 'entity_id',
+      'extra' => array(array('field' => 'etid', 'value' => _field_sql_storage_etid($entity), 'numeric' => TRUE)),
+    );
+
+    if (!empty($entity_info['object keys']['revision']) && !empty($entity_info['revision table'])) {
+      $data[$revision_table]['table']['join'][$entity_info['revision table']] = array(
+        'left_field' => $entity_info['object keys']['revision'],
+        'field' => 'revision_id',
+        'extra' => array(array('field' => 'etid', 'value' => _field_sql_storage_etid($entity), 'numeric' => TRUE)),
+      );
+
+      $supports_revisions = TRUE;
+    }
+
+    foreach ($bundles as $bundle) {
+      $bundles_names[] = t('@entity:@bundle', array('@entity' => $entity, '@bundle' => $bundle));
+    }
+  }
+
+  $tables = array();
+  $tables[FIELD_LOAD_CURRENT] = $current_table;
+  if ($supports_revisions) {
+    $tables[FIELD_LOAD_REVISION] = $revision_table;
+  }
+
+  // Add the field handler for this field.
+  $title_short = $field['field_name'];
+  foreach ($tables as $type => $table) {
+    if ($type == FIELD_LOAD_CURRENT) {
+      $group = t('Fields');
+      $column = 'entity_id';
+    }
+    else {
+      $group = t('Fields (historical data)');
+      $column = 'revision_id';
+    }
+
+    $data[$table][$column] = array(
+      'group' => $group,
+      'title' => $title_short,
+      'title short' => $title_short,
+      'help' =>  t('Appears in: @bundles', array('@bundles' => implode(', ', $bundles_names))),
+    );
+    $data[$table][$column]['field'] = array(
+      'field' => $column,
+      'table' => $table,
+      'handler' => 'views_handler_field_field',
+      'click sortable' => TRUE,
+      'field_name' => $field['field_name'],
+      'additional fields' => array('etid'),
+    );
+  }
+
+  foreach ($field['columns'] as $column => $attributes) {
+    $sort = !empty($attributes['sortable']) ? TRUE : FALSE;
+
+    // Identify likely filters and arguments for each column based on field type.
+    switch ($attributes['type']) {
+      case 'int':
+      case 'mediumint':
+      case 'tinyint':
+      case 'bigint':
+      case 'serial':
+        $filter = 'views_handler_filter_numeric';
+        $argument = 'views_handler_argument_numeric';
+        break;
+      case 'numeric':
+      case 'float':
+        $filter = 'views_handler_filter_float';
+        $argument = 'views_handler_argument_numeric';
+        break;
+
+      case 'text':
+      case 'blob':
+        // TODO add markup handlers for these types
+      default:
+        $filter = 'views_handler_filter_string';
+        $argument = 'views_handler_argument_string';
+        break;
+    }
+
+    // Note: we don't have a label available here, because we are at the field
+    // level, not at the instance level.
+    if (count($field['columns']) == 1) {
+      $title = t('@label (!name)', array('@label' => $field['field_name'], '!name' => $field['field_name']));
+      $title_short = $field['field_name'];
+    }
+    else {
+      $title = t('@label (!name) - !column', array('@label' => $field['field_name'], '!name' => $field['field_name'], '!column' => $column));
+      $title_short = t('@label-truncated - !column', array('@label-truncated' => $field['field_name'], '!column' => $column));
+    }
+
+    foreach ($tables as $type => $table) {
+      $group = $type == FIELD_LOAD_CURRENT ? t('Fields') : t('Fields (historical data)');
+      $column_real_name = $field['sql'][$type][$table][$column];
+
+      // Load all the fields from the table by default.
+      $additional_fields = array_values($field['sql'][$type][$table]);
+
+      $data[$table][$column_real_name] = array(
+        'group' => $group,
+        'title' => $title,
+        'title short' => $title_short,
+        'help' =>  t('Appears in: @bundles', array('@bundles' => implode(', ', $bundles_names))),
+      );
+
+      $data[$table][$column_real_name]['argument'] = array(
+        'field' => $column_real_name,
+        'table' => $table,
+        'handler' => $argument,
+        'additional fields' => $additional_fields,
+        'content_field_name' => $field['field_name'],
+        'empty field name' => t('<No value>'),
+      );
+      $data[$table][$column_real_name]['filter'] = array(
+        'field' => $column_real_name,
+        'table' => $table,
+        'handler' => $filter,
+        'additional fields' => $additional_fields,
+        'content_field_name' => $field['field_name'],
+        'allow empty' => TRUE,
+      );
+      if (!empty($sort)) {
+        $data[$table][$column_real_name]['sort'] = array(
+          'field' => $column_real_name,
+          'table' => $table,
+          'handler' => 'content_handler_sort',
+          'additional fields' => $additional_fields,
+          'content_field_name' => $field['field_name'],
+        );
+      }
+
+      // Expose additional delta column for multiple value fields.
+      if ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
+        $title = t('@label (!name) - delta', array('@label' => $field['field_name'], '!name' => $field['field_name']));
+        $title_short = t('!name - delta', array('@name' => $field['field_name']));
+
+        $data[$table]['delta'] = array(
+          'group' => $group,
+          'title' => $title,
+          'title short' => $title_short,
+          'help' => t('Delta - Appears in: @bundles', array('@bundles' => implode(', ', $bundles_names))),
+        );
+        $data[$table]['delta']['argument'] = array(
+          'field' => 'delta',
+          'table' => $table,
+          'handler' => 'views_handler_argument_numeric',
+          'additional fields' => $additional_fields,
+          'empty field name' => t('<No value>'),
+        );
+        $data[$table]['delta']['filter'] = array(
+          'field' => 'delta',
+          'table' => $table,
+          'handler' => 'views_handler_filter_numeric',
+          'additional fields' => $additional_fields,
+          'allow empty' => TRUE,
+        );
+        $data[$table]['delta']['sort'] = array(
+          'field' => 'delta',
+          'table' => $table,
+          'handler' => 'views_handler_sort',
+          'additional fields' => $additional_fields,
+        );
+      }
+    }
+  }
+
+  return $data;
+}
+
+/**
+ * Implements hook_views_handlers().
+ */
+function field_views_handlers() {
+  return array(
+    'info' => array(
+      'path' => drupal_get_path('module', 'views') . '/modules/field',
+    ),
+    'handlers' => array(
+      'views_handler_field_field' => array(
+        'parent' => 'views_handler_field',
+      ),
+    ),
+  );
+}
+
+
+/**
+ * @}
+ */
diff --git modules/field/views_handler_field_field.inc modules/field/views_handler_field_field.inc
new file mode 100644
index 0000000..9a8e37c
--- /dev/null
+++ modules/field/views_handler_field_field.inc
@@ -0,0 +1,118 @@
+<?php
+// $Id$
+
+/**
+ * Helper function: Return an array of formatter options for a field type.
+ *
+ * Borrowed from field_ui.
+ */
+function _field_view_formatter_options($field_type = NULL) {
+  $options = &drupal_static(__FUNCTION__);
+
+  if (!isset($options)) {
+    $field_types = field_info_field_types();
+    $options = array();
+    foreach (field_info_formatter_types() as $name => $formatter) {
+      foreach ($formatter['field types'] as $formatter_field_type) {
+        // Check that the field type exists.
+        if (isset($field_types[$formatter_field_type])) {
+          $options[$formatter_field_type][$name] = $formatter['label'];
+        }
+      }
+    }
+  }
+
+  if ($field_type) {
+    return !empty($options[$field_type]) ? $options[$field_type] : array();
+  }
+  return $options;
+}
+
+/**
+ * A field that displays fields.
+ */
+class views_handler_field_field extends views_handler_field {
+  /**
+   * Called to add the field to a query.
+   */
+  function query() {
+    // TODO: we should try to use the data from the join if possible first.
+    // $join = $this->get_join();
+    // That would avoid joining at all the field table.
+    return parent::query();
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $field = field_info_field($this->definition['field_name']);
+    $field_type = field_info_field_types($field['type']);
+
+    $options['type'] = array(
+      'default' => $field_type['default_formatter'],
+      'translatable' => TRUE,
+    );
+
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+
+    $field = field_info_field($this->definition['field_name']);
+    $formatters = _field_view_formatter_options($field['type']);
+
+    $form['type'] = array(
+      '#type' => 'select',
+      '#title' => t('Formatter'),
+      '#options' => $formatters,
+      '#default_value' => $this->options['type'],
+    );
+  }
+
+  function pre_render(&$values) {
+    if (!empty($values) && empty($values[0]->_object)) {
+
+      // OMG, this is *ugly*.
+      $obj_type_map = db_query('SELECT etid, type FROM {field_config_entity_type}')->fetchAllKeyed();
+
+      // Load the full objects.
+      $objects_by_type = array();
+      foreach ($values as $key => $object) {
+        // Derive the entity type. For some field types, etid might be empty.
+        if (isset($object->{$this->aliases['etid']}) && isset($obj_type_map[$object->{$this->aliases['etid']}])) {
+          $obj_type = $obj_type_map[$object->{$this->aliases['etid']}];
+          $entity_id = $object->{$this->field_alias};
+          $objects_by_type[$obj_type][$key] = $entity_id;
+        }
+      }
+
+      // Load the objects.
+      foreach ($objects_by_type as $obj_type => $oids) {
+        $objects = entity_load($obj_type, $oids);
+        foreach ($oids as $key => $entity_id) {
+          if (isset($objects[$entity_id])) {
+            $values[$key]->_obj_type = $obj_type;
+            $values[$key]->_object = $objects[$entity_id];
+          }
+        }
+      }
+    }
+  }
+
+  function render($values) {
+    if (isset($values->_obj_type)) {
+      $field_name = $this->definition['field_name'];
+      $display = array(
+        'type' => $this->options['type'],
+        'label' => 'hidden',
+      );
+
+      return drupal_render(field_view_field($values->_obj_type, $values->_object, $field_name, $display));
+    }
+    else {
+      return '';
+    }
+  }
+}
+
diff --git views.info views.info
index cce5340..6957808 100644
--- views.info
+++ views.info
@@ -88,6 +88,8 @@ files[] = modules/comment/views_handler_sort_ncs_last_comment_name.inc
 files[] = modules/comment/views_handler_sort_ncs_last_updated.inc
 files[] = modules/comment/views_plugin_row_comment_rss.inc
 files[] = modules/comment/views_plugin_row_comment_view.inc
+files[] = modules/field.views.inc
+files[] = modules/field/views_handler_field_field.inc
 files[] = modules/locale.views.inc
 files[] = modules/locale/views_handler_argument_locale_group.inc
 files[] = modules/locale/views_handler_argument_locale_language.inc
