diff --git a/entity.api.php b/entity.api.php index b47a6b8192031bab9079bfada0988763b3e96a72..edfdaf34cf41297a19be49bc0d958253a035ddff 100644 --- a/entity.api.php +++ b/entity.api.php @@ -401,6 +401,28 @@ 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 56895549c67fb7fcebb5d1128aa79b282c06014e..ee3c3cf951e2d414a64bcde83dce038a5c2b531f 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 43bcdd5ec5922357ee8f501d0ca5191087ebc3b8..c13876a46879d2d3dfe45d1aa57706e8e198bf8b 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. @@ -41,6 +41,21 @@ function entity_get_property_info($entity_type = NULL) { return empty($entity_type) ? $info : (isset($info[$entity_type]) ? $info[$entity_type] : array()); } +/** + * 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. * @@ -310,6 +325,22 @@ function entity_property_list_extract_type($type) { return FALSE; } +/** + * Extracts the innermost type for a type string like list>. + * + * @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. */ diff --git a/views/entity.views.inc b/views/entity.views.inc index f1314e228ea83eaa71d116504b42f0f9df4442a8..9e97daaa342ed9c17abcf75f7c65af80cbfbf6ee 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,9 +37,224 @@ 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(). */ @@ -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 0000000000000000000000000000000000000000..7e98e2c267ed25f8fc25090bc3b5d335faa6cd7c --- /dev/null +++ b/views/entity_views_example_query.php @@ -0,0 +1,88 @@ +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 0000000000000000000000000000000000000000..66a822d2e1ee27083ce3a5fc45e6dfdb7a44f29f --- /dev/null +++ b/views/handlers/entity_views_handler_field_boolean.inc @@ -0,0 +1,104 @@ + 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 0000000000000000000000000000000000000000..2b5c68970ccb451660f46678dd54599f406f62a0 --- /dev/null +++ b/views/handlers/entity_views_handler_field_entity.inc @@ -0,0 +1,171 @@ + '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 0000000000000000000000000000000000000000..af956d63f0435fcd5a1ab000858f56bc87c90bea --- /dev/null +++ b/views/handlers/entity_views_handler_field_field.inc @@ -0,0 +1,114 @@ +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 0000000000000000000000000000000000000000..3ddd9d12c2efe74d262a6e66f092b706ec5351c1 --- /dev/null +++ b/views/handlers/entity_views_handler_field_numeric.inc @@ -0,0 +1,104 @@ + 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 0000000000000000000000000000000000000000..99d4579942d0b1caa50331b4c003cb3e40ef8b04 --- /dev/null +++ b/views/handlers/entity_views_handler_field_text.inc @@ -0,0 +1,104 @@ +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 0000000000000000000000000000000000000000..359bbe100678d7c1bc6bf60c3cac7fe1bce78a6c --- /dev/null +++ b/views/handlers/entity_views_handler_field_uri.inc @@ -0,0 +1,104 @@ +definition['multiple'])) { + $form['multiple_note'] = array( + '#markup' => t('Note: 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']; + } + +}