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 / EstateClass.php

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

491 lines 21.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Theme adapter: WpEstate.
4 *
5 * One of the per-theme adapters in enviroment/. Maps RESO-standard MLS data
6 * onto WpEstate's post type (estate_property), agent post type (estate_agent),
7 * post meta keys and its wp_estate_custom_fields / wp_estate_feature_list
8 * options. The importer calls these methods to persist extra meta, property
9 * features, hardcoded single-property display defaults, and to keep the theme's
10 * custom-field registry in sync with the selected MLS fields.
11 */
12 if ( ! defined( 'ABSPATH' ) ) {
13 // Block direct web access — only load when WordPress is bootstrapped.
14 exit; // Exit if accessed directly
15 }
16
17 /**
18 * Description of EstateClass
19 *
20 * @author mlsimport
21 */
22 class EstateClass {
23
24 /**
25 * No setup required — the adapter is stateless.
26 */
27 public function __construct() {
28 }
29
30
31 /**
32 * return custom post field
33 *
34 * @since 1.0.0
35 * @access protected
36 * @var string $plugin_name
37 */
38 public function get_property_post_type() {
39 // WpEstate's property CPT slug — imported listings are stored here.
40 return 'estate_property';
41 }
42
43
44
45 /**
46 * return custom post field
47 *
48 * @since 1.0.0
49 * @access protected
50 * @var string $plugin_name
51 */
52 public function get_agent_post_type() {
53 // WpEstate's agent CPT slug — imported agents are stored here.
54 return 'estate_agent';
55 }
56
57 /**
58 * image save
59 *
60 * @since 1.0.0
61 * @access protected
62 * @var string $plugin_name
63 */
64 public function enviroment_image_save( $property_id, $attach_id ) {
65 // No-op for WpEstate — single-image association is handled elsewhere.
66 return;
67 }
68
69 /**
70 * Format extra meta values into a safe string representation.
71 *
72 * Flattens an array meta value into a comma-joined string (JSON-encoding
73 * any nested arrays, dropping empty entries); scalars are trimmed and
74 * their comma spacing normalized.
75 *
76 * @param mixed $meta_value Raw RESO meta value (scalar or array).
77 * @return string Normalized single-line string.
78 */
79 private function normalizeExtraMetaValue($meta_value) {
80 // Array value: build a normalized list of string parts.
81 if (is_array($meta_value)) {
82 $normalized = array();
83
84 foreach ($meta_value as $value) {
85 // Nested array → JSON-encode it as one entry.
86 if (is_array($value)) {
87 $encoded = function_exists('wp_json_encode') ? wp_json_encode($value) : json_encode($value);
88
89 // Keep only a successful encode.
90 if (false !== $encoded && null !== $encoded) {
91 $normalized[] = $encoded;
92 }
93 } else {
94 // Scalar → trim and keep if non-empty.
95 $value = trim((string) $value);
96 if ('' !== $value) {
97 $normalized[] = $value;
98 }
99 }
100 }
101
102 // Nothing survived → empty string.
103 if (empty($normalized)) {
104 return '';
105 }
106
107 // Join the collected parts.
108 return implode(', ', $normalized);
109 }
110
111 // Scalar value: trim and normalize comma spacing to ", ".
112 return preg_replace('/\s*,\s*/', ', ', trim((string) $meta_value));
113 }
114
115 /**
116 * Format Rooms extra meta entries into a single string.
117 *
118 * Turns the RESO Rooms collection (each entry an array with RoomType,
119 * RoomLevel, RoomLength/Width and units) into a human-readable
120 * "Type: level, LxW units | Type: …" string. Entries without a RoomType
121 * are skipped.
122 *
123 * @param mixed $rooms RESO Rooms array (array of room detail arrays).
124 * @return string Pipe-separated room summary, or '' when empty.
125 */
126 private function formatRoomsExtraMeta($rooms) {
127 // Guard: nothing to format unless it's a non-empty array.
128 if (!is_array($rooms) || empty($rooms)) {
129 return '';
130 }
131
132 $formatted_rooms = array();
133
134 foreach ($rooms as $room_details) {
135 // Skip malformed (non-array) room entries.
136 if (!is_array($room_details)) {
137 continue;
138 }
139
140 // Pull each RESO room field, trimmed, defaulting to ''.
141 $room_type = isset($room_details['RoomType']) ? trim((string) $room_details['RoomType']) : '';
142 $room_level = isset($room_details['RoomLevel']) ? trim((string) $room_details['RoomLevel']) : '';
143 $room_length = isset($room_details['RoomLength']) ? trim((string) $room_details['RoomLength']) : '';
144 $room_width = isset($room_details['RoomWidth']) ? trim((string) $room_details['RoomWidth']) : '';
145 $room_units = isset($room_details['RoomLengthWidthUnits']) ? trim((string) $room_details['RoomLengthWidthUnits']) : '';
146
147 // RoomType is required — skip entries without one.
148 if ($room_type === '') {
149 continue;
150 }
151
152 // Collect optional detail parts (level, dimensions).
153 $details = array();
154 if ($room_level !== '') {
155 $details[] = $room_level;
156 }
157
158 // Build a dimension string from whichever of length/width is present.
159 $dimension = '';
160 if ($room_length !== '' && $room_width !== '') {
161 $dimension = $room_length . ' x ' . $room_width;
162 } elseif ($room_length !== '') {
163 $dimension = $room_length;
164 } elseif ($room_width !== '') {
165 $dimension = $room_width;
166 }
167
168 // Append units to the dimension, or keep units alone if no dimension.
169 if ($dimension !== '') {
170 if ($room_units !== '') {
171 $dimension .= ' ' . $room_units;
172 }
173 $details[] = $dimension;
174 } elseif ($room_units !== '') {
175 $details[] = $room_units;
176 }
177
178 // Compose "Type: detail, detail" for this room.
179 $formatted_value = $room_type;
180 if (!empty($details)) {
181 $formatted_value .= ': ' . implode(', ', $details);
182 }
183
184 $formatted_rooms[] = $formatted_value;
185 }
186
187 // Join all rooms with a pipe separator.
188 return implode(' | ', $formatted_rooms);
189 }
190
191 /**
192 * Deal with extra meta
193 *
194 * Persists the property's RESO extra_meta onto WpEstate post meta. Only
195 * fields enabled in mlsimport_admin_fields_select['mls-fields'] are saved.
196 * Rooms get the dedicated 'rooms' meta; every other field is stored under its
197 * lowercased RESO name, and additionally copied to a mapped custom post meta
198 * key when one is configured. Returns a running history/log the importer
199 * accumulates. Property features are appended by the helper below.
200 *
201 * @param int $property_id WpEstate property post ID.
202 * @param array $property SaaS property payload (expects 'extra_meta', 'meta').
203 * @return array { property_history, extra_meta_log } log strings.
204 */
205 public function mlsimportSaasSetExtraMeta( $property_id, $property ) {
206 // Accumulators for the human-facing history and the log file.
207 $property_history = '';
208 $extra_meta_log = '';
209 $answer = array();
210 // Selected-fields config: which MLS fields the user enabled and their mappings.
211 $options = get_option( 'mlsimport_admin_fields_select' );
212 $permited_meta = isset( $options['mls-fields'] ) ? $options['mls-fields'] : array();
213
214 // Only process when the payload carries an extra_meta array.
215 if ( isset( $property['extra_meta'] ) && is_array( $property['extra_meta'] ) ) {
216 $meta_properties = $property['extra_meta'];
217
218 foreach ( $meta_properties as $meta_name => $meta_value ) {
219 // Skip fields the user did not enable in field selection.
220 if ( ! isset( $permited_meta[ $meta_name ] ) || intval( $permited_meta[ $meta_name ] ) === 0 ) {
221 continue;
222 }
223
224 // Rooms are special-cased into a formatted 'rooms' meta.
225 if ( 'Rooms' === $meta_name && is_array( $meta_value ) ) {
226 $formatted_rooms = $this->formatRoomsExtraMeta( $meta_value );
227 if ( '' !== $formatted_rooms ) {
228 update_post_meta( $property_id, 'rooms', $formatted_rooms );
229 $property_history .= 'Updated EXTRA Meta rooms</br>';
230 $extra_meta_log .= 'Property with ID ' . $property_id . ' Updated EXTRA Meta rooms with value ' . $formatted_rooms . PHP_EOL;
231 }
232 continue;
233 }
234
235 // Normalize the value and store it under the lowercased RESO field name.
236 $normalized_value = $this->normalizeExtraMetaValue( $meta_value );
237 $meta_key = strtolower( $meta_name );
238
239 update_post_meta( $property_id, $meta_key, $normalized_value );
240
241 // If this field is mapped to a custom post meta key, copy the value there too.
242 if ( isset( $options['mls-fields-map-postmeta'][ $meta_key ] ) && $options['mls-fields-map-postmeta'][ $meta_key ] !== '' ) {
243 $new_post_meta_key = $options['mls-fields-map-postmeta'][ $meta_key ];
244 update_post_meta( $property_id, $new_post_meta_key, $normalized_value );
245 $property_history .= 'Updated CUSTOM post meta ' . $new_post_meta_key . ' original ' . $meta_key . ' and value ' . $normalized_value . '</br>';
246 }
247
248 // Record what was saved.
249 $property_history .= 'Updated EXTRA Meta ' . $meta_key . ' with meta_value ' . $normalized_value . '</br>';
250 $extra_meta_log .= 'Property with ID ' . $property_id . ' Updated EXTRA Meta ' . $meta_key . ' with value ' . $normalized_value . PHP_EOL;
251 }
252
253 // Seed the answer with what this pass logged.
254 $answer['property_history'] = $property_history;
255 $answer['extra_meta_log'] = $extra_meta_log;
256 }
257
258 // Append property-feature handling to the same answer.
259 $answer = $this->mlsimport_saas_set_extra_meta_features( $property_id, $property, $answer );
260
261 return $answer;
262 }
263
264 /**
265 * Store property features as boolean post meta and grow the theme's feature list.
266 *
267 * Iterates $property['meta']['property_features'] (each value a feature name or
268 * an array of them), writes a sanitized meta key = 1 per feature, and appends
269 * any newly seen feature to the wp_estate_feature_list option so WpEstate knows
270 * about it. Appends its own history/log strings onto the passed $answer.
271 *
272 * @param int $property_id WpEstate property post ID.
273 * @param array $property SaaS property payload (expects meta.property_features).
274 * @param array $answer Running { property_history, extra_meta_log } to extend.
275 * @return array The extended $answer.
276 */
277 public function mlsimport_saas_set_extra_meta_features( $property_id, $property, $answer ) {
278 // Per-call history/log accumulators.
279 $property_history = '';
280 $extra_meta_log = '';
281
282 // Current comma-separated theme feature list, and the target post ID.
283 $feature_list = esc_html( get_option( 'wp_estate_feature_list' ) );
284 $post_id = $property_id;
285
286 // Only run when the payload has a property_features array.
287 if ( isset( $property['meta']['property_features'] ) && is_array( $property['meta']['property_features'] ) ) :
288 foreach ( $property['meta']['property_features'] as $key => $feature_name ) :
289 // A feature entry may itself be an array of feature names.
290 if ( is_array( $feature_name ) ) {
291 foreach ( $feature_name as $key => $feature_name_from_arr ) {
292 // Derive a sanitized meta key from the feature name ($to_insert is unset here).
293 if ( '' === $to_insert ) {
294 $post_var_name = str_replace( ' ', '_', trim( $feature_name_from_arr ) );
295 $input_name = sanitize_title( $post_var_name );
296 $input_name = sanitize_key( $input_name );
297 } else {
298 $input_name = $to_insert;
299 $feature_name = $to_insert;
300 }
301
302 // Flag the feature on the property.
303 update_post_meta( $post_id, $input_name, 1 );
304 $property_history .= 'Updated Featured ' . $input_name . ' with yes</br>';
305 $extra_meta_log .= 'Property with ID ' . $property_id . ' pdated Featured ' . $input_name . ' with yes' . PHP_EOL;
306
307 // Register the feature on the theme list if it isn't already present.
308 if ( false === strpos( $feature_list, $feature_name_from_arr ) && '' !== $feature_name ) {
309 $feature_list .= ',' . $feature_name_from_arr;
310 update_option( 'wp_estate_feature_list', $feature_list );
311 }
312 }
313 } else {
314 // Scalar feature name → same sanitize-to-meta-key derivation.
315 if ( '' === $to_insert ) {
316 $post_var_name = str_replace( ' ', '_', trim( $feature_name ) );
317 $input_name = sanitize_title( $post_var_name );
318 $input_name = sanitize_key( $input_name );
319 } else {
320 $post_var_name = str_replace( ' ', '_', trim( $to_insert ) );
321 $input_name = sanitize_title( $post_var_name );
322 $input_name = sanitize_key( $input_name );
323
324 $feature_name = $to_insert;
325 }
326
327 // Flag the feature on the property.
328 update_post_meta( $post_id, $input_name, 1 );
329 $property_history .= 'Updated Featured ' . $input_name . ' with yes</br>';
330 $extra_meta_log .= 'Property with ID ' . $property_id . ' pdated Featured ' . $input_name . ' with yes' . PHP_EOL;
331
332 // Register the feature on the theme list if it isn't already present.
333 if ( false === strpos( $feature_list, $feature_name ) && '' !== $feature_name ) {
334 $feature_list .= ',' . $feature_name;
335 update_option( 'wp_estate_feature_list', $feature_list );
336 }
337 }
338 endforeach;
339 endif;
340
341 // Concatenate this pass's logs onto whatever the caller already collected.
342 $answer['property_history'] = $answer['property_history'] . $property_history;
343 $answer['extra_meta_log'] = $answer['extra_meta_log'] . $extra_meta_log;
344
345 return $answer;
346 }
347
348
349
350
351
352 /**
353 * set hardcode fields after updated
354 *
355 * @since 1.0.0
356 * @access protected
357 * @var string $plugin_name
358 */
359 public function correlationUpdateAfter( $is_insert, $property_id, $global_extra_fields ) {
360 // Only run on a fresh insert, not on updates of existing listings.
361 if ( 'yes' === $is_insert ) {
362 // Seed WpEstate single-property display defaults (sliders, content, zoom, etc.).
363 update_post_meta( $property_id, 'local_pgpr_slider_type', 'global' );
364 update_post_meta( $property_id, 'local_pgpr_content_type', 'global' );
365 update_post_meta( $property_id, 'prop_featured', 0 );
366 update_post_meta( $property_id, 'page_custom_zoom', 16 );
367 // Assign the configured default agent from the MLS sync settings.
368 $options_mls = get_option( 'mlsimport_admin_mls_sync' );
369 update_post_meta( $property_id, 'property_agent', $options_mls['property_agent'] );
370
371 update_post_meta( $property_id, 'property_page_desing_local', '' );
372 update_post_meta( $property_id, 'header_transparent', 'global' );
373 update_post_meta( $property_id, 'page_show_adv_search', 'global' );
374 update_post_meta( $property_id, 'page_show_adv_search', 'global' );
375 update_post_meta( $property_id, 'header_type', 0 );
376 update_post_meta( $property_id, 'sidebar_agent_option', 'global' );
377 update_post_meta( $property_id, 'local_pgpr_slider_type', 'global' );
378 update_post_meta( $property_id, 'local_pgpr_content_type', 'global' );
379 update_post_meta( $property_id, 'sidebar_select', 'global' );
380 update_post_meta( $property_id, 'sidebar_option', 'global' );
381 // Let the theme recompute its hidden-address flag for this listing.
382 if ( function_exists( 'wpestate_update_hiddent_address_single' ) ) {
383 wpestate_update_hiddent_address_single( $property_id );
384 }
385 }
386 }
387
388
389
390 /**
391 * Gallery save hook — no-op for WpEstate (gallery handled elsewhere).
392 *
393 * @param int $property_id WpEstate property post ID.
394 * @param array $post_attachments Attachment IDs for the listing gallery.
395 */
396 public function enviroment_image_save_gallery( $property_id, $post_attachments ) {
397
398
399 }
400
401
402 /**
403 * save custom fields per environment
404 *
405 * @since 1.0.0
406 * @access protected
407 * @var string $plugin_name
408 */
409 public function enviroment_custom_fields( $option_name ) {
410
411 // Existing WpEstate custom-field definitions (each a numeric array row).
412 $custom_fields = get_option( 'wp_estate_custom_fields', true );
413
414 // Ensure we always work with an array.
415 if ( ! is_array( $custom_fields ) ) {
416 $custom_fields = array();
417 }
418 // Starting order index for newly added custom fields.
419 $custom_field_no = 100;
420 $options = get_option( $option_name . '_admin_fields_select' );
421
422 foreach ( $options['mls-fields'] as $key => $value ) {
423 // Field is enabled AND not flagged as admin-only → it becomes a theme custom field.
424 if ( 1 === intval($value) && 0 === intval( $options['mls-fields-admin'][ $key ]) ) {
425 // Not already registered (and non-empty key) → add a new custom-field row.
426 if ( ! in_array( $key, array_column( $custom_fields, 0 ) ) && '' !== $key ) {
427 ++$custom_field_no;
428 $temp_array = array();
429 $temp_array[0] = $key;
430 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
431 $temp_array[1] = $label;
432
433 $temp_array[2] = 'short text';
434 $temp_array[3] = $custom_field_no;
435 $custom_fields[] = $temp_array;
436 } else {
437 // Already registered → just refresh its label (index 1).
438 $to_replace_key = array_search( $key, array_column( $custom_fields, 0 ) );
439 $label = isset( $options['mls-fields-label'][ $key ] ) && '' !== $options['mls-fields-label'][ $key ] ? $options['mls-fields-label'][ $key ] : $key;
440 $custom_fields[ $to_replace_key ]['1'] = $label;
441 }
442 } else {
443 // remove item from custom fields
444 $key_remove = $this->searchForId( $key, $custom_fields );
445
446 // Drop the row when a matching definition exists.
447 if ( intval( $key_remove ) > 0 ) {
448 unset( $custom_fields[ $key_remove ] );
449 }
450 }
451 }
452
453 // Persist the rebuilt custom-field list back to the theme option.
454 update_option( 'wp_estate_custom_fields', $custom_fields );
455 }
456
457
458 /**
459 * Find the array index of a custom-field row whose first element equals $id.
460 *
461 * @param string $id The field key to look for (matched against $val[0]).
462 * @param array $array Rows of custom-field definitions.
463 * @return int|string|null Matching key, or null when not found.
464 */
465 public function searchForId( $id, $array ) {
466 // Linear scan comparing each row's key column (index 0).
467 foreach ( $array as $key => $val ) {
468 if ( $val[0] === $id ) {
469 return $key;
470 }
471 }
472 // No match.
473 return null;
474 }
475
476
477
478 /**
479 * return theme schema
480 *
481 * @since 1.0.0
482 * @access protected
483 * @var string $plugin_name
484 */
485 public function return_theme_schema() {
486
487 // No schema exposed for WpEstate — returns nothing.
488 return;
489 }
490 }
491