? handlers/location_handler_argument_location_distance.inc Index: location.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/location.inc,v retrieving revision 1.95.2.4 diff -u -p -r1.95.2.4 location.inc --- location.inc 19 Jan 2010 02:02:03 -0000 1.95.2.4 +++ location.inc 15 Mar 2010 21:52:02 -0000 @@ -78,6 +78,8 @@ function location_map_link($location = a * 'province' => the province code defined in the country-specific include file * 'country' => the lower-case of the two-letter ISO code (REQUIRED) * 'postal_code' => the postal-code (REQUIRED) + * @param $geocode_in_db + * Boolean. If TRUE, the coordinates for the zipcode may be geocoded, and cached in the database. * * @return * Array or NULL. NULL if the delegated-to function that does the @@ -88,10 +90,26 @@ function location_map_link($location = a * * @ingroup Location */ -function location_get_postalcode_data($location = array()) { +function location_get_postalcode_data($location = array(), $geocode_in_db = FALSE) { $location['country'] = isset($location['country']) ? trim($location['country']) : NULL; $location['postal_code'] = isset($location['postal_code']) ? trim($location['postal_code']) : NULL; - if (is_null($location['postal_code']) || is_null($location['country']) || empty($location['country']) || empty($location['postal_code']) || $location['postal_code'] == 'xx') { + + // TK edit: add city and province (they are in the zipcode table and are useful) + $location['city'] = isset($location['city']) ? trim($location['city']) : NULL; + $location['province'] = isset($location['province']) ? trim($location['province']) : NULL; + + // TK edit: separated out country check from postal code check for flexibility + // TK edit: ensure we have a country + if (is_null($location['country']) || empty($location['country'])) { + return NULL; + } + // TK edit: ensure if we have a postcode. + if (is_null($location['postal_code']) || empty($location['postal_code']) || $location['postal_code'] == 'xx') { + return NULL; + } + + // Normalize postalcode data + if (!location_standardize_postalcode($location['postal_code'], $location['country'])) { return NULL; } location_load_country($location['country']); @@ -100,6 +118,158 @@ function location_get_postalcode_data($l return $country_specific_function($location); } else { + + // Check whether postalcode data is present in zipcode table + $result = db_query("SELECT * FROM {zipcodes} WHERE country = '%s' AND zip = '%s'", $location['country'], $location['postal_code']); + if ($row = db_fetch_object($result)) { + // We can't be absolutely sure that city/province are filled in the database, but some callers use it + // TK edit: also return the 'postal_code' for completeness sake and to make sure the return array is uniform + return array('lat' => $row->latitude, 'lon' => $row->longitude, 'city' => $row->city, 'postal_code' => $row->zip, 'province' => $row->state, 'country' => $row->country); + } + elseif ($geocode_in_db) { + + // Try to get exact location. If it succeeds, cache it in zipcode table + if ($data = location_latlon_exact($location)) { + $location['latitude'] = $data['lat']; + $location['longitude'] = $data['lon']; + // TK edit: update our values to the values from our results if available + // this way we are using verified values ones when we store them and return them to the function + if (array_key_exists('city', $data) && !empty($data['city'])) { + $location['city'] = $data['city']; + } + if (array_key_exists('postal_code', $data) && !empty($data['postal_code'])) { + $location['postal_code'] = $data['postal_code']; + } + if (array_key_exists('province', $data) && !empty($data['province'])) { + $location['province'] = $data['province']; + } + // Try to see whether we can get timezone/dst data based on location. If not, we'll insert zeroes. + $country_specific_function = 'location_timezone_data_'. $location['country']; + if (function_exists($country_specific_function)) { + $tzdata = $country_specific_function($location); + } + else { + $tzdata = array('timezone' => 0, 'dst' => 0); + } + // Make sure values have the right keys and write record to database. + if (isset($location['province'])) { + $tzdata['state'] = $location['province']; + } + $tzdata['zip'] = $location['postal_code']; + // only write to the db if it isn't there already + $result = db_query("SELECT * FROM {zipcodes} WHERE country = '%s' AND zip = '%s'", $location['country'], $location['postal_code']); + if (!$row = db_fetch_object($result)) { + drupal_write_record('zipcodes', array_merge($location, $tzdata)); + } + //TK edit: added city postal_code and province to the return array to give complete results + return array('lat' => $location['latitude'], 'lon' => $location['longitude'], 'city' => $location['city'], 'postal_code' => $location['postal_code'], 'province' => $location['province']); + } + } + return NULL; + } +} + +/** + * Try to extract the the Latitude and Longitude data from the + * city. + * + * @param $location + * Array. the location data + * -> the values are: + * 'street' => the string representing the street location + * 'additional' => the string representing the additional street location portion in the location form + * 'city' => the city name (REQUIRED) + * 'province' => the province code defined in the country-specific include file + * 'country' => the lower-case of the two-letter ISO code (REQUIRED) + * 'postal_code' => the postal-code + * @param $geocode_in_db + * Boolean. If TRUE, the coordinates for the zipcode may be geocoded, and cached in the database. + * + * @return + * Array or NULL. NULL if the delegated-to function that does the + * actual look-up does not exist. If the appropriate function exists, + * then this function returns an associative array where + * 'lon' => A floating point number for the longitude coordinate of the parameter location + * 'lat' => A floating point number for the latitude coordinate of the parameter location + * + * @ingroup Location + */ +function location_get_city_data($location = array()) { + $location['country'] = isset($location['country']) ? trim($location['country']) : NULL; + $location['postal_code'] = isset($location['postal_code']) ? trim($location['postal_code']) : NULL; + $location['city'] = isset($location['city']) ? trim($location['city']) : NULL; + $location['province'] = isset($location['province']) ? trim($location['province']) : NULL; + + // ensure we have a country + if (is_null($location['country']) || empty($location['country'])) { + return NULL; + } + // ensure if we have a city. + if (is_null($location['city']) || empty($location['city']) || $location['city'] == 'xx') { + return NULL; + } + + // Normalize city data -- note: function implemented starting on line 641 + if (!location_standardize_postalcode($location['city'], $location['city'])) { + return NULL; + } + location_load_country($location['country']); + $country_specific_function = 'location_get_city_data_'. $location['country']; + if (function_exists($country_specific_function)) { + return $country_specific_function($location); + } + else { + + /* + * @@@ Should we try to get city from the db? - problem is that we either need a cities table or we need to + * query the zipcodes table, in which case we will likely get multiple results and need to determine + * the best one to use - the code below takes the first one (we could sort on postalcode first) + * + * I personally dont care about this and will always hit the geocoder for a city search. + * + $result = db_query("SELECT * FROM {zipcodes} WHERE country = '%s' AND city = '%s'", $location['country'], $location['city']); + if ($row = db_fetch_object($result)) { + return array('lat' => $row[0]->latitude, 'lon' => $row[0]->longitude, 'city' => $location['city'], 'postal_code' => $row[0]->zip, 'province' => $row[0]->state, 'country' => $row->country); + } + elseif ($geocode_in_db) { + */ + // Try to get exact location. If it succeeds, cache it in zipcode table + if ($data = location_latlon_exact($location)) { + $location['latitude'] = $data['lat']; + $location['longitude'] = $data['lon']; + // TK edit: update our values to the values from our results if available + if (array_key_exists('city', $data) && !empty($data['city'])) { + $location['city'] = $data['city']; + } + if (array_key_exists('postal_code', $data) && !empty($data['postal_code'])) { + $location['postal_code'] = $data['postal_code']; + } + if (array_key_exists('province', $data) && !empty($data['province'])) { + $location['province'] = $data['province']; + } + /* + * @@@ I dont care about adding my results to the db, but others might, so I leave this in + * + // Try to see whether we can get timezone/dst data based on location. If not, we'll insert zeroes. + $country_specific_function = 'location_timezone_data_'. $location['country']; + if (function_exists($country_specific_function)) { + $tzdata = $country_specific_function($location); + } + else { + $tzdata = array('timezone' => 0, 'dst' => 0); + } + // Make sure values have the right keys and write record to database. + if (isset($location['province'])) { + $tzdata['state'] = $location['province']; + } + $tzdata['zip'] = $location['postal_code']; + drupal_write_record('zipcodes', array_merge($location, $tzdata)); + } + */ + return array('lat' => $location['latitude'], 'lon' => $location['longitude'], 'city' => $location['city'], 'postal_code' => $location['postal_code'], 'province' => $location['province']); + } + + // - uncomment if you decide to check the db first } return NULL; } } @@ -458,6 +628,45 @@ function location_province_code($country // @@@ New in 3.x, document. /** + * Canonicalize a postal code. + */ +function location_standardize_postalcode(&$postalcode, $country = 'us') { + + // Standard things go here + $postalcode = trim($postalcode); + + // Country-custom stuff + location_load_country($country); + $country_specific_function = 'location_standardize_postalcode_'. $country; + if (function_exists($country_specific_function)) { + return $country_specific_function($postalcode); + } + + // If no custom functions yielded 'invalid', the postal code is OK + return TRUE; +} + +/** + * Canonicalize a city. + */ +function location_standardize_city(&$city, $country = 'us') { + + // Standard things go here + $city = trim($city); + + // Country-custom stuff + location_load_country($country); + $country_specific_function = 'location_standardize_city_'. $country; + if (function_exists($country_specific_function)) { + return $country_specific_function($city); + } + + // If no custom functions yielded 'invalid', the postal code is OK + return TRUE; +} + +// @@@ New in 3.x, document. +/** * Canonicalize a country code. */ function location_standardize_country_code(&$country) { Index: location.views.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/location.views.inc,v retrieving revision 1.16 diff -u -p -r1.16 location.views.inc --- location.views.inc 3 Dec 2008 22:51:23 -0000 1.16 +++ location.views.inc 15 Mar 2010 21:52:02 -0000 @@ -51,6 +51,12 @@ function location_views_handlers() { 'location_handler_argument_location_province' => array( 'parent' => 'views_handler_argument', ), + 'location_handler_argument_location_distance' => array( + 'parent' => 'views_handler_argument', + ), +// 'location_handler_argument_location_proximity' => array( +// 'parent' => 'views_handler_argument', +// ), 'location_handler_field_location_address' => array( 'parent' => 'views_handler_field', ), @@ -306,6 +312,9 @@ function location_views_data() { 'filter' => array( 'handler' => 'location_views_handler_filter_proximity', ), + 'argument' => array( + 'handler' => 'location_handler_argument_location_distance', + ), // 'relationship' => array( // 'handler' => 'location_handler_relationship_location_distance', // ), Index: geocoding/google.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/geocoding/google.inc,v retrieving revision 1.11.2.3 diff -u -p -r1.11.2.3 google.inc --- geocoding/google.inc 29 Jun 2009 14:11:16 -0000 1.11.2.3 +++ geocoding/google.inc 15 Mar 2010 21:52:02 -0000 @@ -142,10 +142,23 @@ function google_geocode_location($locati $latlon_match = array(); preg_match('/(.*)<\/coordinates>/', $http_reply->data, $latlon_match); - $latlon_exploded = explode(',', $latlon_match[1]); - return array('lat' => $latlon_exploded[1], 'lon' => $latlon_exploded[0]); + //TK edit: also get and return city postcode and province + preg_match('/(.*)<\/LocalityName>/', $http_reply->data, $city_match); + $city_exploded = explode(',', $city_match[1]); + + preg_match('/(.*)<\/PostalCodeNumber>/', $http_reply->data, $postcode_match); + $postcode = ''; + if (!empty($postcode_match)) { + $postcode_exploded = explode(',', $postcode_match[1]); + $postcode = $postcode_exploded[0]; + } + + preg_match('/(.*)<\/AdministrativeAreaName>/', $http_reply->data, $province_match); + $province_exploded = explode(',', $province_match[1]); + + return array('lat' => $latlon_exploded[1], 'lon' => $latlon_exploded[0], 'city'=> $city_exploded[0], 'postal_code' => $postcode, 'province'=> $province_exploded[0]); } /** Index: handlers/location_handler_field_location_distance.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/handlers/location_handler_field_location_distance.inc,v retrieving revision 1.2.2.1 diff -u -p -r1.2.2.1 location_handler_field_location_distance.inc --- handlers/location_handler_field_location_distance.inc 21 Jan 2009 21:39:16 -0000 1.2.2.1 +++ handlers/location_handler_field_location_distance.inc 15 Mar 2010 21:52:02 -0000 @@ -40,6 +40,7 @@ class location_handler_field_location_di 'hybrid' => t("User's location (fall back to static if unset)"), 'static' => t("Static location"), 'tied' => t("Use Distance / Proximity filter"), + 'arg' => t("Use Distance / Proximity argument"), ), '#description' => t('FIXME'), '#default_value' => $this->options['origin'], @@ -104,6 +105,18 @@ class location_handler_field_location_di } } } + if ($this->options['origin'] == 'arg') { + if (!empty($this->view->argument)) { + foreach ($this->view->argument as $k => $v) { + if ($v->table == 'location' && $v->field == 'distance' && $v->options['relationship'] == $this->options['relationship']) { + if ($v->calculate_coords()) { + $latitude = (float)$v->value['latitude']; + $longitude = (float)$v->value['longitude']; + } + } + } + } + } $this->ensure_my_table(); Index: handlers/location_views_handler_filter_proximity.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/handlers/location_views_handler_filter_proximity.inc,v retrieving revision 1.3.2.1 diff -u -p -r1.3.2.1 location_views_handler_filter_proximity.inc --- handlers/location_views_handler_filter_proximity.inc 22 Jan 2010 16:38:05 -0000 1.3.2.1 +++ handlers/location_views_handler_filter_proximity.inc 15 Mar 2010 21:52:03 -0000 @@ -22,7 +22,8 @@ class location_views_handler_filter_prox 'latitude' => '', 'longitude' => '', 'postal_code' => '', - 'country' => '', + 'city' => '', + 'country' => variable_get('location_default_country', 'us'), 'search_distance' => 100, 'search_units' => 'mile', ), @@ -56,6 +57,10 @@ class location_views_handler_filter_prox 'latlon' => t('Latitude / Longitude input'), 'postal' => t('Postal Code / Country'), 'postal_default' => t('Postal Code (assume default country)'), + 'city' => t('City / Country'), + 'city_default' => t('City (assume default country)'), + 'postal_or_city' => t('Postal Code or City / Country'), + 'postal_or_city_default' => t('Postal Code or City (assume default country)'), ), //'#id' => 'edit-options-type', '#description' => t('FIXME'), @@ -126,6 +131,14 @@ class location_views_handler_filter_prox '#dependency' => array('edit-options-type' => array('postal', 'postal_default')), ); + $form['value']['city'] = array( + '#type' => 'textfield', + '#title' => t('City'), + '#default_value' => $this->value['city'], + '#process' => array('views_process_dependency'), + '#dependency' => array('edit-options-type' => array('city')), + ); + $form['value']['country'] = array( '#type' => 'select', '#title' => t('Country'), @@ -172,11 +185,19 @@ class location_views_handler_filter_prox unset($form[$key]['longitude']); } if ($type != 'postal' && $type != 'postal_default') { - unset($form[$key]['postal_code']); + if ($type == 'postal_or_city' || $type == 'postal_or_city_default') { + $form[$key]['postal_code']['#title'] = t('Postal Code or City'); + } + else { + unset($form[$key]['postal_code']); + } } if ($type != 'postal') { unset($form[$key]['country']); } + if ($type != 'city' && $type != 'city_default') { + unset($form[$key]['city']); + } } // Used from the distance field. @@ -187,28 +208,60 @@ class location_views_handler_filter_prox } // @@@ Switch to mock location object and rely on location more? - if ($this->options['type'] == 'postal' || $this->options['type'] == 'postal_default') { + if (in_array($this->options['type'], array('postal', 'postal_default', 'city', 'city_default', 'postal_or_city', 'postal_or_city_default'))) { // Force default for country. - if ($this->options['type'] == 'postal_default') { + if (in_array($this->options['type'], array('postal_default', 'city_default', 'postal_or_city_default'))) { $this->value['country'] = variable_get('location_default_country', 'us'); } - + // we need a country, so if it is not set, return false + if (empty($this->value['country'])) { + return false; + } // Zip code lookup. - if (!empty($this->value['postal_code']) && !empty($this->value['country'])) { - $coord = location_latlon_rough($this->value); - if ($coord) { - $this->value['latitude'] = $coord['lat']; - $this->value['longitude'] = $coord['lon']; - } - else { + $coord = NULL; + if (!empty($this->value['postal_code']) && in_array($this->options['type'], array('postal', 'postal_default', 'postal_or_city', 'postal_or_city_default'))) { + $coord = location_get_postalcode_data($this->value, TRUE); + //will fail validation if not a proper postal code and $coord will be NULL + } + // City lookup using postcode value + if (!$coord && empty($this->value['city']) && in_array($this->options['type'], array('postal_or_city', 'postal_or_city_default'))) { + //copy the value of postal_code into city + $this->value['city'] = $this->value['postal_code']; + unset($this->value['postal_code']); + $coord = location_get_city_data($this->value, TRUE); + // clean up by setting the values back and returning false; + if (!$coord){ + $this->value['postal_code'] = $this->value['city']; + unset($this->value['city']); return false; } } + // City lookup. + if (!empty($this->value['city']) && in_array($this->options['type'], array('city', 'city_default'))) { + $coord = location_get_city_data($this->value, TRUE); + } + if ($coord) { + $this->value['latitude'] = $coord['lat']; + $this->value['longitude'] = $coord['lon']; + // TK edit: Add city, post_code and province from our results to $this->value, + // which keeps things accurate and tidy + if (array_key_exists('city', $coord) && !empty($coord['city'])) { + $this->value['city'] = $coord['city']; + } + if (array_key_exists('postal_code', $coord) && !empty($coord['postal_code'])) { + $this->value['postal_code'] = $coord['postal_code']; + } + if (array_key_exists('province', $coord) && !empty($coord['province'])) { + $this->value['province'] = $coord['province']; + } + } else { - // @@@ Implement full address lookup? return false; } } + else { + return false; + } if (empty($this->value['latitude']) || empty($this->value['longitude'])) { return false; } @@ -219,7 +272,6 @@ class location_views_handler_filter_prox if (empty($this->value)) { return; } - // Coordinates available? if (!$this->calculate_coords()) { // Distance set? Index: supported/location.ca.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/supported/location.ca.inc,v retrieving revision 1.11 diff -u -p -r1.11 location.ca.inc --- supported/location.ca.inc 8 Mar 2008 22:25:39 -0000 1.11 +++ supported/location.ca.inc 15 Mar 2010 21:52:03 -0000 @@ -387,3 +387,24 @@ function location_geocode_ca_geocoder($l } } } + +function location_standardize_postalcode_ca(&$postalcode) { + if (!is_string($postalcode)) { + return FALSE; + } + + $code = strtoupper(str_replace(' ','',$postalcode)); + if (preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/", $code) + || preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/", $code) + || preg_match("/^GIR0[A-Z]{2}$/", $code)) { + + preg_match('/^[a-zA-Z]*[0-9 ]+/', $postalcode, $matches); + $postalcode = substr_replace(str_replace(' ', '', $matches[0]), '', -1); + return TRUE; + } + // Return TRUE anyway. The code in http://drupal.org/files/issues/location.proximity.handler.patch + // does not protest if the postal code does not adhere to above regexps. + // @@@ Someone with knowledge about CA zipcodes please check whether we should return FALSE. + // The database ({zipcode} cache) is better off when 'wrongly formatted' postal codes get denied. + return TRUE; +} Index: supported/location.de.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/supported/location.de.inc,v retrieving revision 1.16.4.1 diff -u -p -r1.16.4.1 location.de.inc --- supported/location.de.inc 4 Feb 2009 23:16:17 -0000 1.16.4.1 +++ supported/location.de.inc 15 Mar 2010 21:52:03 -0000 @@ -256,3 +256,11 @@ function location_get_postalcode_data_de return NULL; } } + +function location_standardize_postalcode_de(&$postalcode) { + // @@@ omg, like, srly. The "$dash_index === FALSE" SO does not make sense. + // Someone please check the code in location_get_postalcode_data_de() + // and insert something _proper_ here, please? + // (Was someone trying to strip a trailing 'D-', or something?) + return TRUE; +} Index: supported/location.nl.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/supported/location.nl.inc,v retrieving revision 1.7 diff -u -p -r1.7 location.nl.inc --- supported/location.nl.inc 23 Jul 2008 18:13:07 -0000 1.7 +++ supported/location.nl.inc 15 Mar 2010 21:52:03 -0000 @@ -20,6 +20,13 @@ function location_province_list_nl() { ); } +function location_timezone_data_nl($location) { + return array( + 'timezone' => 1, + 'dst' => 1 + ); +} + function location_map_link_nl_providers() { return array( 'google' => array( @@ -50,3 +57,19 @@ function location_map_link_nl_google($lo return NULL; } } + +function location_standardize_postalcode_nl(&$postalcode) { + if (!is_string($postalcode)) { + return FALSE; + } + $postalcode = strtoupper(str_replace(' ', '', $postalcode)); + // 4-digit as well as 4+2 letters are accepted. (The latter yields better geocoding) + switch (strlen($postalcode)) { + case 4: + return is_numeric($postalcode); + case 6: + return is_numeric(substr($postalcode,0,4)) && preg_match('/^[A-Z]{2}/', substr($postalcode,-2)); + default: + return FALSE; + } +} Index: supported/location.uk.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/supported/location.uk.inc,v retrieving revision 1.7 diff -u -p -r1.7 location.uk.inc --- supported/location.uk.inc 8 Mar 2008 18:55:18 -0000 1.7 +++ supported/location.uk.inc 15 Mar 2010 21:52:03 -0000 @@ -238,6 +238,13 @@ function location_province_list_uk() { 'WRX' => "Wrexham"); } +function location_timezone_data_uk($location) { + return array( + 'timezone' => 0, + 'dst' => 1 + ); +} + function location_map_link_uk_providers() { return array( 'google' => array( @@ -268,3 +275,26 @@ function location_map_link_uk_google($lo return NULL; } } + +function location_standardize_postalcode_uk(&$postalcode) { + if (!is_string($postalcode)) { + return FALSE; + } + //capitalize it + $postalcode = strtoupper($postalcode); + // return true if it is a full valid postcode + $pattern = '/(^gir\s0aa$)|(^[a-pr-uwyz]((\d{1,2})|([a-hk-y]\d{1,2})|(\d[a-hjks-uw])|([a-hk-y]\d[abehmnprv-y]))\s\d[abd-hjlnp-uw-z]{2}$)/i'; + if (preg_match($pattern, $postalcode)) { + // first change it so that only the outward (first) part and the first digit of the inward + // part are used (that's all google uses for licensing reasons - Royal Mail prohibits them from + // providing full postcode geocoding data to third parties) - this will keep our db accurate + $postalcode = substr($postalcode, 0, strpos($postalcode, ' ')+2); + return TRUE; + } + // return true if it is a valid outward + $pattern = '/(^gir\s0aa$)|(^[a-pr-uwyz]((\d{1,2})|([a-hk-y]\d{1,2})|(\d[a-hjks-uw])|([a-hk-y]\d[abehmnprv-y])))/i'; + if (preg_match($pattern, $postalcode)) { + return TRUE; + } + return FALSE; +} Index: supported/location.us.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/location/supported/location.us.inc,v retrieving revision 1.23.2.1 diff -u -p -r1.23.2.1 location.us.inc --- supported/location.us.inc 4 Feb 2009 23:16:17 -0000 1.23.2.1 +++ supported/location.us.inc 15 Mar 2010 21:52:03 -0000 @@ -605,3 +605,16 @@ function location_province_list_numeric_ '059' => 'Virgin Islands' ); } + +function location_standardize_postalcode_us(&$postalcode) { + if (!is_string($postalcode)) { + return FALSE; + } + + // If we're dealing with a 9-digit US zipcode, strip hyphen and the last 4 digits + $dash_index = strpos($postalcode, '-'); + if ($dash_index !== FALSE) { + $postalcode = substr($postalcode, 0, $dash_index); + } + return is_numeric($postalcode); +}