You are here

geolocation_googlemaps.module in Geolocation Field 7

Same filename and directory in other branches
  1. 6 modules/geolocation_googlemaps/geolocation_googlemaps.module

Google Maps widget and formatters for Geolocation.

File

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

/**
 * @file
 * Google Maps widget and formatters for Geolocation.
 */

/**
 * Implements hook_field_formatter_info().
 */
function geolocation_googlemaps_field_formatter_info() {
  return array(
    'geolocation_googlemaps_static' => array(
      'label' => t('Static Google Map'),
      'field types' => array(
        'geolocation_latlng',
        'geofield',
      ),
      'settings' => array(
        'map_width' => '300px',
        'map_height' => '300px',
        'map_zoomlevel' => '7',
        'map_imageformat' => 'png8',
        'map_link' => 0,
        'map_maptype' => 'roadmap',
        'marker_icon' => '',
      ),
    ),
    'geolocation_googlemaps_dynamic' => array(
      'label' => t('Dynamic Google Map'),
      'field types' => array(
        'geolocation_latlng',
        'geofield',
      ),
      'settings' => array(
        'map_width' => '300px',
        'map_height' => '300px',
        'map_zoomlevel' => '7',
        'map_imageformat' => 'png8',
        'map_maptype' => 'roadmap',
        'marker_icon' => '',
        'map_scrollwheel' => FALSE,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function geolocation_googlemaps_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $type = $display['type'];
  $settings = $display['settings'];
  $element = array();

  // Implement some configuration options.
  // http://code.google.com/intl/en/apis/maps/documentation/staticmaps/
  $element['map_width'] = array(
    '#type' => 'textfield',
    '#title' => t('Map width'),
    '#description' => t('Enter custom map width (e.g. 300px or 300%), default is 300px. Percentages do not work with static maps.'),
    '#default_value' => $settings['map_width'],
  );
  $element['map_height'] = array(
    '#type' => 'textfield',
    '#title' => t('Map height'),
    '#description' => t('Enter custom map height (e.g. 300px or 300%), default is 300px. Percentages do not work with static maps.'),
    '#default_value' => $settings['map_height'],
  );
  $element['marker_icon'] = array(
    '#type' => 'textfield',
    '#title' => t('Marker icon'),
    '#description' => t('Set the path to the icon marker you wish to use. Path can be absolute (with http://) or relative to the Drupal instalation. If left blank, the default will be used'),
    '#default_value' => $settings['marker_icon'],
  );

  // Image format is used only for static maps.
  switch ($type) {
    case 'geolocation_googlemaps_static':
      $element['map_imageformat'] = array(
        '#type' => 'select',
        '#title' => t('Image format'),
        '#options' => array(
          'png8' => '8-bit PNG (default)',
          'png32' => '32-bit PNG',
          'gif' => 'GIF',
          'jpg' => 'JPEG',
          'jpg-baseline' => 'JPEG (non-progressive)',
        ),
        '#description' => t('Choose an Image Format. jpg and jpg-baseline typically provide the smallest image size, though they do so through "lossy" compression which may degrade the image. gif, png8 and png32 provide lossless compression.'),
        '#default_value' => $settings['map_imageformat'],
      );
      $element['map_link'] = array(
        '#type' => 'checkbox',
        '#title' => t('Create link to Google Maps'),
        '#description' => t('Link the static map image to Google Maps.'),
        '#default_value' => $settings['map_link'],
      );
      break;
    case 'geolocation_googlemaps_dynamic':
      $element['map_scrollwheel'] = array(
        '#type' => 'checkbox',
        '#title' => t('Scroll Zoom'),
        '#description' => t('By default zooming is done with double click and/or using the map controls to avoid interrupting the normal window scroll. It can optionally be enabled here.'),
        '#default_value' => $settings['map_scrollwheel'],
      );
      break;
  }
  $element['map_maptype'] = array(
    '#type' => 'select',
    '#title' => t('Map Type'),
    '#options' => array(
      'roadmap' => 'Roadmap (default)',
      'satellite' => 'Satellite',
      'terrain' => 'Terrain',
      'hybrid' => 'Hybrid',
    ),
    '#description' => t('Choose map type. <em>roadmap</em> (default) specifies a standard roadmap image, as is normally shown on the Google Maps website. <em>satellite</em> specifies a satellite image. <em>terrain</em> specifies a physical relief map image, showing terrain and vegetation. <em>hybrid</em> specifies a hybrid of the satellite and roadmap image, showing a transparent layer of major streets and place names on the satellite image.'),
    '#default_value' => $settings['map_maptype'],
  );
  $element['map_zoomlevel'] = array(
    '#type' => 'select',
    '#title' => t('Zoom level'),
    '#options' => array(
      '1' => '1',
      '2' => '2',
      '3' => '3',
      '4' => '4',
      '5' => '5',
      '6' => '6',
      '7' => '7 (default)',
      '8' => '8',
      '9' => '9',
      '10' => '10',
      '11' => '11',
      '12' => '12',
      '13' => '13',
      '14' => '14',
      '15' => '15',
      '16' => '16',
      '17' => '17',
      '18' => '18',
      '19' => '19',
    ),
    '#description' => t('Choose a custom zoom level - the higher the number the closer. <em>High zoom warning:</em> Google might not return images for any combination of Map Type and Zoom Level.'),
    '#default_value' => $settings['map_zoomlevel'],
  );
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function geolocation_googlemaps_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $type = $display['type'];
  $settings = $display['settings'];
  $summary = t("<strong>Map:</strong> !mapwidth by !mapheight", array(
    '!mapwidth' => $settings['map_width'],
    '!mapheight' => $settings['map_height'],
  ));
  switch ($type) {
    case 'geolocation_googlemaps_static':
      $summary .= ' (' . $settings['map_imageformat'] . ')';
      $summary .= '<br />';
      break;
    case 'geolocation_googlemaps_dynamic':
      $summary .= '<br />';
      $scrl = $settings['map_scrollwheel'] ? t('Yes') : t('No');
      $summary .= '<strong>Scrollable:</strong> ' . $scrl . '<br />';
      break;
  }
  $summary .= '<strong>Type:</strong> ' . $settings['map_maptype'] . '<br />';
  $summary .= '<strong>Zoom:</strong> ' . $settings['map_zoomlevel'];
  if (!empty($settings['marker_icon'])) {
    $vars = array(
      'path' => $settings['marker_icon'],
    );
    $summary .= '<br /><strong>Icon:</strong> ' . theme('image', $vars);
  }
  return $summary;
}

/**
 * Implements hook_field_formatter_view().
 */
function geolocation_googlemaps_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $settings = $display['settings'];
  $settings['api_key'] = variable_get('geolocation_googlemaps_api_key', '');
  $element = array();
  $lat_key = 'lat';
  if (strpos($settings['map_height'], 'px')) {
    $height = strtok($settings['map_height'], 'p');
  }
  elseif (strpos($settings['map_height'], '%')) {
    $height = strtok($settings['map_height'], '%');
  }
  elseif (!strpos($settings['map_height'], '%') || !strpos($settings['map_height'], 'px')) {
    $height = $settings['map_height'];
  }
  if (strpos($settings['map_width'], 'px')) {
    $width = strtok($settings['map_width'], 'p');
  }
  elseif (strpos($settings['map_width'], '%')) {
    $width = strtok($settings['map_width'], '%');
  }
  elseif (!strpos($settings['map_width'], '%') || !strpos($settings['map_width'], 'px')) {
    $width = $settings['map_width'];
  }

  // To make this widget compatible with geofiled we need to rename the keys for
  // longitude. Geofield uses "lon" while Geolocation Field uses "lng".
  $lng_key = $field['type'] == 'geofield' ? 'lon' : 'lng';
  switch ($display['type']) {
    case 'geolocation_googlemaps_static':
      foreach ($items as $delta => $item) {
        $query = array(
          'zoom' => $settings['map_zoomlevel'],
          'size' => $width . "x" . $height,
          'format' => $settings['map_imageformat'],
          'maptype' => $settings['map_maptype'],
          'markers' => 'size:mid|color:red|' . $item[$lat_key] . ',' . $item[$lng_key],
        );
        if ($settings['marker_icon']) {
          $path = file_create_url($settings['marker_icon']);
          $query['markers'] = 'icon:' . $path . '|' . $query['markers'];
        }
        if (!empty($settings['api_key'])) {
          $query['key'] = $settings['api_key'];
        }
        $variables = array(
          'path' => url('//maps.google.com/maps/api/staticmap', array(
            'query' => $query,
            'external' => TRUE,
          )),
          'alt' => 'Geolocation',
          'attributes' => array(
            'class' => 'geolocation-googlemaps-static',
          ),
        );
        $map_img = theme('image', $variables);
        if ($settings['map_link'] === 1) {
          $map_link_query['q'] = $item[$lat_key] . ',' . $item[$lng_key];
          $element[$delta]['#markup'] = '<div>' . l($map_img, '//maps.google.com/maps', array(
            'query' => $map_link_query,
            'external' => TRUE,
            'html' => TRUE,
          )) . '</div>';
        }
        else {
          $element[$delta]['#markup'] = '<div>' . $map_img . '</div>';
        }
      }
      break;
    case 'geolocation_googlemaps_dynamic':
      $info = entity_get_info($entity_type);
      $key = isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'];
      $eid = $entity->{$key};
      foreach ($items as $delta => $item) {
        $width = $settings['map_width'];
        $height = $settings['map_height'];
        $id = 'geolocation-googlemaps-dynamic-' . 'e_' . $eid . 'i_' . $instance['id'] . '-d_' . $delta;
        $map_element['googlemap'] = array(
          '#prefix' => '<div id="' . $id . '" class="geolocation-map geolocation-googlemaps-dynamic" ' . ' style="width:' . htmlentities($width) . ';height:' . htmlentities($height) . ';">',
          '#suffix' => '</div>',
        );

        // Attach CSS and JS files via FAPI '#attached'.
        $map_element['googlemap']['#attached']['css'][] = drupal_get_path('module', 'geolocation_googlemaps') . '/geolocation_googlemaps.css';
        $api_key = variable_get('geolocation_googlemaps_api_key', '');
        $data = array();
        if (!empty($api_key)) {
          $data['key'] = $api_key;
        }
        $query = drupal_http_build_query($data);
        $map_element['googlemap']['#attached']['js'][] = array(
          'data' => '//maps.google.com/maps/api/js?' . $query,
          'type' => 'external',
        );
        $map_element['googlemap']['#attached']['js'][] = array(
          'data' => '//www.google.com/jsapi',
          'type' => 'external',
        );
        $map_element['googlemap']['#attached']['js'][] = array(
          'data' => drupal_get_path('module', 'geolocation_googlemaps') . '/geolocation_googlemaps_dynamic_formatter.js',
          'type' => 'file',
          'scope' => 'footer',
        );

        // Create correct url for marker_icon.
        if ($settings['marker_icon']) {
          $settings['marker_icon'] = file_create_url($settings['marker_icon']);
        }

        // Add each delta to the settings array.
        $data = array(
          'formatters' => array(
            'e_' . $eid . 'i_' . $instance['id'] => array(
              "settings" => $settings,
              "deltas" => array(
                'd_' . $delta => array(
                  'lat' => $item[$lat_key],
                  'lng' => $item[$lng_key],
                ),
              ),
            ),
          ),
        );
        $map_element['googlemap']['#attached']['js'][] = array(
          'data' => array(
            'geolocationGooglemaps' => $data,
          ),
          'type' => 'setting',
        );
        $element[$delta] = $map_element;
      }
      break;
  }
  return $element;
}

/**
 * Implements hook_field_widget_info().
 */
function geolocation_googlemaps_field_widget_info() {
  return array(
    'geolocation_googlemap' => array(
      'label' => t('Google Map'),
      'field types' => array(
        'geolocation_latlng',
        'geofield',
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_settings_form().
 */
function geolocation_googlemaps_field_widget_settings_form($field, $instance) {
  $widget = $instance['widget'];
  $settings = $widget['settings'];
  $form['scrollwheel'] = array(
    '#type' => 'checkbox',
    '#title' => t('Scroll Zoom'),
    '#description' => t('By default zooming is done with double click and/or using the map controls to avoid interrupting the normal window scroll. It can optionally be enabled here.'),
    '#default_value' => isset($settings['scrollwheel']) ? $settings['scrollwheel'] : FALSE,
  );
  $form['marker_draggable'] = array(
    '#type' => 'checkbox',
    '#title' => t('Draggable Marker'),
    '#description' => t('Enabling this will allow the user to drag/drop the marker to select a location.'),
    '#default_value' => isset($settings['marker_draggable']) ? $settings['marker_draggable'] : FALSE,
  );
  return $form;
}

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

  // In order to make Geolocation field work with the popular Field collection
  // module we check if our map widget is part of a Field collection and add the
  // #field_parents delta.
  if ($instance['entity_type'] == 'field_collection_item') {
    $depth = count($element['#field_parents']) - 1;
    $parent_delta = $element['#field_parents'][$depth];
    $id = $instance['id'] . '-' . $parent_delta . '-' . $delta;
  }
  else {
    $id = $instance['id'] . '-' . $delta;
  }
  $lat_value = isset($items[$delta]['lat']) ? $items[$delta]['lat'] : NULL;

  // To make this widget compatible with geofiled we need to rename the keys for
  // longitude. Geofield uses "lon" while Geolocation Field uses "lng".
  if ($field['type'] == 'geofield') {
    $lng_value = isset($items[$delta]['lon']) ? $items[$delta]['lon'] : NULL;
  }
  else {
    $lng_value = isset($items[$delta]['lng']) ? $items[$delta]['lng'] : NULL;
  }
  $element += array(
    '#delta' => $delta,
  );
  switch ($instance['widget']['type']) {
    case 'geolocation_googlemap':
      $element['address'] = array(
        '#type' => 'item',
        '#title' => $element['#title'],
        '#prefix' => '<div id="geolocation-address-' . $id . '" class="geolocation-address">',
        '#suffix' => '</div>',
        '#required' => $instance['required'],
      );
      $element['address']['field'] = array(
        '#type' => 'textfield',
        '#maxlength' => 120,
      );
      $element['address']['geocode'] = array(
        '#prefix' => '<span id="geolocation-address-geocode-' . $id . '" class="geolocation-address-geocode">',
        '#suffix' => '</span>',
        '#markup' => t('Get location'),
      );
      $element['address']['geocode-suggestions'] = array(
        '#prefix' => '<div id="geolocation-address-geocode-suggestions-' . $id . '" class="geolocation-address-geocode-suggestions">',
        '#suffix' => '</div>',
        '#markup' => '<label>' . t('Suggestions') . '</label><div class="suggestion-options">' . t('Not found') . '</div>',
      );
      $element['help'] = array(
        '#prefix' => '<div id="geolocation-help-' . $id . '" class="geolocation-help">',
        '#suffix' => '</div>',
        '#markup' => t('Enter an address / location / Google map URL in the textfield or you can also click on the map to set a marker'),
      );
      $element['googlemap'] = array(
        '#prefix' => '<div id="geolocation-map-' . $id . '" class="geolocation-map" style="width:100%;height:400px;">',
        '#suffix' => '</div>',
      );

      // Presentational item.
      $element['latitem'] = array(
        '#type' => 'item',
        '#title' => t('Latitude:'),
        '#prefix' => '<div id="geolocation-lat-item-' . $id . '" class="geolocation-lat-item">',
        '#suffix' => '</div>',
        '#markup' => '<span class="geolocation-lat-item-value">' . $lat_value . '</span>',
        '#required' => $instance['required'],
      );
      $element['lat'] = array(
        '#type' => 'hidden',
        '#prefix' => '<div id="geolocation-lat-' . $id . '" class="geolocation-lat">',
        '#suffix' => '</div>',
        '#default_value' => $lat_value,
      );

      // Presentational item.
      $element['lngitem'] = array(
        '#type' => 'item',
        '#title' => t('Longitude:'),
        '#prefix' => '<div id="geolocation-lng-item-' . $id . '" class="geolocation-lng-item">',
        '#suffix' => '</div>',
        '#markup' => '<span class="geolocation-lat-item-value">' . $lat_value . '</span>',
        '#required' => $instance['required'],
      );
      $element['lng'] = array(
        '#type' => 'hidden',
        '#prefix' => '<div id="geolocation-lng-' . $id . '" class="geolocation-lng">',
        '#suffix' => '</div>',
        '#default_value' => $lng_value,
      );
      $element['remove'] = array(
        '#prefix' => '<div id="geolocation-remove-' . $id . '" class="geolocation-remove"><span>',
        '#suffix' => '</span></div>',
        '#markup' => t('Remove'),
      );

      // Attach CSS and JS files via FAPI '#attached'.
      $element['googlemap']['#attached']['css'][] = drupal_get_path('module', 'geolocation_googlemaps') . '/geolocation_googlemaps.css';
      $element['googlemap']['#attached']['js'][] = array(
        'data' => drupal_get_path('module', 'geolocation_googlemaps') . '/geolocation_googlemaps_widget.js',
        'type' => 'file',
        'scope' => 'footer',
      );
      geolocation_googlemaps_attach_google_js($element);

      // Make defaults available as javascript settings. In JS files use:
      // Drupal.settings.mapDefaults.lat.
      $map_defaults_lat = $lat_value ? $lat_value : '';
      $map_defaults_lng = $lng_value ? $lng_value : '';
      $map_defaults = array(
        $id => array(
          'lat' => $map_defaults_lat,
          'lng' => $map_defaults_lng,
        ),
      );
      $data = array(
        'defaults' => $map_defaults,
        'settings' => $instance['widget']['settings'],
      );
      $element['googlemap']['#attached']['js'][] = array(
        'data' => array(
          'geolocation' => $data,
        ),
        'type' => 'setting',
      );
      $element['field_type'] = array(
        '#type' => 'value',
        '#value' => $field['type'],
      );
      $element['#element_validate'] = array(
        'geolocation_googlemaps_field_widget_validate',
      );
      $element['#element_validate'][] = 'geolocation_googlemaps_field_widget_set_value';
      break;
  }
  return $element;
}

/**
 * Validation handler for geolocation_googlemaps_field_widget_form().
 */
function geolocation_googlemaps_field_widget_validate($element, &$form_state, $form) {
  if ($element['#required']) {
    if (!$element['lat']['#value'] || !$element['lng']['#value']) {
      form_error($element, t('!name field is required.', array(
        '!name' => $element['#title'],
      )));
    }
  }
  else {
    switch (TRUE) {
      case $element['lng']['#value'] && !$element['lat']['#value']:
        form_error($element, t('!name field is incomplete, latitude value is missing.', array(
          '!name' => $element['#title'],
        )));
        break;
      case !$element['lng']['#value'] && $element['lat']['#value']:
        form_error($element, t('!name field is incomplete, longitude value is missing.', array(
          '!name' => $element['#title'],
        )));
        break;
    }
  }
}
function geolocation_googlemaps_field_widget_set_value($element, &$form_state, $form) {
  $values =& drupal_array_get_nested_value($form_state['values'], $element['#parents']);
  if ($values['field_type'] == 'geofield') {

    // Geofield needs the values in their own format which is exactly what
    // geofield_compute_values does, but we have to change first the longitude
    // key because geofield uses a different one.
    $values['lon'] = $values['lng'];
    $values = geofield_compute_values($values);
  }
}

/**
 * Implements hook_field_widget_error().
 */
function geolocation_googlemaps_field_widget_error($element, $error, $form, &$form_state) {
  switch ($error['error']) {
    case 'geolocation_invalid_lat':
    case 'geolocation_invalid_lng':
      form_error($element, $error['message']);
      break;
  }
}

/**
 * Implements hook_menu().
 */
function geolocation_googlemaps_menu() {
  $items['admin/config/services/googlemaps'] = array(
    'title' => 'Geolocation Google Maps',
    'description' => 'Configure Geolocation Google Maps settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'geolocation_googlemaps_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}
function geolocation_googlemaps_admin_settings() {
  $form['geolocation_googlemaps_api_key'] = array(
    '#title' => t('Your Google Simple API Access key'),
    '#type' => 'textfield',
    '#default_value' => variable_get('geolocation_googlemaps_api_key', ''),
    '#size' => 50,
    '#maxlength' => 255,
    '#description' => t('Using an API key is mandatory to view Google maps. You can find more information about API keys on <a href="https://developers.google.com/maps/documentation/javascript/tutorial#api_key">Google Maps JavaScript API v3</a>.'),
  );
  return system_settings_form($form);
}

/**
 * Implements hook_field_widget_form_alter().
 */
function geolocation_googlemaps_field_widget_form_alter(&$element, &$form_state, $context) {

  // Attaches widget JS to each media reference widget.
  if (isset($element['#type']) && $element['#type'] == 'media') {
    geolocation_googlemaps_attach_google_js($element);
  }
}

/**
 * Helper function that attaches JS to the given element.
 *
 * @param array $element
 *   Element.
 */
function geolocation_googlemaps_attach_google_js(&$element) {
  $js_added_already =& drupal_static(__FUNCTION__, FALSE);
  if (!$js_added_already) {
    $data = array();
    $api_key = variable_get('geolocation_googlemaps_api_key', '');
    if (!empty($api_key)) {
      $data['key'] = $api_key;
    }
    $query = drupal_http_build_query($data);
    $element['#attached']['js'][] = array(
      'data' => '//maps.google.com/maps/api/js?' . $query,
      'type' => 'external',
    );
    $element['#attached']['js'][] = array(
      'data' => '//www.google.com/jsapi',
      'type' => 'external',
    );
    $js_added_already = TRUE;
  }
}