PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.1.2
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.1.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 6.0.5 All 36 releases
mlsimport / includes / standalone / class-mlsimport-customizer.php

class-mlsimport-customizer.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.1.2, at includes/standalone/class-mlsimport-customizer.php

312 lines 11.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Standalone design settings in the WordPress Customizer (theme_id 990).
4 *
5 * A second editor over the SAME mlsimport_standalone_options as the React
6 * settings page — Appearance → Customize → "MLSImport Design". The whole panel is
7 * GENERATED from mlsimport_standalone_settings_schema(); there is no per-field
8 * wiring here, so a field added to the registry appears in the Customizer
9 * automatically. Colour previews live (postMessage); everything else reloads the
10 * preview (refresh). See specs/customizer-standalone-design-settings.md.
11 *
12 * @package Mlsimport
13 */
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 require_once __DIR__ . '/class-mlsimport-standalone-settings.php';
20
21 const MLSIMPORT_CUSTOMIZER_PANEL = 'mlsimport_design';
22
23 /**
24 * Register the MLSImport Design panel, one section per schema tab and one control
25 * per field. Standalone mode only — in integrated theme mode the active theme
26 * owns the front-end look and there is nothing for us to expose.
27 *
28 * @param WP_Customize_Manager $wp_customize The Customizer manager.
29 * @return void
30 */
31 function mlsimport_customizer_register( $wp_customize ): void {
32 // Standalone (990) only — in theme mode the active theme owns the look.
33 if ( ! function_exists( 'mlsimport_is_standalone_mode' ) || ! mlsimport_is_standalone_mode() ) {
34 return;
35 }
36
37 // WP_Customize_Control is only loaded now, so the custom control is defined lazily.
38 require_once __DIR__ . '/class-mlsimport-customize-sections-control.php';
39
40 // The arranger draws itself from a JS Underscore template (render_content() is
41 // empty). Registering the control TYPE makes WordPress print that template into
42 // the footer, so the control renders in ANY section — not only whichever one
43 // happens to be expanded on load. Without this the Arrange Sections / Arrange
44 // Fields controls come up blank.
45 if ( class_exists( 'Mlsimport_Customize_Sections_Control' ) ) {
46 $wp_customize->register_control_type( 'Mlsimport_Customize_Sections_Control' );
47 }
48
49 // The root "MLSImport Design" panel that holds the schema-driven sections.
50 $wp_customize->add_panel(
51 MLSIMPORT_CUSTOMIZER_PANEL,
52 array(
53 'title' => __( 'MLSImport Design', 'mlsimport' ),
54 'description' => __( 'Appearance of your listings, cards, search, maps, property and agent pages. These are the same settings as the MLS Import Design Settings screen.', 'mlsimport' ),
55 'priority' => 130,
56 )
57 );
58
59 // A field's Property Page sub-tab (pp_general, pp_layout, …) lives in the
60 // settings-page presentation map. A section whose fields carry sub-tabs (the
61 // Property Page) becomes its OWN panel with one section per sub-tab, so the
62 // Customizer drills down — click "Property Page" → its sub-tabs → a sub-tab's
63 // settings, with the back arrow — exactly like the settings page's nested menu.
64 // (The Customizer can't nest a panel inside a panel, so the Property Page panel
65 // sits at the root next to "MLSImport Design"; a flat "one section per sub-tab"
66 // would instead dump every field into one screen.)
67 // Presentation map (per-field sub-tab) and the sub-tab id => title list.
68 $ui = function_exists( 'mlsimport_standalone_settings_ui' ) ? mlsimport_standalone_settings_ui() : array();
69 $subtabs = function_exists( 'mlsimport_standalone_settings_subtabs' ) ? mlsimport_standalone_settings_subtabs() : array();
70
71 // Helper: add one Customizer section under $panel and its per-field controls.
72 $add_section = function ( $section_id, $title, $fields, $panel ) use ( $wp_customize ) {
73 $wp_customize->add_section(
74 $section_id,
75 array(
76 'title' => $title,
77 'panel' => $panel,
78 )
79 );
80 // One control per field in the section.
81 foreach ( $fields as $field ) {
82 mlsimport_customizer_add_field( $wp_customize, $section_id, $field );
83 }
84 };
85
86 $priority = 131; // Property Page panel sits right after "MLSImport Design".
87 // Walk the schema, turning each section into a Customizer section or drill-down panel.
88 foreach ( mlsimport_standalone_settings_schema() as $section ) {
89 // Bucket the section's fields by sub-tab (empty string = no sub-tab).
90 $by_subtab = array();
91 $has_subtabs = false;
92 foreach ( $section['fields'] as $field ) {
93 // This field's sub-tab id from the presentation map ('' when none).
94 $sub = isset( $ui[ $field['key'] ]['subtab'] ) ? (string) $ui[ $field['key'] ]['subtab'] : '';
95 // Any sub-tab present flips this section into drill-down mode.
96 if ( '' !== $sub ) {
97 $has_subtabs = true;
98 }
99 $by_subtab[ $sub ][] = $field;
100 }
101
102 // Flat section: no sub-tabs, so all fields live in one Customizer section.
103 if ( ! $has_subtabs ) {
104 $add_section( MLSIMPORT_CUSTOMIZER_PANEL . '_' . $section['id'], $section['title'], $section['fields'], MLSIMPORT_CUSTOMIZER_PANEL );
105 continue;
106 }
107
108 // This section drills down: its own panel, one section per sub-tab.
109 $sub_panel = MLSIMPORT_CUSTOMIZER_PANEL . '_' . $section['id'];
110 $wp_customize->add_panel(
111 $sub_panel,
112 array(
113 'title' => $section['title'],
114 'priority' => $priority++,
115 )
116 );
117 // One section per sub-tab that actually has fields, in sub-tab order.
118 foreach ( $subtabs as $sub_id => $sub_title ) {
119 // Skip sub-tabs with no fields in this section.
120 if ( empty( $by_subtab[ $sub_id ] ) ) {
121 continue;
122 }
123 $add_section( $sub_panel . '_' . $sub_id, $sub_title, $by_subtab[ $sub_id ], $sub_panel );
124 }
125 // Any field without a sub-tab still gets a home inside the sub-panel.
126 if ( ! empty( $by_subtab[''] ) ) {
127 $add_section( $sub_panel . '_misc', $section['title'], $by_subtab[''], $sub_panel );
128 }
129 }
130 }
131
132 /**
133 * Register the setting + control for one schema field. The setting is bound to the
134 * multidimensional option key mlsimport_standalone_options[<key>], so the
135 * Customizer writes the very same option the settings page and front end read —
136 * one source of truth, no divergence.
137 *
138 * @param WP_Customize_Manager $wp_customize The Customizer manager.
139 * @param string $section_id The Customizer section id.
140 * @param array $field A schema field.
141 * @return void
142 */
143 function mlsimport_customizer_add_field( $wp_customize, string $section_id, array $field ): void {
144 // Bind the setting to the multidimensional option key options[<key>].
145 $key = $field['key'];
146 $setting_id = MLSIMPORT_STANDALONE_OPTION . '[' . $key . ']';
147 $defaults = mlsimport_standalone_option_defaults();
148
149 // Register the setting: option storage, default, cap, transport, per-field sanitizer.
150 $wp_customize->add_setting(
151 $setting_id,
152 array(
153 'type' => 'option',
154 'default' => isset( $defaults[ $key ] ) ? $defaults[ $key ] : '',
155 'capability' => 'manage_options',
156 // Colours preview live; everything else refreshes the preview.
157 'transport' => 'postMessage' === $field['transport'] ? 'postMessage' : 'refresh',
158 'sanitize_callback' => function ( $value ) use ( $key ) {
159 // Reuse the exact save-path sanitizer, per field.
160 $clean = mlsimport_sanitize_standalone_options( array( $key => $value ) );
161 return $clean[ $key ];
162 },
163 )
164 );
165
166 // Shared control args; each type below adds its own specifics.
167 $control_args = array(
168 'label' => $field['label'],
169 'section' => $section_id,
170 'settings' => $setting_id,
171 );
172
173 // Pick the control class/type from the field's declared type.
174 switch ( $field['type'] ) {
175 case 'color':
176 // Native colour picker.
177 $wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, $setting_id, $control_args ) );
178 break;
179
180 case 'int': // mls_logo_id — an attachment id chosen through the media modal.
181 $wp_customize->add_control(
182 new WP_Customize_Media_Control(
183 $wp_customize,
184 $setting_id,
185 $control_args + array( 'mime_type' => 'image' )
186 )
187 );
188 break;
189
190 case 'select':
191 // Dropdown built from the field's options map.
192 $wp_customize->add_control(
193 $setting_id,
194 $control_args + array(
195 'type' => 'select',
196 'choices' => is_array( $field['options'] ) ? $field['options'] : array(),
197 )
198 );
199 break;
200
201 case 'textarea':
202 case 'html':
203 // Both render as a plain textarea control.
204 $wp_customize->add_control( $setting_id, $control_args + array( 'type' => 'textarea' ) );
205 break;
206
207 case 'number':
208 // Numeric input.
209 $wp_customize->add_control( $setting_id, $control_args + array( 'type' => 'number' ) );
210 break;
211
212 case 'email':
213 // Email input.
214 $wp_customize->add_control( $setting_id, $control_args + array( 'type' => 'email' ) );
215 break;
216
217 case 'sections':
218 // Resolve the choice catalog from the field's callable, if any.
219 $catalog = array();
220 if ( ! empty( $field['catalog'] ) && is_callable( $field['catalog'] ) ) {
221 $catalog = (array) call_user_func( $field['catalog'] );
222 }
223 // Custom reorder/enable-disable arranger control.
224 $wp_customize->add_control(
225 new Mlsimport_Customize_Sections_Control(
226 $wp_customize,
227 $setting_id,
228 $control_args + array( 'catalog' => $catalog )
229 )
230 );
231 break;
232
233 default: // text.
234 // Fallback single-line text input.
235 $wp_customize->add_control( $setting_id, $control_args + array( 'type' => 'text' ) );
236 break;
237 }
238 }
239
240 /**
241 * Enqueue the custom "arrange sections" control JS + CSS in the Customizer
242 * controls pane (the left panel, not the preview). Standalone mode only.
243 *
244 * @return void
245 */
246 function mlsimport_customizer_enqueue_controls(): void {
247 // Standalone (990) only.
248 if ( ! function_exists( 'mlsimport_is_standalone_mode' ) || ! mlsimport_is_standalone_mode() ) {
249 return;
250 }
251 // Cache-busting version and plugin base URL.
252 $ver = defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : '1';
253 $url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' );
254
255 // Controls-pane script + stylesheet for the arranger control.
256 wp_enqueue_script(
257 'mlsimport-customizer-controls',
258 $url . 'admin/js/mlsimport-customizer-controls.js',
259 array( 'jquery', 'customize-controls', 'wp-api-fetch' ),
260 $ver,
261 true
262 );
263 wp_enqueue_style(
264 'mlsimport-customizer-controls',
265 $url . 'admin/css/mlsimport-customizer-controls.css',
266 array(),
267 $ver
268 );
269
270 // Land the preview on the listings archive so the accent colour and card style
271 // are visible the moment the panel opens (see the preview-landing controls JS).
272 // Pass the listings archive URL to JS so the preview lands there on open.
273 $archive = function_exists( 'get_post_type_archive_link' ) ? get_post_type_archive_link( 'mlsimport_property' ) : '';
274 wp_add_inline_script(
275 'mlsimport-customizer-controls',
276 'window.mlsimportCustomizer = ' . wp_json_encode( array( 'archiveUrl' => $archive ? $archive : '' ) ) . ';',
277 'before'
278 );
279 }
280
281 /**
282 * Enqueue the accent live-preview script INSIDE the preview iframe. Standalone
283 * mode only.
284 *
285 * @return void
286 */
287 function mlsimport_customizer_enqueue_preview(): void {
288 // Standalone (990) only.
289 if ( ! function_exists( 'mlsimport_is_standalone_mode' ) || ! mlsimport_is_standalone_mode() ) {
290 return;
291 }
292 // Cache-busting version and plugin base URL.
293 $ver = defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : '1';
294 $url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' );
295
296 // Accent live-preview script, loaded inside the preview iframe.
297 wp_enqueue_script(
298 'mlsimport-customizer-preview',
299 $url . 'admin/js/mlsimport-customizer-preview.js',
300 array( 'customize-preview' ),
301 $ver,
302 true
303 );
304 }
305
306 // Wire the panel registration and the two enqueue passes into the Customizer.
307 if ( function_exists( 'add_action' ) ) {
308 add_action( 'customize_register', 'mlsimport_customizer_register' );
309 add_action( 'customize_controls_enqueue_scripts', 'mlsimport_customizer_enqueue_controls' );
310 add_action( 'customize_preview_init', 'mlsimport_customizer_enqueue_preview' );
311 }
312