diff --git a/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/entityreference/selection/SelectionBase.php b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/entityreference/selection/SelectionBase.php index ca81cf1..eeeb0a1 100644 --- a/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/entityreference/selection/SelectionBase.php +++ b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/entityreference/selection/SelectionBase.php @@ -42,6 +42,9 @@ public static function settingsForm($field, $instance) { $entity_info = entity_get_info($field['settings']['target_type']); // Merge-in default values. + if (!isset($field['settings']['handler_settings'])) { + $field['settings']['handler_settings'] = array(); + } $field['settings']['handler_settings'] += array( 'target_bundles' => array(), 'sort' => array( diff --git a/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/display/Entityreference.php b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/display/Entityreference.php new file mode 100644 index 0000000..c5d7cda --- /dev/null +++ b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/display/Entityreference.php @@ -0,0 +1,166 @@ + 'entityreference'); + $options['defaults']['default']['style'] = FALSE; + $options['row']['contains']['type'] = array('default' => 'entityreference'); + $options['defaults']['default']['row'] = FALSE; + + // Make sure the query is not cached. + $options['defaults']['default']['cache'] = 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; + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::getStyleType(). + */ + protected function getStyleType() { + return 'entityreference'; + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::execute(). + */ + public function execute() { + return $this->view->render($this->display['id']); + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::render(). + */ + public 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 ''; + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::usesExposed(). + */ + public function usesExposed() { + return FALSE; + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::query(). + */ + public function query() { + $options = $this->getOption('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->storage->base_table, $this->view->storage->base_field); + if (strpos($id_field, '.') === FALSE) { + $id_field = $this->view->storage->base_table . '.' . $this->id_field_alias; + } + + // Restrict the autocomplete options based on what's been typed already. + if (isset($options['match'])) { + $style_options = $this->getOption('style'); + $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['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(0, $conditions); + } + + // Add an IN condition for validation. + if (!empty($options['ids'])) { + $this->view->query->add_where(0, $id_field, $options['ids']); + } + + $this->view->setItemsPerPage($options['limit']); + } + + /** + * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::validate(). + */ + public function validate() { + $errors = parent::validate(); + // Verify that search fields are set up. + $style = $this->getOption('style'); + 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/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/row/Entityreference.php b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/row/Entityreference.php new file mode 100644 index 0000000..655cdc7 --- /dev/null +++ b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/row/Entityreference.php @@ -0,0 +1,61 @@ + '-'); + + return $options; + } + + /** + * Overrides Drupal\views\Plugin\views\row\Fields::buildOptionsForm(). + */ + public function buildOptionsForm(&$form, &$form_state) { + parent::buildOptionsForm($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." ); + } + + /** + * Overrides Drupal\views\Plugin\views\row\Fields::pre_render(). + */ + public function pre_render($row) { + // Force all fields to be inline by default. + if (empty($this->options['inline'])) { + $fields = $this->view->getItems('field', $this->displayHandler->display['id']); + $this->options['inline'] = drupal_map_assoc(array_keys($fields)); + } + + return parent::pre_render($row); + } +} diff --git a/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/style/Entityreference.php b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/style/Entityreference.php new file mode 100644 index 0000000..6ee9476 --- /dev/null +++ b/core/modules/field/modules/entityreference/lib/Drupal/entityreference/Plugin/views/style/Entityreference.php @@ -0,0 +1,105 @@ + NULL); + + return $options; + } + + /** + * Overrides Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::buildOptionsForm(). + */ + public function buildOptionsForm(&$form, &$form_state) { + parent::buildOptionsForm($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, + ); + } + } + + /** + * Overrides Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::render(). + */ + public function render() { + $options = $this->displayHandler->getOption('entityreference_options'); + + // Play nice with Views UI 'preview': if the view is not executed through + // Drupal\views\Plugin\entityreference\selection\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 entityreference_plugin_display. + $id_field_alias = $this->displayHandler->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 $records) { + foreach ($records as $values) { + // 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)))); + $this->view->row_index++; + } + } + unset($this->view->row_index); + return $results; + } + + /** + * Overrides Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::even_empty(). + */ + function even_empty() { + return TRUE; + } +} diff --git a/core/modules/field/modules/options/options.module b/core/modules/field/modules/options/options.module index 31652e3..4436089 100644 --- a/core/modules/field/modules/options/options.module +++ b/core/modules/field/modules/options/options.module @@ -437,14 +437,14 @@ function options_field_widget_info() { return array( 'options_select' => array( 'label' => t('Select list'), - 'field types' => array('list_integer', 'list_float', 'list_text'), + 'field types' => array('list_integer', 'list_float', 'list_text', 'entityreference'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), 'options_buttons' => array( 'label' => t('Check boxes/radio buttons'), - 'field types' => array('list_integer', 'list_float', 'list_text', 'list_boolean'), + 'field types' => array('list_integer', 'list_float', 'list_text', 'list_boolean', 'entityreference'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), diff --git a/core/modules/views/lib/Drupal/views/Plugin/entityreference/selection/Views.php b/core/modules/views/lib/Drupal/views/Plugin/entityreference/selection/Views.php new file mode 100644 index 0000000..815cbeb --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/entityreference/selection/Views.php @@ -0,0 +1,218 @@ +field = $field; + $this->instance = $instance; + $this->entity = $entity; + } + + /** + * Implements Drupal\entityreference\Plugin\Type\Selection\SelectionInterface::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->storage->base_table == $entity_info['base table']) { + $options[$view->storage->name . ':' . $display_id] = $view->storage->name . ' - ' . $view->storage->display[$display_id]['display_title']; + } + } + + // 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). + $plugin = new static($field, $instance); + $form['view']['#element_validate'] = array(array($plugin, 'settingsFormValidate')); + + if ($options) { + $default = !empty($view_settings['view_name']) ? $view_settings['view_name'] . ':' . $view_settings['display_name'] : NULL; + $form['view']['view_and_display'] = array( + '#type' => 'select', + '#title' => t('View used to select the entities'), + '#required' => TRUE, + '#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 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; + } + + /** + * Initializes a view. + * + * @param string $match + * @param string $match_operator + * @param int $limit + * @param array $ids + * + * @return bool + * Return TRUE if the views was initialized, FALSE otherwise. + */ + 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->storage->display[$display_name]) || !$this->view->access($display_name)) { + watchdog('entityreference', 'The view %view_name is no longer eligible for the %field_name field.', array('%view_name' => $view_name, '%field_name' => $this->instance['label']), WATCHDOG_WARNING); + return FALSE; + } + $this->view->setDisplay($display_name); + + // 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->displayHandlers[$display_name]->setOption('entityreference_options', $entityreference_options); + return TRUE; + } + + /** + * Implements Drupal\entityreference\Plugin\Type\Selection\SelectionInterface::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']; + $result = array(); + if ($this->initializeView($match, $match_operator, $limit)) { + // Get the results. + $result = $this->view->executeDisplay($display_name, $args); + } + + $return = array(); + if ($result) { + $target_type = $this->field['settings']['target_type']; + $entity_info = entity_get_info($target_type); + $entities = entity_load_multiple($target_type, array_keys($result)); + foreach($entities as $entity) { + $return[$entity->bundle()][$entity->id()] = $result[$entity->id()]; + } + } + return $return; + } + + /** + * Implements Drupal\entityreference\Plugin\Type\Selection\SelectionInterface::countReferencableEntities(). + */ + public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') { + $this->getReferencableEntities($match, $match_operator); + return $this->view->total_items; + } + + /** + * Implements Drupal\entityreference\Plugin\Type\Selection\SelectionInterface::validateReferencableEntities(). + */ + public function validateReferencableEntities(array $ids) { + $display_name = $this->field['settings']['handler_settings']['view']['display_name']; + $args = $this->field['settings']['handler_settings']['view']['args']; + $result = array(); + if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) { + // Get the results. + $entities = $this->view->executeDisplay($display_name, $args); + $result = array_keys($entities); + } + return $result; + } + + /** + * Implements Drupal\entityreference\Plugin\Type\Selection\SelectionInterface::validateAutocompleteInput(). + */ + public function validateAutocompleteInput($input, &$element, &$form_state, $form) { + return NULL; + } + + /** + * Implements Drupal\entityreference\Plugin\Type\Selection\SelectionInterface::entityFieldQueryAlter(). + */ + public function entityFieldQueryAlter(SelectInterface $query) { } + + /** + * Element validate; Check View is valid. + */ + public function settingsFormValidate($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 { + form_error($element, t('The views entity selection mode requires a view.')); + return; + } + + // 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']); + if ($args_string === '') { + $args = array(); + } + else { + // array_map is called to trim whitespaces from the arguments. + $args = array_map('trim', explode(',', $args_string)); + } + + $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args); + form_set_value($element, $value, $form_state); + } +}