Merge branch 'release-2.140.115'
[feisty_meow.git] / production / example_apps / zippy_maps / reference / zipcode.php
diff --git a/production/example_apps/zippy_maps/reference/zipcode.php b/production/example_apps/zippy_maps/reference/zipcode.php
new file mode 100644 (file)
index 0000000..1bfe72f
--- /dev/null
@@ -0,0 +1,829 @@
+<?php
+ /*******************************************************************************
+ *                ZIP Code and Distance Claculation Class
+ *******************************************************************************
+ *      Author:     Micah Carrick
+ *      Email:      email@micahcarrick.com
+ *      Website:    http://www.micahcarrick.com
+ *
+ *      File:       zipcode.class.php
+ *      Version:    1.2.0
+ *      Copyright:  (c) 2005 - Micah Carrick 
+ *                  You are free to use, distribute, and modify this software 
+ *                  under the terms of the GNU General Public License.  See the
+ *                  included license.txt file.
+ *
+ *******************************************************************************
+ *  VERION HISTORY:
+ *      v1.2.0 [Oct 22, 2006] - Using a completely new database based on user
+                                contributions which resolves many data bugs.
+                              - Added sorting to get_zips_in_range()
+                              - Added ability to include/exclude the base zip
+                                from get_zips_in_range()
+                              
+ *      v1.1.0 [Apr 30, 2005] - Added Jeff Bearer's code to make it MUCH faster!
+ *      v1.0.1 [Apr 22, 2005] - Fixed a typo :)
+ *      v1.0.0 [Apr 12, 2005] - Initial Version
+ *
+ *******************************************************************************
+ *  DESCRIPTION:
+ *    A PHP Class and MySQL table to find the distance between zip codes and 
+ *    find all zip codes within a given mileage or kilometer range.
+ *      
+ *******************************************************************************
+ *
+ * Note:  The following 2 database definitions are needed to import zipcode data from
+ * the new style P.O. data which is a great departure.
+ * One will either have to dynamically specify the data source or
+ * create zipcode_import.php and zipcode_new.php models containing below information
+ * 
+ *
+ *class ZipcodeNew extends AppModel {
+   var $name = 'ZipcodeNew';
+   var $useTable = 'zip_code_new';
+   var $primaryKey = 'id';
+   var $useDbConfig = 'avengerShared';
+}
+
+class ZipcodeImport extends AppModel {
+   var $name = 'ZipcodeImport';
+   var $useTable = 'free_zipcode_database';
+   var $primaryKey = 'RecordNumber';
+   var $useDbConfig = 'avengerShared';
+
+}
+
+ *
+ *
+ *
+ *
+*/
+
+// constants for setting the $units data member
+define('_UNIT_MILES', 'm');
+define('_UNIT_KILOMETERS', 'k');
+
+// constants for passing $sort to get_zips_in_range()
+define('_ZIPS_SORT_BY_DISTANCE_ASC', 1);
+define('_ZIPS_SORT_BY_DISTANCE_DESC', 2);
+define('_ZIPS_SORT_BY_ZIP_ASC', 3);
+define('_ZIPS_SORT_BY_ZIP_DESC', 4);
+
+// constant for miles to kilometers conversion
+define('_M2KM_FACTOR', 1.609344);
+
+
+class Zipcode extends AppModel {
+   var $name = 'Zipcode';
+   var $useTable = 'zip_code';
+   var $primaryKey = 'zip_code';
+   var $useDbConfig = 'avengerShared';
+   var $zipInRangeData = array();
+   var $zipPageConditions = array();
+
+   
+       function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null) {
+               //$conditions[] ="1 = 1 GROUP BY week, away_team_id, home_team_id";
+               //$recursive = -1;
+               //$fields = array('week', 'away_team_id', 'home_team_id');
+               $this->get_zips_in_range($this->zipPageConditions['zip'], $this->zipPageConditions['range'], $this->zipPageConditions['sort']);
+               $page_data = array();
+               //loop through page data
+               
+               
+               return $this->zipInRangeData;
+       }
+       
+       function paginateCount($conditions = null, $recursive = 0) {
+               return count($this->zipInRangeData);
+       }
+       
+   
+   
+   /*
+    * TAKEN FROM MICCAH CODE
+    */
+   
+       var $last_error = "";            // last error message set by this class
+   var $units = _UNIT_MILES;        // miles or kilometers
+   var $decimals = 2;               // decimal places for returned distance
+   
+   function get_distance($zip1, $zip2) {
+
+      // returns the distance between to zip codes.  If there is an error, the 
+      // function will return false and set the $last_error variable.
+      
+      if ($zip1 == $zip2)
+       return 0; // same zip code means 0 miles between. :)
+   
+   
+      // get details from database about each zip and exit if there is an error
+      
+      $details1 = $this->get_zip_point($zip1);
+      $details2 = $this->get_zip_point($zip2);
+      if ($details1 == false) {
+         $this->last_error = "No details found for zip code: $zip1";
+         return false;
+      }
+      if ($details2 == false) {
+         $this->last_error = "No details found for zip code: $zip2";
+         return false;
+      }     
+
+
+      // calculate the distance between the two points based on the lattitude
+      // and longitude pulled out of the database.
+      
+      $miles = $this->calculate_mileage($details1['lat'], $details2['lat'], $details1['lon'], $details2['lon']);
+      
+      
+      if ($this->units == _UNIT_KILOMETERS) return round($miles * _M2KM_FACTOR, $this->decimals);
+      else return round($miles, $this->decimals);       // must be miles
+      
+   }   
+
+   
+       /**
+        * 
+        * Lookups up a zip code and returns single array or multi-array based on sending multiple zip codes in array or just a string
+        * @param $zip
+        */
+       function lookup($zip, $options = array()) {
+               $default_options = 
+                       array(
+                               'bare' => false,        //set to true to strip outer Zipcode dimension
+                       );
+               $options = am($default_options, $options);
+               
+               if(is_array($zip)) {
+                       // find all
+                       $data = $this->find('all', array('conditions'=>array('Zipcode.zip_code'=>$zip), 'recursive' => -1));
+               } else {
+                       // find first
+                       $data = $this->find('first', array('conditions'=>array('Zipcode.zip_code'=>$zip), 'recursive' => -1));
+               }
+               
+               if($options['bare']) {
+                       $data = $data['Zipcode'];
+               }
+               return $data;
+       }
+   
+   /**
+    * 
+    * This returns all zip codes (even if one sent)
+    * @param $zip
+    */
+   function get_zip_details($zip) {
+         if (!$data = $this->find('all',array('conditions'=>array('zip_code'=>$zip)))){ 
+         return false;
+      } else {
+         return $data;       
+      }
+   }
+
+   function get_details_by_city($cityname) {
+
+         if (!$data = $this->find('all', array('conditions'=>array('city LIKE'=>$cityname),'recursive'=>-1))) {
+         return false;
+      } else {
+         return $data;       
+      }
+   }
+
+   function get_details_by_lat_lon($geolocation) {
+         if (!$data = $this->find('all', array('conditions'=>array('lat'=>$geolocation['latitude'],'lon'=>$geolocation['longitude']),'recursive'=>-1))) {
+         return false;
+      } else {
+         return $data;       
+      }
+   }
+   function get_details_by_location($locationtext) {
+       
+         if (!$data = $this->find('all', array('conditions'=>array('locationtext LIKE'=>$locationtext),'recursive'=>-1))) { 
+         return false;
+      } else {
+         return $data;       
+      }
+   }
+   
+  function get_details_by_county($county, $state_id){
+               if (!$data = $this->find('all', array('conditions'=>array('county LIKE'=>$county, 'state_prefix LIKE'=>$state_id),'recursive'=>-1))) { 
+                return false;
+           } else {
+                return $data;       
+           }
+    
+   }
+
+   
+   function get_locations_by_state($stateprefix) {
+      if (!$data = $this->find('all', array('conditions'=>array('state_prefix LIKE'=>$stateprefix),'recursive'=>-1,'order'=>array('zip_code'=>'asc')))){
+         return false;
+      } else {
+         return $data;       
+      }
+   }
+
+   function get_locations_by_county($county) {
+  
+      if (!$data = $this->find('all', array('conditions'=>array('county LIKE'=>$county),'recursive'=>-1))){
+
+         return false;
+      } else {
+         return $data;       
+      }
+   }
+
+   function get_counties_by_state($stateprefix) {
+  
+      if (!$data = $this->find('all', array('fields'=>array('DISTINCT county'),'conditions'=>array('state_prefix LIKE'=>$stateprefix),'recursive'=>-1,'order'=>array('county'=>'asc')))){
+         return false;
+      } else {
+         return $data;       
+      }
+   }
+
+   function get_zip_point($zip) {
+
+      // This function pulls just the lattitude and longitude from the
+      // database for a given zip code.
+      //$sql = "SELECT lat, lon from {$this->tablePrefix}zip_code WHERE zip_code='$zip'";
+      //$r = mysql_query($sql);
+
+      if (!$data = $this->find(array('zip_code'=>$zip), array('lat', 'lon'))) {
+         //$this->last_error = mysql_error();
+         return false;
+      } else {
+         //$row = mysql_fetch_array($r);
+         //mysql_free_result($r);
+         //var_dump($data['Zipcode']);
+         //return $row;
+         return $data['Zipcode'];       
+      }      
+   }
+
+   
+   function calculate_mileage($lat1, $lat2, $lon1, $lon2) {
+      // used internally, this function actually performs that calculation to
+      // determine the mileage between 2 points defined by lattitude and
+      // longitude coordinates.  This calculation is based on the code found
+      // at http://www.cryptnet.net/fsp/zipdy/
+       
+      // Convert lattitude/longitude (degrees) to radians for calculations
+      $lat1 = deg2rad($lat1);
+      $lon1 = deg2rad($lon1);
+      $lat2 = deg2rad($lat2);
+      $lon2 = deg2rad($lon2);
+      
+      // Find the deltas
+      $delta_lat = $lat2 - $lat1;
+      $delta_lon = $lon2 - $lon1;
+       
+      // Find the Great Circle distance 
+      $temp = pow(sin($delta_lat/2.0),2) + cos($lat1) * cos($lat2) * pow(sin($delta_lon/2.0),2);
+      $distance = 3956 * 2 * atan2(sqrt($temp),sqrt(1-$temp));
+
+      return $distance;
+   }
+   
+   function get_zips_in_range($zip, $range, $sort=1, $include_base = true, $options = array()) {
+               $default_options = array('datasource' => null, 'conditions' => array());
+               $options = Set::merge($default_options, $options);
+               
+      //echo 'kevin';
+      // returns an array of the zip codes within $range of $zip. Returns
+      // an array with keys as zip codes and values as the distance from 
+      // the zipcode defined in $zip.
+               //var_dump($zip,$range);
+      
+         if(is_array($zip)) {
+               //passing an array with zip code detail(s) known, don't need to lookup
+               if(isset($zip['latitude'])) {
+                       $zip['lat'] = $zip['latitude'];
+                       unset($zip['latitude']);
+               }
+               if(isset($zip['longitude'])) {
+                       $zip['lon'] = $zip['longitude'];
+                       unset($zip['longitude']);
+               }
+               $details = $zip;
+         } else {
+       $details = $this->get_zip_point($zip);  // base zip details
+         }
+
+      if ($details == false) return false;
+      
+      // This portion of the routine  calculates the minimum and maximum lat and
+      // long within a given range.  This portion of the code was written
+      // by Jeff Bearer (http://www.jeffbearer.com). This significanly decreases
+      // the time it takes to execute a query.  My demo took 3.2 seconds in 
+      // v1.0.0 and now executes in 0.4 seconds!  Greate job Jeff!
+      
+      // Find Max - Min Lat / Long for Radius and zero point and query
+      // only zips in that range.
+      
+     
+      $lat_range = $range/69.172;
+      $lon_range = abs($range/(cos($details['lat']) * 69.172));
+      $min_lat = number_format($details['lat'] - $lat_range, "4", ".", "");
+      $max_lat = number_format($details['lat'] + $lat_range, "4", ".", "");
+      $min_lon = number_format($details['lon'] - $lon_range, "4", ".", "");
+      $max_lon = number_format($details['lon'] + $lon_range, "4", ".", "");
+
+      $return = array();    // declared here for scope
+
+     /* $sql = "SELECT zip_code, lat, lon FROM zip_code ";
+      if (!$include_base) $sql .= "WHERE zip_code <> '$zip' AND ";
+      else $sql .= "WHERE "; 
+      $sql .= "lat BETWEEN '$min_lat' AND '$max_lat' 
+               AND lon BETWEEN '$min_lon' AND '$max_lon'"; */
+      
+      $conditions = array();
+      if($range === 'all') {
+               // don't want to have a range
+               // $conditions = array()
+      } else {
+             if(!$include_base) {
+               $conditions['zip_code'] = "<> $zip";
+             } else {
+               $conditions['lat BETWEEN ? AND ?'] = array($min_lat, $max_lat);
+               $conditions['lon BETWEEN ? AND ?'] = array($min_lon, $max_lon);
+             }
+      } 
+      
+      if($options['conditions']) {
+       $conditions = Set::merge($options['conditions'], $conditions);
+      }
+      
+      if($options['datasource']) {
+       $datasource =& ClassRegistry::init($options['datasource'], 'model');
+       $data = $datasource->find('all', array('conditions' =>$conditions, 'recursive' => -1));
+       
+       list($pluginName, $modelName) = pluginSplit($options['datasource']);
+       
+      } else {
+       $data = $this->find('all', array('conditions' =>$conditions));
+       $pluginName = null;
+       $modelName = 'Zipcode';
+      }
+      
+      if (!$data) {    // sql error
+               //var_dump($data);
+         //$this->last_error = mysql_error();
+         return false;
+         
+      } else {
+         //return;
+         /*while ($row = mysql_fetch_row($r)) {
+   
+            // loop through all 40 some thousand zip codes and determine whether
+            // or not it's within the specified range.
+            
+            $dist = $this->calculate_mileage($details[0],$row[1],$details[1],$row[2]);
+            if ($this->units == _UNIT_KILOMETERS) $dist = $dist * _M2KM_FACTOR;
+            if ($dist <= $range) {
+               $return[str_pad($row[0], 5, "0", STR_PAD_LEFT)] = round($dist, $this->decimals);
+            }
+         }
+         mysql_free_result($r);*/
+       $zip1VarName = 'zip_code';
+       $zip1 = null;
+       if(isset($details['zip'])) {
+               $zip1 = $details['zip'];
+               $zip1VarName = 'zip';
+       } else if (isset($details['zipcode'])) {
+               $zip1 = $details['zipcode'];
+               $zip1VarName = 'zipcode';
+       } else if (isset($details['zip_code'])) {
+               $zip1 = $details['zip_code'];
+               $zip1VarName = 'zip_code';
+       }
+       
+       $zip2VarName = 'zip_code';
+       
+       $dataByZip = array();
+       foreach($data as $row) {
+               if(isset($row[$modelName]['lat']) and isset($row[$modelName]['lon'])) {
+                       $dist = $this->calculate_mileage($details['lat'],$row[$modelName]['lat'],$details['lon'],$row[$modelName]['lon']);
+               } else {
+                       $zip2 = null;
+                       if(isset($row[$modelName]['zip'])) {
+                               $zip2 = $row[$modelName]['zip'];
+                               $zip2VarName = 'zip';
+                       } else if (isset($row[$modelName]['zipcode'])) {
+                               $zip2 = $row[$modelName]['zipcode'];
+                               $zip2VarName = 'zipcode';
+                       } else if (isset($row[$modelName]['zip_code'])) {
+                               $zip2 = $row[$modelName]['zip_code'];
+                               $zip2VarName = 'zip_code';
+                       }
+                       
+                       $dist = $this->get_distance($zip1, $zip2);
+               }
+               
+            if ($this->units == _UNIT_KILOMETERS) $dist = $dist * _M2KM_FACTOR;
+            if ($range === 'all' or $dist <= $range) {
+               $return[str_pad($row[$modelName][$zip2VarName], 5, "0", STR_PAD_LEFT)] = round($dist, $this->decimals);
+               $dataByZip[str_pad($row[$modelName][$zip2VarName], 5, "0", STR_PAD_LEFT)] = $row;
+            }
+       }
+      }
+      
+      // sort array
+      switch($sort)
+      {
+         case _ZIPS_SORT_BY_DISTANCE_ASC:
+            asort($return);
+            break;
+            
+         case _ZIPS_SORT_BY_DISTANCE_DESC:
+            arsort($return);
+            break;
+            
+         case _ZIPS_SORT_BY_ZIP_ASC:
+            ksort($return);
+            break;
+            
+         case _ZIPS_SORT_BY_ZIP_DESC:
+            krsort($return);
+            break; 
+      }
+      
+      $this->zipInRangeData = $return;
+      
+      if($options['datasource']) {
+       //merge database results back into return (with distance calculated)
+       $newReturn = array();
+       foreach($return as $zip => $distance) {
+               if(isset($dataByZip[$zip])) {
+                       $tmp = $dataByZip[$zip];
+                       $tmp['distance'] = $distance;
+                       $newReturn[] = $tmp;
+                       
+               }
+             }
+             $return = $newReturn;
+      }
+      
+      if (empty($return)) return false;
+      return $return;
+   }
+   
+   function get_detailed_zips_in_range($zip, $range, $sort=1, $include_base = true) {
+       // returns an array of the zip codes within $range of $zip with their details
+       //var_dump($zip,$range);
+   
+       $details = $this->get_zip_point($zip);  // base zip details
+
+       if ($details == false) return false;
+   
+       // This portion of the routine  calculates the minimum and maximum lat and
+       // long within a given range.  This portion of the code was written
+       // by Jeff Bearer (http://www.jeffbearer.com). This significanly decreases
+       // the time it takes to execute a query.  My demo took 3.2 seconds in
+       // v1.0.0 and now executes in 0.4 seconds!  Greate job Jeff!
+   
+       // Find Max - Min Lat / Long for Radius and zero point and query
+       // only zips in that range.
+       $lat_range = $range/69.172;
+       $lon_range = abs($range/(cos($details['lat']) * 69.172));
+       $min_lat = number_format($details['lat'] - $lat_range, "4", ".", "");
+       $max_lat = number_format($details['lat'] + $lat_range, "4", ".", "");
+       $min_lon = number_format($details['lon'] - $lon_range, "4", ".", "");
+       $max_lon = number_format($details['lon'] + $lon_range, "4", ".", "");
+   
+       $return = array();    // declared here for scope
+   
+       $conditions = array();
+       $conditions['z_primary'] = 'PRIMARY';
+       if(!$include_base) {
+               $conditions['zip_code'] = "<> $zip";
+       } else {
+               $conditions['lat BETWEEN ? AND ?'] = array($min_lat, $max_lat);
+               $conditions['lon BETWEEN ? AND ?'] = array($min_lon, $max_lon);
+       }
+   
+       if (!$data = $this->find('all', array('conditions' =>$conditions))) {    // sql error
+
+               return false;
+                
+       } else {
+               foreach($data as $row) {
+
+                       $dist = $this->calculate_mileage($details['lat'],$row['Zipcode']['lat'],$details['lon'],$row['Zipcode']['lon']);
+                       //$return['locationdetails'] = $this->find('all',array('conditions'=>array('zip_code'=>$zip)));
+                       if ($this->units == _UNIT_KILOMETERS) $dist = $dist * _M2KM_FACTOR;
+                       if ($dist <= $range) {
+                               $row['Zipcode']['distance'] = round($dist, $this->decimals);
+                               $return[] = $row;
+                       }
+               }
+       }
+
+       // sort array
+       function dist_sort($a,$b) {
+               //var_dump($a['Zipcode']['distance']);
+               if($a['Zipcode']['distance'] > $b['Zipcode']['distance'])
+                       return 1;//here,if you return -1,return 1 below,the result will be descending
+               if($a['Zipcode']['distance'] < $b['Zipcode']['distance'])
+                       return -1;
+               if($a['Zipcode']['distance'] == $b['Zipcode']['distance'])
+                       return 0;
+       }
+       switch ($sort) {
+               case '_ZIPS_SORT_BY_DISTANCE_ASC':
+                       //asort($return);
+                       //var_dump('test');
+                       uasort($return, 'dist_sort');
+                       
+                       break;
+   
+               case '_ZIPS_SORT_BY_DISTANCE_DESC':
+                       arsort($return);
+                       break;
+       }
+   
+       $this->zipInRangeData = $return;
+       //var_dump($return);
+       if (empty($return)) return false;
+       return $return;
+   }
+   
+
+       /**
+       * Returns a flat array (e.g. 04072,03801, etc.) for SQL IN statements
+    *
+    * @return array
+    */
+       function getZipsInRangeFlat() {
+               return array_keys($this->zipInRangeData);
+       }
+   
+   
+   function zipRangeOrderCase($zips = array(), $column_name = 'zip') {
+   
+               /*Order by (CASE City
+                WHEN 'Paris'   THEN 1
+                               WHEN 'Chicago'  THEN 2
+                               WHEN 'Boston'   THEN 3
+                               WHEN 'New York' THEN 4
+                               WHEN 'Berkeley' THEN 5
+                               WHEN 'Dallas'   THEN 6
+                               ELSE 100 END) ASC */
+               $order_by = '';
+               $zip_count = count($zips);
+               for($x=0; $x < $zip_count; $x++) {
+                       $order_by .= " WHEN {$zips[$x]} THEN ". ($x + 1);
+               }
+          
+               if(!empty($order_by)) {
+                       $order_by = "(CASE $column_name". $order_by ." ELSE 100 END) ASC";
+                       return $order_by;
+               } else {
+                       return false;
+               }
+   
+   }   
+       
+       /**
+        * 
+        * Takes a string [from a form]
+        * @param $search string
+        * @param $options array
+        * return false if no zip found; or text if a single zip code or an array if multiple zip codes
+        */
+       function findZipFromText($search, $options = array()) {
+               App::import('Sanitize');
+               
+               $default_options = 
+                       array(
+                       'distance' => 0,
+                       'stateList' => array(), //supply list of states, array('SN' => 'State Name');, to use instead of full geography helper list... will increase performance if you supply a small list
+                       'defaultState' => Configure::read('avorders.avengerDefaultState'), //array('ME'),       //an array of state abbreviations to use by default when a state isn't found
+                       'firstMatch' => true,   //will return first zip code that matches if distance is 0 (otherwise, returns all zipcodes)
+                       'primary'       => true,        // set to false to return all zip codes, not just primary zip codes
+                       );
+               $options = am($default_options, $options);
+               
+               if(is_numeric($search)) {
+                       //assume it's a zip code
+                       $zip = trim($search);
+                       $zip = substr($zip, 0, 5);      //make sure it's only 5 numbers
+                       
+               } else {
+                       // searching by a string
+                       // 1. clean/sanitize data
+                       // 2a. see if preg_match finds a zip code in user-entered text, if so use it
+                       // 2b. otherwise, explode on spaces, b/c each word needs can be matched, then find unique zip codes
+                       // 3. create conditions, add in distance condition and zips in range if specified
+                       // 4. append to join conditions
+                       
+                       $location_array = array();
+                       $location = Sanitize::paranoid($search, array(' ', ',', '.'));
+                       $location = trim($location);
+                       
+                       if(preg_match("/([0-9]{5})(-[0-9]{4})?/i", $location, $match)) {
+                               //zip code entered with text, use it
+                               $zip = $match[0];
+                       } else {
+                               App::import('Helper', 'Geography');
+                               $geography = new GeographyHelper();
+                               
+                               $terms = array();
+                               $stateFound = false;
+                               
+                               // zip code not in text, so try to find zipcode using city, state if known
+                               // check to see if there is a comma, if so explode on that: portsmouth, new hampshire
+                               if(strpos($location, ',') !== false) {
+                                       $location_array = explode(',', $location);
+                               } else {
+                                       //look for state names (not abbreviations) contained within search term
+                                       $stateAbbr = $geography->isAState($location, array('search' => true, 'list' => $options['stateList'])); // looking to see if a state is buried in this query 
+                                       if($stateAbbr) {
+                                               // state found, set to state_prefix and remove from search terms (for down-the-line processing)
+                                               $stateFound = true;
+                                               $terms['Zipcode.state_prefix'] = $stateAbbr['state'];
+                                               $location = str_ireplace($stateAbbr['term'], '', $location);
+                                               $location = trim($location);
+                                       }
+                                       
+                                       // explode remaining search terms on spaces
+                                       $location_array = explode(' ', $location);
+                               }
+                                       
+                               $cityTerms = array();
+                               foreach($location_array as $term) {
+                                       
+                                       if(!$stateFound) {
+                                               $stateAbbr = $geography->isAState($term);
+                                               if($stateAbbr) {
+                                                       //we know this is a state, and the 2 letter abbreviation
+                                                       $terms['Zipcode.state_prefix'] = $stateAbbr;
+                                                       $stateFound = true;
+                                               } else {
+                                                       //not a state, must be a city, eliminated everything else
+                                                       $cityTerm = $term .'%';
+                                                       if(!empty($cityTerms)) {
+                                                               $cityTerm = '%'. $cityTerm;     // if it's not the first word, need to allow double-sided wild card
+                                                       }
+                                                       $cityTerms[] = $cityTerm;
+                                               }
+                                       } else {
+                                               //state already found, these terms must be a city
+                                                $cityTerm = $term .'%';
+                                                       if(!empty($cityTerms)) {
+                                                               $cityTerm = '%'. $cityTerm;     // if it's not the first word, need to allow double-sided wild card
+                                                       }
+                                                       $cityTerms[] = $cityTerm;
+                                       }
+                               }
+                               
+                               if(!empty($cityTerms)) {
+                                       if(count($cityTerms) == 1) {
+                                               $terms['Zipcode.city LIKE'] = bootArrayFirstValue($cityTerms);
+                                       } else {
+                                               $terms['AND'] = array();
+                                               foreach($cityTerms as $term) {
+                                                       $terms['AND'][] = array('Zipcode.city LIKE' => $term);
+                                               }
+                                       }
+                               }
+                               
+                               //default state to ME, b/c this is originally for MyMaineTherapist
+                               if(!isset($terms['Zipcode.state_prefix'])) {
+                                       $terms['Zipcode.state_prefix'] = $options['defaultState'];
+                               }
+                               
+                               // see if we are checking for primary-only zip codes
+                               if($options['primary']) {
+                                       $terms['Zipcode.z_primary'] = 'PRIMARY';
+                               }
+                               
+                               $zips = $this->find('all', array('conditions' => $terms, 'fields' => 'Zipcode.city, Zipcode.state_prefix, Zipcode.zip_code, Zipcode.z_primary', 'group' => 'zip_code', 'recursive' => -1));
+                               if($zips) {
+                                       if($options['firstMatch'] or !empty($options['distance'])) {
+                                               // only want the first zip code (if distance is specified, firstMatch is implied) 
+                                               $zip = bootArrayFirstValue($zips);
+                                               $zip = isset($zip['Zipcode']['zip_code']) ? $zip['Zipcode']['zip_code'] : false;
+                                       } else {
+                                               // for multiple zip codes, you should show a list of available zip codes and allow user to pick zip code in calling function
+                                               $zip = Set::extract('/Zipcode/zip_code', $zips);
+                                       }
+                               }
+                       }
+               }
+               
+               //      ADD IN DISTANCE-BASED SEARCHING
+               if(!empty($options['distance']) and isset($zip) and $zip) {
+                       
+                       if($options['distance'] == 'all') {
+                               $distance = 'all';
+                       } else {
+                               $distance = (int) $options['distance'];
+                               $distance = ($distance > 100) ? 100 : $distance;        //cap it at 100, that's a lot of zipcodes
+                       }
+                                
+                       $zipsInRange = $this->get_zips_in_range($zip, $distance);
+                       if($zipsInRange) {
+                               $zip = array_keys($zipsInRange);
+                       }
+               } 
+               
+               
+               
+               if(!isset($zip) or !$zip) {
+                       // basically, turning zipcode off, so no results will be found... 
+                       return false;
+               }
+
+               return $zip;
+               
+       }
+       
+       
+       /**
+        * Performs a Web Service call to FreeGeoIP.net to get IP Whois Information
+        * @param string $ipaddress
+        * @return array $location zip, lat, long, etc. of ip address
+        */
+       function fetchFreeGeoIpLocation($ipaddress) {
+               //http://freegeoip.net/{format}/{ip_or_hostname}
+               // create curl resource
+               $location = array();
+               
+               
+               $ipurl = 'http://freegeoip.net/json/'.$ipaddress;
+               
+               $ch = curl_init();
+               // set url
+               curl_setopt( $ch, CURLOPT_TIMEOUT_MS, 1500 ); 
+               curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT_MS, 1500 ); 
+               
+               curl_setopt($ch, CURLOPT_URL, $ipurl);
+               //return the transfer as a string
+               curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+               // $output contains the output string
+               $output = curl_exec($ch);
+               // close curl resource to free up system resources
+               curl_close($ch);
+           if(is_string($output) and !empty($output)){
+            $location = json_decode($output, true);
+           
+            if(!isset($location['zipcode']) or empty($location['zipcode'])){
+                //need to find zip code based on geocoded lat/long
+                if(isset($location['latitude']) and !empty($location['latitude']) and isset($location['longitude']) and !empty($location['longitude'])) {
+                    $zipcode = $this->get_zips_in_range($location, 10);    //no zip code supplied, grab the one that is closest within 10 miles
+                    if(is_array($zipcode) and !empty($zipcode)){
+                       $location['zipcode'] = bootArrayFirstValue(array_keys($zipcode));
+                    }else{
+                       $location['zipcode'] = '10001';
+                    }
+                }
+            }
+            
+            if(is_array($location) and !empty($location)) {
+                   // change to match format from ZipCode table column names
+                   $location['zip_code'] = $location['zipcode'];
+                   unset($location['zipcode']);
+                   
+                   $location['lat'] = $location['latitude'];
+                   unset($location['latitude']);
+                   
+                   $location['lon'] = $location['longitude'];
+                   unset($location['longitude']);
+                   
+                   $location['state_prefix'] = $location['region_code'];
+                   unset($location['region_code']);
+                   
+                   $location['country'] = $location['country_code'];
+                   unset($location['country_code']);
+                   
+                   $location['locationtext'] = $location['city'].', '.$location['state_prefix'];
+            }
+            
+        } else {
+            $location = false;
+        }
+               
+               
+               return $location;
+               
+       }
+       
+       function getClosestLocation($location, $options = array()) {
+               $default_options = array('range' => 50);
+               $options = Set::merge($default_options, $options);
+       
+               if($this->hasField('active')) {
+                       $params['conditions'] = array('AvordersStore.active'=>1);
+               }
+               return bootArrayFirstValue($this->get_zips_in_range($location, $options['range'], 1, true, null));
+       }
+    
+}
\ No newline at end of file