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-page-block-blocks.php

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

199 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Standalone (theme_id 990) page-block Gutenberg adapter (generic).
4 *
5 * One adapter that loops the page-block manifest and registers a dynamic,
6 * server-rendered Gutenberg block (mlsimport/<slug>) per entry, whose
7 * render_callback hands off to the one dispatcher — so block, shortcode and
8 * Elementor widget emit identical markup. A single editor script registers every
9 * block in the editor and builds its inspector controls from the arg schema.
10 * Adding a block needs no change here. See docs/adr/0007.
11 *
12 * @package Mlsimport
13 */
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 require_once __DIR__ . '/page-block-registry.php';
20
21 /**
22 * Registers a dynamic block per registered page block.
23 */
24 class Mlsimport_Page_Block_Blocks {
25
26 /**
27 * Register one dynamic block for every manifest entry.
28 *
29 * @return void
30 */
31 public static function register(): void {
32 if ( ! function_exists( 'register_block_type' ) ) {
33 return;
34 }
35
36 $editor_script = self::register_editor_script();
37
38 foreach ( mlsimport_get_page_blocks() as $slug => $def ) {
39 // Half Map, Search Results, Content Slider, Property List and Map with Listings
40 // are owned by Mlsimport_Standalone_Block so they reuse the rich listings
41 // inspector (the "Initial filter" panel + per-field toggles) instead of one raw
42 // text input per key. Skip them here to avoid a double register.
43 if ( 'half_map' === $slug || 'property_list_filters' === $slug || 'content_slider' === $slug || 'item_list' === $slug || 'map_listings' === $slug ) {
44 continue;
45 }
46 $block_name = 'mlsimport/' . str_replace( '_', '-', $slug );
47 if ( WP_Block_Type_Registry::get_instance()->is_registered( $block_name ) ) {
48 continue;
49 }
50 register_block_type(
51 $block_name,
52 array(
53 'api_version' => 2,
54 'attributes' => self::attributes( $def['args'] ),
55 'render_callback' => static function ( $attributes ) use ( $slug ) {
56 return mlsimport_render_page_block( $slug, (array) $attributes );
57 },
58 'editor_script' => $editor_script,
59 )
60 );
61 }
62 }
63
64 /**
65 * Block attribute schema derived from a block's arg schema.
66 *
67 * @param array $args Arg schema.
68 * @return array
69 */
70 private static function attributes( array $args ): array {
71 $attributes = array();
72 foreach ( $args as $key => $schema ) {
73 $type = isset( $schema['type'] ) ? $schema['type'] : 'text';
74 if ( 'number' === $type ) {
75 $attributes[ $key ] = array( 'type' => 'number', 'default' => isset( $schema['default'] ) ? (float) $schema['default'] : 0 );
76 } elseif ( 'toggle' === $type ) {
77 $attributes[ $key ] = array( 'type' => 'boolean', 'default' => ! empty( $schema['default'] ) );
78 } elseif ( 'repeater' === $type ) {
79 $attributes[ $key ] = array( 'type' => 'array', 'default' => isset( $schema['default'] ) ? (array) $schema['default'] : array() );
80 } else {
81 // Includes 'multiselect': like every taxonomy filter it stores a comma
82 // list of term ids as a string (the FormTokenField serializes to it).
83 $attributes[ $key ] = array( 'type' => 'string', 'default' => isset( $schema['default'] ) ? (string) $schema['default'] : '' );
84 }
85 }
86 return $attributes;
87 }
88
89 /**
90 * Register + localize the shared editor script that registers all page blocks
91 * in the editor. Returns the handle (or '' if scripts unavailable).
92 *
93 * @return string
94 */
95 private static function register_editor_script(): string {
96 if ( ! function_exists( 'wp_register_script' ) ) {
97 return '';
98 }
99
100 $handle = 'mlsimport-page-block-blocks';
101 if ( ! wp_script_is( $handle, 'registered' ) ) {
102 $url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL . 'admin/js/mlsimport-page-block-blocks.js' : '';
103 // Version by file mtime so a JS edit busts the editor cache even when the
104 // plugin version constant is unchanged — otherwise the browser keeps the
105 // stale script and ignores fixes (e.g. the repeater array-attribute fix).
106 $path = defined( 'MLSIMPORT_PLUGIN_PATH' ) ? MLSIMPORT_PLUGIN_PATH . 'admin/js/mlsimport-page-block-blocks.js' : '';
107 $ver = ( $path && file_exists( $path ) ) ? (string) filemtime( $path ) : ( defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : false );
108 wp_register_script(
109 $handle,
110 $url,
111 array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-server-side-render' ),
112 $ver,
113 true
114 );
115
116 $blocks = array();
117 foreach ( mlsimport_get_page_blocks() as $slug => $def ) {
118 // Half Map, Search Results, Content Slider, Property List and Map with Listings
119 // register via Mlsimport_Standalone_Block's editor script; if this generic
120 // script also registered them, the second registerBlockType would be a no-op
121 // warning. Keep them out of this manifest.
122 if ( 'half_map' === $slug || 'property_list_filters' === $slug || 'content_slider' === $slug || 'item_list' === $slug || 'map_listings' === $slug ) {
123 continue;
124 }
125 $blocks[] = array(
126 'slug' => str_replace( '_', '-', $slug ),
127 'title' => $def['label'],
128 'args' => self::js_controls( $def['args'] ),
129 );
130 }
131 $icon = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL . 'img/mlsimport_menu.png' : '';
132 wp_localize_script( $handle, 'MLSImportPageBlocks', $blocks );
133 wp_add_inline_script( $handle, 'window.MLSImportPageBlocksIcon=' . wp_json_encode( $icon ) . ';', 'before' );
134 }
135
136 return $handle;
137 }
138
139 /**
140 * Reduce a block's arg schema to the data the editor JS needs to build a
141 * control per field (key, type, label, default, options).
142 *
143 * @param array $args Arg schema.
144 * @return array
145 */
146 private static function js_controls( array $args ): array {
147 $controls = array();
148 foreach ( $args as $key => $schema ) {
149 $type = isset( $schema['type'] ) ? $schema['type'] : 'text';
150
151 // A multiselect (the Category widgets' per-taxonomy pickers, or the Team
152 // Directory agent picker) resolves its options only in the admin editor,
153 // where the picker is shown; the front end reads the saved picks and never
154 // needs the list. Source is a taxonomy's terms or a post type's posts. An
155 // empty option set (no terms/posts yet) gets no picker.
156 if ( 'multiselect' === $type && ( isset( $schema['taxonomy'] ) || isset( $schema['post_type'] ) ) ) {
157 if ( ! is_admin() ) {
158 $options = array();
159 } elseif ( isset( $schema['taxonomy'] ) ) {
160 $options = mlsimport_category_term_options( (string) $schema['taxonomy'] );
161 } else {
162 $options = mlsimport_post_type_options( (string) $schema['post_type'] );
163 }
164 if ( is_admin() && empty( $options ) ) {
165 continue;
166 }
167 $controls[] = array(
168 'key' => $key,
169 'type' => 'multiselect',
170 'label' => isset( $schema['label'] ) ? $schema['label'] : $key,
171 'default' => '',
172 'options' => $options,
173 );
174 continue;
175 }
176
177 $control = array(
178 'key' => $key,
179 'type' => $type,
180 'label' => isset( $schema['label'] ) ? $schema['label'] : $key,
181 'default' => isset( $schema['default'] ) ? $schema['default'] : '',
182 'options' => isset( $schema['options'] ) ? $schema['options'] : null,
183 );
184 // A repeater carries its row sub-field schema so the editor can build a
185 // control per column.
186 if ( isset( $schema['fields'] ) ) {
187 $control['fields'] = self::js_controls( (array) $schema['fields'] );
188 }
189 // A show-only-when rule ({ other_key: value|values }, Elementor's format), so
190 // the editor hides e.g. a row's slider bounds on non-range fields.
191 if ( ! empty( $schema['condition'] ) ) {
192 $control['condition'] = (array) $schema['condition'];
193 }
194 $controls[] = $control;
195 }
196 return $controls;
197 }
198 }
199