diff --git node_reference/node_reference.module node_reference/node_reference.module index 11db51b..c4e8d51 100644 --- node_reference/node_reference.module +++ 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,42 @@ function node_reference_field_settings_form($field, $instance, $has_data) { : array(), '#options' => array_map('check_plain', node_type_get_names()), ); + + if (module_exists('views')) { + $form['views'] = array( + '#type' => 'fieldset', + '#title' => t('Views - Nodes that can be referenced'), + '#collapsible' => TRUE, + '#collapsed' => !isset($settings['views']['view']) || $settings['views']['view'] == '', + ); + + $views_options = references_get_views('node'); + if ($views_options) { + $views_options = array('' => '<' . t('none') . '>') + $views_options; + $form['views']['view'] = array( + '#type' => 'select', + '#title' => t('View used to select the nodes'), + '#options' => $views_options, + '#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(''), + ); + $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.'), + ); + } + 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 +603,7 @@ function _node_reference_options($field) { $options = array(); foreach ($references as $key => $value) { - $options[$key] = $value['title']; + $options[$key] = $value['rendered']; } return $options; @@ -612,7 +654,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 +671,89 @@ 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) { + list($view_name, $display_name) = explode(':', $field['settings']['views']['view']); + + // Check that the view is valid and the display still exists. + $view = views_get_view($view_name); + if (!$view || $view->base_table != 'node' || !isset($view->display[$display_name])) { + return FALSE; + } + + // Collect arguments if any. + $view_args = array(); + if (!empty($field['settings']['views']['view_args'])) { + $view_args = array_map('trim', explode(',', $field['settings']['views']['view_args'])); + } + + // Temporary backwards compatibility for fields migrated from CCK D6: accept + // 'default' display. + // @todo Test + // @todo Add the corresponding migration code in hook_content_migrate_instance_alter(). + if ($display_name == 'default') { + $display_name = $view->add_display('references'); + $view->display[$display_name]->set_option('style_plugin', 'references_plugin_style'); + } + + $view->set_display($display_name); + + // @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. + + // @todo : isn't there a way to force this ? + $view->display_handler->set_option('row_plugin', 'fields'); + + // Additional options to let references_plugin_display::query() + // narrow the results. + $options = array( + 'ids' => $ids, + 'title_field' => 'title', + 'string' => $string, + 'match' => $match, + ); + $view->display_handler->set_option('references_options', $options); + + // We do need the title field, so add it if not present (unlikely, but...) + $fields = $view->get_items('field', $display_name); + if (!isset($fields['title'])) { + // @todo : exclude from display ? (at least, no 'Title' label) + $view->add_item($display_name, 'field', 'node', 'title'); + } + + // Limit result set size. + $limit = isset($limit) ? $limit : 0; + // @todo check this ('type' => 'some' ??) + $view->display_handler->set_option('pager', array('type' => 'some', 'options' => array('items_per_page' => $limit))); + + // If not set, make all fields inline and define a separator. + // @todo doesn't work ? + $row_options = $view->display_handler->get_option('row_options'); + if (empty($row_options['inline'])) { + $row_options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display_name))); + } + if (empty($row_options['separator'])) { + $row_options['separator'] = '-'; + } + $view->display_handler->set_option('row_options', $row_options); + + // Make sure the query is not cached + $view->is_cacheable = FALSE; + + // Get the results. + $results = $view->execute_display($display_name, $view_args); + + // @todo : remove links ? + + return $results; +} + +/** * Helper function for _node_reference_potential_references(). * * List of referenceable nodes defined by content types. diff --git references.info references.info index c5a0dfe..b6cc1c5 100644 --- references.info +++ 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 references.module references.module index 550b9a6..4e75bdf 100644 --- references.module +++ references.module @@ -10,6 +10,78 @@ */ function references_views_api() { return array( - 'api' => '3.0', + 'api' => 3, + '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( + 'display' => array( + 'references' => array( + // @todo Why doesn't this display appear in the main Views list ? + 'title' => 'References', + 'help' => 'Selects referenceable entities for a reference field (node_reference, user_reference...)', + 'handler' => 'references_plugin_display', + 'uses hook menu' => FALSE, + 'use ajax' => FALSE, + 'use pager' => FALSE, + 'accept attachments' => FALSE, + // Custom property, used with views_get_applicable_views() to retrieve + // all views with a 'References' display. + 'references display' => TRUE, + // @todo : is there a way to prevent having a 'view title' ? + ), + ), + 'style' => array( + 'references_style' => array( + 'title' => 'References list', + 'help' => '@todo Returns the view as a PHP array of names + rendered rows.', + 'handler' => 'references_plugin_style', + 'theme' => 'views_view_unformatted', + 'uses row plugin' => TRUE, + 'uses fields' => TRUE, +// @todo ? +// 'uses row class' => TRUE, +// 'uses grouping' => TRUE, + // @todo : 'Feed' displays get automatically created with the correct + // overriden style. + 'type' => 'references', + 'even empty' => TRUE, + ), + ), + ); + return $plugins; +} + +/** + * Retrieves the list of views + * @param $entity_type + * The entity type. + * + * @return + * @todo return format is not great + */ +function references_get_views($entity_type) { + // Filter views that contain a 'references' display. + $views = views_get_applicable_views('references display'); + + // Filter views that list the entity type we want. + $entity_info = entity_get_info($entity_type); + foreach ($views as $data) { + list($view, $display_id) = $data; + if ($view->base_table == $entity_info['base table']) { + $id = $view->name . ':' . $display_id; + $display_title = $view->name ." ". strtolower($view->display[$display_id]->display_title) . ' ['. $id . ']'; + $result[$id] = $display_title; + } + } + + return $result; +} diff --git user_reference/user_reference.module user_reference/user_reference.module index fe442c7..7175cef 100644 --- user_reference/user_reference.module +++ 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,42 @@ function user_reference_field_settings_form($field, $instance, $has_data) { : array(1), '#options' => array(1 => t('Active'), 0 => t('Blocked')), ); + + if (module_exists('views')) { + $form['views'] = array( + '#type' => 'fieldset', + '#title' => t('Views - Users that can be referenced'), + '#collapsible' => TRUE, + '#collapsed' => !isset($settings['views']['view']) || $settings['views']['view'] == '', + ); + + $views_options = references_get_views('users'); + if ($views_options) { + $views_options = array('' => '<' . t('none') . '>') + $views_options; + $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(''), + ); + $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.'), + ); + } + 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 +440,7 @@ function _user_reference_options($field) { $options = array(); foreach ($references as $key => $value) { - $options[$key] = $value['title']; + $options[$key] = $value['rendered']; } return $options; @@ -443,7 +486,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 +505,88 @@ 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) { + list($view_name, $display_name) = explode(':', $field['settings']['views']['view']); + + // Check that the view is valid and the display still exists. + $view = views_get_view($view_name); + if (!$view || $view->base_table != 'node' || !isset($view->display[$display_name])) { + return FALSE; + } + + // Collect arguments if any. + $view_args = array(); + if (!empty($field['settings']['views']['view_args'])) { + $view_args = array_map('trim', explode(',', $field['settings']['views']['view_args'])); + } + + // Temporary backwards compatibility for fields migrated from CCK D6: accept + // 'default' display. + // @todo Test + // @todo Add the corresponding migration code in hook_content_migrate_instance_alter(). + if ($display_name == 'default') { + $display_name = $view->add_display('references'); + $view->display[$display_name]->set_option('style_plugin', 'references_plugin_style'); + } + + $view->set_display($display_name); + // @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. + + // @todo : isn't there a way to force this ? + $view->display_handler->set_option('row_plugin', 'fields'); + + // Additional options to let references_plugin_display::query() + // narrow the results. + $options = array( + 'ids' => $ids, + 'title_field' => 'name', + 'string' => $string, + 'match' => $match, + ); + $view->display_handler->set_option('references_options', $options); + + // We do need the name field, so add it if not present (unlikely, but...) + $fields = $view->get_items('field', $display_name); + if (!isset($fields['name'])) { + // @todo : exclude from display ? (at least, no 'Title' label) + $view->add_item($display_name, 'field', 'users', 'name'); + } + + // Limit result set size. + $limit = isset($limit) ? $limit : 0; + // @todo check this ('type' => 'some' ??) + $view->display_handler->set_option('pager', array('type' => 'some', 'options' => array('items_per_page' => $limit))); + + // If not set, make all fields inline and define a separator. + // @todo doesn't work ? + $row_options = $view->display_handler->get_option('row_options'); + if (empty($row_options['inline'])) { + $row_options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display_name))); + } + if (empty($row_options['separator'])) { + $row_options['separator'] = '-'; + } + $view->display_handler->set_option('row_options', $row_options); + + // Make sure the query is not cached + $view->is_cacheable = FALSE; + + // Get the results. + $results = $view->execute_display($display_name, $view_args); + + // @todo : remove links ? + + return $results; +} + +/** + * 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 views/references_plugin_display.inc views/references_plugin_display.inc new file mode 100644 index 0000000..c0e6940 --- /dev/null +++ views/references_plugin_display.inc @@ -0,0 +1,63 @@ +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() { + // 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 = $this->view->query->add_field($this->view->base_table, $this->view->base_field); + + if ($options = $this->get_option('references_options')) { + // Restrict on the incoming string, or incoming ids. + if ($options['string'] !== '') { + switch ($options['match']) { + case 'equals': + $operator = '='; + $value = $options['string']; + break; + + case 'starts_with': + $operator = 'LIKE'; + $value = db_like($options['string']) . '%'; + break; + + case 'contains': + default: + $operator = 'LIKE'; + $value = '%' . db_like($options['string']) . '%'; + break; + } + + $table_alias = $this->view->query->ensure_table($this->view->base_table); + $this->view->query->add_where(NULL, $table_alias . '.' . $options['title_field'], $value, $operator); + } + elseif ($options['ids']) { + $table_alias = $this->view->query->ensure_table($this->view->base_table); + $this->view->query->add_where(NULL, $table_alias . '.' . $this->view->base_field, $options['ids'], 'IN'); + } + } + } +} diff --git views/references_plugin_style.inc views/references_plugin_style.inc new file mode 100644 index 0000000..fa78af2 --- /dev/null +++ views/references_plugin_style.inc @@ -0,0 +1,42 @@ +display->handler->get_option('references_options')) { + // Group the rows according to the grouping field, if specified. + $sets = $this->render_grouping($this->view->result, $this->options['grouping']); + + $title_field = $references_options['title_field']; + $title_field_alias = $this->view->field[$title_field]->field_alias; + $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(); + $this->view->row_index = 0; + foreach ($sets as $title => $records) { + foreach ($records as $label => $row) { + $results[$row->{$id_field_alias}] = array( + // @todo : should this use $this->get_value() ? + 'title' => $row->{$title_field_alias}, + 'rendered' => $this->row_plugin->render($row), + ); + $this->view->row_index++; + } + } + unset($this->view->row_index); + + return $results; + } + else { + // No 'references_options' : we are not being rendered through + // _*reference_potential_references_views() ('preview', for instance). + // Just display the HTML. + return parent::render(); + } + } +}