You are here

search_api_federated_solr.module in Search API Federated Solr 7.3

search_api_federated_solr.module Contains hook implementations for the Federated Solr Search API Module.

@copyright Copyright (c) 2018-19 Palantir.net

File

search_api_federated_solr.module
View source
<?php

/**
 * @file search_api_federated_solr.module
 * Contains hook implementations for the Federated Solr Search API Module.
 *
 * @copyright Copyright (c) 2018-19 Palantir.net
 */

/**
 * Implements hook_help().
 */
function search_api_federated_solr_help($path, $arg) {
  switch ($path) {

    // Main module help for the search_api_federated_solr module.
    case 'admin/help#search_api_federated_solr':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Allows indexing into a single Solr search index.') . '</p>';
      $output .= '<p>' . t('This help text uses Markdown. Install the <a href="@markdown">Markdown module</a> or <a href="@readme">view it online</a> for easier reading', [
        '@markdown' => 'https://www.drupal.org/project/markdown',
        '@readme' => 'https://git.drupalcode.org/project/search_api_federated_solr/tree/7.x-2.x',
      ]) . '</p>';
      $text = file_get_contents(dirname(__FILE__) . '/README.md');
      $output .= search_api_federated_solr_parse_help($text);
      return $output;
  }
}

/**
 * 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().
 */
function search_api_federated_solr_search_api_alter_callback_info() {
  $callbacks['site_name'] = array(
    'name' => 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.'),
    'class' => 'SearchApiFederatedSolrSiteName',
  );
  $callbacks['search_api_urls'] = array(
    'name' => t('URLs'),
    'description' => t('The links to the node on all available sites. This can be useful if indexing multiple sites with a single search index.'),
    'class' => 'SearchApiFederatedSolrUrls',
  );
  $callbacks['canonical_url'] = array(
    'name' => t('Canonical URL'),
    'description' => t('Preferred URL for this content.'),
    'class' => 'SearchApiFederatedSolrCanonicalUrl',
  );
  $callbacks['federated_field'] = array(
    'name' => t('Federated Field'),
    'description' => t('A token or free text field that can be customized per-bundle.'),
    'class' => 'SearchApiFederatedSolrField',
  );
  $callbacks['federated_terms'] = array(
    'name' => t('Federated Term'),
    'description' => t('By adding this field to your search index configuration, you have enabled the federated terms processor to run when new items are indexed.  Next, add a "Federated Terms" field to any taxonomy vocabulary whose terms should be mapped to a "federated" term (this helps map terms across vocabularies and sites to a single "federated" term).  Then, edit terms in those vocabularies to add the federated term destination value (i.e. "Conditions>Blood Disorders").  Once that tagged content gets indexed, it will have "federated_terms" populated with any matching federated term destination values.'),
    'class' => 'SearchApiFederatedSolrTerms',
  );
  $callbacks['remap'] = array(
    'name' => t('Re-map Field Names'),
    'description' => t(''),
    'class' => 'SearchApiFederatedSolrRemap',
  );
  return $callbacks;
}

/**
 * Implements hook_block_info().
 */
