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-single.php

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

236 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Live mode: the virtual single-property page.
4 *
5 * Live listings have no posts, so they get a virtual route instead of a
6 * permalink: /{base}/{ListingKey}/ (base filterable, default "listing").
7 * The route resolves through template_include — fetch the record (cached),
8 * 404 when the MLS doesn't know the key, else render the loop-less live
9 * template. The property sections then read their view model through seam #2
10 * (mlsimport_property_data_pre), built here from the raw RESO record with the
11 * same helpers the stored VM uses.
12 *
13 * @package Mlsimport
14 */
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * The URL base segment for live single pages.
22 *
23 * @return string
24 */
25 function mlsimport_live_url_base(): string {
26 /** Filter the live single-page URL base segment. @since 6.4 */
27 $base = (string) apply_filters( 'mlsimport_live_url_base', 'listing' );
28 return '' !== $base ? $base : 'listing';
29 }
30
31 /**
32 * The virtual single-page URL for one live listing.
33 *
34 * @param string $listing_key RESO ListingKey.
35 * @return string
36 */
37 function mlsimport_live_url( string $listing_key ): string {
38 return home_url( '/' . mlsimport_live_url_base() . '/' . rawurlencode( $listing_key ) . '/' );
39 }
40
41 /**
42 * Register the live single route (rewrite + query var) while live mode is on.
43 * The settings screen flushes rewrite rules when the toggle changes, so the
44 * rule appears/disappears with the mode.
45 *
46 * @return void
47 */
48 function mlsimport_live_register_route(): void {
49 if ( ! mlsimport_live_mode_active() ) {
50 return;
51 }
52 add_rewrite_rule(
53 '^' . mlsimport_live_url_base() . '/([^/]+)/?$',
54 'index.php?mlsimport_live_listing=$matches[1]',
55 'top'
56 );
57 }
58
59 /**
60 * Expose the live listing query var.
61 *
62 * @param string[] $vars Public query vars.
63 * @return string[]
64 */
65 function mlsimport_live_query_vars( array $vars ): array {
66 $vars[] = 'mlsimport_live_listing';
67 return $vars;
68 }
69
70 /**
71 * The raw RESO record behind the page being rendered (set by the route,
72 * read by the VM seam). Request-scoped.
73 *
74 * @param array|null $set When given, becomes the current record.
75 * @return array|null
76 */
77 function mlsimport_live_current_record( ?array $set = null ): ?array {
78 static $record = null;
79 if ( null !== $set ) {
80 $record = $set;
81 }
82 return $record;
83 }
84
85 /**
86 * Resolve the virtual route: fetch the listing (cached) and serve the live
87 * single template, or 404 when the key is unknown/unreachable.
88 *
89 * @param string $template The template WP resolved.
90 * @return string
91 */
92 function mlsimport_live_template_include( $template ) {
93 $key = (string) get_query_var( 'mlsimport_live_listing' );
94 if ( '' === $key || ! mlsimport_live_mode_active() ) {
95 return $template;
96 }
97
98 $record = mlsimport_live_get( $key );
99 if ( ! is_array( $record ) ) {
100 global $wp_query;
101 $wp_query->set_404();
102 status_header( 404 );
103 return get_404_template();
104 }
105
106 mlsimport_live_current_record( $record );
107 return MLSIMPORT_PLUGIN_PATH . 'templates/live/single-mlsimport-property-live.php';
108 }
109
110 /**
111 * Seam #2 callback: answer mlsimport_property_data( 0 ) with the live VM while
112 * a live record is being rendered. Real post IDs keep the stored behavior.
113 *
114 * @param array|null $pre Short-circuit value (null = build normally).
115 * @param int $id The requested property post ID.
116 * @return array|null
117 */
118 function mlsimport_live_property_data_pre( $pre, int $id ) {
119 if ( null !== $pre || 0 !== $id ) {
120 return $pre;
121 }
122 $record = mlsimport_live_current_record();
123 if ( null === $record ) {
124 return $pre;
125 }
126
127 static $vm = null;
128 if ( null === $vm ) {
129 $vm = mlsimport_live_property_vm( $record );
130 }
131 return $vm;
132 }
133
134 /**
135 * Build the property view model from a raw RESO record — the same keys
136 * mlsimport_property_data() assembles from the post + flat row, built with
137 * the same helpers, so every section renders unchanged. id is 0 (no post);
138 * media is URL-based (gallery_urls / image_url from Media[]).
139 *
140 * @param array $record Raw RESO property record.
141 * @return array
142 */
143 function mlsimport_live_property_vm( array $record ): array {
144 $row = mlsimport_live_row_from_reso( $record );
145
146 // Same reader contracts the stored assembly uses: $meta by RESO field name
147 // (stored meta keys are mlsimport_<Field>, so names line up 1:1), $num/$str
148 // from the flat-row columns.
149 $meta = static function ( $key ) use ( $record ) {
150 return isset( $record[ $key ] ) && is_scalar( $record[ $key ] ) ? (string) $record[ $key ] : '';
151 };
152 $num = static function ( $col ) use ( $row ) {
153 return isset( $row->$col ) && null !== $row->$col && '' !== $row->$col ? (float) $row->$col : null;
154 };
155 $str = static function ( $col ) use ( $row ) {
156 return isset( $row->$col ) ? (string) $row->$col : '';
157 };
158
159 $gallery_urls = mlsimport_live_media_urls( $record );
160 $address = mlsimport_property_build_address( $meta, $str );
161
162 $vm = array(
163 'id' => 0,
164 'title' => $address,
165 'permalink' => mlsimport_live_url( (string) $row->listing_key ),
166 'content' => $meta( 'PublicRemarks' ),
167 'excerpt' => '',
168
169 // Price + variants.
170 'price' => $num( 'price' ),
171 'price_per_sqft' => ( null !== $num( 'price' ) && $num( 'living_area' ) ) ? (int) round( $num( 'price' ) / $num( 'living_area' ) ) : null,
172 'original_price' => '' !== $meta( 'OriginalListPrice' ) ? (float) $meta( 'OriginalListPrice' ) : null,
173 'close_price' => '' !== $meta( 'ClosePrice' ) ? (float) $meta( 'ClosePrice' ) : null,
174 'previous_price' => '' !== $meta( 'PreviousListPrice' ) ? (float) $meta( 'PreviousListPrice' ) : null,
175 'hoa_fee' => '' !== $meta( 'AssociationFee' ) ? (float) $meta( 'AssociationFee' ) : null,
176 'hoa_frequency' => $meta( 'AssociationFeeFrequency' ),
177
178 // Structure / facts.
179 'bedrooms' => $num( 'bedrooms' ),
180 'bathrooms' => $num( 'bathrooms' ),
181 'living_area' => $num( 'living_area' ),
182 'lot_size' => $num( 'lot_size' ),
183 'year_built' => null !== $num( 'year_built' ) ? (int) $num( 'year_built' ) : null,
184 'garage' => '' !== $meta( 'GarageSpaces' ) ? (int) (float) $meta( 'GarageSpaces' ) : null,
185 'stories' => '' !== $meta( 'StoriesTotal' ) ? (int) (float) $meta( 'StoriesTotal' ) : null,
186 'days_on_market' => null !== $num( 'days_on_market' ) ? (int) $num( 'days_on_market' ) : null,
187
188 // Location.
189 'street' => mlsimport_property_street_line( $meta ),
190 'city' => $str( 'city' ),
191 'state' => $str( 'state' ),
192 'zip' => $str( 'zip' ),
193 'subdivision' => $str( 'subdivision' ),
194 'county' => $meta( 'CountyOrParish' ),
195 'country' => 'US' === $meta( 'Country' ) ? __( 'United States', 'mlsimport' ) : $meta( 'Country' ),
196 'latitude' => '' !== $meta( 'Latitude' ) ? (float) $meta( 'Latitude' ) : null,
197 'longitude' => '' !== $meta( 'Longitude' ) ? (float) $meta( 'Longitude' ) : null,
198 'address' => $address,
199
200 // Type / status.
201 'property_type' => $str( 'property_type' ),
202 'property_sub_type' => $meta( 'PropertySubType' ),
203 'listing_type' => $str( 'listing_type' ),
204 'status' => '' !== $str( 'status' ) ? $str( 'status' ) : $meta( 'MlsStatus' ),
205
206 // Provenance / freshness (display-only).
207 'mls_id' => '' !== $meta( 'ListingId' ) ? $meta( 'ListingId' ) : $meta( 'ListingKey' ),
208 'updated' => mlsimport_property_format_date( $meta( 'ModificationTimestamp' ) ),
209
210 // Media: CDN URLs, no attachments.
211 'thumbnail_id' => 0,
212 'image_url' => array() === $gallery_urls ? '' : $gallery_urls[0],
213 'gallery_ids' => array(),
214 'gallery_urls' => $gallery_urls,
215 'virtual_tour' => $meta( 'VirtualTourURLUnbranded' ),
216 'video_url' => $meta( 'VideoURL' ),
217
218 // Features: no amenity terms without a post.
219 'features' => array(),
220
221 // Agent from the record's ListAgent*/ListOffice* fields (id 0 = no linked post).
222 'agent' => mlsimport_property_agent( 0, $meta ),
223
224 // Live extras: the key for lead/context use + the raw record.
225 'listing_key' => (string) $row->listing_key,
226 );
227
228 /** Filter the property view model — same seam the stored VM passes. @since 6.3 */
229 return (array) apply_filters( 'mlsimport_property_data', $vm, 0 );
230 }
231
232 add_action( 'init', 'mlsimport_live_register_route' );
233 add_filter( 'query_vars', 'mlsimport_live_query_vars' );
234 add_filter( 'template_include', 'mlsimport_live_template_include', 20 );
235 add_filter( 'mlsimport_property_data_pre', 'mlsimport_live_property_data_pre', 10, 2 );
236