| 1 |
<?php |
| 2 |
/** |
| 3 |
* Standalone listings search form. Theme-overridable: copy to your theme's |
| 4 |
* mlsimport/search-form.php. Field name attributes map 1:1 to filter params. |
| 5 |
* Receives $args (current filter values) for pre-filling. |
| 6 |
* |
| 7 |
* @package Mlsimport |
| 8 |
*/ |
| 9 |
|
| 10 |
if ( ! defined( 'ABSPATH' ) ) { |
| 11 |
exit; |
| 12 |
} |
| 13 |
|
| 14 |
// Current filter values passed in for pre-filling; default to an empty set. |
| 15 |
$mli_args = isset( $args ) && is_array( $args ) ? $args : array(); |
| 16 |
// Helper: return a single scalar arg as a string (array-valued args yield ''). |
| 17 |
$mli_val = function ( $key ) use ( $mli_args ) { |
| 18 |
return isset( $mli_args[ $key ] ) && ! is_array( $mli_args[ $key ] ) ? (string) $mli_args[ $key ] : ''; |
| 19 |
}; |
| 20 |
|
| 21 |
// Field visibility (the listings block/shortcode's per-field on/off toggles): null |
| 22 |
// means show every field (default), else only the listed keys appear. Keywords and |
| 23 |
// Sort gate on their own keys; each catalog field gates on its catalog key. |
| 24 |
$mli_visible = class_exists( 'Mlsimport_Standalone_Render' ) ? Mlsimport_Standalone_Render::visible_fields( $mli_args ) : null; |
| 25 |
// Helper: is this field visible? null visibility means show everything. |
| 26 |
$mli_show = function ( $key ) use ( $mli_visible ) { |
| 27 |
return null === $mli_visible || in_array( $key, $mli_visible, true ); |
| 28 |
}; |
| 29 |
|
| 30 |
// Search fields per row (the listings/half-map "fields per row" setting): a CSS |
| 31 |
// custom property the stylesheet turns into the grid column count. Unset/invalid |
| 32 |
// emits nothing, so the listings/half-map CSS fallback column count applies. |
| 33 |
$mli_cols = function_exists( 'mlsimport_search_cols_style' ) ? mlsimport_search_cols_style( $mli_val( 'fields_per_row' ) ) : ''; |
| 34 |
?> |
| 35 |
<form class="mlsimport-search" role="search"<?php echo $mli_cols; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- integer CSS var built internally. ?>> |
| 36 |
<?php if ( $mli_show( 'keywords' ) ) : /* free-text keywords input, gated on its own key */ ?> |
| 37 |
<label class="mlsimport-search__field"> |
| 38 |
<?php esc_html_e( 'Keywords', 'mlsimport' ); ?> |
| 39 |
<input type="text" name="keywords" value="<?php echo esc_attr( $mli_val( 'keywords' ) ); ?>" placeholder="<?php esc_attr_e( 'Pool, lake view…', 'mlsimport' ); ?>"> |
| 40 |
</label> |
| 41 |
<?php endif; ?> |
| 42 |
|
| 43 |
<?php |
| 44 |
// Every searchable field — taxonomies then fast-table columns (no geo) — from |
| 45 |
// the shared catalog, so this form and the Search Form block never drift. |
| 46 |
if ( class_exists( 'Mlsimport_Page_Block_Search_Fields' ) && function_exists( 'mlsimport_render_search_field' ) ) { |
| 47 |
// An explicit selection also sets the ORDER, so a form can lead with the fields |
| 48 |
// that matter to this site — e.g. a hero search offering Location, Type and |
| 49 |
// Price should hand the visitor those same three first on the results page. |
| 50 |
// Without a selection the shared catalog order applies (taxonomies, then |
| 51 |
// fast-table columns), which is the long-standing default. |
| 52 |
$mli_order = null !== $mli_visible |
| 53 |
? $mli_visible |
| 54 |
: Mlsimport_Page_Block_Search_Fields::catalog(); |
| 55 |
foreach ( $mli_order as $mli_field ) { |
| 56 |
// Skip fields toggled off for this form. |
| 57 |
if ( ! $mli_show( $mli_field ) ) { |
| 58 |
continue; |
| 59 |
} |
| 60 |
// Fetch the field's definition; render it when the catalog knows the field. |
| 61 |
$mli_def = Mlsimport_Page_Block_Search_Fields::definition( $mli_field ); |
| 62 |
if ( null !== $mli_def ) { |
| 63 |
echo mlsimport_render_search_field( $mli_def, $mli_args, '', 'mlsimport-search__field' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped within. |
| 64 |
} |
| 65 |
} |
| 66 |
} |
| 67 |
?> |
| 68 |
|
| 69 |
<?php |
| 70 |
// Sort is NOT a filter field: it renders in the results toolbar (count left / |
| 71 |
// sort right) above the grid — see Mlsimport_Standalone_Render::render_grid(). It |
| 72 |
// still drives the same AJAX repaint; mlsimport-listings.js reads its |
| 73 |
// select[name="orderby"] from the toolbar, outside this form. |
| 74 |
|
| 75 |
// Carry every ACTIVE filter that has no visible field as a hidden input. |
| 76 |
// |
| 77 |
// This form is the AJAX layer's only state: mlsimport-listings.js builds the request |
| 78 |
// from its FormData and nothing else. So a filter that is not re-submitted here is |
| 79 |
// DROPPED the first time the visitor refines or paginates — page 1 is server-rendered |
| 80 |
// and looks right, page 2 silently widens. That is reachable whenever a block sets an |
| 81 |
// initial-filter preset whose search field is switched off ("Miami condos", where the |
| 82 |
// City field is hidden so visitors cannot change it) or has no search field at all |
| 83 |
// (the map box). It used to be patched one key at a time — `limit` and `agent` each had |
| 84 |
// their own hardcoded input — which fixed those two and left every other filter broken. |
| 85 |
// |
| 86 |
// One rule instead: a filter is either editable in the bar, or it rides along hidden. |
| 87 |
// For each active-but-not-editable filter, emit hidden input(s) so it rides along on submit. |
| 88 |
foreach ( mlsimport_search_form_hidden_args( $mli_args, $mli_visible ) as $mli_name => $mli_value ) : |
| 89 |
// Multi-value filters (arrays) get one hidden input per value. |
| 90 |
foreach ( (array) $mli_value as $mli_item ) : |
| 91 |
?> |
| 92 |
<input type="hidden" name="<?php echo esc_attr( $mli_name ); ?>" value="<?php echo esc_attr( (string) $mli_item ); ?>"> |
| 93 |
<?php |
| 94 |
endforeach; |
| 95 |
endforeach; |
| 96 |
?> |
| 97 |
|
| 98 |
<button type="submit" class="mlsimport-search__submit"><?php esc_html_e( 'Search', 'mlsimport' ); ?></button> |
| 99 |
</form> |
| 100 |
|