db = $db; } /** * Get properties matching criteria * * @param array $args Query arguments * @return array Property objects */ public function get_properties($args = array()) { global $wpdb; $defaults = array( 'status' => null, // Active, Pending, Closed 'property_type' => null, // Residential, Land, Commercial 'city' => null, 'county' => null, 'postal_code' => null, 'min_price' => null, 'max_price' => null, 'min_beds' => null, 'max_beds' => null, 'min_baths' => null, 'min_sqft' => null, 'max_sqft' => null, 'year_built_min' => null, 'year_built_max' => null, 'listing_key' => null, 'listing_id' => null, 'search' => null, // Search in address/remarks 'limit' => 20, 'offset' => 0, 'orderby' => 'modification_timestamp', 'order' => 'DESC', 'include_media' => false, 'fields' => '*', // Specific fields or * ); $args = wp_parse_args($args, $defaults); // Build query $table = $this->db->properties_table(); // Fields if ($args['fields'] === '*') { $select = '*'; } else { $fields = array_map('sanitize_key', (array) $args['fields']); $select = implode(', ', $fields); } $sql = "SELECT {$select} FROM {$table}"; // WHERE conditions $where = array('mlg_can_view = 1'); $values = array(); if ($args['status']) { $where[] = 'standard_status = %s'; $values[] = $args['status']; } if ($args['property_type']) { $where[] = 'property_type = %s'; $values[] = $args['property_type']; } if ($args['city']) { $where[] = 'city = %s'; $values[] = $args['city']; } if ($args['county']) { $where[] = 'county = %s'; $values[] = $args['county']; } if ($args['postal_code']) { $where[] = 'postal_code = %s'; $values[] = $args['postal_code']; } if ($args['min_price']) { $where[] = 'list_price >= %d'; $values[] = (int) $args['min_price']; } if ($args['max_price']) { $where[] = 'list_price <= %d'; $values[] = (int) $args['max_price']; } if ($args['min_beds']) { $where[] = 'bedrooms_total >= %d'; $values[] = (int) $args['min_beds']; } if ($args['max_beds']) { $where[] = 'bedrooms_total <= %d'; $values[] = (int) $args['max_beds']; } if ($args['min_baths']) { $where[] = 'bathrooms_total >= %d'; $values[] = (int) $args['min_baths']; } if ($args['min_sqft']) { $where[] = 'living_area >= %d'; $values[] = (int) $args['min_sqft']; } if ($args['max_sqft']) { $where[] = 'living_area <= %d'; $values[] = (int) $args['max_sqft']; } if ($args['year_built_min']) { $where[] = 'year_built >= %d'; $values[] = (int) $args['year_built_min']; } if ($args['year_built_max']) { $where[] = 'year_built <= %d'; $values[] = (int) $args['year_built_max']; } if ($args['listing_key']) { $where[] = 'listing_key = %s'; $values[] = $args['listing_key']; } if ($args['listing_id']) { $where[] = 'listing_id = %s'; $values[] = $args['listing_id']; } if ($args['search']) { $search_term = '%' . $wpdb->esc_like($args['search']) . '%'; $where[] = '(street_name LIKE %s OR city LIKE %s OR public_remarks LIKE %s OR listing_id LIKE %s)'; $values[] = $search_term; $values[] = $search_term; $values[] = $search_term; $values[] = $search_term; } $sql .= ' WHERE ' . implode(' AND ', $where); // ORDER BY $allowed_orderby = array( 'modification_timestamp', 'list_price', 'bedrooms_total', 'bathrooms_total', 'living_area', 'year_built', 'days_on_market', 'city', 'created_at', ); $orderby = in_array($args['orderby'], $allowed_orderby) ? $args['orderby'] : 'modification_timestamp'; $order = strtoupper($args['order']) === 'ASC' ? 'ASC' : 'DESC'; $sql .= " ORDER BY {$orderby} {$order}"; // LIMIT/OFFSET $sql .= ' LIMIT %d OFFSET %d'; $values[] = (int) $args['limit']; $values[] = (int) $args['offset']; // Execute $results = $wpdb->get_results($wpdb->prepare($sql, $values)); // Include media if requested if ($args['include_media'] && $results) { foreach ($results as &$property) { $property->media = $this->get_property_media($property->listing_key); } } return $results; } /** * Get a single property * * @param string $identifier Listing key or listing ID * @return object|null Property object */ public function get_property($identifier) { global $wpdb; $table = $this->db->properties_table(); // Try listing_key first $property = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$table} WHERE listing_key = %s AND mlg_can_view = 1", $identifier )); // Try listing_id if not found if (!$property) { $property = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$table} WHERE listing_id = %s AND mlg_can_view = 1", $identifier )); } return $property; } /** * Get media for a property * * @param string $listing_key Listing key * @return array Media objects */ public function get_property_media($listing_key) { global $wpdb; return $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$this->db->media_table()} WHERE listing_key = %s ORDER BY media_order ASC", $listing_key )); } /** * Get primary image URL * * @param string $listing_key Listing key * @return string|null Image URL */ public function get_primary_image($listing_key) { global $wpdb; return $wpdb->get_var($wpdb->prepare( "SELECT local_url FROM {$this->db->media_table()} WHERE listing_key = %s AND local_url IS NOT NULL ORDER BY media_order ASC LIMIT 1", $listing_key )); } /** * Get distinct cities * * @param string|null $status Optional status filter * @return array City names */ public function get_distinct_cities($status = null) { global $wpdb; $table = $this->db->properties_table(); 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 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 ORDER BY city ASC" ); } return $cities; } /** * Get distinct counties * * @param string|null $status Optional status filter * @return array County names */ public function get_distinct_counties($status = null) { global $wpdb; $table = $this->db->properties_table(); 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 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 ORDER BY county ASC" ); } return $counties; } /** * Get property count * * @param array $args Filter arguments (same as get_properties) * @return int Count */ public function get_count($args = array()) { global $wpdb; $table = $this->db->properties_table(); $where = array('mlg_can_view = 1'); $values = array(); 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['county'])) { $where[] = 'county = %s'; $values[] = $args['county']; } 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']; } if (!empty($args['min_baths'])) { $where[] = 'bathrooms_total >= %d'; $values[] = (int) $args['min_baths']; } $sql = "SELECT COUNT(*) FROM {$table} WHERE " . implode(' AND ', $where); if (!empty($values)) { return (int) $wpdb->get_var($wpdb->prepare($sql, $values)); } return (int) $wpdb->get_var($sql); } /** * Check if data exists * * @return bool */ public function has_data() { global $wpdb; $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->db->properties_table()} WHERE mlg_can_view = 1" ); return (int) $count > 0; } /** * Get property types with counts * * @param string|null $status Optional status filter * @return array Property types with counts */ public function get_property_types($status = null) { global $wpdb; $table = $this->db->properties_table(); 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 GROUP BY property_type ORDER BY count DESC", $status )); } return $wpdb->get_results( "SELECT property_type, COUNT(*) as count FROM {$table} WHERE mlg_can_view = 1 AND property_type IS NOT NULL GROUP BY property_type ORDER BY count DESC" ); } /** * Get price range * * @param string|null $status Optional status filter * @return object Min and max prices */ public function get_price_range($status = null) { global $wpdb; $table = $this->db->properties_table(); 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", $status )); } 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" ); } /** * Get formatted address for a property * * @param object $property Property object * @return string Formatted address */ public function format_address($property) { $parts = array(); if ($property->street_number) { $parts[] = $property->street_number; } if ($property->street_name) { $parts[] = $property->street_name; } if ($property->street_suffix) { $parts[] = $property->street_suffix; } if ($property->unit_number) { $parts[] = '#' . $property->unit_number; } $street = implode(' ', $parts); $location_parts = array(); if ($property->city) { $location_parts[] = $property->city; } if ($property->state_or_province) { $location_parts[] = $property->state_or_province; } if ($property->postal_code) { $location_parts[] = $property->postal_code; } $location = implode(', ', $location_parts); if ($street && $location) { return $street . ', ' . $location; } return $street ?: $location; } }