From f64016ec3be0ff86defa3fe8b29c66070550d153 Mon Sep 17 00:00:00 2001 From: Tim Plunkett Date: Mon, 28 Mar 2011 17:33:36 -0400 Subject: [PATCH] Issue #945004 by jyee, matt2000, tim.plunkett: Add views integration for reference selection. --- node_reference/node_reference.module | 166 +++++++++++++++++++++++++++++++++- references.info | 2 + references.module | 37 ++++++++ user_reference/user_reference.module | 168 +++++++++++++++++++++++++++++++++- views/references_plugin_display.inc | 48 ++++++++++ views/references_plugin_style.inc | 33 +++++++ 6 files changed, 448 insertions(+), 6 deletions(-) create mode 100644 views/references_plugin_display.inc create mode 100644 views/references_plugin_style.inc diff --git a/node_reference/node_reference.module b/node_reference/node_reference.module index 0d28e0a..3c79d8d 100644 --- a/node_reference/node_reference.module +++ b/node_reference/node_reference.module @@ -29,7 +29,13 @@ function node_reference_field_info() { 'node_reference' => array( 'label' => t('Node reference'), 'description' => t('This field stores the ID of a related node as an integer value.'), - 'settings' => array('referenceable_types' => array()), + 'settings' => array( + 'referenceable_types' => array(), + 'views' => array( + 'view' => '', + 'view_args' => '', + ), + ), // It probably make more sense to have the referenceable types be per-field than per-instance // 'instance settings' => array('referenceable_types' => array()), 'default_widget' => 'options_select', // node_reference_autocomplete', @@ -79,6 +85,68 @@ function node_reference_field_settings_form($field, $instance, $has_data) { : array(), '#options' => array_map('check_plain', node_type_get_names()), ); + + if (module_exists('views')) { + $views = array('' => '<' . t('none') . '>'); + $all_views = views_get_all_views(); + + foreach ($all_views as $view_name => $view) { + // Only 'node' views that have fields will work for our purpose. + if ($view->base_table == 'node') { + foreach ((array)$view->display as $display_name => $display) { + $id = $view_name . ':' . $display_name; + // Get display title. + $view->set_display($display_name); + $display_title = $view->get_title(); + if (!$display_title) { + // No title, we have to construct a title. + $display_title = ucfirst($view_name) ." ". strtolower($view->display[$display_name]->display_title); + } + // Append $id to the title for disambiguation in lists. + $display_title .= ' ['. $id . ']'; + + if ($view->type == 'Default') { + $views[t('Default views')][$id] = $display_title; + } + else { + $views[t('Existing views')][$id] = $display_title; + } + } + } + } + + $form['views'] = array( + '#type' => 'fieldset', + '#title' => t('Views - Nodes that can be referenced'), + '#collapsible' => TRUE, + '#collapsed' => !isset($settings['views']['view']) || $settings['views']['view'] == '', + ); + if (count($views) > 1) { + $form['views']['view'] = array( + '#type' => 'select', + '#title' => t('View used to select the nodes'), + '#options' => $views, + '#default_value' => isset($settings['views']['view']) ? $settings['views']['view'] : '', + '#description' => t('

Choose the "Views module" view that selects the nodes that can be referenced.
Note:

') . + t(''), + '#disabled' => $has_data, + ); + $form['views']['view_args'] = array( + '#type' => 'textfield', + '#title' => t('View arguments'), + '#default_value' => isset($settings['views']['view_args']) ? $settings['views']['view_args'] : '', + '#required' => FALSE, + '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + '#disabled' => $has_data, + ); + } + else { + $form['views']['no_view_help'] = array( + '#value' => t('

The list of nodes that can be referenced can be based on a "Views module" view but no appropriate views were found.
Note:

