You are here

farm_map_geofield.module in farmOS 7

Farm Map Geofield integration.

File

modules/farm/farm_map/farm_map_geofield/farm_map_geofield.module
View source
<?php

/**
 * @file
 * Farm Map Geofield integration.
 */

/**
 * Implements hook_field_formatter_info().
 */
function farm_map_geofield_field_formatter_info() {
  return array(
    'farm_map_geofield' => array(
      'label' => t('farmOS Map'),
      'field types' => array(
        'geofield',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function farm_map_geofield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();

  // First check to see if we have any value and remove any unset deltas.
  foreach ($items as $delta => $item) {
    if (empty($item['geom'])) {
      unset($items[$delta]);
    }
  }

  // If there are no items, stop here. We won't show anything.
  if (empty($items)) {
    return $element;
  }

  // Ensure GeoPHP is available.
  geophp_load();

  // Create array of features.
  $features = array();
  foreach ($items as $delta) {
    if (array_key_exists('geom', $delta)) {
      $geometry = geoPHP::load($delta['geom']);
    }
    else {
      $geometry = geoPHP::load($delta);
    }
    $features[] = array(
      'wkt' => $geometry
        ->out('wkt'),
      'projection' => 'EPSG:4326',
    );
  }

  // If there are no features at this point, bail.
  if (empty($features)) {
    return $element;
  }

  // Build a map for each item.
  $map_name = 'farm_map_geofield';
  foreach ($features as $delta => $feature) {
    $element[$delta] = array(
      '#type' => 'farm_map',
      '#map_name' => $map_name,
      '#wkt' => $feature['wkt'],
    );
  }
  return $element;
}

/**
 * Implements hook_field_widget_info().
 */
function farm_map_geofield_field_widget_info() {
  return array(
    'farm_map_geofield' => array(
      'label' => t('farmOS Map'),
      'field types' => array(
        'geofield',
      ),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_settings_form().
 */
function farm_map_geofield_field_widget_settings_form($field, $instance) {
  $widget = $instance['widget'];
  $settings = $widget['settings'];
  $form = array();

  // Add Geocoder support.
  $use_geocoder = isset($settings['use_geocoder']) ? $settings['use_geocoder'] : 0;
  $geocoder_form = array(
    '#type' => 'fieldset',
    '#title' => t('Geocoder settings'),
  );
  $geocoder_form['use_geocoder'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable geocoding of location data'),
    '#default_value' => $use_geocoder,
    // Can't nest this in a fieldset element without affecting data storage so
    // instead hardcode one.
    '#prefix' => '<fieldset><legend><span="fieldset-legend">' . t('Geocoder settings') . '</span></legend><div class="fieldset-wrapper">',
  );

  // Load the Geocoder widget settings.
  module_load_include('inc', 'geocoder', 'geocoder.widget');
  $new = geocoder_field_widget_settings_form($field, $instance);
  $new['geocoder_field']['#options'][$field['field_name']] = t('Add extra field');

  // If there are no options available search by ourselves to ensure the text
  // field type was taken in account.
  if (empty($new['geocoder_handler']['#options'])) {
    $supported_field_types = geocoder_supported_field_types();
    if (isset($supported_field_types['text'])) {
      $processors = geocoder_handler_info();
      foreach ($supported_field_types['text'] as $handler) {
        $new['geocoder_handler']['#options'][$handler] = $processors[$handler]['title'];
      }
    }
  }

  // Show the geocoder fields only if geocoder is selected.
  farm_map_geofield_widget_add_states($new, ':input[name="instance[widget][settings][use_geocoder]"]');

  // Close the fieldset we opened in the #prefix to use_geocoder.
  $element_children = element_children($new);
  $new[end($element_children)]['#suffix'] = '</div></fieldset>';
  $geocoder_form += $new;
  $form += $geocoder_form;
  return $form;
}

/**
 * Recurse through form elements adding a visibility #states selector
 * and removing #required flags.
 */
function farm_map_geofield_widget_add_states(&$element, $selector) {
  foreach (element_children($element) as $key) {
    $element[$key]['#required'] = FALSE;

    // Don't override any existing #states settings.
    if (!isset($element[$key]['#states'])) {
      $element[$key]['#states'] = array();
    }
    if (!isset($element[$key]['#states']['visible'])) {
      $element[$key]['#states']['visible'] = array();
    }
    $element[$key]['#states']['visible'][$selector] = array(
      'checked' => TRUE,
    );
    farm_map_geofield_widget_add_states($element[$key], $selector);
  }
}

/**
 * Implements hook_field_widget_form().
 */
function farm_map_geofield_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {

  // Start with the Geofield WKT widget.
  $instance['widget']['type'] = 'geofield_wkt';
  $element = geofield_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);

  // Get the geometry (as WKT), from form state input (if available), or from
  // the saved field value.
  $wkt = '';
  if (!empty($form_state['input'][$field['field_name']][$langcode][$delta]['geom'])) {
    $wkt = $form_state['input'][$field['field_name']][$langcode][$delta]['geom'];
  }
  elseif (!empty($items[$delta]['geom'])) {
    $wkt = $items[$delta]['geom'];
  }

  // Add a farmOS map instance with the WKT and drawing controls.
  $element['map'] = array(
    '#type' => 'farm_map',
    '#map_name' => 'farm_map_geofield_widget',
    '#wkt' => $wkt,
    '#edit' => TRUE,
  );

  // Move the geometry field below the map.
  $element['geom']['#weight'] = 100;

  // Time to deal with optional geocoder integration
  // Conditionally add geocoder button.
  $parents = array_merge($element['#field_parents'], array(
    $element['#field_name'],
    $langcode,
    $delta,
  ));
  $field_name = $field['field_name'];
  $id_prefix = implode('-', array_merge($parents, array(
    $field_name,
    $delta,
  )));
  $wrapper_id = drupal_html_id($id_prefix . '-use-geocoder-wrapper');
  $is_settings_form = isset($form['#title']) && $form['#title'] == t('Default value');
  $settings = $instance['widget']['settings'];
  if (!$is_settings_form && !empty($settings['use_geocoder']) && !empty($settings['geocoder_field'])) {
    if ($settings['geocoder_field'] == $element['#field_name']) {

      // Extra field.
      $element['geocoder_input'] = array(
        '#type' => 'textfield',
        '#title' => t('Geocode'),
        '#description' => t('Enter the place to geocode.'),
      );
      $label = $element['geocoder_input']['#title'];
    }
    elseif ($field = field_info_instance($instance['entity_type'], $settings['geocoder_field'], $instance['bundle'])) {
      $label = $field['label'];
    }
    else {
      switch ($settings['geocoder_field']) {
        case 'title':
          $label = t('Title');
          break;
        case 'name':
          $label = t('Name');
          break;
        default:
          $label = $settings['geocoder_field'];
      }
    }
    $element['#prefix'] = '<div id="' . $wrapper_id . '">';
    $element['#suffix'] = '</div>';
    $element['use_geocoder'] = array(
      '#type' => 'submit',
      '#name' => strtr($id_prefix, '-', '_') . '_use_geocoder',
      '#value' => t('Find using @field field', array(
        '@field' => $label,
      )),
      '#attributes' => array(
        'class' => array(
          'field-use-geocoder-submit',
        ),
      ),
      // Avoid validation errors for e.g. required fields but do pass the value
      // of the geocoder field.
      '#limit_validation_errors' => array(),
      '#ajax' => array(
        'callback' => 'farm_map_geofield_geocode_ajax_callback',
        'wrapper' => $wrapper_id,
        'effect' => 'fade',
      ),
      '#submit' => array(
        'farm_map_geofield_use_geocoder_submit',
      ),
      '#weight' => 101,
    );
  }

  // Add the element to an array, because it's the format that
  // FIELD_BEHAVIOR_CUSTOM expects.

  /**
   * @todo
   * This is necessary due to a legacy decision that was made in early farmOS
   * development, when we were using the OpenLayers module. The farmOS Geofield
   * (field_farm_geofield) was set up with a cardinality of -1 (allowing
   * unlimited values), and the OpenLayers Geofield widget was configured to
   * save all features as a combined geometry. So, even though the field was
   * configured to allow unlimited values, only one value was ever saved. And
   * only one value was/is ever needed. So in the future, when we move to Drupal
   * 8, we should plan to change the cardinality to 1. The decision was made to
   * leave it as -1 in 7.x-1.x, because changing it would cause a change to the
   * REST API (which expects an array of geometries, even though only one should
   * ever be provided).
   *
   * Using FIELD_BEHAVIOR_CUSTOM and wrapping the element in an array is the
   * same approach that the openlayers_geofield module takes.
   */
  $full_element = array(
    $element,
  );

  // Return the widget element.
  return $full_element;
}

/**
 * Submit handler for the geocoder integration.
 */
function farm_map_geofield_use_geocoder_submit($form, &$form_state) {
  $button = $form_state['triggering_element'];

  // Go one level up in the form, to the widgets container.
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
  $field_name = $element['#field_name'];
  $langcode = $element['#language'];
  $delta = $element['#delta'];
  $parents = $element['#field_parents'];

  // Set the widget value based on geocoding results.
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
  $geocoder_field = $field_state['instance']['widget']['settings']['geocoder_field'];
  $geocoder_field_parents = array_merge(array_slice($button['#array_parents'], 0, -4), array(
    $geocoder_field,
    $langcode,
  ));

  // Since all validation errors are disabled we need to fetch the values for
  // the geocoding as only validate values are sent to the values array of the
  // form state.
  $values = drupal_array_get_nested_value($form_state['input'], $geocoder_field_parents);

  // Inject the values into the values array to ensure the geofield module is
  // able to handle the data.
  drupal_array_set_nested_value($form_state['values'], $geocoder_field_parents, $values);
  if ($field_value = geocoder_widget_get_field_value($element['#entity_type'], $field_state['instance'], NULL, $values)) {
    geophp_load();
    $geometry = geoPHP::load($field_value[$langcode][$delta]['geom']);

    // Openlayers can only use WKT, so translate.
    $field_value[$langcode][$delta]['geom'] = $geometry
      ->out('wkt');
    $field_value[$langcode][$delta]['input_format'] = 'wkt';

    // Override the field's value in the 'input' array to substitute the new
    // field value for the one that was submitted.
    drupal_array_set_nested_value($form_state, array_merge(array(
      'input',
    ), array_slice($button['#array_parents'], 0, -4), array(
      $field_name,
    )), $field_value);
  }

  // Rebuild the form to ensure that the map is recreated from scratch.
  // See field_add_more_submit() in core field.form.inc for similar usage.
  $form_state['rebuild'] = TRUE;
}

/**
 * Return the altered form element from an AJAX request.
 *
 * @see farm_map_geofield_field_widget_form()
 */
function farm_map_geofield_geocode_ajax_callback($form, $form_state) {
  $button = $form_state['triggering_element'];

  // Go one level up in the form, to the widgets container.
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));

  // Return the map, but remove the '_weight' element inserted
  // by the field API.
  unset($element['_weight']);
  return $element;
}

/**
 * Implements hook_geocoder_geocode_values_alter().
 */
function farm_map_geofield_geocoder_geocode_values_alter(&$source_field_values, &$field_info, $handler_settings, $field_instance) {

  // If this is a farm_map_geofield pointing to its extra field adjust the
  // field values and mock a text field.
  if (isset($field_instance['widget']['settings']['geocoder_field']) && $field_instance['widget']['type'] == 'farm_map_geofield' && $field_instance['widget']['settings']['geocoder_field'] == $field_info['field_name']) {
    if (isset($source_field_values[0]['geocoder_input'])) {
      $source_field_values = array(
        array(
          'value' => $source_field_values[0]['geocoder_input'],
        ),
      );
      $field_info = array(
        'type' => 'text',
      );
    }
  }
}