diff --git a/README.md b/README.md
index 04bdefec5..2bace85aa 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,22 @@ On each site included in the federated search, you will need to:
In order to display results from the Solr index:
1. Configure the application route and settings at `/admin/config/search/federated-search-settings`
-2. Set permissions for `Use Federated Search` and `Administer Federated Search` for the proper roles.
-3. Optional: [Theme the ReactJS search app](https://www.drupal.org/docs/7/modules/search-api-federated-solr/search-api-federated-solr-module/theming-the-reactjs-search)
-4. Optional: Add the federated search page form block to your site theme
+1. Set permissions for `Use Federated Search` and `Administer Federated Search` for the proper roles.
+1. Optional: Configure default fields for queries. The default query field for search queries made through the proxy provided by this module is the `rendered_item` field. To set a different value for the default query fields there are two options:
+ 1. Set `$config['search_api_federated_solr_proxy_query_fields']` to an array of _Fulltext_ field machine names (i.e. `['rendered_item', 'full_text_title']`) from your search index in `settings.php`.
+ - This method will not work if you disable the proxy that this module provides for querying your solr backend in the search app or block autocomplete settings
+ - By default, the proxy will validate the field names to ensure that they are full text and that they exist on the index for this site. Then it will translate the index field name into its solr field name counterpart. If you need to disable this validation + transformation (for example to search fields on a D8 site index whose machine names are different than the D7 site counterpart), set `$config['search_api_federated_solr_proxy_validate_query_fields_against_schema']` to `FALSE`. Then you must supply the _solr field names_. To determine what these field names are on your D7 site, use the drush command `drush sapifs-f`, which will output a table with index field names and their solr field name counterparts.
+ 1. Configure that Search API server to set default query fields for your default [Request Handler](https://lucene.apache.org/solr/guide/6_6/requesthandlers-and-searchcomponents-in-solrconfig.html#RequestHandlersandSearchComponentsinSolrConfig-SearchHandlers). (See [example](https://github.com/palantirnet/federated-search-demo/blob/master/conf/solr/drupal8/custom/solr-conf/4.x/solrconfig_extra.xml#L94) in Federated Search Demo site Solr server config)
+1. Optional: [Theme the ReactJS search app](https://www.drupal.org/docs/7/modules/search-api-federated-solr/search-api-federated-solr-module/theming-the-reactjs-search)
+1. Optional: Add the federated search page form block to your site theme
+
+## Adding Solr query debug information to proxy response
+
+To see debug information when using the proxy for your search queries, set `$conf['search_api_federated_solr_proxy_debug_query']` to `TRUE` in your settings.php.
+
+Then user your browsers developer tools to inspect network traffic. When your site makes a search query through the proxy, inspect the response for this request and you should now see a `debug` object added to the response object.
+
+*Note: we recommend leaving this set to `FALSE` for production environments, as it could have an impact on performance.*
## Updating the bundled React application
diff --git a/css/search_api_federated_solr_autocomplete.css b/css/search_api_federated_solr_autocomplete.css
new file mode 100644
index 000000000..fffa8a2de
--- /dev/null
+++ b/css/search_api_federated_solr_autocomplete.css
@@ -0,0 +1,83 @@
+#autocomplete-wrapper {
+ position: relative;
+}
+
+#federated-search-page-block-form .search-autocomplete-container {
+ position: absolute;
+ border: 1px solid #999;
+ background: #fff;
+ z-index: 1000;
+ width: 100%;
+ display: block;
+}
+
+#federated-search-page-block-form .search-autocomplete-container:not(.visually-hidden) .search-autocomplete-container__title {
+ font-size:.8em;
+ line-height:1.46667em;
+ position: relative;
+ font-weight: bold;
+ padding: 10px 20px;
+ border-bottom: 1px dashed #ccc;
+ display: block;
+}
+.search-autocomplete-container:not(.visually-hidden) .search-autocomplete-container__close-button {
+ font-size:.8em;
+ line-height:1.46667em;
+ font-style:normal;
+ padding:3px 7px;
+ border-color:#ccc;
+ color:#333;
+ cursor:pointer;
+ position:absolute;
+ right:5px
+}
+
+.search-autocomplete-container:not(.visually-hidden) .search-autocomplete-container__close-button:hover {
+ border-color:#b5b5b5;
+ background-color:#f6f6f6
+}
+
+#federated-search-page-block-form .search-autocomplete-container:not(.visually-hidden) .search-autocomplete-container__directions {
+ display: block;
+ padding:10px 20px;
+}
+
+#federated-search-page-block-form .search-autocomplete-container:not(.visually-hidden) .search-autocomplete-container__directions-item {
+ display: block;
+ font-size:.8em;
+ line-height:1.46667em;
+}
+
+.search-autocomplete-container:not(.visually-hidden) .autocomplete-suggestion {
+ cursor:pointer;
+ background-color:#fff;
+ white-space: nowrap;
+}
+
+.search-autocomplete-container:not(.visually-hidden) .autocomplete-suggestion__link {
+ display: block;
+ color:#737373;
+ text-decoration: none;
+ font-size:.8em;
+ line-height:1.46667em;
+ padding:15px 20px;
+ border:1px solid #fff;
+ border-bottom-color:#ccc;
+}
+
+.search-autocomplete-container:not(.visually-hidden) .highlight .autocomplete-suggestion__link,
+.search-autocomplete-container:not(.visually-hidden) .autocomplete-suggestion__link:hover {
+ text-decoration: underline;
+ background-color:#f6f6f6;
+ border:1px solid #f6f6f6;
+ border-bottom-color:#ccc;
+}
+
+.autocomplete-selected {
+ background: #f0f0f0;
+}
+
+.visually-hidden {
+ position: absolute !important;
+ clip: rect(1px 1px 1px 1px);
+}
diff --git a/js/search_api_federated_solr_autocomplete.js b/js/search_api_federated_solr_autocomplete.js
new file mode 100644
index 000000000..fb485e80d
--- /dev/null
+++ b/js/search_api_federated_solr_autocomplete.js
@@ -0,0 +1,484 @@
+/**
+ * @file
+ * Adds autocomplete functionality to search_api_solr_federated block form.
+ */
+(function($) {
+ var autocomplete = {};
+
+ /**
+ * Polyfill for Object.assign
+ * @see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
+ */
+ if (typeof Object.assign != 'function') {
+ // Must be writable: true, enumerable: false, configurable: true
+ Object.defineProperty(Object, "assign", {
+ value: function assign(target, varArgs) { // .length of function is 2
+ 'use strict';
+ if (target == null) { // TypeError if undefined or null
+ throw new TypeError('Cannot convert undefined or null to object');
+ }
+
+ var to = Object(target);
+
+ for (var index = 1; index < arguments.length; index++) {
+ var nextSource = arguments[index];
+
+ if (nextSource != null) { // Skip over if undefined or null
+ for (var nextKey in nextSource) {
+ // Avoid bugs when hasOwnProperty is shadowed
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ },
+ writable: true,
+ configurable: true
+ });
+ }
+
+ /**
+ * Attaches our custom autocomplete settings to the search_api_federated_solr block search form field.
+ *
+ * @type {Drupal~behavior}
+ *
+ * @prop {Drupal~behaviorAttach} attach
+ * Attaches the autocomplete behaviors.
+ */
+ Drupal.behaviors.searchApiFederatedSolrAutocomplete = {
+ attach: function attach(context) {
+ // Find our fields with autocomplete settings
+ $(context)
+ .find(".js-search-api-federated-solr-block-form-autocomplete")
+ .once("search-api-federated-solr-autocomplete-search")
+ .each(function() {
+ // Halt execution if we don't have the required config.
+ if (
+ !Object.hasOwnProperty.call(
+ Drupal.settings,
+ "searchApiFederatedSolr"
+ ) ||
+ !Object.hasOwnProperty.call(
+ Drupal.settings.searchApiFederatedSolr,
+ "block"
+ ) ||
+ !Object.hasOwnProperty.call(
+ Drupal.settings.searchApiFederatedSolr.block,
+ "autocomplete"
+ ) ||
+ !Object.hasOwnProperty.call(
+ Drupal.settings.searchApiFederatedSolr.block.autocomplete,
+ "url"
+ )
+ ) {
+ return;
+ }
+ // Set default settings.
+ var defaultSettings = {
+ isEnabled: false,
+ appendWildcard: false,
+ userpass: "",
+ numChars: 2,
+ suggestionRows: 5,
+ mode: "result",
+ result: {
+ titleText: "What are you looking for?",
+ hideDirectionsText: 0
+ }
+ };
+ // Get passed in config from block config.
+ var config = Drupal.settings.searchApiFederatedSolr.block.autocomplete;
+ // Merge defaults with passed in config.
+ var options = Object.assign({}, defaultSettings, config);
+
+ // Set scaffolding markup for suggestions container
+ var suggestionsContainerScaffoldingMarkup = '
Press ENTER to search for your current term or ESC to close.Press \u2191 and \u2193 to highlight a suggestion then ENTER to be redirected to that suggestion.
';
+
+ // Cache selectors.
+ var $input = $(this);
+ var $form = $("#federated-search-page-block-form");
+
+ // Set up input with attributes, suggestions scaffolding.
+ $input.attr("role", "combobox").attr("aria-owns", "res").attr("aria-autocomplete", "list").attr("aria-expanded", "false");
+ $(suggestionsContainerScaffoldingMarkup).insertAfter($input);
+
+ // Cache inserted selectors.
+ var $results = $("#res");
+ var $autocompleteContainer = $(".js-search-autocomplete-container");
+ var $closeButton = $(
+ ".js-search-autocomplete-container__close-button"
+ );
+ var $ariaLive = $("#search-api-federated-solr-autocomplete-aria-live");
+
+ // Initiate helper vars.
+ var current;
+ var counter = 1;
+ var keys = {
+ ESC: 27,
+ TAB: 9,
+ RETURN: 13,
+ UP: 38,
+ DOWN: 40
+ };
+
+ // Determine param values for any set default filters/facets.
+ var defaultParams = "";
+ $('input[type="hidden"]', $form).each(function(index, input) {
+ var fq = $(input).attr("name") + ':("' + $(input).val() + '")';
+ defaultParams += "&fq=" + encodeURI(fq);
+ });
+
+ // Set the text default query.
+ var urlWithDefaultParams = options.url + defaultParams;
+
+ // Bind events to input.
+ $input.bind("input", function(event) {
+ doSearch(options.suggestionRows);
+ });
+
+ $input.bind("keydown", function(event) {
+ doKeypress(keys, event);
+ });
+
+ // Define event handlers.
+ function doSearch(suggestionRows) {
+ $input.removeAttr("aria-activedescendant");
+ var value = $input.val();
+ // Remove spaces on either end of the value.
+ var trimmed = value.trim();
+ // Default to the trimmed value.
+ var query = trimmed;
+ // If the current value has more than the configured number of characters.
+ if (query.length > options.numChars) {
+ // Append wildcard to the query if configured to do so.
+ if (options.appendWildcard) {
+ // Note: syntax for wildcard query depends on the query endpoint
+ if (options.proxyIsDisabled) {
+ // One method of supporting search-as-you-type is to append a wildcard '*'
+ // to match zero or more additional characters at the end of the users search term.
+ // @see: https://lucene.apache.org/solr/guide/6_6/the-standard-query-parser.html#TheStandardQueryParser-WildcardSearches
+ // @see: https://opensourceconnections.com/blog/2013/06/07/search-as-you-type-with-solr/
+ // Split into word chunks.
+ var words = trimmed.split(" ");
+
+ // If there are multiple chunks, join them with "+", repeat the last word + append "*".
+ if (words.length > 1) {
+ query = words.join("+") + words.pop() + "*";
+ }
+ else {
+ // If there is only 1 word, repeat it an append "*".
+ query = words + "+" + words + "*";
+ }
+ }
+ else {
+ query = trimmed + "*";
+ }
+ }
+
+ // Replace the placeholder with the query value.
+ var url = urlWithDefaultParams.replace(/(\[val\])/gi, query);
+
+ // Set up basic auth if we need it.
+ var xhrFields = {};
+ var headers = {};
+
+ if (options.userpass) {
+ xhrFields = {
+ withCredentials: true
+ };
+ headers = {
+ Authorization: "Basic " + options.userpass
+ };
+ }
+
+ // Make the ajax request
+ $.ajax({
+ xhrFields: xhrFields,
+ headers: headers,
+ url: url,
+ dataType: "json",
+ success: function success(results) {
+ // Currently we only support the response structure from Solr:
+ // {
+ // response: {
+ // docs: [
+ // {
+ // ss_federated_title: ,
+ // ss_url: ,
+ // }
+ // ]
+ // }
+ // }
+ // @todo provide hook for transform function to be passed in
+ // via Drupal.settings then all it here.
+ if (results.response.docs.length >= 1) {
+ // Remove all suggestions
+ $(".js-autocomplete-suggestion").remove();
+ $autocompleteContainer.removeClass("visually-hidden");
+ $("#search-autocomplete").append("");
+ $input.attr("aria-expanded", "true");
+ counter = 1;
+
+ // Bind click event for close button
+ $closeButton.bind("click", function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ $input.removeAttr("aria-activedescendant");
+
+ // Remove all suggestions
+ $(".js-autocomplete-suggestion").remove();
+ $autocompleteContainer.addClass("visually-hidden");
+ $input.attr("aria-expanded", "false");
+ $input.focus();
+
+ // Emit a custom events for removing.
+ $(document).trigger("SearchApiFederatedSolr::block::autocomplete::suggestionsRemoved", [{}]);
+ });
+
+ // Get first [suggestionRows] results
+ var limitedResults = results.response.docs.slice(0, suggestionRows);
+ limitedResults.forEach(function(item) {
+ // Highlight query chars in returned title
+ var pattern = new RegExp(trimmed, "gi");
+ var highlighted = item.ss_federated_title.replace(
+ pattern,
+ function(string) {
+ return "" + string + "";
+ }
+ );
+ // Default the URL to the passed ss_url.
+ var href = item.ss_url;
+ // Ensure that the result returned for the item from solr
+ // (via proxy or directly) is assigned an absolute URL.
+ if (!options.directUrl) {
+ // Initialize url to compute from solr sm_urls array.
+ var sm_url;
+ // Use the canonical url.
+ if (Array.isArray(item.sm_urls)) {
+ sm_url = item.sm_urls[0];
+ }
+ // If no valid urls are passed from solr, skip this item.
+ if (!sm_url) {
+ return;
+ }
+ // Override the current href value.
+ href = sm_url;
+ }
+ //Add results to the list
+ var $suggestionTemplate = "
"
+ );
+ $results.append($suggestionTemplate);
+ counter = counter + 1;
+ });
+
+ // On link click, emit an event whose data can be used to write to analytics, etc.
+ $(".js-autocomplete-suggestion__link").bind("click",
+ function(e) {
+ $(document).trigger("SearchApiFederatedSolr::block::autocomplete::selection",
+ [
+ {
+ referrer: $(location).attr("href"),
+ target: $(this).attr("href"),
+ term: $input.val()
+ }
+ ]
+ );
+ }
+ );
+ // Emit a custom events for results.
+ $(document).trigger("SearchApiFederatedSolr::block::autocomplete::suggestionsLoaded", [{}]);
+ // Announce the number of suggestions.
+ var number = $results.children('[role="option"]').length;
+ if (number >= 1) {
+ $ariaLive.text(number + " suggestions displayed. To navigate use up and down arrow keys.");
+ }
+ }
+ else {
+ // No results, remove suggestions and hide container
+ $(".js-autocomplete-suggestion").remove();
+ $autocompleteContainer.addClass("visually-hidden");
+ $input.attr("aria-expanded", "false"); // Emit a custom events for removing.
+
+ $(document).trigger(
+ "SearchApiFederatedSolr::block::autocomplete::suggestionsRemoved",
+ [{}]
+ );
+ }
+ },
+ error: function error(jqXHR, textStatus, errorThrown) {
+ // No results, remove suggestions and hide container
+ $(".js-autocomplete-suggestion").remove();
+ $autocompleteContainer.addClass("visually-hidden");
+ $input.attr("aria-expanded", "false"); // Emit a custom events for removing.
+
+ $(document).trigger(
+ "SearchApiFederatedSolr::block::autocomplete::suggestionsRemoved",
+ [{}]
+ );
+ }
+ });
+ }
+ else {
+ // Remove suggestions and hide container
+ $(".js-autocomplete-suggestion").remove();
+ $autocompleteContainer.addClass("visually-hidden");
+ $input.attr("aria-expanded", "false"); // Emit a custom events for removing.
+
+ $(document).trigger(
+ "SearchApiFederatedSolr::block::autocomplete::suggestionsRemoved",
+ [{}]
+ );
+ }
+ }
+
+ function doKeypress(keys, event) {
+ var $suggestions = $(".js-autocomplete-suggestion");
+ var highlighted = false;
+ highlighted = $results.children("div").hasClass("highlight");
+
+ switch (event.which) {
+ case keys.ESC:
+ event.preventDefault();
+ event.stopPropagation();
+ $input.removeAttr("aria-activedescendant");
+ $suggestions.remove();
+ $autocompleteContainer.addClass("visually-hidden");
+ $input.attr("aria-expanded", "false");
+ break;
+
+ case keys.TAB:
+ $input.removeAttr("aria-activedescendant");
+ $suggestions.remove();
+ $autocompleteContainer.addClass("visually-hidden");
+ $input.attr("aria-expanded", "false");
+ break;
+
+ case keys.RETURN:
+ if (highlighted) {
+ event.preventDefault();
+ event.stopPropagation();
+ return selectOption(highlighted, $(".highlight").find("a").attr("href"));
+ }
+ else {
+ $form.submit();
+ return false;
+ }
+
+ break;
+
+ case keys.UP:
+ event.preventDefault();
+ event.stopPropagation();
+ return moveUp(highlighted);
+ break;
+
+ case keys.DOWN:
+ event.preventDefault();
+ event.stopPropagation();
+ return moveDown(highlighted);
+ break;
+
+ default:
+ return;
+ }
+ }
+
+ function moveUp(highlighted) {
+ $input.removeAttr("aria-activedescendant");
+ var ariaLiveText = '';
+ // if highlighted exists and if the highlighted item is not the first option
+ if (highlighted && !$results.children().first("div").hasClass("highlight")) {
+ removeCurrent();
+ current.prev("div").addClass("highlight").attr("aria-selected", true);
+ $input.attr("aria-activedescendant", current.prev("div").attr("id"));
+ ariaLiveText = current.prev("div").text();
+ $ariaLive.text(ariaLiveText);
+ }
+ else {
+ // Go to bottom of list
+ removeCurrent();
+ current = $results.children().last("div");
+ current.addClass("highlight").attr("aria-selected", true);
+ $input.attr("aria-activedescendant", current.attr("id"));
+ ariaLiveText = current.text();
+ $ariaLive.text(ariaLiveText);
+ }
+ }
+
+ function moveDown(highlighted) {
+ $input.removeAttr("aria-activedescendant");
+ var ariaLiveText = '';
+ // if highlighted exists and if the highlighted item is not the last option
+ if (highlighted && !$results.children().last("div").hasClass("highlight")) {
+ removeCurrent();
+ current.next("div").addClass("highlight").attr("aria-selected", true);
+ $input.attr("aria-activedescendant", current.next("div").attr("id"));
+ ariaLiveText = current.next("div").text();
+ $ariaLive.text(ariaLiveText);
+ }
+ else {
+ // Go to top of list
+ removeCurrent();
+ current = $results.children().first("div");
+ current.addClass("highlight").attr("aria-selected", true);
+ $input.attr("aria-activedescendant", current.attr("id"));
+ ariaLiveText = current.text();
+ $ariaLive.text(ariaLiveText);
+ }
+ }
+
+ function removeCurrent() {
+ current = $results.find(".highlight");
+ current.attr("aria-selected", false);
+ current.removeClass("highlight");
+ }
+
+ function selectOption(highlighted, href) {
+ if (highlighted && href) {
+ // @todo add logic for non-link suggestions
+ // Emit an event whose data can be used to write to analytics, etc.
+ $(document).trigger("SearchApiFederatedSolr::block::autocomplete::selection",
+ [
+ {
+ referrer: $(location).attr("href"),
+ target: href,
+ term: $input.val()
+ }
+ ]
+ );
+ // Redirect to the selected link.
+ $(location).attr("href", href);
+ }
+ else {
+ return;
+ }
+ }
+ });
+ }
+ };
+ Drupal.SearchApiFederatedSolrAutocomplete = autocomplete;
+})(jQuery);
diff --git a/search_api_federated_solr.admin.inc b/search_api_federated_solr.admin.inc
new file mode 100644
index 000000000..ee0064c91
--- /dev/null
+++ b/search_api_federated_solr.admin.inc
@@ -0,0 +1,521 @@
+machine_name] = $index->name;
+ $settings[$index->machine_name] = $index->options;
+ }
+
+ $form['#prefix'] = '
';
+ $form['#suffix'] = '
';
+
+ $form['setup'] = [
+ '#type' => 'fieldset',
+ '#title' => 'Search Results Page > Set Up',
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ ];
+
+ $form['setup']['search_api_federated_solr_path'] = [
+ '#type' => 'textfield',
+ '#title' => t('Search app path'),
+ '#default_value' => variable_get('search_api_federated_solr_path', 'search-app'),
+ '#description' => t('The path for the search app (Default: "search-app").'),
+ ];
+
+ $form['setup']['search_api_federated_solr_search_index'] = [
+ '#type' => 'select',
+ '#title' => t('Search API index'),
+ '#description' => t('Defines which search_api index and server the search app should use.'),
+ '#options' => $indexes,
+ '#default_value' => variable_get('search_api_federated_solr_search_index'),
+ '#required' => TRUE,
+ '#ajax' => [
+ 'callback' => 'get_site_name',
+ 'wrapper' => 'search-api-federated-solr-config-form',
+ ],
+ ];
+
+ $form['setup']['search_api_federated_solr_disable_query_proxy'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Do not use the proxy for the search query') . '',
+ '#default_value' => variable_get('search_api_federated_solr_disable_query_proxy'),
+ '#description' => t('Check this box to configure the search app to query the Solr server directly. When checked, it is highly recommended that you also procure and configure read-only basic auth credentials for the search app. When unchecked, this site will act as a proxy for requests to the Solr server of the chosen Search API index using the Drupal route defined by this module.
Note: Acquia Search customers must leave this box unchecked.'),
+ '#attributes' => [
+ 'data-direct-query-enabler' => TRUE,
+ ],
+ ];
+
+ $form['setup']['search_api_federated_solr_search_index_basic_auth'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Search Index Basic Authentication'),
+ '#description' => t('If your Solr server is protected by basic HTTP authentication, enter the login data here. These credentials will be accessible to the client in an obscured, but non-secure method. It should, therefore, only provide read access to the index AND be different from that provided when configuring the server in Search API. The Password field is intentionally not obscured to emphasize this distinction.'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-direct-query-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['setup']['search_api_federated_solr_search_index_basic_auth']['search_api_federated_solr_search_index_basic_auth_username'] = [
+ '#type' => 'textfield',
+ '#title' => t('Username'),
+ '#default_value' => variable_get('search_api_federated_solr_search_index_basic_auth_username'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-direct-query-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['setup']['search_api_federated_solr_search_index_basic_auth']['search_api_federated_solr_search_index_basic_auth_password'] = [
+ '#type' => 'textfield',
+ '#title' => t('Password'),
+ '#default_value' => variable_get('search_api_federated_solr_search_index_basic_auth_password'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-direct-query-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ /**
+ * Search results page options:
+ * - show empty search results (i.e. filterable listing page),
+ * - customize "no results" text
+ * - custom search prompt
+ * - renders in result area when show empty results no enabled and no query value
+ * - max number of search results per page
+ * - max number of "numbered" pagination buttons to show
+ */
+
+ $form['search_page_options'] = [
+ '#type' => 'fieldset',
+ '#title' => 'Search Results Page > Options',
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ ];
+
+ $form['search_page_options']['search_api_federated_solr_show_empty_search_results'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Show results for empty search'),
+ '#default_value' => variable_get('search_api_federated_solr_show_empty_search_results'),
+ '#description' => t('When checked, this option allows users to see all results when no search term is entered. By default, empty searches are disabled and yield no results.'),
+ ];
+
+ $form['search_page_options']['search_api_federated_solr_no_results_text'] = [
+ '#type' => 'textfield',
+ '#title' => t('No results text'),
+ '#default_value' => variable_get('search_api_federated_solr_no_results_text'),
+ '#description' => t('This text is shown when a query returns no results. (Default: "Your search yielded no results.")'),
+ ];
+
+ $form['search_page_options']['search_api_federated_solr_search_prompt_text'] = [
+ '#type' => 'textfield',
+ '#title' => t('Search prompt text'),
+ '#default_value' => variable_get('search_api_federated_solr_search_prompt_text'),
+ '#description' => t('This text is shown when no query term has been entered. (Default: "Please enter a search term.")'),
+ ];
+
+ $form['search_page_options']['search_api_federated_solr_rows'] = [
+ '#type' => 'textfield',
+ '#attributes' => array(
+ ' type' => 'number',
+ ),
+ '#title' => t('Number of search results per page'),
+ '#default_value' => variable_get('search_api_federated_solr_rows'),
+ '#description' => t('The max number of results to render per search results page. (Default: 20)'),
+ ];
+
+ $form['search_page_options']['search_api_federated_solr_page_buttons'] = [
+ '#type' => 'textfield',
+ '#attributes' => array(
+ ' type' => 'number',
+ ),
+ '#title' => t('Number of pagination buttons'),
+ '#default_value' => variable_get('search_api_federated_solr_page_buttons'),
+ '#description' => t('The max number of numbered pagination buttons to show at a given time. (Default: 5)'),
+ ];
+
+ /**
+ * Settings and values for search facets and filters:
+ * - set the site name facet to the current site name property
+ */
+
+ $form['search_form_values'] = [
+ '#type' => 'fieldset',
+ '#title' => 'Search Results Page > Facets & Filters',
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ ];
+
+ /**
+ * Set hidden form element value based on presence of field properties on
+ * the selected index. This value will determine which inputs are
+ * visible for setting default facet/filter values and hiding in the UI.
+ */
+ $form['search_form_values']['defaults'] = [
+ '#type' => 'fieldset',
+ '#title' => 'Set facet / filter default values'
+ ];
+
+ $form['search_form_values']['defaults']['search_api_federated_solr_set_search_site'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Set the "Site name" facet to this site'),
+ '#default_value' => variable_get('search_api_federated_solr_set_search_site'),
+ '#description' => t('When checked, only search results from this site will be shown, by default, until this site\'s checkbox is unchecked in the search app\'s "Site name" facet. This feature works best when the proxy server is enabled.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="search_api_federated_solr_has_site_name_property"]' => [
+ 'value' => "true"
+ ],
+ ],
+ ],
+ ];
+
+ if (!empty(variable_get('search_api_federated_solr_search_index'))) {
+ $index = variable_get('search_api_federated_solr_search_index');
+ if (isset($settings[$index])) {
+ $fields = $settings[$index]['fields'];
+ }
+ }
+
+ $form['search_form_values']['search_api_federated_solr_has_site_name_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['site-name-property'],
+ ],
+ '#default_value' => isset($fields['site_name']) ? 'true' : variable_get('search_api_federated_solr_has_site_name_property'),
+ ];
+
+ $form['search_form_values']['search_api_federated_solr_has_federated_date_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['date-property'],
+ ],
+ '#value' => isset($fields['federated_date']) ? 'true' : variable_get('search_api_federated_solr_has_federated_date_property', 'true'),
+ ];
+
+ $form['search_form_values']['search_api_federated_solr_has_federated_type_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['type-property'],
+ ],
+ '#value' => isset($fields['federated_type']) ? 'true' : variable_get('search_api_federated_solr_has_federated_type_property', 'true'),
+ ];
+
+ $form['search_form_values']['search_api_federated_solr_has_federated_terms_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['terms-property'],
+ ],
+ '#value' => isset($fields['federated_terms']) ? 'true' : variable_get('search_api_federated_solr_has_federated_terms_property', 'true'),
+ ];
+
+ /**
+ * Enable hiding available facets / filters.
+ * These form elements will only be visible if their corresopnding
+ * property exists on the index.
+ */
+ $form['search_form_values']['hidden'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Hide facets / filters from sidebar'),
+ '#description' => t('The checked facets / filters will be hidden from the search app.'),
+ ];
+
+ $form['search_form_values']['hidden']['search_api_federated_solr_hide_site_name'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Site name facet'),
+ '#default_value' => variable_get('search_api_federated_solr_hide_site_name'),
+ '#description' => t('When checked, the ability to select which sites should be included in the results will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="site_name_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ $form['search_form_values']['hidden']['search_api_federated_solr_hide_type'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Type facet'),
+ '#default_value' => variable_get('search_api_federated_solr_hide_type'),
+ '#description' => t('When checked, the ability to select those types (i.e. bundles) which should have results returned will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="type_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ $form['search_form_values']['hidden']['search_api_federated_solr_hide_date'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Date filter'),
+ '#default_value' => variable_get('search_api_federated_solr_hide_date'),
+ '#description' => t('When checked, the ability to filter results by date will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="date_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ $form['search_form_values']['hidden']['search_api_federated_solr_hide_terms'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Terms facet'),
+ '#default_value' => variable_get('search_api_federated_solr_hide_terms'),
+ '#description' => t('When checked, the ability to select those terms which should have results returned will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="terms_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ /**
+ * Autocomplete settings:
+ * - endpoint URL
+ * - use wildcard to support partial terms
+ * - customize number of autocomplete results
+ * - number of characters after which autocomplete query should be executed
+ * - autocomplete results mode (search results, terms)
+ * - title for autocomplete results
+ * - show/hide autocomplete keyboard directions
+ */
+
+ $form['autocomplete'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Search Results Page > Search Form > Autocomplete'),
+ '#description' => '
' . t('These options apply to the autocomplete functionality on the search for which appears above the search results on the search results page. Configure your placed Federated Search Page Form block to add autocomplete to that form.') . '
',
+ '#collapsible' => TRUE,
+ '#collapsed' => !variable_get('search_api_federated_solr_autocomplete_is_enabled'),
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_is_enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Enable autocomplete for the search results page search form') . '',
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_is_enabled'),
+ '#description' => t('Checking this will expose more configuration options for autocomplete behavior for the search form on the Search Results page at the end of this form.'),
+ '#attributes' => [
+ 'id' => ['autocomplete-is-enabled'],
+ ],
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_is_append_wildcard'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Append a wildcard \'*\' to support partial text search') . '',
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_is_append_wildcard'),
+ '#description' => t('Check this box to append a wildcard * to the end of the autocomplete query term (i.e. "car" becomes "car+car*"). This option is recommended if your solr config does not add a field(s) with NGram Tokenizers to your index or if your autocomplete Request Handler is not configured to search those fields.'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_disable_query_proxy'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Do not use the proxy for the search app autocomplete query') . '',
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_disable_query_proxy', 0),
+ '#description' => t('Check this box to configure the search app to query the Solr server directly. When checked, it is highly recommended that you also procure and configure read-only basic auth credentials for the search app. When unchecked, this site will act as a proxy for requests to the Solr server of the Search API index chosen above in Search Results Page > Set Up using the Drupal route defined by this module.
Note: Acquia Search customers must leave this box unchecked.'),
+ '#attributes' => [
+ 'data-autocomplete-direct-query-enabler' => TRUE,
+ ],
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['direct'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Autocomplete Direct Query Settings'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-direct-query-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['direct']['search_api_federated_solr_autocomplete_url'] = [
+ '#type' => 'textfield',
+ '#title' => t('Solr Endpoint URL'),
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_url'),
+ '#maxlength' => 2048,
+ '#size' => 50,
+ '#description' => t('The URL where requests for autocomplete queries should be made. (Default: the url of the selectRequest Handler on the server of the selected Search API index.)
Supports an absolute url pattern to any other Request Handler for an index on your solr server
The value of the main search field will be appended to the url as the main query param (i.e. ?q=[value of the search field, wildcard appended if enabled])
Any facet/filter default values set for the search app will automatically be appended (i.e. &sm_site_name=[value of the site name for the index])
The format param &wt=json will automatically be appended
Include any other necessary url params corresponding to query parameters.
' . t('If your Solr server is protected by basic HTTP authentication (highly recommended), enter the login data here. These credentials will be accessible to the client in an obscured, but non-secure method. It should, therefore, only provide read access to the index AND be different from that provided when configuring the server in Search API. The Password field is intentionally not obscured to emphasize this distinction.') . '
',
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-direct-query-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['direct']['basic_auth']['search_api_federated_solr_autocomplete_use_search_app_creds'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Use credentials provided for Search Index Basic Authentication in Search Results Page > Set Up above'),
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_use_search_app_creds'),
+ '#attributes' => [
+ 'data-autocomplete-use-search-app-creds' => TRUE,
+ ],
+ ];
+
+ $form['autocomplete']['direct']['basic_auth']['search_api_federated_solr_autocomplete_username'] = [
+ '#type' => 'textfield',
+ '#title' => t('Username'),
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_username'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-use-search-app-creds]' => [
+ 'checked' => FALSE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['direct']['basic_auth']['search_api_federated_solr_autocomplete_password'] = [
+ '#type' => 'textfield',
+ '#title' => t('Password'),
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_password'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-use-search-app-creds]' => [
+ 'checked' => FALSE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_suggestion_rows'] = [
+ '#type' => 'textfield',
+ '#title' => t('Number of results'),
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_suggestion_rows'),
+ '#description' => t('The max number of results to render in the autocomplete results dropdown. (Default: 5)'),
+ '#size' => 5,
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_num_chars'] = [
+ '#type' => 'textfield',
+ '#title' => t('Number of characters after which autocomplete query should execute'),
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_num_chars'),
+ '#description' => t('Autocomplete query will be executed after a user types this many characters in the search query field. (Default: 2)'),
+ '#size' => 5,
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $autocomplete_mode = variable_get('search_api_federated_solr_autocomplete_mode') ?: 'result' ;
+ $title_text_config_key = 'search_api_federated_solr_autocomplete_' . $autocomplete_mode . '_title_text';
+ $hide_directions_text_config_key = 'search_api_federated_solr_autocomplete_' . $autocomplete_mode . '_hide_directions_text';
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_mode'] = [
+ '#type' => 'select',
+ '#title' => t('Autocomplete mode'),
+ '#description' => t('Type of results the autocomplete response returns: search results (default) or search terms.'),
+ '#options' => [
+ 'result' => t('Search results (i.e. search as you type functionality)'),
+ 'Search terms (coming soon)' => [],
+ ],
+ '#default_value' => $autocomplete_mode || 'result',
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete'][$title_text_config_key] = [
+ '#type' => 'textfield',
+ '#title' => t('Results title text'),
+ '#size' => 50,
+ '#default_value' => $autocomplete_mode ? variable_get($title_text_config_key) : '',
+ '#description' => t('The title text is shown above the results in the autocomplete drop down. (Default: "What are you interested in?")'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete'][$hide_directions_text_config_key] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Hide keyboard directions') . '',
+ '#default_value' => $autocomplete_mode ? variable_get($hide_directions_text_config_key) : 0,
+ '#description' => t('Check this box to make hide the autocomplete keyboard usage directions in the results dropdown. For sites that want to maximize their accessibility UX for sighted keyboard users, we recommend leaving this unchecked. (Default: directions are visible)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['#cache'] = ['max-age' => 0];
+
+ return system_settings_form($form);
+}
diff --git a/search_api_federated_solr.drush.inc b/search_api_federated_solr.drush.inc
new file mode 100644
index 000000000..b508f48ef
--- /dev/null
+++ b/search_api_federated_solr.drush.inc
@@ -0,0 +1,104 @@
+ 'List all fields, as they map to solr machine names, for a given index.',
+ 'examples' => array(
+ 'drush sapifs-f' => dt('List all fields, as they map to solr machine names, for a given index.'),
+ ),
+ 'aliases' => array('sapifs-f'),
+ );
+
+ return $items;
+}
+
+
+/**
+ * List all fields, as they map to solr machine names, for a given index.
+ */
+function drush_search_api_federated_solr_fields() {
+ if (search_api_federated_solr_drush_static(__FUNCTION__)) {
+ return;
+ }
+
+ $search_index = variable_get('search_api_federated_solr_search_index');
+ // Get the index entity.
+ /** @var \SearchApiIndex $index */
+ $index = search_api_index_load($search_index);
+ if (!$index) {
+ drush_print(dt('The search app index could not be loaded.'));
+ return;
+ }
+ // Get server entity.
+ /** @var \SearchApiServer $server */
+ try {
+ $server = $index->server();
+ }
+ catch (SearchApiException $e) {
+ drush_print('The search server could not be loaded.');
+ return;
+ }
+ // Get the search api solr service class.
+ /** @var \SearchApiSolrService $solr */
+ $solr = new SearchApiSolrService($server);
+
+ // If this is an acquia environment and if it is an acquia search server
+ // get the Acquia Search Service instead.
+ if (isset($_ENV['AH_SITE_ENVIRONMENT']) && module_exists('search_api_acquia') && $server->class === 'acquia_search_service') {
+ /** @var \SearchApiAcquiaSearchService $solr */
+ $solr = new SearchApiAcquiaSearchService($server);
+ }
+
+ // Get the index field names as they map to solr names.
+ $field_names = $solr->getFieldNames($index);
+
+ $rows[] = array(
+ dt('Index Field Machine Name'),
+ dt('| Solr Field Machine Name'),
+ );
+
+ $rows[] = array(
+ dt('-------------------------'),
+ dt('| -----------------------'),
+ );
+
+ foreach ($field_names as $key => $value) {
+ $row = array(
+ $key,
+ '| ' . $value,
+ );
+ $rows[] = $row;
+ }
+ drush_print_table($rows);
+}
+
+/**
+ * Does a static lookup to prevent Drush 4 from running twice.
+ *
+ * @param string $function
+ * The Drush function being called.
+ *
+ * @return bool
+ * TRUE if the function was already called in this Drush execution, FALSE
+ * otherwise.
+ *
+ * @see http://drupal.org/node/704848
+ */
+function search_api_federated_solr_drush_static($function) {
+ static $index = array();
+ if (isset($index[$function])) {
+ return TRUE;
+ }
+ $index[$function] = TRUE;
+ return FALSE;
+}
diff --git a/search_api_federated_solr.info b/search_api_federated_solr.info
index 2b861297b..292e3bbf2 100644
--- a/search_api_federated_solr.info
+++ b/search_api_federated_solr.info
@@ -1,4 +1,4 @@
-; @copyright (c) Copyright 2018 Palantir.net
+; @copyright (c) Copyright 2018-19 Palantir.net
name = Search API Federated Solr
description = Allows indexing multiple Drupal sites into a single Solr search index.
diff --git a/search_api_federated_solr.install b/search_api_federated_solr.install
index 4dd51f994..1c2c64032 100644
--- a/search_api_federated_solr.install
+++ b/search_api_federated_solr.install
@@ -18,3 +18,26 @@ function search_api_federated_solr_field_schema($field) {
),
);
}
+
+/**
+ * Sets default values on installation.
+ */
+function search_api_federated_solr_install() {
+ variable_set('search_api_federated_solr_query_fields', ['rendered_item']);
+}
+
+/**
+ * Update hook to install new defaults.
+ */
+function search_api_federated_solr_update_7100(&$sandbox) {
+ variable_set('search_api_federated_solr_query_fields', ['rendered_item']);
+}
+
+/**
+ * Removes variables on uninstall.
+ */
+function search_api_federated_solr_uninstall() {
+ foreach (search_api_federated_solr_variables() as $var) {
+ variable_del($var);
+ }
+}
diff --git a/search_api_federated_solr.module b/search_api_federated_solr.module
index 672a7215c..264f8ca9b 100644
--- a/search_api_federated_solr.module
+++ b/search_api_federated_solr.module
@@ -4,7 +4,7 @@
* @file search_api_federated_solr.module
* Contains hook implementations for the Federated Solr Search API Module.
*
- * @copyright Copyright (c) 2018 Palantir.net
+ * @copyright Copyright (c) 2018-19 Palantir.net
*/
/**
@@ -21,6 +21,40 @@ function search_api_federated_solr_help($path, $arg) {
}
}
+/**
+ * Implements hook_menu().
+ */
+function search_api_federated_solr_menu() {
+ $search_path = variable_get('search_api_federated_solr_path', 'search-app');
+
+ $items[$search_path] = array(
+ 'title' => 'Search',
+ 'page callback' => 'page_search_api_federated_solr',
+ 'access arguments' => array('use federated search'),
+ 'file' => 'search_api_federated_solr.page.inc',
+ );
+
+ $items['admin/config/search/federated-search-settings'] = array(
+ 'title' => 'Federated Search App',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_api_federated_solr_admin'),
+ 'access arguments' => array('administer federated search'),
+ 'type' => MENU_NORMAL_ITEM,
+ 'file' => 'search_api_federated_solr.admin.inc',
+ 'description' => 'Configure Federated Search application settings',
+ );
+
+ $items['search-api-federated-solr/search'] = array(
+ 'title' => 'Federated Search App',
+ 'page callback' => 'search_api_federated_solr_proxy',
+ 'access arguments' => array('use federated search'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'search_api_federated_solr.proxy.inc',
+ );
+
+ return $items;
+}
+
/**
* Implements hook_search_api_alter_callback_info().
*/
@@ -54,42 +88,6 @@ function search_api_federated_solr_search_api_alter_callback_info() {
return $callbacks;
}
-/**
- * Implements hook_menu().
- */
-function search_api_federated_solr_menu() {
- $search_path = variable_get('search_api_federated_solr_path', 'search-app');
-
- $items[$search_path] = array(
- 'title' => 'Search',
- 'page callback' => 'page_search_api_federated_solr',
- 'access arguments' => array('use federated search'),
- );
-
- $items['admin/config/search/federated-search-settings'] = array(
- 'title' => 'Search API Federated Solr: Search App Settings',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('search_api_federated_solr_admin'),
- 'access arguments' => array('administer federated search'),
- 'type' => MENU_NORMAL_ITEM,
- );
-
- return $items;
-}
-
-/**
-* Constructs the search page.
-*/
-function page_search_api_federated_solr() {
- $data_federated_search_app_config = search_api_federated_solr_config_json();
- $element = [];
- $element['#markup'] = '
Federated Solr Search App: If you see this message in your DevTools, it likely means there is an issue adding the app javascript library to this page. Follow the steps in the search_api_federated_solr module README.
';
- $element['#attached']['library'][] = ['search_api_federated_solr', 'search-app'];
-
- return $element;
-}
-
-
/**
* Implements hook_block_info().
*/
@@ -115,6 +113,258 @@ function search_api_federated_solr_block_view($delta = '') {
return $block;
}
+/**
+ * Configuration options for the block.
+ */
+function search_api_federated_solr_block_configure($delta = '') {
+ if ($delta != 'federated_search_page_form_block') {
+ return;
+ }
+ $form = [];
+ /**
+ * Autocomplete settings:
+ * - endpoint URL
+ * - use wildcard to support partial terms
+ * - customize number of autocomplete results
+ * - number of characters after which autocomplete query should be executed
+ * - autocomplete results mode (search results, terms)
+ * - title for autocomplete results
+ * - show/hide autocomplete keyboard directions
+ */
+
+ $form['autocomplete'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Search Results Page > Search Form > Autocomplete'),
+ '#description' => '
' . t('These options apply to the autocomplete functionality on the search for which appears above the search results on the search results page. Configure your placed Federated Search Page Form block to add autocomplete to that form.') . '
',
+ '#collapsible' => TRUE,
+ '#collapsed' => !variable_get('search_api_federated_solr_autocomplete_block_is_enabled'),
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_block_is_enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Enable autocomplete for the search results page search form') . '',
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_is_enabled'),
+ '#description' => t('Checking this will expose more configuration options for autocomplete behavior for the search form on the Search Results page at the end of this form.'),
+ '#attributes' => [
+ 'id' => ['autocomplete-is-enabled'],
+ ],
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_block_is_append_wildcard'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Append a wildcard \'*\' to support partial text search') . '',
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_is_append_wildcard'),
+ '#description' => t('Check this box to append a wildcard * to the end of the autocomplete query term (i.e. "car" becomes "car+car*"). This option is recommended if your solr config does not add a field(s) with NGram Tokenizers to your index or if your autocomplete Request Handler is not configured to search those fields.'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['search_api_federated_solr_autocomplete_block_disable_query_proxy'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . t('Do not use the proxy for the search app autocomplete query') . '',
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_disable_query_proxy', 0),
+ '#description' => t('Check this box to configure the block search form to query the Solr server directly. When checked, it is highly recommended that you also procure and configure read-only basic auth credentials for the search app. When unchecked, this site will act as a proxy for requests to the Solr server of the Search API index chosen on the Federated Search App settings page in Search Results Page > Set Up using the Drupal route defined by this module.
Note: Acquia Search customers must either leave this box unchecked or check the box and enter the URL for a view REST export endpoint. Using a url pointing directly to your Solr backend will not work.',
+ ['@url' => url('admin/config/search/federated-search-settings')]
+ ),
+ '#attributes' => [
+ 'data-autocomplete-direct-query-enabler' => TRUE,
+ ],
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['direct'] = [
+ '#type' => 'fieldset',
+ '#title' => t('Autocomplete Direct Query Settings'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-direct-query-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['direct']['search_api_federated_solr_autocomplete_block_url'] = [
+ '#type' => 'textfield',
+ '#title' => t('Solr Endpoint URL'),
+ '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_url'),
+ '#maxlength' => 2048,
+ '#size' => 50,
+ '#description' => t('The URL where requests for autocomplete queries should be made. (Default: the url of the selectRequest Handler on the server of the selected Search API index.)
Supports an absolute url pattern to any other Request Handler for an index on your solr server
The value of the main search field will be appended to the url as the main query param (i.e. ?q=[value of the search field, wildcard appended if enabled])
Any facet/filter default values set for the search app will automatically be appended (i.e. &sm_site_name=[value of the site name for the index])
The format param &wt=json will automatically be appended
Include any other necessary url params corresponding to query parameters.
' . t('If your Solr server is protected by basic HTTP authentication (highly recommended), enter the login data here. These credentials will be accessible to the client in an obscured, but non-secure method. It should, therefore, only provide read access to the index AND be different from that provided when configuring the server in Search API. The Password field is intentionally not obscured to emphasize this distinction.') . '
';
-
- $form['setup'] = [
- '#type' => 'fieldset',
- '#title' => 'Search Results Page > Set Up',
- '#collapsible' => TRUE,
- '#collapsed' => FALSE,
- ];
-
- $form['setup']['search_api_federated_solr_path'] = [
- '#type' => 'textfield',
- '#title' => t('Search app path'),
- '#default_value' => variable_get('search_api_federated_solr_path', 'search-app'),
- '#description' => t('The path for the search app (Default: "search-app").'),
- ];
-
- $form['setup']['search_api_federated_solr_search_index'] = [
- '#type' => 'select',
- '#title' => t('Search API index'),
- '#description' => t('Defines which search_api index and server the search app should use.'),
- '#options' => $indexes,
- '#default_value' => variable_get('search_api_federated_solr_search_index'),
- '#required' => TRUE,
- '#ajax' => [
- 'callback' => 'get_site_name',
- 'wrapper' => 'search-api-federated-solr-config-form',
- ],
- ];
-
- $form['search_api_federated_solr_has_site_name_property'] = [
- '#type' => 'hidden',
- '#default_value' => variable_get('search_api_federated_solr_has_site_name_property') ? 'true' : NULL,
- ];
-
- $form['search_api_federated_solr_set_search_site'] = [
- '#type' => 'checkbox',
- '#title' => t('Set the "Site name" facet to this site'),
- '#default_value' => variable_get('search_api_federated_solr_set_search_site'),
- '#description' => t('When checked, only search results from this site will be shown, by default, until this site\'s checkbox is unchecked in the search app\'s "Site name" facet.'),
- '#states' => [
- 'visible' => [
- ':input[name="search_api_federated_solr_has_site_name_property"]' => [
- 'value' => "true"
- ],
- ],
- ],
- ];
-
- $form['setup']['search_api_federated_solr_search_index_basic_auth'] = [
- '#type' => 'fieldset',
- '#title' => t('Search Index Basic Authentication'),
- '#description' => t('If your Solr server is protected by basic HTTP authentication, enter the login data here. This will be accessible to the client in an obscured, but non-secure method. It should, therefore, only provide read access to the index AND be different from that provided when configuring the server in Search API. The Password field is intentionally not obscured to emphasize this distinction.')
- ];
-
- $form['setup']['search_api_federated_solr_search_index_basic_auth']['search_api_federated_solr_search_index_basic_auth_username'] = [
- '#type' => 'textfield',
- '#title' => t('Username'),
- '#default_value' => variable_get('search_api_federated_solr_search_index_basic_auth_username'),
- ];
-
- $form['setup']['search_api_federated_solr_search_index_basic_auth']['search_api_federated_solr_search_index_basic_auth_password'] = [
- '#type' => 'textfield',
- '#title' => t('Password'),
- '#default_value' => variable_get('search_api_federated_solr_search_index_basic_auth_password'),
- ];
- $form['setup']['search_api_federated_solr_autocomplete_is_enabled'] = [
- '#type' => 'checkbox',
- '#title' => '' . t('Enable autocomplete for the search results page search form') . '',
- '#default_value' => variable_get('search_api_federated_solr_autocomplete_is_enabled'),
- '#description' => t('Checking this will expose more configuration options for autocomplete behavior for the search form on the Search Results page at the end of this form.'),
- '#attributes' => [
- 'id' => ['autocomplete-is-enabled'],
- ],
- ];
-
- $form['search_api_federated_solr_show_empty_search_results'] = [
- '#type' => 'checkbox',
- '#title' => t('Show results for empty search'),
- '#default_value' => variable_get('search_api_federated_solr_show_empty_search_results'),
- '#description' => t('When checked, this option allows users to see all results when no search term is entered. By default, empty searches are disabled and yield no results.'),
- ];
-
- $form['search_api_federated_solr_no_results_text'] = [
- '#type' => 'textfield',
- '#title' => t('No results text'),
- '#default_value' => variable_get('search_api_federated_solr_no_results_text'),
- '#description' => t('This text is shown when a query returns no results. (Default: "Your search yielded no results.")'),
- ];
-
- $form['search_api_federated_solr_search_prompt_text'] = [
- '#type' => 'textfield',
- '#title' => t('Search prompt text'),
- '#default_value' => variable_get('search_api_federated_solr_search_prompt_text'),
- '#description' => t('This text is shown when no query term has been entered. (Default: "Please enter a search term.")'),
- ];
-
- $form['search_api_federated_solr_rows'] = [
- '#type' => 'textfield',
- '#attributes' => array(
- ' type' => 'number',
- ),
- '#title' => t('Number of search results per page'),
- '#default_value' => variable_get('search_api_federated_solr_rows'),
- '#description' => t('The max number of results to render per search results page. (Default: 20)'),
- ];
-
- $form['search_api_federated_solr_page_buttons'] = [
- '#type' => 'textfield',
- '#attributes' => array(
- ' type' => 'number',
- ),
- '#title' => t('Number of pagination buttons'),
- '#default_value' => variable_get('search_api_federated_solr_page_buttons'),
- '#description' => t('The max number of numbered pagination buttons to show at a given time. (Default: 5)'),
- ];
-
- $form['#cache'] = ['max-age' => 0];
-
- return system_settings_form($form);
-}
-
/**
* Create search_api_federated_solr_form_alter to validate the search path format.
*/
function search_api_federated_solr_form_search_api_federated_solr_admin_alter(&$form, &$form_state, $form_id) {
$form['#validate'][] = '_path_form_validate';
+ $form['#validate'][] = '_direct_url_form_validate';
}
function _path_form_validate($form, &$form_state) {
$form_state['values']['search_api_federated_solr_path'] = trim($form_state['values']['search_api_federated_solr_path'], '/');
}
+/**
+ * Ensure that valid URL is passed to autocomplete endpoint setting.
+ */
+function _direct_url_form_validate($form, &$form_state) {
+ // Check if URL is valid if proxy is disabled and url field is populated.
+ if (array_key_exists('search_api_federated_solr_autocomplete_url', $form_state['values']) && $form_state['values']['search_api_federated_solr_autocomplete_url'] && $form_state['values']['search_api_federated_solr_autocomplete_disable_query_proxy']) {
+ $is_external = url_is_external($form_state['values']['search_api_federated_solr_autocomplete_url']);
+ if (!valid_url($form_state['values']['search_api_federated_solr_autocomplete_url'], $is_external)) {
+ form_set_error('search_api_federated_solr_autocomplete_url', t('Please enter a valid external or internal URL for the autocomplete endpoint.'));
+ }
+ }
+}
+
+/**
+ * Ensure that valid URL is passed to block autocomplete endpoint setting.
+ */
+function search_api_federated_solr_block_validate($form, &$form_state) {
+ // Check if URL is valid if proxy is disabled and url field is populated.
+ if (array_key_exists('search_api_federated_solr_autocomplete_block_url', $form_state['values']) && $form_state['values']['search_api_federated_solr_autocomplete_block_url'] && $form_state['values']['search_api_federated_solr_autocomplete_block_disable_query_proxy']) {
+ $is_external = url_is_external($form_state['values']['search_api_federated_solr_autocomplete_block_url']);
+ if (!valid_url($form_state['values']['search_api_federated_solr_autocomplete_block_url'], $is_external)) {
+ form_set_error('search_api_federated_solr_autocomplete_block_url', t('Please enter a valid external or internal URL for the autocomplete endpoint.'));
+ }
+ }
+}
+
/**
* Ajax callback for search_api_federated_solr_search_index.
*/
@@ -347,74 +525,6 @@ function get_site_name($form, $form_state) {
return $form;
}
-
-/**
- * Create search_api_federated_solr config json endpoint.
- */
-function search_api_federated_solr_config_json() {
- $response_data = [];
-
- $search_index = variable_get('search_api_federated_solr_search_index');
- // Get the index configuration object.
- $index = search_api_index_load($search_index);
- $server = search_api_server_load($index->server);
- $server_url = $server->options['scheme'] . '://' . $server->options['host'] . ':' . $server->options['port'];
- // Check for the non-required server config field data before appending.
- $server_url .= $server->options['path'] ?: '';
- // Append the request handler.
- $server_url .= '/select';
-
- if ($server_url) {
- $response_data['url'] = $server_url;
- }
-
- $basic_auth_username = variable_get('search_api_federated_solr_search_index_basic_auth_username');
- $basic_auth_password = variable_get('search_api_federated_solr_search_index_basic_auth_password');
- if ($basic_auth_username || $basic_auth_password) {
- $response_data['userpass'] = base64_encode($basic_auth_username . ':' . $basic_auth_password);
- }
-
- $is_site_name_property = variable_get('search_api_federated_solr_has_site_name_property');
- $set_default_site = variable_get('search_api_federated_solr_set_search_site');
- if ($is_site_name_property == 'true' && !$set_default_site) {
- variable_set('search_api_federated_solr_set_search_site', 0);
- }
-
- $no_response = variable_get('search_api_federated_solr_no_results_text');
- if ($no_response) {
- $response_data['noResults'] = $no_response;
- }
-
- $show_empty_search_results = variable_get('search_api_federated_solr_show_empty_search_results');
- if ($show_empty_search_results) {
- $response_data['showEmptySearchResults'] = $show_empty_search_results;
- }
-
- $search_prompt = variable_get('search_api_federated_solr_search_prompt_text');
- if ($search_prompt) {
- $response_data['searchPrompt'] = $search_prompt;
- }
-
- $rows = variable_get('search_api_federated_solr_rows');
- if ($rows) {
- $response_data['rows'] = $rows;
- }
-
- $pagination_buttons = variable_get('search_api_federated_solr_page_buttons');
- if ($pagination_buttons) {
- $response_data['paginationButtons'] = $pagination_buttons;
- }
-
- if (function_exists('domain_get_domain')) {
- $domain = domain_get_domain();
- if (isset($domain['path'])) {
- $response_data['hostname'] = parse_url($domain['path'], PHP_URL_HOST) ;
- }
- }
-
- return json_encode($response_data, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
-}
-
/**
* Implements hook_field_info().
*/
@@ -514,13 +624,13 @@ function search_api_federated_solr_library() {
'title' => 'Federated Search App',
'version' => variable_get('css_js_query_string', '0'),
'js' => array(
- 'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v1.0.10/js/main.d41fc3fe.js' => array(
- 'type' => 'external',
+ 'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v2.1.2/js/main.f36175e5.js' => array(
'scope' => 'footer',
+ 'type' => 'external',
),
),
'css' => array(
- 'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v1.0.10/css/main.cf6a58ce.css' => array(
+ 'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v2.1.2/css/main.ec684809.css' => array(
'type' => 'external',
),
),
@@ -565,3 +675,242 @@ function search_api_federated_solr_image_default_styles() {
return $styles;
}
+
+/**
+ * Returns the URL for the active search service.
+ *
+ * @return string URL|NULL if not set.
+ */
+function search_api_federated_solr_get_server_url() {
+ $search_index = variable_get('search_api_federated_solr_search_index');
+ if (empty($search_index)) {
+ return NULL;
+ }
+ // Get the index configuration object.
+ $index = search_api_index_load($search_index);
+ $server = search_api_server_load($index->server);
+ $server_url = trim($server->getSolrConnection()->getBaseUrl(), '/');
+ // Append the request handler.
+ $server_url .= '/select';
+ return $server_url;
+}
+
+/**
+ * Lists our variables.
+ */
+function search_api_federated_solr_variables() {
+ return [
+ 'search_api_federated_solr_path',
+ 'search_api_federated_solr_search_index',
+ 'search_api_federated_solr_disable_query_proxy',
+ 'search_api_federated_solr_search_index_basic_auth',
+ 'search_api_federated_solr_search_index_basic_auth_username',
+ 'search_api_federated_solr_search_index_basic_auth_password',
+ 'search_api_federated_solr_proxy_validate_query_fields_against_schema',
+ 'search_api_federated_solr_proxy_debug_query',
+ 'search_api_federated_solr_proxy_query_fields',
+ 'search_api_federated_solr_show_empty_search_results',
+ 'search_api_federated_solr_no_results_text',
+ 'search_api_federated_solr_search_prompt_text',
+ 'search_api_federated_solr_rows',
+ 'search_api_federated_solr_page_buttons',
+ 'search_api_federated_solr_has_site_name_property',
+ 'search_api_federated_solr_has_federated_date_property',
+ 'search_api_federated_solr_has_federated_type_property',
+ 'search_api_federated_solr_has_federated_terms_property',
+ 'search_api_federated_solr_hide_site_name',
+ 'search_api_federated_solr_hide_type',
+ 'search_api_federated_solr_hide_date',
+ 'search_api_federated_solr_hide_terms',
+ 'search_api_federated_solr_set_search_site',
+ 'search_api_federated_solr_autocomplete_is_enabled',
+ 'search_api_federated_solr_autocomplete_is_append_wildcard',
+ 'search_api_federated_solr_autocomplete_disable_query_proxy',
+ 'search_api_federated_solr_autocomplete_url',
+ 'search_api_federated_solr_autocomplete_use_search_app_creds',
+ 'search_api_federated_solr_autocomplete_username',
+ 'search_api_federated_solr_autocomplete_password',
+ 'search_api_federated_solr_autocomplete_suggestion_rows',
+ 'search_api_federated_solr_autocomplete_num_chars',
+ 'search_api_federated_solr_autocomplete_mode',
+ 'search_api_federated_solr_autocomplete_result_title_text',
+ 'search_api_federated_solr_autocomplete_result_hide_directions_text',
+ 'search_api_federated_solr_autocomplete_block_is_enabled',
+ 'search_api_federated_solr_autocomplete_block_is_append_wildcard',
+ 'search_api_federated_solr_autocomplete_block_disable_query_proxy',
+ 'search_api_federated_solr_autocomplete_block_suggestion_rows',
+ 'search_api_federated_solr_autocomplete_block_num_chars',
+ 'search_api_federated_solr_autocomplete_block_mode',
+ 'search_api_federated_solr_autocomplete_block_result_title_text',
+ 'search_api_federated_solr_autocomplete_block_result_hide_directions_text',
+ 'search_api_federated_solr_autocomplete_block_url',
+ 'search_api_federated_solr_autocomplete_block_use_search_app_creds',
+ 'search_api_federated_solr_autocomplete_block_username',
+ 'search_api_federated_solr_autocomplete_block_password',
+ ];
+}
+
+function search_api_federated_solr_map_variables($variables) {
+ $mode = $variables['search_api_federated_solr_autocomplete_block_mode'];
+ $proxy_disabled = $variables['search_api_federated_solr_autocomplete_block_disable_query_proxy'];
+ $direct_url = $variables['search_api_federated_solr_autocomplete_block_url'];
+
+ if ($proxy_disabled) {
+ // Default to provided username and password.
+ $username = $variables['search_api_federated_solr_autocomplete_block_username'];
+ $password = $variables['search_api_federated_solr_autocomplete_block_password'];
+ // If we should use search app credentials, get them.
+ $use_search_app_creds = $variables['search_api_federated_solr_autocomplete_block_use_search_app_creds'];
+ if ($use_search_app_creds) {
+ $username = variable_get('search_api_federated_solr_search_index_basic_auth_username');
+ $password = variable_get('search_api_federated_solr_search_index_basic_auth_password');
+ }
+ // Set encoded credentials if provided.
+ if ($username && $password) {
+ // Set creds.
+ $autocomplete['userpass'] = base64_encode($username . ':' . $password);
+ }
+ // Supply the necessary search term + format params for the solr server endpoint.
+ $params = '?q=[val]&wt=json';
+ }
+ // Proxy is enabled.
+ else {
+ // Supply the necessary search term + format params for the proxy endpoint.
+ $params = '?search=[val]&wt=json';
+ // Remove direct url value if the proxy is enabled.
+ $direct_url = '';
+
+ // @TODO: Add the sitename restriction logic from the proxy controller.
+ }
+
+ // Do not append params to directly supplied urls (i.e. views endpoints)
+ if ($direct_url) {
+ $params = '';
+ }
+
+ // Determine the autocomplete endpoint based on block config.
+ $url = search_api_federated_solr_get_endpoint_url($proxy_disabled, $direct_url, $params);
+
+ // Set autocomplete variables.
+ $autocomplete['url'] = $url;
+ $autocomplete['directUrl'] = $direct_url;
+ $autocomplete['isEnabled'] = $variables['search_api_federated_solr_autocomplete_block_is_enabled'];
+ $autocomplete['appendWildcard'] = $variables['search_api_federated_solr_autocomplete_block_is_append_wildcard'];
+ if ($variables['search_api_federated_solr_autocomplete_block_num_chars']) {
+ $autocomplete['numChars'] = $variables['search_api_federated_solr_autocomplete_block_num_chars'];
+ }
+ if ($variables['search_api_federated_solr_autocomplete_block_suggestion_rows']) {
+ $autocomplete['suggestionRows'] = $variables['search_api_federated_solr_autocomplete_block_suggestion_rows'];
+ }
+ $autocomplete['mode'] = $mode;
+ $autocomplete['result'] = [];
+ $title_text = $variables['search_api_federated_solr_autocomplete_block_' . $mode . '_title_text'] ? $variables['search_api_federated_solr_autocomplete_block_' . $mode . '_title_text'] : "What are you looking for?";
+ $autocomplete['result']['titleText'] = $title_text;
+ $autocomplete['result']['hideDirectionsText'] = $variables['search_api_federated_solr_autocomplete_block_' . $mode . '_hide_directions_text'];
+
+ return $autocomplete;
+}
+
+function search_api_federated_solr_proxy_params() {
+ $params = [];
+ $request = str_replace(request_path() . '?', '', request_uri());
+ $query = explode('&', trim($request, '/'));
+ foreach ($query as $string) {
+ $parts = explode('=', $string);
+ if (isset($parts[1])) {
+ $params[$parts[0]] = $parts[1];
+ }
+ }
+ return $params;
+}
+
+/**
+ * Parses a querystring with support for multiple keys not using array[] syntax.
+ * @see: http://php.net/manual/en/function.parse-str.php#76792
+ *
+ * @param $str
+ * The querystring from the request object.
+ *
+ * @return array
+ * Array of querystring params and their values.
+ */
+function search_api_federated_solr_parse_str_multiple($str) {
+ # result array
+ $arr = [];
+
+ # split on outer delimiter
+ $pairs = explode('&', $str);
+
+ # loop through each pair
+ foreach ($pairs as $i) {
+ # split into name and value
+ if (strpos($i, '=') !== FALSE) {
+ list($name,$value) = explode('=', $i, 2);
+ }
+ else {
+ continue;
+ }
+
+ # if name already exists
+ if (isset($arr[$name])) {
+ # stick multiple values into an array
+ if (is_array($arr[$name])) {
+ $arr[$name][] = $value;
+ }
+ else {
+ $arr[$name] = array($arr[$name], $value);
+ }
+ }
+ # otherwise, simply stick it in a scalar
+ else {
+ $arr[$name] = $value;
+ }
+ }
+
+ # return result array
+ return $arr;
+}
+
+/**
+ * Determines url to use for app search + autocomplete queries based on config:
+ * - defaults to absolute url to proxy route, appends qs params
+ * - if proxy disabled
+ * - compute and fallback to the server url
+ * - if direct url endpoint passed, use it
+ *
+ * @param integer $proxy_is_disabled
+ * Flag indicating whether or not the autocomplete proxy is disabled (0 || 1)
+ * @param string $direct_url
+ * Value of the direct url ("" || )
+ * @param string $qs
+ * Querystring params to append to proxy url
+ *
+ * @return string
+ * URL for the endpoint to be used for query requests.
+ */
+function search_api_federated_solr_get_endpoint_url($proxy_is_disabled, $direct_url, $qs = '') {
+ // Default to proxy url.
+ $options = [
+ 'absolute' => TRUE,
+ ];
+ $endpoint_url = url('search-api-federated-solr/search', $options);
+
+ if ($proxy_is_disabled) {
+ // Override with direct URL if provided.
+ if ($direct_url) {
+ $endpoint_url = $direct_url;
+ }
+ else {
+ // Fallback to solr backend select handler URL.
+ $endpoint_url = search_api_federated_solr_get_server_url();
+ }
+ }
+
+ // Append qs params for block form autocomplete js unless configured
+ // with a direct url (like a view rest export endpoint).
+ if ($qs && !$direct_url) {
+ $endpoint_url .= $qs;
+ }
+
+ return $endpoint_url;
+}
diff --git a/search_api_federated_solr.page.inc b/search_api_federated_solr.page.inc
new file mode 100644
index 000000000..df73f7560
--- /dev/null
+++ b/search_api_federated_solr.page.inc
@@ -0,0 +1,172 @@
+
Federated Solr Search App: If you see this message in your DevTools, it likely means there is an issue adding the app javascript library to this page. Follow the steps in the search_api_federated_solr module README.
';
+ $element['#attached']['library'][] = ['search_api_federated_solr', 'search-app'];
+
+ return $element;
+}
+
+/**
+ * Create search_api_federated_solr config json endpoint.
+ */
+function search_api_federated_solr_config_json() {
+ $federated_search_app_config = [];
+
+ $search_index = variable_get('search_api_federated_solr_search_index');
+ if (empty($search_index)) {
+ return json_encode($federated_search_app_config);
+ }
+
+ // Set the D7 flag.
+ $federated_search_app_config['isD7'] = TRUE;
+ // Determine the proper endpoint for searches.
+ $proxy_is_disabled = variable_get('search_api_federated_solr_disable_query_proxy', 0);
+ $federated_search_app_config['proxyIsDisabled'] = $proxy_is_disabled;
+
+ // Default is the proxy URL.
+ $options = [
+ 'absolute' => TRUE,
+ ];
+ $federated_search_app_config['url'] = url('search-api-federated-solr/search', $options);
+
+ // Fall back to Solr server select handler if proxy is disabled.
+ if ($proxy_is_disabled) {
+ $server_url = search_api_federated_solr_get_server_url();
+ $federated_search_app_config['url'] = $server_url;
+ }
+
+ /* OPTIONAL:
+ * The username and password for Basic Authentication on the server.
+ * The username and password will be
+ * combined and base64 encoded as per the application.
+ */
+ $basic_auth_username = variable_get('search_api_federated_solr_search_index_basic_auth_username');
+ $basic_auth_password = variable_get('search_api_federated_solr_search_index_basic_auth_password');
+ if ($basic_auth_username || $basic_auth_password) {
+ $federated_search_app_config['userpass'] = base64_encode($basic_auth_username . ':' . $basic_auth_password);
+ }
+
+ // Create an index property field map array to determine which fields
+ // exist on the index and should be hidden in the app UI.
+ $search_fields = [
+ "sm_site_name" => [
+ "property" => variable_get('search_api_federated_solr_has_site_name_property', 1),
+ "is_hidden" => variable_get('search_api_federated_solr_hide_site_name'),
+ ],
+ "ss_federated_type" => [
+ "property" => variable_get('search_api_federated_solr_has_federated_type_property', 1),
+ "is_hidden" => variable_get('search_api_federated_solr_hide_type'),
+ ],
+ "ds_federated_date" => [
+ "property" => variable_get('search_api_federated_solr_has_federated_date_property', 1),
+ "is_hidden" => variable_get('search_api_federated_solr_hide_date'),
+ ],
+ "sm_federated_terms" => [
+ "property" => variable_get('search_api_federated_solr_has_federated_terms_property', 1),
+ "is_hidden" => variable_get('search_api_federated_solr_hide_terms'),
+ ],
+ ];
+
+ // Set hiddenSearchFields to an array of keys of those $search_fields items
+ // which both exist as an index property and are set to be hidden.
+
+ // OPTIONAL: Machine name of those search fields whose facets/filter and
+ // current values should be hidden in UI.
+ $federated_search_app_config['hiddenSearchFields'] = array_keys(array_filter($search_fields, function ($value) {
+ return $value['property'] && $value['is_hidden'];
+ }));
+
+ // OPTIONAL: The text to display when the app loads with no search term.
+ $search_prompt = variable_get('search_api_federated_solr_search_prompt_text');
+ if ($search_prompt) {
+ $federated_search_app_config['searchPrompt'] = $search_prompt;
+ }
+
+ // OPTIONAL: The text to display when a search returns no results.
+ $no_response = variable_get('search_api_federated_solr_no_results_text');
+ if ($no_response) {
+ $federated_search_app_config['noResults'] = $no_response;
+ }
+
+ // OPTIONAL: The text to display when a search returns no results.
+ $show_empty_search_results = variable_get('search_api_federated_solr_show_empty_search_results');
+ if ($show_empty_search_results) {
+ $federated_search_app_config['showEmptySearchResults'] = $show_empty_search_results;
+ }
+
+ // OPTIONAL: The number of search results to show per page.
+ $rows = variable_get('search_api_federated_solr_rows');
+ if ($rows) {
+ $federated_search_app_config['rows'] = $rows;
+ }
+
+ // OPTIONAL: The number of page buttons to show for pagination.
+ $pagination_buttons = variable_get('search_api_federated_solr_page_buttons');
+ if ($pagination_buttons) {
+ $federated_search_app_config['paginationButtons'] = $pagination_buttons;
+ }
+
+ /** Not implemented yet in D7.
+ // OPTIONAL: The rendered title of the search page.
+ if ($page_title = $config->get('page_title')) {
+ $federated_search_app_config['pageTitle'] = $page_title;
+ }*/
+
+ $federated_search_app_config['autocomplete'] = FALSE;
+ if ($autocomplete_is_enabled = variable_get('search_api_federated_solr_autocomplete_is_enabled')) {
+ $proxy_disabled = variable_get('search_api_federated_solr_autocomplete_disable_query_proxy');
+ $federated_search_app_config['autocomplete']['proxyIsDisabled'] = $proxy_disabled;
+ // REQUIRED: Autocomplete endpoint
+ $direct_url = variable_get('search_api_federated_solr_autocomplete_url');
+ // Determine the autocomplete endpoint based on block config.
+ $url = search_api_federated_solr_get_endpoint_url($proxy_disabled, $direct_url);
+ $federated_search_app_config['autocomplete']['url'] = $url;
+
+ // OPTIONAL: defaults to false, whether or not to append wildcard to query term
+ if ($autocomplete_append_wildcard = variable_get('search_api_federated_solr_autocomplete_is_append_wildcard')) {
+ $federated_search_app_config['autocomplete']['appendWildcard'] = $autocomplete_append_wildcard;
+ }
+ // OPTIONAL: defaults to 5, max number of autocomplete results to return
+ if ($autocomplete_suggestion_rows = variable_get('search_api_federated_solr_autocomplete_suggestion_rows')) {
+ $federated_search_app_config['autocomplete']['suggestionRows'] = $autocomplete_suggestion_rows;
+ }
+ // OPTIONAL: defaults to 2, number of characters *after* which autocomplete results should appear
+ if ($autocomplete_num_chars = variable_get('search_api_federated_solr_autocomplete_num_chars')) {
+ $federated_search_app_config['autocomplete']['numChars'] = $autocomplete_num_chars;
+ }
+ // REQUIRED: show search-as-you-type results ('result', default) or search term ('term') suggestions
+ if ($autocomplete_mode = variable_get('search_api_federated_solr_autocomplete_mode')) {
+ $federated_search_app_config['autocomplete']['mode'] = $autocomplete_mode;
+ // OPTIONAL: default set, title to render above autocomplete results
+ if ($autocomplete_mode_title_text = variable_get('search_api_federated_solr_autocomplete_' . $autocomplete_mode . '_title_text')) {
+ $federated_search_app_config['autocomplete'][$autocomplete_mode]['titleText'] = $autocomplete_mode_title_text;
+ }
+ // OPTIONAL: defaults to false, whether or not to hide the keyboard usage directions text
+ if ($autocomplete_mode_hide_directions = variable_get('search_api_federated_solr_autocomplete_' . $autocomplete_mode . '_hide_directions_text')) {
+ $federated_search_app_config['autocomplete'][$autocomplete_mode]['showDirectionsText'] = FALSE;
+ }
+ }
+ }
+
+ if (function_exists('domain_get_domain')) {
+ $domain = domain_get_domain();
+ if (isset($domain['path'])) {
+ $federated_search_app_config['hostname'] = parse_url($domain['path'], PHP_URL_HOST) ;
+ }
+ }
+
+ return json_encode($federated_search_app_config, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
+}
diff --git a/search_api_federated_solr.proxy.inc b/search_api_federated_solr.proxy.inc
new file mode 100644
index 000000000..6b38629d4
--- /dev/null
+++ b/search_api_federated_solr.proxy.inc
@@ -0,0 +1,193 @@
+server();
+ }
+ catch (SearchApiException $e) {
+ watchdog_exception('search_api_federated_solr', $e, '%type while getting server for @index: @message in %function (line %line of %file).', array('@index' => $index->label()));
+ }
+ // Get the solr service proxy class.
+ /** @var \SearchApiSolrService $solr */
+ $solr = new SearchApiSolrService($server);
+ /** @var \SearchApiSolrConnection $connection */
+ $connection = $solr->getSolrConnection();
+
+ // If this is an acquia environment and if it is an acquia search server
+ // get the Acquia Search Service + Connection instead.
+ if (isset($_ENV['AH_SITE_ENVIRONMENT']) && module_exists('search_api_acquia') && $server->class === 'acquia_search_service') {
+ /** @var \SearchApiAcquiaSearchService $solr */
+ $solr = new SearchApiAcquiaSearchService($server);
+ /** @var \SearchApiAcquiaSearchConnection $connection */
+ $connection = $solr->getSolrConnection();
+ }
+
+ // Get the configured default query fields.
+ $config_fields = variable_get('search_api_federated_solr_proxy_query_fields', ['rendered_item']);
+ $query_fields = $config_fields;
+
+ // Validate default query fields against index schema, unless flagged not to.
+ $is_validate_query_fields = variable_get('search_api_federated_solr_proxy_validate_query_fields_against_schema', TRUE);
+ if ($is_validate_query_fields) {
+ // Get index field names mapped to their solr field name counterparts
+ $backend_field_names_map = $solr->getFieldNames($index);
+ // Get all full text fields from the index.
+ $full_text_index_fields = $index->getFullTextFields();
+ // We can only search full text fields, so validate supplied field names.
+ $full_text_query_fields = array_intersect($config_fields, $full_text_index_fields);
+ // Filter the field names map by our query fields.
+ $query_fields_map = array_intersect_key($backend_field_names_map, array_flip($full_text_query_fields));
+ // Get the solr field name for our supplied full text query fields.
+ $query_fields = array_values($query_fields_map);
+ }
+
+ // If site search is restricted, enforce it here.
+ $restrict_site = variable_get('search_api_federated_solr_set_search_site', 0)
+ && variable_get('search_api_federated_solr_hide_site_name', 0);
+ if ($restrict_site) {
+ if (!empty($params['fq']) && !is_array($params['fq'])) {
+ $params['fq'] = array($params['fq']);
+ }
+ elseif(empty($params['fq'])) {
+ $params['fq'] = array();
+ }
+ foreach ($params['fq'] as $id => $element) {
+ if (substr_count($element, 'sm_site_name:') > 0) {
+ unset($params['fq'][$id]);
+ }
+ }
+ // The site name can be configured as part of the filter.
+ // Get the proper variable.
+ if (!empty($index->options['data_alter_callbacks']['site_name']['settings']['site_name'])) {
+ $site_name = $index->options['data_alter_callbacks']['site_name']['settings']['site_name'];
+ }
+ // Handle domain access.
+ elseif (function_exists('domain_get_domain') && !empty($index->options['data_alter_callbacks']['site_name']['settings'])) {
+ $domain = domain_get_domain();
+ if (!empty($index->options['data_alter_callbacks']['site_name']['settings']['domain'][$domain['machine_name']])) {
+ $site_name = $index->options['data_alter_callbacks']['site_name']['settings']['domain'][$domain['machine_name']];
+ }
+ else {
+ $site_name = isset($domain['sitename']) ? $domain['sitename'] : variable_get('site_name');
+ }
+ }
+ else {
+ $site_name = variable_get('site_name');
+ }
+ $params['fq'][] = 'sm_site_name:("' . $site_name . '")';
+ }
+
+ // Set facet params
+ if (isset($params['facet.field']) && is_array($params['facet.field']) && count($params['facet.field'])) {
+ // Set defaults for facet params if they don't already exist.
+ $params += [
+ 'facet' => 'on',
+ 'facet.limit' => -1,
+ 'facet.sort' => 'index',
+ ];
+ }
+
+ // Support empty search result set (i.e. show all results)
+ if (variable_get('search_api_federated_solr_show_empty_search_results', 0)) {
+ // Set the default search param to '*' if it doesn't already exist.
+ $params += [
+ 'search' => '*',
+ ];
+ }
+
+ // Add flag to debug the query if flag exists in settings.
+ if (variable_get('search_api_federated_solr_proxy_debug_query', FALSE)) {
+ $params += [
+ 'debug' => 'true',
+ ];
+ }
+
+ // Merge in the default params.
+ $params += [
+ 'start' => 0,
+ 'rows' => 20,
+ 'sort' => 'score desc',
+ 'hl' => 'true',
+ 'hl.simple.pre' => '',
+ 'hl.simple.post' => '',
+ 'hl.fl' => 'tm_rendered_item',
+ 'hl.usePhraseHighlighter' => 'true',
+ 'qf' => implode(' ', $query_fields),
+ ];
+
+ if (isset($params['search'])) {
+ $query = $params['search'];
+ unset($params['search']);
+ try {
+ $results = $connection->search(urldecode($query), $params);
+ }
+ catch (SearchApiException $e) {
+ watchdog_exception('search_api_federated_solr', $e, '%type while executing query on @server: @message in %function (line %line of %file).', array('@server' => $server->label()));
+ }
+ }
+
+ // Post process results.
+ if (isset($results)) {
+ // Convert each facet_fields->item into an array, i.e.:
+ // FROM object:
+ // sm_site_name: {
+ // Federated SOLR D7 = 0,
+ // Federated Search Demo (D8, Single) = 0
+ // }
+ // TO ARRAY:
+ // sm_site_name: [
+ // "Federated SOLR D7",
+ // 0,
+ // "Federated Search Demo (D8, single)",
+ // 0
+ // ]
+ if (isset($results->facet_counts->facet_fields) && $facet_fields = $results->facet_counts->facet_fields) {
+ foreach ($facet_fields as $field => $value) {
+ $facet_fields_array = [];
+ foreach ($value as $facet_key => $facet_value) {
+ array_push($facet_fields_array, $facet_key);
+ array_push($facet_fields_array, $facet_value);
+ }
+ $facet_fields->$field = $facet_fields_array;
+ }
+ }
+ }
+
+ /* Test formatting.
+ $response = (object) [
+ 'response' => $results->response,
+ 'facet_counts' => $results->facet_counts,
+ 'highlighting' => $results->highlighting,
+ ];*/
+
+ return drupal_json_output($results);
+}
diff --git a/src/SearchApiFederatedSolrSiteName.php b/src/SearchApiFederatedSolrSiteName.php
index 834355746..0151b7d09 100644
--- a/src/SearchApiFederatedSolrSiteName.php
+++ b/src/SearchApiFederatedSolrSiteName.php
@@ -65,7 +65,7 @@ protected function addDomainName(array &$items) {
$domain = domain_lookup($domain_id);
$ds[] = !empty($this->options['domain'][$domain['machine_name']]) ? $this->options['domain'][$domain['machine_name']] : $domain['sitename'];
}
-
+
$item->site_name = $ds;
}
@@ -94,8 +94,6 @@ public function configurationForm() {
'#type' => 'textfield',
'#title' => t('Site Name'),
'#description' => t('The name of the site from which this content originated. This can be useful if indexing multiple sites with a single search index.'),
- '#default_value' => !empty($this->options['site_name']) ? $this->options['site_name'] : variable_get('site_name'),
- '#required' => TRUE,
];
}