PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.2
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.2
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 6.0.7 All 35 releases
mlsimport / enviroment / RealHomesClass.php

RealHomesClass.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.2, at enviroment/RealHomesClass.php

370 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * RealHomesClass — theme adapter for the "Real Homes" (Inspiry) WordPress theme.
4 *
5 * One of the plugin's theme adapters (see enviroment/ directory). Each adapter maps
6 * RESO-standard MLS property data onto the post type + post-meta conventions of a
7 * specific real-estate theme. This class targets Real Homes, whose property meta keys
8 * are prefixed REAL_HOMES_* / inspiry_*.
9 *
10 * Stored Listing Write owns shared selection, normalization, and WordPress
11 * persistence. This adapter keeps only the Real Homes gallery/additional-details
12 * representation, creation defaults, custom-field registry, and remote-image
13 * display filters that genuinely vary by theme.
14 *
15 * @package MLSImport
16 */
17 // Abort if the file is accessed directly outside of WordPress.
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit; // Exit if accessed directly
20 }
21
22
23 /**
24 * Persist genuine Real Homes variation behind the Stored adapter seam.
25 *
26 * @author mlsimport
27 */
28 class RealHomesClass {
29
30 /**
31 * Register the filters that make remote MLS images render like local attachments.
32 */
33 public function __construct() {
34 // Enable support for remote MLS images in media JS and thumbnails
35 // Inject remote URL/sizes into the media library's JS attachment payload.
36 add_filter( 'wp_prepare_attachment_for_js', [ $this, 'inject_remote_image_data' ], 20 );
37 // Short-circuit image_downsize so wp_get_attachment_image() returns the remote URL.
38 add_filter( 'image_downsize', [ $this, 'override_image_downsize' ], 10, 3 );
39 }
40
41
42 /**
43 * return custom post field
44 *
45 * The post type Real Homes uses to store property listings.
46 *
47 * @since 1.0.0
48 * @access protected
49 * @var string $plugin_name
50 * @return string The 'property' post type slug.
51 */
52 public function get_property_post_type() {
53 // Real Homes stores listings in the 'property' post type.
54 return 'property';
55 }
56
57 /**
58 * Return the Real Homes post type used for Managed Listing lookup/write.
59 *
60 * @return string Real Homes property post-type slug.
61 */
62 public function property_post_type(): string {
63 return 'property';
64 }
65
66 /**
67 * Replace Real Homes' repeated gallery-meta rows with the final ordered set.
68 *
69 * @param int $property_id Managed Listing post ID.
70 * @param array<int, int> $attachment_ids Final ordered attachment IDs.
71 * @return bool Whether the gallery representation was written.
72 */
73 public function write_gallery( int $property_id, array $attachment_ids ): bool {
74 delete_post_meta( $property_id, 'REAL_HOMES_property_images' );
75 foreach ( $attachment_ids as $attachment_id ) {
76 add_post_meta( $property_id, 'REAL_HOMES_property_images', (int) $attachment_id );
77 }
78 return true;
79 }
80
81 /**
82 * Persist Real Homes coordinates, details, and creation-time defaults.
83 *
84 * @param int $property_id Managed Listing post ID.
85 * @param array<string, mixed> $property Raw incoming property.
86 * @param array<string, mixed> $context Prepared fields and creation choices.
87 * @return bool Whether the theme projection completed.
88 */
89 public function write_theme_projection( int $property_id, array $property, array $context ): bool {
90 $meta = is_array( $property['meta'] ?? null ) ? $property['meta'] : array();
91 if ( isset( $meta['property_longitude'], $meta['property_latitude'] ) ) {
92 update_post_meta( $property_id, 'REAL_HOMES_property_location', (string) $meta['property_latitude'] . ',' . (string) $meta['property_longitude'] );
93 }
94
95 $details = array();
96 foreach ( (array) ( $context['fields'] ?? array() ) as $field ) {
97 // GitHub issue #286: the identity field is never projected as theme
98 // meta or an additional-details row; its lowercased form collides
99 // with the listing identity under case-insensitive meta keys.
100 if ( 'listingkey' === strtolower( (string) $field['field'] ) ) {
101 continue;
102 }
103 if ( '' === (string) ( $field['value'] ?? '' ) ) {
104 continue;
105 }
106 if ( ! empty( $field['admin'] ) ) {
107 update_post_meta( $property_id, strtolower( (string) $field['label'] ), (string) $field['value'] );
108 continue;
109 }
110 $details[ (string) $field['label'] ] = (string) $field['value'];
111 }
112 update_post_meta( $property_id, 'REAL_HOMES_additional_details', $details );
113 update_post_meta( $property_id, 'REAL_HOMES_additional_details_list', $details );
114 if ( empty( $context['is_new'] ) ) {
115 return true;
116 }
117
118 update_post_meta( $property_id, 'REAL_HOMES_featured', 0 );
119 update_post_meta( $property_id, 'REAL_HOMES_agent_display_option', 'agent_info' );
120 update_post_meta( $property_id, 'REAL_HOMES_agents', (int) ( $context['assigned_agent_id'] ?? 0 ) );
121 update_post_meta( $property_id, 'REAL_HOMES_property_map', 0 );
122 update_post_meta( $property_id, 'inspiry_property_label_color', '#fb641c' );
123 if ( '' === get_post_meta( $property_id, 'REAL_HOMES_property_size_postfix', true ) ) {
124 update_post_meta( $property_id, 'REAL_HOMES_property_size_postfix', 'Sq Ft' );
125 }
126 if ( '' === get_post_meta( $property_id, 'REAL_HOMES_property_lot_size_postfix', true ) ) {
127 update_post_meta( $property_id, 'REAL_HOMES_property_lot_size_postfix', 'Sq Ft' );
128 }
129 return true;
130 }
131
132
133
134 /**
135 * return custom post field
136 *
137 * The post type(s) Real Homes uses for agents/agencies.
138 *
139 * @since 1.0.0
140 * @access protected
141 * @var string $plugin_name
142 * @return array The 'agency' and 'agent' post-type slugs.
143 */
144 public function get_agent_post_type() {
145 // Real Homes represents contacts as both 'agency' and 'agent' post types.
146 return array('agency','agent');
147 }
148
149
150 /**
151 * save custom fields per environment
152 *
153 * Syncs the plugin's selected MLS fields into the theme's custom-fields registry
154 * (stored in the 'wpresidence_admin' option as 'wpestate_custom_fields_list').
155 * Enabled non-admin, non-taxonomy fields are added/updated; others are removed.
156 * Finally the list is re-sorted by field order.
157 *
158 * @since 1.0.0
159 * @access protected
160 * @var string $plugin_name
161 * @param string $option_name Option prefix used to read '{prefix}_admin_fields_select'.
162 */
163 public function enviroment_custom_fields( $option_name ) {
164 // Load the theme option that holds the custom-fields registry.
165 $theme_options = get_option( 'wpresidence_admin' );
166 $custom_fields = array();
167 if ( isset( $theme_options['wpestate_custom_fields_list'] ) ) {
168 $custom_fields = $theme_options['wpestate_custom_fields_list'];
169 }
170
171 // Ensure we have an array to work with.
172 if ( ! is_array( $custom_fields ) ) {
173 $custom_fields = array();
174 }
175
176 // Guarantee every parallel column exists as an array.
177 foreach ( array( 'add_field_name', 'add_field_label', 'add_field_type', 'add_field_order', 'add_dropdown_order' ) as $field_key ) {
178 if ( ! isset( $custom_fields[ $field_key ] ) || ! is_array( $custom_fields[ $field_key ] ) ) {
179 $custom_fields[ $field_key ] = array();
180 }
181 }
182
183 // Load the plugin's field-selection options for this prefix.
184 $options = mlsimport_normalized_field_configuration();
185 if ( ! is_array( $options ) ) {
186 $options = array();
187 }
188 // Extract the sub-maps that decide import/admin/taxonomy/order per field.
189 $mls_fields = isset( $options['mls-fields'] ) && is_array( $options['mls-fields'] ) ? $options['mls-fields'] : array();
190 $mls_fields_admin = isset( $options['mls-fields-admin'] ) && is_array( $options['mls-fields-admin'] ) ? $options['mls-fields-admin'] : array();
191 $mls_fields_tax = isset( $options['mls-fields-map-taxonomy'] ) && is_array( $options['mls-fields-map-taxonomy'] ) ? $options['mls-fields-map-taxonomy'] : array();
192 $field_order = isset( $options['field_order'] ) && is_array( $options['field_order'] ) ? $options['field_order'] : array();
193 $active_fields = array_fill_keys( array_keys( mlsimport_field_configuration_metadata() ), true );
194
195 // Reconcile each MLS field against the theme's custom-fields registry.
196 foreach ( $mls_fields as $key => $value ) {
197 // Per-field flags: enabled, admin-managed, taxonomy target, order.
198 $import = intval( $value );
199 $admin = isset( $mls_fields_admin[ $key ] ) ? intval( $mls_fields_admin[ $key ] ) : 0;
200 $taxonomy = isset( $mls_fields_tax[ $key ] ) ? $mls_fields_tax[ $key ] : '';
201 $order_value = isset( $field_order[ $key ] ) ? intval( $field_order[ $key ] ) + 100 : 100;
202
203 // Add/update only enabled, non-admin, non-taxonomy fields.
204 if ( isset( $active_fields[ $key ] ) && 1 === $import && 0 === $admin && '' === $taxonomy ) {
205 // Is this field already registered?
206 $existing_index = array_search( $key, $custom_fields['add_field_name'], true );
207
208 if ( false === $existing_index ) {
209 // New field: append across all parallel columns.
210 $custom_fields['add_field_name'][] = $key;
211 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
212 $custom_fields['add_field_label'][] = $label;
213 $custom_fields['add_field_type'][] = 'short text';
214 $custom_fields['add_field_order'][] = $order_value;
215 $custom_fields['add_dropdown_order'][] = '';
216 } else {
217 // Existing field: refresh its label and order in place.
218 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
219 $custom_fields['add_field_label'][ $existing_index ] = $label;
220 $custom_fields['add_field_order'][ $existing_index ] = $order_value;
221 }
222 } else {
223 // Not eligible: remove it from every parallel column if present.
224 $remove_index = array_search( $key, $custom_fields['add_field_name'], true );
225 if ( false !== $remove_index ) {
226 unset( $custom_fields['add_field_name'][ $remove_index ] );
227 unset( $custom_fields['add_field_label'][ $remove_index ] );
228 unset( $custom_fields['add_field_type'][ $remove_index ] );
229 unset( $custom_fields['add_field_order'][ $remove_index ] );
230 unset( $custom_fields['add_dropdown_order'][ $remove_index ] );
231 }
232 }
233 }
234
235 // Re-sort all columns by field order so the registry stays ordered.
236 if ( ! empty( $custom_fields['add_field_order'] ) ) {
237 // Sort the order column, preserving its keys as the new sequence.
238 asort( $custom_fields['add_field_order'] );
239 $ordered = array(
240 'add_field_name' => array(),
241 'add_field_label' => array(),
242 'add_field_type' => array(),
243 'add_field_order' => array(),
244 'add_dropdown_order' => array(),
245 );
246 // Rebuild each column following the sorted order of indices.
247 foreach ( array_keys( $custom_fields['add_field_order'] ) as $idx ) {
248 $ordered['add_field_name'][] = $custom_fields['add_field_name'][ $idx ];
249 $ordered['add_field_label'][] = $custom_fields['add_field_label'][ $idx ];
250 $ordered['add_field_type'][] = $custom_fields['add_field_type'][ $idx ];
251 $ordered['add_field_order'][] = $custom_fields['add_field_order'][ $idx ];
252 $ordered['add_dropdown_order'][] = $custom_fields['add_dropdown_order'][ $idx ];
253 }
254 $custom_fields = $ordered;
255 }
256
257 // Persist the updated registry back to the theme option.
258 $theme_options['wpestate_custom_fields_list'] = $custom_fields;
259 update_option( 'wpresidence_admin', $theme_options );
260
261 }
262
263
264
265
266
267
268 /**
269 * return theme schema
270 *
271 * No-op placeholder: Real Homes provides no field schema through this adapter.
272 *
273 * @since 1.0.0
274 * @access protected
275 * @var string $plugin_name
276 */
277 public function return_theme_schema() {
278 // No schema to return for this theme.
279 return;
280 }
281
282 /**
283 * Filter wp_prepare_attachment_for_js to inject remote image URL and fake sizes.
284 *
285 * This ensures Meta Box and the WordPress media library display thumbnails
286 * for images that are stored remotely (i.e., imported via MLS).
287 *
288 * @param array $response Attachment response data prepared for JS.
289 * @return array Modified response with remote image URL and sizes if applicable.
290 */
291 public function inject_remote_image_data( $response ) {
292 // Nothing to do without an attachment ID.
293 if ( empty( $response['id'] ) ) {
294 return $response;
295 }
296
297 $attachment_id = $response['id'];
298
299 // Only modify attachments imported by MLS
300 if ( intval( get_post_meta( $attachment_id, 'is_mlsimport', true ) ) === 1 ) {
301 // Bail if the attachment post cannot be loaded.
302 $attachment = get_post( $attachment_id );
303 if ( ! $attachment ) {
304 return $response;
305 }
306
307 // Determine the correct remote URL
308 // Prefer a valid URL in the guid, else fall back to the stored external URL.
309 $url = filter_var( $attachment->guid, FILTER_VALIDATE_URL )
310 ? $attachment->guid
311 : get_post_meta( $attachment_id, 'houzez_external_url', true );
312
313 // Inject the URL and fake thumbnail size into the response
314 if ( $url ) {
315 $response['url'] = $url;
316 $response['sizes'] = [
317 'thumbnail' => [
318 'url' => $url,
319 'width' => 150,
320 'height' => 150,
321 ],
322 ];
323
324 }
325 }
326
327 return $response;
328 }
329
330 /**
331 * Filter image_downsize to return remote image data instead of local file paths.
332 *
333 * This enables WordPress functions like wp_get_attachment_image() to work
334 * with remote images by returning a URL and fake dimensions.
335 *
336 * @param bool|array $out Whether to short-circuit the image downsize. Default false.
337 * @param int $id Attachment ID.
338 * @param string|array $size Requested image size.
339 * @return array|false Array of image data (URL, width, height, crop) or false to fall back.
340 */
341 public function override_image_downsize( $out, $id, $size ) {
342 // Only override for MLS-imported attachments
343 if ( intval( get_post_meta( $id, 'is_mlsimport', true ) ) !== 1 ) {
344 return false;
345 }
346
347 // Bail if the attachment post cannot be loaded.
348 $attachment = get_post( $id );
349 if ( ! $attachment ) {
350 return false;
351 }
352
353 // Determine the remote image URL
354 // Prefer a valid URL in the guid, else the stored external URL.
355 $url = filter_var( $attachment->guid, FILTER_VALIDATE_URL )
356 ? $attachment->guid
357 : get_post_meta( $id, 'houzez_external_url', true );
358
359 // No URL resolved -> let WordPress handle it normally.
360 if ( ! $url ) {
361 return false;
362 }
363
364
365
366 // Return URL with fake dimensions (used for thumbnail display)
367 return [ $url, 150, 150, false ];
368 }
369 }
370