You are here

geocoder_field.module in Geocoder 8.2

Geocoder Field module.

File

modules/geocoder_field/geocoder_field.module
View source
<?php

/**
 * @file
 * Geocoder Field module.
 */
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldConfigInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Component\Plugin\Exception\PluginException;
use Geocoder\Model\AddressCollection;

/**
 * Implements hook_form_alter().
 *
 * Eventually Disable or Hide Geocode Fields.
 */
function geocoder_field_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  if ($form_state
    ->getFormObject() instanceof ContentEntityFormInterface) {

    /* @var \Drupal\Core\Entity\EntityForm $entity_form */
    $entity_form = $form_state
      ->getFormObject();

    /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $entity_form
      ->getEntity();
    _geocoder_field_alter_form_fields($entity, $form);
  }
}

/**
 * Implements hook_inline_entity_form_entity_form_alter().
 *
 * Eventually Disable or Hide Geocode Fields.
 */
function geocoder_field_inline_entity_form_entity_form_alter(&$entity_form, FormStateInterface &$form_state) {

  /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  $entity = $entity_form['#entity'];
  _geocoder_field_alter_form_fields($entity, $entity_form);
}

/**
 * Helper function for disable or hide geocode fields.
 *
 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
 *   Entity.
 * @param array $form
 *   Form.
 */
