PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / trunk
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings vtrunk
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 / includes / standalone / class-mlsimport-customizer.php

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

317 lines 12.0 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. It also learns which
224 // slugs the field's default keeps disabled, so a catalog entry the saved
225 // value never mentions is shown where the sanitizer will put it on save.
226 $wp_customize->add_control(
227 new Mlsimport_Customize_Sections_Control(
228 $wp_customize,
229 $setting_id,
230 $control_args + array(
231 'catalog' => $catalog,
232 'default_inactive' => isset( $field['default']['inactive'] ) ? array_values( (array) $field['default']['inactive'] ) : array(),
233 )
234 )
235 );
236 break;
237
238 default: // text.
239 // Fallback single-line text input.
240 $wp_customize->add_control( $setting_id, $control_args + array( 'type' => 'text' ) );
241 break;
242 }
243 }
244
245 /**
246 * Enqueue the custom "arrange sections" control JS + CSS in the Customizer
247 * controls pane (the left panel, not the preview). Standalone mode only.
248 *
249 * @return void
250 */
251 function mlsimport_customizer_enqueue_controls(): void {
252 // Standalone (990) only.
253 if ( ! function_exists( 'mlsimport_is_standalone_mode' ) || ! mlsimport_is_standalone_mode() ) {
254 return;
255 }
256 // Cache-busting version and plugin base URL.
257 $ver = defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : '1';
258 $url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' );
259
260 // Controls-pane script + stylesheet for the arranger control.
261 wp_enqueue_script(
262 'mlsimport-customizer-controls',
263 $url . 'admin/js/mlsimport-customizer-controls.js',
264 array( 'jquery', 'customize-controls', 'wp-api-fetch' ),
265 $ver,
266 true
267 );
268 wp_enqueue_style(
269 'mlsimport-customizer-controls',
270 $url . 'admin/css/mlsimport-customizer-controls.css',
271 array(),
272 $ver
273 );
274
275 // Land the preview on the listings archive so the accent colour and card style
276 // are visible the moment the panel opens (see the preview-landing controls JS).
277 // Pass the listings archive URL to JS so the preview lands there on open.
278 $archive = function_exists( 'get_post_type_archive_link' ) ? get_post_type_archive_link( 'mlsimport_property' ) : '';
279 wp_add_inline_script(
280 'mlsimport-customizer-controls',
281 'window.mlsimportCustomizer = ' . wp_json_encode( array( 'archiveUrl' => $archive ? $archive : '' ) ) . ';',
282 'before'
283 );
284 }
285
286 /**
287 * Enqueue the accent live-preview script INSIDE the preview iframe. Standalone
288 * mode only.
289 *
290 * @return void
291 */
292 function mlsimport_customizer_enqueue_preview(): void {
293 // Standalone (990) only.
294 if ( ! function_exists( 'mlsimport_is_standalone_mode' ) || ! mlsimport_is_standalone_mode() ) {
295 return;
296 }
297 // Cache-busting version and plugin base URL.
298 $ver = defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : '1';
299 $url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' );
300
301 // Accent live-preview script, loaded inside the preview iframe.
302 wp_enqueue_script(
303 'mlsimport-customizer-preview',
304 $url . 'admin/js/mlsimport-customizer-preview.js',
305 array( 'customize-preview' ),
306 $ver,
307 true
308 );
309 }
310
311 // Wire the panel registration and the two enqueue passes into the Customizer.
312 if ( function_exists( 'add_action' ) ) {
313 add_action( 'customize_register', 'mlsimport_customizer_register' );
314 add_action( 'customize_controls_enqueue_scripts', 'mlsimport_customizer_enqueue_controls' );
315 add_action( 'customize_preview_init', 'mlsimport_customizer_enqueue_preview' );
316 }
317