| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701 | <?php/** * @file * Provide views data for modules making use of the entity CRUD API. *//** * Implements hook_views_data(). * * Provides Views integration for entities if they satisfy one of these * conditions: *  - hook_entity_info() specifies a 'views controller class' key. *  - hook_entity_info() specifies a 'module' key, and the module does not *    implement hook_views_data(). * * @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.    if (!isset($info['views controller class'])) {      $info['views controller class'] = isset($info['module']) && !module_hook($info['module'], 'views_data') ? 'EntityDefaultViewsController' : FALSE;    }    if ($info['views controller class']) {      $controller = new $info['views controller class']($type);      // Relationship data may return views data for already existing tables,      // so merge results on the second level.      foreach ($controller->views_data() as $table => $table_data) {        $data += array($table => array());        $data[$table] = array_merge($data[$table], $table_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;    }    // Generally expose properties marked as 'entity views field'.    $data['views_entity_' . $type] = array();    foreach (entity_get_all_property_info($type) as $key => $property) {      if (!empty($property['entity views field'])) {        entity_views_field_definition($key, $property, $data['views_entity_' . $type]);      }    }  }  // Expose generally usable entity-related fields.  foreach (entity_get_info() as $entity_type => $info) {    if (entity_type_supports($entity_type, 'view')) {      // Expose a field allowing to display the rendered entity.      $data['views_entity_' . $entity_type]['rendered_entity'] = array(        'title' => t('Rendered @entity-type', array('@entity-type' => $info['label'])),        'help' => t('The @entity-type of the current relationship rendered using a view mode.', array('@entity-type' => $info['label'])),        'field' => array(          'handler' => 'entity_views_handler_field_entity',          'type' => $entity_type,          // The EntityFieldHandlerHelper treats the 'entity object' data          // selector as special case for loading the base entity.          'real field' => 'entity object',        ),      );    }  }  $data['entity__global']['table']['group'] = t('Entity');  $data['entity__global']['table']['join'] = array(    // #global let's it appear all the time.    '#global' => array(),  );  $data['entity__global']['entity'] = array(    'title' => t('Rendered entity'),    'help' => t('Displays a single chosen entity.'),    'area' => array(      'handler' => 'entity_views_handler_area_entity',    ),  );  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. * @param $exclude *   Whether properties already exposed as 'entity views field' should be *   excluded. Defaults to TRUE, as they are available for all views tables for *   the entity type anyways. * * @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, $exclude = TRUE) {  // 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])) {    // Work-a-round to fix updating, see http://drupal.org/node/1330874.    // Views data might be rebuilt on update.php before the registry is rebuilt,    // thus the class cannot be auto-loaded.    if (!class_exists('EntityFieldHandlerHelper')) {      module_load_include('inc', 'entity', 'views/handlers/entity_views_field_handler_helper');    }    $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) {      if (!$exclude || empty($property['entity views field'])) {        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['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() {  // Have views cache the table list for us so it gets  // cleared at the appropriate times.  $data = views_cache_get('entity_base_tables', TRUE);  if (!empty($data->data)) {    $base_tables = $data->data;  }  else {    $base_tables = array();    foreach (views_fetch_data() as $table => $data) {      if (!empty($data['table']['entity type']) && !empty($data['table']['base'])) {        $base_tables[] = $table;      }    }    views_cache_set('entity_base_tables', $base_tables, TRUE);  }  if (!empty($base_tables)) {    return array(      'module' => 'entity',      'row' => array(        'entity' => array(          'title' => t('Rendered entity'),          'help' => t('Renders a single entity in a specific view mode (e.g. teaser).'),          'handler' => 'entity_views_plugin_row_entity_view',          'uses fields' => FALSE,          'uses options' => TRUE,          'type' => 'normal',          'base' => $base_tables,        ),      ),    );  }}/** * Default controller for generating basic views integration. * * The controller tries to generate suiting views integration for the entity * based upon the schema information of its base table and the provided entity * property information. * For that it is possible to map a property name to its schema/views field * name by adding a 'schema field' key with the name of the field as value to * the property info. */class EntityDefaultViewsController {  protected $type, $info, $relationships;  public function __construct($type) {    $this->type = $type;    $this->info = entity_get_info($type);  }  /**   * Defines the result for hook_views_data().   */  public function views_data() {    $data = array();    $this->relationships = array();    if (!empty($this->info['base table'])) {      $table = $this->info['base table'];      // Define the base group of this table. Fields that don't      // have a group defined will go into this field by default.      $data[$table]['table']['group']  = drupal_ucfirst($this->info['label']);      $data[$table]['table']['entity type'] = $this->type;      // If the plural label isn't available, use the regular label.      $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label'];      $data[$table]['table']['base'] = array(        'field' => $this->info['entity keys']['id'],        'access query tag' => $this->type . '_access',        'title' => drupal_ucfirst($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.      $data += $this->relationships;    }    if (!empty($this->info['revision table']) && !empty($this->info['entity keys']['revision'])) {      $revision_table = $this->info['revision table'];      $data[$table]['table']['default_relationship'] = array(        $revision_table => array(          'table' => $revision_table,          'field' => $this->info['entity keys']['revision'],        ),      );      // Define the base group of this table. Fields that don't      // have a group defined will go into this field by default.      $data[$revision_table]['table']['group']  = drupal_ucfirst($this->info['label']) . ' ' . t('Revisions');      $data[$revision_table]['table']['entity type'] = $this->type;      // If the plural label isn't available, use the regular label.      $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label'];      $data[$revision_table]['table']['base'] = array(        'field' => $this->info['entity keys']['revision'],        'access query tag' => $this->type . '_access',        'title' => drupal_ucfirst($label) . ' ' . t('Revisions'),        'help' => (isset($this->info['description']) ? $this->info['description'] . ' ' : '') . t('Revisions'),      );      $data[$revision_table]['table']['entity type'] = $this->type;      $data[$revision_table] += $this->schema_revision_fields();      // Add in any reverse-relationships which have been determined.      $data += $this->relationships;      // For other base tables, explain how we join.      $data[$revision_table]['table']['join'] = array(        // Directly links to base table.        $table => array(          'left_field' => $this->info['entity keys']['revision'],          'field' => $this->info['entity keys']['revision'],        ),      );      $data[$revision_table]['table']['default_relationship'] = array(        $table => array(          'table' => $table,          'field' => $this->info['entity keys']['id'],        ),      );    }    return $data;  }  /**   * Try to come up with some views fields with the help of the schema and   * the entity property information.   */  protected function schema_fields() {    $schema = drupal_get_schema($this->info['base table']);    $properties = entity_get_property_info($this->type) + array('properties' => array());    $data = array();    foreach ($properties['properties'] as $name => $property_info) {      if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) {        if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) {          $data[$name] = $views_info;        }      }    }    return $data;  }  /**   * Try to come up with some views fields with the help of the revision schema   * and the entity property information.   */  protected function schema_revision_fields() {    $data = array();    if (!empty($this->info['revision table'])) {      $schema = drupal_get_schema($this->info['revision table']);      $properties = entity_get_property_info($this->type) + array('properties' => array());      foreach ($properties['properties'] as $name => $property_info) {        if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) {          if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) {            $data[$name] = $views_info;          }        }      }    }    return $data;  }  /**   * Comes up with views information based on the given schema and property   * info.   */  protected function map_from_schema_info($property_name, $schema_field_info, $property_info) {    $type = isset($property_info['type']) ? $property_info['type'] : 'text';    $views_field_name = $property_info['schema field'];    $return = array();    if (!empty($schema_field_info['serialize'])) {      return FALSE;    }    $description = array(      'title' => $property_info['label'],      'help' => isset($property_info['description']) ? $property_info['description'] : NULL,    );      // Add in relationships to related entities.    if (($info = entity_get_info($type)) && !empty($info['base table'])) {      // Prepare reversed relationship data.      $label_lowercase = drupal_strtolower($this->info['label'][0]) . drupal_substr($this->info['label'], 1);      $property_label_lowercase = drupal_strtolower($property_info['label'][0]) . drupal_substr($property_info['label'], 1);      // We name the field of the first reverse-relationship just with the      // base table to be backward compatible, for subsequents relationships we      // append the views field name in order to get a unique name.      $name = !isset($this->relationships[$info['base table']][$this->info['base table']]) ? $this->info['base table'] : $this->info['base table'] . '_' . $views_field_name;      $this->relationships[$info['base table']][$name] = array(        'title' => $this->info['label'],        'help' => t("Associated @label via the @label's @property.", array('@label' => $label_lowercase, '@property' => $property_label_lowercase)),        'relationship' => array(          'label' => $this->info['label'],          'handler' => $this->getRelationshipHandlerClass($this->type, $type),          'base' => $this->info['base table'],          'base field' => $views_field_name,          'relationship field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],        ),      );      $return['relationship'] = array(        'label' => drupal_ucfirst($info['label']),        'handler' => $this->getRelationshipHandlerClass($type, $this->type),        'base' => $info['base table'],        'base field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],        'relationship field' => $views_field_name,      );      // Add in direct field/filters/sorts for the id itself too.      $type = isset($info['entity keys']['name']) ? 'token' : 'integer';      // Append the views-field-name to the title if it is different to the      // property name.      if ($property_name != $views_field_name) {        $description['title'] .= ' ' . $views_field_name;      }    }    switch ($type) {      case 'token':      case 'text':        $return += $description + array(          'field' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_field',            'click sortable' => TRUE,           ),          'sort' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_sort',          ),          'filter' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_filter_string',          ),          'argument' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_argument_string',          ),        );      break;      case 'decimal':      case 'integer':        $return += $description + array(          'field' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_field_numeric',            'click sortable' => TRUE,            'float' => ($type == 'decimal'),           ),          'sort' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_sort',          ),          'filter' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_filter_numeric',          ),          'argument' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_argument_numeric',          ),        );      break;      case 'date':        $return += $description + array(          'field' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_field_date',            'click sortable' => TRUE,           ),          'sort' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_sort_date',          ),          'filter' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_filter_date',          ),          'argument' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_argument_date',          ),        );      break;      case 'uri':        $return += $description + array(          'field' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_field_url',            'click sortable' => TRUE,           ),          'sort' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_sort',          ),          'filter' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_filter_string',          ),          'argument' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_argument_string',          ),        );      break;      case 'boolean':        $return += $description + array(          'field' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_field_boolean',            'click sortable' => TRUE,           ),          'sort' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_sort',          ),          'filter' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_filter_boolean_operator',          ),          'argument' => array(            'real field' => $views_field_name,            'handler' => 'views_handler_argument_string',          ),        );      break;    }    // If there is an options list callback, add to the filter and field.    if (isset($return['filter']) && !empty($property_info['options list'])) {      $return['filter']['handler'] = 'views_handler_filter_in_operator';      $return['filter']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');      $return['filter']['options arguments'] = array($this->type, $property_name, 'view');    }    // @todo: This class_exists is needed until views 3.2.    if (isset($return['field']) && !empty($property_info['options list']) && class_exists('views_handler_field_machine_name')) {      $return['field']['handler'] = 'views_handler_field_machine_name';      $return['field']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');      $return['field']['options arguments'] = array($this->type, $property_name, 'view');    }    return $return;  }  /**   * Determines the handler to use for a relationship to an entity type.   *   * @param $entity_type   *   The entity type to join to.   * @param $left_type   *   The data type from which to join.   */  function getRelationshipHandlerClass($entity_type, $left_type) {    // Look for an entity type which is used as bundle for the given entity    // type. If there is one, allow filtering the relation by bundle by using    // our own handler.    foreach (entity_get_info() as $type => $info) {      // In case we already join from the bundle entity we do not need to filter      // by bundle entity any more, so we stay with the general handler.      if (!empty($info['bundle of']) && $info['bundle of'] == $entity_type && $type != $left_type) {        return 'entity_views_handler_relationship_by_bundle';      }    }    return 'views_handler_relationship';  }  /**   * A callback returning property options, suitable to be used as views options callback.   */  public static function optionsListCallback($type, $selector, $op = 'view') {    $wrapper = entity_metadata_wrapper($type, NULL);    $parts = explode(':', $selector);    foreach ($parts as $part) {      $wrapper = $wrapper->get($part);    }    return $wrapper->optionsList($op);  }}
 |