diff --git a/entityreference.info b/entityreference.info index a231b23..561d74a 100644 --- a/entityreference.info +++ b/entityreference.info @@ -10,7 +10,12 @@ files[] = entityreference.migrate.inc ; Our plugins interfaces and abstract implementations. files[] = plugins/selection/abstract.inc +files[] = plugins/selection/views.inc files[] = plugins/behavior/abstract.inc +files[] = views/entityreference_plugin_display.inc +files[] = views/entityreference_plugin_style.inc +files[] = views/entityreference_plugin_row_fields.inc + ; Tests. files[] = tests/entityreference.handlers.test diff --git a/entityreference.module b/entityreference.module index 18b329e..8d1dbed 100644 --- a/entityreference.module +++ b/entityreference.module @@ -783,11 +783,12 @@ function entityreference_autocomplete_callback($type, $field_name, $entity_type, // Loop through the products and convert them into autocomplete output. foreach ($entity_labels as $entity_id => $label) { $key = "$label ($entity_id)"; + $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key))))); // Names containing commas or quotes must be wrapped in quotes. if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) { $key = '"' . str_replace('"', '""', $key) . '"'; } - $matches[$prefix . $key] = '
' . 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.') . '
' . t('No eligible views were found. Create a view with an Entity Reference display, or add such a display to an existing view.', array( + '@create' => url('admin/structure/views/add'), + '@existing' => url('admin/structure/views'), + )) . '
', + ); + } + return $form; + } + + protected function initializeView($match = NULL, $match_operator = 'CONTAINS', $limit = 0, $ids = NULL) { + $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. + $this->view = views_get_view($view_name); + if (!$this->view || !isset($this->view->display[$display_name]) || !$this->view->access($display_name)) { + watchdog('entityreference', 'The view %view_name is no longer eligible.', array('%view_name' => $view_name), WATCHDOG_WARNING); + return FALSE; + } + $this->view->set_display($display_name); + + // Make sure the query is not cached + $this->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, + 'ids' => $ids, + ); + $this->view->display_handler->set_option('entityreference_options', $entityreference_options); + return TRUE; + } + + /** + * Implements EntityReferenceHandler::getReferencableEntities(). + */ + public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + $display_name = $this->field['settings']['handler_settings']['view']['display_name']; + $args = $this->field['settings']['handler_settings']['view']['args']; + if ($this->initializeView($match, $match_operator, $limit)) { + // Get the results. + return $this->view->execute_display($display_name, $args); + } + } + + /** + * Implements EntityReferenceHandler::countReferencableEntities(). + */ + function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') { + $this->getReferencableEntities($match, $match_operator); + return $this->view->total_items; + } + + function validateReferencableEntities(array $ids) { + $display_name = $this->field['settings']['handler_settings']['view']['display_name']; + $args = $this->field['settings']['handler_settings']['view']['args']; + if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) { + // Get the results. + $entities = $this->view->execute_display($display_name, $args); + return array_keys($entities); + } + } + + /** + * Implements EntityReferenceHandler::getLabel(). + */ + public function getLabel($entity) { + return entity_label($this->field['settings']['target_type'], $entity); + } + + /** + * Implements EntityReferenceHandler::entityFieldQueryAlter(). + */ + public function entityFieldQueryAlter(SelectQueryInterface $query) { + + } + +} + +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); +} diff --git a/plugins/selection/views.inc b/plugins/selection/views.inc new file mode 100644 index 0000000..5694bf3 --- /dev/null +++ b/plugins/selection/views.inc @@ -0,0 +1,9 @@ + t('Views: Filter by an entity reference view'), + 'class' => 'EntityReference_SelectionHandler_Views', + 'weight' => 0, + ); +} diff --git a/views/entityreference.views.inc b/views/entityreference.views.inc index 0b23d74..e095dd9 100644 --- a/views/entityreference.views.inc +++ b/views/entityreference.views.inc @@ -84,3 +84,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..7068d75 --- /dev/null +++ b/views/entityreference_plugin_display.inc @@ -0,0 +1,96 @@ +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 + // EntityReference_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 + $field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias]; + // 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']); + } +} 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'] .= '