| 1 |
<?php |
| 2 |
/** |
| 3 |
* Live mode: search-form option lists and slider bounds from the MLS. |
| 4 |
* |
| 5 |
* The form, its field catalog and its controls are unchanged |
| 6 |
* (Mlsimport_Page_Block_Search_Fields); only the option SOURCE switches. Each |
| 7 |
* taxonomy-backed field draws its dropdown from its RESO field's enum in the |
| 8 |
* saved MLS metadata (mlsimport_mls_metadata_mls_enums) — the full list, as |
| 9 |
* the Import Task screen ships it. A field with no RESO enum renders empty |
| 10 |
* and hides itself, exactly like an empty taxonomy in stored mode. |
| 11 |
* |
| 12 |
* @package Mlsimport |
| 13 |
*/ |
| 14 |
|
| 15 |
if ( ! defined( 'ABSPATH' ) ) { |
| 16 |
exit; |
| 17 |
} |
| 18 |
|
| 19 |
/** |
| 20 |
* Feed a search field's options from the MLS enums (seam #4). Hooked on the |
| 21 |
* pre filter, BEFORE the taxonomy terms are queried — live mode never needs |
| 22 |
* the local terms, so the DB work is skipped rather than discarded. |
| 23 |
* |
| 24 |
* @param array|null $pre Prior short-circuit value (null = query terms). |
| 25 |
* @param array $def The field definition. |
| 26 |
* @return array|null |
| 27 |
*/ |
| 28 |
function mlsimport_live_search_field_options( $pre, $def ) { |
| 29 |
// Already answered, gate off, or no field def: leave term-querying to run. |
| 30 |
if ( null !== $pre || ! mlsimport_live_mode_active() || ! is_array( $def ) ) { |
| 31 |
return $pre; |
| 32 |
} |
| 33 |
|
| 34 |
// The same param => RESO field vocabulary the live query filters by, so a |
| 35 |
// dropdown can only ever offer values the search will honour. |
| 36 |
$lists = mlsimport_live_param_vocabulary()['lists']; |
| 37 |
$key = isset( $def['key'] ) ? (string) $def['key'] : ''; |
| 38 |
// status maps to StandardStatus; other fields come from the vocabulary. |
| 39 |
$field = 'status' === $key ? 'StandardStatus' : ( isset( $lists[ $key ] ) ? $lists[ $key ] : '' ); |
| 40 |
// No RESO field behind this control: render empty (field hides itself). |
| 41 |
if ( '' === $field ) { |
| 42 |
return array(); |
| 43 |
} |
| 44 |
|
| 45 |
// Pull this field's enum from the saved MLS metadata. |
| 46 |
$enums = mlsimport_live_property_enums(); |
| 47 |
if ( empty( $enums[ $field ] ) || ! is_array( $enums[ $field ] ) ) { |
| 48 |
return array(); |
| 49 |
} |
| 50 |
|
| 51 |
// value => label (falling back to the value itself when the label is blank). |
| 52 |
$out = array(); |
| 53 |
foreach ( $enums[ $field ] as $value => $label ) { |
| 54 |
$out[ (string) $value ] = is_string( $label ) && '' !== $label ? $label : (string) $value; |
| 55 |
} |
| 56 |
return $out; |
| 57 |
} |
| 58 |
add_filter( 'mlsimport_search_field_options_pre', 'mlsimport_live_search_field_options', 10, 2 ); |
| 59 |
|
| 60 |
/** |
| 61 |
* Hide the keyword box in live mode: OData has no full-text search, so the |
| 62 |
* field would silently do nothing (spec v1 limitation). |
| 63 |
* |
| 64 |
* @param array|null $fields Visible field keys; null means show every field. |
| 65 |
* @return array|null |
| 66 |
*/ |
| 67 |
function mlsimport_live_hide_keyword_field( $fields ) { |
| 68 |
// Gate off: leave the visible-field list untouched. |
| 69 |
if ( ! mlsimport_live_mode_active() ) { |
| 70 |
return $fields; |
| 71 |
} |
| 72 |
// null means "all fields": expand to the full default set first so the |
| 73 |
// diff below has a concrete list to remove keywords from. |
| 74 |
if ( null === $fields ) { |
| 75 |
$fields = array_merge( array( 'keywords' ), Mlsimport_Page_Block_Search_Fields::catalog(), array( 'sort' ) ); |
| 76 |
} |
| 77 |
// Drop the keyword field and re-index. |
| 78 |
return array_values( array_diff( (array) $fields, array( 'keywords' ) ) ); |
| 79 |
} |
| 80 |
add_filter( 'mlsimport_listings_visible_fields', 'mlsimport_live_hide_keyword_field' ); |
| 81 |
|
| 82 |
/** |
| 83 |
* Hide the half-map draw-on-map tools in live mode: point-in-polygon search |
| 84 |
* needs the local listings table (MLS OData has no portable geo.intersects), |
| 85 |
* so a drawn shape could not be honoured. |
| 86 |
* |
| 87 |
* @param bool $enabled Whether the draw tools render. |
| 88 |
* @return bool |
| 89 |
*/ |
| 90 |
function mlsimport_live_hide_draw_tools( $enabled ) { |
| 91 |
// Force the draw tools off in live mode; otherwise honour the caller's value. |
| 92 |
return mlsimport_live_mode_active() ? false : $enabled; |
| 93 |
} |
| 94 |
add_filter( 'mlsimport_half_map_draw_tools', 'mlsimport_live_hide_draw_tools' ); |
| 95 |
|
| 96 |
/** |
| 97 |
* The saved MLS PropertyEnums map: RESO field => ( value => label ). |
| 98 |
* |
| 99 |
* The enum JSON runs to hundreds of KB, so the decode is memoized on the raw |
| 100 |
* option value — a form full of dropdowns parses it once, while a mid-request |
| 101 |
* option change (settings save, tests) still takes effect. |
| 102 |
* |
| 103 |
* @return array |
| 104 |
*/ |
| 105 |
function mlsimport_live_property_enums(): array { |
| 106 |
// Memoize the decode on the raw option so a form of dropdowns parses once. |
| 107 |
static $memo_raw = null; |
| 108 |
static $memo = array(); |
| 109 |
|
| 110 |
// No saved enum blob: no options. |
| 111 |
$raw = get_option( 'mlsimport_mls_metadata_mls_enums', '' ); |
| 112 |
if ( ! is_string( $raw ) || '' === $raw ) { |
| 113 |
return array(); |
| 114 |
} |
| 115 |
// Same raw string as last time: return the cached decode. |
| 116 |
if ( $raw === $memo_raw ) { |
| 117 |
return $memo; |
| 118 |
} |
| 119 |
|
| 120 |
// Decode and pull the PropertyEnums map; remember it against this raw value. |
| 121 |
$decoded = json_decode( $raw, true ); |
| 122 |
$memo_raw = $raw; |
| 123 |
$memo = isset( $decoded['global_array']['PropertyEnums'] ) && is_array( $decoded['global_array']['PropertyEnums'] ) |
| 124 |
? $decoded['global_array']['PropertyEnums'] |
| 125 |
: array(); |
| 126 |
|
| 127 |
return $memo; |
| 128 |
} |
| 129 |
|
| 130 |
/** |
| 131 |
* The price slider's ceiling from the MLS: the highest live list price, |
| 132 |
* cached a day (the stored version reads MAX(price) from the local table). |
| 133 |
* |
| 134 |
* @param int $ceiling The stored-mode ceiling. |
| 135 |
* @return int |
| 136 |
*/ |
| 137 |
function mlsimport_live_price_ceiling( $ceiling ) { |
| 138 |
// Gate off: keep the stored-mode ceiling. |
| 139 |
if ( ! mlsimport_live_mode_active() ) { |
| 140 |
return $ceiling; |
| 141 |
} |
| 142 |
|
| 143 |
// Recompute only on a cold cache. |
| 144 |
$cached = get_transient( 'mlsimport_live_price_ceiling' ); |
| 145 |
if ( false === $cached ) { |
| 146 |
// One-record search sorted by price desc = the single most expensive listing. |
| 147 |
$result = mlsimport_live_search( |
| 148 |
array( |
| 149 |
'orderby' => 'price', |
| 150 |
'order' => 'DESC', |
| 151 |
'limit' => 1, |
| 152 |
'page' => 1, |
| 153 |
) |
| 154 |
); |
| 155 |
// Pull that top listing's price via the reso-map row. |
| 156 |
$top = null; |
| 157 |
if ( is_array( $result ) && ! empty( $result['records'][0] ) && is_array( $result['records'][0] ) ) { |
| 158 |
$top = mlsimport_live_row_from_reso( $result['records'][0] )->price; |
| 159 |
} |
| 160 |
if ( null === $top || (float) $top <= 0 ) { |
| 161 |
// MLS unreachable or priceless top record: keep the stored ceiling, |
| 162 |
// don't cache the failure. |
| 163 |
return $ceiling; |
| 164 |
} |
| 165 |
// Round up to a whole currency unit and cache for a day. |
| 166 |
$cached = (int) ceil( (float) $top ); |
| 167 |
set_transient( 'mlsimport_live_price_ceiling', $cached, DAY_IN_SECONDS ); |
| 168 |
} |
| 169 |
|
| 170 |
// Never return below 1 (the slider needs a positive ceiling). |
| 171 |
return max( 1, (int) $cached ); |
| 172 |
} |
| 173 |
add_filter( 'mlsimport_search_price_ceiling', 'mlsimport_live_price_ceiling' ); |
| 174 |
|