summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfago2011-11-03 09:44:09 (GMT)
committerfago2011-11-03 09:44:09 (GMT)
commit25427b011302c05c741fd03db739826d7e043344 (patch)
tree2464a330051ce7f812c994f6ce7f0f644161fe49
parent21c15b658da93e77ec221b7b517a3e6e840c99ce (diff)
#1266036 patch by drunken monkey, fago, thegreat: add Views base tables, fields and relationships based upon data selection on entity objects.
-rw-r--r--entity.api.php22
-rw-r--r--entity.info21
-rw-r--r--includes/entity.property.inc37
-rw-r--r--views/entity.views.inc220
-rw-r--r--views/entity_views_example_query.php88
-rw-r--r--views/handlers/entity_views_field_handler_helper.inc464
-rw-r--r--views/handlers/entity_views_handler_field_boolean.inc104
-rw-r--r--views/handlers/entity_views_handler_field_date.inc104
-rw-r--r--views/handlers/entity_views_handler_field_duration.inc135
-rw-r--r--views/handlers/entity_views_handler_field_entity.inc171
-rw-r--r--views/handlers/entity_views_handler_field_field.inc114
-rw-r--r--views/handlers/entity_views_handler_field_numeric.inc104
-rw-r--r--views/handlers/entity_views_handler_field_options.inc122
-rw-r--r--views/handlers/entity_views_handler_field_text.inc104
-rw-r--r--views/handlers/entity_views_handler_field_uri.inc104
-rw-r--r--views/handlers/entity_views_handler_relationship.inc51
16 files changed, 1956 insertions, 9 deletions
diff --git a/entity.api.php b/entity.api.php
index b47a6b8..edfdaf3 100644
--- a/entity.api.php
+++ b/entity.api.php
@@ -402,5 +402,27 @@ function entity_hook_field_info() {
}
/**
+ * Alter the handlers used by the data selection tables provided by this module.
+ *
+ * @param array $field_handlers
+ * An array of the field handler classes to use for specific types. The keys
+ * are the types, mapped to their respective classes. Contained types are:
+ * - All primitive types known by the entity API (see
+ * hook_entity_property_info()).
+ * - options: Special type for fields having an options list.
+ * - field: Special type for Field API fields.
+ * - entity: Special type for entity-valued fields.
+ * - relationship: Views relationship handler to use for relationships.
+ * Values for all specific entity types can be additionally added.
+ *
+ * @see entity_views_field_definition()
+ * @see entity_views_get_field_handlers()
+ */
+function hook_entity_views_field_handlers_alter(array &$field_handlers) {
+ $field_handlers['duration'] = 'example_duration_handler';
+ $field_handlers['node'] = 'example_node_handler';
+}
+
+/**
* @} End of "addtogroup hooks".
*/
diff --git a/entity.info b/entity.info
index 5689554..ee3c3cf 100644
--- a/entity.info
+++ b/entity.info
@@ -1,13 +1,24 @@
name = Entity API
description = Enables modules to work with any entity type and to provide entities.
core = 7.x
-files[] = views/plugins/entity_plugin_row_entity_view.inc
-files[] = includes/entity.controller.inc
-files[] = includes/entity.inc
-files[] = includes/entity.ui.inc
-files[] = includes/entity.wrapper.inc
files[] = entity.features.inc
files[] = entity.info.inc
files[] = entity.rules.inc
files[] = entity.test
+files[] = includes/entity.inc
+files[] = includes/entity.controller.inc
+files[] = includes/entity.ui.inc
+files[] = includes/entity.wrapper.inc
+files[] = views/handlers/entity_views_field_handler_helper.inc
+files[] = views/handlers/entity_views_handler_field_boolean.inc
+files[] = views/handlers/entity_views_handler_field_date.inc
+files[] = views/handlers/entity_views_handler_field_duration.inc
+files[] = views/handlers/entity_views_handler_field_entity.inc
+files[] = views/handlers/entity_views_handler_field_field.inc
+files[] = views/handlers/entity_views_handler_field_numeric.inc
+files[] = views/handlers/entity_views_handler_field_options.inc
+files[] = views/handlers/entity_views_handler_field_text.inc
+files[] = views/handlers/entity_views_handler_field_uri.inc
files[] = views/handlers/entity_views_handler_relationship_by_bundle.inc
+files[] = views/handlers/entity_views_handler_relationship.inc
+files[] = views/plugins/entity_plugin_row_entity_view.inc
diff --git a/includes/entity.property.inc b/includes/entity.property.inc
index 43bcdd5..c13876a 100644
--- a/includes/entity.property.inc
+++ b/includes/entity.property.inc
@@ -10,12 +10,12 @@
/**
* Get the entity property info array of an entity type.
*
- * @see hook_entity_property_info()
- * @see hook_entity_property_info_alter()
- *
* @param $entity_type
* The entity type, e.g. node, for which the info shall be returned, or NULL
* to return an array with info about all types.
+ *
+ * @see hook_entity_property_info()
+ * @see hook_entity_property_info_alter()
*/
function entity_get_property_info($entity_type = NULL) {
// Use the advanced drupal_static() pattern, since this is called very often.
@@ -42,6 +42,21 @@ function entity_get_property_info($entity_type = NULL) {
}
/**
+ * Returns the default information for an entity property.
+ *
+ * @return
+ * An array of optional property information keys mapped to their defaults.
+ *
+ * @see hook_entity_property_info()
+ */
+function entity_property_info_defaults() {
+ return array(
+ 'type' => 'text',
+ 'getter callback' => 'entity_property_verbatim_get',
+ );
+}
+
+/**
* Gets an array of info about all properties of a given entity type.
*
* In contrast to entity_get_property_info(), this function returns info about
@@ -311,6 +326,22 @@ function entity_property_list_extract_type($type) {
}
/**
+ * Extracts the innermost type for a type string like list<list<date>>.
+ *
+ * @param $type
+ * The type to examine.
+ *
+ * @return
+ * For list types, the innermost type. The type itself otherwise.
+ */
+function entity_property_extract_innermost_type($type) {
+ while (strpos($type, 'list<') === 0 && $type[strlen($type)-1] == '>') {
+ $type = substr($type, 5, -1);
+ }
+ return $type;
+}
+
+/**
* Gets the property just as it is set in the data.
*/
function entity_property_verbatim_get($data, array $options, $name, $type, $info) {
diff --git a/views/entity.views.inc b/views/entity.views.inc
index f1314e2..9e97daa 100644
--- a/views/entity.views.inc
+++ b/views/entity.views.inc
@@ -14,10 +14,12 @@
* - hook_entity_info() specifies a 'module' key, and the module does not
* implement hook_views_data().
*
- * @see entity_crud_hook_entity_info().
+ * @see entity_crud_hook_entity_info()
+ * @see entity_views_table_definition()
*/
function entity_views_data() {
$data = array();
+
foreach (entity_crud_get_info() as $type => $info) {
// Provide default integration with the basic controller class if we know
// the module providing the entity and it does not provide views integration.
@@ -35,10 +37,225 @@ function entity_views_data() {
}
}
+ // Add tables based upon data selection "queries" for all entity types.
+ foreach (entity_get_info() as $type => $info) {
+ $table = entity_views_table_definition($type);
+ if ($table) {
+ $data['entity_' . $type] = $table;
+ }
+ }
+
return $data;
}
/**
+ * Helper function for getting data selection based entity Views table definitions.
+ *
+ * This creates extra tables for each entity type that are not associated with a
+ * query plugin (and thus are not base tables) and just rely on the entities to
+ * retrieve the displayed data. To obtain the entities corresponding to a
+ * certain result set, the field handlers defined on the table use a generic
+ * interface defined for query plugins that are based on entity handling, and
+ * which is described in the entity_views_example_query class.
+ *
+ * These tables are called "data selection tables".
+ *
+ * Other modules providing Views integration with new query plugins that are
+ * based on entities can then use these tables as a base for their own tables
+ * (by directly using this method and modifying the returned table) and/or by
+ * specifying relationships to them. The tables returned here already specify
+ * relationships to each other wherever an entity contains a reference to
+ * another (e.g., the node author constructs a relationship from nodes to
+ * users).
+ *
+ * As filtering and other query manipulation is potentially more plugin-specific
+ * than the display, only field handlers and relationships are provided with
+ * these tables. By providing a add_selector_orderby() method, the query plugin
+ * can, however, support click-sorting for the field handlers in these tables.
+ *
+ * For a detailed discussion see http://drupal.org/node/1266036
+ *
+ * For example use see the Search API views module in the Search API project:
+ * http://drupal.org/project/search_api
+ *
+ * @param $type
+ * The entity type whose table definition should be returned.
+ *
+ * @return
+ * An array containing the data selection Views table definition for the
+ * entity type.
+ *
+ * @see entity_views_field_definition()
+ */
+function entity_views_table_definition($type) {
+ // As other modules might want to copy these tables as a base for their own
+ // Views integration, we statically cache the tables to save some time.
+ $tables = &drupal_static(__FUNCTION__, array());
+
+ if (!isset($tables[$type])) {
+ $info = entity_get_info($type);
+ $tables[$type]['table'] = array(
+ 'group' => $info['label'],
+ 'entity type' => $type,
+ );
+ foreach (entity_get_all_property_info($type) as $key => $property) {
+ entity_views_field_definition($key, $property, $tables[$type]);
+ }
+ }
+
+ return $tables[$type];
+}
+
+/**
+ * Helper function for adding a Views field definition to data selection based Views tables.
+ *
+ * @param $field
+ * The data selector of the field to add. E.g. "title" would derive the node
+ * title property, "body:summary" the node body's summary.
+ * @param array $property_info
+ * The property information for which to create a field definition.
+ * @param array $table
+ * The table into which the definition should be inserted.
+ * @param $title_prefix
+ * Internal use only.
+ *
+ * @see entity_views_table_definition()
+ */
+function entity_views_field_definition($field, array $property_info, array &$table, $title_prefix = '') {
+ $additional = array();
+ $additional_field = array();
+
+ // Create a valid Views field identifier (no colons, etc.). Keep the original
+ // data selector as real field though.
+ $key = _entity_views_field_identifier($field, $table);
+ if ($key != $field) {
+ $additional['real field'] = $field;
+ }
+ $field_name = EntityFieldHandlerHelper::get_selector_field_name($field);
+
+ $field_handlers = entity_views_get_field_handlers();
+
+ $property_info += entity_property_info_defaults();
+ $type = entity_property_extract_innermost_type($property_info['type']);
+ $title = $title_prefix . $property_info['label'];
+ if ($info = entity_get_info($type)) {
+ $additional_field['entity type'] = $type;
+ $additional['relationship'] = array(
+ 'handler' => $field_handlers['relationship'],
+ 'base' => 'entity_' . $type,
+ 'base field' => $info['entity keys']['id'],
+ 'relationship field' => $field,
+ 'label' => $title,
+ );
+ if ($property_info['type'] != $type) {
+ // This is a list of entities, so we should mark the relationship as such.
+ $additional['relationship']['multiple'] = TRUE;
+ }
+ // Implementers of the field handlers alter hook could add handlers for
+ // specific entity types.
+ if (!isset($field_handlers[$type])) {
+ $type = 'entity';
+ }
+ }
+ elseif (!empty($property_info['field'])) {
+ $type = 'field';
+ // Views' Field API field handler needs some extra definitions to work.
+ $additional_field['field_name'] = $field_name;
+ $additional_field['entity_tables'] = array();
+ $additional_field['entity type'] = $table['table']['entity type'];
+ $additional_field['is revision'] = FALSE;
+ }
+ // Copied from EntityMetadataWrapper::optionsList()
+ elseif (isset($property_info['options list']) && is_callable($property_info['options list'])) {
+ // If this is a nested property, we need to get rid of all prefixes first.
+ $type = 'options';
+ $additional_field['options callback'] = array(
+ 'function' => $property_info['options list'],
+ 'info' => $property_info,
+ );
+ }
+ elseif ($type == 'decimal') {
+ $additional_field['float'] = TRUE;
+ }
+
+ if (isset($field_handlers[$type])) {
+ $table += array($key => array());
+ $table[$key] += array(
+ 'title' => $title,
+ 'help' => empty($property_info['description']) ? t('(No information available)') : $property_info['description'],
+ 'field' => array(),
+ );
+ $table[$key]['field'] += array(
+ 'handler' => $field_handlers[$type],
+ 'type' => $property_info['type'],
+ );
+ $table[$key] += $additional;
+ $table[$key]['field'] += $additional_field;
+ }
+ if (!empty($property_info['property info'])) {
+ foreach ($property_info['property info'] as $nested_key => $nested_property) {
+ entity_views_field_definition($field . ':' . $nested_key, $nested_property, $table, $title . ' ยป ');
+ }
+ }
+}
+
+/**
+ * @return array
+ * The handlers to use for the data selection based Views tables.
+ *
+ * @see hook_entity_views_field_handlers_alter()
+ */
+function entity_views_get_field_handlers() {
+ $field_handlers = drupal_static(__FUNCTION__);
+ if (!isset($field_handlers)) {
+ // Field handlers for the entity tables, by type.
+ $field_handlers = array(
+ 'text' => 'entity_views_handler_field_text',
+ 'token' => 'entity_views_handler_field_text',
+ 'integer' => 'entity_views_handler_field_numeric',
+ 'decimal' => 'entity_views_handler_field_numeric',
+ 'date' => 'entity_views_handler_field_date',
+ 'duration' => 'entity_views_handler_field_duration',
+ 'boolean' => 'entity_views_handler_field_boolean',
+ 'uri' => 'entity_views_handler_field_uri',
+ 'options' => 'entity_views_handler_field_options',
+ 'field' => 'entity_views_handler_field_field',
+ 'entity' => 'entity_views_handler_field_entity',
+ 'relationship' => 'entity_views_handler_relationship',
+ );
+ drupal_alter('entity_views_field_handlers', $field_handlers);
+ }
+ return $field_handlers;
+}
+
+/**
+ * Helper function for creating valid Views field identifiers out of data selectors.
+ *
+ * Uses $table to test whether the identifier is already used, and also
+ * recognizes if a definition for the same field is already present and returns
+ * that definition's identifier.
+ *
+ * @return string
+ * A valid Views field identifier that is not yet used as a key in $table.
+ */
+function _entity_views_field_identifier($field, array $table) {
+ $key = $base = preg_replace('/[^a-zA-Z0-9]+/S', '_', $field);
+ $i = 0;
+ // The condition checks whether this sanitized field identifier is already
+ // used for another field in this table (and whether the identifier is
+ // "table", which can never be used).
+ // If $table[$key] is set, the identifier is already used, but this might be
+ // already for the same field. To test that, we need the original field name,
+ // which is either $table[$key]['real field'], if set, or $key. If this
+ // original field name is equal to $field, we can use that key. Otherwise, we
+ // append numeric suffixes until we reach an unused key.
+ while ($key == 'table' || (isset($table[$key]) && (isset($table[$key]['real field']) ? $table[$key]['real field'] : $key) != $field)) {
+ $key = $base . '_' . ++$i;
+ }
+ return $key;
+}
+
+/**
* Implements hook_views_plugins().
*/
function entity_views_plugins() {
@@ -102,6 +319,7 @@ class EntityDefaultViewsController {
'title' => drupal_ucfirst($this->info['label']),
'help' => isset($this->info['description']) ? $this->info['description'] : '',
);
+ $data[$table]['table']['entity type'] = $this->type;
$data[$table] += $this->schema_fields();
// Add in any reverse-relationships which have been determined.
diff --git a/views/entity_views_example_query.php b/views/entity_views_example_query.php
new file mode 100644
index 0000000..7e98e2c
--- /dev/null
+++ b/views/entity_views_example_query.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains an example for a Views query plugin that could use the data selection tables.
+ */
+
+/**
+ * Describes the additional methods looked for on a query plugin if data selection based tables or fields are used.
+ *
+ * Only get_result_entities() needs to be present, so results can be retrieved.
+ * The other methods are optional.
+ *
+ * If the table does not contain entities, however, the get_result_wrappers()
+ * method is necessary, too. If this is the case and there are no relations to
+ * entity tables, the get_result_entities() method is not needed.
+ *
+ * @see entity_views_table_definition()
+ */
+abstract class entity_views_example_query extends views_plugin_query {
+
+ /**
+ * Add a sort to the query.
+ *
+ * This is used to add a sort based on an Entity API data selector instead
+ * of a field alias.
+ *
+ * This method has to be present if click-sorting on fields should be allowed
+ * for some fields using the default Entity API field handlers.
+ *
+ * @param $selector
+ * The field to sort on, as an Entity API data selector.
+ * @param $order
+ * The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
+ */
+ public abstract function add_selector_orderby($selector, $order = 'ASC');
+
+ /**
+ * Returns the according entity objects for the given query results.
+ *
+ * This is compatible to the get_result_entities() method used by Views.
+ *
+ * The method is responsible for resolving the relationship and returning the
+ * entity objects for that relationship. The helper methods
+ * EntityFieldHandlerHelper::construct_property_selector() and
+ * EntityFieldHandlerHelper::extract_property_multiple() can be used to do
+ * this.
+ *
+ * @param $results
+ * The results of the query, as returned by this query plugin.
+ * @param $relationship
+ * (optional) A relationship for which the entities should be returned.
+ * @param $field
+ * (optional) The field for which the entity should be returned. This is
+ * only needed in case a field is derived via a referenced entity without
+ * using a relationship. For example, if the node's field "author:name" is
+ * used, the user entity would be returned instead of the node entity.
+ *
+ * @return
+ * A numerically indexed array containing two items: the entity type of
+ * entities returned by this method; and the array of entities, keyed by the
+ * same indexes as the results.
+ *
+ * @see EntityFieldHandlerHelper::extract_property_multiple()
+ */
+ public abstract function get_result_entities($results, $relationship = NULL, $field = NULL);
+
+ /**
+ * Returns the according metadata wrappers for the given query results.
+ *
+ * This can be used if no entities for the results can be given, but entity
+ * metadata wrappers can be constructed for them.
+ *
+ * @param $results
+ * The results of the query, as returned by this query plugin.
+ * @param $relationship
+ * (optional) A relationship for which the wrappers should be returned.
+ * @param $field
+ * (optional) The field of which a wrapper should be returned.
+ *
+ * @return
+ * A numerically indexed array containing two items: the data type of
+ * the wrappers returned by this method; and the array of retrieved
+ * EntityMetadataWrapper objects, keyed by the same indexes as the results.
+ */
+ public abstract function get_result_wrappers($results, $relationship = NULL, $field = NULL);
+
+}
diff --git a/views/handlers/entity_views_field_handler_helper.inc b/views/handlers/entity_views_field_handler_helper.inc
new file mode 100644
index 0000000..8e54683
--- /dev/null
+++ b/views/handlers/entity_views_field_handler_helper.inc
@@ -0,0 +1,464 @@
+<?php
+
+/**
+ * @file
+ * Contains the EntityFieldHandlerHelper class.
+ */
+
+/**
+ * Helper class containing static implementations of common field handler methods.
+ *
+ * Used by the data selection entity field handlers to avoid code duplication.
+ *
+ * @see entity_views_table_definition()
+ */
+class EntityFieldHandlerHelper {
+
+ /**
+ * Provide appropriate default options for a handler.
+ */
+ public static function option_definition($handler) {
+ if (entity_property_list_extract_type($handler->definition['type'])) {
+ $options['list']['contains']['mode'] = array('default' => 'collapse');
+ $options['list']['contains']['separator'] = array('default' => ', ');
+ }
+ $options['link_to_entity'] = array('default' => FALSE);
+
+ return $options;
+ }
+
+ /**
+ * Provide an appropriate default option form for a handler.
+ */
+ public static function options_form($handler, &$form, &$form_state) {
+ if (entity_property_list_extract_type($handler->definition['type'])) {
+ $form['list']['mode'] = array(
+ '#type' => 'select',
+ '#title' => t('List handling'),
+ '#options' => array(
+ 'collapse' => t('Concatenate values using a seperator'),
+ 'first' => t('Show first (if present)'),
+ 'count' => t('Show item count'),
+ ),
+ '#default_value' => $handler->options['list']['mode'],
+ );
+ $form['list']['separator'] = array(
+ '#type' => 'textfield',
+ '#title' => t('List seperator'),
+ '#default_value' => $handler->options['list']['separator'],
+ '#dependency' => array('edit-options-list-mode' => array('collapse')),
+ );
+ }
+ $form['link_to_entity'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Link this field to its entity'),
+ '#description' => t("When using this, you should not set any other link on the field."),
+ '#default_value' => $handler->options['link_to_entity'],
+ );
+ }
+
+ /**
+ * Add the field for the entity ID (if necessary).
+ */
+ public static function query($handler) {
+ // Some of the parent handlers might require this.
+ $handler->field_alias = $handler->real_field;
+ $handler->base_field = self::get_selector_field_name($handler->real_field);
+ }
+
+ /**
+ * Extracts the innermost field name from a data selector.
+ *
+ * @param $selector
+ * The data selector.
+ *
+ * @return
+ * The last component of the data selector.
+ */
+ public static function get_selector_field_name($selector) {
+ return ltrim(substr($selector, strrpos($selector, ':')), ':');
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ *
+ * @param $order
+ * Either 'ASC' or 'DESC'.
+ */
+ public static function click_sort($handler, $order) {
+ // The normal orderby() method for this usually won't work here. So we need
+ // query plugins to provide their own method for this.
+ if (method_exists($handler->query, 'add_selector_orderby')) {
+ $selector = self::construct_property_selector($handler, TRUE);
+ $handler->query->add_selector_orderby($selector, $order);
+ }
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ *
+ * Automatically takes care of relationships, including data selection
+ * relationships.
+ */
+ public static function pre_render($handler, &$values, $load_always = FALSE) {
+ if (empty($values)) {
+ return;
+ }
+ if (!$load_always && empty($handler->options['link_to_entity'])) {
+ // Check whether we even need to load the entities.
+ $selector = self::construct_property_selector($handler, TRUE);
+ $load = FALSE;
+ foreach ($values as $row) {
+ if (empty($row->_entity_properties) || !array_key_exists($selector, $row->_entity_properties)) {
+ $load = TRUE;
+ break;
+ }
+ }
+ if (!$load) {
+ return;
+ }
+ }
+
+ if (method_exists($handler->query, 'get_result_wrappers')) {
+ list($handler->entity_type, $handler->wrappers) = $handler->query->get_result_wrappers($values, $handler->relationship, $handler->real_field);
+ }
+ else {
+ list($handler->entity_type, $entities) = $handler->query->get_result_entities($values, $handler->relationship, $handler->real_field);
+ $handler->wrappers = array();
+ foreach ($entities as $id => $entity) {
+ $handler->wrappers[$id] = entity_metadata_wrapper($handler->entity_type, $entity);
+ }
+ }
+ }
+
+ /**
+ * Return an Entity API data selector for the given handler's relationship.
+ *
+ * A data selector is a concatenation of properties which should be followed
+ * to arrive at a desired property that may be nested in related entities or
+ * structures. The separate properties are herein concatenated with colons.
+ *
+ * For instance, a data selector of "author:roles" would mean to first
+ * access the "author" property of the given wrapper, and then for this new
+ * wrapper to access and return the "roles" property.
+ *
+ * Lists of entities are handled automatically by always returning only the
+ * first entity.
+ *
+ * @param $handler
+ * The handler for which to construct the selector.
+ * @param $complete
+ * If TRUE, the complete selector for the field is returned, not just the
+ * one for its parent. Defaults to FALSE.
+ *
+ * @return
+ * An Entity API data selector for the given handler's relationship.
+ */
+ public static function construct_property_selector($handler, $complete = FALSE) {
+ $return = '';
+ if ($handler->relationship) {
+ $current_handler = $handler;
+ $view = $current_handler->view;
+ while (!empty($current_handler->relationship) && !empty($view->relationship[$current_handler->relationship])) {
+ $current_handler = $view->relationship[$current_handler->relationship];
+ $return = $current_handler->real_field . ($return ? ":$return" : '');
+ }
+ }
+
+ if ($complete) {
+ $return .= ($return ? ':' : '') . $handler->real_field;
+ }
+ elseif ($pos = strrpos($handler->real_field, ':')) {
+ // If we have a selector as the real_field, append this to the returned
+ // relationship selector.
+ $return .= ($return ? ':' : '') . substr($handler->real_field, 0, $pos);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Extracts data from several metadata wrappers based on a data selector.
+ *
+ * All metadata wrappers passed to this function have to be based on the exact
+ * same property information. The data will be returned wrapped by one or more
+ * metadata wrappers.
+ *
+ * Can be used in query plugins for the get_result_entities() and
+ * get_result_wrappers() methods.
+ *
+ * @param array $wrappers
+ * The EntityMetadataWrapper objects from which to extract data.
+ * @param $selector
+ * The selector specifying the data to extract.
+ *
+ * @return array
+ * An array with numeric indices, containing the type of the extracted
+ * wrappers in the first element. The second element of the array contains
+ * the extracted property value(s) for each wrapper, keyed to the same key
+ * that was used for the respecive wrapper in $wrappers. All extracted
+ * properties are returned as metadata wrappers.
+ */
+ public static function extract_property_multiple(array $wrappers, $selector) {
+ $parts = explode(':', $selector, 2);
+ $name = $parts[0];
+
+ $results = array();
+ $entities = array();
+ $type = '';
+ foreach ($wrappers as $i => $wrapper) {
+ try {
+ $property = $wrapper->$name;
+ $type = $property->type();
+ if ($property instanceof EntityDrupalWrapper) {
+ // Remember the entity IDs to later load all at once (so as to
+ // properly utilize multiple load functionality).
+ $id = $property->getIdentifier();
+ $entities[$type][$i] = $id;
+ }
+ elseif ($property instanceof EntityStructureWrapper) {
+ $results[$i] = $property;
+ }
+ elseif ($property instanceof EntityListWrapper) {
+ foreach ($property as $item) {
+ $results[$i] = $item;
+ $type = $item->type();
+ break;
+ }
+ }
+ // Do nothing in case it cannot be applied.
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // Skip single empty properties.
+ }
+ }
+
+ if ($entities) {
+ // Map back the loaded entities back to the results array.
+ foreach ($entities as $type => $id_map) {
+ $loaded = entity_load($type, $id_map);
+ foreach ($id_map as $i => $id) {
+ if (isset($loaded[$id])) {
+ $results[$i] = entity_metadata_wrapper($type, $loaded[$id]);
+ }
+ }
+ }
+ }
+
+ // If there are no further parts in the selector, we are done now.
+ if (empty($parts[1])) {
+ return array($type, $results);
+ }
+ return self::extract_property_multiple($results, $parts[1]);
+ }
+
+ /**
+ * Get the value of a certain data selector.
+ *
+ * Uses $values->_entity_properties to look for already extracted properties.
+ *
+ * @param $handler
+ * The field handler for which to return a value.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ * @param $field
+ * The field to extract. If no value is given, the field of the given
+ * handler is used instead. The special "entity object" value can be used to
+ * get the base entity instead of a special field.
+ * @param $default
+ * The value to return if the entity or field are not present.
+ */
+ public static function get_value($handler, $values, $field = NULL, $default = NULL) {
+ // There is a value cache on each handler so parent handlers rendering a
+ // single field value from a list will get the single value, not the whole
+ // list.
+ if (!isset($field) && isset($handler->current_value)) {
+ return $handler->current_value;
+ }
+ $field = isset($field) ? $field : $handler->base_field;
+ $selector = self::construct_property_selector($handler);
+ $selector = $selector ? "$selector:$field" : $field;
+ if (!array_key_exists($selector, $values->_entity_properties)) {
+ if (!isset($handler->wrappers[$handler->view->row_index])) {
+ $values->_entity_properties[$selector] = $default;
+ }
+ elseif (is_array($handler->wrappers[$handler->view->row_index])) {
+ $values->_entity_properties[$selector] = self::extract_list_wrapper_values($handler->wrappers[$handler->view->row_index], $field);
+ }
+ else {
+ $wrapper = $handler->wrappers[$handler->view->row_index];
+ try {
+ if ($field === 'entity object') {
+ $values->_entity_properties[$selector] = $wrapper->value();
+ }
+ else {
+ $values->_entity_properties[$selector] = isset($wrapper->$field) ? $wrapper->$field->value(array('identifier' => TRUE)) : $default;
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ $values->_entity_properties[$selector] = $default;
+ }
+ }
+ }
+ return $values->_entity_properties[$selector];
+ }
+
+ /**
+ * Helper method for extracting the values from an array of wrappers.
+ *
+ * Nested arrays of wrappers are also handled, the values are returned in a
+ * flat (not nested) array.
+ */
+ public static function extract_list_wrapper_values(array $wrappers, $field) {
+ $return = array();
+ foreach ($wrappers as $wrapper) {
+ if (is_array($wrapper)) {
+ $values = self::extract_list_wrapper_values($wrapper, $field);
+ if ($values) {
+ $return = array_merge($return, $values);
+ }
+ }
+ else {
+ try {
+ if ($field == 'entity object') {
+ $return[] = $wrapper->value();
+ }
+ elseif(isset($wrapper->$field)) {
+ $return[] = $wrapper->$field->value(array('identifier' => TRUE));
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ // An exception probably signifies a non-present property, so we just
+ // ignore it.
+ }
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Render the field.
+ *
+ * Implements the entity link functionality and list handling. Basic handling
+ * of the single values is delegated back to the field handler.
+ *
+ * @param $handler
+ * The field handler whose field should be rendered.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value for the field.
+ */
+ public static function render($handler, $values) {
+ $value = $handler->get_value($values);
+ if (is_array($value)) {
+ return self::render_list($handler, $value, $values);
+ }
+ return self::render_entity_link($handler, $value, $values);
+ }
+
+ /**
+ * Render a list of values.
+ *
+ * @param $handler
+ * The field handler whose field is rendered.
+ * @param $list
+ * The list of values to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value for the given list.
+ */
+ public static function render_list($handler, $list, $values) {
+ // Allow easy overriding of this behaviour in the specific field handler.
+ if (method_exists($handler, 'render_list')) {
+ return $handler->render_list($list, $values);
+ }
+ if (isset($handler->options['list']['mode'])) {
+ if ($handler->options['list']['mode'] == 'first') {
+ $list = count($list) ? array_shift($list) : NULL;
+ if (is_array($list)) {
+ return self::render_list($handler, $list, $values);
+ }
+ elseif (isset($list)) {
+ return self::render_entity_link($handler, $list, $values);
+ }
+ return NULL;
+ }
+ if ($handler->options['list']['mode'] == 'count') {
+ return count($list);
+ }
+ }
+ $inner_values = array();
+ foreach ($list as $value) {
+ $value = is_array($value) ? self::render_list($handler, $value, $values) : self::render_entity_link($handler, $value, $values);
+ if ($value) {
+ $inner_values[] = $value;
+ }
+ }
+ $separator = isset($handler->options['list']['separator']) ? $handler->options['list']['separator'] : ', ';
+ return implode($separator, $inner_values);
+ }
+
+ /**
+ * Render a single value as a link to the entity if applicable.
+ *
+ * @param $handler
+ * The field handler whose field is rendered.
+ * @param $value
+ * The single value to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value.
+ */
+ public static function render_entity_link($handler, $value, $values) {
+ // Allow easy overriding of this behaviour in the specific field handler.
+ if (method_exists($handler, 'render_entity_link')) {
+ return $handler->render_entity_link($value, $values);
+ }
+ $render = self::render_single_value($handler, $value, $values);
+ if (!$handler->options['link_to_entity']) {
+ return $render;
+ }
+ $entity = $handler->get_value($values, 'entity object');
+ if (is_object($entity) && ($url = entity_uri($handler->entity_type, $entity))) {
+ return l($render, $url['path'], array('html' => TRUE) + $url['options']);
+ }
+ return $render;
+ }
+
+ /**
+ * Render a single value.
+ *
+ * @param $handler
+ * The field handler whose field is rendered.
+ * @param $value
+ * The single value to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ *
+ * @return
+ * The rendered value.
+ */
+ public static function render_single_value($handler, $value, $values) {
+ // Try to use the method in the specific field handler.
+ if (method_exists($handler, 'render_single_value')) {
+ $handler->current_value = $value;
+ $return = $handler->render_single_value($value, $values);
+ unset($handler->current_value);
+ return $return;
+ }
+ // Default fallback in case the field handler doesn't provide the method.
+ return is_scalar($value) ? check_plain($value) : nl2br(check_plain(print_r($value, TRUE)));
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_boolean.inc b/views/handlers/entity_views_handler_field_boolean.inc
new file mode 100644
index 0000000..66a822d
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_boolean.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_boolean class.
+ */
+
+/**
+ * A handler to provide proper displays for booleans.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_boolean extends views_handler_field_boolean {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_date.inc b/views/handlers/entity_views_handler_field_date.inc
new file mode 100644
index 0000000..2c3e459
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_date.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_date class.
+ */
+
+/**
+ * A handler to provide proper displays for dates.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_date extends views_handler_field_date {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_duration.inc b/views/handlers/entity_views_handler_field_duration.inc
new file mode 100644
index 0000000..1a7096d
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_duration.inc
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_duration class.
+ */
+
+/**
+ * A handler to provide proper displays for duration properties retrieved via data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_duration extends views_handler_field {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options += EntityFieldHandlerHelper::option_definition($this);
+
+ $options['format_interval'] = array('default' => TRUE);
+ $options['granularity'] = array('default' => 2);
+ $options['prefix'] = array('default' => '', 'translatable' => TRUE);
+ $options['suffix'] = array('default' => '', 'translatable' => TRUE);
+
+ return $options;
+ }
+
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+
+ $form['format_interval'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Format interval'),
+ '#description' => t('If checked, the value will be formatted as a time interval. Otherwise, just the number of seconds will be displayed.'),
+ '#default_value' => $this->options['format_interval'],
+ );
+ $form['granularity'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Granularity'),
+ '#default_value' => $this->options['granularity'],
+ '#description' => t('Specify how many different units to display.'),
+ '#dependency' => array('edit-options-format-interval' => array(TRUE)),
+ '#size' => 2,
+ );
+ $form['prefix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Prefix'),
+ '#default_value' => $this->options['prefix'],
+ '#description' => t('Text to put before the duration text.'),
+ );
+ $form['suffix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Suffix'),
+ '#default_value' => $this->options['suffix'],
+ '#description' => t('Text to put after the duration text.'),
+ );
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ if ($this->options['format_interval']) {
+ $value = format_interval($value, (int) $this->options['granularity']);
+ }
+ return $this->sanitize_value($this->options['prefix'], 'xss') .
+ $this->sanitize_value($value) .
+ $this->sanitize_value($this->options['suffix'], 'xss');
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_entity.inc b/views/handlers/entity_views_handler_field_entity.inc
new file mode 100644
index 0000000..2b5c689
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_entity.inc
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_entity class.
+ */
+
+/**
+ * A handler to provide proper displays for entities retrieved via data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_entity extends views_handler_field_entity {
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options += EntityFieldHandlerHelper::option_definition($this);
+
+ $options['display'] = array('default' => 'label');
+ $options['link_to_entity']['default'] = TRUE;
+ $options['view_mode'] = array('default' => 'default');
+
+ return $options;
+ }
+
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ // We want a different form field at a different place.
+ unset($form['link_to_entity']);
+
+ $options = array(
+ 'label' => t('Show entity label'),
+ 'id' => t('Show entity ID'),
+ 'view' => t('Show complete entity'),
+ );
+ $form['display'] = array(
+ '#type' => 'select',
+ '#title' => t('Display'),
+ '#description' => t('Decide how this field will be displayed.'),
+ '#options' => $options,
+ '#default_value' => $this->options['display'],
+ );
+ $form['link_to_entity'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Link to entity'),
+ '#description' => t('Link this field to the entity.'),
+ '#default_value' => $this->options['link_to_entity'],
+ '#dependency' => array('edit-options-display' => array('label', 'id')),
+ );
+
+ // Stolen from entity_plugin_row_entity_view.
+ $entity_info = entity_get_info($this->definition['entity type']);
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $mode => $settings) {
+ $options[$mode] = $settings['label'];
+ }
+ }
+
+ if (count($options) > 1) {
+ $form['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $this->options['view_mode'],
+ '#dependency' => array('edit-options-display' => array('view')),
+ );
+ }
+ else {
+ $form['view_mode'] = array(
+ '#type' => 'value',
+ '#value' => $options ? key($options) : 'default',
+ );
+ }
+ }
+
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a value as a link to the entity if applicable.
+ *
+ * @param $value
+ * The value to render.
+ * @param $values
+ * The values for the current row retrieved from the Views query, as an
+ * object.
+ */
+ public function render_entity_link($entity, $values) {
+ $type = $this->definition['entity type'];
+ if (!is_object($entity) && isset($entity) && $entity !== FALSE) {
+ $entity = entity_load_single($type, $entity);
+ }
+ if (!$entity) {
+ return '';
+ }
+ $render = $this->render_single_value($entity, $values);
+ if (!$this->options['link_to_entity']) {
+ return $render;
+ }
+ if (is_object($entity) && ($url = entity_uri($type, $entity))) {
+ return l($render, $url['path'], array('html' => TRUE) + $url['options']);
+ }
+ return $render;
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($entity, $values) {
+ $type = $this->definition['entity type'];
+ if (!is_object($entity) && isset($entity) && $entity !== FALSE) {
+ $entity = entity_load_single($type, $entity);
+ }
+ if (!$entity) {
+ return '';
+ }
+
+ if ($this->options['display'] === 'view') {
+ $entity_view = entity_view($type, array($entity), $this->options['view_mode']);
+ return render($entity_view);
+ }
+
+ if ($this->options['display'] == 'label') {
+ $value = entity_label($type, $entity);
+ }
+ // Either $options[display] == 'id', or we have no label.
+ if (empty($value)) {
+ $value = entity_id($type, $entity);
+ }
+ $value = $this->sanitize_value($value);
+
+ return $value;
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_field.inc b/views/handlers/entity_views_handler_field_field.inc
new file mode 100644
index 0000000..af956d6
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_field.inc
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_field class.
+ */
+
+/**
+ * A handler to provide proper displays for Field API fields.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_field extends views_handler_field_field {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * The entity for which this field is currently rendered.
+ */
+ public $entity;
+
+ /**
+ * Return TRUE if the user has access to view this field.
+ */
+ public function access() {
+ return field_access('view', $this->field_info, $this->definition['entity type']);
+ }
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query($use_groupby = FALSE) {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Override so it doesn't do any harm (or, anything at all).
+ */
+ public function post_execute(&$values) { }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values, TRUE);
+ }
+
+ /**
+ * Overridden to get the items our way.
+ */
+ public function get_items($values) {
+ $items = array();
+ // Set the entity type for the parent handler.
+ $values->_field_data[$this->field_alias]['entity_type'] = $this->entity_type;
+ // We need special handling for lists of entities as the base.
+ $entities = EntityFieldHandlerHelper::get_value($this, $values, 'entity object');
+ if (!is_array($entities)) {
+ $entities = array($entities);
+ }
+ foreach ($entities as $entity) {
+ // Only try to render the field if it is even present on this bundle.
+ // Otherwise, field_view_field() will trigger a fatal.
+ list (, , $bundle) = entity_extract_ids($this->entity_type, $entity);
+ if (field_info_instance($this->entity_type, $this->definition['field_name'], $bundle)) {
+ // Set the currently rendered entity.
+ $values->_field_data[$this->field_alias]['entity'] = $this->entity = $entity;
+ $items = array_merge($items, $this->set_items($values, $this->view->row_index));
+ }
+ }
+ return $items;
+ }
+
+ /**
+ * Overridden to get the entity and put it where it is assumed to be.
+ */
+ public function get_value($values, $field = NULL) {
+ if (!$this->entity) {
+ return NULL;
+ }
+ if ($field == 'entity') {
+ return $this->entity;
+ }
+ return parent::get_value($values, $field);
+ }
+}
diff --git a/views/handlers/entity_views_handler_field_numeric.inc b/views/handlers/entity_views_handler_field_numeric.inc
new file mode 100644
index 0000000..3ddd9d1
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_numeric.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_numeric class.
+ */
+
+/**
+ * Render a field as a numeric value.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_numeric extends views_handler_field_numeric {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_options.inc b/views/handlers/entity_views_handler_field_options.inc
new file mode 100644
index 0000000..95b557a
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_options.inc
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_options class.
+ */
+
+/**
+ * A handler to provide proper displays for values chosen from a set of options.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_options extends views_handler_field {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * The key / name mapping for the options.
+ */
+ public $option_list;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Specifies the options this handler uses.
+ */
+ public function option_definition() {
+ $options = parent::option_definition();
+ $options += EntityFieldHandlerHelper::option_definition($this);
+ $options['format_name'] = array('default' => TRUE);
+ return $options;
+ }
+
+ /**
+ * Returns an option form for setting this handler's options.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+
+ $form['format_name'] = array(
+ '#title' => t('Use human-readable name'),
+ '#type' => 'checkbox',
+ '#description' => t("If this is checked, the values' names will be displayed instead of their internal identifiers."),
+ '#default_value' => $this->options['format_name'],
+ '#weight' => -5,
+ );
+ }
+
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ if (!isset($this->option_list)) {
+ $this->option_list = array();
+ $callback = $this->definition['options callback'];
+ if (is_callable($callback['function'])) {
+ $this->option_list = call_user_func($callback['function'], $this->base_field, $callback['info'], 'view');
+ }
+ }
+ if ($this->options['format_name'] && isset($this->option_list[$value])) {
+ $value = $this->option_list[$value];
+ }
+
+ return $this->sanitize_value($value);
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_text.inc b/views/handlers/entity_views_handler_field_text.inc
new file mode 100644
index 0000000..99d4579
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_text.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_text class.
+ */
+
+/**
+ * A handler to display text data.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_text extends views_handler_field {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return $this->sanitize_value($value, 'xss');
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_field_uri.inc b/views/handlers/entity_views_handler_field_uri.inc
new file mode 100644
index 0000000..359bbe1
--- /dev/null
+++ b/views/handlers/entity_views_handler_field_uri.inc
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_field_uri class.
+ */
+
+/**
+ * Field handler to provide simple renderer that turns a URL into a clickable link.
+ *
+ * Overrides the default Views handler to retrieve the data from an entity via
+ * data selection.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_field_uri extends views_handler_field_url {
+
+ /**
+ * Stores the entity type which is loaded by this field.
+ */
+ public $entity_type;
+
+ /**
+ * Stores the result entities' metadata wrappers.
+ */
+ public $wrappers = array();
+
+ /**
+ * The base name of the field, without data selector.
+ */
+ public $base_field;
+
+ /**
+ * Stores the current value when rendering list fields.
+ */
+ public $current_value;
+
+ /**
+ * Overridden to add the field for the entity ID (if necessary).
+ */
+ public function query() {
+ EntityFieldHandlerHelper::query($this);
+ }
+
+ /**
+ * Adds a click-sort to the query.
+ */
+ public function click_sort($order) {
+ EntityFieldHandlerHelper::click_sort($this, $order);
+ }
+
+ /**
+ * Load the entities for all rows that are about to be displayed.
+ */
+ public function pre_render(&$values) {
+ parent::pre_render($values);
+ EntityFieldHandlerHelper::pre_render($this, $values);
+ }
+
+ /**
+ * Overridden to use a metadata wrapper.
+ */
+ public function get_value($values, $field = NULL) {
+ return EntityFieldHandlerHelper::get_value($this, $values, $field);
+ }
+
+ /**
+ * Provide options for this handler.
+ */
+ public function option_definition() {
+ return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
+ }
+
+ /**
+ * Provide a options form for this handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ EntityFieldHandlerHelper::options_form($this, $form, $form_state);
+ }
+
+ /**
+ * Render the field.
+ *
+ * @param $values
+ * The values retrieved from the database.
+ */
+ public function render($values) {
+ return EntityFieldHandlerHelper::render($this, $values);
+ }
+
+ /**
+ * Render a single field value.
+ */
+ public function render_single_value($value, $values) {
+ return parent::render($values);
+ }
+
+}
diff --git a/views/handlers/entity_views_handler_relationship.inc b/views/handlers/entity_views_handler_relationship.inc
new file mode 100644
index 0000000..a15c056
--- /dev/null
+++ b/views/handlers/entity_views_handler_relationship.inc
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains the entity_views_handler_relationship class.
+ */
+
+/**
+ * Relationship handler for data selection tables.
+ *
+ * This handler may only be used in conjunction with data selection based Views
+ * tables or other base tables using a query plugin that supports data
+ * selection.
+ *
+ * @see entity_views_field_definition()
+ * @ingroup views_field_handlers
+ */
+class entity_views_handler_relationship extends views_handler_relationship {
+
+ /**
+ * Slightly modify the options form provided by the parent handler.
+ */
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+ // This won't work with data selector-based relationships, as we only
+ // inspect those *after* the results are known.
+ $form['required']['#access'] = FALSE;
+ // Notify the user of our restrictions regarding lists of entities, if
+ // appropriate.
+ if (!empty($this->definition['multiple'])) {
+ $form['multiple_note'] = array(
+ '#markup' => t('<strong>Note:</strong> This is a multi-valued relationship, which is currently not supported. ' .
+ 'Only the first related entity will be shown.'),
+ '#weight' => -5,
+ );
+ }
+ }
+
+ /**
+ * Called to implement a relationship in a query.
+ *
+ * As we don't add any data to the query itself, we don't have to do anything
+ * here. Views just don't thinks we have been called unless we set our
+ * $alias property. Otherwise, this override is just here to keep PHP from
+ * blowing up by calling inexistent methods on the query plugin.
+ */
+ public function query() {
+ $this->alias = $this->options['id'];
+ }
+
+}