social_geolocation-8.x-1.2/modules/social_geolocation_search/social_geolocation_search.module

modules/social_geolocation_search/social_geolocation_search.module
<?php

/**
 * @file
 * Contains hook implementations for the Social Geolocation Search module.
 */

use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\group\Entity\GroupType;
use Drupal\search_api\Plugin\views\query\SearchApiQuery;
use Drupal\search_api\Query\QueryInterface;

// The factor that is used to convert miles to kilometers.
const SOCIAL_GEOLOCATION_SEARCH_MI_TO_KM = 1.609344;

// The radius of the earth in kilometers.
const SOCIAL_GEOLOCATION_SEARCH_EARTH_RADIUS = 6371;

/**
 * Implements hook_help().
 */
function social_geolocation_search_help($route_name, RouteMatchInterface $route_match): ?string {
  switch ($route_name) {
    case 'help.page.social_geolocation_search':
      return 'This module allows you to find events, groups and users by location in search. It works with a SQL and SOLR back-end.';
  }
  return NULL;
}

/**
 * Retrieves the arguments for the search views.
 *
 * All returned values are normalised to kilometers.
 *
 * @return array
 *   An array containing proximity, proximity-lat, proximity-lng or an empty
 *   array if one of the values was missing.
 */
function _social_geolocation_search_get_proximity_arguments(): array {
  $values = [];

  $args = \Drupal::request()->query->all();

  // Filter out the arguments needed without iterating over a possibly long list
  // of arguments.
  foreach (['proximity', 'proximity-lat', 'proximity-lng'] as $key) {
    // If one of the keys is missing none can be used.
    if (empty($args[$key])) {
      return [];
    }
    $values[$key] = $args[$key];
  }

  // Check whether we use miles or kilometers for our proximity.
  $unit_of_measurement = \Drupal::config('social_geolocation.settings')->get('unit_of_measurement');
  if ($unit_of_measurement === 'mi') {
    $values['proximity'] *= SOCIAL_GEOLOCATION_SEARCH_MI_TO_KM;
  }

  return $values;
}

/**
 * Implements hook_search_api_db_query_alter().
 *
 * Alters the search API query for the database backend to implement proximity
 * searching. This is needed because there seems to be no module that properly
 * provides this for a database backend. Additionally actual SQL is needed to
 * calculate the distances so this can't be used with other backends.
 */
function social_geolocation_search_api_db_query_alter(SelectInterface $db_query, QueryInterface $query): void {
  $args = _social_geolocation_search_get_proximity_arguments();

  // If there is no proximity data then there is no alter to perform.
  if (empty($args)) {
    return;
  }

  // Get the tables affected by this query.
  $tables = $db_query->getTables();
  $index = $query->getIndex()->id();
  $table = 'search_api_db_' . $index;

  // If we have only one table, that's our table.
  if (count($tables) === 1) {
    $aliases = array_keys($tables);
    $alias = reset($aliases);
  }
  // With multiple tables, check that we're using the index table.
  else {
    $found = FALSE;

    foreach ($tables as $alias => $data) {
      if ($data['table'] === $table) {
        $found = TRUE;
        break;
      }
    }

    // Return if this query does not have the index table (should never happen?)
    if (!$found) {
      return;
    }
  }

  // Add a lot of access checks for anonymous users.
  // https://bitbucket.org/goalgorilla/pachamama/commits/238ea4f08d7caca20cb92f393d9df968fec60a2e
  // https://bitbucket.org/goalgorilla/pachamama/commits/d79cd6713333f0a1008924613ab89ab1b6c073cf
  if (\Drupal::currentUser()->isAnonymous()) {
    $group_types = GroupType::loadMultiple();
    $group_type_ids = [];

    // Collect the group types that anonymous users are allowed to see.
    // (This probably won't work for Flexible groups)
    /** @var \Drupal\group\Entity\GroupTypeInterface $group_type */
    foreach ($group_types as $group_type_id => $group_type) {
      $anonymous_role = \Drupal::entityTypeManager()
        ->getStorage('group_role')
        ->load($group_type->id() . '-anonymous');
      if ($anonymous_role->hasPermission('view group')) {
        $group_type_ids[] = $group_type_id;
      }
    }

    if ($index === 'social_all') {
      // Field specifying the index' data source (e.g. entity:group,
      // entity:profile, etc.)
      $source_field = $alias . '.search_api_datasource';
      $entity_type = 'entity:group';

      // If anonymous users are allowed to see groups then we limit results
      // for groups to be of that group type.
      if (!empty($group_type_ids)) {
        $and = $db_query->andConditionGroup()
          ->condition($source_field, $entity_type)
          ->condition("$alias.group_type", $group_type_ids, 'IN');

        $or = $db_query->orConditionGroup()
          ->condition($source_field, $entity_type, '<>')
          ->condition($and);

        $db_query->condition($or);
      }
      // If a user is not allowed to see any groups then we remove groups from
      // the results altogether.
      else {
        $db_query->condition($source_field, $entity_type, '<>');
      }
    }

    // For group searches we limit results to group types the AN user is
    // allowed to see.
    if ($index === 'social_groups') {
      if (!empty($group_type_ids)) {
        $db_query->condition("$alias.type", $group_type_ids, 'IN');
      }
      else {
        return;
      }
    }
  }

  // No idea why we join the base table with itself if it's not there yet.
  if (count($tables) == 1 && !is_string($tables[$alias]['table'])) {
    $new_alias = $alias . '_base';
    $db_query->join($table, $new_alias, "$alias.item_id = $new_alias.item_id");
    $alias = $new_alias;
  }

  $snippet = '';

  // Entity types that can have location data for each index.
  $entity_types = [
    'social_content' => 'node',
    'social_groups' => 'group',
    'social_users' => 'profile',
  ];
  if ($index === 'social_all') {
    $entity_types = array_values($entity_types);
  }
  else {
    $entity_types = [$entity_types[$index]];
  }

  // Add a complex snippet.
  foreach ($entity_types as $id => $entity_type) {
    if ($id) {
      $snippet .= ') OR (';
    }

    $snippet .= "( $alias.search_api_datasource = 'entity:$entity_type' ) AND (
      ( ACOS(LEAST(1,
        :filter_latcos
        * $alias.{$entity_type}_lat_cos
        * COS( :filter_lng - $alias.{$entity_type}_lng_rad  )
        +
        :filter_latsin
        * $alias.{$entity_type}_lat_sin
      )) * :earth_radius
    ) < :proximity )";
  }

  $db_query->where("(( $snippet ))", [
    ':filter_latcos' => cos(deg2rad($args['proximity-lat'])),
    ':filter_lng' => deg2rad($args['proximity-lng']),
    ':filter_latsin' => sin(deg2rad($args['proximity-lat'])),
    ':earth_radius' => SOCIAL_GEOLOCATION_SEARCH_EARTH_RADIUS,
    ':proximity' => $args['proximity'],
  ]);
}

