PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.2.1
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.2.1
7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 6.0.5 All 36 releases
mlsimport / includes / mlsimport-provider-map.php

mlsimport-provider-map.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.2.1, at includes/mlsimport-provider-map.php

292 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Provider Family module and legacy compatibility helpers.
4 *
5 * Saved provider type is authoritative. Numeric MLS ranges remain only for old
6 * configurations that do not yet have a saved type.
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 /**
14 * Whether an mls_id belongs to a PropTx / AMPRE RESO Web API board (e.g. TRREB).
15 *
16 * PropTx boards are allocated the 9000-9999 id block, consistent with the
17 * exclusive-upper 1000-wide blocks used by the other providers.
18 *
19 * @param int|string $mls_id
20 * @return bool
21 */
22 function mlsimport_is_proptx_provider( $mls_id ) {
23 // Coerce to int so numeric strings ("9001") compare by value.
24 $mls_id_int = (int) $mls_id;
25 // True only inside the 9000..9999 PropTx block (upper bound exclusive).
26 return $mls_id_int >= 9000 && $mls_id_int < 10000;
27 }
28
29 /**
30 * Normalise a stored last-import date into a full OData DateTimeOffset literal.
31 *
32 * The plugin stores the last-import marker as 'Y-m-d\TH:i' (e.g. 2026-07-01T00:00),
33 * which RESO Web API providers such as AMPRE reject in a
34 * $filter=ModificationTimestamp comparison. This produces a UTC literal with a Z
35 * offset (e.g. 2026-07-01T00:00:00.000Z). An empty input is returned unchanged so
36 * callers can keep treating '' as "no incremental marker".
37 *
38 * @param string $last_date
39 * @return string
40 */
41 function mlsimport_format_odata_modification_time( $last_date ) {
42 // Empty marker passes through unchanged ("no incremental marker").
43 if ( '' === $last_date ) {
44 return '';
45 }
46
47 // Parse the stored value as UTC, then re-emit as a full DateTimeOffset (Z) literal.
48 $date_time = new DateTime( $last_date, new DateTimeZone( 'UTC' ) );
49 return $date_time->format( 'Y-m-d\TH:i:s.000\Z' );
50 }
51
52 /**
53 * Public entry point for selecting one Provider Family adapter.
54 *
55 * Callers give this module the provider type saved with the MLS configuration
56 * and the numeric MLS ID. The saved type is checked first so old numeric ranges
57 * cannot override authoritative provider data.
58 */
59 class Mlsimport_Provider_Family {
60
61 /**
62 * Return the adapter selected by saved provider type.
63 *
64 * @param string $saved_type Provider type saved with the MLS configuration.
65 * @param int|string $mls_id Numeric MLS identifier used only as fallback.
66 * @param object|null $theme_importer Active theme importer when WordPress supplies one.
67 * @return ResoBase Selected provider adapter.
68 */
69 public static function adapter( $saved_type, $mls_id, $theme_importer = null ) {
70 // Normalize the saved value once before choosing the provider class.
71 $type = strtolower( trim( (string) $saved_type ) );
72 $id = (int) $mls_id;
73
74 // Only a missing saved type may use the old numeric-ID compatibility map.
75 if ( '' === $type ) {
76 $type = self::type_from_mls_id( $id );
77 }
78
79 // The only provider-name map lives at this public module boundary. Each
80 // mapped class owns its own behavior; callers never branch on these names.
81 $classes = array(
82 'bridge' => 'BridgeResoClass',
83 'spark' => 'SparkResoClass',
84 'trestle' => 'TresleResoClass',
85 'mlsgrid' => 'MlsgridResoClass',
86 'rmls' => 'RmlsResoClass',
87 'utah_real_estate'=> 'UtahRealEstateResoClass',
88 'realcomp' => 'RealcompResoClass',
89 'realtorca' => 'RealtorCaResoClass',
90 'brightmls' => 'BrightMlsResoClass',
91 'rapattoni' => 'RapattoniResoClass',
92 'paragon' => 'ParagonResoClass',
93 'connectmls' => 'ConnectMlsResoClass',
94 'proptx' => 'ProptxResoClass',
95 'centris' => 'CentrisResoClass',
96 );
97
98 // A present recognized type is authoritative, regardless of numeric ID.
99 if ( isset( $classes[ $type ] ) ) {
100 $class_name = $classes[ $type ];
101 return new $class_name( $theme_importer );
102 }
103
104 // Never turn a present but unknown type into Bridge. Return a normal
105 // adapter-shaped error so admin and request callers handle it identically.
106 return new UnsupportedResoClass( $type );
107 }
108
109 /**
110 * Return the saved provider type only when it belongs to the selected MLS.
111 *
112 * Keeping the MLS ID beside the type prevents a provider saved for the prior
113 * selection from winning during the first request after an MLS change.
114 *
115 * @param int|string $mls_id Currently selected MLS identifier.
116 * @return string Saved provider type, or empty when absent/stale.
117 */
118 public static function saved_type( $mls_id ) {
119 $saved_mls_id = (string) get_option( 'mlsimport_provider_type_mls_id', '' );
120 if ( '' === $saved_mls_id || $saved_mls_id !== (string) $mls_id ) {
121 return '';
122 }
123
124 return strtolower( trim( (string) get_option( 'mlsimport_provider_type', '' ) ) );
125 }
126
127 /**
128 * Save the authoritative provider type returned for one MLS configuration.
129 *
130 * @param string $type Provider type returned by the SaaS configuration.
131 * @param int|string $mls_id MLS identifier that owns the type.
132 * @return void
133 */
134 public static function remember_type( $type, $mls_id ) {
135 $clean_type = strtolower( trim( (string) $type ) );
136 if ( '' === $clean_type || '' === trim( (string) $mls_id ) ) {
137 return;
138 }
139
140 update_option( 'mlsimport_provider_type', $clean_type );
141 update_option( 'mlsimport_provider_type_mls_id', (string) $mls_id );
142 }
143
144 /**
145 * Clear state that belongs only to the previously selected MLS.
146 *
147 * Saved credential fields are deliberately untouched so switching back to a
148 * prior provider restores its inputs. Active type, config, connection flag,
149 * metadata, and cached access tokens are removed immediately.
150 *
151 * @return void
152 */
153 public static function clear_active_state() {
154 delete_option( 'mlsimport_provider_type' );
155 delete_option( 'mlsimport_provider_type_mls_id' );
156 delete_option( 'mlsimport_live_mls_config' );
157 delete_option( 'mlsimport_connection_test' );
158 // Flat legacy flag only: each connection's own "_{mls_id}" populated
159 // flag (#275) stays, so switching back to a prior MLS keeps its
160 // gathered state; the save path clears the newly saved MLS's flag.
161 delete_option( 'mlsimport_mls_metadata_populated' );
162
163 self::clear_access_tokens();
164 }
165
166 /**
167 * Clear cached SaaS and Direct MLS access tokens after credentials change.
168 *
169 * @return void
170 */
171 public static function clear_access_tokens() {
172 delete_transient( 'mlsimport_saas_token' );
173 self::clear_direct_access_tokens();
174 }
175
176 /**
177 * Clear every expiring token owned by a Direct MLS provider adapter.
178 *
179 * @return void
180 */
181 public static function clear_direct_access_tokens() {
182 foreach ( array( 'trestle', 'realcomp', 'realtorca', 'brightmls', 'rapattoni' ) as $type ) {
183 delete_transient( 'mlsimport_live_token_' . $type );
184 }
185 }
186
187 /**
188 * Build the provider configuration handed to the plain admin JavaScript.
189 *
190 * Each listed MLS ID is resolved through the same adapter method used by PHP
191 * requests. JavaScript receives final field names and only shows/hides them;
192 * it never interprets provider ranges or names.
193 *
194 * @param array $mls_ids MLS IDs present in the autocomplete list.
195 * @param string $saved_type Authoritative type for the current MLS.
196 * @param int|string $saved_type_mls_id MLS ID that owns the saved type.
197 * @return array{by_mls_id:array,all_credential_fields:array}
198 */
199 public static function browser_config_for_ids( array $mls_ids, $saved_type = '', $saved_type_mls_id = '' ) {
200 $by_mls_id = array();
201 foreach ( $mls_ids as $mls_id ) {
202 $id = (string) $mls_id;
203 $type = $id === (string) $saved_type_mls_id ? $saved_type : '';
204 $provider = self::adapter( $type, $id );
205 $by_mls_id[ $id ] = array(
206 'type' => $provider->type(),
207 'credential_fields' => $provider->credential_fields(),
208 );
209 }
210
211 // Collect the form's full credential catalog from real adapters, not a UI map.
212 $all_fields = array();
213 foreach ( self::supported_types() as $type ) {
214 $all_fields = array_merge( $all_fields, self::adapter( $type, 0 )->credential_fields() );
215 }
216
217 return array(
218 'by_mls_id' => $by_mls_id,
219 'all_credential_fields' => array_values( array_unique( $all_fields ) ),
220 );
221 }
222
223 /**
224 * Return every recognized saved provider type.
225 *
226 * @return string[]
227 */
228 private static function supported_types() {
229 return array(
230 'bridge',
231 'spark',
232 'trestle',
233 'mlsgrid',
234 'rmls',
235 'utah_real_estate',
236 'realcomp',
237 'realtorca',
238 'brightmls',
239 'rapattoni',
240 'paragon',
241 'connectmls',
242 'proptx',
243 'centris',
244 );
245 }
246
247 /**
248 * Translate the historic numeric MLS ranges into a provider type.
249 *
250 * This method exists only for older saved configurations that have no type.
251 * New saved types always win before this compatibility method is called.
252 *
253 * @param int $mls_id Numeric MLS identifier.
254 * @return string Provider type used by the legacy range map.
255 */
256 private static function type_from_mls_id( $mls_id ) {
257 // Bright MLS is the reserved 8001 ID inside the ConnectMLS block.
258 if ( 8001 === $mls_id ) {
259 return 'brightmls';
260 }
261
262 // Centris is the reserved 9001 ID inside the PropTx block. Centris is a
263 // different provider with a different feed, so the surrounding range
264 // must never resolve it to PropTx when no type has been saved yet.
265 if ( 9001 === $mls_id ) {
266 return 'centris';
267 }
268
269 if ( $mls_id >= 9000 && $mls_id < 10000 ) {
270 return 'proptx';
271 }
272 if ( $mls_id >= 8000 && $mls_id < 9000 ) {
273 return 'connectmls';
274 }
275 if ( $mls_id >= 7000 && $mls_id < 8000 ) {
276 return 'realtorca';
277 }
278 if ( $mls_id >= 6000 && $mls_id < 7000 ) {
279 return 'paragon';
280 }
281 if ( $mls_id >= 5000 && $mls_id < 6000 ) {
282 return 'rapattoni';
283 }
284 if ( $mls_id > 900 && $mls_id < 3000 ) {
285 return 'trestle';
286 }
287
288 // The old code treated all remaining IDs as token-based Bridge.
289 return 'bridge';
290 }
291 }
292