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 / includes / live / live-parse.php

live-parse.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.2, at includes/live/live-parse.php

120 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Direct MLS access: normalize a provider response into { records, total }.
4 *
5 * Pure functions — no WordPress, no network. Family 1 providers all answer
6 * with the standard OData envelope: @odata.count + value[]. Provider-family
7 * variants (Rapattoni, Realtor.ca) extend this file when they are ported.
8 *
9 * @package Mlsimport
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Parse a raw response body into records + total.
18 *
19 * @param string $body Raw HTTP response body.
20 * @param array $config Per-MLS config retained for call compatibility. Provider
21 * adapters normalize any family-specific record fields.
22 * @return array{records:array,total:int}|null Null when the body is not a
23 * usable listing payload (bad JSON or a provider error envelope) —
24 * callers treat null as "keep the warm cache".
25 */
26 function mlsimport_live_parse_response( string $body, array $config ): ?array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $config is the family-dispatch seam.
27 // Bad JSON or an error envelope: null tells callers to keep the warm cache.
28 $data = json_decode( $body, true );
29 if ( ! is_array( $data ) || isset( $data['error'] ) ) {
30 return null;
31 }
32
33 // Bridge native listings envelope: success/total/bundle. A single-listing
34 // fetch returns bundle as one object instead of a list.
35 if ( array_key_exists( 'success', $data ) || array_key_exists( 'bundle', $data ) ) {
36 // Reject an unsuccessful or malformed bundle.
37 if ( empty( $data['success'] ) || ! isset( $data['bundle'] ) || ! is_array( $data['bundle'] ) ) {
38 return null;
39 }
40 $bundle = $data['bundle'];
41 // A list-shaped bundle stays a list; a single object is wrapped into one.
42 $records = array_keys( $bundle ) === range( 0, count( $bundle ) - 1 ) || array() === $bundle
43 ? array_values( $bundle )
44 : array( $bundle );
45 // Use the envelope's total when given, else the returned record count.
46 $total = isset( $data['total'] ) && is_numeric( $data['total'] ) ? (int) $data['total'] : count( $records );
47
48 return array(
49 'records' => $records,
50 'total' => $total,
51 );
52 }
53
54 // Generic RESO OData requires a real value[] member. An unrelated JSON
55 // object is an invalid response, not a successful zero-listing result.
56 if ( ! array_key_exists( 'value', $data ) || ! is_array( $data['value'] ) ) {
57 return null;
58 }
59 $records = array_values( $data['value'] );
60 // @odata.count when present, otherwise the page's own record count.
61 $total = isset( $data['@odata.count'] ) && is_numeric( $data['@odata.count'] )
62 ? (int) $data['@odata.count']
63 : count( $records );
64
65 return array(
66 'records' => $records,
67 'total' => $total,
68 );
69 }
70
71 /**
72 * The SaaS /clusters answer as the map's { type: clusters } payload — the
73 * same shape stored mode emits, so the map JS needs nothing new. Anything
74 * off (failed, missing pieces, malformed rows) yields null and the caller
75 * keeps the v1 zoom-in state.
76 *
77 * @param mixed $answer Decoded /clusters response body.
78 * @return array|null
79 */
80 function mlsimport_live_clusters_payload( $answer ): ?array {
81 // Anything missing or malformed: null keeps the caller on the zoom-in state.
82 if ( ! is_array( $answer ) || empty( $answer['success'] ) || ! isset( $answer['total'] ) || ! is_array( $answer['clusters'] ?? null ) ) {
83 return null;
84 }
85
86 $clusters = array();
87 // Keep only well-formed cluster rows, coercing their types.
88 foreach ( $answer['clusters'] as $row ) {
89 if ( ! is_array( $row ) || ! isset( $row['lat'], $row['lng'], $row['count'] ) ) {
90 continue;
91 }
92 $clusters[] = array(
93 'lat' => (float) $row['lat'],
94 'lng' => (float) $row['lng'],
95 'count' => (int) $row['count'],
96 );
97 }
98
99 // The map JS's { type: clusters } shape: total + the cleaned bubbles.
100 $payload = array(
101 'type' => 'clusters',
102 'total' => (int) $answer['total'],
103 'clusters' => $clusters,
104 );
105
106 // The index's exact in-bbox bounds: lets an unfiltered map block fit
107 // itself from the same cached answer, no record fetch.
108 $bounds = $answer['bounds'] ?? null;
109 if ( is_array( $bounds ) && isset( $bounds['lat_min'], $bounds['lat_max'], $bounds['lng_min'], $bounds['lng_max'] ) ) {
110 $payload['bounds'] = array(
111 'lat_min' => (float) $bounds['lat_min'],
112 'lat_max' => (float) $bounds['lat_max'],
113 'lng_min' => (float) $bounds['lng_min'],
114 'lng_max' => (float) $bounds['lng_max'],
115 );
116 }
117
118 return $payload;
119 }
120