/**
 * Alternative to the search_api_solr alter hook.
 *
 * This method is called by social_geolocation_search_views_query_alter().
 *
 * Alters the search API query for the SOLR backend to provide location based
 * searching. This is done to complement the databae query alter. It's performed
 * here instead of in a contrib module so we can work with the existing fields.
 */
function _social_geolocation_search_alter_solr_query(SearchApiQuery $query): void {
  $args = _social_geolocation_search_get_proximity_arguments();

  // If there is no proximity data then there is no alter to perform.
  if (empty($args)) {
    return;
  }

  $datasource_ids = $query->getIndex()->getDatasourceIds();

  // Location searches can't be performed for multiple entity types at the same
  // time.
  if (count($datasource_ids) > 1) {
    return;
  }

  // The datasource is specified as type:type_id.
  // e.g. entity:profile for profile entities.
  $datasource = explode(':', $datasource_ids[0]);
  [$datasource_type, $entity_type] = $datasource;

  // Only entity based searches are supported.
  if ($datasource_type !== 'entity') {
    return;
  }

  $location_options = $query->getOption('search_api_location', []);
  $location_options[] = [
    // The field as known to our backend.
    'field' => "{$entity_type}_geolocation",
    // The search latitude and longitude.
    'lat' => $args['proximity-lat'],
    'lon' => $args['proximity-lng'],
    // The search radius.
    'radius' => $args['proximity'],
  ];
  $query->setOption('search_api_location', $location_options);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Enhance the Views exposed filter blocks forms.
 */
function social_geolocation_search_form_views_exposed_form_alter(&$form, FormStateInterface $form_state, $form_id): void {
  $filter_forms = [
    'views-exposed-form-search-all-page',
    'views-exposed-form-search-all-page-no-value',
    'views-exposed-form-search-content-page',
    'views-exposed-form-search-content-page-no-value',
    'views-exposed-form-search-users-page',
    'views-exposed-form-search-users-page-no-value',
    'views-exposed-form-search-groups-page',
    'views-exposed-form-search-groups-page-no-value',
  ];

  if (!in_array($form['#id'], $filter_forms, TRUE)) {
    return;
  }

  social_geolocation_attach_views_location_filter($form);

  // On the content search page the location filtering is only available when
  // the user is searching for events specifically as other node types don't
  // have location data at this moment.
  if (isset($form['location_details']) && in_array($form['#id'], ['views-exposed-form-search-content-page', 'views-exposed-form-search-content-page-no-value'], TRUE)) {
    $form['location_details']['#states'] = [
      'visible' => [
        ':input[name=type]' => [
          'value' => 'event',
        ],
      ],
    ];
  }
}

/**
 * Implements hook_block_view_BASE_BLOCK_ID_alter().
 *
 * Enhance the Views exposed filter blocks.
 */
function social_geolocation_search_block_view_views_exposed_filter_block_alter(array &$build, BlockPluginInterface $block): void {
  $filter_blocks = [
    'search_all-page',
    'search_groups-page',
  ];

  if (in_array($build['#derivative_plugin_id'], $filter_blocks)) {
    // Disable cache for exposed filter block to get correct current path,
    // which is used in $form['#action'].
    $build['#cache'] = [
      'max-age' => 0,
    ];
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc