You are here

search_api_location_page.module in Search API Location 7.2

File

search_api_location_page/search_api_location_page.module
View source
<?php

/**
 * Implements hook_form_FORM_ID_alter().
 */
function search_api_location_page_form_search_api_page_admin_add_alter(&$form, &$form_state) {
  if (!isset($form_state['step_one'])) {
    return;
  }
  $index = search_api_index_load($form_state['step_one']['index_id']);
  if ($fields = search_api_location_get_location_fields($index)) {
    $form['#validate'][] = 'search_api_location_page_edit_validate';
    $form['search_api_location'] = array(
      '#type' => 'fieldset',
      '#title' => t('Location'),
      '#collapsible' => TRUE,
      '#tree' => TRUE,
      '#prefix' => '<div id="search-api-loaction-fieldset">',
      '#suffix' => '</div>',
    );
    $form['submit']['#weight'] = 5;
    $radius_types = array(
      'fixed' => t('Fixed value'),
      'textfield' => t('Text field'),
      'select' => t('Select field'),
    );
    $plugin_options = search_api_location_get_input_plugin_options();
    $plugin_default = key($plugin_options);

    // Use a reference here so we don't have to check for isset().
    $values =& $form_state['values']['search_api_location'];
    foreach ($fields as $key => $field) {
      $css_key = '#edit-search-api-location-' . drupal_clean_css_identifier($key);
      $states['visible'][$css_key . '-enabled']['checked'] = TRUE;
      $form['search_api_location'][$key] = array(
        '#type' => 'fieldset',
        '#title' => check_plain($field['name']),
        '#collapsible' => TRUE,
      );
      $form['search_api_location'][$key]['enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Include location filter'),
        '#default_value' => FALSE,
      );
      $form['search_api_location'][$key]['compact'] = array(
        '#type' => 'checkbox',
        '#title' => t('Show filter in the search block'),
        '#default_value' => FALSE,
        '#states' => $states,
      );
      $form['search_api_location'][$key]['plugin'] = array(
        '#type' => 'select',
        '#title' => t('Input method'),
        '#description' => t('Select the method to use for parsing locations entered by the user.'),
        '#options' => $plugin_options,
        '#default_value' => $plugin_default,
        '#states' => $states,
        '#ajax' => array(
          'callback' => 'search_api_location_page_ajax_callback',
          'wrapper' => 'search-api-loaction-fieldset',
        ),
      );
      $plugin_id = !empty($values[$key]['plugin']) ? $values[$key]['plugin'] : $plugin_default;
      $plugin = search_api_location_get_input_plugins($plugin_id);
      $form['search_api_location'][$key]['plugin_settings'] = array(
        '#type' => 'fieldset',
        '#title' => t('Input method settings'),
        '#description' => $plugin['description'],
        '#states' => $states,
      );
      if (!empty($plugin['form callback'])) {
        $plugin_form = $plugin['form callback']($form_state, array());
        if ($plugin_form) {
          $form['search_api_location'][$key]['plugin_settings'] += $plugin_form;
        }
      }
      $form['search_api_location'][$key]['radius_type'] = array(
        '#type' => 'select',
        '#title' => t('Type of distance filter'),
        '#options' => $radius_types,
        '#default_value' => 'fixed',
        '#states' => $states,
      );
      $states['visible'][$css_key . '-radius-type']['value'] = 'fixed';
      $form['search_api_location'][$key]['radius_value'] = array(
        '#type' => 'textfield',
        '#title' => t('Maximum distance from location (in kilometers)'),
        '#default_value' => 5,
        '#states' => $states,
      );
      $states['visible'][$css_key . '-radius-type']['value'] = 'textfield';
      $form['search_api_location'][$key]['radius_unit'] = array(
        '#type' => 'select',
        '#title' => t('Unit used'),
        '#options' => search_api_location_get_units(),
        '#default_value' => '1',
        '#states' => $states,
      );
      $states['visible'][$css_key . '-radius-type']['value'] = 'select';
      $form['search_api_location'][$key]['radius_options'] = array(
        '#type' => 'textarea',
        '#title' => t('Distance options'),
        '#description' => t('Add one line per option for “Distance” you want to provide. The first part of each line is the distance in kilometers, everything after the first space is the label. Include a line starting with "- " to include a no-op option.'),
        '#default_value' => "- -\n5 5 km\n10 10 km\n16.0935 10 mi",
        '#states' => $states,
      );
    }
  }
}

