[min_lat, max_lat, min_lng, max_lng] * * @var array */ private static $state_bounds = null; /** * Load state bounds data * * @return array */ private static function get_state_bounds() { if (self::$state_bounds === null) { $bounds_file = MLS_PLUGIN_DIR . 'data/state-bounds.php'; if (file_exists($bounds_file)) { self::$state_bounds = include $bounds_file; } else { self::$state_bounds = array(); } } return self::$state_bounds; } /** * Check if coordinates are valid for a given state * * @param float $latitude Latitude * @param float $longitude Longitude * @param string $state_code Two-letter state code (e.g., 'MN', 'IA') * @return bool True if coordinates are within state bounds */ public static function validate_coordinates($latitude, $longitude, $state_code) { // Null or empty coordinates are invalid if ($latitude === null || $longitude === null || $latitude === '' || $longitude === '') { return false; } $lat = (float) $latitude; $lng = (float) $longitude; // Basic sanity check - valid lat/lng ranges if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) { return false; } // Continental US rough bounds check (catches obviously wrong data like European coords) // This is a quick filter before state-specific validation if ($lat < 24 || $lat > 50 || $lng < -125 || $lng > -66) { // Allow Alaska, Hawaii, and territories which fall outside continental bounds if (!in_array(strtoupper($state_code), array('AK', 'HI', 'PR', 'VI', 'GU'))) { return false; } } // Get state-specific bounds $bounds = self::get_state_bounds(); $state_code = strtoupper($state_code); if (!isset($bounds[$state_code])) { // Unknown state - can't validate, assume valid return true; } list($min_lat, $max_lat, $min_lng, $max_lng) = $bounds[$state_code]; // Check if coordinates fall within state bounding box // Add small buffer (0.1 degrees ~= 7 miles) for edge cases $buffer = 0.1; return $lat >= ($min_lat - $buffer) && $lat <= ($max_lat + $buffer) && $lng >= ($min_lng - $buffer) && $lng <= ($max_lng + $buffer); } /** * Validate a property record and return whether coordinates are invalid * * @param object|array $property Property data with latitude, longitude, state_or_province * @return int 1 if coordinates are invalid, 0 if valid */ public static function check_property($property) { // Convert to array if object if (is_object($property)) { $lat = isset($property->latitude) ? $property->latitude : null; $lng = isset($property->longitude) ? $property->longitude : null; $state = isset($property->state_or_province) ? $property->state_or_province : null; } else { $lat = isset($property['latitude']) ? $property['latitude'] : null; $lng = isset($property['longitude']) ? $property['longitude'] : null; $state = isset($property['state_or_province']) ? $property['state_or_province'] : null; } // If no coordinates, mark as invalid for map purposes if ($lat === null || $lng === null) { return 1; } // If no state, can't validate - assume valid if (empty($state)) { return 0; } // Normalize state code (handle full names or codes) $state_code = self::normalize_state_code($state); // Validate return self::validate_coordinates($lat, $lng, $state_code) ? 0 : 1; } /** * Normalize state name/code to two-letter code * * @param string $state State name or code * @return string Two-letter state code */ private static function normalize_state_code($state) { $state = trim($state); // Already a two-letter code if (strlen($state) === 2) { return strtoupper($state); } // Common state name to code mappings $name_to_code = array( 'minnesota' => 'MN', 'iowa' => 'IA', 'wisconsin' => 'WI', 'north dakota' => 'ND', 'south dakota' => 'SD', 'nebraska' => 'NE', 'missouri' => 'MO', 'illinois' => 'IL', // Add more as needed ); $lower = strtolower($state); if (isset($name_to_code[$lower])) { return $name_to_code[$lower]; } // Return first two characters as fallback return strtoupper(substr($state, 0, 2)); } /** * Validate all existing properties in the database * Updates coordinates_invalid column for each record * * @param callable $progress_callback Optional callback for progress updates * @return array Results with counts */ public static function validate_all_properties($progress_callback = null) { global $wpdb; $table = $wpdb->prefix . MLS_TABLE_PROPERTIES; $results = array( 'total' => 0, 'valid' => 0, 'invalid' => 0, 'errors' => array(), ); // Get total count $results['total'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}"); if ($results['total'] === 0) { return $results; } // Process in batches $batch_size = 500; $offset = 0; while ($offset < $results['total']) { $properties = $wpdb->get_results($wpdb->prepare( "SELECT id, latitude, longitude, state_or_province FROM {$table} LIMIT %d OFFSET %d", $batch_size, $offset )); if (empty($properties)) { break; } $valid_ids = array(); $invalid_ids = array(); foreach ($properties as $property) { $is_invalid = self::check_property($property); if ($is_invalid) { $invalid_ids[] = $property->id; $results['invalid']++; } else { $valid_ids[] = $property->id; $results['valid']++; } } // Batch update valid records if (!empty($valid_ids)) { $ids_string = implode(',', array_map('intval', $valid_ids)); $wpdb->query("UPDATE {$table} SET coordinates_invalid = 0 WHERE id IN ({$ids_string})"); } // Batch update invalid records if (!empty($invalid_ids)) { $ids_string = implode(',', array_map('intval', $invalid_ids)); $wpdb->query("UPDATE {$table} SET coordinates_invalid = 1 WHERE id IN ({$ids_string})"); } $offset += $batch_size; // Progress callback if ($progress_callback && is_callable($progress_callback)) { $progress_callback($offset, $results['total'], $results['valid'], $results['invalid']); } } return $results; } /** * Get statistics about coordinate validity * * @return array Statistics */ public static function get_stats() { global $wpdb; $table = $wpdb->prefix . MLS_TABLE_PROPERTIES; return array( 'total' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}"), 'valid' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE coordinates_invalid = 0"), 'invalid' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE coordinates_invalid = 1"), 'null_coords' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE latitude IS NULL OR longitude IS NULL"), ); } }