function search_api_federated_solr_block_info() {
  $blocks['federated_search_page_form_block'] = array(
    'info' => t('Federated Search Page Form block'),
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function search_api_federated_solr_block_view($delta = '') {
  $block = array();
  switch ($delta) {
    case 'federated_search_page_form_block':
      $block['title'] = 'Federated Search Page Form block';
      $srchblk = drupal_get_form('search_api_federated_solr_search_block_form');
      $block['content'] = drupal_render($srchblk);
      break;
  }
  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' => '<p>' . 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.') . '</p>',
    '#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' => '<b>' . t('Enable autocomplete for the search results page search form') . '</b>',
    '#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' => '<b>' . t('Append a wildcard \'*\' to support partial text search') . '</b>',
    '#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 <a href="https://lucene.apache.org/solr/guide/6_6/tokenizers.html" target="_blank">NGram Tokenizers</a> to your index or if your autocomplete <a href="https://lucene.apache.org/solr/guide/6_6/requesthandlers-and-searchcomponents-in-solrconfig.html#RequestHandlersandSearchComponentsinSolrConfig-RequestHandlers" target="_blank">Request Handler</a> 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' => '<strong>' . t('Do not use the proxy for the search app autocomplete query') . '</strong>',
    '#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 <a href="@url">Federated Search App settings page</a> in Search Results Page > Set Up using the Drupal route defined by this module.<br/><br/>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  <code>select</code> <a href="https://lucene.apache.org/solr/guide/6_6/requesthandlers-and-searchcomponents-in-solrconfig.html#RequestHandlersandSearchComponentsinSolrConfig-RequestHandlers" target="_blank">Request Handler</a> on the server of the selected Search API index.)<ul><li>Supports an absolute url pattern to any other Request Handler for an index on your solr server</li><li>The value of the main search field will be appended to the url as the main query param (i.e. <code>?q=[value of the search field, wildcard appended if enabled]</code>)</li><li>Any facet/filter default values set for the search app will automatically be appended (i.e. <code>&sm_site_name=[value of the site name for the index]</code>)</li><li>The format param <code>&wt=json</code> will automatically be appended</li><li>Include any other necessary url params corresponding to <a href="https://lucene.apache.org/solr/guide/6_6/common-query-parameters.html" target="_blank">query parameters</a>.</li>'),
    '#states' => [
      'visible' => [
        ':input[data-autocomplete-direct-query-enabler]' => [
          'checked' => TRUE,
        ],
      ],
    ],
  ];
  $form['autocomplete']['direct']['basic_auth'] = [
    '#type' => 'fieldset',
    '#title' => t('Search App Autocomplete Endpoint Basic Authentication'),
    '#description' => '<p>' . 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.') . '</p>',
    '#states' => [
      'visible' => [
        ':input[data-autocomplete-direct-query-enabler]' => [
          'checked' => TRUE,
        ],
      ],
    ],
  ];
  $form['autocomplete']['direct']['basic_auth']['search_api_federated_solr_autocomplete_block_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_block_use_search_app_creds'),
    '#attributes' => [
      'data-autocomplete-use-search-app-creds' => TRUE,
    ],
  ];
  $form['autocomplete']['direct']['basic_auth']['search_api_federated_solr_autocomplete_block_username'] = [
    '#type' => 'textfield',
    '#title' => t('Username'),
    '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_username'),
    '#states' => [
      'visible' => [
        ':input[data-autocomplete-use-search-app-creds]' => [
          'checked' => FALSE,
        ],
      ],
    ],
  ];
  $form['autocomplete']['direct']['basic_auth']['search_api_federated_solr_autocomplete_block_password'] = [
    '#type' => 'textfield',
    '#title' => t('Password'),
    '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_password'),
    '#states' => [
      'visible' => [
        ':input[data-autocomplete-use-search-app-creds]' => [
          'checked' => FALSE,
        ],
      ],
    ],
  ];
  $form['autocomplete']['search_api_federated_solr_autocomplete_block_suggestion_rows'] = [
    '#type' => 'textfield',
    '#title' => t('Number of results'),
    '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_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_block_num_chars'] = [
    '#type' => 'textfield',
    '#title' => t('Number of characters after which autocomplete query should execute'),
    '#default_value' => variable_get('search_api_federated_solr_autocomplete_block_num_chars'),
    '#description' => t('Autocomplete query will be executed <em>after</em> 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_block_mode') ?: 'result';
  $title_text_config_key = 'search_api_federated_solr_autocomplete_block_' . $autocomplete_mode . '_title_text';
  $hide_directions_text_config_key = 'search_api_federated_solr_autocomplete_block_' . $autocomplete_mode . '_hide_directions_text';
  $form['autocomplete']['search_api_federated_solr_autocomplete_block_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' => '<b>' . t('Hide keyboard directions') . '</b>',
    '#default_value' => $autocomplete_mode ? variable_get($hide_directions_text_config_key) : 0,
    '#description' => t('Check this box to 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,
        ],
      ],
    ],
  ];
  return $form;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function search_api_federated_solr_form_block_admin_configure_alter(&$form, &$form_state, $form_id) {
  if ($form['module']['#value'] === 'search_api_federated_solr') {
    $form['#validate'][] = 'search_api_federated_solr_block_validate';
  }
}

/**
 * Implements hook_block_save().
 */