function _geocoder_field_alter_form_fields(ContentEntityInterface $entity, array &$form) {
  foreach ($entity
    ->getFields() as $field_name => $field) {

    /** @var \Drupal\Core\Field\FieldConfigInterface $field_config */
    if (!($field_config = $field
      ->getFieldDefinition()) instanceof FieldConfigInterface) {

      // Only configurable fields are subject of geocoding.
      continue;
    }

    // Eventually Disable the Geocoded Field.
    $geocoder = $field_config
      ->getThirdPartySettings('geocoder_field');
    if (isset($geocoder['method']) && $geocoder['method'] !== 'none') {
      if (isset($geocoder['disabled']) && $geocoder['disabled'] == TRUE) {
        $form[$field_name]['#disabled'] = TRUE;
      }

      // Eventually Hide the Geocoded Field.
      if (isset($geocoder['hidden']) && $geocoder['hidden'] == TRUE) {
        $form[$field_name]['#access'] = FALSE;
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function geocoder_field_form_field_config_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {

  /** @var \Drupal\Core\Field\FieldConfigInterface $field */
  $field = $form_state
    ->getFormObject()
    ->getEntity();

  /** @var \Drupal\geocoder_field\GeocoderFieldPluginInterface $field_plugin */
  if (!($field_plugin = \Drupal::service('geocoder_field.plugin.manager.field')
    ->getPluginByFieldType($field
    ->getType()))) {

    // There's no geocoding field plugin to handle this type of field.
    return;
  }
  $form['third_party_settings']['geocoder_field'] = $field_plugin
    ->getSettingsForm($field, $form, $form_state);

  // Temporary store the field plugin to be used in the validation phase.
  $form['third_party_settings']['geocoder_field']['field_plugin'] = [
    '#type' => 'value',
    '#value' => $field_plugin,
  ];
  $form['#validate'][] = 'geocoder_field_field_config_edit_form_validate';
}

/**
 * Provides an additional form validation callback for 'field_config_edit_form'.
 *
 * @param array $form
 *   A form API form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state object.
 */
function geocoder_field_field_config_edit_form_validate(array $form, FormStateInterface $form_state) {

  // Don't store any settings for this field if it's not configured to use
  // geocoding.
  $third_party_settings = $form_state
    ->getValue('third_party_settings');
  if ($third_party_settings['geocoder_field']['method'] == 'none') {
    unset($third_party_settings['geocoder_field']);
    $form_state
      ->setValue('third_party_settings', $third_party_settings);
    return;
  }

  // Clean-up and normalize the plugin list.
  $trail = [
    'third_party_settings',
    'geocoder_field',
    'plugins',
  ];
  $plugins = array_keys(array_filter($form_state
    ->getValue($trail), function ($item) {
    return (bool) $item['checked'];
  }));
  $form_state
    ->setValue($trail, $plugins);

  // Give a chance to the geocoder field plugin to perform its own validation.
  $geocoder_data = (new FormState())
    ->setValues($form_state
    ->getValue([
    'third_party_settings',
    'geocoder_field',
  ]));
  $trail = [
    'third_party_settings',
    'geocoder_field',
    'field_plugin',
  ];

  /** @var \Drupal\geocoder_field\GeocoderFieldPluginInterface $field_plugin */
  $field_plugin = $form_state
    ->getValue($trail);
  $field_plugin
    ->validateSettingsForm($form, $geocoder_data);

  // Update the $form_state with the $geocoder_data possibly altered in the
  // validateSettingsForm method.
  $form_state
    ->setValue([
    'third_party_settings',
    'geocoder_field',
  ], $geocoder_data
    ->getValues());

  // Copy back any error.
  foreach ($geocoder_data
    ->getErrors() as $name => $error) {
    $form_state
      ->setErrorByName($name, $error);
  }

  // Unset temporary field plugin value.
  $form_state
    ->unsetValue($trail);
}

/**
 * Implements hook_entity_presave().
 */
function geocoder_field_entity_presave(EntityInterface $entity) {

  /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  if (!$entity instanceof ContentEntityInterface) {
    return;
  }
  $geocoder_config = \Drupal::configFactory()
    ->get('geocoder.settings');

  // Check geocoder_presave_disabled setting and do nothing if enabled.
  if ($geocoder_config
    ->get('geocoder_presave_disabled')) {
    return;
  }

  /** @var \Drupal\geocoder_field\PreprocessorPluginManager $preprocessor_manager */
  $preprocessor_manager = \Drupal::service('plugin.manager.geocoder.preprocessor');

  /** @var \Drupal\geocoder\DumperPluginManager $dumper_manager */
  $dumper_manager = \Drupal::service('plugin.manager.geocoder.dumper');

  // Reorder the list of fields to be Geocoded | Reverse Geocoded.
  $order_geocoder_fields = $preprocessor_manager
    ->getOrderedGeocodeFields($entity);
  foreach ($order_geocoder_fields as $field_name => $field) {

    /** @var \Drupal\Core\Field\FieldConfigInterface $field_config */
    if (!($field_config = $field
      ->getFieldDefinition()) instanceof FieldConfigInterface) {

      // Only configurable fields are subject of geocoding.
      continue;
    }
    $geocoder = $field_config
      ->getThirdPartySettings('geocoder_field');
    if (empty($geocoder['method']) || $geocoder['method'] === 'none') {

      // This field was not configured to geocode/reverse_geocode from/to
      // other field.
      continue;
    }
    switch ($geocoder['method']) {
      case 'source':

        /* @var Drupal\Core\Field\FieldItemListInterface $remote_field */
        $remote_field = isset($geocoder['geocode_field']) ? $entity
          ->get($geocoder['geocode_field']) : NULL;
        if (isset($entity->original) && isset($geocoder['geocode_field'])) {

          /* @var Drupal\Core\Field\FieldItemListInterface $original_field */
          $original_field = $entity->original
            ->get($geocoder['geocode_field']);
        }
        break;
      case 'destination':
        $remote_field = isset($geocoder['reverse_geocode_field']) ? $entity
          ->get($geocoder['reverse_geocode_field']) : NULL;
        if (isset($entity->original) && isset($geocoder['reverse_geocode_field'])) {

          /* @var Drupal\Core\Field\FieldItemListInterface $original_field */
          $original_field = $entity->original
            ->get($geocoder['reverse_geocode_field']);
        }
        break;
      default:
    }
    if (isset($remote_field)) {

      // Skip any action if:
      // remote value is null and the field value should be preserved
      // geofield has value and remote field value has not changed.
      if (empty($remote_field
        ->getValue()) && $geocoder['failure']['handling'] == 'preserve' || isset($original_field) && !$entity
        ->get($field_name)
        ->isEmpty() && $preprocessor_manager
        ->sourceFieldIsSameOfOriginal($remote_field, $original_field)) {
        continue;
      }

      // First we need to Pre-process field.
      // Note: in case of Address module integration this creates the
      // value as formatted address.
      $preprocessor_manager
        ->preprocess($remote_field);
      try {

        /** @var \Drupal\geocoder\DumperInterface|\Drupal\Component\Plugin\PluginInspectionInterface $dumper */
        $dumper = $dumper_manager
          ->createInstance($geocoder['dumper']);
      } catch (PluginException $e) {
        \Drupal::logger('geocoder')
          ->error('No Dumper has been set');
      }
      $default_values = $entity
        ->get($field_name);
      $result = [];
      $failure_status_message = NULL;

      // Skip the Geocode/Reverse Geocode operation is so requested for not
      // empty coordinates.
      if (isset($geocoder['skip_not_empty_value']) && $geocoder['skip_not_empty_value'] && isset($default_values) && !$default_values
        ->isEmpty()) {
        return;
      }
      foreach ($remote_field
        ->getValue() as $delta => $value) {
        if ($remote_field
          ->getFieldDefinition()
          ->getType() == 'address_country') {

          /** @var \Drupal\Core\Locale\CountryManager $country_manager */
          $country_manager = \Drupal::service('country_manager');
          $value['value'] = $country_manager
            ->getList()[$value['value']];
        }

        /* @var \Geocoder\Model\AddressCollection|\Geometry $geo_collection */
        switch ($geocoder['method']) {
          case 'source':
            $failure_status_message = t("Unable to geocode '@text'.", [
              '@text' => isset($value['value']) ? $value['value'] : '',
            ]);
            $geo_collection = isset($value['value']) ? \Drupal::service('geocoder')
              ->geocode($value['value'], $geocoder['plugins']) : NULL;
            break;
          case 'destination':
            $failure_status_message = t("Unable to reverse geocode the <em>@field_name</em> value.", [
              '@field_name' => $field_name,
            ]);
            $geo_collection = isset($value['lat']) && isset($value['lon']) ? \Drupal::service('geocoder')
              ->reverse($value['lat'], $value['lon'], $geocoder['plugins']) : NULL;
            break;
          default:
        }
        if (isset($dumper) && $geo_collection && !empty($geo_collection)) {

          // Normally each geocode/reverse op would return an AddressCollection.
          if ($geo_collection instanceof AddressCollection) {
            switch ($geocoder['delta_handling']) {

              // Single-to-multiple handling - if we can, explode out the
              // component geometries.
              case 's_to_m':
                foreach ($geo_collection
                  ->all() as $collection_delta => $address) {
                  $this_result = $dumper
                    ->dump($address);

                  // Check|Fix some incompatibility between Dumper output and
                  // Field Config.
                  $dumper_manager
                    ->fixDumperFieldIncompatibility($this_result, $dumper, $field_config);
                  $result[] = $this_result;
                }
                break;

              // Default delta handling: just pass one delta to the next.
              default:
                $result[$delta] = $dumper
                  ->dump($geo_collection
                  ->first());

                // Check|Fix some incompatibility between Dumper output and
                // Field Config.
                $dumper_manager
                  ->fixDumperFieldIncompatibility($result[$delta], $dumper, $field_config);

                // If an Address field is being processed, transform its Dumper
                // result into array to comply to Address entity->set Api.
                if ($field_config
                  ->getType() == 'address' && $dumper
                  ->getPluginId() == 'geojson') {
                  $result[$delta] = $dumper_manager
                    ->setAddressFieldFromGeojson($result[$delta]);
                }

                // Or an Address Country field is being processed ...
                if ($field_config
                  ->getType() === 'address_country' && $dumper
                  ->getPluginId() === 'geojson') {
                  $result[$delta] = $dumper_manager
                    ->setCountryFromGeojson($result[$delta]);
                }
            }
            continue;
          }
          elseif ($geo_collection instanceof Geometry && array_key_exists($dumper
            ->getPluginId(), \Drupal::service('geofield.geophp')
            ->getAdapterMap())) {

            // Single-to-multiple handling - if we can, explode out the
            // component geometries.
            if ($geo_collection instanceof GeometryCollection && $geocoder['delta_handling'] == 's_to_m') {

              /* @var \GeometryCollection $address_collection */
              if ($geo_collection instanceof GeometryCollection) {
                $components = $geo_collection
                  ->getComponents();

                /* @var \Geometry $component */
                foreach ($components as $component) {
                  $this_result = $component
                    ->out($dumper
                    ->getPluginId());

                  // Check|Fix some incompatibility between Dumper output and
                  // Field Config.
                  $dumper_manager
                    ->fixDumperFieldIncompatibility($this_result, $dumper, $field_config);
                  $result[] = $this_result;
                }
              }
            }
            else {
              $result[] = $geo_collection
                ->out($dumper
                ->getPluginId());

              // Check|Fix some incompatibility between result and Field Config.
              $dumper_manager
                ->fixDumperFieldIncompatibility($result[$delta], $dumper, $field_config);
            }
            continue;
          }
        }
        switch ($geocoder['failure']['handling']) {
          case 'preserve':
            $result[$delta] = isset($default_values[$delta]) ? $default_values[$delta]
              ->getValue() : NULL;
            break;
          case 'empty':
            $result[$delta] = NULL;
            break;
        }

        // Display a status message.
        if ($failure_status_message !== NULL && $geocoder['failure']['status_message']) {
          \Drupal::messenger()
            ->addMessage($failure_status_message, 'warning');
        }

        // Log the failure.
        if ($failure_status_message !== NULL && $geocoder['failure']['log']) {
          \Drupal::logger('geocoder')
            ->warning($failure_status_message);
        }
      }
      $entity
        ->set($field_name, $result);
    }
  }
}