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.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
← All changes | enviroment/ResidenceClass.php +103 -314 6.3.57.2 View file →
@@ -1,5 +1,17 @@
1 -<?php
1 +<?php
2 +/**
3 + * ResidenceClass — theme adapter for the WpResidence (WpEstate family) WordPress theme.
4 + *
5 + * One of the plugin's theme adapters (enviroment/ directory). Maps RESO-standard MLS
6 + * property data onto WpResidence's post type ('estate_property') and post-meta
7 + * keys (property_*, wpestate_*). Stored Listing Write owns shared selection and
8 + * persistence; this adapter keeps only WpResidence gallery representation,
9 + * theme field projection, creation defaults, and custom-field registry support.
10 + *
11 + * @package MLSImport
12 + */
13 +// Abort if the file is accessed directly outside of WordPress.
2 14 if ( ! defined( 'ABSPATH' ) ) {
3 15 exit; // Exit if accessed directly
4 16 }
5 17
@@ -4,14 +16,17 @@
4 16 }
5 17
6 18
7 19 /**
8 - * Description of ResidenceClass
20 + * Persist genuine WpResidence variation behind the Stored adapter seam.
9 21 *
10 22 * @author mlsimport
11 23 */
12 24 class ResidenceClass {
13 25
26 + /**
27 + * Constructor — no setup required for this adapter.
28 + */
14 29 public function __construct() {
15 30
16 31 }
17 32
@@ -18,361 +33,117 @@
18 33
19 34 /**
20 35 * return custom post field
21 36 *
37 + * The post type WpResidence uses to store property listings.
38 + *
22 39 * @since 1.0.0
23 40 * @access protected
24 41 * @var string $plugin_name
42 + * @return string The 'estate_property' post-type slug.
25 43 */
26 44 public function get_property_post_type() {
45 + // WpResidence stores listings in the 'estate_property' post type.
27 46 return 'estate_property';
28 47 }
29 -
30 48
31 -
32 49 /**
33 - * return custom post field
50 + * Return the WpResidence property post type to Stored Listing Write.
34 51 *
35 - * @since 1.0.0
36 - * @access protected
37 - * @var string $plugin_name
52 + * @return string Property post-type slug.
38 53 */
39 - public function get_agent_post_type() {
40 - return array('estate_agent','estate_agency','estate_developer');
54 + public function property_post_type(): string {
55 + return 'estate_property';
41 56 }
42 57
43 -
44 58 /**
45 - * image save
59 + * Persist WpResidence's one-array gallery representation.
46 60 *
47 - * @since 1.0.0
48 - * @access protected
49 - * @var string $plugin_name
61 + * @param int $property_id Managed Listing post ID.
62 + * @param array<int, int> $attachment_ids Final ordered attachment IDs.
63 + * @return bool Whether the gallery representation was written.
50 64 */
51 - public function enviroment_image_save( $property_id, $attach_id ) {
52 - return;
65 + public function write_gallery( int $property_id, array $attachment_ids ): bool {
66 + update_post_meta( $property_id, 'wpestate_property_gallery', $attachment_ids );
67 + return true;
53 68 }
54 69
55 -
56 -
57 70 /**
58 - * image save
71 + * Persist only WpResidence-specific fields, defaults, and agent linkage.
59 72 *
60 - * @since 1.0.0
61 - * @access protected
62 - * @var string $plugin_name
73 + * Shared selection, normalization, mappings, Rooms, and order have already
74 + * been resolved by Stored Listing Write. Existing listings skip the defaults
75 + * and Assigned Agent so later task changes cannot reassign them.
76 + *
77 + * @param int $property_id Managed Listing post ID.
78 + * @param array<string, mixed> $property Raw incoming property.
79 + * @param array<string, mixed> $context Prepared fields and creation choices.
80 + * @return bool Whether the theme projection completed.
63 81 */
64 - public function enviroment_image_save_gallery( $property_id, $post_attachments ) {
65 - $gallery_meta = implode(',', $post_attachments);
66 - update_post_meta($property_id, 'wpestate_property_gallery', $post_attachments);
82 + public function write_theme_projection( int $property_id, array $property, array $context ): bool {
83 + foreach ( (array) ( $context['fields'] ?? array() ) as $field ) {
84 + // GitHub issue #286: 'listingkey' is the same case-insensitive meta row
85 + // as the listing identity, and a WPResidence custom field the edit
86 + // screen saves blank. The identity is never projected as theme meta.
87 + if ( 'listingkey' === strtolower( (string) $field['field'] ) ) {
88 + continue;
89 + }
90 + update_post_meta( $property_id, strtolower( (string) $field['field'] ), (string) $field['value'] );
91 + }
92 + if ( empty( $context['is_new'] ) ) {
93 + return true;
94 + }
67 95
96 + update_post_meta( $property_id, 'prop_featured', 0 );
97 + update_post_meta( $property_id, 'page_custom_zoom', 16 );
98 + // Country comes from the feed itself (Centris sends "CA"); the helper
99 + // falls back to the historical "United States" default when absent.
100 + update_post_meta( $property_id, 'property_country', mlsimport_property_country( $property ) );
101 + update_post_meta( $property_id, 'property_agent', (int) ( $context['assigned_agent_id'] ?? 0 ) );
102 + update_post_meta( $property_id, 'property_page_desing_local', '' );
103 + foreach ( array( 'header_transparent', 'page_show_adv_search', 'sidebar_agent_option', 'local_pgpr_slider_type', 'local_pgpr_content_type', 'sidebar_select', 'sidebar_option' ) as $key ) {
104 + update_post_meta( $property_id, $key, 'global' );
105 + }
106 + update_post_meta( $property_id, 'header_type', 0 );
107 + if ( function_exists( 'wpestate_update_hiddent_address_single' ) ) {
108 + wpestate_update_hiddent_address_single( $property_id );
109 + }
110 + return true;
68 111 }
69 112
70 113
71 114
72 -
73 -
74 - /**
75 - * Format extra meta values into a safe string representation.
76 - */
77 - private function normalizeExtraMetaValue($meta_value) {
78 - if (is_array($meta_value)) {
79 - $normalized = array();
80 -
81 - foreach ($meta_value as $value) {
82 - if (is_array($value)) {
83 - if (function_exists('wp_json_encode')) {
84 - $encoded = wp_json_encode($value);
85 - } else {
86 - $encoded = json_encode($value);
87 - }
88 -
89 - if (false !== $encoded && null !== $encoded) {
90 - $normalized[] = $encoded;
91 - }
92 - } else {
93 - $value = trim((string) $value);
94 - if ('' !== $value) {
95 - $normalized[] = $value;
96 - }
97 - }
98 - }
99 -
100 - if (empty($normalized)) {
101 - return '';
102 - }
103 -
104 - return implode(', ', $normalized);
105 - }
106 -
107 - return preg_replace('/\s*,\s*/', ', ', trim((string) $meta_value));
108 - }
109 -
110 - /**
111 - * Format Rooms extra meta entries into a single string.
112 - */
113 - private function formatRoomsExtraMeta($rooms, $property_id) {
114 - if (!is_array($rooms) || empty($rooms)) {
115 - return '';
116 - }
117 -
118 -
119 - $formatted_rooms = array();
120 -
121 - foreach ($rooms as $index => $room_details) {
122 - if (!is_array($room_details)) {
123 - continue;
124 - }
125 -
126 - $room_type = isset($room_details['RoomType']) ? trim((string) $room_details['RoomType']) : '';
127 - $room_level = isset($room_details['RoomLevel']) ? trim((string) $room_details['RoomLevel']) : '';
128 - $room_length = isset($room_details['RoomLength']) ? trim((string) $room_details['RoomLength']) : '';
129 - $room_width = isset($room_details['RoomWidth']) ? trim((string) $room_details['RoomWidth']) : '';
130 - $room_units = isset($room_details['RoomLengthWidthUnits']) ? trim((string) $room_details['RoomLengthWidthUnits']) : '';
131 -
132 - if ($room_type === '') {
133 - continue;
134 - }
135 -
136 - $details = array();
137 - if ($room_level !== '') {
138 - $details[] = $room_level;
139 - }
140 -
141 - $dimension = '';
142 - if ($room_length !== '' && $room_width !== '') {
143 - $dimension = $room_length . ' x ' . $room_width;
144 - } elseif ($room_length !== '') {
145 - $dimension = $room_length;
146 - } elseif ($room_width !== '') {
147 - $dimension = $room_width;
148 - }
149 -
150 - if ($dimension !== '') {
151 - if ($room_units !== '') {
152 - $dimension .= ' ' . $room_units;
153 - }
154 - $details[] = $dimension;
155 - } elseif ($room_units !== '') {
156 - $details[] = $room_units;
157 - }
158 -
159 - $formatted_value = $room_type;
160 - if (!empty($details)) {
161 - $formatted_value .= ': ' . implode(', ', $details);
162 - }
163 -
164 - $formatted_rooms[] = $formatted_value;
165 - }
166 -
167 - $result = implode(' | ', $formatted_rooms);
168 -
169 - return $result;
170 - }
171 -
172 -
173 - /**
174 - * Deal with extra meta
175 - */
176 -
177 -
178 - /**
179 - * Deal with extra meta
180 - */
181 - public function mlsimportSaasSetExtraMeta($property_id, $property) {
182 - // Memory tracking
183 - $startMemory = memory_get_usage(true);
184 - $startMemoryFormatted = round($startMemory / 1048576, 2);
185 -
186 - $property_history = array();
187 - $answer = array();
188 -
189 - if (!isset($property['extra_meta']) || !is_array($property['extra_meta'])) {
190 - $answer['property_history'] = '';
191 -
192 - $endMemory = memory_get_usage(true);
193 - $endMemoryFormatted = round($endMemory / 1048576, 2);
194 - $memoryDiff = $endMemoryFormatted - $startMemoryFormatted;
195 -
196 - return $answer;
197 - }
198 -
199 - // Get options once and extract only what we need
200 - $options = get_option('mlsimport_admin_fields_select');
201 - $permited_meta = isset($options['mls-fields']) ? $options['mls-fields'] : array();
202 -
203 - // Process meta in smaller batches
204 - $meta_batches = array_chunk(array_keys($property['extra_meta']), 10, true);
205 -
206 - $batch_count = 0;
207 - foreach ($meta_batches as $meta_batch_keys) {
208 - $batch_count++;
209 -
210 - $batchMemory = memory_get_usage(true);
211 - $batchMemoryFormatted = round($batchMemory / 1048576, 2);
212 -
213 - foreach ($meta_batch_keys as $meta_name) {
214 - // Skip if meta doesn't exist in property extra_meta
215 - if (!isset($property['extra_meta'][$meta_name])) {
216 - continue;
217 - }
218 -
219 - $meta_value = $property['extra_meta'][$meta_name];
220 -
221 - // Check if extra meta is set to import
222 - if (!isset($permited_meta[$meta_name])) {
223 - // We do not have the extra meta
224 - continue;
225 - } elseif (isset($permited_meta[$meta_name]) && intval($permited_meta[$meta_name]) === 0) {
226 - // Meta exists but is set to no
227 - continue;
228 - }
229 -
230 - if ('Rooms' === $meta_name && is_array($meta_value)) {
231 - $formatted_rooms = $this->formatRoomsExtraMeta($meta_value, $property_id);
232 - if ($formatted_rooms !== '') {
233 - update_post_meta($property_id, 'rooms', $formatted_rooms);
234 - $property_history[] = 'Updated EXTRA Meta rooms';
235 - }
236 - continue;
237 - }
238 -
239 - $normalized_meta_value = $this->normalizeExtraMetaValue($meta_value);
240 -
241 - $original_meta_name = $meta_name;
242 - $meta_name_lower = strtolower($meta_name);
243 -
244 - // Process custom postmeta mapping
245 - if (isset($options['mls-fields-map-postmeta'][$original_meta_name]) && $options['mls-fields-map-postmeta'][$original_meta_name] !== '') {
246 - $new_post_meta_key = $options['mls-fields-map-postmeta'][$original_meta_name];
247 -
248 - update_post_meta($property_id, $new_post_meta_key, $normalized_meta_value);
249 - $property_history[] = 'Updated CUSTOM post meta ' . $new_post_meta_key . ' original ' . $meta_name;
250 - }
251 - // Process custom taxonomy mapping
252 - else if (isset($options['mls-fields-map-taxonomy'][$original_meta_name]) && $options['mls-fields-map-taxonomy'][$original_meta_name] !== '') {
253 - $new_taxonomy = $options['mls-fields-map-taxonomy'][$original_meta_name];
254 - $custom_label = isset($options['mls-fields-label'][$original_meta_name]) ? $options['mls-fields-label'][$original_meta_name] : '';
255 -
256 - if ($custom_label == 'none') {
257 - $custom_label = '';
258 - }
259 -
260 - $meta_value_with_label = array();
261 - $term_value = trim($custom_label . ' ' . $normalized_meta_value);
262 - if ($term_value !== '') {
263 - $meta_value_with_label[] = $term_value;
264 - }
265 -
266 - if (!empty($meta_value_with_label)) {
267 - wp_set_object_terms($property_id, $meta_value_with_label, $new_taxonomy, true);
268 -
269 - // Fix the clean_term_cache call to avoid SQL errors
270 - $term_ids = array();
271 - foreach ($meta_value_with_label as $term_name) {
272 - $term = get_term_by('name', $term_name, $new_taxonomy);
273 - if ($term && !is_wp_error($term)) {
274 - $term_ids[] = $term->term_id;
275 - }
276 - }
277 -
278 - if (!empty($term_ids)) {
279 - clean_term_cache($term_ids, $new_taxonomy);
280 - }
281 - }
282 -
283 - $property_history[] = 'Updated CUSTOM TAX: ' . $new_taxonomy . ' original ' . $original_meta_name;
284 - }
285 - // Standard meta update
286 - else {
287 - update_post_meta($property_id, $meta_name_lower, $normalized_meta_value);
288 - $property_history[] = 'Updated EXTRA Meta ' . $meta_name_lower;
289 - }
290 -
291 - // Clear each variable after use
292 - $meta_value = null;
293 - $normalized_meta_value = null;
294 - }
295 -
296 - // Clean up after each batch
297 - wp_cache_flush();
298 - gc_collect_cycles();
299 -
300 - $afterBatchMemory = memory_get_usage(true);
301 - $afterBatchMemoryFormatted = round($afterBatchMemory / 1048576, 2);
302 - $batchMemoryDiff = $afterBatchMemoryFormatted - $batchMemoryFormatted;
303 - }
304 -
305 - // Remove unused code that was referencing undefined variables
306 - // (The processed_field_values code wasn't being used)
307 -
308 - // Prepare the answer with limited history to conserve memory
309 - if (count($property_history) > 20) {
310 - $property_history = array_slice($property_history, -20);
311 - $property_history[] = '... [truncated history to save memory] ...';
312 - }
313 -
314 - $answer['property_history'] = implode('</br>', $property_history);
315 -
316 - // Clean up variables
317 - $property_history = null;
318 - $options = null;
319 - $permited_meta = null;
320 -
321 - // Final memory tracking
322 - $endMemory = memory_get_usage(true);
323 - $endMemoryFormatted = round($endMemory / 1048576, 2);
324 - $memoryDiff = $endMemoryFormatted - $startMemoryFormatted;
325 -
326 - return $answer;
327 - }
328 -
329 -
330 -
331 -
332 -
333 115 /**
334 - * set hardcode fields after updated
116 + * return custom post field
335 117 *
118 + * The post type(s) WpResidence uses for agents, agencies, and developers.
119 + *
336 120 * @since 1.0.0
337 121 * @access protected
338 122 * @var string $plugin_name
123 + * @return array The agent/agency/developer post-type slugs.
339 124 */
340 - public function correlationUpdateAfter( $is_insert, $property_id, $global_extra_fields, $new_agent ) {
341 - if ( 'yes' === $is_insert ) {
342 - update_post_meta( $property_id, 'prop_featured', 0 );
343 - update_post_meta( $property_id, 'page_custom_zoom', 16 );
344 - update_post_meta( $property_id, 'property_country', 'United States' );
345 -
346 - update_post_meta( $property_id, 'property_agent', $new_agent );
347 - update_post_meta( $property_id, 'property_page_desing_local', '' );
348 - update_post_meta( $property_id, 'header_transparent', 'global' );
349 - update_post_meta( $property_id, 'page_show_adv_search', 'global' );
350 - update_post_meta( $property_id, 'page_show_adv_search', 'global' );
351 - update_post_meta( $property_id, 'header_type', 0 );
352 - update_post_meta( $property_id, 'sidebar_agent_option', 'global' );
353 - update_post_meta( $property_id, 'local_pgpr_slider_type', 'global' );
354 - update_post_meta( $property_id, 'local_pgpr_content_type', 'global' );
355 - update_post_meta( $property_id, 'sidebar_select', 'global' );
356 - update_post_meta( $property_id, 'sidebar_option', 'global' );
357 -
358 - if ( function_exists( 'wpestate_update_hiddent_address_single' ) ) {
359 - wpestate_update_hiddent_address_single( $property_id );
360 - }
361 - }
125 + public function get_agent_post_type() {
126 + // WpResidence splits contacts across three post types.
127 + return array('estate_agent','estate_agency','estate_developer');
362 128 }
363 129
364 130
365 131
366 -
367 132 /**
368 133 * save custom fields per environment
369 134 *
135 + * Syncs the plugin's selected MLS fields into WpResidence's custom-fields registry
136 + * (stored in 'wpresidence_admin' -> 'wpestate_custom_fields_list'). Enabled,
137 + * non-admin, non-taxonomy fields are added/updated; others removed; list re-sorted.
138 + *
370 139 * @since 1.0.0
371 140 * @access protected
372 141 * @var string $plugin_name
142 + * @param string $option_name Option prefix used to read '{prefix}_admin_fields_select'.
373 143 */
374 - public function enviroment_custom_fields( $option_name ) {
144 + public function enviroment_custom_fields( $option_name ) {
145 + // Load the theme option holding the custom-fields registry.
375 146 $theme_options = get_option( 'wpresidence_admin' );
376 147 $custom_fields = array();
377 148 if ( isset( $theme_options['wpestate_custom_fields_list'] ) ) {
378 149 $custom_fields = $theme_options['wpestate_custom_fields_list'];
@@ -377,12 +148,14 @@
377 148 if ( isset( $theme_options['wpestate_custom_fields_list'] ) ) {
378 149 $custom_fields = $theme_options['wpestate_custom_fields_list'];
379 150 }
380 151
152 + // Ensure we have an array to work with.
381 153 if ( ! is_array( $custom_fields ) ) {
382 154 $custom_fields = array();
383 155 }
384 156
157 + // Guarantee every parallel column exists as an array.
385 158 foreach ( array( 'add_field_name', 'add_field_label', 'add_field_type', 'add_field_order', 'add_dropdown_order' ) as $field_key ) {
386 159 if ( ! isset( $custom_fields[ $field_key ] ) || ! is_array( $custom_fields[ $field_key ] ) ) {
387 160 $custom_fields[ $field_key ] = array();
388 161 }
@@ -387,20 +160,27 @@
387 160 $custom_fields[ $field_key ] = array();
388 161 }
389 162 }
390 163
391 - $options = get_option( $option_name . '_admin_fields_select' );
164 + // Load the plugin's field-selection options for this prefix.
165 + $options = mlsimport_normalized_field_configuration();
166 + $active_fields = array_fill_keys( array_keys( mlsimport_field_configuration_metadata() ), true );
392 167
168 + // Reconcile each MLS field against the theme registry.
393 169 foreach ( $options['mls-fields'] as $key => $value ) {
170 + // Per-field flags: enabled, admin-managed, taxonomy target, order.
394 171 $import = intval( $value );
395 172 $admin = isset( $options['mls-fields-admin'][ $key ] ) ? intval( $options['mls-fields-admin'][ $key ] ) : 0;
396 173 $taxonomy = isset( $options['mls-fields-map-taxonomy'][ $key ] ) ? $options['mls-fields-map-taxonomy'][ $key ] : '';
397 174 $order_value = isset( $options['field_order'][ $key ] ) ? intval( $options['field_order'][ $key ] ) + 100 : 100;
398 175
399 - if ( 1 === $import && 0 === $admin && '' === $taxonomy ) {
176 + // Add/update only enabled, non-admin, non-taxonomy fields.
177 + if ( isset( $active_fields[ $key ] ) && 1 === $import && 0 === $admin && '' === $taxonomy ) {
178 + // Is this field already registered?
400 179 $existing_index = array_search( $key, $custom_fields['add_field_name'], true );
401 180
402 181 if ( false === $existing_index ) {
182 + // New field: append across all parallel columns.
403 183 $custom_fields['add_field_name'][] = $key;
404 184 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
405 185 $custom_fields['add_field_label'][] = $label;
406 186 $custom_fields['add_field_type'][] = 'short text';
@@ -406,13 +186,15 @@
406 186 $custom_fields['add_field_type'][] = 'short text';
407 187 $custom_fields['add_field_order'][] = $order_value;
408 188 $custom_fields['add_dropdown_order'][] = '';
409 189 } else {
190 + // Existing field: refresh its label and order in place.
410 191 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
411 192 $custom_fields['add_field_label'][ $existing_index ] = $label;
412 193 $custom_fields['add_field_order'][ $existing_index ] = $order_value;
413 194 }
414 195 } else {
196 + // Not eligible: remove it from every parallel column if present.
415 197 $remove_index = array_search( $key, $custom_fields['add_field_name'], true );
416 198 if ( false !== $remove_index ) {
417 199 unset( $custom_fields['add_field_name'][ $remove_index ] );
418 200 unset( $custom_fields['add_field_label'][ $remove_index ] );
@@ -422,9 +204,11 @@
422 204 }
423 205 }
424 206 }
425 207
208 + // Re-sort all columns by field order so the registry stays ordered.
426 209 if ( ! empty( $custom_fields['add_field_order'] ) ) {
210 + // Sort the order column, preserving keys as the new sequence.
427 211 asort( $custom_fields['add_field_order'] );
428 212 $ordered = array(
429 213 'add_field_name' => array(),
430 214 'add_field_label' => array(),
@@ -431,8 +215,9 @@
431 215 'add_field_type' => array(),
432 216 'add_field_order' => array(),
433 217 'add_dropdown_order' => array(),
434 218 );
219 + // Rebuild each column following the sorted order of indices.
435 220 foreach ( array_keys( $custom_fields['add_field_order'] ) as $idx ) {
436 221 $ordered['add_field_name'][] = $custom_fields['add_field_name'][ $idx ];
437 222 $ordered['add_field_label'][] = $custom_fields['add_field_label'][ $idx ];
438 223 $ordered['add_field_type'][] = $custom_fields['add_field_type'][ $idx ];
@@ -441,8 +226,9 @@
441 226 }
442 227 $custom_fields = $ordered;
443 228 }
444 229
230 + // Persist the updated registry back to the theme option.
445 231 $theme_options['wpestate_custom_fields_list'] = $custom_fields;
446 232 update_option( 'wpresidence_admin', $theme_options );
447 233
448 234 }
@@ -454,13 +240,16 @@
454 240
455 241 /**
456 242 * return theme schema
457 243 *
244 + * No-op placeholder: WpResidence provides no field schema through this adapter.
245 + *
458 246 * @since 1.0.0
459 247 * @access protected
460 248 * @var string $plugin_name
461 249 */
462 250 public function return_theme_schema() {
251 + // No schema to return for this theme.
463 252 return;
464 253 }
465 254
466 255