| 1 |
<?php |
| 2 |
/** |
| 3 |
* Reusable searchable multi-select for picking terms from a plugin taxonomy. |
| 4 |
* |
| 5 |
* Anywhere the plugin needs the operator to pick one or more terms from a |
| 6 |
* registered taxonomy (the standalone mlsimport_* taxonomies, or any other), |
| 7 |
* render this instead of hand-rolling a <select>. It emits the same markup the |
| 8 |
* Import Task City/County picker already uses — a chip row, a live filter input, |
| 9 |
* and a native <select multiple> — so the existing mlsimport-searchable-select.js |
| 10 |
* behaviour (live filter, click-to-toggle without Ctrl, removable chips) and the |
| 11 |
* styles in mlsimport-admin.css apply with nothing new to ship. |
| 12 |
* |
| 13 |
* The native <select multiple> is the source of truth: it submits the usual |
| 14 |
* name[]-array payload, so save handlers read $_POST[ name ] as an array. |
| 15 |
* |
| 16 |
* @package Mlsimport |
| 17 |
*/ |
| 18 |
|
| 19 |
if ( ! defined( 'ABSPATH' ) ) { |
| 20 |
exit; |
| 21 |
} |
| 22 |
|
| 23 |
/** |
| 24 |
* Renders a searchable, chip-backed multi-select of a taxonomy's terms. |
| 25 |
*/ |
| 26 |
class Mlsimport_Term_Select { |
| 27 |
|
| 28 |
/** |
| 29 |
* Handle for the shared dropdown multi-select component (the same one the |
| 30 |
* standalone front-end search form uses). |
| 31 |
*/ |
| 32 |
const SCRIPT_HANDLE = 'mlsimport-multiselect'; |
| 33 |
|
| 34 |
/** |
| 35 |
* Ensure the dropdown multi-select component (JS + CSS) is enqueued. Idempotent |
| 36 |
* — call it from any screen that renders a term select. |
| 37 |
* |
| 38 |
* @return void |
| 39 |
*/ |
| 40 |
public static function enqueue(): void { |
| 41 |
// Plugin base URL + version for cache-busting (constants when the plugin booted). |
| 42 |
$base = defined( 'MLSIMPORT_PLUGIN_URL' ) ? MLSIMPORT_PLUGIN_URL : plugin_dir_url( dirname( __DIR__ ) . '/mlsimport.php' ); |
| 43 |
$ver = defined( 'MLSIMPORT_VERSION' ) ? MLSIMPORT_VERSION : false; |
| 44 |
|
| 45 |
// Register the shared JS/CSS once (guarded so repeated calls don't re-register). |
| 46 |
if ( ! wp_script_is( self::SCRIPT_HANDLE, 'registered' ) ) { |
| 47 |
wp_register_script( self::SCRIPT_HANDLE, $base . 'public/js/mlsimport-multiselect.js', array(), $ver, true ); |
| 48 |
} |
| 49 |
if ( ! wp_style_is( self::SCRIPT_HANDLE, 'registered' ) ) { |
| 50 |
wp_register_style( self::SCRIPT_HANDLE, $base . 'public/css/mlsimport-multiselect.css', array(), $ver ); |
| 51 |
} |
| 52 |
// Enqueue the component's assets for this request. |
| 53 |
wp_enqueue_script( self::SCRIPT_HANDLE ); |
| 54 |
wp_enqueue_style( self::SCRIPT_HANDLE ); |
| 55 |
} |
| 56 |
|
| 57 |
/** |
| 58 |
* Build the term multi-select as an escaped HTML string (the caller echoes it). |
| 59 |
* |
| 60 |
* @param array $args { |
| 61 |
* @type string $taxonomy Required. Taxonomy slug to list terms from. |
| 62 |
* @type string $name Required. Field name; submitted as name[]. |
| 63 |
* @type array $selected Currently-selected values (term IDs, or slugs |
| 64 |
* when value_field is 'slug'). Default array(). |
| 65 |
* @type string $label Optional label shown above the control. |
| 66 |
* @type string $value_field Option value: 'term_id' (default), 'slug' or 'name'. |
| 67 |
* @type string $placeholder Empty-state text shown on the control. |
| 68 |
* @type string $id Select id (auto-derived when omitted). |
| 69 |
* } |
| 70 |
* @return string Escaped markup, or '' when the taxonomy/name is invalid. |
| 71 |
*/ |
| 72 |
public static function render( array $args ): string { |
| 73 |
$args = wp_parse_args( |
| 74 |
$args, |
| 75 |
array( |
| 76 |
'taxonomy' => '', |
| 77 |
'name' => '', |
| 78 |
'selected' => array(), |
| 79 |
'label' => '', |
| 80 |
'value_field' => 'term_id', |
| 81 |
'placeholder' => __( 'Any', 'mlsimport' ), |
| 82 |
'id' => '', |
| 83 |
) |
| 84 |
); |
| 85 |
|
| 86 |
// Required args must be present and the taxonomy real, else render nothing. |
| 87 |
$taxonomy = (string) $args['taxonomy']; |
| 88 |
$name = (string) $args['name']; |
| 89 |
if ( '' === $taxonomy || '' === $name || ! taxonomy_exists( $taxonomy ) ) { |
| 90 |
return ''; |
| 91 |
} |
| 92 |
|
| 93 |
// Pull every term (including empty ones) to build the option list. |
| 94 |
$terms = get_terms( |
| 95 |
array( |
| 96 |
'taxonomy' => $taxonomy, |
| 97 |
'hide_empty' => false, |
| 98 |
) |
| 99 |
); |
| 100 |
// No terms (or a lookup error): nothing to select from. |
| 101 |
if ( is_wp_error( $terms ) || empty( $terms ) ) { |
| 102 |
return ''; |
| 103 |
} |
| 104 |
|
| 105 |
// Which term property becomes the option value (default term_id); coerce the |
| 106 |
// selected values to strings for the in_array comparison below. |
| 107 |
$value_field = in_array( $args['value_field'], array( 'slug', 'name' ), true ) ? $args['value_field'] : 'term_id'; |
| 108 |
$selected = array_map( 'strval', (array) $args['selected'] ); |
| 109 |
// Caller-supplied id, or a stable one derived from taxonomy + field name. |
| 110 |
$id = '' !== $args['id'] ? (string) $args['id'] : 'mlsimport-term-select-' . sanitize_html_class( $taxonomy . '-' . $name ); |
| 111 |
|
| 112 |
// Buffer the markup so the method can return it as a string. |
| 113 |
ob_start(); |
| 114 |
?> |
| 115 |
<div class="mlsimport-term-select"> |
| 116 |
<?php if ( '' !== (string) $args['label'] ) : ?> |
| 117 |
<label for="<?php echo esc_attr( $id ); ?>"><?php echo esc_html( (string) $args['label'] ); ?></label> |
| 118 |
<?php endif; ?> |
| 119 |
<select |
| 120 |
class="mlsimport-multiselect" |
| 121 |
id="<?php echo esc_attr( $id ); ?>" |
| 122 |
name="<?php echo esc_attr( $name ); ?>[]" |
| 123 |
multiple |
| 124 |
data-placeholder="<?php echo esc_attr( (string) $args['placeholder'] ); ?>" |
| 125 |
> |
| 126 |
<?php |
| 127 |
// One <option> per term; its value comes from the chosen value_field. |
| 128 |
foreach ( $terms as $term ) { |
| 129 |
if ( 'slug' === $value_field ) { |
| 130 |
$value = (string) $term->slug; |
| 131 |
} elseif ( 'name' === $value_field ) { |
| 132 |
$value = (string) $term->name; |
| 133 |
} else { |
| 134 |
$value = (string) $term->term_id; |
| 135 |
} |
| 136 |
// Mark the option selected when its value is in the selected set. |
| 137 |
printf( |
| 138 |
'<option value="%s"%s>%s</option>', |
| 139 |
esc_attr( $value ), |
| 140 |
selected( in_array( $value, $selected, true ), true, false ), |
| 141 |
esc_html( $term->name ) |
| 142 |
); |
| 143 |
} |
| 144 |
?> |
| 145 |
</select> |
| 146 |
</div> |
| 147 |
<?php |
| 148 |
return (string) ob_get_clean(); |
| 149 |
} |
| 150 |
} |
| 151 |
|