Newer
Older
Brandon Bergren
committed
// $Id$
/**
* @file
* Location module main routines.
* An implementation of a universal API for location manipulation. Provides functions for
* postal_code proximity searching, deep-linking into online mapping services. Currently,
* some options are configured through an interface provided by location.module.
*/
define('LOCATION_PATH', drupal_get_path('module', 'location'));
define('LOCATION_LATLON_UNDEFINED', 0);
define('LOCATION_LATLON_USER_SUBMITTED', 1);
Ankur Rishi
committed
define('LOCATION_LATLON_GEOCODED_APPROX', 2);
Brandon Bergren
committed
define('LOCATION_LATLON_JIT_GEOCODING', 4); // Force regeocoding immediately.
define('LOCATION_USER_DONT_COLLECT', 0);
define('LOCATION_USER_COLLECT', 1);
function location_menu() {
$items = array();
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
$items['location/autocomplete'] = array(
'access arguments' => array('access content'),
'page callback' => '_location_autocomplete',
'type' => MENU_CALLBACK,
);
$items['admin/settings/location'] = array(
'title' => 'Location',
'description' => 'Settings for Location module',
'page callback' => 'drupal_get_form',
'page arguments' => array('location_admin_settings'),
'file' => 'location.admin.inc',
'access arguments' => array('administer site configuration'),
);
$items['admin/settings/location/main'] = array(
'title' => 'Main settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/settings/location/maplinking'] = array(
'title' => 'Map links',
'page callback' => 'drupal_get_form',
'page arguments' => array('location_map_link_options_form'),
'access arguments' => array('administer site configuration'),
'file' => 'location.admin.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
$items['admin/settings/location/geocoding'] = array(
'title' => 'Geocoding options',
'page callback' => 'drupal_get_form',
'page arguments' => array('location_geocoding_options_form'),
'access arguments' => array('administer site configuration'),
'file' => 'location.admin.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 2,
);
$items['admin/settings/location/geocoding/%/%'] = array(
'page callback' => 'location_geocoding_parameters_page',
'page arguments' => array(4, 5),
'access arguments' => array('administer site configuration'),
'type' => MENU_CALLBACK,
);
$items['admin/settings/location/util'] = array(
'title' => 'Location utilities',
'page callback' => 'drupal_get_form',
'page arguments' => array('location_util_form'),
'access arguments' => array('administer site configuration'),
'file' => 'location.admin.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 3,
);
return $items;
}
/**
*
*/
function location_api_variant() {
return array(
'submit latitude/longitude',
);
}
* @TODO: check/fix this: admin/content/configure/types (still use %? still same url?)
function location_help($path, $arg) {
switch ($path) {
case 'admin/help#location':
$output = '<p>'. t('The location module allows you to associate a geographic location with content and users. Users can do proximity searches by postal code. This is useful for organizing communities that have a geographic presence.') .'</p>';
$output .= '<p>'. t('To administer locative information for content, use the content type administration page. To support most location enabled features, you will need to install the country specific include file. To support postal code proximity searches for a particular country, you will need a database dump of postal code data for that country. As of June 2007 only U.S. and German postal codes are supported.') .'</p>';
$output .= t('<p>You can</p>
<ul>
<li>administer locative information at <a href="@admin-node-configure-types">Administer >> Content management >> Content types</a> to configure a type and see the locative information.</li>
<li>administer location at <a href="@admin-settings-location">Administer >> Site configuration >> Location</a>.</li>
<li>use a database dump for a U.S. and/or German postal codes table that can be found at <a href="@external-http-cvs-drupal-org">zipcode database</a>.</li>
', array('@admin-node-configure-types' => url('admin/content/types'), '@admin-settings-location' => url('admin/settings/location'), '@external-http-cvs-drupal-org' => 'http://cvs.drupal.org/viewcvs/drupal/contributions/modules/location/database/')) .'</ul>';
$output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@location">Location page</a>.', array('@location' => 'http://www.drupal.org/handbook/modules/location/')) .'</p>';
return $output;
/**
* Implementation of hook_elements().
*/
function location_elements() {
return array(
'location_element' => array(
'#input' => TRUE,
'#process' => array('_location_expand_location'),
'#location_settings' => array(),
'#required' => FALSE,
'#attributes' => array('class' => 'location'),
Brandon Bergren
committed
// Element level validation.
'#element_validate' => array('location_element_validate'),
'location_settings' => array(
'#input' => TRUE,
'#process' => array('_location_expand_location_settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#tree' => TRUE,
),
Brandon Bergren
committed
/**
* Theme function to fixup location elements.
Brandon Bergren
committed
*/
function theme_location_element($element) {
// Prevent spurious "Array" from appearing.
unset($element['#value']);
return theme('fieldset', $element);
}
/**
* Implementation of hook_theme().
*/
function location_theme() {
return array(
'location_settings' => array('arguments' => array('element')),
'locations' => array(
'template' => 'locations',
'arguments' => array(
'locations' => NULL,
'hide' => array(),
),
),
'location' => array(
'template' => 'location',
'arguments' => array(
'location' => NULL,
'hide' => array(),
),
),
'location_latitude_dms' => array('arguments' => array('latitude')),
'location_longitude_dms' => array('arguments' => array('longitude')),
Brandon Bergren
committed
'location_map_link_options' => array('arguments' => array('form')),
'location_geocoding_options' => array('arguments' => array('form')),
Brandon Bergren
committed
'location_element' => array('arguments' => array('element')),
'location_distance' => array('template' => 'location_distance', 'arguments' => array('distance' => 0, 'units' => 'km')),
);
}
Brandon Bergren
committed
/**
* Implementation of hook_views_api().
*/
function location_views_api() {
return array(
'api' => 2,
//'path' => drupal_get_path('module', 'location') .'/includes',
);
}
/**
* Process a location element.
*/
function _location_expand_location($element) {
drupal_add_css(drupal_get_path('module', 'location') .'/location.css');
if (!isset($element['#title'])) {
$element['#title'] = t('Location');
}
if (empty($element['#location_settings'])) {
$element['#location_settings'] = array();
}
if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
Brandon Bergren
committed
$element['#default_value'] = array();
Brandon Bergren
committed
$element['location_settings'] = array(
'#type' => 'value',
'#value' => $element['#location_settings'],
);
Brandon Bergren
committed
// Ensure this isn't accidentally used later.
unset($element['#location_settings']);
// Make a reference to the settings.
$settings =& $element['location_settings']['#value'];
if (isset($element['#default_value']['lid']) && $element['#default_value']['lid']) {
// Keep track of the old LID.
$element['lid'] = array(
'#type' => 'value',
'#value' => $element['#default_value']['lid'],
);
}
// Fill in missing defaults, etc.
location_normalize_settings($settings, $element['#required']);
$defaults = location_empty_location($settings);
Brandon Bergren
committed
if (isset($element['lid']['#value']) && $element['lid']['#value']) {
$defaults = location_load_location($element['lid']['#value']);
}
$fsettings =& $settings['form']['fields'];
// $settings -> $settings['form']['fields']
// $defaults is not necessarily what we want.
// If #default_value was already specified, we want to use that, because
// otherwise we will lose our values on preview!
$fdefaults = $defaults;
foreach ($element['#default_value'] as $k => $v) {
$fdefaults[$k] = $v;
}
Brandon Bergren
committed
$fields = location_field_names();
foreach ($fields as $field => $title) {
if (!isset($element[$field])) {
// @@@ Permission check hook?
if ($fsettings[$field]['collect'] != 0) {
$element[$field] = location_invoke_locationapi($fdefaults[$field], 'field_expand', $field, $fsettings[$field]['collect'], $fdefaults);
$element[$field]['#weight'] = (int)$fsettings[$field]['weight'];
Ankur Rishi
committed
// Only include 'Street Additional' if 'Street' is 'allowed' or 'required'
if ($field == 'street' && $fsettings[$field]['collect']) {
$element['additional'] = location_invoke_locationapi($defaults['additional'], 'field_expand', 'additional', 1, $defaults);
$element['additional']['#weight'] = (int)$fsettings['additional']['weight'];
Ankur Rishi
committed
}
// @@@ Split into submit and view permissions?
if (user_access('submit latitude/longitude') && $fsettings['locpick']['collect']) {
$element['locpick'] = array('#weight' => $fsettings['locpick']['weight']);
if (location_has_coordinates($defaults, FALSE)) {
$element['locpick']['current'] = array(
'#type' => 'fieldset',
'#title' => t('Current coordinates'),
);
$element['locpick']['current']['current_latitude'] = array(
'#type' => 'item',
'#title' => t('Latitude'),
'#value' => $defaults['latitude'],
);
$element['locpick']['current']['current_longitude'] = array(
'#type' => 'item',
'#title' => t('Longitude'),
'#value' => $defaults['longitude'],
);
$source = t('Unknown');
switch($defaults['source']) {
case LOCATION_LATLON_USER_SUBMITTED:
$source = t('User-submitted');
break;
case LOCATION_LATLON_GEOCODED_APPROX:
$source = t('Geocoded (Postal code level)');
break;
case LOCATION_LATLON_GEOCODED_EXACT:
$source = t('Geocoded (Exact)');
}
$element['locpick']['current']['current_source'] = array(
'#type' => 'item',
'#title' => t('Source'),
'#value' => $source,
);
}
$element['locpick']['user_latitude'] = array(
'#type' => 'textfield',
'#title' => t('Latitude'),
'#default_value' => isset($element['#default_value']['locpick']['user_latitude']) ? $element['#default_value']['locpick']['user_latitude'] : '',
'#size' => 16,
'#attributes' => array('class' => 'container-inline'),
'#maxlength' => 20,
);
$element['locpick']['user_longitude'] = array(
'#type' => 'textfield',
'#title' => t('Longitude'),
'#default_value' => isset($element['#default_value']['locpick']['user_longitude']) ? $element['#default_value']['locpick']['user_longitude'] : '',
'#size' => 16,
'#maxlength' => 20,
);
Brandon Bergren
committed
$element['locpick']['instructions'] = array(
'#type' => 'markup',
'#weight' => 1,
'#prefix' => '<div class=\'description\'>',
'#value' => '<br /><br />' . t('If you wish to supply your own latitude and longitude, you may enter them above. If you leave these fields blank, the system will attempt to determine a latitude and longitude for you from the entered address. To have the system recalculate your location from the address, for example if you change the address, delete the values for these fields.'),
Brandon Bergren
committed
'#suffix' => '</div>',
);
if (function_exists('gmap_get_auto_mapid') && variable_get('location_usegmap', FALSE)) {
$mapid = gmap_get_auto_mapid();
$map = gmap_parse_macro(variable_get('location_locpick_macro', '[gmap]'));
$map['id'] = $mapid;
$map['points'] = array();
$map['pointsOverlays'] = array();
$map['lines'] = array();
$map['behavior']['locpick'] = TRUE;
// Use previous coordinates to center the map.
if (location_has_coordinates($defaults, FALSE)) {
$map['latitude'] = (float)$defaults['latitude'];
$map['longitude'] = (float)$defaults['longitude'];
$map['markers'][] = array(
'latitude' => $defaults['latitude'],
'longitude' => $defaults['longitude'],
'markername' => 'small gray', // @@@ Settable?
'offset' => 0,
'opts' => array(
'clickable' => FALSE,
),
);
}
$element['locpick']['user_latitude']['#map'] = $mapid;
gmap_widget_setup($element['locpick']['user_latitude'], 'locpick_latitude');
$element['locpick']['user_longitude']['#map'] = $mapid;
gmap_widget_setup($element['locpick']['user_longitude'], 'locpick_longitude');
$element['locpick']['map'] = array(
'#map' => $mapid,
'#settings' => $map,
Brandon Bergren
committed
$element['locpick']['map_instructions'] = array(
'#type' => 'markup',
'#weight' => 2,
'#prefix' => '<div class=\'description\'>',
'#value' => t('You may set the location by clicking on the map, or dragging the location marker. To clear the location and cause it to be recalculated, click on the marker.'),
Brandon Bergren
committed
'#suffix' => '</div>',
);
if (isset($defaults['lid']) && !empty($defaults['lid'])) {
$element['delete_location'] = array(
'#type' => 'checkbox',
'#title' => t('Delete'),
'#default_value' => isset($fdefaults['delete_location']) ? $fdefaults['delete_location'] : FALSE,
'#description' => t('Check this box to delete this location.'),
);
Ankur Rishi
committed
}
function _location_expand_location_settings(&$element) {
// Set a value for the fieldset that doesn't interfere with rendering and doesn't generate a warning.
$element['#tree'] = TRUE;
$element['#theme'] = 'location_settings';
if (!isset($element['#title'])) {
$element['#title'] = t('Location Fields');
}
if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
$element['#default_value'] = array();
}
// Force #tree on.
$element['#tree'] = TRUE;
$defaults = $element['#default_value'];
if (!isset($defaults) || !is_array($defaults)) {
$defaults = array();
}
$temp = location_invoke_locationapi($element, 'defaults');
foreach ($temp as $k => $v) {
if (!isset($defaults[$k])) {
$defaults[$k] = array();
}
$defaults[$k] = array_merge($v, $defaults[$k]);
}
$fields = location_field_names();
// Options for fields.
$options = array(
0 => t('Do not collect'),
1 => t('Allow'),
2 => t('Require'),
4 => t('Force Default'), // Need to consider the new "defaults" when saving.
foreach ($fields as $field => $title) {
$element[$field] = array(
'#type' => 'fieldset',
'#tree' => TRUE,
);
$element[$field]['name'] = array(
'#type' => 'item',
'#value' => $title,
);
$element[$field]['collect'] = array(
'#type' => 'select',
'#default_value' => $defaults[$field]['collect'],
'#options' => $options,
);
$temp = $defaults[$field]['default'];
$element[$field]['default'] = location_invoke_locationapi($temp, 'field_expand', $field, 1, $defaults);
$defaults[$field]['default'] = $temp;
$element[$field]['weight'] = array(
'#type' => 'weight',
'#delta' => 100,
'#default_value' => $defaults[$field]['weight'],
Ankur Rishi
committed
}
Ankur Rishi
committed
// 'Street Additional' field should depend on 'Street' setting.
// It should never be required and should only display when the street field is 'allowed' or 'required'
// unset($element['additional']);
function theme_location_settings($element) {
$rows = array();
$header = array(
array(
'data' => t('Name'),
'colspan' => 2,
),
t('Collect'), t('Default'), t('Weight'));
// Force country required.
$element['country']['default']['#required'] = TRUE;
unset($element['country']['collect']['#options'][0]);
foreach (element_children($element) as $key) {
$element[$key]['weight']['#attributes']['class'] = 'location-settings-weight';
unset($element[$key]['default']['#title']);
$row = array();
$row[] = array('data' => '', 'class' => 'location-settings-drag');
$row[] = drupal_render($element[$key]['name']);
$row[] = drupal_render($element[$key]['collect']);
$row[] = drupal_render($element[$key]['default']);
$row[] = array('data' => drupal_render($element[$key]['weight']), 'class' => 'delta-order');
$rows[] = array(
'#weight' => (int)$element[$key]['weight']['#value'],
'data' => $row,
'class' => 'draggable',
);
}
uasort($rows, 'element_sort');
foreach ($rows as $k => $v) {
unset($rows[$k]['#weight']);
}
drupal_add_tabledrag('location-settings-table', 'order', 'sibling', 'location-settings-weight');
$output = theme('table', $header, $rows, array('id' => 'location-settings-table'));
//return theme('form_element', $element, $element['#children']);
return $output;
Brandon Bergren
committed
function location_field_names($all = FALSE) {
Brandon Bergren
committed
static $allfields;
if ($all) {
if (empty($allfields)) {
$dummy = array();
$allfields = location_invoke_locationapi($dummy, 'fields');
Brandon Bergren
committed
$virtual = location_invoke_locationapi($dummy, 'virtual fields');
$allfields += $virtual;
Brandon Bergren
committed
}
return $allfields;
}
else {
if (empty($fields)) {
Brandon Bergren
committed
$dummy = array();
Brandon Bergren
committed
$fields = location_invoke_locationapi($dummy, 'fields');
}
return $fields;
/**
* Implementation of hook_locationapi().
*/
function location_locationapi(&$obj, $op, $a3 = NULL, $a4 = NULL, $a5 = NULL) {
switch ($op) {
case 'fields':
return array('name' => t('Location name'), 'street' => t('Street location'), 'additional' => t('Additional'), 'city' => t('City'), 'province' => t('State/Province'), 'postal_code' => t('Postal code'), 'country' => t('Country'), 'locpick' => t('Coordinate Chooser'));
Brandon Bergren
committed
case 'virtual fields':
Brandon Bergren
committed
return array('province_name' => t('Province name'), 'country_name' => t('Country name'), 'map_link' => t('Map link'), 'coords' => t('Coordinates'));
Brandon Bergren
committed
case 'defaults':
'lid' => array('default' => FALSE),
'name' => array('default' => '', 'collect' => 1, 'weight' => 2),
'street' => array('default' => '', 'collect' => 1, 'weight' => 4),
'additional' => array('default' => '', 'collect' => 1, 'weight' => 6),
'city' => array('default' => '', 'collect' => 0, 'weight' => 8),
'province' => array('default' => '', 'collect' => 0, 'weight' => 10),
'postal_code' => array('default' => '', 'collect' => 0, 'weight' => 12),
'country' => array('default' => variable_get('location_default_country', 'us'), 'collect' => 1, 'weight' => 14), // @@@ Fix weight?
'locpick' => array('default' => FALSE, 'collect' => 1, 'weight' => 20, 'nodiff' => TRUE),
'latitude' => array('default' => 0),
'longitude' => array('default' => 0),
'source' => array('default' => LOCATION_LATLON_UNDEFINED),
'is_primary' => array('default' => 0), // @@@
'delete_location' => array('default' => FALSE, 'nodiff' => TRUE),
case 'validate':
if (!empty($obj['country'])) {
if (!empty($obj['province'])) {
$provinces = location_get_provinces($obj['country']);
$found = FALSE;
$p = strtoupper($obj['province']);
foreach ($provinces as $k => $v) {
if ($p == strtoupper($k) || $p == strtoupper($v)) {
$found = TRUE;
break;
}
}
if (!$found) {
form_error($a3['province'], t('The specified province was not found in the specified country.'));
}
}
}
if (!empty($obj['locpick']) && is_array($obj['locpick'])) {
// Can't specify just latitude or just longitude.
if (_location_floats_are_equal($obj['locpick']['user_latitude'], 0) xor _location_floats_are_equal($obj['locpick']['user_longitude'], 0)) {
$ref = &$a3['locpick']['user_latitude'];
if (_location_floats_are_equal($obj['locpick']['user_longitude'], 0)) {
$ref = &$a3['locpick']['user_longitude'];
}
form_error($ref, t('You must fill out both latitude and longitude or you must leave them both blank.'));
case 'field_expand':
switch ($a3) {
case 'name':
return array(
'#type' => 'textfield',
'#title' => t('Location name'),
'#default_value' => $obj,
'#size' => 64,
'#maxlength' => 64,
'#description' => t('e.g. a place of business, venue, meeting point'),
'#attributes' => NULL,
'#required' => ($a4 == 2),
);
case 'street':
return array(
'#type' => 'textfield',
'#title' => t('Street'),
'#default_value' => $obj,
'#size' => 64,
'#maxlength' => 64,
'#required' => ($a4 == 2),
);
// Additional is linked to street.
case 'additional':
return array(
'#type' => 'textfield',
'#title' => t('Additional'),
'#default_value' => $obj,
'#size' => 64,
'#maxlength' => 64,
// Required is forced OFF because this is technically part of street.
case 'city':
return array(
'#type' => 'textfield',
'#title' => t('City'),
'#default_value' => $obj,
'#size' => 64,
'#maxlength' => 64,
'#description' => NULL,
'#attributes' => NULL,
'#required' => ($a4 == 2),
);
drupal_add_js(drupal_get_path('module', 'location') .'/location_autocomplete.js');
$country = $a5['country'] ? $a5['country'] : variable_get('location_default_country', 'us');
return array(
'#type' => 'textfield',
'#title' => t('State/Province'),
'#autocomplete_path' => 'location/autocomplete/'. $country,
'#default_value' => $obj,
'#size' => 64,
'#maxlength' => 64,
'#description' => NULL,
// Used by province autocompletion js.
'#attributes' => array('class' => 'location_auto_province'),
'#required' => ($a4 == 2),
if ($a4 == 4) {
return array(
'#type' => 'value',
'#value' => $obj,
$options = array_merge(array('' => t('Please select'), 'xx' => t('NOT LISTED')), location_get_iso3166_list());
return array(
'#type' => 'select',
'#title' => t('Country'),
'#default_value' => $obj,
'#options' => $options,
'#description' => NULL,
'#required' => ($a4 == 2),
// Used by province autocompletion js.
'#attributes' => array('class' => 'location_auto_country'),
);
}
break;
case 'postal_code':
return array(
'#type' => 'textfield',
'#title' => t('Postal code'),
'#size' => 16,
'#maxlength' => 16,
'#required' => ($a4 == 2),
);
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
switch ($a3) {
case 'lid':
// Consider 0, NULL, and FALSE to be equivilent.
if (empty($obj[$a3]) && empty($a4)) {
return TRUE;
}
break;
case 'latitude':
case 'longitude':
if (_location_floats_are_equal($obj[$a3], $a4)) {
return TRUE;
}
break;
case 'country':
// Consider ' ' and '' to be equivilent, due to us storing country
// as char(2) in the database.
if (trim($obj[$a3]) == trim($a4)) {
return TRUE;
}
break;
case 'province_name':
case 'country_name':
case 'map_link':
case 'coords':
case 'locpick':
Brandon Bergren
committed
case 'delete_location':
// Always considered unchanged.
}
Brandon Bergren
committed
}
}
Ankur Rishi
committed
function location_geocoding_parameters_page($country_iso, $service) {
drupal_set_title(t('Configure parameters for %service geocoding', array('%service' => $service)));
$breadcrumbs = drupal_get_breadcrumb();
$breadcrumbs[] = l('location', 'admin/settings/location');
$breadcrumbs[] = l('geocoding', 'admin/settings/location/geocoding');
$countries = location_get_iso3166_list();
$breadcrumbs[] = l($countries[$country_iso], 'admin/settings/location/geocoding', array('fragment' => $country_iso));
drupal_set_breadcrumb($breadcrumbs);
return drupal_get_form('location_geocoding_parameters_form', $country_iso, $service);
}
function location_geocoding_parameters_form(&$form_state, $country_iso, $service) {
location_load_country($country_iso);
$geocode_settings_form_function_specific = 'location_geocode_'. $country_iso .'_'. $service .'_settings';
$geocode_settings_form_function_general = $service .'_geocode_settings';
if (function_exists($geocode_settings_form_function_specific)) {
return system_settings_form($geocode_settings_form_function_specific());
}
location_load_geocoder($service);
if (function_exists($geocode_settings_form_function_general)) {
return system_settings_form($geocode_settings_form_function_general());
}
else {
return system_settings_form(array(
'#type' => 'markup',
'#value' => t('No configuration parameters are necessary, or a form to take such paramters has not been implemented.')
));
/**
* Load associated locations.
*
* @param $id The identifier to match. (An integer.)
* @param $key The search key for {location_instance} (usually vid or uid.)
* @return An array of loaded locations.
*/
function location_load_locations($id, $key = 'vid') {
Brandon Bergren
committed
if (empty($id)) {
// If the id is 0 or '' (or false), force returning early.
// Otherwise, this could accidentally load a huge amount of data
// by accident. 0 and '' are reserved for "not applicable."
return array();
}
Brandon Bergren
committed
if ($key == 'genid') {
$result = db_query('SELECT lid FROM {location_instance} WHERE '. db_escape_table($key) ." = '%s'", $id);
}
else {
$result = db_query('SELECT lid FROM {location_instance} WHERE '. db_escape_table($key) .' = %d', $id);
}
$locations = array();
while ($lid = db_fetch_object($result)) {
$locations[] = location_load_location($lid->lid);
/**
* Save associated locations.
*
* @param $locations The associated locations.
* You can pass an empty array to remove all location references associated
* with the given criteria. This is useful if you are about to delete an object,
* and need Location to clean up any locations that are no longer referenced.
*
* @param $criteria An array of instance criteria to save as.
* Example: array('genid' => 'my_custom_1111')
*/
function location_save_locations(&$locations, $criteria) {
if (isset($locations) && is_array($locations) && !empty($criteria) && is_array($criteria)) {
foreach (array_keys($locations) as $key) {
Brandon Bergren
committed
location_save($locations[$key], TRUE, $criteria);
}
$columns = array();
$placeholders = array();
$qfrags = array();
$args = array();
foreach (array('nid' => '%d', 'vid' => '%d', 'uid' => '%d', 'genid' => "'%s'") as $key => $placeholder) {
if (isset($criteria[$key])) {
$columns[] = $key;
$placeholders[] = $placeholder;
$args[] = $criteria[$key];
$qfrags[] = "$key = $placeholder";
}
}
$querybase = 'FROM {location_instance} WHERE '. implode(' AND ', $qfrags);
$oldlids = array();
$newlids = array();
// Find affected lids.
$query = "SELECT lid $querybase";
$result = db_query($query, $args);
while ($t = db_fetch_object($result)) {
$oldlids[] = $t->lid;
}
$query = "DELETE $querybase";
db_query($query, $args);
// Tack on the lid.
$columns[] = 'lid';
$placeholders[] = '%d';
foreach ($locations as $location) {
// Don't save "empty" locations.
// location_save() explicitly returns FALSE for empty locations,
// so it should be ok to rely on the data type.
if ($location['lid'] !== FALSE) {
$args[] = $location['lid'];
$newlids[] = $location['lid'];
db_query('INSERT INTO {location_instance} ('. implode(', ', $columns) .') VALUES ('. implode(', ', $placeholders) .')', $args);
array_pop($args);
}
// Check anything that dropped a reference during this operation.
foreach (array_diff($oldlids, $newlids) as $check) {
// An instance may have been deleted. Check reference count.
$count = db_result(db_query('SELECT COUNT(*) FROM {location_instance} WHERE lid = %d', $check));
if ($count !== FALSE && $count == 0) {
watchdog('location', t('Deleting unreferenced location with LID %lid.', array('%lid' => $check)));
$location = array('lid' => $check);
location_invoke_locationapi($location, 'delete');
db_query('DELETE FROM {location} WHERE lid = %d', $location['lid']);
}
}
* Load a single location by lid.
*
* @param $lid Location ID to load.
* @return A location array.
function location_load_location($lid) {
$location = db_fetch_array(db_query('SELECT * FROM {location} WHERE lid = %d', $lid));
// @@@ Just thought of this, but I am not certain it is a good idea...
if (empty($location)) {
$location = array('lid' => $lid);
if (isset($location['source']) && $location['source'] == LOCATION_LATLON_USER_SUBMITTED) {
// Set up location chooser or lat/lon fields from the stored location.
$location['locpick'] = array(
'user_latitude' => $location['latitude'],
'user_longitude' => $location['longitude'],
);
}
Brandon Bergren
committed
// JIT Geocoding
// Geocodes during load, useful with bulk imports.
if (isset($location['source']) && $location['source'] == LOCATION_LATLON_JIT_GEOCODING) {
if (variable_get('location_jit_geocoding', FALSE)) {
_location_geo_logic($location, array('street' => 1), array());
db_query("UPDATE {location} SET latitude = '%f', longitude = '%f', source = %d WHERE lid = %d",
$location['latitude'],
$location['longitude'],
$location['source'],
$location['lid']);
}
}
Brandon Bergren
committed
$location['province_name'] = '';
$location['country_name'] = '';
if (!empty($location['country'])) {
$location['country_name'] = location_country_name($location['country']);
if (!empty($location['province'])) {
$location['province_name'] = location_province_name($location['country'], $location['province']);
}
}
$location = array_merge($location, location_invoke_locationapi($location, 'load', $lid));
/**
* Create a list of states from a given country.
*
* @param $country
* String. The country code
* @param $string
* String (optional). The state name typed by user
* @return
* Javascript array. List of states
*/
function _location_autocomplete($country, $string = '') {
$string = strtolower($string);
$string = '/^'. $string .'/';
$matches = array();
Brandon Bergren
committed
if (strpos($country, ',') !== FALSE) {
// Multiple countries specified.
$provinces = array();
$country = explode(',', $country);
foreach($country as $c) {
$provinces = $provinces + location_get_provinces($c);
}
}
else {
$provinces = location_get_provinces($country);
}
if (!empty($provinces)) {
while (list($code, $name) = each($provinces)) {
if ($counter < 5) {
if (preg_match($string, strtolower($name))) {
$matches[$name] = $name;
++$counter;
}
}
}
}
echo drupal_to_js($matches);
* Helper function for seeing if two floats are equal. We could use other functions, but all
* of them belong to libraries that do not come standard with PHP out of the box.
*/
function _location_floats_are_equal($x, $y) {
$x = floatval($x);
$y = floatval($y);
return (abs(max($x, $y) - min($x, $y)) < pow(10, -6));
}
Brandon Bergren
committed
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
/**
* Check whether a location has coordinates or not.
*
* @param $location The location to check.
* @param $canonical Is this a location that is fully saved?
* If set to TRUE, only the source will be checked.
*/
function location_has_coordinates($location, $canonical = FALSE) {
// Locations that have been fully saved have an up to date source.
if ($canonical) {
return ($location['source'] != LOCATION_LATLON_UNDEFINED);
}
// Otherwise, we need to do the full checks.
// If latitude or longitude are empty / missing
if (empty($location['latitude']) || empty($location['longitude'])) {
return FALSE;
}
// If the latitude or longitude are zeroed (Although it could be a good idea to relax this slightly sometimes)
if (_location_floats_are_equal($location['latitude'], 0.0) || _location_floats_are_equal($location['longitude'], 0.0)) {
return FALSE;
}
return TRUE;
}
/**
* Invoke a hook_locationapi() operation on all modules.
*
* @param &$location A location object.
* @param $op A string containing the name of the locationapi operation.
* @param $a3, $a4, $a5 Arguments to pass on to the hook.
* @return The returned value of the invoked hooks.
*/
function location_invoke_locationapi(&$location, $op, $a3 = NULL, $a4 = NULL, $a5 = NULL) {
$return = array();
foreach (module_implements('locationapi') as $name) {
$function = $name .'_locationapi';
$result = $function($location, $op, $a3, $a4, $a5);
if (isset($result) && is_array($result)) {
$return = array_merge($return, $result);
}
else if (isset($result)) {
$return[] = $result;
}
Ankur Rishi
committed
}
return $return;