function search_api_federated_solr_block_save($delta = '', $edit) {
  if ($delta != 'federated_search_page_form_block') {
    return;
  }
  foreach ($edit as $key => $value) {
    if (substr_count($key, 'search_api_federated_solr_autocomplete_block') > 0) {
      variable_set($key, $value);
    }
  }
}

/**
 * Create custom search form.
 */
function search_api_federated_solr_search_block_form($form, &$form_state) {
  $form['q'] = array(
    '#type' => 'textfield',
    '#title' => t('Search'),
    '#title_display' => 'invisible',
    '#size' => 15,
    '#default_value' => '',
    '#attributes' => array(
      'title' => t('Enter the terms you wish to search for.'),
      'placeholder' => '',
      'autocomplete' => 'off',
    ),
    '#prefix' => '<div class="container-inline">',
  );

  // @TODO: Block autocomplete.
  $auto = variable_get('search_api_federated_solr_autocomplete_block_is_enabled');
  if (!empty($auto)) {
    drupal_add_js(drupal_get_path('module', 'search_api_federated_solr') . '/js/search_api_federated_solr_autocomplete.js', 'file');
    $form['q']['#attributes']['class'][] = 'js-search-api-federated-solr-block-form-autocomplete';

    // Write the block autocomplete config to Drupal settings.
    $autocomplete = search_api_federated_solr_block_variables();
    drupal_add_js([
      'searchApiFederatedSolr' => [
        'block' => [
          'autocomplete' => $autocomplete,
        ],
      ],
    ], 'setting');
    drupal_add_css(drupal_get_path('module', 'search_api_federated_solr') . '/css/search_api_federated_solr_autocomplete.css', 'file');
  }

  // Send site name as qs param if app is configured to load w/default site.
  if (variable_get('search_api_federated_solr_set_search_site')) {
    $site_name = search_api_federated_solr_get_site_name();
    if ($site_name) {
      $form['sm_site_name'] = [
        '#type' => 'hidden',
        '#name' => 'sm_site_name',
        '#default_value' => $site_name,
      ];
    }
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Search'),
    '#name' => '',
    '#suffix' => '</div>',
  );
  return $form;
}

/**
 * Gets and transforms D7 variables for use with the JS code.
 *
 * @return array.
 */
function search_api_federated_solr_block_variables() {
  $autocomplete = [];
  $vars = search_api_federated_solr_variables();
  foreach ($vars as $key => $value) {
    if (substr_count($value, 'search_api_federated_solr_autocomplete_block') > 0) {
      $autocomplete[$value] = variable_get($value);
    }
  }
  return search_api_federated_solr_map_variables($autocomplete);
}

/**
 * Create custom search form handler.
 */
function search_api_federated_solr_search_block_form_submit($form, &$form_state) {
  $search_path = variable_get('search_api_federated_solr_path', 'search-app');

  // Define default search app qs params (i.e. search term).
  $qs_params = [
    'search' => $form_state['values']['q'],
  ];

  // If there is a site name and it should be set by default, add it as qs param.
  $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) {
    $search_index = variable_get('search_api_federated_solr_search_index');

    // Get the index configuration object.
    $index = search_api_index_load($search_index);

    // Get the domain machine name from Domain Access.
    if (function_exists('domain_get_domain')) {
      $domain = domain_get_domain()['machine_name'];
    }

    // If the site is using Domain Access and there's an altered site name.
    if (isset($domain) && !empty($index->options['data_alter_callbacks']['site_name']['settings']['domain'][$domain])) {

      // Lookup the altered site name matching the domain and set it.
      $domain_site_name = $index->options['data_alter_callbacks']['site_name']['settings']['domain'][$domain];
      $qs_params['sm_site_name'] = $domain_site_name;
    }
    elseif (!empty($index->options['data_alter_callbacks']['site_name']['settings']['site_name'])) {
      $site_name = $index->options['data_alter_callbacks']['site_name']['settings']['site_name'];
      $qs_params['sm_site_name'] = $site_name;
    }
    else {
      $qs_params['sm_site_name'] = variable_get('site_name');
    }
  }

  // Redirect to the search app path with necessary qs params.
  drupal_goto($search_path, [
    'query' => $qs_params,
  ]);
}

/**
 * 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.
 */
