diff --git a/entity.info b/entity.info index 958e28c..d1e6903 100644 --- a/entity.info +++ b/entity.info @@ -1,12 +1,23 @@ 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.controller.inc +files[] = includes/entity.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.inc +files[] = views/plugins/entity_plugin_row_entity_view.inc diff --git a/entity.module b/entity.module index 45d2231..8cab7f1 100644 --- a/entity.module +++ b/entity.module @@ -1095,6 +1095,41 @@ function entity_metadata_wrapper($type, $data = NULL, array $info = array()) { } /** + * Returns property wrappers for all given data objects. + * + * @param $type + * The type of the passed data. + * @param array $objects + * The data objects to wrap. + * @param $info + * (optional) Specify additional information for the passed data: + * - langcode: (optional) If the data is language specific, its langauge + * code. Defaults to NULL, what means language neutral. + * - bundle: (optional) If an entity is wrapped but not passed, use this key + * to specify the bundle to return a wrapper for. + * - property info: (optional) May be used to use a wrapper with an arbitrary + * data structure (type 'struct'). Use this key for specifying info about + * properties in the same structure as used by hook_entity_property_info(). + * - property info alter: (optional) A callback for altering the property + * info before it is utilized by the wrapper. + * - property defaults: (optional) An array of defaults for the info of + * each property of the wrapped data item. + * + * @return array + * An array of EntityMetadataWrapper objects wrapping the passed data, keyed + * the same way as $objects. + * + * @see entity_metadata_wrapper() + */ +function entity_metadata_wrapper_multiple($type, array $objects = array(), array $info = array()) { + $wrappers = array(); + foreach ($objects as $id => $object) { + $wrappers[$id] = entity_metadata_wrapper($type, $object, $info); + } + return $wrappers; +} + +/** * Returns a metadata wrapper for accessing site-wide properties. * * Although there is no 'site' entity or such, modules may provide info about @@ -1113,6 +1148,172 @@ function entity_metadata_site_wrapper() { } /** + * Extracts data from a metadata wrapper based on a property selector. + * + * The data will be returned wrapped by one or more metadata wrappers. + * + * A property 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 property 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 values in a property are automatically handled. They will result in + * nested arrays being returned. + * + * @param EntityMetadataWrapper $wrapper + * The wrapper from which to extract data. + * @param $property + * The selector specifying the data to extract. + * @param array $options + * An array of options that should be passed to the + * EntityMetadataWrapper::value() method (see there). + * + * @return + * Either an (arbitrarily nested) array containing EntityMetadataWrapper + * objects for the given property, or one such wrapper, depending on whether + * the property selector contains any list-typed properties. + */ +function entity_metadata_extract_property(EntityMetadataWrapper $wrapper, $property, array $options = array()) { + $ret = entity_metadata_extract_property_multiple(array($wrapper), $property, $options); + return reset($ret); +} + +/** + * Extracts data from several metadata wrappers based on a property selector. + * + * All metadata wrappers passed to this function have to be based on the exact + * same property information. + * + * @param array $wrappers + * The EntityMetadataWrapper objects from which to extract data. + * @param $property + * The selector specifying the data to extract. + * @param array $options + * An array of options that should be passed to the + * EntityMetadataWrapper::value() method (see there). + * + * @return array + * 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. + * + * @see entity_metadata_extract_property() + */ +function entity_metadata_extract_property_multiple(array $wrappers, $property, array $options = array()) { + $options['identifier'] = TRUE; + if (!$wrappers) { + return array(); + } + // If an error occurs, we need a valid return array. + foreach ($wrappers as $i => $wrapper) { + $ret[$i] = NULL; + } + try { + $type = reset($wrappers)->type(); + // List types create nested arrays. + if ($t = entity_property_list_extract_type($type)) { + // If these are entities, we should do a multiple load. + if (entity_get_info($t)) { + $nested_entities = array(); + $wrapper_refs = array(); + foreach ($wrappers as $i => $wrapper) { + foreach ($wrapper as $nested) { + $id = $nested->value($options); + $nested_entities[$id] = $id; + $wrapper_refs[$i][] = $id; + } + } + $nested_entities = entity_load($t, $nested_entities); + $new_wrappers = entity_metadata_wrapper_multiple($t, $nested_entities); + } + else { + $new_wrappers = array(); + $wrapper_refs = array(); + $count = 0; + foreach ($wrappers as $i => $wrapper) { + $ret[$i] = array(); + foreach ($wrapper as $nested) { + $new_wrappers[$count] = $nested; + $wrapper_refs[$i][] = $count; + ++$count; + } + } + } + // Extract data from all contained list items. + $data = entity_metadata_extract_property_multiple($new_wrappers, $property, $options); + $ret = array(); + // Now aggregate them as the return value for the given wrappers, defaulting + // to empty arrays where no data was contained. + foreach ($wrappers as $i => $wrapper) { + foreach ($wrapper_refs[$i] as $id) { + // Don't add NULL values or empty arrays. + if ($data[$id] || (isset($data[$id]) && !is_array($data[$id]))) { + $ret[$i][] = $data[$id]; + } + } + } + return $ret; + } + if (strpos($property, ':')) { + // We extract a property from a referenced entity or contained structure. + list($field, $nested) = explode(':', $property, 2); + } + else { + $field = $property; + } + $t = reset($wrappers)->$field->type(); + // As above: if we build entity-valued nested wrappers, we should do a + // multiple load. + if (entity_get_info($t)) { + $nested_entities = array(); + $wrapper_refs = array(); + foreach ($wrappers as $i => $wrapper) { + if (isset($wrapper->$field)) { + $id = $wrapper->$field->value($options); + if ($id !== FALSE) { + $nested_entities[$id] = $id; + $wrapper_refs[$i] = $id; + } + } + } + // If we aren't extracting a nested value, we are done now. + if (!isset($nested)) { + return entity_metadata_wrapper_multiple($t, $wrapper_refs) + $ret; + } + $nested_entities = entity_load($t, $nested_entities); + foreach ($wrapper_refs as $i => $id) { + if (isset($nested_entities[$id])) { + $new_wrappers[$i] = entity_metadata_wrapper($t, $nested_entities[$id]); + } + } + } + else { + $new_wrappers = array(); + foreach ($wrappers as $i => $wrapper) { + if (isset($wrapper->$field)) { + $new_wrappers[$i] = $wrapper->$field; + } + } + } + // If $nested is set, we have a deeper level to go to and extract data from. + // Otherwise, we are finished here. + return (isset($nested) ? entity_metadata_extract_property_multiple($new_wrappers, $nested, $options) : $new_wrappers) + $ret; + } + catch (EntityMetadataWrapperException $e) { + $vars = array( + '@property' => $property, + '@type' => $type, + '@msg' => $e->getMessage(), + ); + watchdog('entity', nl2br(check_plain($e->getTraceAsString())), NULL);#'An error occurred while extracting property @property from wrappers of type @type: @msg', $vars, WATCHDOG_WARNING); + return $ret; + } +} + +/** * Implements hook_module_implements_alter(). * * Moves the hook_entity_info_alter() implementation to the bottom so it is diff --git a/includes/entity.property.inc b/includes/entity.property.inc index 2c7700b..711fd86 100644 --- a/includes/entity.property.inc +++ b/includes/entity.property.inc @@ -42,6 +42,21 @@ function entity_get_property_info($entity_type = NULL) { } /** + * Returns the default information for an entity property. + * + * @see hook_entity_property_info() + * + * @return + * An array of optional property information keys mapped to their defaults. + */ +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,20 @@ function entity_property_list_extract_type($type) { } /** + * Extracts the innermost type for a list type string like list. + * + * @return + * The innermost type in the nested list type, or the type itself if it is no + * list type. + */ +function entity_property_list_inner_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) { diff --git a/views/entity.views.inc b/views/entity.views.inc index aef255a..f91bbc7 100644 --- a/views/entity.views.inc +++ b/views/entity.views.inc @@ -10,8 +10,9 @@ */ function entity_views_data() { $data = array(); + foreach (entity_crud_get_info() as $type => $info) { - // Only enable the integration by default, it we know the module providing + // Only enable the integration by default if we know the module providing // the entity and it does not provide views integration yet. if (!isset($info['views controller class'])) { $info['views controller class'] = isset($info['module']) && !module_hook($info['module'], 'views_data') ? 'EntityDefaultViewsController' : FALSE; @@ -27,15 +28,176 @@ function entity_views_data() { } } + // Add generic (non-base) tables 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 the entity Views table definition for a type. + * + * @param $type + * The entity type whose table definition should be return. + * + * @return + * An array containing a generic Views table definition for the entity type. + */ +function entity_views_table_definition($type) { + $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 creating a Views field definition. + * + * @param $field + * The key to be used for the property's field handler (and as the prefix for + * nested ones). + * @param array $property + * 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. + */ +function entity_views_field_definition($field, array $property, array &$table, $title_prefix = '') { + $additional = array(); + $additional_field = array(); + + // Create a valid Views field identifier (no colons, etc.). + $key = _entity_views_field_identifier($field, $table); + if ($key != $field) { + $additional['real field'] = $field; + } + + // 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', + ); + // @todo Some alter hook? + + $property += entity_property_info_defaults(); + $type = entity_property_list_inner_type($property['type']); + $title = $title_prefix . $property['label']; + if (entity_get_info($type)) { + $additional_field['entity type'] = $type; + // Others might have unset the relationship handler in the alter hook. + if (isset($field_handlers['relationship'])) { + $additional['relationship'] = array( + 'handler' => $field_handlers['relationship'], + 'base' => 'entity_' . $type, + 'relationship field' => $field, + 'label' => $title, + ); + } + // They could also add handlers for specific entity types. + if (!isset($field_handlers[$type])) { + $type = 'entity'; + } + } + elseif (!empty($property['field'])) { + $type = 'field'; + $field_name = ltrim(substr($field, strrpos($field, ':')), ':'); + $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['options list']) && is_callable($property['options list'])) { + // If this is a nested property, we need to get rid of all prefixes first. + $field_name = ltrim(substr($field, strrpos($field, ':')), ':'); + $options = call_user_func($property['options list'], $field_name, $property, 'view'); + if ($options) { + $type = 'options'; + $additional_field['options'] = $options; + } + } + elseif ($type == 'decimal') { + $additional_field['float'] = TRUE; + } + + if (isset($field_handlers[$type])) { + $table += array($key => array()); + $table[$key] += array( + 'title' => $title, + 'help' => empty($property['description']) ? t('(No information available)') : $property['description'], + 'field' => array(), + ); + $table[$key]['field'] += array( + 'handler' => $field_handlers[$type], + 'type' => $property['type'], + ); + $table[$key] += $additional; + $table[$key]['field'] += $additional_field; + } + if (!empty($property['property info'])) { + foreach ($property['property info'] as $nested_key => $nested_property) { + entity_views_field_definition($field . ':' . $nested_key, $nested_property, $table, $title . ' ยป '); + } + } +} + +/** + * Helper function for creating valid Views field identifiers out of property accessors. + * + * @return string + * A valid Views field identifier that isn't yet used as a key in $table. + */ +function _entity_views_field_identifier($field, array $table) { + $aliases = &drupal_static(__FUNCTION__, array()); + // If we re-use aliases (tables might be different), we have to make sure we + // never return the same alias for two different fields (even this is highly + // unlikely). + $used = array_flip($aliases); + + if (!isset($aliases[$field])) { + $key = $base = preg_replace('/[^a-zA-Z0-9]+/S', '_', $field); + $i = 0; + while (isset($table[$key]) || (isset($used[$key]) && $used[$key] !== $field)) { + $key = $base . '_' . ++$i; + } + $aliases[$field] = $key; + } + + return $aliases[$field]; +} + +/** * Implements hook_views_plugins(). */ function entity_views_plugins() { foreach (views_fetch_data() as $table => $data) { - if (!empty($data['table']['base']['entity type'])) { + if (!empty($data['table']['entity type'])) { $base_tables[] = $table; } } @@ -93,8 +255,8 @@ class EntityDefaultViewsController { 'title' => drupal_ucfirst($this->info['label']), // @todo: Support an entity info description key or such? 'help' => '', - 'entity type' => $this->type, ); + $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..461cca2 --- /dev/null +++ b/views/entity_views_example_query.php @@ -0,0 +1,65 @@ +definition['type'])) { + $options['list']['contains']['mode'] = array('default' => 'collapse'); + $options['list']['contains']['glue'] = 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']['glue'] = array( + '#type' => 'textfield', + '#title' => t('List seperator'), + '#default_value' => $handler->options['list']['glue'], + '#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 = ltrim(substr($handler->real_field, strrpos($handler->real_field, ':')), ':'); + } + + /** + * 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, either defined via Views or via + * Entity API property selectors. + */ + public static function pre_render($handler, &$values) { + if (empty($values)) { + return; + } + if (method_exists($handler->query, 'get_result_wrappers')) { + list($type, $wrappers) = $handler->query->get_result_wrappers($values, NULL); + } + elseif (method_exists($handler->query, 'get_result_entities')) { + list($type, $entities) = $handler->query->get_result_entities($values, NULL); + $wrappers = entity_metadata_wrapper_multiple($type, $entities); + } + else { + return; + } + + $relationship = self::construct_property_selector($handler); + if ($relationship) { + $wrappers = entity_metadata_extract_property_multiple($wrappers, $relationship); + unset($entities); + $w = $wrappers; + while (is_array($w)) { + $w = array_filter($w); + $w = reset($w); + } + if ($w) { + $type = $w->type(); + } + } + $wrappers = array_filter($wrappers); + + $handler->entity_type = $type; + $handler->entities = array(); + $handler->wrappers = $wrappers; + if (isset($entities)) { + $handler->entities = array_filter($entities); + } + elseif (entity_get_info($type)) { + // We can just load the entities from the wrappers. + foreach ($wrappers as $id => $wrapper) { + $handler->entities[$id] = $wrapper->value(); + } + } + } + + /** + * Return a Entity API property selector for the given handler's relationship. + * + * @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 property selector for the given handler's relationship. + * + * @see entity_metadata_extract_property() + */ + public static function construct_property_selector($handler, $complete = FALSE) { + $ret = ''; + if ($handler->relationship) { + $h = $handler; + $view = $h->view; + while (!empty($h->relationship) && !empty($view->relationship[$h->relationship])) { + $h = $view->relationship[$h->relationship]; + $ret = $h->real_field . ($ret ? ":$ret" : ''); + } + } + + // If we have a selector as the real_field, append this to the returned + // relationship selector. + if ($complete) { + $ret .= ($ret ? ':' : '') . $handler->real_field; + } + elseif ($pos = strrpos($handler->real_field, ':')) { + $ret .= ($ret ? ':' : '') . substr($handler->real_field, 0, $pos); + } + + return $ret; + } + + /** + * Overridden to use a metadata wrapper (if necessary). + * + * Uses $values->_entity_properties to look for already extracted fields. + * + * @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 right data. + if (isset($handler->_cache[$field])) { + return $handler->_cache[$field]; + } + $field = isset($field) ? $field : $handler->base_field; + $selector = self::construct_property_selector($handler); + $selector = $selector ? "$selector:$field" : $field; + if (!isset($values->_entity_properties[$selector])) { + if (!isset($handler->wrappers[$handler->view->row_index])) { + $values->_entity_properties[$selector] = $default; + } + else { + $wrapper = $handler->wrappers[$handler->view->row_index]; + 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; + } + } + } + return $values->_entity_properties[$selector]; + } + + /** + * 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 $value + * 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, $value, $values) { + // Allow easy overriding of this behaviour in the specific field handler. + if (method_exists($handler, 'render_list')) { + return $handler->render_list($value, $values); + } + if (isset($handler->options['list']['mode'])) { + if ($handler->options['list']['mode'] == 'first') { + $value = count($value) ? array_shift($value) : NULL; + if (is_array($value)) { + return self::render_entity_link($handler, $value, $values); + } + elseif (isset($value)) { + return self::render_entity_link($handler, $value, $values); + } + return NULL; + } + if ($handler->options['list']['mode'] == 'count') { + return count($value); + } + } + $vs = array(); + foreach ($value as $v) { + $v = is_array($v) ? self::render_list($handler, $v, $values) : self::render_entity_link($handler, $v, $values); + if ($v) { + $vs[] = $v; + } + } + $glue = isset($handler->options['list']['glue']) ? $handler->options['list']['glue'] : ', '; + return implode($glue, $vs); + } + + /** + * 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))) { + // We LOVE our core bugs! (http://drupal.org/node/1057242) + if ($handler->entity_type === 'file' && !is_array($url)) { + $url = array( + 'path' => file_create_url($url), + 'options' => array(), + ); + } + 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->_cache[NULL] = $value; + $ret = $handler->render_single_value($value, $values); + unset($handler->_cache[NULL]); + return $ret; + } + // Lame 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..715b20e --- /dev/null +++ b/views/handlers/entity_views_handler_field_boolean.inc @@ -0,0 +1,103 @@ + 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..b78aee7 --- /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'], + ); + } + 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 = 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))) { + // We LOVE our core bugs! (http://drupal.org/node/1057242) + if ($type === 'file' && !is_array($url)) { + $url = array( + 'path' => file_create_url($url), + 'options' => array(), + ); + } + 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') { + return entity_view($type, $entity, $this->options['view_mode']); + } + + 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..d4adafa --- /dev/null +++ b/views/handlers/entity_views_handler_field_field.inc @@ -0,0 +1,108 @@ +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); + } + + /** + * Overridden to get the items our way. + */ + public function get_items($values) { + // Only try to render the field if it is even present on this bundle. + // Otherwise, field_view_field() will trigger a fatal. + // The $this->get_value() call is actually necessary in any case, to fill + // some $values keys required by set_items(). + $entity = $this->get_value($values, 'entity'); + list (, , $bundle) = entity_extract_ids($this->entity_type, $entity); + if (field_info_instance($this->entity_type, $this->definition['field_name'], $bundle)) { + return $this->set_items($values, $this->view->row_index); + } + return array(); + } + + /** + * Overridden to get the entity and put it where it is assumed to be. + */ + public function get_value($values, $field = NULL) { + if (!isset($this->entities[$this->view->row_index])) { + return NULL; + } + $entity = $this->entities[$this->view->row_index]; + if (empty($values->_field_data[$this->field_alias]['entity'])) { + $values->_field_data[$this->field_alias]['entity'] = $entity; + $values->_field_data[$this->field_alias]['entity_type'] = $this->entity_type; + } + if ($field == 'entity') { + return $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..4b2b5f3 --- /dev/null +++ b/views/handlers/entity_views_handler_field_numeric.inc @@ -0,0 +1,103 @@ + 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 ($this->options['format_name'] && isset($this->definition['options'][$value])) { + $value = $this->definition['options'][$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..658d0e8 --- /dev/null +++ b/views/handlers/entity_views_handler_field_text.inc @@ -0,0 +1,103 @@ +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..3c95bb5 --- /dev/null +++ b/views/handlers/entity_views_handler_field_uri.inc @@ -0,0 +1,103 @@ +alias = $this->real_field; + } + +} diff --git a/views/plugins/entity_plugin_row_entity_view.inc b/views/plugins/entity_plugin_row_entity_view.inc index fcdbe65..d0cc743 100644 --- a/views/plugins/entity_plugin_row_entity_view.inc +++ b/views/plugins/entity_plugin_row_entity_view.inc @@ -18,8 +18,8 @@ class entity_plugin_row_entity_view extends views_plugin_row { parent::init($view, $display, $options); $table_data = views_fetch_data($this->view->base_table); - $this->entity_type = $table_data['table']['base']['entity type']; - $this->skip_load = !empty($table_data['table']['base']['skip entity load']); + $this->entity_type = $table_data['table']['entity type']; + $this->skip_load = !empty($table_data['table']['skip entity load']); // Set base table and field information as used by views_plugin_row to // select the entity id if used with default query class.