You are here

search_autocomplete.module in Search Autocomplete 8

Provides autocompletion in any field from GUI.

The Search Autocomplete module provides autocompletion configurations to help autocompleting any fields.

File

search_autocomplete.module
View source
<?php

use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\views\Views;

/**
 * @file
 * Provides autocompletion in any field from GUI.
 *
 * The Search Autocomplete module provides autocompletion configurations
 * to help autocompleting any fields.
 */

/**
 * Implements hook_element_info_alter().
 *
 * The purpose of this is to include our own settings in formAPI for
 * autocompletion configurations.
 */
function search_autocomplete_element_info_alter(&$types) {

  /*
   * Iterate throught the elements to find autocompletion enabled ones.
   * This methods let's all autocompletion enabled elements to be overriden,
   * even if coming from other modules or if unknown yet.
   */
  foreach ($types as $type => $info) {
    $process_methods = isset($info['#process']) ? $info['#process'] : [];
    foreach ($process_methods as $method) {

      // If the element is autocompleted, overrides it.
      if (in_array('processAutocomplete', (array) $method)) {
        $types[$type]['#process'][] = 'process_search_autocomplete';
        break;
      }
    }
  }
  return $types;
}

/**
 * Adds autocomplete functionality to elements.
 *
 * This sets up autocomplete functionality for elements with an
 * #autocomplete_configuration property.
 *
 * @param array $element
 *   The form element to process.
 *
 * @return array
 *   The form element.
 */
function process_search_autocomplete(&$element) {
  if (!empty($element['#autocomplete_configuration'])) {
    attach_configuration_to_element($element, $element['#autocomplete_configuration']);
    $element['#attributes']['class'][] = 'form-autocomplete';
    $element['#attached']['library'][] = 'core/drupal.autocomplete';
    $element['#attributes']['data-key'] = $element['#autocomplete_configuration'];
  }
  return $element;
}

/**
 * This helper method will analyse an autocomplete_configuration to build
 * the element properties and add necessary libraries, configurations and
 * data necessary for autocompletion to happen.
 *
 * @param array $element
 *   An element of a form.
 * @param string $config_id
 *   The ID of the autocomplete_configuration to use.
 *
 * @return array
 *   The element once filled with data.
 */
function attach_configuration_to_element(&$element, $config_id) {

  // Load the configuration.
  $config = Drupal::entityTypeManager()
    ->getStorage('autocompletion_configuration')
    ->load($config_id);

  // No need to continue if the $config is empty.
  if ($config == NULL || !$config
    ->getStatus()) {
    return;
  }
  $source = $config
    ->getSource();
  $exposed_filters = [];

  // Build the callback URL.
  $input_source = explode('::', $source);

  // If from View, build view URL.
  if (count($input_source) == 2) {

    // Load the view.
    $view = Views::getView($input_source[0]);

    // Load the required display.
    if ($view != NULL && $view
      ->setDisplay($input_source[1])) {

      // Retrieve the display URL.
      $display = $view
        ->getDisplay();
      $source = '/' . $display
        ->getPath();

      // Build filters
      $filters = $view
        ->getHandlers('filter');
      foreach ($filters as $filter) {
        if (isset($filter['exposed']) && $filter['exposed'] && isset($filter['expose'])) {
          $exposed_filters[] = $filter['expose']['identifier'];
        }
      }
    }
  }

  // In case of internal URL, convert it to absolute.
  if (!UrlHelper::isExternal($source)) {
    $prefix = 'internal:';
    if (strpos($source, '/') !== 0) {
      $prefix .= '/';
    }
    $source = Url::fromUri($prefix . $source, [
      'absolute' => FALSE,
    ])
      ->toString();
  }
  $settings = [
    'source' => $source,
    'selector' => $config
      ->getSelector(),
    'minChars' => $config
      ->getMinChar(),
    'maxSuggestions' => $config
      ->getMaxSuggestions(),
    'autoSubmit' => $config
      ->getAutoSubmit(),
    'autoRedirect' => $config
      ->getAutoRedirect(),
    'theme' => basename($config
      ->getTheme(), '.css'),
    // theme without extension
    'filters' => $exposed_filters,
    'noResult' => [
      'group' => [
        'group_id' => 'no_results',
      ],
      'label' => $config
        ->getNoResultLabel(),
      'value' => $config
        ->getNoResultValue(),
      'link' => $config
        ->getNoResultLink(),
    ],
    'moreResults' => [
      'group' => [
        'group_id' => 'more_results',
      ],
      'label' => $config
        ->getMoreResultsLabel(),
      'value' => $config
        ->getMoreResultsValue(),
      'link' => $config
        ->getMoreResultsLink(),
    ],
  ];
  $element['#attached']['drupalSettings']['search_autocomplete'][$config
    ->id()] = $settings;

  // Add the theme library if available
  if (Drupal::service('library.discovery')
    ->getLibraryByName('search_autocomplete', 'theme.' . $config
    ->getTheme())) {
    $element['#attached']['library'][] = 'search_autocomplete/theme.' . $config
      ->getTheme();
  }
  return $element;
}

