array( 'label' => t('Geocode from another field'), 'field types' => array('geofield', 'geolocation_latlng', 'location'), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, 'default value' => FIELD_BEHAVIOR_NONE, ), ), ); } /** * Implements field_widget_settings_form(). */ function geocoder_field_widget_settings_form($this_field, $instance) { $settings = $instance['widget']['settings']; $entity_fields = field_info_instances($instance['entity_type'], $instance['bundle']); $all_fields = field_info_fields(); $supported_field_types = geocoder_supported_field_types(); $processors = geocoder_handler_info(); $handlers_by_type = array(); $field_types = array(); $valid_fields = array(); $available_handlers = array(); // Add in the title/name //@@TODO Do this programatically by getting entity_info switch ($instance['entity_type']) { case 'node': $all_fields['title'] = array( 'field_name' => 'title', 'type' => 'text', ); $entity_fields['title']['label'] = t('Title'); break; case 'taxonomy_term': $all_fields['name'] = array( 'field_name' => 'name', 'type' => 'text', ); $entity_fields['name']['label'] = t('Name'); break; } // Get a list of all valid fields that we both support and are part of this entity foreach ($all_fields as $field) { if (array_key_exists($field['field_name'], $entity_fields)) { if (in_array($field['type'], array_keys($supported_field_types)) && ($field['field_name'] != $this_field['field_name'])) { $valid_fields[$field['field_name']] = $entity_fields[$field['field_name']]['label']; foreach ($supported_field_types[$field['type']] as $handler) { $available_handlers[$handler] = $processors[$handler]['title']; $handlers_by_type[$field['type']][] = $handler; $field_types[$field['field_name']] = $field['type']; } } } } $form['geocoder_field'] = array( '#type' => 'select', '#title' => t('Geocode from field'), '#default_value' => isset($settings['geocoder_field']) ? $settings['geocoder_field']: '', '#options' => $valid_fields, '#description' => t('Select which field you would like to geocode from.'), '#required' => TRUE, '#attributes' => array('onchange' => 'geocoder_admin_field_selected();'), ); $form['geocoder_handler'] = array( '#type' => 'select', '#title' => t('Geocoder'), '#prefix' => '
', '#suffix' => '
', '#default_value' => isset($settings['geocoder_handler']) ? $settings['geocoder_handler']: '', '#options' => $available_handlers, '#description' => t('Select which type of geocoding handler you would like to use'), '#attributes' => array('onchange' => 'geocoder_admin_handler_selected();'), '#required' => TRUE, ); $form['handler_settings'] = array( '#tree' => TRUE, ); // Add the handler settings forms foreach ($processors as $handler_id => $handler) { if (isset($handler['settings_callback']) || isset($handler['terms_of_service'])) { $default_values = isset($settings['handler_settings'][$handler_id]) ? $settings['handler_settings'][$handler_id] : array(); $form['handler_settings'][$handler_id] = array(); $form['handler_settings'][$handler_id]['#type'] = 'fieldset'; $form['handler_settings'][$handler_id]['#attributes'] = array('class' => array('geocoder-handler-setting', 'geocoder-handler-setting-' . $handler_id)); $form['handler_settings'][$handler_id]['#title'] = $handler['title'] . ' Settings'; if (isset($handler['terms_of_service'])) { $form['handler_settings'][$handler_id]['tos'] = array( '#type' => 'item', '#markup' => t('This handler has terms of service. Click the following link to learn more.') . ' ' . l($handler['terms_of_service'], $handler['terms_of_service']), ); } if (isset($handler['settings_callback'])) { $settings_callback = $handler['settings_callback']; $form['handler_settings'][$handler_id] = array_merge($form['handler_settings'][$handler_id], $settings_callback($default_values)); } } } $form['delta_handling'] = array( '#type' => 'select', '#title' => t('Multi-value input handling'), '#description' => t('Should geometries from multiple inputs be: '), '#default_value' => isset($settings['delta_handling']) ? $settings['delta_handling']: 'default', '#options' => array( 'default' => 'Match Multiples (default)', 'm_to_s' => 'Multiple to Single', 's_to_m' => 'Single to Multiple', 'c_to_s' => 'Concatenate to Single', 'c_to_m' => 'Concatenate to Multiple', ), '#required' => TRUE, ); drupal_add_js(array('geocoder_widget_settings' => array('handlers' => $handlers_by_type, 'types' => $field_types)), 'setting'); drupal_add_js(drupal_get_path('module', 'geocoder') . '/geocoder.admin.js', 'file'); return $form; } /** * Implements hook_field_attach_presave(). * * Geocoding for the geocoder widget is done here to ensure that only validated * and fully processed fields values are accessed. */ function geocoder_field_attach_presave($entity_type, $entity) { // Loop over any geofield using our geocode widget $entity_info = entity_get_info($entity_type); $bundle_name = empty($entity_info['entity keys']['bundle']) ? $entity_type : $entity->{$entity_info['entity keys']['bundle']}; foreach (field_info_instances($entity_type, $bundle_name) as $field_instance) { if ($field_instance['widget']['type'] === 'geocoder') { // Required settings if (isset($field_instance['widget']['settings']['geocoder_handler']) && isset($field_instance['widget']['settings']['geocoder_field'])) { $handler = geocoder_get_handler($field_instance['widget']['settings']['geocoder_handler']); $field_name = is_array($field_instance['widget']['settings']['geocoder_field']) ? reset($field_instance['widget']['settings']['geocoder_field']) : $field_instance['widget']['settings']['geocoder_field']; $target_info = field_info_field($field_instance['field_name']); $target_type = $target_info['type']; // Get the handler-specific-settings if (isset($field_instance['widget']['settings']['handler_settings'][$handler['name']])) { $handler_settings = $field_instance['widget']['settings']['handler_settings'][$handler['name']]; } // Determine how we deal with deltas (multi-value fields) if (empty($field_instance['widget']['settings']['delta_handling'])) { $delta_handling = 'default'; } else { $delta_handling = $field_instance['widget']['settings']['delta_handling']; } list($field_info, $items) = geocoder_widget_get_field_data($entity_type, $entity, $field_name, $delta_handling); if (is_array($items) && count($items)) { $values = array(); // Geocode geometries $geometries = array(); foreach ($items as $delta => $item) { // Geocode any value from our source field. try { if (isset($handler_settings)) { $geometry = call_user_func($handler['field_callback'], $field_info, $item, $handler_settings); } else { $geometry = call_user_func($handler['field_callback'], $field_info, $item); } if ($geometry instanceof Geometry) { $geometries[] = $geometry; } } catch (Exception $e) { drupal_set_message($e->getMessage(), 'error'); } } if (empty($geometries)) $values = array(NULL); else { // Resolve multiple-values - get back values from our delta-resolver $values = geocoder_widget_resolve_deltas($geometries, $delta_handling, $target_type); } // Set the values - geofields do not support languages $entity->{$field_instance['field_name']}[LANGUAGE_NONE] = $values; } } } } } /** * Get field items and info * * We always pass the full field-item array (with all columns) to the handler, but there is some preprocessing * that we need to do for the special case of entity-labels and multi-field contactenation * For these two special cases we "mock-up" a text-field and pass it back for geocoding */ function geocoder_widget_get_field_data($entity_type, $entity, $field_name, $delta_handling) { $entity_info = entity_get_info($entity_type); // First check to see if it's an entity field if (in_array($field_name, $entity_info['entity keys'])) { $items[]['value'] = $entity->$field_name; $field_info = array( 'type' => 'text', 'entity key' => TRUE, ); } else { // It's a normal field-api field $field_info = field_info_field($field_name); $langcode = field_language($entity_type, $entity, $field_name); $items = field_get_items($entity_type, $entity, $field_name, $langcode); } // Check if we should concatenate if ($delta_handling == 'c_to_s' || $delta_handling == 'c_to_m') { $support_concat = array('text', 'text_long', 'computed', 'text_with_summary', 'computed'); if (!in_array($field_info['type'], $support_concat)) { drupal_set_message(t('Trying to concatenate a non-text field')); return; } $concat = ''; foreach ($items as $item) { $concat .= trim($item['value']) . ', '; } $concat = trim($concat, ', '); $items = array(array('value' => $concat)); $field_info = array( 'type' => 'text', 'concated' => TRUE, ); } return array( $field_info, $items, ); } /** * Geocder Widget - Resolve Deltas * * Given a list of geometries, and user configuration on how to handle deltas, * we created a list of items to be inserted into the fields. */ function geocoder_widget_resolve_deltas($geometries, $delta_handling = 'default', $field_type) { $values = array(); // Default delta handling: just pass one delta to the next if ($delta_handling == 'default') { foreach ($geometries as $geometry) { $values[] = geocoder_widget_values_from_geometry($geometry, $field_type); } } // Single-to-multiple handling - if we can, explode out the component geometries if ($delta_handling == 's_to_m' || $delta_handling = 'c_to_m') { $type = $geometries[0]->geometryType(); if (in_array($type, array('MultiPoint', 'MultiLineString', 'MultiPolygon', 'GeometryCollection'))) { $components = $geometries[0]->getComponents(); foreach ($components as $component) { $values[] = geocoder_widget_values_from_geometry($component, $field_type); } } else { $values[] = geocoder_widget_values_from_geometry($geometries[0], $field_type); } } // For multiple-to-single handling, run it though geometryReduce if ($delta_handling == 'm_to_s' || $delta_handling = 'c_to_s') { $reduced_geom = geoPHP::geometryReduce($geometries); $values[] = geocoder_widget_values_from_geometry($reduced_geom, $field_type); } return $values; } /** * Geocder Widget - Field values from geometry * * Given a geometry and the field type, return back a values array for that field. * The passed back array represents a single delta. */ function geocoder_widget_values_from_geometry($geometry, $field_type) { if ($field_type == 'geofield') return geofield_get_values_from_geometry($geometry); if ($field_type == 'geolocation_latlng') { $centroid = $geometry->centroid(); $lat = $centroid->y(); $lng = $centroid->x(); return array( 'lat' => $lat, 'lng' => $lng, 'lat_sin' => sin(deg2rad($lat)), 'lat_cos' => cos(deg2rad($lng)), 'lng_rad' => deg2rad($lng), ); } if ($field_type == 'location') { $centroid = $geometry->centroid(); return array( 'latitude' => $centroid->y(), 'longitude' => $centroid->x(), 'source' => 2, ); } } /** * Geocoder Widget - Parse an address field */ function geocoder_widget_parse_addressfield($field_item) { $address = ''; if (!empty($field_item['premise'])) $address .= $field_item['premise'] . ','; if (!empty($field_item['thoroughfare'])) $address .= $field_item['thoroughfare'] . ','; if (!empty($field_item['locality'])) $address .= $field_item['locality'] . ','; if (!empty($field_item['administrative_area'])) $address .= $field_item['administrative_area'] . ','; if (!empty($field_item['sub_administrative_area'])) $address .= $field_item['sub_administrative_area'] . ','; if (!empty($field_item['country'])) $address .= $field_item['country'] . ','; if (!empty($field_item['postal_code'])) $address .= $field_item['postal_code'] . ','; $address = rtrim($address, ', '); return $address; }