farmOS/modules/farm/farm_map/farm_map_geofield/farm_map_geofield.module

342 lines
12 KiB
Plaintext

<?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');
}
}
}