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

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

660 lines 33.4 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 * Responsibilities:
11 * - Declare the theme's property/agent post types.
12 * - Persist featured image and gallery attachments in the theme's expected meta.
13 * - Map imported "extra" MLS fields to custom post meta, taxonomies, or the theme's
14 * additional-details list, honoring the admin field-selection options.
15 * - Set hardcoded default meta on newly inserted properties.
16 * - Sync selected MLS fields into the theme's custom-fields registry.
17 * - Serve remote (MLS-hosted) images to the media library and image_downsize so
18 * thumbnails display without a local file.
19 *
20 * @package MLSImport
21 */
22 // Abort if the file is accessed directly outside of WordPress.
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit; // Exit if accessed directly
25 }
26
27
28 /**
29 * Description of RealHomesClass
30 *
31 * @author mlsimport
32 */
33 class RealHomesClass {
34
35 /**
36 * Register the filters that make remote MLS images render like local attachments.
37 */
38 public function __construct() {
39 // Enable support for remote MLS images in media JS and thumbnails
40 // Inject remote URL/sizes into the media library's JS attachment payload.
41 add_filter( 'wp_prepare_attachment_for_js', [ $this, 'inject_remote_image_data' ], 20 );
42 // Short-circuit image_downsize so wp_get_attachment_image() returns the remote URL.
43 add_filter( 'image_downsize', [ $this, 'override_image_downsize' ], 10, 3 );
44 }
45
46
47 /**
48 * return custom post field
49 *
50 * The post type Real Homes uses to store property listings.
51 *
52 * @since 1.0.0
53 * @access protected
54 * @var string $plugin_name
55 * @return string The 'property' post type slug.
56 */
57 public function get_property_post_type() {
58 // Real Homes stores listings in the 'property' post type.
59 return 'property';
60 }
61
62
63
64 /**
65 * return custom post field
66 *
67 * The post type(s) Real Homes uses for agents/agencies.
68 *
69 * @since 1.0.0
70 * @access protected
71 * @var string $plugin_name
72 * @return array The 'agency' and 'agent' post-type slugs.
73 */
74 public function get_agent_post_type() {
75 // Real Homes represents contacts as both 'agency' and 'agent' post types.
76 return array('agency','agent');
77 }
78
79 /**
80 * image save
81 *
82 * Attach one gallery image to the property using the theme's gallery meta.
83 *
84 * @since 1.0.0
85 * @access protected
86 * @var string $plugin_name
87 * @param int $property_id The property post ID.
88 * @param int $attach_id The image attachment ID.
89 */
90 public function enviroment_image_save( $property_id, $attach_id ) {
91 // Real Homes reads gallery images from repeated REAL_HOMES_property_images meta rows.
92 add_post_meta( $property_id, 'REAL_HOMES_property_images', intval( $attach_id ) );
93 }
94
95
96 /**
97 * gallery save
98 *
99 * No-op for Real Homes: gallery images are saved one-by-one via
100 * enviroment_image_save() rather than as a single batch, so this hook does nothing.
101 *
102 * @since 1.0.0
103 * @access protected
104 * @var string $plugin_name
105 * @param int $property_id The property post ID.
106 * @param array $post_attachments Attachment IDs (unused).
107 */
108 public function enviroment_image_save_gallery( $property_id, $post_attachments ) {
109 // Intentionally does nothing for this theme.
110 return;
111 }
112
113 /**
114 * Format extra meta values into a safe string representation.
115 *
116 * Collapses arrays (and nested arrays) into a comma-separated string; scalars are
117 * trimmed and their comma spacing normalized.
118 *
119 * @param mixed $meta_value Raw meta value (scalar or array).
120 * @return string Comma-separated, normalized string form.
121 */
122 private function normalizeExtraMetaValue($meta_value) {
123 // Array values are flattened into a comma-separated list.
124 if (is_array($meta_value)) {
125 $normalized = array();
126
127 // Walk each element, JSON-encoding nested arrays and trimming scalars.
128 foreach ($meta_value as $value) {
129 if (is_array($value)) {
130 // Nested array: store its JSON encoding (wp_json_encode preferred).
131 $encoded = function_exists('wp_json_encode') ? wp_json_encode($value) : json_encode($value);
132
133 // Keep only a successful encoding.
134 if (false !== $encoded && null !== $encoded) {
135 $normalized[] = $encoded;
136 }
137 } else {
138 // Scalar: trim and skip empties.
139 $value = trim((string) $value);
140 if ('' !== $value) {
141 $normalized[] = $value;
142 }
143 }
144 }
145
146 // Nothing usable collected -> empty string.
147 if (empty($normalized)) {
148 return '';
149 }
150
151 // Join collected pieces with commas.
152 return implode(', ', $normalized);
153 }
154
155 // Scalar input: trim and normalize whitespace around commas.
156 return preg_replace('/\s*,\s*/', ', ', trim((string) $meta_value));
157 }
158
159 /**
160 * Format Rooms extra meta entries into a single string.
161 *
162 * Turns the RESO "Rooms" collection into a human-readable string like
163 * "Kitchen: Main, 10 x 12 ft | Bedroom: Upper" for storage in a single meta field.
164 *
165 * @param array $rooms Array of room detail arrays (RESO Rooms structure).
166 * @return string Pipe-separated room summaries, or '' when nothing usable.
167 */
168 private function formatRoomsExtraMeta($rooms) {
169 // Guard: nothing to format.
170 if (!is_array($rooms) || empty($rooms)) {
171 return '';
172 }
173
174 $formatted_rooms = array();
175
176 // Build a summary line for each room entry.
177 foreach ($rooms as $room_details) {
178 // Skip malformed (non-array) entries.
179 if (!is_array($room_details)) {
180 continue;
181 }
182
183 // Pull the RESO room fields, trimming each to a string.
184 $room_type = isset($room_details['RoomType']) ? trim((string) $room_details['RoomType']) : '';
185 $room_level = isset($room_details['RoomLevel']) ? trim((string) $room_details['RoomLevel']) : '';
186 $room_length = isset($room_details['RoomLength']) ? trim((string) $room_details['RoomLength']) : '';
187 $room_width = isset($room_details['RoomWidth']) ? trim((string) $room_details['RoomWidth']) : '';
188 $room_units = isset($room_details['RoomLengthWidthUnits']) ? trim((string) $room_details['RoomLengthWidthUnits']) : '';
189
190 // A room with no type is not worth listing.
191 if ($room_type === '') {
192 continue;
193 }
194
195 $details = array();
196 // Include the floor/level when present.
197 if ($room_level !== '') {
198 $details[] = $room_level;
199 }
200
201 // Compose the dimension string from length and/or width.
202 $dimension = '';
203 if ($room_length !== '' && $room_width !== '') {
204 $dimension = $room_length . ' x ' . $room_width;
205 } elseif ($room_length !== '') {
206 $dimension = $room_length;
207 } elseif ($room_width !== '') {
208 $dimension = $room_width;
209 }
210
211 // Append units to the dimension, or use units alone if no dimension.
212 if ($dimension !== '') {
213 if ($room_units !== '') {
214 $dimension .= ' ' . $room_units;
215 }
216 $details[] = $dimension;
217 } elseif ($room_units !== '') {
218 $details[] = $room_units;
219 }
220
221 // Start with the room type, then append its details after a colon.
222 $formatted_value = $room_type;
223 if (!empty($details)) {
224 $formatted_value .= ': ' . implode(', ', $details);
225 }
226
227 $formatted_rooms[] = $formatted_value;
228 }
229
230 // Join all room summaries with a pipe separator.
231 return implode(' | ', $formatted_rooms);
232 }
233
234 /**
235 * Deal with extra meta
236 *
237 * Persists the property's coordinate meta and iterates every selected RESO "extra"
238 * field, routing each to one of: the theme's REAL_HOMES_property_location meta, a
239 * mapped custom post meta key, a mapped taxonomy term, the theme's additional-details
240 * list, or a lowercased-label post meta — according to the admin field-selection
241 * options in 'mlsimport_admin_fields_select'.
242 *
243 * @param int $property_id The property post ID.
244 * @param array $property Parsed property payload (meta, extra_meta, etc.).
245 * @return array Log strings: 'property_history' (HTML) and 'extra_meta_log' (plain).
246 */
247 public function mlsimportSaasSetExtraMeta( $property_id, $property ) {
248 // Accumulators for the two log formats returned to the caller.
249 $property_history = '';
250 $extra_meta_log = '';
251 $answer = array();
252 $extra_fields = array();
253 // Admin's field-selection options drive which meta is imported and how.
254 $options = get_option( 'mlsimport_admin_fields_select' );
255 if ( ! is_array( $options ) ) {
256 $options = array();
257 }
258 // Map of field-name => enabled flag, and the desired display order.
259 $permited_meta = isset( $options['mls-fields'] ) && is_array( $options['mls-fields'] ) ? $options['mls-fields'] : array();
260 $field_order = isset( $options['field_order'] ) && is_array( $options['field_order'] ) ? $options['field_order'] : array();
261
262 // Store lat/long as a single "lat,long" string in the theme's location meta.
263 if ( isset( $property['meta']['property_longitude'] ) && isset( $property['meta']['property_latitude'] ) ) {
264 $savingx = $property['meta']['property_latitude'] . ',' . $property['meta']['property_longitude'];
265 update_post_meta( $property_id, 'REAL_HOMES_property_location', $savingx );
266 $property_history .= 'Update Coordinates Meta with ' . $savingx . '</br>';
267 $extra_meta_log .= 'Property with ID ' . $property_id . ' Update Coordinates Meta with ' . $savingx . PHP_EOL;
268 }
269
270 // Process each extra MLS field carried on the property payload.
271 if ( isset( $property['extra_meta'] ) && is_array( $property['extra_meta'] ) ) {
272 $meta_properties = $property['extra_meta'];
273
274 foreach ( $meta_properties as $meta_name => $meta_value ) {
275 // Skip fields not present in the selection map.
276 if ( ! isset( $permited_meta[ $meta_name ] ) ) {
277 continue;
278 // Skip fields explicitly disabled (flag === 0).
279 } elseif ( isset( $permited_meta[ $meta_name ] ) && intval( $permited_meta[ $meta_name ] ) === 0 ) {
280 continue;
281 }
282
283 // Special-case the RESO "Rooms" collection -> single formatted meta.
284 if ( 'Rooms' === $meta_name && is_array( $meta_value ) ) {
285 // Flatten the rooms collection and store it in the 'rooms' meta.
286 $formatted_rooms = $this->formatRoomsExtraMeta( $meta_value );
287 if ( '' !== $formatted_rooms ) {
288 update_post_meta( $property_id, 'rooms', $formatted_rooms );
289 $property_history .= 'Updated EXTRA Meta rooms</br>';
290 $extra_meta_log .= 'Property with ID ' . $property_id . ' Update EXTRA Meta rooms with value ' . $formatted_rooms . PHP_EOL;
291 }
292 continue;
293 }
294
295 // Normalize the value and resolve the display label for this field.
296 $meta_value = $this->normalizeExtraMetaValue( $meta_value );
297 $orignal_meta_name = $meta_name;
298 $feature_label = isset( $options['mls-fields-label'][ $meta_name ] ) && '' !== $options['mls-fields-label'][ $meta_name ] ? $options['mls-fields-label'][ $meta_name ] : $meta_name;
299
300 // Route 1: field mapped to an explicit custom post meta key.
301 if ( isset( $options['mls-fields-map-postmeta'][ $orignal_meta_name ] ) && '' !== $options['mls-fields-map-postmeta'][ $orignal_meta_name ] ) {
302 $new_post_meta_key = $options['mls-fields-map-postmeta'][ $orignal_meta_name ];
303 update_post_meta( $property_id, $new_post_meta_key, $meta_value );
304 $property_history .= 'Updated CUSTOM post meta ' . $new_post_meta_key . ' original ' . $meta_name . ' and value ' . $meta_value . '</br>';
305 // Route 2: field mapped to a taxonomy -> attach a labeled term.
306 } elseif ( isset( $options['mls-fields-map-taxonomy'][ $orignal_meta_name ] ) && '' !== $options['mls-fields-map-taxonomy'][ $orignal_meta_name ] ) {
307 $new_taxonomy = $options['mls-fields-map-taxonomy'][ $orignal_meta_name ];
308 $custom_label = $options['mls-fields-label'][ $orignal_meta_name ];
309 // Prefix the value with its custom label to form the term name.
310 $meta_value_with_label = array( trim( $custom_label . ' ' . $meta_value ) );
311
312 // Append (append=true) the term and refresh the term cache.
313 wp_set_object_terms( $property_id, $meta_value_with_label, $new_taxonomy, true );
314 clean_term_cache( $property_id, $new_taxonomy );
315
316 $property_history .= 'Updated CUSTOM TAX: ' . $new_taxonomy . '<-- original ' . $orignal_meta_name . '/' . $meta_name . '/' . $custom_label . ' and value ' . json_encode( $meta_value_with_label );
317 // Route 3: no explicit mapping -> additional-details list or plain meta.
318 } else {
319 // Only when the value is non-empty and the field is enabled.
320 if (
321 '' !== $meta_value &&
322 isset( $options['mls-fields'][ $meta_name ] ) &&
323 1 === intval( $options['mls-fields'][ $meta_name ] )
324 ) {
325 // admin flag 0 -> collect for the ordered additional-details list.
326 if (
327 isset( $options['mls-fields-admin'][ $meta_name ] ) &&
328 0 === intval( $options['mls-fields-admin'][ $meta_name ] )
329 ) {
330 // Resolve the sort order: direct key, else index lookup, else 9999.
331 if ( isset( $field_order[ $orignal_meta_name ] ) ) {
332 $order = intval( $field_order[ $orignal_meta_name ] );
333 } else {
334 $index = array_search( $orignal_meta_name, $field_order, true );
335 $order = ( false !== $index ) ? intval( $index ) : 9999;
336 }
337 // Buffer the field for later sorting/writing.
338 $extra_fields[] = array(
339 'label' => $feature_label,
340 'value' => $meta_value,
341 'order' => $order,
342 );
343 // admin flag 1 -> store directly as lowercased-label meta.
344 } elseif (
345 isset( $options['mls-fields-admin'][ $meta_name ] ) &&
346 1 === intval( $options['mls-fields-admin'][ $meta_name ] )
347 ) {
348 update_post_meta( $property_id, strtolower( $feature_label ), $meta_value );
349 }
350 }
351 }
352
353 // Record this field in both log formats.
354 $property_history .= 'Updated EXTRA Meta ' . $meta_name . ' with label ' . $feature_label . ' and value ' . $meta_value . '</br>';
355 $extra_meta_log .= 'Property with ID ' . $property_id . ' Update EXTRA Meta ' . $meta_name . ' with value ' . $meta_value . PHP_EOL;
356 }
357
358 // Sort buffered additional-detail fields by their resolved order.
359 usort(
360 $extra_fields,
361 function ( $a, $b ) {
362 $orderA = isset( $a['order'] ) ? intval( $a['order'] ) : 9999;
363 $orderB = isset( $b['order'] ) ? intval( $b['order'] ) : 9999;
364 return $orderA <=> $orderB;
365 }
366 );
367 // Flatten to a label => value map in sorted order.
368 $ordered_extra_fields = array();
369 foreach ( $extra_fields as $field ) {
370 $ordered_extra_fields[ $field['label'] ] = $field['value'];
371 }
372
373 // Write the theme's additional-details meta (two keys the theme reads).
374 update_post_meta( $property_id, 'REAL_HOMES_additional_details', $ordered_extra_fields );
375 update_post_meta( $property_id, 'REAL_HOMES_additional_details_list', $ordered_extra_fields );
376
377 // Return the logs to the caller.
378 $answer['property_history'] = $property_history;
379 $answer['extra_meta_log'] = $extra_meta_log;
380 }
381
382 return $answer;
383 }
384
385
386
387
388
389 /**
390 * set hardcode fields after updated
391 *
392 * On first insert of a property, seed the theme's default meta (featured flag,
393 * agent display, assigned agent, map flag, label color, size/lot-size units).
394 *
395 * @since 1.0.0
396 * @access protected
397 * @var string $plugin_name
398 * @param string $is_insert 'yes' when the property was newly inserted.
399 * @param int $property_id The property post ID.
400 * @param array $global_extra_fields Extra fields (unused here).
401 * @param mixed $new_agent Agent ID to assign to the property.
402 */
403 public function correlationUpdateAfter( $is_insert, $property_id, $global_extra_fields, $new_agent ) {
404 // Only run defaults for freshly inserted properties.
405 if ( 'yes' === $is_insert ) {
406 // Not featured by default.
407 update_post_meta( $property_id, 'REAL_HOMES_featured', 0 );
408 // Show the agent info box on the property page.
409 update_post_meta( $property_id, 'REAL_HOMES_agent_display_option', 'agent_info' );
410
411 $options_mls = get_option( 'mlsimport_admin_mls_sync' );
412 // update_post_meta($property_id, 'REAL_HOMES_agents', $options_mls['property_agent']);
413 // Assign the imported/default agent to the property.
414 update_post_meta( $property_id, 'REAL_HOMES_agents', $new_agent );
415
416 // Disable the per-property map by default.
417 update_post_meta( $property_id, 'REAL_HOMES_property_map', 0 );
418 // Default the listing label color.
419 update_post_meta( $property_id, 'inspiry_property_label_color', '#fb641c' );
420
421 // Default the floor-area unit to "Sq Ft" only if not already set.
422 $fave_property_size_prefix = get_post_meta( $property_id, 'REAL_HOMES_property_size_postfix', true );
423 if ( '' === $fave_property_size_prefix ) {
424 update_post_meta( $property_id, 'REAL_HOMES_property_size_postfix', 'Sq Ft' );
425 }
426
427 // Default the lot-size unit to "Sq Ft" only if not already set.
428 $fave_property_land_postfix = get_post_meta( $property_id, 'REAL_HOMES_property_lot_size_postfix', true );
429 if ( '' === $fave_property_land_postfix ) {
430 update_post_meta( $property_id, 'REAL_HOMES_property_lot_size_postfix', 'Sq Ft' );
431 }
432
433
434 }
435 }
436
437
438
439
440
441 /**
442 * save custom fields per environment
443 *
444 * Syncs the plugin's selected MLS fields into the theme's custom-fields registry
445 * (stored in the 'wpresidence_admin' option as 'wpestate_custom_fields_list').
446 * Enabled non-admin, non-taxonomy fields are added/updated; others are removed.
447 * Finally the list is re-sorted by field order.
448 *
449 * @since 1.0.0
450 * @access protected
451 * @var string $plugin_name
452 * @param string $option_name Option prefix used to read '{prefix}_admin_fields_select'.
453 */
454 public function enviroment_custom_fields( $option_name ) {
455 // Load the theme option that holds the custom-fields registry.
456 $theme_options = get_option( 'wpresidence_admin' );
457 $custom_fields = array();
458 if ( isset( $theme_options['wpestate_custom_fields_list'] ) ) {
459 $custom_fields = $theme_options['wpestate_custom_fields_list'];
460 }
461
462 // Ensure we have an array to work with.
463 if ( ! is_array( $custom_fields ) ) {
464 $custom_fields = array();
465 }
466
467 // Guarantee every parallel column exists as an array.
468 foreach ( array( 'add_field_name', 'add_field_label', 'add_field_type', 'add_field_order', 'add_dropdown_order' ) as $field_key ) {
469 if ( ! isset( $custom_fields[ $field_key ] ) || ! is_array( $custom_fields[ $field_key ] ) ) {
470 $custom_fields[ $field_key ] = array();
471 }
472 }
473
474 // Load the plugin's field-selection options for this prefix.
475 $options = get_option( $option_name . '_admin_fields_select' );
476 if ( ! is_array( $options ) ) {
477 $options = array();
478 }
479 // Extract the sub-maps that decide import/admin/taxonomy/order per field.
480 $mls_fields = isset( $options['mls-fields'] ) && is_array( $options['mls-fields'] ) ? $options['mls-fields'] : array();
481 $mls_fields_admin = isset( $options['mls-fields-admin'] ) && is_array( $options['mls-fields-admin'] ) ? $options['mls-fields-admin'] : array();
482 $mls_fields_tax = isset( $options['mls-fields-map-taxonomy'] ) && is_array( $options['mls-fields-map-taxonomy'] ) ? $options['mls-fields-map-taxonomy'] : array();
483 $field_order = isset( $options['field_order'] ) && is_array( $options['field_order'] ) ? $options['field_order'] : array();
484
485 // Reconcile each MLS field against the theme's custom-fields registry.
486 foreach ( $mls_fields as $key => $value ) {
487 // Per-field flags: enabled, admin-managed, taxonomy target, order.
488 $import = intval( $value );
489 $admin = isset( $mls_fields_admin[ $key ] ) ? intval( $mls_fields_admin[ $key ] ) : 0;
490 $taxonomy = isset( $mls_fields_tax[ $key ] ) ? $mls_fields_tax[ $key ] : '';
491 $order_value = isset( $field_order[ $key ] ) ? intval( $field_order[ $key ] ) + 100 : 100;
492
493 // Add/update only enabled, non-admin, non-taxonomy fields.
494 if ( 1 === $import && 0 === $admin && '' === $taxonomy ) {
495 // Is this field already registered?
496 $existing_index = array_search( $key, $custom_fields['add_field_name'], true );
497
498 if ( false === $existing_index ) {
499 // New field: append across all parallel columns.
500 $custom_fields['add_field_name'][] = $key;
501 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
502 $custom_fields['add_field_label'][] = $label;
503 $custom_fields['add_field_type'][] = 'short text';
504 $custom_fields['add_field_order'][] = $order_value;
505 $custom_fields['add_dropdown_order'][] = '';
506 } else {
507 // Existing field: refresh its label and order in place.
508 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
509 $custom_fields['add_field_label'][ $existing_index ] = $label;
510 $custom_fields['add_field_order'][ $existing_index ] = $order_value;
511 }
512 } else {
513 // Not eligible: remove it from every parallel column if present.
514 $remove_index = array_search( $key, $custom_fields['add_field_name'], true );
515 if ( false !== $remove_index ) {
516 unset( $custom_fields['add_field_name'][ $remove_index ] );
517 unset( $custom_fields['add_field_label'][ $remove_index ] );
518 unset( $custom_fields['add_field_type'][ $remove_index ] );
519 unset( $custom_fields['add_field_order'][ $remove_index ] );
520 unset( $custom_fields['add_dropdown_order'][ $remove_index ] );
521 }
522 }
523 }
524
525 // Re-sort all columns by field order so the registry stays ordered.
526 if ( ! empty( $custom_fields['add_field_order'] ) ) {
527 // Sort the order column, preserving its keys as the new sequence.
528 asort( $custom_fields['add_field_order'] );
529 $ordered = array(
530 'add_field_name' => array(),
531 'add_field_label' => array(),
532 'add_field_type' => array(),
533 'add_field_order' => array(),
534 'add_dropdown_order' => array(),
535 );
536 // Rebuild each column following the sorted order of indices.
537 foreach ( array_keys( $custom_fields['add_field_order'] ) as $idx ) {
538 $ordered['add_field_name'][] = $custom_fields['add_field_name'][ $idx ];
539 $ordered['add_field_label'][] = $custom_fields['add_field_label'][ $idx ];
540 $ordered['add_field_type'][] = $custom_fields['add_field_type'][ $idx ];
541 $ordered['add_field_order'][] = $custom_fields['add_field_order'][ $idx ];
542 $ordered['add_dropdown_order'][] = $custom_fields['add_dropdown_order'][ $idx ];
543 }
544 $custom_fields = $ordered;
545 }
546
547 // Persist the updated registry back to the theme option.
548 $theme_options['wpestate_custom_fields_list'] = $custom_fields;
549 update_option( 'wpresidence_admin', $theme_options );
550
551 }
552
553
554
555
556
557
558 /**
559 * return theme schema
560 *
561 * No-op placeholder: Real Homes provides no field schema through this adapter.
562 *
563 * @since 1.0.0
564 * @access protected
565 * @var string $plugin_name
566 */
567 public function return_theme_schema() {
568 // No schema to return for this theme.
569 return;
570 }
571
572 /**
573 * Filter wp_prepare_attachment_for_js to inject remote image URL and fake sizes.
574 *
575 * This ensures Meta Box and the WordPress media library display thumbnails
576 * for images that are stored remotely (i.e., imported via MLS).
577 *
578 * @param array $response Attachment response data prepared for JS.
579 * @return array Modified response with remote image URL and sizes if applicable.
580 */
581 public function inject_remote_image_data( $response ) {
582 // Nothing to do without an attachment ID.
583 if ( empty( $response['id'] ) ) {
584 return $response;
585 }
586
587 $attachment_id = $response['id'];
588
589 // Only modify attachments imported by MLS
590 if ( intval( get_post_meta( $attachment_id, 'is_mlsimport', true ) ) === 1 ) {
591 // Bail if the attachment post cannot be loaded.
592 $attachment = get_post( $attachment_id );
593 if ( ! $attachment ) {
594 return $response;
595 }
596
597 // Determine the correct remote URL
598 // Prefer a valid URL in the guid, else fall back to the stored external URL.
599 $url = filter_var( $attachment->guid, FILTER_VALIDATE_URL )
600 ? $attachment->guid
601 : get_post_meta( $attachment_id, 'houzez_external_url', true );
602
603 // Inject the URL and fake thumbnail size into the response
604 if ( $url ) {
605 $response['url'] = $url;
606 $response['sizes'] = [
607 'thumbnail' => [
608 'url' => $url,
609 'width' => 150,
610 'height' => 150,
611 ],
612 ];
613
614 }
615 }
616
617 return $response;
618 }
619
620 /**
621 * Filter image_downsize to return remote image data instead of local file paths.
622 *
623 * This enables WordPress functions like wp_get_attachment_image() to work
624 * with remote images by returning a URL and fake dimensions.
625 *
626 * @param bool|array $out Whether to short-circuit the image downsize. Default false.
627 * @param int $id Attachment ID.
628 * @param string|array $size Requested image size.
629 * @return array|false Array of image data (URL, width, height, crop) or false to fall back.
630 */
631 public function override_image_downsize( $out, $id, $size ) {
632 // Only override for MLS-imported attachments
633 if ( intval( get_post_meta( $id, 'is_mlsimport', true ) ) !== 1 ) {
634 return false;
635 }
636
637 // Bail if the attachment post cannot be loaded.
638 $attachment = get_post( $id );
639 if ( ! $attachment ) {
640 return false;
641 }
642
643 // Determine the remote image URL
644 // Prefer a valid URL in the guid, else the stored external URL.
645 $url = filter_var( $attachment->guid, FILTER_VALIDATE_URL )
646 ? $attachment->guid
647 : get_post_meta( $id, 'houzez_external_url', true );
648
649 // No URL resolved -> let WordPress handle it normally.
650 if ( ! $url ) {
651 return false;
652 }
653
654
655
656 // Return URL with fake dimensions (used for thumbnail display)
657 return [ $url, 150, 150, false ];
658 }
659 }
660