diff --git a/reference.module b/reference.module index a3a44c4..faa4fc5 100644 --- a/reference.module +++ b/reference.module @@ -4,6 +4,27 @@ * Defines a field type for referencing other entites. */ +/** + * Implements hook_autoload_info(). + */ +function reference_autoload_info() { + return array( + 'reference_plugin_display' => 'views/reference_plugin_display.inc', + 'reference_plugin_row_fields' => 'views/reference_plugin_row_fields.inc', + 'reference_plugin_style' => 'views/reference_plugin_style.inc', + ); +} + +/** + * Implements hook_views_api(). + */ +function reference_views_api() { + return array( + 'api' => 3, + 'path' => backdrop_get_path('module', 'reference') . '/views', + ); +} + /** * Implements hook_entity_info_alter(). * @@ -425,6 +446,13 @@ function reference_field_views_data($field) { $target_info = entity_get_info($field['settings']['entity_type']); // Get the default values of the views config data for this field. $data = field_views_field_default_views_data($field); + + $entity_type = $target_info['label']; + if ($entity_type == t('Node')) { + $entity_type = t('Content'); + } + $parameters = array('@type' => $entity_type, '!field_name' => $field['field_name']); + // Alter data for both the field_data and field_revision tables. foreach ($data as $table_name => $table_data) { // Add a relationship to the target table from the [field_name]_target_id. @@ -432,8 +460,62 @@ function reference_field_views_data($field) { 'handler' => 'views_handler_relationship', 'base' => $target_info['base table'], 'base field' => $target_info['entity keys']['id'], - 'label' => t('!type referenced by !field_name', array('!type' => $target_info['label'], '!field_name' => $field['field_name'])), + 'label' => t('@type referenced by !field_name', $parameters), + 'group' => t('Reference'), + 'title' => t('Reference to'), + 'help' => t('A bridge to the @type that is referenced in the field !field_name', $parameters), ); } return $data; } + +/** + * Implements hook_field_views_data_views_data_alter(). + * + * Views integration to provide reverse relationships on reference fields. + */ +function reference_field_views_data_views_data_alter(&$data, $field) { + foreach ($field['bundles'] as $entity_type => $bundles) { + if (isset($field['settings']['target_type'])) { + $target_entity_info = entity_get_info($field['settings']['target_type']); + if (isset($target_entity_info['base table'])) { + $entity_info = entity_get_info($entity_type); + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + $target_entity = $target_entity_info['label']; + if ($target_entity == t('Node')) { + $target_entity = t('Content'); + } + + $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type; + $replacements = array('@entity' => $entity, '@target_entity' => $target_entity, '!field_name' => $field['field_name']); + $data[$target_entity_info['base table']][$pseudo_field_name]['relationship'] = array( + 'handler' => 'views_handler_relationship_entity_reverse', + 'field_name' => $field['field_name'], + 'field table' => _field_sql_storage_tablename($field), + 'field field' => $field['field_name'] . '_target_id', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('@entity referencing @target_entity from !field_name', $replacements), + 'group' => t('Entity Reference'), + 'title' => t('Reference'), + 'help' => t('A bridge to the @entity that is referencing @target_entity via !field_name', $replacements), + 'join_extra' => array( + 0 => array( + 'field' => 'entity_type', + 'value' => $entity_type, + ), + 1 => array( + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ), + ), + ); + } + } + } +} + diff --git a/reference.views.inc b/reference.views.inc new file mode 100644 index 0000000..290144d --- /dev/null +++ b/reference.views.inc @@ -0,0 +1,87 @@ + $entity_type, '!field_name' => $field['field_name']); + + // Alter data for both the field_data and field_revision tables. + foreach ($data as $table_name => $table_data) { + // Add a relationship TO the target table from the [field_name]_target_id. + $data[$table_name][$field['field_name'] . '_target_id']['relationship'] = array( + 'handler' => 'views_handler_relationship', + 'base' => $target_info['base table'], + 'base field' => $target_info['entity keys']['id'], + 'label' => t('@type referenced by !field_name', $parameters), + 'group' => t('Reference'), + 'title' => t('Reference to'), + 'help' => t('A bridge to the @type that is referenced in the field !field_name', $parameters), + ); + } + + return $data; +} + +/** + * Implements hook_field_views_data_views_data_alter(). + * + * Views integration to provide reverse relationships on reference fields. + */ +function reference_field_views_data_views_data_alter(&$data, $field) { + foreach ($field['bundles'] as $entity_type => $bundles) { + if (array_key_exists('entity_type', $field['settings'])) { + $target_entity_info = entity_get_info($field['settings']['entity_type']); + if (isset($target_entity_info['base table'])) { + $entity_info = entity_get_info($entity_type); + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + $target_entity = $target_entity_info['label']; + if ($target_entity == t('Node')) { + $target_entity = t('Content'); + } + + $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type; + $replacements = array('@entity' => $entity, '@target_entity' => $target_entity, '!field_name' => $field['field_name']); + $data[$target_entity_info['base table']][$pseudo_field_name]['relationship'] = array( + 'handler' => 'views_handler_relationship_entity_reverse', + 'field_name' => $field['field_name'], + 'field table' => _field_sql_storage_tablename($field), + 'field field' => $field['field_name'] . '_target_id', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('@entity referencing @target_entity from !field_name', $replacements), + 'group' => t('Entity Reference'), + 'title' => t('Reference from'), + 'help' => t('A bridge to the @entity that has @target_entity in its field !field_name', $replacements), + 'join_extra' => array( + 0 => array( + 'field' => 'entity_type', + 'value' => $entity_type, + ), + 1 => array( + 'field' => 'deleted', + 'value' => 0, + 'numeric' => TRUE, + ), + ), + ); + } + } + } +} diff --git a/views/reference_plugin_display.inc b/views/reference_plugin_display.inc new file mode 100644 index 0000000..50c46fd --- /dev/null +++ b/views/reference_plugin_display.inc @@ -0,0 +1,124 @@ +view->render($this->display->id); + } + + function render() { + if (!empty($this->view->result) || !empty($this->view->style_plugin->definition['even empty'])) { + return $this->view->style_plugin->render($this->view->result); + } + return ''; + } + + function uses_exposed() { + return FALSE; + } + + function query() { + $options = $this->get_option('reference_options'); + + // Play nice with Views UI 'preview' : if the view is not executed through + // reference_SelectionHandler_Views::getReferencableEntities(), + // don't alter the query. + if (empty($options)) { + return; + } + + // Make sure the id field is included in the results, and save its alias + // so that references_plugin_style can retrieve it. + $this->id_field_alias = $id_field = $this->view->query->add_field($this->view->base_table, $this->view->base_field); + if (strpos($id_field, '.') === FALSE) { + $id_field = $this->view->base_table . '.' . $this->id_field_alias; + } + + // Restrict the autocomplete options based on what's been typed already. + if (isset($options['match'])) { + $style_options = $this->get_option('style_options'); + $value = db_like($options['match']) . '%'; + if ($options['match_operator'] != 'STARTS_WITH') { + $value = '%' . $value; + } + + // Multiple search fields are OR'd together + $conditions = db_or(); + + // Build the condition using the selected search fields + foreach ($style_options['search_fields'] as $field_alias) { + if (!empty($field_alias)) { + + // Get the table and field names for the checked field. + if (empty($this->view->field[$field_alias]->field_info)) { + $field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias]; + } + else { + $this->view->query->add_field($this->view->field[$field_alias]->options['table'], $this->view->field[$field_alias]->real_field, $this->view->field[$field_alias]->options['field'], array()); + $field = $this->view->query->fields[$this->view->field[$field_alias]->options['field']]; + } + // Add an OR condition for the field + $conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE'); + } + } + + $this->view->query->add_where(NULL, $conditions); + } + + // Add an IN condition for validation. + if (!empty($options['ids'])) { + $this->view->query->add_where(NULL, $id_field, $options['ids']); + } + + $this->view->set_items_per_page($options['limit']); + } + + /** + * Extend the default validation. + */ + function validate() { + $errors = parent::validate(); + // Verify that search fields are set up. + $style_options = $this->get_option('style_options'); + if (!isset($style_options['search_fields'])) { + $errors[] = t('Display "@display" needs a selected search fields to work properly. See the settings for the Entity Reference list format.', array('@display' => $this->display->display_title)); + } + else { + // Verify that the search fields used actually exist. + //$fields = array_keys($this->view->get_items('field')); + $fields = array_keys($this->handlers['field']); + foreach ($style_options['search_fields'] as $field_alias => $enabled) { + if ($enabled && !in_array($field_alias, $fields)) { + $errors[] = t('Display "@display" uses field %field as search field, but the field is no longer present. See the settings for the Entity Reference list format.', array('@display' => $this->display->display_title, '%field' => $field_alias)); + } + } + } + return $errors; + } +} diff --git a/views/reference_plugin_row_fields.inc b/views/reference_plugin_row_fields.inc new file mode 100644 index 0000000..c212d75 --- /dev/null +++ b/views/reference_plugin_row_fields.inc @@ -0,0 +1,35 @@ + '-'); + + return $options; + } + + /** + * Provide a form for setting options. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + // Expand the description of the 'Inline field' checkboxes. + $form['inline']['#description'] .= '
' . t("Note: In 'Entity Reference' displays, all fields will be displayed inline unless an explicit selection of inline fields is made here." ); + } + + function pre_render($row) { + // Force all fields to be inline by default. + if (empty($this->options['inline'])) { + $fields = $this->view->get_items('field', $this->display->id); + $this->options['inline'] = backdrop_map_assoc(array_keys($fields)); + } + + return parent::pre_render($row); + } +} diff --git a/views/reference_plugin_style.inc b/views/reference_plugin_style.inc new file mode 100644 index 0000000..80929be --- /dev/null +++ b/views/reference_plugin_style.inc @@ -0,0 +1,63 @@ + NULL); + + return $options; + } + + // Create the options form. + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $options = array(); + + if (isset($form['grouping'])) { + $options = $form['grouping'][0]['field']['#options']; + unset($options['']); + $form['search_fields'] = array( + '#type' => 'checkboxes', + '#title' => t('Search fields'), + '#options' => $options, + '#required' => TRUE, + '#default_value' => $this->options['search_fields'], + '#description' => t('Select the field(s) that will be searched when using the autocomplete widget.'), + '#weight' => -3, + ); + } + } + + function render() { + $options = $this->display->handler->get_option('reference_options'); + + // Play nice with Views UI 'preview' : if the view is not executed through + // reference_SelectionHandler_Views::getReferencableEntities(), just + // display the HTML. + if (empty($options)) { + return parent::render(); + } + + // Group the rows according to the grouping field, if specified. + $sets = $this->render_grouping($this->view->result, $this->options['grouping']); + // Grab the alias of the 'id' field added by reference_plugin_display. + $id_field_alias = $this->display->handler->id_field_alias; + + // @todo We don't display grouping info for now. Could be useful for select + // widget, though. + $results = array(); + foreach ($sets as $records) { + foreach ($records as $index => $values) { + $this->view->row_index = $index; + // Sanitize html, remove line breaks and extra whitespace. + $results[$values->{$id_field_alias}] = filter_xss_admin(preg_replace('/\s\s+/', ' ', str_replace("\n", '', $this->row_plugin->render($values)))); + } + } + unset($this->view->row_index); + return $results; + } +}