Implement launch blockers and MLS state filter

- Add MLS state filter for MN/IA only queries
- Add property inquiry form auto-population with read-only display
- Update broker info and office hours in footer
- Remove Bridge Realty text from about page
- Update service area to Minnesota and Iowa
- Add HomeProz listing identification (is_homeproz column)
- Add dynamic featured listings on front page
- Add gallery thumbnail preloading and loading spinners
- Update FEATURES_PENDING with completion status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hanson.xyz Dev
2025-12-16 13:07:12 -06:00
parent 15449b9131
commit 07a8d1756e
21 changed files with 680 additions and 91 deletions
@@ -11,9 +11,9 @@ class MLS_DB {
/**
* Schema version for index migrations
* Increment this when adding new indexes
* Increment this when adding new indexes or columns
*/
const SCHEMA_VERSION = 2;
const SCHEMA_VERSION = 3;
/**
* Get table name with prefix
@@ -126,6 +126,7 @@ class MLS_DB {
list_office_key VARCHAR(50) DEFAULT NULL,
list_office_mls_id VARCHAR(50) DEFAULT NULL,
list_office_name VARCHAR(150) DEFAULT NULL,
is_homeproz TINYINT(1) NOT NULL DEFAULT 0,
photos_count INT(5) DEFAULT 0,
modification_timestamp DATETIME NOT NULL,
@@ -332,8 +333,41 @@ class MLS_DB {
update_option('mls_schema_version', 2);
}
// Migration to schema version 3: Add is_homeproz column and index
if ($current_schema < 3) {
$table_properties = $wpdb->prefix . MLS_TABLE_PROPERTIES;
// Check if column exists
$column_exists = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'is_homeproz'",
DB_NAME,
$table_properties
));
if (!$column_exists) {
$wpdb->query("ALTER TABLE {$table_properties} ADD COLUMN is_homeproz TINYINT(1) NOT NULL DEFAULT 0 AFTER list_office_name");
}
// Add index if not exists
$existing_indexes = self::get_existing_indexes($table_properties);
if (!isset($existing_indexes['idx_is_homeproz'])) {
$wpdb->query("ALTER TABLE {$table_properties} ADD INDEX idx_is_homeproz (is_homeproz)");
}
// Update existing HomeProz listings
if (defined('MLS_HOMEPROZ_OFFICE_ID')) {
$wpdb->query($wpdb->prepare(
"UPDATE {$table_properties} SET is_homeproz = 1 WHERE list_office_mls_id = %s",
MLS_HOMEPROZ_OFFICE_ID
));
}
update_option('mls_schema_version', 3);
}
// Future migrations go here:
// if ($current_schema < 3) { ... }
// if ($current_schema < 4) { ... }
}
/**
@@ -23,6 +23,23 @@ class MLS_Query {
$this->db = $db;
}
/**
* Get the state filter SQL clause
* Restricts results to MN and IA only
*
* @return string SQL clause
*/
private function get_state_filter() {
if (!defined('MLS_ALLOWED_STATES') || empty(MLS_ALLOWED_STATES)) {
return '';
}
$states = array_map(function($s) {
global $wpdb;
return $wpdb->prepare('%s', $s);
}, MLS_ALLOWED_STATES);
return 'state_or_province IN (' . implode(',', $states) . ')';
}
/**
* Get properties matching criteria
*
@@ -79,6 +96,12 @@ class MLS_Query {
$where = array('mlg_can_view = 1');
$values = array();
// Add state filter (MN and IA only)
$state_filter = $this->get_state_filter();
if ($state_filter) {
$where[] = $state_filter;
}
if ($args['status']) {
$where[] = 'standard_status = %s';
$values[] = $args['status'];
@@ -305,18 +328,20 @@ class MLS_Query {
global $wpdb;
$table = $this->db->properties_table();
$state_filter = $this->get_state_filter();
$state_clause = $state_filter ? " AND {$state_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
WHERE mlg_can_view = 1 AND standard_status = %s AND city IS NOT NULL{$state_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
WHERE mlg_can_view = 1 AND city IS NOT NULL{$state_clause}
ORDER BY city ASC"
);
}
@@ -334,18 +359,20 @@ class MLS_Query {
global $wpdb;
$table = $this->db->properties_table();
$state_filter = $this->get_state_filter();
$state_clause = $state_filter ? " AND {$state_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
WHERE mlg_can_view = 1 AND standard_status = %s AND county IS NOT NULL{$state_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
WHERE mlg_can_view = 1 AND county IS NOT NULL{$state_clause}
ORDER BY county ASC"
);
}
@@ -367,6 +394,12 @@ class MLS_Query {
$where = array('mlg_can_view = 1');
$values = array();
// Add state filter (MN and IA only)
$state_filter = $this->get_state_filter();
if ($state_filter) {
$where[] = $state_filter;
}
if (!empty($args['status'])) {
$where[] = 'standard_status = %s';
$values[] = $args['status'];
@@ -437,8 +470,11 @@ class MLS_Query {
public function has_data() {
global $wpdb;
$state_filter = $this->get_state_filter();
$state_clause = $state_filter ? " AND {$state_filter}" : '';
$count = $wpdb->get_var(
"SELECT COUNT(*) FROM {$this->db->properties_table()} WHERE mlg_can_view = 1"
"SELECT COUNT(*) FROM {$this->db->properties_table()} WHERE mlg_can_view = 1{$state_clause}"
);
return (int) $count > 0;
@@ -454,12 +490,14 @@ class MLS_Query {
global $wpdb;
$table = $this->db->properties_table();
$state_filter = $this->get_state_filter();
$state_clause = $state_filter ? " AND {$state_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
WHERE mlg_can_view = 1 AND standard_status = %s AND property_type IS NOT NULL{$state_clause}
GROUP BY property_type
ORDER BY count DESC",
$status
@@ -469,7 +507,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
WHERE mlg_can_view = 1 AND property_type IS NOT NULL{$state_clause}
GROUP BY property_type
ORDER BY count DESC"
);
@@ -485,12 +523,14 @@ class MLS_Query {
global $wpdb;
$table = $this->db->properties_table();
$state_filter = $this->get_state_filter();
$state_clause = $state_filter ? " AND {$state_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",
WHERE mlg_can_view = 1 AND standard_status = %s AND list_price > 0{$state_clause}",
$status
));
}
@@ -498,7 +538,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"
WHERE mlg_can_view = 1 AND list_price > 0{$state_clause}"
);
}
@@ -694,6 +694,7 @@ class MLS_Sync_Engine {
'list_office_key' => $property['ListOfficeKey'] ?? null,
'list_office_mls_id' => $property['ListOfficeMlsId'] ?? null,
'list_office_name' => $property['ListOfficeName'] ?? null,
'is_homeproz' => (($property['ListOfficeMlsId'] ?? '') === MLS_HOMEPROZ_OFFICE_ID) ? 1 : 0,
'photos_count' => $property['PhotosCount'] ?? 0,
'modification_timestamp' => $this->format_timestamp($property['ModificationTimestamp'] ?? null),
@@ -32,6 +32,12 @@ define('MLS_TABLE_RATE_LIMITS', 'mls_rate_limits');
define('MLS_TABLE_SYNC_LOG', 'mls_sync_log');
define('MLS_TABLE_MEDIA_LOG', 'mls_media_log');
// HomeProz office MLS ID for identifying our listings
define('MLS_HOMEPROZ_OFFICE_ID', 'NST253235');
// Allowed states for MLS queries (MN and IA only)
define('MLS_ALLOWED_STATES', array('MN', 'IA'));
/**
* Main plugin class
*/