PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.1.2
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.1.2
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.1.2, at includes/mlsimport-provider-map.php

289 lines 9.4 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 delete_option( 'mlsimport_mls_metadata_populated' );
159
160 self::clear_access_tokens();
161 }
162
163 /**
164 * Clear cached SaaS and Direct MLS access tokens after credentials change.
165 *
166 * @return void
167 */
168 public static function clear_access_tokens() {
169 delete_transient( 'mlsimport_saas_token' );
170 self::clear_direct_access_tokens();
171 }
172
173 /**
174 * Clear every expiring token owned by a Direct MLS provider adapter.
175 *
176 * @return void
177 */
178 public static function clear_direct_access_tokens() {
179 foreach ( array( 'trestle', 'realcomp', 'realtorca', 'brightmls', 'rapattoni' ) as $type ) {
180 delete_transient( 'mlsimport_live_token_' . $type );
181 }
182 }
183
184 /**
185 * Build the provider configuration handed to the plain admin JavaScript.
186 *
187 * Each listed MLS ID is resolved through the same adapter method used by PHP
188 * requests. JavaScript receives final field names and only shows/hides them;
189 * it never interprets provider ranges or names.
190 *
191 * @param array $mls_ids MLS IDs present in the autocomplete list.
192 * @param string $saved_type Authoritative type for the current MLS.
193 * @param int|string $saved_type_mls_id MLS ID that owns the saved type.
194 * @return array{by_mls_id:array,all_credential_fields:array}
195 */
196 public static function browser_config_for_ids( array $mls_ids, $saved_type = '', $saved_type_mls_id = '' ) {
197 $by_mls_id = array();
198 foreach ( $mls_ids as $mls_id ) {
199 $id = (string) $mls_id;
200 $type = $id === (string) $saved_type_mls_id ? $saved_type : '';
201 $provider = self::adapter( $type, $id );
202 $by_mls_id[ $id ] = array(
203 'type' => $provider->type(),
204 'credential_fields' => $provider->credential_fields(),
205 );
206 }
207
208 // Collect the form's full credential catalog from real adapters, not a UI map.
209 $all_fields = array();
210 foreach ( self::supported_types() as $type ) {
211 $all_fields = array_merge( $all_fields, self::adapter( $type, 0 )->credential_fields() );
212 }
213
214 return array(
215 'by_mls_id' => $by_mls_id,
216 'all_credential_fields' => array_values( array_unique( $all_fields ) ),
217 );
218 }
219
220 /**
221 * Return every recognized saved provider type.
222 *
223 * @return string[]
224 */
225 private static function supported_types() {
226 return array(
227 'bridge',
228 'spark',
229 'trestle',
230 'mlsgrid',
231 'rmls',
232 'utah_real_estate',
233 'realcomp',
234 'realtorca',
235 'brightmls',
236 'rapattoni',
237 'paragon',
238 'connectmls',
239 'proptx',
240 'centris',
241 );
242 }
243
244 /**
245 * Translate the historic numeric MLS ranges into a provider type.
246 *
247 * This method exists only for older saved configurations that have no type.
248 * New saved types always win before this compatibility method is called.
249 *
250 * @param int $mls_id Numeric MLS identifier.
251 * @return string Provider type used by the legacy range map.
252 */
253 private static function type_from_mls_id( $mls_id ) {
254 // Bright MLS is the reserved 8001 ID inside the ConnectMLS block.
255 if ( 8001 === $mls_id ) {
256 return 'brightmls';
257 }
258
259 // Centris is the reserved 9001 ID inside the PropTx block. Centris is a
260 // different provider with a different feed, so the surrounding range
261 // must never resolve it to PropTx when no type has been saved yet.
262 if ( 9001 === $mls_id ) {
263 return 'centris';
264 }
265
266 if ( $mls_id >= 9000 && $mls_id < 10000 ) {
267 return 'proptx';
268 }
269 if ( $mls_id >= 8000 && $mls_id < 9000 ) {
270 return 'connectmls';
271 }
272 if ( $mls_id >= 7000 && $mls_id < 8000 ) {
273 return 'realtorca';
274 }
275 if ( $mls_id >= 6000 && $mls_id < 7000 ) {
276 return 'paragon';
277 }
278 if ( $mls_id >= 5000 && $mls_id < 6000 ) {
279 return 'rapattoni';
280 }
281 if ( $mls_id > 900 && $mls_id < 3000 ) {
282 return 'trestle';
283 }
284
285 // The old code treated all remaining IDs as token-based Bridge.
286 return 'bridge';
287 }
288 }
289