/**
 * AJAX callback for the plugin settings.
 */
function search_api_location_page_ajax_callback(array $form, array &$form_state) {
  return !empty($form['search_api_location']) ? $form['search_api_location'] : $form['options']['search_api_location'];
}

/**
 * Additional validation function for the altered search page add / edit form.
 */
function search_api_location_page_edit_validate(array $form, array &$form_state) {
  $form_values =& $form_state['values'];

  // Little trick to work with both the add and the edit form.
  if (!isset($form_values['search_api_location'])) {
    if (isset($form_values['options'])) {
      unset($form_values);
      $form_values =& $form_state['values']['options'];
      $form = $form['options'];
    }
    else {
      return;
    }
  }

  // Loop over all location fields.
  foreach ($form_values['search_api_location'] as $key => $values) {
    if (empty($values['enabled'])) {
      continue;
    }
    $sub_form = $form['search_api_location'][$key];
    switch ($values['radius_type']) {
      case 'fixed':
        $value = $values['radius_value'];
        if ($value != '' && !is_numeric($value) || (double) $value < 0) {
          form_error($sub_form['radius_value'], t('Maximum distance must be a non-negative number.'));
        }
        break;
      case 'select':
        $options = array();
        $lines = array_filter(array_map('trim', explode("\n", $values['radius_options'])));
        foreach ($lines as $line) {
          list($range, $label) = explode(' ', $line, 2);
          if ($range == '-') {
            $range = NULL;
          }
          elseif (is_numeric($range)) {
            $range = (double) $range;
          }
          else {
            continue;
          }
          $options[$range] = trim($label);
        }
        if (empty($options[$range])) {
          form_error($sub_form['radius_options'], t('Specify at least one distance option.'));
        }
        $form_values['search_api_location'][$key]['radius_options'] = $options;
        break;
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function search_api_location_page_form_search_api_page_admin_edit_alter(&$form, &$form_state) {
  $page = $form_state['page'];
  $index = search_api_index_load($page->index_id);
  if ($fields = search_api_location_get_location_fields($index)) {
    $options = isset($page->options['search_api_location']) ? $page->options['search_api_location'] : array();
    $form['#validate'][] = 'search_api_location_page_edit_validate';
    $form['options']['search_api_location'] = array(
      '#type' => 'fieldset',
      '#title' => t('Location filters'),
      '#collapsible' => TRUE,
      '#tree' => TRUE,
      '#prefix' => '<div id="search-api-loaction-fieldset">',
      '#suffix' => '</div>',
    );
    $form['submit']['#weight'] = 5;
    $radius_types = array(
      'fixed' => t('Fixed value'),
      'textfield' => t('Text field'),
      'select' => t('Select field'),
    );
    $plugin_options = search_api_location_get_input_plugin_options();
    $plugin_default = key($plugin_options);

    // Use a reference here so we don't have to check for isset().
    $values =& $form_state['values']['options']['search_api_location'];
    foreach ($fields as $key => $field) {
      $css_key = '#edit-options-search-api-location-' . drupal_clean_css_identifier($key);
      $states['visible'][$css_key . '-enabled']['checked'] = TRUE;
      $options += array(
        $key => array(),
      );
      $options[$key] += array(
        'enabled' => FALSE,
        'compact' => FALSE,
        'plugin' => $plugin_default,
        'plugin_settings' => array(),
        'radius_type' => 'fixed',
        'radius_value' => 5,
        'radius_unit' => '1',
        'radius_options' => "- -\n5 5 km\n10 10 km\n16.0935 10 mi",
      );
      if (is_array($options[$key]['radius_options'])) {
        $radius_options = array();
        foreach ($options[$key]['radius_options'] as $range => $label) {
          $range = isset($range) ? $range : '-';
          $radius_options[] = "{$range} {$label}";
        }
        $options[$key]['radius_options'] = implode("\n", $radius_options);
      }
      $form['options']['search_api_location'][$key] = array(
        '#type' => 'fieldset',
        '#title' => check_plain($field['name']),
        '#collapsible' => TRUE,
      );
      $form['options']['search_api_location'][$key]['enabled'] = array(
        '#type' => 'checkbox',
        '#title' => t('Include location filter'),
        '#default_value' => $options[$key]['enabled'],
      );
      $form['options']['search_api_location'][$key]['compact'] = array(
        '#type' => 'checkbox',
        '#title' => t('Show filter in the search block'),
        '#default_value' => $options[$key]['compact'],
        '#states' => $states,
      );
      $form['options']['search_api_location'][$key]['plugin'] = array(
        '#type' => 'select',
        '#title' => t('Input method'),
        '#description' => t('Select the method to use for parsing locations entered by the user.'),
        '#options' => $plugin_options,
        '#default_value' => $options[$key]['plugin'],
        '#states' => $states,
        '#ajax' => array(
          'callback' => 'search_api_location_page_ajax_callback',
          'wrapper' => 'search-api-loaction-fieldset',
        ),
      );
      $plugin_id = !empty($values[$key]['plugin']) ? $values[$key]['plugin'] : $options[$key]['plugin'];
      $plugin = search_api_location_get_input_plugins($plugin_id);
      $form['options']['search_api_location'][$key]['plugin_settings'] = array(
        '#type' => 'fieldset',
        '#title' => t('Input method settings'),
        '#description' => $plugin['description'],
        '#states' => $states,
      );
      if (!empty($plugin['form callback'])) {
        $plugin_options = $plugin_id == $options[$key]['plugin'] ? $options[$key]['plugin_settings'] : array();
        $plugin_form = $plugin['form callback']($form_state, $plugin_options);
        if ($plugin_form) {
          $form['options']['search_api_location'][$key]['plugin_settings'] += $plugin_form;
        }
      }
      $form['options']['search_api_location'][$key]['radius_type'] = array(
        '#type' => 'select',
        '#title' => t('Type of distance filter'),
        '#options' => $radius_types,
        '#default_value' => $options[$key]['radius_type'],
        '#states' => $states,
      );
      $states['visible'][$css_key . '-radius-type']['value'] = 'fixed';
      $form['options']['search_api_location'][$key]['radius_value'] = array(
        '#type' => 'textfield',
        '#title' => t('Maximum distance from location (in kilometers)'),
        '#default_value' => $options[$key]['radius_value'],
        '#states' => $states,
      );
      $states['visible'][$css_key . '-radius-type']['value'] = 'textfield';
      $form['options']['search_api_location'][$key]['radius_unit'] = array(
        '#type' => 'select',
        '#title' => t('Unit used'),
        '#options' => search_api_location_get_units(),
        '#default_value' => $options[$key]['radius_unit'],
        '#states' => $states,
      );
      $states['visible'][$css_key . '-radius-type']['value'] = 'select';
      $form['options']['search_api_location'][$key]['radius_options'] = array(
        '#type' => 'textarea',
        '#title' => t('Distance options'),
        '#description' => t('Add one line per option for “Distance” you want to provide. The first part of each line is the distance in kilometers, everything after the first space is the label. Include a line starting with "- " to include a no-op option.'),
        '#default_value' => $options[$key]['radius_options'],
        '#states' => $states,
      );
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function search_api_location_page_form_search_api_page_search_form_alter(&$form, &$form_state) {

  // Handle both compact and complete form.
  $compact = TRUE;
  $orig_form =& $form;
  if (isset($form['form'])) {
    $form =& $form['form'];
    $compact = FALSE;
  }
  $page = search_api_page_load($form['id']['#value']);
  $index = search_api_index_load($page->index_id);
  $fields = search_api_location_get_location_fields($index);
  if ($fields && !empty($page->options['search_api_location'])) {
    $orig_form['#submit'][] = 'search_api_location_page_search_form_submit';
    $units = search_api_location_get_units();
    $form['submit_' . $page->id]['#weight'] = 5;
    foreach ($page->options['search_api_location'] as $field => $options) {
      if (empty($fields[$field]) || empty($options['enabled']) || $compact && empty($options['compact'])) {
        continue;
      }
      $name = $fields[$field]['name'];
      $name_parts = explode(' » ', $name);
      if (count($name_parts) > 1) {
        $name = $name_parts[count($name_parts) - 2];
      }
      $form[$field] = array(
        '#type' => 'fieldset',
        '#title' => check_plain($name),
        '#attributes' => array(
          'class' => array(
            'container-inline',
          ),
        ),
      );
      $default = !empty($_GET['location']) ? $_GET['location'] : (!empty($_GET["{$field}-location"]) ? $_GET["{$field}-location"] : '');
      $form[$field]["{$field}-location"] = array(
        '#type' => 'textfield',
        '#size' => $compact ? 10 : 20,
        '#default_value' => $default,
      );
      switch ($options['radius_type']) {
        case 'textfield':
          $form[$field]["{$field}-radius"] = array(
            '#type' => 'textfield',
            '#title' => $compact ? t('within') : t('Maximum distance'),
            '#size' => $compact ? 3 : 5,
            '#element_validate' => array(
              'search_api_location_validate_distance',
            ),
          );
          if (isset($units[$options['radius_unit']])) {
            $form[$field]["{$field}-radius"]['#suffix'] = '<div>' . $units[$options['radius_unit']] . '</div>';
          }
          break;
        case 'select':
          $form[$field]["{$field}-radius"] = array(
            '#type' => 'select',
            '#title' => $compact ? t('within') : t('Maximum distance'),
            '#options' => $options['radius_options'],
          );
          break;
      }
      $default = !empty($_GET['radius']) ? $_GET['radius'] : (!empty($_GET["{$field}-radius"]) ? $_GET["{$field}-radius"] : '');
      if ($default) {
        $form[$field]["{$field}-radius"]['#default_value'] = $default;
      }
    }
  }
}

/**
 * Form submission handler for the altered search_api_page_search_form().
 *
 * @see search_api_location_page_search_api_page_search_form_validate()
 */
function search_api_location_page_search_form_submit(array $form, array &$form_state) {
  if (is_array($form_state['redirect'])) {
    list($redirect, $url_options) = $form_state['redirect'];
  }
  else {
    $redirect = $form_state['redirect'];
    $url_options = array();
  }
  $url_options += array(
    'query' => array(),
  );
  $page = search_api_page_load($form_state['values']['id']);
  $num = 0;
  foreach ($page->options['search_api_location'] as $field) {
    if (!empty($field['enabled'])) {
      ++$num;
    }
  }
  if ($num == 1) {
    $params = _search_api_location_page_filter_parameters($form_state['values'], FALSE);
    $location = reset($params);
    $field = key($params);
    if ($field) {
      $field = substr($field, 0, -9);
      $url_options['query']['location'] = $location;
      if (!empty($form_state['values'][$field . '-radius'])) {
        $url_options['query']['radius'] = $form_state['values'][$field . '-radius'];
      }
    }
  }
  else {
    $url_options['query'] += _search_api_location_page_filter_parameters($form_state['values']);
  }
  if ($url_options) {
    $form_state['redirect'] = array(
      $redirect,
      $url_options,
    );
  }
}

/**
 * Implements hook_search_api_query_alter().
 */
function search_api_location_page_search_api_query_alter(SearchApiQueryInterface $query) {
  if (!preg_match('/^search_api_page:(.*)$/', $query
    ->getOption('search id'), $m)) {
    return;
  }
  $page = search_api_page_load_multiple(FALSE, array(
    'path' => $m[1],
  ));
  $page = reset($page);
  if (!$page) {
    return;
  }
  $location_options = $query
    ->getOption('search_api_location', array());
  if (!empty($_GET['location'])) {
    foreach ($page->options['search_api_location'] as $key => $field) {
      if (!empty($field['enabled'])) {
        $plugin = search_api_location_get_input_plugins($field['plugin']);
        if (!$plugin) {
          return;
        }
        $plugin_options = !empty($field['plugin_settings']) ? $field['plugin_settings'] : array();
        $location = $plugin['input callback']($_GET['location'], $plugin_options);
        if (!$location) {
          drupal_set_message(t('The location %address could not be resolved and was ignored.', array(
            '%address' => $_GET['location'],
          )), 'warning');
          return;
        }
        $location = explode(',', $location, 2);
        $filter = array(
          'field' => $key,
          'lat' => $location[0],
          'lon' => $location[1],
        );
        if (isset($_GET['radius'])) {
          $filter['radius'] = $_GET['radius'];
          if ($field['radius_type'] == 'textfield' && is_numeric($field['radius_unit'])) {
            $filter['radius'] *= $field['radius_unit'];
          }
        }
        $location_options[] = $filter;
        break;
      }
    }
  }
  else {
    foreach (_search_api_location_page_filter_parameters($_GET, FALSE) as $key => $value) {
      $field = substr($key, 0, -9);
      $field_options = $page->options['search_api_location'][$field];
      $plugin = search_api_location_get_input_plugins($field_options['plugin']);
      if (!$plugin) {
        continue;
      }
      $plugin_options = !empty($field_options['plugin_settings']) ? $field_options['plugin_settings'] : array();
      $location = $plugin['input callback']($value, $plugin_options);
      if (!$location) {
        drupal_set_message(t('The location %address could not be resolved and was ignored.', array(
          '%address' => $value,
        )), 'warning');
        continue;
      }
      unset($radius);
      if (!empty($_GET[$field . '-radius'])) {
        $radius = $_GET[$field . '-radius'];
      }
      elseif (!empty($field_options['radius_type'])) {
        if ($field_options['radius_type'] == 'fixed') {
          $radius = $field_options['radius_value'];
        }
      }
      $location = explode(',', $location, 2);
      $filter = array(
        'field' => $field,
        'lat' => $location[0],
        'lon' => $location[1],
      );
      if (isset($radius)) {
        $filter['radius'] = $radius;
        $field = $field_options;
        if ($field['radius_type'] == 'textfield' && is_numeric($field['radius_unit'])) {
          $filter['radius'] *= $field['radius_unit'];
        }
      }
      $location_options[] = $filter;
    }
  }
  if ($location_options) {
    $query
      ->setOption('search_api_location', $location_options);
  }
}

/**
 * Helper function for returning those entries of a parameter array relevant to location search.
 *
 * These are the ones with non-empty values and keys ending on "-location" or
 * (when $include_radius is set) "-radius". A radius without location is always
 * ignored.
 */
function _search_api_location_page_filter_parameters(array $params, $include_radius = TRUE) {
  $ret = array();
  foreach ($params as $key => $value) {
    if (!$value) {
      continue;
    }
    $pos = strrpos($key, '-');
    if (!$pos) {
      continue;
    }
    if (substr($key, $pos + 1) == 'location') {
      $ret[$key] = $value;
      if ($include_radius && isset($params[$key = substr($key, 0, $pos) . '-radius'])) {
        $ret[$key] = $params[$key];
      }
    }
  }
  return $ret;
}