| 1 |
<?php |
| 2 |
/** |
| 3 |
* Standalone (theme_id 990) Gutenberg block (M7). |
| 4 |
* |
| 5 |
* A dynamic, server-rendered block: its render_callback maps the block |
| 6 |
* attributes through the same atts->args + render_grid path as the shortcode, |
| 7 |
* so block / shortcode / widget all emit identical output. (The editor-side |
| 8 |
* inspector UI is a separate JS build; this is the render contract.) |
| 9 |
* |
| 10 |
* @package Mlsimport |
| 11 |
*/ |
| 12 |
|
| 13 |
if ( ! defined( 'ABSPATH' ) ) { |
| 14 |
exit; |
| 15 |
} |
| 16 |
|
| 17 |
require_once __DIR__ . '/class-mlsimport-standalone-render.php'; |
| 18 |
require_once __DIR__ . '/class-mlsimport-standalone-shortcodes.php'; |
| 19 |
require_once __DIR__ . '/class-mlsimport-page-block-search-fields.php'; |
| 20 |
require_once __DIR__ . '/page-block-registry.php'; |
| 21 |
|
| 22 |
/** |
| 23 |
* Registers and renders the mlsimport/listings and mlsimport/half-map blocks. |
| 24 |
* |
| 25 |
* Both share the rich listings inspector (filter presets + per-field toggles + |
| 26 |
* "properties to display"). Half Map is owned here — not by the generic page-block |
| 27 |
* adapter (Mlsimport_Page_Block_Blocks, which skips it) — so it reuses that |
| 28 |
* inspector instead of one raw text input per filter key. Its markup still comes |
| 29 |
* from the page-block dispatcher, so block / shortcode / Elementor stay identical. |
| 30 |
*/ |
| 31 |
class Mlsimport_Standalone_Block { |
| 32 |
|
| 33 |
const NAME = 'mlsimport/listings'; |
| 34 |
|
| 35 |
/** The Half Map block: the listings inspector + a Layout panel (map side, height). */ |
| 36 |
const HALF_MAP_NAME = 'mlsimport/half-map'; |
| 37 |
|
| 38 |
/** The Search Results block: a trimmed listings inspector (per-page, show-bar, |
| 39 |
* fields-per-row, per-field toggles). Owned here — not the generic page-block |
| 40 |
* adapter — so it reuses those toggles instead of a raw search_fields text input. */ |
| 41 |
const RESULTS_NAME = 'mlsimport/property-list-filters'; |
| 42 |
|
| 43 |
/** The Content Slider block: a Slider panel (how many + explicit IDs) + the same |
| 44 |
* rich "Initial filter" panel as the Half Map (taxonomy dropdowns, price, beds/baths, |
| 45 |
* order). Owned here so it reuses that panel instead of one raw text input per key. */ |
| 46 |
const SLIDER_NAME = 'mlsimport/content-slider'; |
| 47 |
|
| 48 |
/** The Property List block: the rich listings inspector (Settings + Initial filter + |
| 49 |
* per-field toggles) minus the map Layout panel. Owned here — not the generic page-block |
| 50 |
* adapter — so it reuses those toggles instead of raw text / comma-list inputs. */ |
| 51 |
const ITEM_LIST_NAME = 'mlsimport/item-list'; |
| 52 |
|
| 53 |
/** The Map with Listings block: a Settings panel (how many + explicit IDs) + the same |
| 54 |
* rich Initial filter panel the Half Map and Property List expose. Owned here — not the |
| 55 |
* generic page-block adapter — so it reuses those dropdowns instead of raw text inputs. */ |
| 56 |
const MAP_NAME = 'mlsimport/map-listings'; |
| 57 |
|
| 58 |
/** Editor script handle (registered at init, localized on editor assets). */ |
| 59 |
const EDITOR_HANDLE = 'mlsimport-standalone-block'; |
| 60 |
|
| 61 |
/** |
| 62 |
* Register the dynamic blocks (idempotent). |
| 63 |
* |
| 64 |
* @return void |
| 65 |
*/ |
| 66 |
public static function register(): void { |
| 67 |
if ( ! function_exists( 'register_block_type' ) ) { |
| 68 |
return; |
| 69 |
} |
| 70 |
|
| 71 |
$editor_script = self::register_editor_script(); |
| 72 |
|
| 73 |
if ( ! WP_Block_Type_Registry::get_instance()->is_registered( self::NAME ) ) { |
| 74 |
register_block_type( |
| 75 |
self::NAME, |
| 76 |
array( |
| 77 |
'render_callback' => array( __CLASS__, 'render' ), |
| 78 |
'attributes' => self::attributes(), |
| 79 |
'editor_script' => $editor_script, |
| 80 |
) |
| 81 |
); |
| 82 |
} |
| 83 |
|
| 84 |
if ( ! WP_Block_Type_Registry::get_instance()->is_registered( self::HALF_MAP_NAME ) ) { |
| 85 |
register_block_type( |
| 86 |
self::HALF_MAP_NAME, |
| 87 |
array( |
| 88 |
'render_callback' => array( __CLASS__, 'render_half_map' ), |
| 89 |
'attributes' => self::half_map_attributes(), |
| 90 |
'editor_script' => $editor_script, |
| 91 |
) |
| 92 |
); |
| 93 |
} |
| 94 |
|
| 95 |
if ( ! WP_Block_Type_Registry::get_instance()->is_registered( self::RESULTS_NAME ) ) { |
| 96 |
register_block_type( |
| 97 |
self::RESULTS_NAME, |
| 98 |
array( |
| 99 |
'render_callback' => array( __CLASS__, 'render_results' ), |
| 100 |
'attributes' => self::results_attributes(), |
| 101 |
'editor_script' => $editor_script, |
| 102 |
) |
| 103 |
); |
| 104 |
} |
| 105 |
|
| 106 |
if ( ! WP_Block_Type_Registry::get_instance()->is_registered( self::SLIDER_NAME ) ) { |
| 107 |
register_block_type( |
| 108 |
self::SLIDER_NAME, |
| 109 |
array( |
| 110 |
'render_callback' => array( __CLASS__, 'render_slider' ), |
| 111 |
'attributes' => self::slider_attributes(), |
| 112 |
'editor_script' => $editor_script, |
| 113 |
) |
| 114 |
); |
| 115 |
} |
| 116 |
|
| 117 |
if ( ! WP_Block_Type_Registry::get_instance()->is_registered( self::ITEM_LIST_NAME ) ) { |
| 118 |
register_block_type( |
| 119 |
self::ITEM_LIST_NAME, |
| 120 |
array( |
| 121 |
'render_callback' => array( __CLASS__, 'render_item_list' ), |
| 122 |
'attributes' => self::item_list_attributes(), |
| 123 |
'editor_script' => $editor_script, |
| 124 |
) |
| 125 |
); |
| 126 |
} |
| 127 |
|
| 128 |
if ( ! WP_Block_Type_Registry::get_instance()->is_registered( self::MAP_NAME ) ) { |
| 129 |
register_block_type( |
| 130 |
self::MAP_NAME, |
| 131 |
array( |
| 132 |
'render_callback' => array( __CLASS__, 'render_map' ), |
| 133 |
'attributes' => self::map_attributes(), |
| 134 |
'editor_script' => $editor_script, |
| 135 |
) |
| 136 |
); |
| 137 |
} |
| 138 |
|
| 139 |
// The taxonomy-term options are only needed in the editor, so localize them |
| 140 |
// there (and not on every front-end request, where get_terms would be dead work). |
| 141 |
add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'localize_editor' ) ); |
| 142 |
} |
| 143 |
|
| 144 |
/** |
| 145 |
* Register the editor script that JS-registers this dynamic block so it |
| 146 |
* appears in the inserter carrying the same brand icon as the Import Tasks |
| 147 |
* menu. Returns the script handle (or '' if scripts are unavailable). |
| 148 |
* |
| 149 |
* @return string |
| 150 |
*/ |
| 151 |
private static function register_editor_script(): string { |
| 152 |
if ( ! function_exists( 'wp_register_script' ) ) { |
| 153 |
return ''; |
| 154 |
} |
| 155 |
$url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' ); |
| 156 |
// Version by file mtime so a JS edit busts the editor cache even when the |
| 157 |
// plugin version constant is unchanged (see Mlsimport_Page_Block_Blocks). |
| 158 |
$path = defined( 'MLSIMPORT_PLUGIN_PATH' ) ? MLSIMPORT_PLUGIN_PATH . 'admin/js/mlsimport-standalone-block.js' : ''; |
| 159 |
$ver = ( $path && file_exists( $path ) ) ? (string) filemtime( $path ) : ( defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : '1' ); |
| 160 |
|
| 161 |
wp_register_script( |
| 162 |
self::EDITOR_HANDLE, |
| 163 |
$url . 'admin/js/mlsimport-standalone-block.js', |
| 164 |
array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-server-side-render' ), |
| 165 |
$ver, |
| 166 |
true |
| 167 |
); |
| 168 |
return self::EDITOR_HANDLE; |
| 169 |
} |
| 170 |
|
| 171 |
/** |
| 172 |
* Attach the editor config to the block script (editor context only). Carries the |
| 173 |
* block name, brand icon, the search-field toggle catalog and — for the initial |
| 174 |
* filter inspector — the term options of every standalone taxonomy. |
| 175 |
* |
| 176 |
* @return void |
| 177 |
*/ |
| 178 |
public static function localize_editor(): void { |
| 179 |
if ( ! function_exists( 'wp_localize_script' ) ) { |
| 180 |
return; |
| 181 |
} |
| 182 |
$url = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' ); |
| 183 |
|
| 184 |
// Re-skin MLSImport block inspector controls to the shared "2025" admin design |
| 185 |
// system (matches the settings page / field_options tab). Scoped by the |
| 186 |
// .mlsimport-block-inspector class the PanelBody wrapper adds, so it only |
| 187 |
// touches MLSImport panels. mtime-versioned so CSS edits bust the editor cache. |
| 188 |
if ( function_exists( 'wp_enqueue_style' ) ) { |
| 189 |
$css_path = defined( 'MLSIMPORT_PLUGIN_PATH' ) ? MLSIMPORT_PLUGIN_PATH . 'admin/css/mlsimport-block-editor.css' : ''; |
| 190 |
$css_ver = ( $css_path && file_exists( $css_path ) ) ? (string) filemtime( $css_path ) : ( defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : '1' ); |
| 191 |
wp_enqueue_style( |
| 192 |
'mlsimport-block-editor', |
| 193 |
$url . 'admin/css/mlsimport-block-editor.css', |
| 194 |
array( 'wp-components' ), |
| 195 |
$css_ver |
| 196 |
); |
| 197 |
} |
| 198 |
|
| 199 |
wp_localize_script( |
| 200 |
self::EDITOR_HANDLE, |
| 201 |
'MLSImportBlock', |
| 202 |
array( |
| 203 |
'name' => self::NAME, |
| 204 |
'iconUrl' => $url . 'img/mlsimport_menu.png', |
| 205 |
'searchFields' => self::search_fields(), |
| 206 |
'taxonomies' => self::taxonomy_options(), |
| 207 |
'blocks' => array( |
| 208 |
array( |
| 209 |
'name' => self::NAME, |
| 210 |
'title' => __( 'MLS Listings', 'mlsimport' ), |
| 211 |
'description' => __( 'Display imported MLS listings in a filterable grid.', 'mlsimport' ), |
| 212 |
'category' => 'widgets', |
| 213 |
'layout' => false, |
| 214 |
'kind' => 'listings', |
| 215 |
), |
| 216 |
array( |
| 217 |
'name' => self::HALF_MAP_NAME, |
| 218 |
'title' => __( 'Half Map', 'mlsimport' ), |
| 219 |
'description' => __( 'Filterable MLS listings beside a live, viewport-clustered map.', 'mlsimport' ), |
| 220 |
'category' => 'mlsimport-real-estate', |
| 221 |
'layout' => true, |
| 222 |
'kind' => 'half-map', |
| 223 |
), |
| 224 |
array( |
| 225 |
'name' => self::RESULTS_NAME, |
| 226 |
'title' => __( 'Search Results', 'mlsimport' ), |
| 227 |
'description' => __( 'MLS search results seeded from the search form, with an optional refine bar.', 'mlsimport' ), |
| 228 |
'category' => 'mlsimport-real-estate', |
| 229 |
'layout' => false, |
| 230 |
'kind' => 'results', |
| 231 |
), |
| 232 |
array( |
| 233 |
'name' => self::SLIDER_NAME, |
| 234 |
'title' => __( 'Property Slider', 'mlsimport' ), |
| 235 |
'description' => __( 'Selected MLS listings as a swipeable carousel, with an initial filter.', 'mlsimport' ), |
| 236 |
'category' => 'mlsimport-real-estate', |
| 237 |
'layout' => false, |
| 238 |
'kind' => 'slider', |
| 239 |
), |
| 240 |
array( |
| 241 |
'name' => self::ITEM_LIST_NAME, |
| 242 |
'title' => __( 'Property List', 'mlsimport' ), |
| 243 |
'description' => __( 'A filterable MLS listings grid with a search bar and pagination.', 'mlsimport' ), |
| 244 |
'category' => 'mlsimport-real-estate', |
| 245 |
'layout' => false, |
| 246 |
'kind' => 'item-list', |
| 247 |
), |
| 248 |
array( |
| 249 |
'name' => self::MAP_NAME, |
| 250 |
'title' => __( 'Map with Listings', 'mlsimport' ), |
| 251 |
'description' => __( 'A map of MLS listings matching an initial filter (or an explicit set).', 'mlsimport' ), |
| 252 |
'category' => 'mlsimport-real-estate', |
| 253 |
'layout' => false, |
| 254 |
'kind' => 'map', |
| 255 |
), |
| 256 |
), |
| 257 |
) |
| 258 |
); |
| 259 |
} |
| 260 |
|
| 261 |
/** |
| 262 |
* The taxonomy filter controls for the initial-filter inspector: one entry per |
| 263 |
* standalone taxonomy field, each carrying its attribute key, label and term |
| 264 |
* options. Option values follow the field's own convention — term name for the |
| 265 |
* fast-column taxonomies, slug for the term-join ones — so a chosen value drops |
| 266 |
* straight into the matching block attribute the render path already understands. |
| 267 |
* |
| 268 |
* Public because the Elementor Property List widget builds the same initial-filter |
| 269 |
* pickers from this one list, so the two builders can never offer different terms. |
| 270 |
* |
| 271 |
* @return array<int,array{key:string,label:string,options:array<int,array{label:string,value:string}>}> |
| 272 |
*/ |
| 273 |
public static function taxonomy_options(): array { |
| 274 |
if ( ! function_exists( 'get_terms' ) ) { |
| 275 |
return array(); |
| 276 |
} |
| 277 |
$out = array(); |
| 278 |
// One control per catalog field that is a real, registered taxonomy field. |
| 279 |
foreach ( Mlsimport_Page_Block_Search_Fields::catalog() as $key ) { |
| 280 |
$def = Mlsimport_Page_Block_Search_Fields::definition( $key ); |
| 281 |
// Skip non-taxonomy fields and any taxonomy that isn't registered. |
| 282 |
if ( null === $def || 'taxonomy' !== $def['group'] || ! taxonomy_exists( $def['tax'] ) ) { |
| 283 |
continue; |
| 284 |
} |
| 285 |
// Pull every term (including empty ones) so the editor lists all options. |
| 286 |
$terms = get_terms( |
| 287 |
array( |
| 288 |
'taxonomy' => $def['tax'], |
| 289 |
'hide_empty' => false, |
| 290 |
) |
| 291 |
); |
| 292 |
if ( is_wp_error( $terms ) || empty( $terms ) ) { |
| 293 |
continue; |
| 294 |
} |
| 295 |
// Build { label, value } options; the value follows the field's own |
| 296 |
// convention (slug for term-join fields, term name for fast-column ones). |
| 297 |
$options = array(); |
| 298 |
foreach ( $terms as $term ) { |
| 299 |
$value = 'slug' === $def['value'] ? (string) $term->slug : (string) $term->name; |
| 300 |
$options[] = array( 'label' => (string) $term->name, 'value' => $value ); |
| 301 |
} |
| 302 |
$out[] = array( |
| 303 |
'key' => $key, |
| 304 |
'label' => (string) $def['label'], |
| 305 |
'options' => $options, |
| 306 |
); |
| 307 |
} |
| 308 |
return $out; |
| 309 |
} |
| 310 |
|
| 311 |
/** |
| 312 |
* Map block attributes to filter args and render the grid (render_callback). The |
| 313 |
* search_fields attribute is display config (the per-field on/off toggles), not a |
| 314 |
* filter key, so it is injected after atts_to_args (which keeps only filter keys). |
| 315 |
* |
| 316 |
* @param array $attributes Block attributes. |
| 317 |
* @return string |
| 318 |
*/ |
| 319 |
public static function render( $attributes ): string { |
| 320 |
$attributes = (array) $attributes; |
| 321 |
$args = Mlsimport_Standalone_Shortcodes::atts_to_args( $attributes ); |
| 322 |
if ( isset( $attributes['search_fields'] ) && '' !== $attributes['search_fields'] ) { |
| 323 |
$args['search_fields'] = (string) $attributes['search_fields']; |
| 324 |
} |
| 325 |
// fields_per_row is search-form display config (columns per row), not a filter |
| 326 |
// key, so it is injected after atts_to_args (which keeps only filter keys). |
| 327 |
if ( isset( $attributes['fields_per_row'] ) && '' !== $attributes['fields_per_row'] ) { |
| 328 |
$args['fields_per_row'] = (string) $attributes['fields_per_row']; |
| 329 |
} |
| 330 |
return Mlsimport_Standalone_Render::render_grid( $args ); |
| 331 |
} |
| 332 |
|
| 333 |
/** |
| 334 |
* Render the Half Map block through the page-block dispatcher, so its markup, |
| 335 |
* assets and hooks match the shortcode and Elementor widget exactly. The block |
| 336 |
* attributes (filter presets + search_fields + map_side + height) map 1:1 to the |
| 337 |
* dispatcher args. |
| 338 |
* |
| 339 |
* @param array $attributes Block attributes. |
| 340 |
* @return string |
| 341 |
*/ |
| 342 |
public static function render_half_map( $attributes ): string { |
| 343 |
return mlsimport_render_page_block( 'half_map', (array) $attributes ); |
| 344 |
} |
| 345 |
|
| 346 |
/** |
| 347 |
* Render the Search Results block through the page-block dispatcher, so its |
| 348 |
* markup, assets and hooks match the shortcode and Elementor widget exactly. The |
| 349 |
* dispatcher's mlsimport_page_block_results reads the GET search; these attributes |
| 350 |
* are only the refine-bar display config. |
| 351 |
* |
| 352 |
* @param array $attributes Block attributes. |
| 353 |
* @return string |
| 354 |
*/ |
| 355 |
public static function render_results( $attributes ): string { |
| 356 |
return mlsimport_render_page_block( 'property_list_filters', (array) $attributes ); |
| 357 |
} |
| 358 |
|
| 359 |
/** |
| 360 |
* Render the Content Slider block through the page-block dispatcher, so its markup, |
| 361 |
* assets and hooks match the shortcode and Elementor widget exactly. The block |
| 362 |
* attributes (initial-filter presets + how-many + explicit IDs) map 1:1 to the |
| 363 |
* dispatcher args; the slider render maps them through the same atts->args path. |
| 364 |
* |
| 365 |
* @param array $attributes Block attributes. |
| 366 |
* @return string |
| 367 |
*/ |
| 368 |
public static function render_slider( $attributes ): string { |
| 369 |
return mlsimport_render_page_block( 'content_slider', (array) $attributes ); |
| 370 |
} |
| 371 |
|
| 372 |
/** |
| 373 |
* Render the Property List block through the page-block dispatcher, so its markup, |
| 374 |
* assets and hooks match the shortcode and Elementor widget exactly. The block |
| 375 |
* attributes (initial-filter presets + per-page + show-bar + fields-per-row + |
| 376 |
* search_fields) map 1:1 to the dispatcher args. |
| 377 |
* |
| 378 |
* @param array $attributes Block attributes. |
| 379 |
* @return string |
| 380 |
*/ |
| 381 |
public static function render_item_list( $attributes ): string { |
| 382 |
return mlsimport_render_page_block( 'item_list', (array) $attributes ); |
| 383 |
} |
| 384 |
|
| 385 |
/** |
| 386 |
* Render the Map with Listings block through the page-block dispatcher, so its markup, |
| 387 |
* assets and hooks match the shortcode and Elementor widget exactly. The block |
| 388 |
* attributes (initial-filter presets + how-many + explicit IDs) map 1:1 to the |
| 389 |
* dispatcher args. |
| 390 |
* |
| 391 |
* @param array $attributes Block attributes. |
| 392 |
* @return string |
| 393 |
*/ |
| 394 |
public static function render_map( $attributes ): string { |
| 395 |
return mlsimport_render_page_block( 'map_listings', (array) $attributes ); |
| 396 |
} |
| 397 |
|
| 398 |
/** |
| 399 |
* Content Slider attribute schema — every filter key as an optional string (the |
| 400 |
* "Initial filter" panel) plus an explicit-IDs list. limit defaults to 6 (the |
| 401 |
* "How many" control), matching the manifest's slider count default. |
| 402 |
* |
| 403 |
* @return array |
| 404 |
*/ |
| 405 |
private static function slider_attributes(): array { |
| 406 |
// One optional string attribute per filter key; limit defaults to the slider count. |
| 407 |
$attributes = array(); |
| 408 |
foreach ( Mlsimport_Standalone_Shortcodes::filter_keys() as $key ) { |
| 409 |
$attributes[ $key ] = array( |
| 410 |
'type' => 'string', |
| 411 |
'default' => 'limit' === $key ? '6' : '', |
| 412 |
); |
| 413 |
} |
| 414 |
$attributes['ids'] = array( |
| 415 |
'type' => 'string', |
| 416 |
'default' => '', |
| 417 |
); |
| 418 |
return $attributes; |
| 419 |
} |
| 420 |
|
| 421 |
/** |
| 422 |
* Property List attribute schema — every filter key as an optional string (so the |
| 423 |
* Initial filter panel presets and any shortcode-set filter validate) plus the |
| 424 |
* search-bar config: per-page (count), show-bar, fields-per-row and the per-field |
| 425 |
* toggle state (search_fields). Per-page is `count`, so the raw `limit` key is dropped. |
| 426 |
* |
| 427 |
* @return array |
| 428 |
*/ |
| 429 |
private static function item_list_attributes(): array { |
| 430 |
$attributes = array(); |
| 431 |
foreach ( Mlsimport_Standalone_Shortcodes::filter_keys() as $key ) { |
| 432 |
if ( 'limit' === $key ) { |
| 433 |
continue; |
| 434 |
} |
| 435 |
$attributes[ $key ] = array( 'type' => 'string', 'default' => '' ); |
| 436 |
} |
| 437 |
$attributes['count'] = array( 'type' => 'string', 'default' => '12' ); |
| 438 |
$attributes['show_filter_bar'] = array( 'type' => 'string', 'default' => '1' ); |
| 439 |
$attributes['fields_per_row'] = array( 'type' => 'string', 'default' => '4' ); |
| 440 |
$attributes['search_fields'] = array( 'type' => 'string', 'default' => '' ); |
| 441 |
return $attributes; |
| 442 |
} |
| 443 |
|
| 444 |
/** |
| 445 |
* Map with Listings attribute schema — every filter key as an optional string (so the |
| 446 |
* Initial filter panel presets and any shortcode-set filter validate). count and ids |
| 447 |
* stay registered so a shortcode-set value (or a pre-existing saved block) still |
| 448 |
* validates, but both default empty: the editor exposes no How-many / IDs / Order |
| 449 |
* control, because a map plots every match at its coordinates. A non-empty count would |
| 450 |
* inject a pointless limit into the map's data-filters (bounds() ignores it and |
| 451 |
* map_payload() strips it anyway), so it defaults to '' — the map shows them all. |
| 452 |
* |
| 453 |
* @return array |
| 454 |
*/ |
| 455 |
private static function map_attributes(): array { |
| 456 |
$attributes = array(); |
| 457 |
foreach ( Mlsimport_Standalone_Shortcodes::filter_keys() as $key ) { |
| 458 |
if ( 'limit' === $key || 'page' === $key ) { |
| 459 |
continue; |
| 460 |
} |
| 461 |
$attributes[ $key ] = array( 'type' => 'string', 'default' => '' ); |
| 462 |
} |
| 463 |
$attributes['count'] = array( 'type' => 'string', 'default' => '' ); |
| 464 |
$attributes['ids'] = array( 'type' => 'string', 'default' => '' ); |
| 465 |
return $attributes; |
| 466 |
} |
| 467 |
|
| 468 |
/** |
| 469 |
* Search Results attribute schema — only the refine-bar display config (the |
| 470 |
* filters themselves come from the request, not saved attributes). Mirrors the |
| 471 |
* manifest defaults so an unconfigured block shows the bar, 4-up, all fields. |
| 472 |
* |
| 473 |
* @return array |
| 474 |
*/ |
| 475 |
private static function results_attributes(): array { |
| 476 |
return array( |
| 477 |
'count' => array( 'type' => 'string', 'default' => '12' ), |
| 478 |
'show_filter_bar' => array( 'type' => 'string', 'default' => '1' ), |
| 479 |
'fields_per_row' => array( 'type' => 'string', 'default' => '4' ), |
| 480 |
'search_fields' => array( 'type' => 'string', 'default' => '' ), |
| 481 |
); |
| 482 |
} |
| 483 |
|
| 484 |
/** |
| 485 |
* Block attribute schema — every filter key as an optional string, plus |
| 486 |
* search_fields (the per-field on/off toggle state: a comma list of the search |
| 487 |
* fields to show; empty = show all). limit defaults to 12 (the "Properties to |
| 488 |
* display" control), matching the render-grid per-page default. |
| 489 |
* |
| 490 |
* @return array |
| 491 |
*/ |
| 492 |
private static function attributes(): array { |
| 493 |
$attributes = array(); |
| 494 |
foreach ( Mlsimport_Standalone_Shortcodes::filter_keys() as $key ) { |
| 495 |
$attributes[ $key ] = array( |
| 496 |
'type' => 'string', |
| 497 |
'default' => 'limit' === $key ? '12' : '', |
| 498 |
); |
| 499 |
} |
| 500 |
$attributes['search_fields'] = array( |
| 501 |
'type' => 'string', |
| 502 |
'default' => '', |
| 503 |
); |
| 504 |
// Search-bar columns (the "Search fields per row" control); 4-up by default for |
| 505 |
// the full-width listings bar. Half Map overrides this to 3 (its narrow pane). |
| 506 |
$attributes['fields_per_row'] = array( |
| 507 |
'type' => 'string', |
| 508 |
'default' => '4', |
| 509 |
); |
| 510 |
return $attributes; |
| 511 |
} |
| 512 |
|
| 513 |
/** |
| 514 |
* Half Map attribute schema — the listings schema plus the two Layout-panel |
| 515 |
* controls (map_side, height). Defaults mirror the manifest so an unconfigured |
| 516 |
* block renders a right-side, full-height map. |
| 517 |
* |
| 518 |
* @return array |
| 519 |
*/ |
| 520 |
private static function half_map_attributes(): array { |
| 521 |
$attributes = self::attributes(); |
| 522 |
$attributes['fields_per_row'] = array( |
| 523 |
'type' => 'string', |
| 524 |
'default' => '3', |
| 525 |
); |
| 526 |
$attributes['map_side'] = array( |
| 527 |
'type' => 'string', |
| 528 |
'default' => 'right', |
| 529 |
); |
| 530 |
$attributes['height'] = array( |
| 531 |
'type' => 'string', |
| 532 |
'default' => '100vh', |
| 533 |
); |
| 534 |
return $attributes; |
| 535 |
} |
| 536 |
|
| 537 |
/** |
| 538 |
* The search form's toggleable fields in render order: the keyword box, every |
| 539 |
* searchable catalog field, then the sort control — each as { key, label } so the |
| 540 |
* editor can build one on/off toggle per field. Mirrors search-form.php. |
| 541 |
* |
| 542 |
* @return array<int,array{key:string,label:string}> |
| 543 |
*/ |
| 544 |
private static function search_fields(): array { |
| 545 |
$labels = array_merge( |
| 546 |
array( 'keywords' => __( 'Keywords', 'mlsimport' ) ), |
| 547 |
Mlsimport_Page_Block_Search_Fields::labels(), |
| 548 |
array( 'sort' => __( 'Sort by', 'mlsimport' ) ) |
| 549 |
); |
| 550 |
$fields = array(); |
| 551 |
foreach ( $labels as $key => $label ) { |
| 552 |
$fields[] = array( 'key' => $key, 'label' => $label ); |
| 553 |
} |
| 554 |
return $fields; |
| 555 |
} |
| 556 |
} |
| 557 |
|