function get_site_name($form, $form_state) {
  if (!empty($form_state['values']['search_api_federated_solr_search_index'])) {
    $search_index = $form_state['values']['search_api_federated_solr_search_index'];
    $index = search_api_index_load($search_index);
    $is_site_name_property = isset($index->options['fields']['site_name']) ? 'true' : '';
    $form['search_api_federated_solr_has_site_name_property']['#value'] = $is_site_name_property;
    $form_state['values']['search_api_federated_solr_has_site_name_property'] = $is_site_name_property;
  }
  return $form;
}

/**
 * Implements hook_field_info().
 */
function search_api_federated_solr_field_info() {
  return array(
    // We name our field as the associative name of the array.
    'federated_terms' => array(
      'label' => t('Federated terms'),
      'description' => t('Stores the solr search api federated term destination value for taxonomy terms.'),
      'settings' => array(
        'max_length' => 255,
      ),
      'default_widget' => 'federated_terms_textfield',
      'default_formatter' => 'string',
      'cardinality' => -1,
    ),
  );
}

/**
 * Implements hook_field_validate().
 */
function search_api_federated_solr_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  foreach ($items as $delta => $item) {
    if (!empty($item['value'])) {
      if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
        $errors[$field['field_name']][$langcode][$delta][] = array(
          'error' => 'federated_field_max_length',
          'message' => t('%name: may not be longer than @max characters.', array(
            '%name' => $instance['label'],
            '%max' => $field['settings']['max_length'],
          )),
        );
      }
    }
  }
}

/**
 * Implements hook_field_widget_error().
 */
function search_api_federated_solr_field_widget_error($element, $error, $form, &$form_state) {
  switch ($error['error']) {
    case 'federated_field_max_length':
      form_error($element, $error['message']);
      break;
  }
}

/**
 * Implements hook_field_is_empty().
 */
function search_api_federated_solr_field_is_empty($item, $field) {
  return $item['value'] === NULL || $item['value'] === '';
}

/**
 * Implements hook_field_widget_info().
 */
function search_api_federated_solr_field_widget_info() {
  return array(
    'federated_terms_textfield' => array(
      'label' => t('Federated Terms Textfield'),
      'field types' => array(
        'federated_terms',
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_form().
 */
function search_api_federated_solr_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $value = isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL;
  $widget = $element;
  $widget['#delta'] = $delta;
  if ($instance['widget']['type'] == 'federated_terms_textfield') {
    $widget += array(
      '#type' => 'textfield',
      '#default_value' => $value,
      '#description' => t('This federated term is used as a facet value in your search application.  It should consist of a hierarchy made up of a type (i.e. "Condition") and term (i.e. "Diabetes"), separated by ">".  For example: Condition>Diabetes.'),
      '#size' => 75,
      '#maxlength' => 255,
      '#attributes' => [
        'class' => [
          'js-text-full',
          'text-full',
        ],
      ],
    );
  }
  $element['value'] = $widget;
  return $element;
}

/**
 * Declare search app static assets as module library.
 */
function search_api_federated_solr_library() {

  // Search app.
  $libraries['search-app'] = array(
    'title' => 'Federated Search App',
    'version' => 3,
    'js' => array(
      'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v3.0.1/js/runtime-main.9cb93041.js' => array(
        'scope' => 'footer',
        'type' => 'external',
      ),
      'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v3.0.1/js/2.7525757a.chunk.js' => array(
        'scope' => 'footer',
        'type' => 'external',
      ),
      'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v3.0.1/js/main.84ad60c9.chunk.js' => array(
        'scope' => 'footer',
        'type' => 'external',
      ),
    ),
    'css' => array(
      'https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v3.0.1/css/main.7345ecc7.chunk.css' => array(
        'type' => 'external',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_permission().
 */
function search_api_federated_solr_permission() {
  return array(
    'administer federated search' => array(
      'title' => t('Administer Federated Search'),
      'description' => t('Update Federated Search page configuration.'),
    ),
    'use federated search' => array(
      'title' => t('Use Federated Search'),
      'description' => t('Allows users to view and use the Federated Search page.'),
    ),
  );
}

/**
 * Implements hook_image_default_styles().
 */
function search_api_federated_solr_image_default_styles() {
  $styles = array();
  $styles['search_api_federated_solr_image'] = array(
    'label' => 'Federated Image',
    'effects' => array(
      array(
        'name' => 'image_scale_and_crop',
        'data' => array(
          'width' => 425,
          'height' => 239,
        ),
        'weight' => 0,
      ),
    ),
  );
  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_allowed_sites',
    'search_api_federated_solr_site_list',
    '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';
  }
  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'];

  // Set the constraints for allowed sites.
  if ($allowed_sites = variable_get('search_api_federated_solr_allowed_sites')) {
    $list = [];
    $sites = array_keys(array_filter($allowed_sites));
    foreach ($sites as $site) {
      $list[] = '"' . $site . '"';
    }
    $autocomplete['sm_site_name'] = '(' . implode(' OR ', $list) . ')';
  }
  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,
        );
      }
    }
    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 ("" || <absolute-url-with-qs-params>)
 * @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;
}

/**
 * Returns the active site name value.
 *
 * @return string
 */
function search_api_federated_solr_get_site_name() {
  $site_name = variable_get('site_name');
  $search_index = variable_get('search_api_federated_solr_search_index');

  // Get the index entity.

  /** @var \SearchApiIndex $index */
  $index = search_api_index_load($search_index);

  // 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'];
  }
  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');
    }
  }
  return $site_name;
}

