| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Live mode: the read functions everything else calls. | |
| 3 | + * Direct MLS access: the read functions everything else calls. | |
| 4 | 4 | * |
| 5 | 5 | * Only mlsimport_live_search() and mlsimport_live_get() talk HTTP to the |
| 6 | 6 | * MLS; both go through the cache. Query building and response parsing stay |
| 7 | 7 | * in the pure modules. |
| @@ -64,21 +64,11 @@ | ||
| 64 | 64 | if ( array() === $config ) { |
| 65 | 65 | return null; |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | - if ( 'bridge' === ( $config['type'] ?? '' ) ) { | |
| 69 | - $query = '?ListingKey=' . rawurlencode( $listing_key ); | |
| 70 | - } else { | |
| 71 | - $query = '?'; | |
| 72 | - if ( ! empty( $config['expand'] ) ) { | |
| 73 | - $query .= '&$expand=' . $config['expand']; | |
| 74 | - } | |
| 75 | - $query .= '&$top=1&$filter=' . mlsimport_live_filter_list_segment( | |
| 76 | - mlsimport_live_field_alias( 'ListingKey', $config ), | |
| 77 | - array( $listing_key ) | |
| 78 | - ); | |
| 79 | - $query = preg_replace( '/ and $/', '', $query ); | |
| 80 | - } | |
| 68 | + $type = isset( $config['type'] ) ? (string) $config['type'] : ''; | |
| 69 | + $provider = Mlsimport_Provider_Family::adapter( $type, $config['mls_id'] ?? 0 ); | |
| 70 | + $query = $provider->build_direct_get_query( $listing_key, $config ); | |
| 81 | 71 | |
| 82 | 72 | // 'q' keys the config shape too (dialect, $expand): a provider-type or |
| 83 | 73 | // expand change refetches instead of serving the stale-shaped record. |
| 84 | 74 | $key = mlsimport_live_cache_key( |
| @@ -151,16 +141,11 @@ | ||
| 151 | 141 | * @param array $config Per-MLS config. |
| 152 | 142 | * @return string |
| 153 | 143 | */ |
| 154 | 144 | function mlsimport_live_endpoint( array $config ): string { |
| 155 | - $base = isset( $config['api_import_url'] ) ? (string) $config['api_import_url'] : ''; | |
| 156 | - if ( 'bridge' === ( $config['type'] ?? '' ) ) { | |
| 157 | - $native = preg_replace( '#/OData/([^/?]+)/Property/?#i', '/$1/listings', $base ); | |
| 158 | - if ( is_string( $native ) && '' !== $native ) { | |
| 159 | - return $native; | |
| 160 | - } | |
| 161 | - } | |
| 162 | - return $base; | |
| 145 | + $type = isset( $config['type'] ) ? (string) $config['type'] : ''; | |
| 146 | + $provider = Mlsimport_Provider_Family::adapter( $type, $config['mls_id'] ?? 0 ); | |
| 147 | + return $provider->direct_endpoint( $config ); | |
| 163 | 148 | } |
| 164 | 149 | |
| 165 | 150 | /** |
| 166 | 151 | * One direct HTTP GET against the MLS import URL. |
| @@ -169,14 +154,16 @@ | ||
| 169 | 154 | * @return array{records:array,total:int}|null Null on transport/auth/parse failure. |
| 170 | 155 | */ |
| 171 | 156 | function mlsimport_live_request( string $query ) { |
| 172 | 157 | $config = mlsimport_live_config(); |
| 158 | + $type = isset( $config['type'] ) ? (string) $config['type'] : ''; | |
| 159 | + $provider = Mlsimport_Provider_Family::adapter( $type, $config['mls_id'] ?? 0 ); | |
| 173 | 160 | $headers = mlsimport_live_auth_headers(); |
| 174 | - if ( array() === $config || array() === $headers ) { | |
| 161 | + if ( array() === $config || ! $provider->supports_direct_access() || array() === $headers ) { | |
| 175 | 162 | return null; |
| 176 | 163 | } |
| 177 | 164 | |
| 178 | - $base = mlsimport_live_endpoint( $config ); | |
| 165 | + $base = $provider->direct_endpoint( $config ); | |
| 179 | 166 | // The base URL may already carry a query part; keep exactly one '?'. |
| 180 | 167 | $url = false === strpos( $base, '?' ) |
| 181 | 168 | ? $base . $query |
| 182 | 169 | : $base . '&' . ltrim( $query, '?&' ); |
| @@ -191,77 +178,29 @@ | ||
| 191 | 178 | 'headers' => $headers, |
| 192 | 179 | ) |
| 193 | 180 | ); |
| 194 | 181 | |
| 195 | - if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { | |
| 182 | + if ( is_wp_error( $response ) ) { | |
| 183 | + error_log( 'MLSImport Direct MLS request failed: request_failed' ); | |
| 196 | 184 | return null; |
| 197 | 185 | } |
| 198 | 186 | |
| 199 | - $result = mlsimport_live_parse_response( (string) wp_remote_retrieve_body( $response ), $config ); | |
| 200 | - | |
| 201 | - // BrightMLS media lives on a separate endpoint; attach it here, inside the | |
| 202 | - // cached request path, so the cache stores records WITH their media. | |
| 203 | - if ( is_array( $result ) && 'brightmls' === ( $config['type'] ?? '' ) && array() !== $result['records'] ) { | |
| 204 | - $result['records'] = mlsimport_live_attach_brightmls_media( $result['records'], $config, $headers ); | |
| 187 | + $outcome = $provider->read_direct_response( | |
| 188 | + (int) wp_remote_retrieve_response_code( $response ), | |
| 189 | + (string) wp_remote_retrieve_body( $response ) | |
| 190 | + ); | |
| 191 | + if ( empty( $outcome['success'] ) ) { | |
| 192 | + $code = isset( $outcome['error']['code'] ) ? $outcome['error']['code'] : 'request_failed'; | |
| 193 | + error_log( 'MLSImport Direct MLS request failed: ' . $code ); | |
| 194 | + return null; | |
| 205 | 195 | } |
| 206 | 196 | |
| 207 | - return $result; | |
| 208 | -} | |
| 209 | - | |
| 210 | -/** | |
| 211 | - * Attach BrightMedia rows as standard Media[] to records that lack media, | |
| 212 | - * fetched from the separate media endpoint in chunks of 100 keys (the same | |
| 213 | - * request the AWS media fetch runs). A failed media chunk degrades to | |
| 214 | - * photo-less cards, never a failed search. | |
| 215 | - * | |
| 216 | - * @param array $records Raw RESO records from the listings response. | |
| 217 | - * @param array $config Per-MLS config (api_media_url). | |
| 218 | - * @param array $headers Auth headers of the listings request. | |
| 219 | - * @return array The records, media attached where the endpoint had it. | |
| 220 | - */ | |
| 221 | -function mlsimport_live_attach_brightmls_media( array $records, array $config, array $headers ): array { | |
| 222 | - $keys = array(); | |
| 223 | - foreach ( $records as $record ) { | |
| 224 | - if ( is_array( $record ) && empty( $record['Media'] ) && isset( $record['ListingKey'] ) ) { | |
| 225 | - $keys[] = (string) $record['ListingKey']; | |
| 226 | - } | |
| 197 | + $outcome = $provider->enrich_direct_media( $outcome, $config, $headers ); | |
| 198 | + if ( ! empty( $outcome['warning']['code'] ) ) { | |
| 199 | + error_log( 'MLSImport Direct MLS warning: ' . $outcome['warning']['code'] ); | |
| 227 | 200 | } |
| 228 | - if ( array() === $keys ) { | |
| 229 | - return $records; | |
| 230 | - } | |
| 231 | 201 | |
| 232 | - $base = isset( $config['api_media_url'] ) ? trim( (string) $config['api_media_url'] ) : ''; | |
| 233 | - if ( '' === $base ) { | |
| 234 | - // The fixed BrightMedia endpoint — the AWS media fetch's default. | |
| 235 | - $base = 'https://bright-reso.brightmls.com/RESO/OData/bright/BrightMedia'; | |
| 236 | - } | |
| 237 | - // BrightMLS's OData server rejects a '/' immediately before '?'. | |
| 238 | - $base = rtrim( $base, '/' ); | |
| 239 | - | |
| 240 | - $map = array(); | |
| 241 | - foreach ( array_chunk( $keys, 100 ) as $chunk ) { | |
| 242 | - $url = $base . mlsimport_live_brightmls_media_query( $chunk ); | |
| 243 | - $url = str_replace( array( ' ', "'" ), array( '%20', '%27' ), $url ); | |
| 244 | - $response = wp_remote_get( | |
| 245 | - $url, | |
| 246 | - array( | |
| 247 | - 'timeout' => 20, | |
| 248 | - 'headers' => $headers, | |
| 249 | - ) | |
| 250 | - ); | |
| 251 | - if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { | |
| 252 | - continue; | |
| 253 | - } | |
| 254 | - $data = json_decode( (string) wp_remote_retrieve_body( $response ), true ); | |
| 255 | - if ( is_array( $data ) && ! empty( $data['value'] ) && is_array( $data['value'] ) ) { | |
| 256 | - $map += mlsimport_live_brightmls_media_map( $data['value'] ); | |
| 257 | - } | |
| 258 | - } | |
| 259 | - | |
| 260 | - foreach ( $records as $i => $record ) { | |
| 261 | - $key = is_array( $record ) && isset( $record['ListingKey'] ) ? (string) $record['ListingKey'] : ''; | |
| 262 | - if ( '' !== $key && isset( $map[ $key ] ) ) { | |
| 263 | - $records[ $i ]['Media'] = $map[ $key ]; | |
| 264 | - } | |
| 265 | - } | |
| 266 | - return $records; | |
| 202 | + return array( | |
| 203 | + 'records' => $outcome['records'], | |
| 204 | + 'total' => $outcome['total'], | |
| 205 | + ); | |
| 267 | 206 | } |