0) { return max(1, $query_paged); } return 1; } /** * Build unique city/state location options from property meta. * * @return array */ public static function get_location_options(): array { $locations = []; $all_properties = get_posts([ 'post_type' => 'bizdexul_property', 'posts_per_page' => -1, 'post_status' => 'publish', 'fields' => 'ids', ]); foreach ($all_properties as $property_id) { $city = bizdex_uplisting_get_post_meta($property_id, 'city', true); $state = bizdex_uplisting_get_post_meta($property_id, 'state_territory', true); if (empty($city)) { continue; } $slug = sanitize_title($city . '-' . $state); $locations[$slug] = [ 'city' => $city, 'state' => $state, 'slug' => $slug, ]; } return $locations; } /** * Read listing filter values from the current request (with optional overrides). * * @param array $overrides Optional keys: location, bedrooms. * @return array */ public static function get_filters_from_request(array $overrides = []): array { $selected_location = self::query_text('location'); if ($selected_location === '' && !empty($overrides['location'])) { $selected_location = sanitize_text_field((string) $overrides['location']); } $start_date = self::query_ymd('start_date'); $end_date = self::query_ymd('end_date'); $selected_dates = ($start_date !== '' && $end_date !== '') ? $start_date . ' to ' . $end_date : ''; $selected_adults = self::query_absint('adults'); $selected_kids = self::query_absint('kids'); $selected_bedrooms = self::query_absint('bedrooms'); if ($selected_bedrooms <= 0 && !empty($overrides['bedrooms'])) { $selected_bedrooms = absint($overrides['bedrooms']); } $selected_bathrooms = self::query_absint('bathrooms'); $selected_beds = self::query_absint('beds'); $total_guests = $selected_adults + $selected_kids; $locations = self::get_location_options(); if ($selected_location !== '' && !isset($locations[$selected_location])) { $selected_location = ''; } return [ 'selected_location' => $selected_location, 'selected_dates' => $selected_dates, 'start_date' => $start_date, 'end_date' => $end_date, 'selected_adults' => $selected_adults, 'selected_kids' => $selected_kids, 'selected_bedrooms' => $selected_bedrooms, 'selected_bathrooms' => $selected_bathrooms, 'selected_beds' => $selected_beds, 'total_guests' => $total_guests, 'locations' => $locations, ]; } /** * Build a WordPress meta_query for property listing filters. * * @param array $filters Output from {@see get_filters_from_request()}. * @return array Empty array when no filters apply. */ public static function build_meta_query(array $filters): array { $meta_query = ['relation' => 'AND']; if (($filters['total_guests'] ?? 0) > 0) { $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('guests'), 'value' => (int) $filters['total_guests'], 'compare' => '>=', 'type' => 'NUMERIC', ]; } if (!empty($filters['selected_bedrooms'])) { $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('bedrooms'), 'value' => (int) $filters['selected_bedrooms'], 'compare' => '>=', 'type' => 'NUMERIC', ]; } if (($filters['selected_bathrooms'] ?? 0) > 0) { $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('bathrooms'), 'value' => (int) $filters['selected_bathrooms'], 'compare' => '>=', 'type' => 'NUMERIC', ]; } if (($filters['selected_beds'] ?? 0) > 0) { $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('beds'), 'value' => (int) $filters['selected_beds'], 'compare' => '>=', 'type' => 'NUMERIC', ]; } $selected_location = (string) ($filters['selected_location'] ?? ''); $locations = is_array($filters['locations'] ?? null) ? $filters['locations'] : []; if ($selected_location !== '' && isset($locations[$selected_location])) { $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('city'), 'value' => $locations[$selected_location]['city'], ]; $meta_query[] = [ 'key' => bizdex_uplisting_meta_key('state_territory'), 'value' => $locations[$selected_location]['state'], ]; } return count($meta_query) > 1 ? $meta_query : []; } /** * Build a listing page URL preserving current filters and switching view. */ public static function build_listing_view_url(string $view): string { $base_url = FrontendManager::get_listing_page_url(); $view = sanitize_key($view); if (!in_array($view, ['grid', 'list'], true)) { $view = 'list'; } $params = self::query_scalars([ 'location', 'start_date', 'end_date', 'dates', 'adults', 'kids', 'bedrooms', 'bathrooms', 'beds', 'view', 'paged', ]); if ( (empty($params['start_date']) || empty($params['end_date'])) && !empty($params['dates']) ) { $parsed = BookingDetails::parse_date_range((string) $params['dates']); if (!empty($parsed['start_date']) && !empty($parsed['end_date'])) { $params['start_date'] = self::normalize_ymd((string) $parsed['start_date']); $params['end_date'] = self::normalize_ymd((string) $parsed['end_date']); } } unset($params['dates']); if (!empty($params['start_date'])) { $params['start_date'] = self::normalize_ymd((string) $params['start_date']); if ($params['start_date'] === '') { unset($params['start_date']); } } if (!empty($params['end_date'])) { $params['end_date'] = self::normalize_ymd((string) $params['end_date']); if ($params['end_date'] === '') { unset($params['end_date']); } } $params['view'] = $view; return add_query_arg($params, $base_url); } /** * Allowed listing view from the current request query string. */ public static function get_request_view(string $default = ''): string { return self::query_view($default); } /** * Read a sanitized text value from the current request query. * * @return string */ private static function query_text(string $key): string { return Request::text('get', $key); } /** * Read a non-negative integer from the current request query. * * @return int */ private static function query_absint(string $key): int { return Request::absint('get', $key); } /** * Normalize a date string to Y-m-d, or return empty when invalid. * * @return string */ private static function normalize_ymd(string $value): string { $value = sanitize_text_field($value); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { return ''; } $parts = explode('-', $value); return checkdate((int) $parts[1], (int) $parts[2], (int) $parts[0]) ? $value : ''; } /** * Read and normalize a Y-m-d date from the current request query. * * @return string */ private static function query_ymd(string $key): string { return self::normalize_ymd(self::query_text($key)); } /** * Read the listing view mode from the request, falling back to the default. * * @return string */ private static function query_view(string $default = ''): string { $allowed = ['grid', 'list']; $view = sanitize_key(self::query_text('view')); return in_array($view, $allowed, true) ? $view : $default; } /** * Collect allowed scalar query keys as sanitized strings. * * @param string[] $allowed_keys * @return array */ private static function query_scalars(array $allowed_keys): array { $params = []; foreach ($allowed_keys as $key) { $value = self::query_text($key); if ($value !== '') { $params[$key] = $value; } } return $params; } /** * Return the current property loop context for template rendering. * * @return array */ public static function get_property_loop(): array { global $bizdex_uplisting_property_loop; return is_array($bizdex_uplisting_property_loop) ? $bizdex_uplisting_property_loop : []; } /** * Build the property archive query and store loop context globally. * * @param array $overrides Optional archive attribute overrides. * @return array */ public static function setup_property_archive(array $overrides = []): array { global $bizdex_uplisting_property_loop; $bizdex_uplisting_property_loop = FrontendManager::instance()->setup_property_archive($overrides); return $bizdex_uplisting_property_loop; } /** * Whether the archive should render the property loop area. */ public static function has_property_loop(): bool { $loop = self::get_property_loop(); return !empty($loop['query']) && $loop['query'] instanceof \WP_Query; } /** * Whether the archive query returned at least one property. */ public static function has_results(): bool { $loop = self::get_property_loop(); return self::has_property_loop() && $loop['query']->found_posts > 0; } /** * Archive page title for property listings. */ public static function get_archive_title(): string { if (is_tax('bizdexul_amenity')) { return single_term_title('', false); } if (is_post_type_archive('bizdexul_property')) { return post_type_archive_title('', false); } if (FrontendManager::is_listing_page()) { $page_id = FrontendManager::get_listing_page_id(); if ($page_id) { return get_the_title($page_id); } } return __('Properties', 'advanced-connector-for-uplisting'); } /** * Open the property grid/list markup. */ public static function loop_start(): void { $loop = self::get_property_loop(); if (empty($loop['grid_classes']) || !is_array($loop['grid_classes'])) { return; } echo '
'; } /** * Close the property grid/list markup. */ public static function loop_end(): void { echo '
'; } }