/**
 * Simplified display of help text without markdown module.
 *
 * @param $text
 *   The help text markdown.
 *
 * @return HTML
 */
function search_api_federated_solr_parse_help($text) {
  $find = "```\n\n";
  $replace = '</pre>';
  $text = str_replace($find, $replace, $text);
  $find = "```";
  $replace = '<pre>';
  $text = str_replace($find, $replace, $text);
  $find = [
    "\n",
  ];
  $replace = [
    '<br />',
  ];
  $text = str_replace($find, $replace, $text);
  return $text;
}

Functions

Namesort descending Description
get_site_name Ajax callback for search_api_federated_solr_search_index.
search_api_federated_solr_block_configure Configuration options for the block.
search_api_federated_solr_block_info Implements hook_block_info().
search_api_federated_solr_block_save Implements hook_block_save().
search_api_federated_solr_block_validate Ensure that valid URL is passed to block autocomplete endpoint setting.
search_api_federated_solr_block_variables Gets and transforms D7 variables for use with the JS code.
search_api_federated_solr_block_view Implements hook_block_view().
search_api_federated_solr_field_info Implements hook_field_info().
search_api_federated_solr_field_is_empty Implements hook_field_is_empty().
search_api_federated_solr_field_validate Implements hook_field_validate().
search_api_federated_solr_field_widget_error Implements hook_field_widget_error().
search_api_federated_solr_field_widget_form Implements hook_field_widget_form().
search_api_federated_solr_field_widget_info Implements hook_field_widget_info().
search_api_federated_solr_form_block_admin_configure_alter Implements hook_form_FORM_ID_alter().
search_api_federated_solr_form_search_api_federated_solr_admin_alter Create search_api_federated_solr_form_alter to validate the search path format.
search_api_federated_solr_get_endpoint_url Determines url to use for app search + autocomplete queries based on config:
search_api_federated_solr_get_server_url Returns the URL for the active search service.
search_api_federated_solr_get_site_name Returns the active site name value.
search_api_federated_solr_help Implements hook_help().
search_api_federated_solr_image_default_styles Implements hook_image_default_styles().
search_api_federated_solr_library Declare search app static assets as module library.
search_api_federated_solr_map_variables
search_api_federated_solr_menu Implements hook_menu().
search_api_federated_solr_parse_help Simplified display of help text without markdown module.
search_api_federated_solr_parse_str_multiple Parses a querystring with support for multiple keys not using array[] syntax. @see: http://php.net/manual/en/function.parse-str.php#76792
search_api_federated_solr_permission Implements hook_permission().
search_api_federated_solr_proxy_params
search_api_federated_solr_search_api_alter_callback_info Implements hook_search_api_alter_callback_info().
search_api_federated_solr_search_block_form Create custom search form.
search_api_federated_solr_search_block_form_submit Create custom search form handler.
search_api_federated_solr_variables Lists our variables.
_direct_url_form_validate Ensure that valid URL is passed to autocomplete endpoint setting.
_path_form_validate