From d228d64dfcb383bbb689e0dc6be34143a42ec4b8 Mon Sep 17 00:00:00 2001 From: DiegoPino Date: Thu, 10 Dec 2015 11:08:21 -0300 Subject: [PATCH 1/4] ISLANDORA-1392: New Islandora Solr Views mechanic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jira: [ISLANDORA-1392](https://jira.duraspace.org/browse/ISLANDORA-1392) and some other un-detected stuff needed to fix this. Also: https://groups.google.com/forum/#!searchin/islandora/views/islandora/LVz 0-JF9fxU/o4jOKV6FAgAJ # What does this Pull Request do? This "Huge" pull request changes the way Islandora Solr Views defines it's fields and handlers to fix freezing and out of memory issues with Solr Indexes that have a large Solr field count. It also fixes a lot of small bugs and adds some additional functionality to this custom view module. # What's new? Instead of having a particular views field matching every existing Solr fields, we define a discrete set of fields classified by type: * General Solr field * A multipurpose views field that allows to use any existing solr field for displaying, filtering and context exposing * Sortable Solr field * A Sortable Solr field that allows a view to be sorted and also can be exposed * Date Solr field * A Solr Date field * Score * A simple score field to sort/display Each of this fields has an option to define to which real-exisiting solr field to "bind", which can be done through an autocomplete form element that only shows those Solr field that match the type (e.g, Date Solr field only shows Solr fields of type 'date'; Sort only sortable, non multivalued ones). Additionally, it's possible to disable facet calculation at all to make view faster. This is done in `'Other' -> 'Query settings' -> 'settings'` during View editing. If facet is `enabled` (`disabled `by default), then the global Islandora Solr Query processor variable is also defined, allowing to use blocks that depend in this one. Lastly a new options is available for Filter fields. There is a `'Don't escape filter value for Solr'` option. When enabled, the Filter value set will not escaped (dangerous) but will allow to use filters like `[ * TO NOW]` and other non-literal strings, functionality not previously available. Please don't expose this ones to user interaction! I'm pretty sure i have put a function to check for this… # How should this be tested? Prior to testing I encourage people to export/backup their current Islandora Solr views. They won't work anymore and will need some refactoring. Once enabled, as any other existing View. Choose which Fields, Filters and Sort to use from the limited set. On each add/edit form you have the option to select which real Solr field you want to select from the index. It defaults always to 'PID' # Backwards compatibility: Sadly it's not backwards compatible with previous Islandora Solr Views, which basically means you have to make new views from scratch, or export your current ones and edit the values to match the new structure. # Additional Notes: This is a big pull, was tested and re-tested but can have some issues, so please comment/interact with me so we can detect and correct them. Could this change impact execution of existing code? Yes, of course, it will. Since the whole mechanic changed no previous Views build using this module will work out of the box. Additionally, some code clean up (coding standards) was made. Sadly it was impossible to fix every already existing issue. more over because Drupal's Views module does not respect coding standards used by us. Also, since we can now put a facet block next to a Islandora Solr View, there is a need to make a new block that is able to handle view interaction. The current one has the nasty habit to use the current path for links, making a click on a facet have no good consequences at all! Work is on progress to make a views respectful facet block that allows to interact with the current view. --- Thanks a lot! Diego Pino Navarro A dog & human friendly developer --- ..._solr_views_handler_argument_by_schema.inc | 63 ++++++ ...ora_solr_views_handler_field_by_schema.inc | 126 ++++++++++++ ...slandora_solr_views_handler_field_date.inc | 38 +++- ...ra_solr_views_handler_filter_by_schema.inc | 192 ++++++++++++++++++ ...dora_solr_views_handler_sort_by_schema.inc | 58 ++++++ includes/blocks.inc | 27 +++ includes/callbacks.inc | 59 ++++++ islandora_solr_views.info | 4 + islandora_solr_views.module | 16 ++ islandora_solr_views.views.inc | 99 +++++---- islandora_solr_views_query.inc | 82 ++++++-- 11 files changed, 701 insertions(+), 63 deletions(-) create mode 100644 handlers/islandora_solr_views_handler_argument_by_schema.inc create mode 100644 handlers/islandora_solr_views_handler_field_by_schema.inc create mode 100644 handlers/islandora_solr_views_handler_filter_by_schema.inc create mode 100644 handlers/islandora_solr_views_handler_sort_by_schema.inc create mode 100644 includes/blocks.inc create mode 100644 includes/callbacks.inc diff --git a/handlers/islandora_solr_views_handler_argument_by_schema.inc b/handlers/islandora_solr_views_handler_argument_by_schema.inc new file mode 100644 index 0000000..b263c9a --- /dev/null +++ b/handlers/islandora_solr_views_handler_argument_by_schema.inc @@ -0,0 +1,63 @@ +argument) && !empty($this->options['solr_field'])) { + $value = islandora_solr_lesser_escape($this->argument); + $solr_field = $this->options['solr_field']; + module_load_include('inc', 'islandora_solr', 'includes/utilities'); + $group = isset($this->options['group']) ? $this->options['group'] : FALSE; + if (is_array($value)) { + $values = array_filter($value); + // Ensure that some values have been selected. + if (!empty($values)) { + $this->query->add_filter($solr_field, '(' . implode('OR', $values) . ')', $group); + } + return; + } + if (!empty($value)) { + $this->query->add_filter($solr_field, $value, $group); + } + } + } + + /** + * Define custom option for our solr field. + */ + function option_definition() { + $options = parent::option_definition(); + $options['solr_field'] = array('default' => 'PID'); + return $options; + } + + /** + * Define form element for real solr field. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['solr_field'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#title' => t('Solr field to use'), + '#description' => t("Select the Solr field to use"), + '#size' => 45, + '#type' => 'textfield', + '#autocomplete_path' => 'islandora_solr_views/autocomplete_luke/all', + '#default_value' => !empty($this->options['solr_field']) ? $this->options['solr_field'] : 'PID', + '#required' => TRUE, + '#weight' => -10, + ); + } +} +// @codingStandardsIgnoreEnd diff --git a/handlers/islandora_solr_views_handler_field_by_schema.inc b/handlers/islandora_solr_views_handler_field_by_schema.inc new file mode 100644 index 0000000..73f6c15 --- /dev/null +++ b/handlers/islandora_solr_views_handler_field_by_schema.inc @@ -0,0 +1,126 @@ +options['link_to_object'])) { + $this->additional_fields['PID'] = array('table' => 'islandora_solr', 'field' => 'PID'); + } + } + + /** + * Get value. + * + * @param type $values + * @param type $field + * + * @return type string + */ + function get_value($values, $field = NULL) { + $alias = isset($field) ? $this->aliases[$field] : $this->field_alias; + + if (isset($values->{$alias})) { + if (is_array($values->{$alias})) { + $values->{$alias} = array_filter($values->{$alias}, 'trim'); + return implode(", ", $values->{$alias}); + } + else { + return $values->{$alias}; + } + } + } + + /** + * Define new option. + */ + function option_definition() { + $options = parent::option_definition(); + $options['link_to_object'] = TRUE; + $options['solr_field'] = array('default' => 'PID'); + return $options; + } + + /** + * Define form element for 'link to object' option and 'solr_field'. + */ + function options_form(&$form, &$form_state) { + $form['link_to_object'] = array( + '#title' => t('Link field to object.'), + '#description' => t("Enabling this will override any existing links in this field."), + '#type' => 'checkbox', + '#default_value' => !empty($this->options['link_to_object']), + ); + $form['solr_field'] = array( + '#title' => t('Solr field to use'), + '#description' => t("Select the Solr field to use"), + '#size' => 45, + '#type' => 'textfield', + '#autocomplete_path' => 'islandora_solr_views/autocomplete_luke/displayable', + '#default_value' => !empty($this->options['solr_field']) ? $this->options['solr_field'] : 'PID', + '#required' => TRUE, + ); + + parent::options_form($form, $form_state); + } + + function ui_name($short = FALSE) { + return $this->get_field(parent::ui_name($short)) . ' ' . $this->options['solr_field']; + } + + /** + * Handles link to object. + * + * Data should be made XSS safe prior to calling this function. + */ + function render_link($data, $values) { + + if (!empty($this->options['link_to_object']) && !empty($this->additional_fields['PID'])) { + if ($data !== NULL && $data !== '') { + $this->options['alter']['make_link'] = TRUE; + $this->options['alter']['path'] = 'islandora/object/' . $this->get_value($values, 'PID'); + } + else { + $this->options['alter']['make_link'] = FALSE; + } + } + return $data; + } + + /** + * Render whatever the data is as a link to the object. + * + * @param type $values + * + * @return type string Rendered link + */ + function render($values) { + $value = $this->get_value($values); + return $this->render_link($this->sanitize_value($value), $values); + } + + /** + * Called to determine what to tell the clicksorter. + */ + function click_sort($order) { + $sort_field = (isset($this->definition['click sort field']) ? $this->definition['click sort field'] : $this->real_field); + $this->query->add_sort($sort_field, $order); + } + +} +// @codingStandardsIgnoreEnd diff --git a/handlers/islandora_solr_views_handler_field_date.inc b/handlers/islandora_solr_views_handler_field_date.inc index fb72521..1066315 100644 --- a/handlers/islandora_solr_views_handler_field_date.inc +++ b/handlers/islandora_solr_views_handler_field_date.inc @@ -12,6 +12,29 @@ // being called all over the place. TODO bring up to coding standards class islandora_solr_views_handler_field_date extends islandora_solr_views_handler_field { + + /** + * Get value. + * + * @param type $values + * @param type $field + * + * @return type string + */ + function get_value($values, $field = NULL) { + + $alias = isset($field) ? $this->aliases[$field] : $this->field_alias; + + if (isset($values->{$alias})) { + if (is_array($values->{$alias})) { + $values->{$alias} = array_filter($values->{$alias}, 'trim'); + return implode(", ", $values->{$alias}); + } + else { + return $values->{$alias}; + } + } + } /** * Define new option. */ @@ -20,6 +43,7 @@ class islandora_solr_views_handler_field_date extends islandora_solr_views_handl $options = parent::option_definition(); // Set defaults. + $options['solr_field'] = array('default' => 'fgs_createdDate_dt'); $options['date_format'] = array('default' => 'small'); $options['custom_date_format'] = array('default' => ''); @@ -39,7 +63,15 @@ class islandora_solr_views_handler_field_date extends islandora_solr_views_handl '@date' => format_date(REQUEST_TIME, $value['type']), )); } - + $form['solr_field'] = array( + '#title' => t('Solr field to use'), + '#description' => t("Select the Solr date field to use"), + '#size' => 45, + '#type' => 'textfield', + '#autocomplete_path' => 'islandora_solr_views/autocomplete_luke/date', + '#default_value' => !empty($this->options['solr_field']) ? $this->options['solr_field'] : 'fgs_createdDate_dt', + '#required' => TRUE, + ); $form['date_format'] = array( '#type' => 'select', '#title' => t('Date format'), @@ -76,7 +108,9 @@ class islandora_solr_views_handler_field_date extends islandora_solr_views_handl parent::options_form($form, $form_state); } - + function ui_name($short = FALSE) { + return $this->get_field(parent::ui_name($short)) . ' ' . $this->options['solr_field']; + } /** * Render field. */ diff --git a/handlers/islandora_solr_views_handler_filter_by_schema.inc b/handlers/islandora_solr_views_handler_filter_by_schema.inc new file mode 100644 index 0000000..430bbbb --- /dev/null +++ b/handlers/islandora_solr_views_handler_filter_by_schema.inc @@ -0,0 +1,192 @@ +value) && !empty($this->options['solr_field'])) { + $value = $this->value; + module_load_include('inc', 'islandora_solr', 'includes/utilities'); + // Only escape if 'value_type' is disabled to allow range queries + // and other non string ones. + if (!$this->options['value_type']) { + $value = islandora_solr_lesser_escape($value); + } + $exclude = isset($this->operator) && $this->operator === '!='; + if (is_array($value)) { + $values = array_filter($value); + // Ensure that some values have been selected. + if (!empty($values)) { + $this->query->add_filter($this->options['solr_field'], '(' . implode('OR', $values) . ')', $this->options['group'], $exclude); + } + return; + } + $this->query->add_filter($this->options['solr_field'], $value, $this->options['group'], $exclude); + } + } + /** + * Define new custom options. + */ + function option_definition() { + $options = parent::option_definition(); + $options['solr_field'] = array('default' => 'PID'); + $options['value_type'] = array('default' => 0); + // There is a bug in Views that makes this option required, + // even when not exposed. + $options['expose']['identifier'] = array('default' => 'solrfilter'); + + return $options; + } + + function admin_summary() { + return check_plain((string) $this->options['solr_field']) . check_plain((string) $this->operator) . ' ' . check_plain((string) $this->value); + } + + /** + * Define custom form elements to match options. + */ + function options_form(&$form, &$form_state) { + // Add an option to allow non string filters like [* TO NOW] + $form['value_type'] = array( + '#type' => 'checkbox', + '#title' => t("Don't escape filter value for Solr"), + '#description' => t('Enable filter value to be passed without escaping. Useful for e.g [* TO NOW]'), + '#default_value' => !empty($this->options['value_type']) ? $this->options['value_type'] : 0, + '#weight' => 11, + ); + $form['solr_field'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#title' => t('Solr field to use'), + '#description' => t("Select the Solr field to use"), + '#size' => 45, + '#type' => 'textfield', + '#autocomplete_path' => 'islandora_solr_views/autocomplete_luke/all', + '#default_value' => !empty($this->options['solr_field']) ? $this->options['solr_field'] : 'PID', + '#required' => TRUE, + '#weight' => -10, + ); + parent::options_form($form, $form_state); + // Modify the title for 'value' + $form['value']['#title'] = t("Filter value for Solr field"); + $form['value']['#weigth'] = 10; + + } + /** + * Provide a simple textfield for equality. + */ + function value_form(&$form, &$form_state) { + $which = 'all'; + $form['value'] = array( + '#type' => 'textfield', + '#title' => check_plain($this->definition['title']), + '#default_value' => $this->value, + ); + if (!empty($form['operator'])) { + $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator'; + } + if (!empty($form_state['exposed'])) { + $identifier = $this->options['expose']['identifier']; + + if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) { + // Exposed and locked. + $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none'; + } + else { + $source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']); + } + } + if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) { + $form_state['input'][$identifier] = $this->value; + } + } + + /** + * Provide default options for exposed filters. + */ + function expose_options() { + parent::expose_options(); + $this->options['expose']['identifier'] = drupal_strtolower(preg_replace('/[^A-Za-z0-9]/', '_', $this->options['id'])); + } + + /** + * Don't allow exposure for not filtered values. + */ + function can_expose() { + return ($this->options['value_type'] == 0); + } + + /** + * Validation handler, the parent:: fails on disabling 'expose'. + */ + function options_validate(&$form, &$form_state) { + $this->operator_validate($form, $form_state); + $this->value_validate($form, $form_state); + if (isset($form_state['values']['expose_button']['checkbox']['checkbox']) && ($form_state['values']['expose_button']['checkbox']['checkbox'] == 1) && !$this->is_a_group()) { + $this->expose_validate($form, $form_state); + } + if ($this->is_a_group()) { + $this->build_group_validate($form, $form_state); + } + } + + function operators() { + $operators = array( + '=' => array( + 'title' => t('Is equal to'), + 'short' => t('='), + 'values' => 1, + ), + '!=' => array( + 'title' => t('Is not equal to'), + 'short' => t('!='), + 'values' => 1, + ), + ); + return $operators; + } + + /** + * Build strings from the operators() for 'select' options. + */ + function operator_options($which = 'title') { + $options = array(); + foreach ($this->operators() as $id => $info) { + $options[$id] = $info[$which]; + } + + return $options; + } + + function operator_values($values = 1) { + $options = array(); + foreach ($this->operators() as $id => $info) { + if (isset($info['values']) && $info['values'] == $values) { + $options[] = $id; + } + } + + return $options; + } + + public function expose_form(&$form, &$form_state) { + parent::expose_form($form, $form_state); + if (empty($form['expose']['identifier']['#default_value'])) { + $form['expose']['identifier']['#default_value'] = $this->options['field']; + } + if (empty($form['expose']['label']['#default_value'])) { + $form['expose']['label']['#default_value'] = $this->definition['title']; + } + if (empty($form['ui_name']['#default_value'])) { + $form['ui_name']['#default_value'] = $this->definition['title']; + } + } +} +// @codingStandardsIgnoreEnd \ No newline at end of file diff --git a/handlers/islandora_solr_views_handler_sort_by_schema.inc b/handlers/islandora_solr_views_handler_sort_by_schema.inc new file mode 100644 index 0000000..b4bab10 --- /dev/null +++ b/handlers/islandora_solr_views_handler_sort_by_schema.inc @@ -0,0 +1,58 @@ +options['order']); + $this->query->add_sort($this->options['solr_field'], $order); + } + /** + * Define new custom option. + */ + function option_definition() { + $options = parent::option_definition(); + $options['solr_field'] = array('default' => 'PID'); + return $options; + } + + /** + * Custom admin summary for field. + */ + function admin_summary() { + $internal_summary = parent::admin_summary(); + return check_plain((string) $this->options['solr_field']) . ' ' . check_plain(drupal_strtolower((string) $this->options['order'])) . ' ' . $internal_summary; + } + + /** + * Define form element for 'solr_field'. + */ + function options_form(&$form, &$form_state) { + $form['solr_field'] = array( + '#title' => t('Solr field to use for sorting'), + '#description' => t("Select the Solr field to use for sorting"), + '#size' => 45, + '#type' => 'textfield', + '#autocomplete_path' => 'islandora_solr_views/autocomplete_luke/sortable', + '#default_value' => !empty($this->options['solr_field']) ? $this->options['solr_field'] : 'PID', + '#required' => TRUE, + ); + + parent::options_form($form, $form_state); + } +} +// @codingStandardsIgnoreEnd diff --git a/includes/blocks.inc b/includes/blocks.inc new file mode 100644 index 0000000..02c78f9 --- /dev/null +++ b/includes/blocks.inc @@ -0,0 +1,27 @@ + array( + 'name' => t('Islandora facets for Solr Views'), + 'module' => 'islandora_solr_views', + 'file' => 'includes/blocks.inc', + 'class' => 'IslandoraSolrResultsView', + 'function' => 'displayFacets', + 'form' => NULL, + ), + ); +} + +class IslandoraSolrResultsView extends IslandoraSolrResults {}; diff --git a/includes/callbacks.inc b/includes/callbacks.inc new file mode 100644 index 0000000..ed50cec --- /dev/null +++ b/includes/callbacks.inc @@ -0,0 +1,59 @@ + $value) { + if (stripos($term, $string) !== FALSE) { + // Search case insensitive, but keep the case on replace. + $term_str = preg_replace("/$string/i", "\$0", $term); + + // Add strong elements to highlight the found string. + $result[$term] = $term_str . '(' . $value['type'] . ')'; + } + } + // Sort alphabetically. + ksort($result); + + drupal_json_output($result); + exit(); +} diff --git a/islandora_solr_views.info b/islandora_solr_views.info index 14d5c68..3e2818e 100644 --- a/islandora_solr_views.info +++ b/islandora_solr_views.info @@ -10,11 +10,15 @@ dependencies[] = views files[] = islandora_solr_views_query.inc files[] = handlers/islandora_solr_views_handler_field.inc +files[] = handlers/islandora_solr_views_handler_field_by_schema.inc files[] = handlers/islandora_solr_views_handler_field_date.inc files[] = handlers/islandora_solr_views_handler_field_manage.inc files[] = handlers/islandora_solr_views_handler_sort.inc +files[] = handlers/islandora_solr_views_handler_sort_by_schema.inc files[] = handlers/islandora_solr_views_handler_argument.inc +files[] = handlers/islandora_solr_views_handler_argument_by_schema.inc files[] = handlers/islandora_solr_views_handler_filter.inc +files[] = handlers/islandora_solr_views_handler_filter_by_schema.inc files[] = handlers/islandora_solr_views_handler_filter_query_lucene.inc files[] = handlers/islandora_solr_views_handler_filter_query_dismax.inc files[] = handlers/islandora_solr_views_handler_collection_count.inc diff --git a/islandora_solr_views.module b/islandora_solr_views.module index 95c415c..ccaaddf 100644 --- a/islandora_solr_views.module +++ b/islandora_solr_views.module @@ -5,6 +5,22 @@ * Provides Views Implementation for Islandora Solr */ +/** + * Implements hook_menu(). + */ +function islandora_solr_views_menu() { + $items['islandora_solr_views/autocomplete_luke/%'] = array( + 'title' => 'Islandora Solr Luke autocomplete', + 'description' => 'Autocomplete callback to populate solr text fields.', + 'page callback' => '_islandora_solr_views_autocomplete_luke', + 'page arguments' => array(2), + 'access arguments' => array('administer islandora solr'), + 'file' => 'includes/callbacks.inc', + 'type' => MENU_CALLBACK, + ); + return $items; +} + /** * Implements hook_views_api(). */ diff --git a/islandora_solr_views.views.inc b/islandora_solr_views.views.inc index d5b9ef8..72a9a4c 100644 --- a/islandora_solr_views.views.inc +++ b/islandora_solr_views.views.inc @@ -29,6 +29,7 @@ function islandora_solr_views_views_plugins() { */ function islandora_solr_views_views_data() { // Set base variables. + module_load_include('inc', 'islandora_solr', 'includes/luke'); $base_field = 'PID'; $base_table = 'islandora_solr'; $data[$base_table]['table']['group'] = t('Islandora Solr'); @@ -39,11 +40,6 @@ function islandora_solr_views_views_data() { 'help' => t('Searches the Islandora Solr index.'), 'field' => $base_field, ); - - // Get the list of the fields in index directly from Solr. - $luke = islandora_solr_get_luke(); - $solr_fields = $luke['fields']; - // Always add score handlers. $data[$base_table]['score'] = array( 'title' => t('Score'), @@ -90,52 +86,75 @@ function islandora_solr_views_views_data() { ), ); - // Loop over all solr fields. - foreach ($solr_fields as $solr_field_name => $solr_field) { + // Get the list of the fields in index directly from Solr. + $luke = islandora_solr_get_luke(); + $solr_fields = $luke['fields']; - // We do not allow to display 'sort_*' fields. - if (strpos($solr_field_name, 'sort_') === 0) { - continue; - } + // Create template array for different supported field + // types. + $solr_fields_types_to_create = array( + 'date' => array( + 'create' => FALSE, + 'title' => t('Date Solr field'), + 'help' => t('Any Solr field of type Date'), + ), + 'sortable' => array( + 'create' => FALSE, + 'title' => t('Sortable Solr field'), + 'help' => t('Any Solr field that can be used to sort'), + ), + 'general' => array( + 'create' => count($solr_fields) > 0 ? TRUE : FALSE, + 'title' => t('General Solr field'), + 'help' => t('Any Solr field that can be used to query, filter or as context argument'), + ), + ); + // Loop over all solr fields. We will group by types + // instead of creating individual fields. + foreach ($solr_fields as $solr_field_name => $solr_field) { // Set luke field variables. $field_type = $solr_field['type']; $field_schema = $solr_field['schema']; - $field_dynamicbase = isset($solr_field['dynamicBase']) ? $solr_field['dynamicBase'] : NULL; - - // Set field handlers. - $field = array(); - $field['title'] = $solr_field_name; - $field['help'] = t('Type') . ': ' . $field_type; - // Field handler. if ($field_type == 'date') { - $field['field']['handler'] = 'islandora_solr_views_handler_field_date'; - } - else { - $field['field']['handler'] = 'islandora_solr_views_handler_field'; + $solr_fields_types_to_create['date']['create'] = TRUE; } - // Check if sortable. if (strstr($field_schema, "I") != FALSE AND strstr($field_schema, "M") == FALSE) { - $field['field']['click sortable'] = TRUE; + $solr_fields_types_to_create['sortable']['create'] = TRUE; } - // Argument handler. - $field['argument'] = array( - 'handler' => 'islandora_solr_views_handler_argument', - ); - // Filter handler. - $field['filter'] = array( - 'handler' => 'islandora_solr_views_handler_filter', - ); - // Sortable handler. - // Check if sortable: must be indexed and can't be multivalued. - // http://wiki.apache.org/solr/CommonQueryParameters#sort - if (strstr($field_schema, "I") != FALSE AND strstr($field_schema, "M") == FALSE) { - $field['sort'] = array( - 'handler' => 'islandora_solr_views_handler_sort', + } + // Create actual fields grouped by schema type. + foreach ($solr_fields_types_to_create as $type => $field_data) { + if ($field_data['create']) { + $field = array(); + $field['title'] = $field_data['title']; + $field['help'] = $field_data['help']; + if ($type == 'date') { + $field['field'] = array( + 'handler' => 'islandora_solr_views_handler_field_date', + ); + } + else { + $field['field'] = array( + 'handler' => 'islandora_solr_views_handler_field_by_schema', + ); + } + $field['filter'] = array( + 'handler' => 'islandora_solr_views_handler_filter_by_schema', ); + if ($type == 'sortable') { + $field['sort'] = array( + 'handler' => 'islandora_solr_views_handler_sort_by_schema', + ); + } + if ($type == 'general') { + $field['argument'] = array( + 'handler' => 'islandora_solr_views_handler_argument_by_schema', + ); + } + $data[$base_table][$type] = $field; } - // Add array. - $data[$base_table][$solr_field_name] = $field; } + // Add our collection counting goodness. $data[$base_table]['solr_collection_count'] = array( 'title' => 'Collection count', diff --git a/islandora_solr_views_query.inc b/islandora_solr_views_query.inc index 6471d4e..1d7a016 100644 --- a/islandora_solr_views_query.inc +++ b/islandora_solr_views_query.inc @@ -58,18 +58,28 @@ class islandora_solr_views_query extends views_plugin_query { */ public function build(&$view) { $view->init_pager(); - // Let the pager modify the query to add limits. $this->pager->query(); - + + // Real Solr fields mapper + $solr_fields = array(); // Set aliases of the fields. + // Since we our real Solr field will not match always the $field_name, + // we will have to build our own naming structure. foreach ($view->field as $field_name => &$field) { - $field->field_alias = $field_name; + $solr_field_name_to_use = isset($field->options['solr_field']) ? $field->options['solr_field'] : $field->real_field; + if (isset($solr_fields[$solr_field_name_to_use])) { + $cardinality = array_push($solr_fields[$solr_field_name_to_use], $field_name); + } + else { + $solr_fields[$solr_field_name_to_use] = array($field_name); + $cardinality = 1; + } + $field->field_alias = $cardinality == 1 ? $solr_field_name_to_use : $solr_field_name_to_use . "_" . ($cardinality-1); $field->aliases['entity_type'] = 'entity_type'; } - // Add fields to the query so they will be shown in solr document. - $this->params['fl'] = array_keys($view->field); + $this->params['fl'] = array_keys($solr_fields); } /** @@ -78,13 +88,25 @@ class islandora_solr_views_query extends views_plugin_query { * Executes the query and fills the associated view object with according * values. * + * @global IslandoraSolrQueryProcessor $_islandora_solr_queryclass + * The IslandoraSolrQueryProcessor object which includes the current query + * settings and the raw Solr results. + * * Values to set: $view->result, $view->total_rows, $view->execute_time, * $view->pager['current_page']. */ public function execute(&$view) { try { + // New query processor class. + $islandora_solr_query = new IslandoraSolrQueryProcessor(); + // Only enable facets if needed. Makes queries faster + if ($this->options['enable_facets']) { + global $_islandora_solr_queryclass; + // Set our famous global so we can enable the facet block + $_islandora_solr_queryclass = $islandora_solr_query; + } + $start = microtime(TRUE); - // Include common.inc. module_load_include('inc', 'islandora_solr', 'includes/common'); @@ -96,7 +118,7 @@ class islandora_solr_views_query extends views_plugin_query { if ($params['rows'] == 0) { $params['rows'] = 1000000; if (!isset($this->offset)) { - $this->offset = 0; + $this->offset = 0; } } // Add fields. @@ -129,10 +151,10 @@ class islandora_solr_views_query extends views_plugin_query { // Add sorting. if (isset($this->orderby)) { module_load_include('inc', 'islandora_solr', 'includes/utilities'); - // Populate sorting parameters. - foreach ($this->orderby as $field => $order) { - $params['sort'][] = islandora_solr_lesser_escape($field) . ' ' . $order; - } + // Populate sorting parameter as one string. + $params['sort'] = array_map(function($item) { + return $item['field'].' '.$item['direction']; + }, $this->orderby); } // Set query. @@ -150,23 +172,22 @@ class islandora_solr_views_query extends views_plugin_query { $query = '*:*'; } - // New query processor class. - $islandora_solr_query = new IslandoraSolrQueryProcessor(); // Check for dismax (not functional yet). if ($dismax != NULL) { $islandora_solr_query->solrDefType = $dismax; $params['defType'] = $dismax; } - // Add query (defaults to *:*). $islandora_solr_query->buildQuery($query, $params); - + // Add 'fl' to solrParams since the query builder does not use them. + $islandora_solr_query->solrParams['fl'] = $params['fl']; + // Disable or enable facets depending on user option. + $islandora_solr_query->solrParams['facet'] = $this->options['enable_facets'] ? 'true' : 'false'; // Add solr limit. $islandora_solr_query->solrLimit = $params['rows']; // Add solr start. $islandora_solr_query->solrStart = $this->offset; - // Excecute query. $islandora_solr_query->executeQuery(FALSE); // Solr results. @@ -205,18 +226,33 @@ class islandora_solr_views_query extends views_plugin_query { return; } } + function option_definition() { + $options = parent::option_definition(); + $options['enable_facets'] = array('default' => FALSE); + return $options; + } + + function options_form(&$form, &$form_state) { + $form['enable_facets'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Facets'), + '#default_value' => $this->options['enable_facets'], + '#description' => t('This option enables facet processing and global Solr variable to display block'), + '#required' => FALSE, + ); + } /** - * Function add_filter. - */ + * Function add_filter. + */ public function add_filter($type, $value, $group, $exclude = FALSE) { module_load_include('inc', 'islandora_solr', 'includes/utilities'); $exclude_string = ($exclude) ? '-' : ''; if ($group) { - $this->params['filters'][$group][] = $exclude_string . islandora_solr_lesser_escape($type) . ':' . $value; + $this->params['filters'][$group][] = $exclude_string . $type . ':' . $value; } else { - $this->params['filters'][] = $exclude_string . islandora_solr_lesser_escape($type) . ':' . $value; + $this->params['filters'][] = $exclude_string . $type . ':' . $value; } } @@ -232,7 +268,11 @@ class islandora_solr_views_query extends views_plugin_query { * Function add_sort. */ public function add_sort($field, $order) { - $this->orderby[$field] = $order; + //$this->orderby[$field] = $order; + $this->orderby[] = array( + 'field' => $field, + 'direction' => drupal_strtolower($order) + ); } /** From 78971367e65361332cfd4588ff0c50922cc38b91 Mon Sep 17 00:00:00 2001 From: DiegoPino Date: Thu, 7 Jan 2016 16:20:10 -0300 Subject: [PATCH 2/4] Coding standard Fixed a coding standard error --- islandora_solr_views_query.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islandora_solr_views_query.inc b/islandora_solr_views_query.inc index 1d7a016..6fa81d6 100644 --- a/islandora_solr_views_query.inc +++ b/islandora_solr_views_query.inc @@ -153,7 +153,7 @@ class islandora_solr_views_query extends views_plugin_query { module_load_include('inc', 'islandora_solr', 'includes/utilities'); // Populate sorting parameter as one string. $params['sort'] = array_map(function($item) { - return $item['field'].' '.$item['direction']; + return $item['field'] .' '. $item['direction']; }, $this->orderby); } From ebbe11fe5ca4a01a781c2bb021bef9fc1d77b34f Mon Sep 17 00:00:00 2001 From: DiegoPino Date: Thu, 7 Jan 2016 16:38:58 -0300 Subject: [PATCH 3/4] Same coding std. error again! Now properly fixed (i hope) --- islandora_solr_views_query.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islandora_solr_views_query.inc b/islandora_solr_views_query.inc index 6fa81d6..a6e2331 100644 --- a/islandora_solr_views_query.inc +++ b/islandora_solr_views_query.inc @@ -153,7 +153,7 @@ class islandora_solr_views_query extends views_plugin_query { module_load_include('inc', 'islandora_solr', 'includes/utilities'); // Populate sorting parameter as one string. $params['sort'] = array_map(function($item) { - return $item['field'] .' '. $item['direction']; + return $item['field'] . ' ' . $item['direction']; }, $this->orderby); } From 6355effd60005f923bbc424baaccf9cc6ddeb982 Mon Sep 17 00:00:00 2001 From: DiegoPino Date: Thu, 14 Jan 2016 15:57:52 -0300 Subject: [PATCH 4/4] Class inheritance as copy/paste fix Copy/paste detector was having trouble with similar multiple handlers. Not sure if having copy/pasta check is good since not even Drupal Views modules passes that one. But ok, i moved things around. lets hope nothing got broken! --- ...ora_solr_views_handler_field_by_schema.inc | 84 +------------------ .../islandora_solr_views_handler_filter.inc | 1 + ...ra_solr_views_handler_filter_by_schema.inc | 55 +----------- 3 files changed, 5 insertions(+), 135 deletions(-) diff --git a/handlers/islandora_solr_views_handler_field_by_schema.inc b/handlers/islandora_solr_views_handler_field_by_schema.inc index 73f6c15..d7fec1b 100644 --- a/handlers/islandora_solr_views_handler_field_by_schema.inc +++ b/handlers/islandora_solr_views_handler_field_by_schema.inc @@ -8,50 +8,15 @@ // @codingStandardsIgnoreStart // This Whole file is ignored due to classes and methods are not camelcase and it // being called all over the place. TODO bring up to coding standards -class islandora_solr_views_handler_field_by_schema extends views_handler_field { - /** - * Class init. - * - * @param type $view - * @param type $options - */ - function init(&$view, &$options) { - parent::init($view, $options); - // Don't add the additional fields to groupby. - if (!empty($this->options['link_to_object'])) { - $this->additional_fields['PID'] = array('table' => 'islandora_solr', 'field' => 'PID'); - } - } +class islandora_solr_views_handler_field_by_schema extends islandora_solr_views_handler_field { - /** - * Get value. - * - * @param type $values - * @param type $field - * - * @return type string - */ - function get_value($values, $field = NULL) { - $alias = isset($field) ? $this->aliases[$field] : $this->field_alias; - - if (isset($values->{$alias})) { - if (is_array($values->{$alias})) { - $values->{$alias} = array_filter($values->{$alias}, 'trim'); - return implode(", ", $values->{$alias}); - } - else { - return $values->{$alias}; - } - } - } /** - * Define new option. + * Define new options for additional solr field. */ function option_definition() { $options = parent::option_definition(); - $options['link_to_object'] = TRUE; $options['solr_field'] = array('default' => 'PID'); return $options; } @@ -60,12 +25,7 @@ class islandora_solr_views_handler_field_by_schema extends views_handler_field { * Define form element for 'link to object' option and 'solr_field'. */ function options_form(&$form, &$form_state) { - $form['link_to_object'] = array( - '#title' => t('Link field to object.'), - '#description' => t("Enabling this will override any existing links in this field."), - '#type' => 'checkbox', - '#default_value' => !empty($this->options['link_to_object']), - ); + $form['solr_field'] = array( '#title' => t('Solr field to use'), '#description' => t("Select the Solr field to use"), @@ -83,44 +43,6 @@ class islandora_solr_views_handler_field_by_schema extends views_handler_field { return $this->get_field(parent::ui_name($short)) . ' ' . $this->options['solr_field']; } - /** - * Handles link to object. - * - * Data should be made XSS safe prior to calling this function. - */ - function render_link($data, $values) { - - if (!empty($this->options['link_to_object']) && !empty($this->additional_fields['PID'])) { - if ($data !== NULL && $data !== '') { - $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = 'islandora/object/' . $this->get_value($values, 'PID'); - } - else { - $this->options['alter']['make_link'] = FALSE; - } - } - return $data; - } - - /** - * Render whatever the data is as a link to the object. - * - * @param type $values - * - * @return type string Rendered link - */ - function render($values) { - $value = $this->get_value($values); - return $this->render_link($this->sanitize_value($value), $values); - } - - /** - * Called to determine what to tell the clicksorter. - */ - function click_sort($order) { - $sort_field = (isset($this->definition['click sort field']) ? $this->definition['click sort field'] : $this->real_field); - $this->query->add_sort($sort_field, $order); - } } // @codingStandardsIgnoreEnd diff --git a/handlers/islandora_solr_views_handler_filter.inc b/handlers/islandora_solr_views_handler_filter.inc index 39bb7c5..efcd8f6 100644 --- a/handlers/islandora_solr_views_handler_filter.inc +++ b/handlers/islandora_solr_views_handler_filter.inc @@ -69,6 +69,7 @@ class islandora_solr_views_handler_filter extends views_handler_filter { * Provide default options for exposed filters. */ function expose_options() { + parent::expose_options(); $this->options['expose']['identifier'] = drupal_strtolower(preg_replace('/[^A-Za-z0-9]/', '_', $this->options['id'])); } diff --git a/handlers/islandora_solr_views_handler_filter_by_schema.inc b/handlers/islandora_solr_views_handler_filter_by_schema.inc index 430bbbb..ead9795 100644 --- a/handlers/islandora_solr_views_handler_filter_by_schema.inc +++ b/handlers/islandora_solr_views_handler_filter_by_schema.inc @@ -8,7 +8,7 @@ // @codingStandardsIgnoreStart // This Whole file is ignored due to classes and methods are not camelcase and it // being called all over the place. TODO bring up to coding standards -class islandora_solr_views_handler_filter_by_schema extends views_handler_filter { +class islandora_solr_views_handler_filter_by_schema extends islandora_solr_views_handler_filter { function query() { if (!empty($this->value) && !empty($this->options['solr_field'])) { @@ -77,43 +77,6 @@ class islandora_solr_views_handler_filter_by_schema extends views_handler_filter // Modify the title for 'value' $form['value']['#title'] = t("Filter value for Solr field"); $form['value']['#weigth'] = 10; - - } - /** - * Provide a simple textfield for equality. - */ - function value_form(&$form, &$form_state) { - $which = 'all'; - $form['value'] = array( - '#type' => 'textfield', - '#title' => check_plain($this->definition['title']), - '#default_value' => $this->value, - ); - if (!empty($form['operator'])) { - $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator'; - } - if (!empty($form_state['exposed'])) { - $identifier = $this->options['expose']['identifier']; - - if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) { - // Exposed and locked. - $which = in_array($this->operator, $this->operator_values(1)) ? 'value' : 'none'; - } - else { - $source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']); - } - } - if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) { - $form_state['input'][$identifier] = $this->value; - } - } - - /** - * Provide default options for exposed filters. - */ - function expose_options() { - parent::expose_options(); - $this->options['expose']['identifier'] = drupal_strtolower(preg_replace('/[^A-Za-z0-9]/', '_', $this->options['id'])); } /** @@ -137,22 +100,6 @@ class islandora_solr_views_handler_filter_by_schema extends views_handler_filter } } - function operators() { - $operators = array( - '=' => array( - 'title' => t('Is equal to'), - 'short' => t('='), - 'values' => 1, - ), - '!=' => array( - 'title' => t('Is not equal to'), - 'short' => t('!='), - 'values' => 1, - ), - ); - return $operators; - } - /** * Build strings from the operators() for 'select' options. */