') . t(''), + ); + } + } + return $form; } @@ -561,7 +629,7 @@ function _node_reference_options($field) { $options = array(); foreach ($references as $key => $value) { - $options[$key] = $value['title']; + $options[$key] = $value['rendered']; } return $options; @@ -612,8 +680,14 @@ function _node_reference_potential_references($field, $string = '', $match = 'co . ($string !== '' ? $string : implode('-', $ids)) . ':' . $limit; if (!isset($results[$cid])) { - $references = _node_reference_potential_references_standard($field, $string, $match, $ids, $limit); + $references = FALSE; + if (module_exists('views') && !empty($field['settings']['views']['view'])) { + $references = _node_reference_potential_references_views($field, $string, $match, $ids, $limit); + } + if ($references === FALSE) { + $references = _node_reference_potential_references_standard($field, $string, $match, $ids, $limit); + } // Store the results. $results[$cid] = !empty($references) ? $references : array(); } @@ -622,6 +696,92 @@ function _node_reference_potential_references($field, $string = '', $match = 'co } /** + * Helper function for _nodereference_potential_references(). + * + * Case of Views-defined referenceable nodes. + */ +function _node_reference_potential_references_views($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + $view_id = $field['settings']['views']['view']; + list($view_name, $view_display) = explode(':', $view_id); + + if ($view = views_get_view($view_name)) { + + // If the user supplied display isn't a references display, derive from 'default'. + $display = ($view->display[$view_display]->display_plugin == 'references_plugin_display') ? $view_display : $view->add_display('references_plugin_display'); + $view->set_display($display); + + // Get the options from the user supplied display. + if ($view_display != 'default' && isset($view->display[$view_display]->display_options)) { + $view->display[$display]->display_options = $view->display[$view_display]->display_options; + } + + // TODO from merlinofchaos on IRC : arguments using summary view can defeat the style setting. + // We might also need to check if there's an argument, and set *its* style_plugin as well. + $view->display_handler->set_option('style_plugin', 'references_plugin_style'); + $view->display_handler->set_option('row_plugin', 'fields'); + // Used in node_reference_style::render(), to get + // the 'field' to be used as title. + $view->display_handler->set_option('title_field', 'title'); + + // Additional options to let references_plugin_display::query() + // narrow the results. + $options = array( + 'table' => 'node', + 'field_string' => 'title', + 'string' => $string, + 'match' => $match, + 'field_id' => 'nid', + 'ids' => $ids, + ); + $view->display_handler->set_option('options', $options); + + // TODO : for consistency, a fair amount of what's below + // should be moved to node_reference_display + + // Limit result set size. + $limit = isset($limit) ? $limit : 0; + $view->display_handler->set_option('pager', array('type' => 'some', 'options' => array('items_per_page' => $limit))); + + // Get arguments for the view. + if (!empty($field['settings']['views']['view_args'])) { + // TODO: Support Tokens using token.module ? + $view_args = array_map('trim', explode(',', $field['settings']['views']['view_args'])); + } + else { + $view_args = array(); + } + + // We do need title field, so add it if not present (unlikely, but...) + $fields = $view->get_items('field', $display); + + if (!isset($fields['title'])) { + $view->add_item($display, 'field', 'node', 'title'); + } + + // If not set, make all fields inline and define a separator. + $options = $view->display_handler->get_option('row_options'); + if (empty($options['inline'])) { + $options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display))); + } + if (empty($options['separator'])) { + $options['separator'] = '-'; + } + $view->display_handler->set_option('row_options', $options); + + // Make sure the query is not cached + $view->is_cacheable = FALSE; + + // Get the results. + $result = $view->execute_display($display, $view_args); + } + else { + $result = FALSE; + } + + return $result; +} + +/** * Helper function for _node_reference_potential_references(). * * List of referenceable nodes defined by content types. diff --git a/references.info b/references.info index c5a0dfe..b6cc1c5 100644 --- a/references.info +++ b/references.info @@ -6,3 +6,5 @@ dependencies[] = field dependencies[] = options files[] = views/references_handler_relationship.inc files[] = views/references_handler_argument.inc +files[] = views/references_plugin_display.inc +files[] = views/references_plugin_style.inc diff --git a/references.module b/references.module index 550b9a6..8888c94 100644 --- a/references.module +++ b/references.module @@ -11,5 +11,42 @@ function references_views_api() { return array( 'api' => '3.0', + 'path' => drupal_get_path('module', 'references') . '/views', ); } + +/** + * Implements hook_views_plugins(). + * + * Defines some plugins used by the Views modes for + * user_reference. + */ +function references_views_plugins() { + $plugins = array( + 'module' => 'user_reference', // This just tells our themes are elsewhere. + 'display' => array( + 'references_plugin_display' => array( + 'title' => 'References', + 'help' => 'Destination-agnostic display. Mostly useful for programmatic views.', + 'handler' => 'references_plugin_display', + 'uses hook menu' => FALSE, + 'use ajax' => FALSE, + 'use pager' => FALSE, + 'accept attachments' => FALSE, + ), + ), + 'style' => array( + 'references_plugin_style' => array( + 'title' => 'Results array (with title)', + 'help' => 'Returns the view as a PHP array of names + rendered rows.', + 'handler' => 'references_plugin_style', + 'no ui' => TRUE, // Programmatic use only. + 'uses row plugin' => TRUE, + 'uses fields' => TRUE, + 'type' => 'normal', + 'even empty' => TRUE, + ), + ), + ); + return $plugins; +} \ No newline at end of file diff --git a/user_reference/user_reference.module b/user_reference/user_reference.module index 610cea7..8eb1a1f 100644 --- a/user_reference/user_reference.module +++ b/user_reference/user_reference.module @@ -27,7 +27,14 @@ function user_reference_field_info() { 'user_reference' => array( 'label' => t('User reference'), 'description' => t('This field stores the ID of a related user as an integer value.'), - 'settings' => array('referenceable_roles' => array(), 'referenceable_status' => array()), + 'settings' => array( + 'referenceable_roles' => array(), + 'referenceable_status' => array(), + 'views' => array( + 'view' => '', + 'view_args' => '', + ), + ), 'default_widget' => 'user_reference_autocomplete', 'default_formatter' => 'user_reference_default', // Support hook_entity_property_info() from contrib "Entity API". @@ -82,6 +89,70 @@ function user_reference_field_settings_form($field, $instance, $has_data) { : array(1), '#options' => array(1 => t('Active'), 0 => t('Blocked')), ); + + if (module_exists('views')) { + $views = array('' => '<' . t('none') . '>'); + $all_views = views_get_all_views(); + + foreach ($all_views as $view_name => $view) { + // Only 'node' views that have fields will work for our purpose. + if ($view->base_table == 'users') { + foreach ((array)$view->display as $display_name => $display) { + $id = $view_name . ':' . $display_name; + + // Get display title. + $view->set_display($display_name); + $display_title = $view->get_title(); + if (!$display_title) { + // No title, we have to construct a title. + $display_title = ucfirst($view_name) ." ". strtolower($view->display[$display_name]->display_title); + } + // Append $id to the title for disambiguation in lists. + $display_title .= ' ['. $id . ']'; + + if ($view->type == 'Default') { + $views[t('Default views')][$id] = $display_title; + } + else { + $views[t('Existing views')][$id] = $display_title; + } + } + } + } + + $form['views'] = array( + '#type' => 'fieldset', + '#title' => t('Views - Users that can be referenced'), + '#collapsible' => TRUE, + '#collapsed' => !isset($settings['views']['view']) || $settings['views']['view'] == '', + ); + if (count($views) > 1) { + $form['views']['view'] = array( + '#type' => 'select', + '#title' => t('View used to select the users'), + '#options' => $views, + '#default_value' => isset($settings['views']['view']) ? $settings['views']['view'] : '', + '#description' => t('

Choose the "Views module" view that selects the users that can be referenced.
Note:

') . + t(''), + '#disabled' => $has_data, + ); + $form['views']['view_args'] = array( + '#type' => 'textfield', + '#title' => t('View arguments'), + '#default_value' => isset($settings['views']['view_args']) ? $settings['views']['view_args'] : '', + '#required' => FALSE, + '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + '#disabled' => $has_data, + ); + } + else { + $form['views']['no_view_help'] = array( + '#value' => t('

The list of user that can be referenced can be based on a "Views module" view but no appropriate views were found.
Note:

') . + t(''), + ); + } + } + return $form; } @@ -397,7 +468,7 @@ function _user_reference_options($field) { $options = array(); foreach ($references as $key => $value) { - $options[$key] = $value['title']; + $options[$key] = $value['rendered']; } return $options; @@ -443,8 +514,14 @@ function _user_reference_potential_references($field, $string = '', $match = 'co . ($string !== '' ? $string : implode('-', $ids)) . ':' . $limit; if (!isset($results[$cid])) { - $references = _user_reference_potential_references_standard($field, $string, $match, $ids, $limit); + $references = FALSE; + if (module_exists('views') && !empty($field['settings']['views']['view'])) { + $references = _user_reference_potential_references_views($field, $string, $match, $ids, $limit); + } + if ($references === FALSE) { + $references = _user_reference_potential_references_standard($field, $string, $match, $ids, $limit); + } // Store the results. $results[$cid] = !empty($references) ? $references : array(); } @@ -455,6 +532,91 @@ function _user_reference_potential_references($field, $string = '', $match = 'co /** * Helper function for _user_reference_potential_references(). * + * Case of Views-defined referenceable users. + */ +function _user_reference_potential_references_views($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { + $view_id = $field['settings']['views']['view']; + list($view_name, $view_display) = explode(':', $view_id); + + if ($view = views_get_view($view_name)) { + + // If the user supplied display isn't a references display, derive from 'default'. + $display = ($view->display[$view_display]->display_plugin == 'references_plugin_display') ? $view_display : $view->add_display('references_plugin_display'); + $view->set_display($display); + + // Get the options from the user supplied display. + if ($view_display != 'default' && isset($view->display[$view_display]->display_options)) { + $view->display[$display]->display_options = $view->display[$view_display]->display_options; + } + + // TODO from merlinofchaos on IRC : arguments using summary view can defeat the style setting. + // We might also need to check if there's an argument, and set *its* style_plugin as well. + $view->display_handler->set_option('style_plugin', 'references_plugin_style'); + $view->display_handler->set_option('row_plugin', 'fields'); + // Used in user_reference_views_plugin_style::render(), to get + // the 'field' to be used as title. + $view->display_handler->set_option('title_field', 'name'); + + // Additional options to let user_reference_views_plugin_display::query() + // narrow the results. + $options = array( + 'table' => 'users', + 'field_string' => 'name', + 'string' => $string, + 'match' => $match, + 'field_id' => 'uid', + 'ids' => $ids, + ); + $view->display_handler->set_option('options', $options); + + // TODO : for consistency, a fair amount of what's below + // should be moved to user_reference_views_plugin_display + + // Limit result set size. + $limit = isset($limit) ? $limit : 0; + $view->display_handler->set_option('items_per_page', $limit); + + // Get arguments for the view. + if (!empty($field['settings']['views']['view_args'])) { + // TODO: Support Tokens using token.module ? + $view_args = array_map('trim', explode(',', $field['settings']['views']['view_args'])); + } + else { + $view_args = array(); + } + + // We do need name field, so add it if not present (unlikely, but...) + $fields = $view->get_items('field', $display); + if (!isset($fields['name'])) { + $view->add_item($display, 'field', 'users', 'name'); + } + + // If not set, make all fields inline and define a separator. + $options = $view->display_handler->get_option('row_options'); + if (empty($options['inline'])) { + $options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display))); + } + if (empty($options['separator'])) { + $options['separator'] = '-'; + } + $view->display_handler->set_option('row_options', $options); + + // Make sure the query is not cached + $view->is_cacheable = FALSE; + + // Get the results. + $result = $view->execute_display($display, $view_args); + } + else { + $result = FALSE; + } + + return $result; +} + +/** ++ * Helper function for _user_reference_potential_references(). ++ * * List of referenceable users defined by user role and status. */ function _user_reference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { diff --git a/views/references_plugin_display.inc b/views/references_plugin_display.inc new file mode 100644 index 0000000..baeb7c3 --- /dev/null +++ b/views/references_plugin_display.inc @@ -0,0 +1,48 @@ +view->render($this->display->id); + } + + function render() { + return !empty($this->view->result) || !empty($this->view->style_plugin->definition['even empty']) ? $this->view->style_plugin->render($this->view->result) : ''; + } + + function uses_exposed() { + return FALSE; + } + + function query() { + $options = $this->get_option('options'); + + if ($options['string'] !== '') { + $like = $GLOBALS["db_type"] == 'pgsql' ? "ILIKE" : "LIKE"; + $match_clauses = array( + 'contains' => array( + 'operator' => $like, + 'value' => '%' . $options['string'] . '%', + ), + 'equals' => array( + 'operator' => '=', + 'value' => $options['string'], + ), + 'starts_with' => array( + 'operator' => $like, + 'value' => $options['string'] . '%', + ), + ); + $clause = isset($match_clauses[$options['match']]) ? $match_clauses[$options['match']] : $match_clauses['contains']; + $alias = $this->view->query->ensure_table($options['table']); + $this->view->query->add_where(NULL, "$alias.$options[field_string]", $clause['value'], $clause['operator']); + } + elseif ($options['ids']) { + $alias = $this->view->query->ensure_table($options['table']); + $this->view->query->add_where(NULL, "$alias.$options[field_id]", $options['ids'], 'IN'); + } + } +} diff --git a/views/references_plugin_style.inc b/views/references_plugin_style.inc new file mode 100644 index 0000000..9b3b15f --- /dev/null +++ b/views/references_plugin_style.inc @@ -0,0 +1,33 @@ +render_grouping($this->view->result, $this->options['grouping']); + + $base_field = $this->view->base_field; + $title_field = $this->display->display_options['title_field']; + $title_field_alias = $this->view->field[$title_field]->field_alias; + + // TODO : We don't display grouping info for now. + // Could be useful for select widget, though. + $this->view->row_index = 0; + foreach ($sets as $title => $records) { + foreach ($records as $label => $row) { + $results[$row->{$base_field}] = array( + 'title' => $row->{$title_field_alias}, + 'rendered' => $this->row_plugin->render($row), + ); + $this->view->row_index++; + } + } + unset($this->view->row_index); + return $results; + } +} -- 1.7.3.2