PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.2.1
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.2.1
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 6.0.5 All 36 releases
mlsimport / includes / standalone / property-print.php

property-print.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.2.1, at includes/standalone/property-print.php

283 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Standalone property print page.
4 *
5 * Printing the live page is a poor deliverable: the mortgage calculator, lead
6 * forms, booking sidebar and sub-nav all print as dead ink, and the theme's
7 * header/footer bleed in. So the Print button opens a dedicated URL —
8 * ?mlsimport_print=1 on the listing's own permalink — that renders a clean,
9 * self-contained HTML document and prints itself on load.
10 *
11 * The route hangs off template_include at priority 30, AFTER live mode's own
12 * router (priority 20). That single hook covers both modes: stored listings
13 * arrive as a real singular post, live listings arrive with the RESO record
14 * already resolved into mlsimport_live_current_record(), so
15 * mlsimport_property_data() answers correctly either way.
16 *
17 * Sections are the ordinary section renderers, dispatched through the same
18 * mlsimport_render_property_section() the page uses — so print inherits every
19 * fix the page gets. Only the header and the photo list are print-specific.
20 *
21 * @package Mlsimport
22 */
23
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit;
26 }
27
28 require_once __DIR__ . '/property-sections.php';
29
30 /**
31 * The print URL for a listing — its own permalink plus the print flag.
32 *
33 * @param string $permalink Listing permalink.
34 * @return string
35 */
36 function mlsimport_property_print_url( string $permalink ): string {
37 // An empty permalink can't be printed; callers fall back to window.print().
38 if ( '' === $permalink ) {
39 return '';
40 }
41 return add_query_arg( 'mlsimport_print', '1', $permalink );
42 }
43
44 /**
45 * The sections that make up the printed document, in order.
46 *
47 * Deliberately excludes everything that is interactive or worthless on paper:
48 * lead/contact forms, schedule-tour, booking sidebar, mortgage calculator,
49 * map (Leaflet never initializes in the print document), video/virtual tour,
50 * similar listings, sub-nav and the share/print bar itself.
51 *
52 * @return string[] Registered section slugs.
53 */
54 function mlsimport_property_print_sections(): array {
55 /**
56 * Filters the sections rendered on the property print page.
57 *
58 * @param string[] $slugs Ordered registered section slugs.
59 */
60 return (array) apply_filters(
61 'mlsimport_property_print_sections',
62 array(
63 'overview',
64 'description',
65 'interior',
66 'exterior',
67 'structure',
68 'utilities',
69 'financial',
70 'schools',
71 'location',
72 'listing_info',
73 'other',
74 'features',
75 'agent_card',
76 'attribution',
77 )
78 );
79 }
80
81 /**
82 * The masthead logo for the print document: the site's custom logo when one is
83 * set, else the site name as text.
84 *
85 * @return string
86 */
87 function mlsimport_property_print_logo_html(): string {
88 $logo_id = (int) get_theme_mod( 'custom_logo' );
89 $src = $logo_id ? wp_get_attachment_image_url( $logo_id, 'medium' ) : '';
90 // No logo configured → the site name still identifies the printout.
91 if ( ! $src ) {
92 return '<span class="mlsimport-print__site">' . esc_html( get_bloginfo( 'name' ) ) . '</span>';
93 }
94 return '<img class="mlsimport-print__logo" src="' . esc_url( $src ) . '" alt="' . esc_attr( get_bloginfo( 'name' ) ) . '" />';
95 }
96
97 /**
98 * The QR code image for a listing URL — scanning the printout opens the listing.
99 *
100 * Rendered from a remote QR service (no bundled library), so it is skipped
101 * silently when the endpoint is filtered away.
102 *
103 * @param string $url Listing URL to encode.
104 * @return string
105 */
106 function mlsimport_property_print_qr_html( string $url ): string {
107 // Nothing to encode without a URL.
108 if ( '' === $url ) {
109 return '';
110 }
111
112 /**
113 * Filters the QR image source for the print page. Return '' to omit the QR.
114 *
115 * @param string $src QR image URL.
116 * @param string $url The listing URL being encoded.
117 */
118 $src = (string) apply_filters(
119 'mlsimport_property_print_qr_src',
120 'https://qrcode.tec-it.com/API/QRCode?size=small&dpi=110&data=' . rawurlencode( $url ),
121 $url
122 );
123 if ( '' === $src ) {
124 return '';
125 }
126 return '<img class="mlsimport-print__qr" src="' . esc_url( $src ) . '" alt="' . esc_attr__( 'QR code linking to this listing', 'mlsimport' ) . '" />';
127 }
128
129 /**
130 * Every listing photo, one per row, at full width.
131 *
132 * Handles both storage modes: stored listings carry attachment ids, live
133 * passthrough listings carry MLS CDN URLs and no attachments at all.
134 *
135 * @param array $data Property view model.
136 * @return string
137 */
138 function mlsimport_property_print_photos_html( array $data ): string {
139 // Attachment ids when stored, CDN URLs when live.
140 $items = ! empty( $data['gallery_ids'] ) ? $data['gallery_ids'] : ( $data['gallery_urls'] ?? array() );
141 if ( empty( $items ) ) {
142 return '';
143 }
144
145 $html = '<div class="mlsimport-print__photos">';
146 foreach ( $items as $item ) {
147 // An int is an attachment; anything else is already a URL.
148 $src = is_numeric( $item ) ? wp_get_attachment_image_url( (int) $item, 'large' ) : (string) $item;
149 // Skip an attachment that no longer resolves.
150 if ( ! $src ) {
151 continue;
152 }
153 $html .= '<div class="mlsimport-print__photo"><img src="' . esc_url( $src ) . '" alt="" /></div>';
154 }
155 $html .= '</div>';
156 return $html;
157 }
158
159 /**
160 * The print document's masthead: logo, title, price, address and QR.
161 *
162 * @param array $data Property view model.
163 * @return string
164 */
165 function mlsimport_property_print_header_html( array $data ): string {
166 $html = '<header class="mlsimport-print__header">';
167 $html .= '<div class="mlsimport-print__brand">' . mlsimport_property_print_logo_html() . '</div>';
168
169 $html .= '<div class="mlsimport-print__headline">';
170 $html .= '<div class="mlsimport-print__headline-text">';
171 if ( '' !== (string) $data['title'] ) {
172 $html .= '<h1 class="mlsimport-print__title">' . esc_html( $data['title'] ) . '</h1>';
173 }
174 if ( null !== $data['price'] ) {
175 $html .= '<p class="mlsimport-print__price">' . esc_html( mlsimport_format_price( $data['price'] ) ) . '</p>';
176 }
177 if ( '' !== (string) $data['address'] ) {
178 $html .= '<p class="mlsimport-print__address">' . esc_html( $data['address'] ) . '</p>';
179 }
180 $html .= '</div>';
181 // QR sits beside the headline so it survives even when there is no hero photo.
182 $html .= mlsimport_property_print_qr_html( (string) $data['permalink'] );
183 $html .= '</div>';
184
185 $html .= '</header>';
186 return $html;
187 }
188
189 /**
190 * Render the complete print document for one listing.
191 *
192 * Self-contained: stylesheets are linked by URL (there is no wp_head here), and
193 * an onload handler fires the print dialog once images have loaded.
194 *
195 * @param int $id Property post ID (0 in live mode).
196 * @return string
197 */
198 function mlsimport_property_print_document( int $id ): string {
199 $data = mlsimport_property_data( $id );
200 // No resolvable listing → no document.
201 if ( empty( $data ) ) {
202 return '';
203 }
204
205 // Sections, dispatched exactly as the page dispatches them.
206 $sections = '';
207 foreach ( mlsimport_property_print_sections() as $slug ) {
208 $sections .= mlsimport_render_property_section( $slug, $id );
209 }
210
211 $url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : '';
212 $ver = defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : '';
213
214 // The section stylesheet gives the reused sections their normal look; the
215 // print stylesheet layers the paper-specific rules on top.
216 $styles = '<link rel="stylesheet" href="' . esc_url( $url . 'public/css/mlsimport-property-sections.css?ver=' . $ver ) . '" />';
217 $styles .= '<link rel="stylesheet" href="' . esc_url( $url . 'public/css/mlsimport-property-print.css?ver=' . $ver ) . '" />';
218 // The brand colour normally rides in as an inline style on the enqueued
219 // handle; inline it here since this document never runs wp_head.
220 if ( function_exists( 'mlsimport_standalone_brand_color_css' ) ) {
221 $brand = mlsimport_standalone_brand_color_css();
222 if ( '' !== $brand ) {
223 $styles .= '<style>' . wp_strip_all_tags( $brand ) . '</style>';
224 }
225 }
226
227 $html = '<!DOCTYPE html><html ' . get_language_attributes() . '><head><meta charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '" />';
228 $html .= '<title>' . esc_html( $data['title'] ) . '</title>';
229 $html .= '<meta name="robots" content="noindex,nofollow" />';
230 $html .= $styles;
231 $html .= '</head><body class="mlsimport-print">';
232 $html .= mlsimport_property_print_header_html( $data );
233 $html .= mlsimport_property_print_photos_html( $data );
234 $html .= '<div class="mlsimport-print__sections">' . $sections . '</div>';
235 // Print once the document (images included) has finished loading.
236 $html .= '<script>window.addEventListener("load",function(){window.print();});</script>';
237 $html .= '</body></html>';
238
239 /**
240 * Filters the complete property print document.
241 *
242 * @param string $html Full HTML document.
243 * @param array $data Property view model.
244 * @param int $id Property post ID (0 in live mode).
245 */
246 return (string) apply_filters( 'mlsimport_property_print_document', $html, $data, $id );
247 }
248
249 /**
250 * Serve the print document when ?mlsimport_print=1 is on a listing URL.
251 *
252 * Runs after live mode's router so the live record is already resolved. Any
253 * other request passes straight through untouched.
254 *
255 * @param string $template The template WordPress resolved.
256 * @return string
257 */
258 function mlsimport_property_print_template_include( $template ) {
259 // Not a print request.
260 if ( ! isset( $_GET['mlsimport_print'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only public view of already-public content.
261 return $template;
262 }
263
264 // Live listing: the router already resolved the record, so id 0 is valid.
265 // Stored listing: must be a real single property page.
266 $is_live = function_exists( 'mlsimport_live_current_record' ) && null !== mlsimport_live_current_record();
267 if ( ! $is_live && ! is_singular( 'mlsimport_property' ) ) {
268 return $template;
269 }
270
271 $document = mlsimport_property_print_document( $is_live ? 0 : (int) get_the_ID() );
272 // Nothing to print → fall back to the normal page rather than a blank window.
273 if ( '' === $document ) {
274 return $template;
275 }
276
277 // phpcs:ignore WordPress.Security.EscapingOutput.OutputNotEscaped -- assembled and escaped in mlsimport_property_print_document().
278 echo $document;
279 exit;
280 }
281
282 add_filter( 'template_include', 'mlsimport_property_print_template_include', 30 );
283