Add US geo data tables, filter bounds API, and URL hash state management
- Add mls_geo_cities and mls_geo_zipcodes tables with 29,880 cities and 33,144 zip codes - Add get_filter_bounds() method to reposition map when filters don't intersect current view - Move all URL state (filters, page, scroll, map position) to hash to avoid WordPress 404s - Add filter bounds AJAX endpoint for map repositioning on filter change Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,16 @@ class MLS_Query {
|
||||
return 'state_or_province IN (' . implode(',', $states) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TBD address exclusion filter
|
||||
* Excludes properties with "TBD" as street number
|
||||
*
|
||||
* @return string SQL clause
|
||||
*/
|
||||
private function get_tbd_exclusion_filter() {
|
||||
return "(street_number IS NULL OR (street_number != 'TBD' AND street_number NOT LIKE 'TBD %'))";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get properties matching criteria
|
||||
*
|
||||
@@ -102,6 +112,9 @@ class MLS_Query {
|
||||
$where[] = $state_filter;
|
||||
}
|
||||
|
||||
// Exclude TBD addresses
|
||||
$where[] = $this->get_tbd_exclusion_filter();
|
||||
|
||||
if ($args['status']) {
|
||||
$where[] = 'standard_status = %s';
|
||||
$values[] = $args['status'];
|
||||
@@ -330,18 +343,19 @@ class MLS_Query {
|
||||
$table = $this->db->properties_table();
|
||||
$state_filter = $this->get_state_filter();
|
||||
$state_clause = $state_filter ? " AND {$state_filter}" : '';
|
||||
$tbd_clause = " AND " . $this->get_tbd_exclusion_filter();
|
||||
|
||||
if ($status) {
|
||||
$cities = $wpdb->get_col($wpdb->prepare(
|
||||
"SELECT DISTINCT city FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND city IS NOT NULL{$state_clause}
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND city IS NOT NULL{$state_clause}{$tbd_clause}
|
||||
ORDER BY city ASC",
|
||||
$status
|
||||
));
|
||||
} else {
|
||||
$cities = $wpdb->get_col(
|
||||
"SELECT DISTINCT city FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND city IS NOT NULL{$state_clause}
|
||||
WHERE mlg_can_view = 1 AND city IS NOT NULL{$state_clause}{$tbd_clause}
|
||||
ORDER BY city ASC"
|
||||
);
|
||||
}
|
||||
@@ -361,18 +375,19 @@ class MLS_Query {
|
||||
$table = $this->db->properties_table();
|
||||
$state_filter = $this->get_state_filter();
|
||||
$state_clause = $state_filter ? " AND {$state_filter}" : '';
|
||||
$tbd_clause = " AND " . $this->get_tbd_exclusion_filter();
|
||||
|
||||
if ($status) {
|
||||
$counties = $wpdb->get_col($wpdb->prepare(
|
||||
"SELECT DISTINCT county FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND county IS NOT NULL{$state_clause}
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND county IS NOT NULL{$state_clause}{$tbd_clause}
|
||||
ORDER BY county ASC",
|
||||
$status
|
||||
));
|
||||
} else {
|
||||
$counties = $wpdb->get_col(
|
||||
"SELECT DISTINCT county FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND county IS NOT NULL{$state_clause}
|
||||
WHERE mlg_can_view = 1 AND county IS NOT NULL{$state_clause}{$tbd_clause}
|
||||
ORDER BY county ASC"
|
||||
);
|
||||
}
|
||||
@@ -400,6 +415,9 @@ class MLS_Query {
|
||||
$where[] = $state_filter;
|
||||
}
|
||||
|
||||
// Exclude TBD addresses
|
||||
$where[] = $this->get_tbd_exclusion_filter();
|
||||
|
||||
if (!empty($args['status'])) {
|
||||
$where[] = 'standard_status = %s';
|
||||
$values[] = $args['status'];
|
||||
@@ -472,9 +490,10 @@ class MLS_Query {
|
||||
|
||||
$state_filter = $this->get_state_filter();
|
||||
$state_clause = $state_filter ? " AND {$state_filter}" : '';
|
||||
$tbd_clause = " AND " . $this->get_tbd_exclusion_filter();
|
||||
|
||||
$count = $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$this->db->properties_table()} WHERE mlg_can_view = 1{$state_clause}"
|
||||
"SELECT COUNT(*) FROM {$this->db->properties_table()} WHERE mlg_can_view = 1{$state_clause}{$tbd_clause}"
|
||||
);
|
||||
|
||||
return (int) $count > 0;
|
||||
@@ -492,12 +511,13 @@ class MLS_Query {
|
||||
$table = $this->db->properties_table();
|
||||
$state_filter = $this->get_state_filter();
|
||||
$state_clause = $state_filter ? " AND {$state_filter}" : '';
|
||||
$tbd_clause = " AND " . $this->get_tbd_exclusion_filter();
|
||||
|
||||
if ($status) {
|
||||
return $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT property_type, COUNT(*) as count
|
||||
FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND property_type IS NOT NULL{$state_clause}
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND property_type IS NOT NULL{$state_clause}{$tbd_clause}
|
||||
GROUP BY property_type
|
||||
ORDER BY count DESC",
|
||||
$status
|
||||
@@ -507,7 +527,7 @@ class MLS_Query {
|
||||
return $wpdb->get_results(
|
||||
"SELECT property_type, COUNT(*) as count
|
||||
FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND property_type IS NOT NULL{$state_clause}
|
||||
WHERE mlg_can_view = 1 AND property_type IS NOT NULL{$state_clause}{$tbd_clause}
|
||||
GROUP BY property_type
|
||||
ORDER BY count DESC"
|
||||
);
|
||||
@@ -525,12 +545,13 @@ class MLS_Query {
|
||||
$table = $this->db->properties_table();
|
||||
$state_filter = $this->get_state_filter();
|
||||
$state_clause = $state_filter ? " AND {$state_filter}" : '';
|
||||
$tbd_clause = " AND " . $this->get_tbd_exclusion_filter();
|
||||
|
||||
if ($status) {
|
||||
return $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT MIN(list_price) as min_price, MAX(list_price) as max_price
|
||||
FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND list_price > 0{$state_clause}",
|
||||
WHERE mlg_can_view = 1 AND standard_status = %s AND list_price > 0{$state_clause}{$tbd_clause}",
|
||||
$status
|
||||
));
|
||||
}
|
||||
@@ -538,7 +559,7 @@ class MLS_Query {
|
||||
return $wpdb->get_row(
|
||||
"SELECT MIN(list_price) as min_price, MAX(list_price) as max_price
|
||||
FROM {$table}
|
||||
WHERE mlg_can_view = 1 AND list_price > 0{$state_clause}"
|
||||
WHERE mlg_can_view = 1 AND list_price > 0{$state_clause}{$tbd_clause}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -588,4 +609,89 @@ class MLS_Query {
|
||||
|
||||
return $street ?: $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get geographic bounds for filtered properties
|
||||
* Returns min/max lat/lng for all properties matching the filters
|
||||
*
|
||||
* @param array $args Filter arguments (same as get_properties, but bounds is ignored)
|
||||
* @return array|null Bounds array with sw_lat, sw_lng, ne_lat, ne_lng or null if no results
|
||||
*/
|
||||
public function get_filter_bounds($args = array()) {
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->db->properties_table();
|
||||
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL');
|
||||
$values = array();
|
||||
|
||||
// Add state filter (MN and IA only)
|
||||
$state_filter = $this->get_state_filter();
|
||||
if ($state_filter) {
|
||||
$where[] = $state_filter;
|
||||
}
|
||||
|
||||
// Exclude TBD addresses
|
||||
$where[] = $this->get_tbd_exclusion_filter();
|
||||
|
||||
if (!empty($args['status'])) {
|
||||
$where[] = 'standard_status = %s';
|
||||
$values[] = $args['status'];
|
||||
}
|
||||
|
||||
if (!empty($args['property_type'])) {
|
||||
$where[] = 'property_type = %s';
|
||||
$values[] = $args['property_type'];
|
||||
}
|
||||
|
||||
if (!empty($args['city'])) {
|
||||
$where[] = 'city = %s';
|
||||
$values[] = $args['city'];
|
||||
}
|
||||
|
||||
if (!empty($args['postal_code'])) {
|
||||
$where[] = 'postal_code = %s';
|
||||
$values[] = $args['postal_code'];
|
||||
}
|
||||
|
||||
if (!empty($args['min_price'])) {
|
||||
$where[] = 'list_price >= %d';
|
||||
$values[] = (int) $args['min_price'];
|
||||
}
|
||||
|
||||
if (!empty($args['max_price'])) {
|
||||
$where[] = 'list_price <= %d';
|
||||
$values[] = (int) $args['max_price'];
|
||||
}
|
||||
|
||||
if (!empty($args['min_beds'])) {
|
||||
$where[] = 'bedrooms_total >= %d';
|
||||
$values[] = (int) $args['min_beds'];
|
||||
}
|
||||
|
||||
$sql = "SELECT
|
||||
MIN(latitude) as sw_lat,
|
||||
MIN(longitude) as sw_lng,
|
||||
MAX(latitude) as ne_lat,
|
||||
MAX(longitude) as ne_lng
|
||||
FROM {$table}
|
||||
WHERE " . implode(' AND ', $where);
|
||||
|
||||
if (!empty($values)) {
|
||||
$result = $wpdb->get_row($wpdb->prepare($sql, $values));
|
||||
} else {
|
||||
$result = $wpdb->get_row($sql);
|
||||
}
|
||||
|
||||
if (!$result || $result->sw_lat === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'sw_lat' => (float) $result->sw_lat,
|
||||
'sw_lng' => (float) $result->sw_lng,
|
||||
'ne_lat' => (float) $result->ne_lat,
|
||||
'ne_lng' => (float) $result->ne_lng,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user