civicrm_entity-8.x-3.0-beta1/src/Plugin/views/filter/Proximity.php
src/Plugin/views/filter/Proximity.php
<?php
namespace Drupal\civicrm_entity\Plugin\views\filter;
use Drupal\views\Attribute\ViewsFilter;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\civicrm_entity\CiviCrmApiInterface;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\Core\Database\Query\Condition;
/**
* Filter handler for proximity.
*
* @ViewsFilter("civicrm_entity_civicrm_address_proximity")
*/
#[ViewsFilter("civicrm_entity_civicrm_address_proximity")]
class Proximity extends FilterPluginBase {
/**
* The CiviCRM API.
*
* @var \Drupal\civicrm_entity\CiviCrmApiInterface
*/
protected $civicrmApi;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, CiviCrmApiInterface $civicrm_api) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->civicrmApi = $civicrm_api;
$this->alwaysMultiple = TRUE;
$this->no_operator = TRUE;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('civicrm_entity.api')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
parent::init($view, $display, $options);
$this->civicrmApi->civicrmInitialize();
\CRM_Contact_BAO_ProximityQuery::initialize();
}
/**
* {@inheritdoc}
*/
public function defineOptions() {
$options = parent::defineOptions();
$options['value'] = [
'contains' => [
'value' => ['default' => ''],
'city' => ['default' => ''],
'state_province_id' => ['default' => ''],
// 'country' => ['default' => ''],
'distance' => ['default' => ''],
'distance_unit' => ['default' => ''],
],
];
return $options;
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
if (!empty($this->options['exposed'])) {
return $this->t('exposed');
}
return $this->t('within @postal_code', ['@postal_code' => $this->value['value']]);
}
/**
* {@inheritdoc}
*/
public function valueForm(&$form, FormStateInterface $form_state) {
$form['value']['#tree'] = TRUE;
$form['value']['#type'] = 'fieldset';
$form['value']['city'] = [
'#type' => 'textfield',
'#title' => $this->t('City'),
'#size' => 30,
'#default_value' => $this->value['city'],
];
$config = \CRM_Core_Config::singleton();
$values = $this->civicrmApi->get('StateProvince', [
'sequential' => 1,
'country_id' => $config->defaultContactCountry,
'options' => ['limit' => 0],
]);
$form['value']['state_province_id'] = [
'#type' => 'select',
'#options' => ['' => $this->t('- Any -')] + array_combine(
array_column($values, 'id'),
array_column($values, 'name')
),
'#title' => $this->t('State/Province'),
'#default_value' => $this->value['state_province_id'],
];
$form['value']['value'] = [
'#type' => 'textfield',
'#title' => $this->t('Postal code'),
'#size' => 30,
'#default_value' => $this->value['value'],
];
$form['value']['distance'] = [
'#type' => 'number',
'#title' => $this->t('Distance'),
'#default_value' => $this->value['distance'],
];
$form['value']['distance_unit'] = [
'#type' => 'select',
'#title' => $this->t('Distance unit'),
'#default_value' => $this->value['distance_unit'],
'#options' => [
'miles' => $this->t('Miles'),
'kilometers' => $this->t('Kilometers'),
],
];
}
/**
* {@inheritdoc}
*/
public function query() {
// Make sure that postal code and distance are set before altering the
// query.
if ((!empty($this->value['value']) || !empty($this->value['city']) || !empty($this->value['state_province_id'])) && !empty($this->value['distance'])) {
$distance = $this->getCalculatedDistance($this->value['distance'], $this->value['distance_unit']);
$config = \CRM_Core_Config::singleton();
$countries = $this
->civicrmApi
->get('Country', [
'sequential' => 1,
'id' => $config->defaultContactCountry,
'return' => ['name'],
]);
$proximity_address = [
'postal_code' => $this->value['value'],
'state_province_id' => $this->value['state_province_id'],
'city' => $this->value['city'],
'country' => !empty($countries) ? $countries[0]['name'] : '',
'country_id' => $config->defaultContactCountry,
'distance_unit' => $this->value['distance_unit'],
];
$geocoded_address = $this->getGeocodedAddress($proximity_address);
$this->view->proximity_center = [
'latitude' => $geocoded_address['latitude'],
'longitude' => $geocoded_address['longitude'],
'distance' => $this->value['distance'],
'distance_unit' => $this->value['distance_unit'],
'distance_meters' => $distance,
];
[$min_longitude, $max_longitude] = \CRM_Contact_BAO_ProximityQuery::earthLongitudeRange($geocoded_address['longitude'], $geocoded_address['latitude'], $distance);
[$min_latitude, $max_latitude] = \CRM_Contact_BAO_ProximityQuery::earthLatitudeRange($geocoded_address['longitude'], $geocoded_address['latitude'], $distance);
$this->ensureMyTable();
$condition = new Condition('AND');
if (!is_nan($min_latitude)) {
$condition->condition("{$this->tableAlias}.geo_code_1", $min_latitude, '>=');
}
if (!is_nan($max_latitude)) {
$condition->condition("{$this->tableAlias}.geo_code_1", $max_latitude, '<=');
}
if (!is_nan($min_longitude)) {
$condition->condition("{$this->tableAlias}.geo_code_2", $min_longitude, '>=');
}
if (!is_nan($max_longitude)) {
$condition->condition("{$this->tableAlias}.geo_code_2", $max_longitude, '<=');
}
if ($condition->count() > 0) {
$this->query->addWhere($this->options['group'], $condition);
}
$expression = "
ACOS(
COS(RADIANS({$this->tableAlias}.geo_code_1)) *
COS(RADIANS({$geocoded_address['latitude']})) *
COS(RADIANS({$this->tableAlias}.geo_code_2) - RADIANS({$geocoded_address['longitude']})) +
SIN(RADIANS({$this->tableAlias}.geo_code_1)) *
SIN(RADIANS({$geocoded_address['latitude']}))
) * 6378137
";
$this->query->addWhereExpression($this->options['group'], "$expression <= $distance");
}
}
/**
* Get the distance.
*
* @param int $distance
* The distance.
* @param string $distance_unit
* The distance unit whether i.e. miles or kilometers.
*
* @return float
* The calculated distance depending on the distance unit.
*
* @see \CRM_Contact_BAO_ProximityQuery::process()
*/
protected function getCalculatedDistance($distance, $distance_unit) {
switch ($distance_unit) {
case 'miles':
$distance *= 1609.344;
break;
case 'kilometers':
default:
$distance *= 1000.00;
break;
}
return $distance;
}
/**
* Get the geocoded data.
*
* @param array $address
* Address based on the format of CRM_Core_BAO_Address::addGeocoderData().
*
* @return array
* An array of geocoded data based on the address.
*
* @see CRM_Core_BAO_Address::addGeocoderData()
*/
protected function getGeocodedAddress(array $address) {
$address = array_filter($address);
if (!\CRM_Core_BAO_Address::addGeocoderData($address)) {
throw new \Exception('Unable to properly geocode address.');
}
return [
'latitude' => $address['geo_code_1'],
'longitude' => $address['geo_code_2'],
];
}
/**
* NEW: Get the processed proximity values for use by distance field.
*/
public function getProximityValues() {
if (empty($this->value) ||
(empty($this->value['value']) && empty($this->value['city']) && empty($this->value['state_province_id'])) ||
empty($this->value['distance'])) {
return [];
}
try {
$config = \CRM_Core_Config::singleton();
$countries = $this->civicrmApi->get('Country', [
'sequential' => 1,
'id' => $config->defaultContactCountry,
'return' => ['name'],
]);
$proximity_address = [
'postal_code' => $this->value['value'],
'state_province_id' => $this->value['state_province_id'],
'city' => $this->value['city'],
'country' => !empty($countries) ? $countries[0]['name'] : '',
'country_id' => $config->defaultContactCountry,
'distance_unit' => $this->value['distance_unit'],
];
$geocoded_address = $this->getGeocodedAddress($proximity_address);
return [
'latitude' => $geocoded_address['latitude'],
'longitude' => $geocoded_address['longitude'],
'distance' => $this->value['distance'],
'distance_unit' => $this->value['distance_unit'],
'distance_meters' => $this->getCalculatedDistance($this->value['distance'], $this->value['distance_unit']),
];
} catch (\Exception $e) {
\Drupal::logger('civicrm_entity')->error('Error getting proximity values: @error', ['@error' => $e->getMessage()]);
return [];
}
}
/**
* Check if proximity filter has valid geocoded coordinates.
*/
public function hasValidProximityData() {
$values = $this->getProximityValues();
return !empty($values['latitude']) && !empty($values['longitude']);
}
}