/**
 * Implements hook_install().
 *
 * Display a notice to user for cache rebuild after install.
 */
function search_autocomplete_install() {
  drupal_set_message(t('To finish install of Search Autocomplete, you must %clear_caches.', [
    '%clear_caches' => Link::fromTextAndUrl(t('Clear all caches'), Url::fromRoute('system.performance_settings'))
      ->toString(),
  ]));
}

/**
 * Implements hook_library_info_build().
 *
 * Add dynamic library definitions.
 *
 * Since we can't add files through css in #attached anymore, this is a trick
 * defining as many libraries as the number of found CSS in the theme folder.
 *
 * @return array
 *   An array of library definitions.
 */
function search_autocomplete_library_info_build() {
  $libraries = [];

  // Find all available themes.
  $themes = [];
  $files = file_scan_directory(drupal_get_path('module', 'search_autocomplete') . '/css/themes', '/.*\\.css\\z/', [
    'recurse' => FALSE,
  ]);

  // Create a new library for all themes.
  foreach ($files as $file) {
    $libraries["theme.{$file->filename}"] = [
      'css' => [
        'base' => [
          "css/themes/{$file->filename}" => [],
        ],
      ],
    ];
  }
  return $libraries;
}

/**
 * Implements hook_library_info_alter().
 */
function search_autocomplete_library_info_alter(array &$libraries, $extension) {
  if ($extension == 'core' && isset($libraries['drupal.autocomplete'])) {
    $libraries['drupal.autocomplete']['js'] = [
      '/' . drupal_get_path('module', 'search_autocomplete') . '/js/jquery.autocomplete.js' => [],
    ];
  }
}

/**
 * Implements hook_page_attachments().
 *
 * Adds the settings from user defined autocomplete_configurations to the page.
 * Also loads the necessary JS library if needed.
 */
function search_autocomplete_page_attachments(array &$attachments) {

  // Check user permissions.
  if (!Drupal::currentUser()
    ->hasPermission('use search autocomplete')) {
    return;
  }

  // Load module settings.
  $module_settings = Drupal::config('search_autocomplete.settings');

  // Check if user have authorization to use admin tools and if enabled.
  if (Drupal::currentUser()
    ->hasPermission('administer Search Autocomplete') && $module_settings
    ->get('admin_helper')) {
    $attachments['#attached']['library'][] = 'search_autocomplete/search_autocomplete.admin.helper';
  }

  // Load all configurations with selectors
  $autocomplete_configuration_entities = Drupal::entityQuery('autocompletion_configuration')
    ->condition('status', TRUE)
    ->condition('selector', '', '<>')
    ->execute();

  // End-up here if no config to set.
  if (empty($autocomplete_configuration_entities)) {
    return;
  }

  // Add the settings to be pass to JS:
  foreach ($autocomplete_configuration_entities as $config_id) {
    attach_configuration_to_element($attachments, $config_id);
  }
  $attachments['#attached']['library'][] = 'core/drupal.autocomplete';
}

/**
 * Implements hook_form_alter().
 *
 * Adds autocomplete_configurations to formAPI for search block and search page.
 * This is an example on how to use configurations from code.
 */
function search_autocomplete_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Prevent a useless query to configurations.
  if ($form_id != $form_id && $form_id != 'search_form') {
    return;
  }

  // Load the default activated configurations.
  $autocomplete_configuration_entities = Drupal::entityQuery('autocompletion_configuration')
    ->condition('status', TRUE)
    ->condition('id', [
    'search_block',
    'search_form_users',
    'search_form_content',
  ], 'IN')
    ->execute();

  // Autocomplete the default search block.
  if ($form_id == 'search_block_form' && isset($autocomplete_configuration_entities['search_block'])) {
    $form['keys']['#autocomplete_configuration'] = 'search_block';
  }
  else {
    if ($form_id == 'search_form') {
      if (Drupal::routeMatch()
        ->getRouteName() == 'search.view_user_search' && isset($autocomplete_configuration_entities['search_form_users'])) {
        $form['basic']['keys']['#autocomplete_configuration'] = 'search_form_users';
      }
      else {
        if (isset($autocomplete_configuration_entities['search_form_users'])) {
          $form['basic']['keys']['#autocomplete_configuration'] = 'search_form_content';
        }
      }
    }
  }
  return $form;
}

Functions

Namesort descending Description
attach_configuration_to_element This helper method will analyse an autocomplete_configuration to build the element properties and add necessary libraries, configurations and data necessary for autocompletion to happen.
process_search_autocomplete Adds autocomplete functionality to elements.
search_autocomplete_element_info_alter Implements hook_element_info_alter().
search_autocomplete_form_alter Implements hook_form_alter().
search_autocomplete_install Implements hook_install().
search_autocomplete_library_info_alter Implements hook_library_info_alter().
search_autocomplete_library_info_build Implements hook_library_info_build().
search_autocomplete_page_attachments Implements hook_page_attachments().