diff --git a/entityreference.info b/entityreference.info index ae1c333..9c45566 100644 --- a/entityreference.info +++ b/entityreference.info @@ -11,7 +11,12 @@ files[] = entityreference.migrate.inc ; Our plugins. files[] = plugins/selection/abstract.inc files[] = plugins/selection/base.class.php +files[] = plugins/selection/views.inc files[] = plugins/behavior/abstract.inc ; Tests. files[] = tests/entityreference.handlers.test + +files[] = views/entityreference_plugin_display.inc +files[] = views/entityreference_plugin_style.inc +files[] = views/entityreference_plugin_row_fields.inc diff --git a/entityreference.module b/entityreference.module index e0efa7e..1e82ea7 100644 --- a/entityreference.module +++ b/entityreference.module @@ -472,6 +472,26 @@ function entityreference_field_property_callback(&$info, $entity_type, $field, $ entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type); } +function entityreference_view_settings_validate($element, &$form_state, $form) { + // Split view name and display name from the 'view_and_display' value. + if (!empty($element['view_and_display']['#value'])) { + list($view, $display) = explode(':', $element['view_and_display']['#value']); + } + else { + $view = ''; + $display = ''; + } + + // Explode the 'args' string into an actual array. Beware, explode() turns an + // empty string into an array with one empty string. We'll need an empty array + // instead. + $args_string = trim($element['args']['#value']); + $args = ($args_string === '') ? array() : array_map('trim', explode(',', $args_string)); + + $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args); + form_set_value($element, $value, $form_state); +} + /** * Implements hook_field_widget_info(). */ diff --git a/plugins/selection/views.inc b/plugins/selection/views.inc new file mode 100644 index 0000000..fa058a6 --- /dev/null +++ b/plugins/selection/views.inc @@ -0,0 +1,106 @@ + t('Views: Filter by an entity reference view'), + 'class' => 'EntityReferenceHandler_views', + 'weight' => 0, + ); +} + +/** + * Entity handler for Views. + */ +class EntityReferenceHandler_views extends EntityReference_SelectionHandler_Generic { + + /** + * Implements EntityReferenceHandler::getInstance(). + */ + public static function getInstance($field, $instance) { + return new EntityReferenceHandler_views($field, $instance); + } + + /** + * Implements EntityReferenceHandler::settingsForm(). + */ + public static function settingsForm($field, $instance) { + $view_settings = empty($field['settings']['handler_settings']['view']) ? '' : $field['settings']['handler_settings']['view']; + $displays = views_get_applicable_views('entityreference display'); + // Filter views that list the entity type we want, and group the separate + // displays by view. + $entity_info = entity_get_info($field['settings']['target_type']); + $options = array(); + foreach ($displays as $data) { + list($view, $display_id) = $data; + if ($view->base_table == $entity_info['base table']) { + $options[$view->name . ':' . $display_id] = $view->name .' - ' . $view->display[$display_id]->display_title; + } + } + + if ($options) { + // The value of the 'view_and_display' select below will need to be split + // into 'view_name' and 'view_display' in the final submitted values, so + // we massage the data at validate time on the wrapping element (not + // ideal). + $form['view']['#element_validate'] = array('entityreference_view_settings_validate'); + + $options = array('' => '<' . t('none') . '>') + $options; + $default = empty($view_settings['view_name']) ? '' : $view_settings['view_name'] . ':' .$view_settings['display_name']; + $form['view']['view_and_display'] = array( + '#type' => 'select', + '#title' => t('View used to select the nodes'), + '#options' => $options, + '#default_value' => $default, + '#description' => '

' . t('Choose the view and display that select the entities that can be referenced.
Only views with a display of type "Entity Reference" are eligible.') . '

', + ); + + $default = empty($view_settings['args']) ? '' : implode(', ', $view_settings['args']); + $form['view']['args'] = array( + '#type' => 'textfield', + '#title' => t('View arguments'), + '#default_value' => $default, + '#required' => FALSE, + '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + ); + } + else { + $form['view']['no_view_help'] = array( + '#markup' => '

' . t('No eligible view was found.') .'

', + ); + } + return $form; + } + + /** + * Implements EntityReferenceHandler::getReferencableEntities(). + */ + public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + $view_name = $this->field['settings']['handler_settings']['view']['view_name']; + $display_name = $this->field['settings']['handler_settings']['view']['display_name']; + $args = $this->field['settings']['handler_settings']['view']['args']; + $entity_type = $this->field['settings']['target_type']; + + // Check that the view is valid and the display still exists. + $view = views_get_view($view_name); + if (!$view || !isset($view->display[$display_name])) { + return FALSE; + } + $view->set_display($display_name); + + // Make sure the query is not cached + $view->is_cacheable = FALSE; + + // Pass options to the display handler to make them available later. + $entityreference_options = array( + 'match' => $match, + 'match_operator' => $match_operator, + 'limit' => $limit, + ); + $view->display_handler->set_option('entityreference_options', $entityreference_options); + + // Get the results. + $options = $view->execute_display($display_name, $args); + return $options; + } + +} diff --git a/views/entityreference.views.inc b/views/entityreference.views.inc index 76cbc2b..2df3085 100644 --- a/views/entityreference.views.inc +++ b/views/entityreference.views.inc @@ -71,3 +71,53 @@ function entityreference_field_views_data_views_data_alter(&$data, $field) { } } } + +/** + * Implements hook_views_plugins(). + */ +function entityreference_views_plugins() { + $plugins = array( + 'display' => array( + 'entityreference' => array( + 'title' => t('Entity Reference'), + 'admin' => t('Entity Reference'), + 'help' => 'Selects referenceable entities for an entity reference field', + 'handler' => 'entityreference_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 'Entity Reference' display. + 'entityreference display' => TRUE, + ), + ), + 'style' => array( + 'entityreference_style' => array( + 'title' => t('Entity Reference list'), + 'help' => 'Returns results as a PHP array of labels and rendered rows.', + 'handler' => 'entityreference_plugin_style', + 'theme' => 'views_view_unformatted', + 'uses row plugin' => TRUE, + 'uses fields' => TRUE, + 'uses options' => TRUE, + 'type' => 'entityreference', + 'even empty' => TRUE, + ), + ), + 'row' => array( + 'entityreference_fields' => array( + 'title' => t('Inline fields'), + 'help' => t('Displays the fields with an optional template.'), + 'handler' => 'entityreference_plugin_row_fields', + 'theme' => 'views_view_fields', + 'theme path' => drupal_get_path('module', 'views') . '/theme', + 'theme file' => 'theme.inc', + 'uses fields' => TRUE, + 'uses options' => TRUE, + 'type' => 'entityreference', + ), + ), + ); + return $plugins; +} diff --git a/views/entityreference_plugin_display.inc b/views/entityreference_plugin_display.inc new file mode 100644 index 0000000..7a522bb --- /dev/null +++ b/views/entityreference_plugin_display.inc @@ -0,0 +1,75 @@ + 'title'); + $options['defaults']['default']['style_plugin'] = FALSE; + $options['defaults']['default']['style_options'] = FALSE; + $options['row_plugin']['default'] = 'entityreference_fields'; + $options['defaults']['default']['row_plugin'] = FALSE; + $options['defaults']['default']['row_options'] = FALSE; + + // Set the display title to an empty string (not used in this display type). + $options['title']['default'] = ''; + $options['defaults']['default']['title'] = FALSE; + + return $options; + } + + function get_style_type() { + return 'entityreference'; + } + + function execute() { + return $this->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('entityreference_options'); + + // Play nice with Views UI 'preview' : if the view is not executed through + // EntityReferenceHandler_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 = $this->view->query->add_field($this->view->base_table, $this->view->base_field); + + // 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; + } + $table_alias = $this->view->query->ensure_table($this->view->base_table); + $this->view->query->add_where(NULL, $table_alias . '.' . $style_options['title_field'], $value, 'LIKE'); + } + $this->view->set_items_per_page($options['limit']); + } +} diff --git a/views/entityreference_plugin_row_fields.inc b/views/entityreference_plugin_row_fields.inc new file mode 100644 index 0000000..400603f --- /dev/null +++ b/views/entityreference_plugin_row_fields.inc @@ -0,0 +1,36 @@ + '-'); + + 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'] = drupal_map_assoc(array_keys($fields)); + } + + return parent::pre_render($row); + } +} diff --git a/views/entityreference_plugin_style.inc b/views/entityreference_plugin_style.inc new file mode 100644 index 0000000..0d2cdfe --- /dev/null +++ b/views/entityreference_plugin_style.inc @@ -0,0 +1,66 @@ + 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['title_field'] = array( + '#type' => 'select', + '#title' => t('Title field'), + '#options' => $options, + '#required' => TRUE, + '#default_value' => $this->options['title_field'], + '#description' => t('Select the field that will be used as the label.'), + '#weight' => -3, + ); + } + } + + function render() { + $options = $this->display->handler->get_option('entityreference_options'); + + // Play nice with Views UI 'preview' : if the view is not executed through + // EntityReferenceHandler_views::getReferencableEntities(), just display + // the HTML. + if (empty($options)) { + return parent::render(); + } + + $title_field = isset($this->options['title_field']) ? $this->options['title_field'] : 'title'; + // 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 entityreference_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(); + $this->view->row_index = 0; + foreach ($sets as $title => $records) { + foreach ($records as $label => $values) { + $results[$values->{$id_field_alias}] = $this->view->field[$title_field]->get_value($values); + $this->view->row_index++; + } + } + unset($this->view->row_index); + return $results; + } +}