id = sanitize_key($id);
$this->taxonomy = jvbCheckBase($taxonomy);
$this->name = jvbNoBase($taxonomy);
$this->title = JVB_TAXONOMY[$this->name]['plural'];
$this->base = $config['base']??'';
$this->config = wp_parse_args($config, [
'types' => false, // for feed block implementation
'max' => 0, // 0 = unlimited
'search' => true,
'label' => $this->name,
'icon' => false,
'autocomplete' => false,
'createNew' => false,
'required' => false,
'hidden' => false,
'base' => '',
'name' => $this->taxonomy,
'update' => true, // Whether to update on close
]);
$this->plural = JVB_TAXONOMY[$taxonomy]['plural'];
$this->singular = JVB_TAXONOMY[$taxonomy]['singular'];
}
/**
* Get the full path for a term (for hierarchical taxonomies)
*
* @param WP_Term $term The term object
* @param bool $returnArray if true, returns the array. If false, a string of terms separated by ' → '
* @return string|array An array of terms or the full term path
*/
public static function getTermPath(WP_Term $term, bool $returnArray = false): string|array {
if (!is_taxonomy_hierarchical($term->taxonomy)) {
return html_entity_decode($term->name);
}
$path = [];
$currentTerm = $term;
while ($currentTerm) {
array_unshift($path, html_entity_decode($currentTerm->name));
if ($currentTerm->parent) {
$currentTerm = get_term($currentTerm->parent);
if (is_wp_error($currentTerm)) {
break;
}
} else {
break;
}
}
return ($returnArray) ? $path : implode(' → ', $path);
}
/**
* Get the single modal HTML structure
*/
public static function outputSelectorModal(): string {
ob_start();
?>
{ loading items }
{ nothing found }
config) && $this->config['output'] === 'minimal') {
return $this->renderTaxonomyToggle($selected, $extra);
}
// Build data attributes
$dataAttrs = $this->buildDataAttributes($selected);
$hasAutocomplete = ($this->config['autocomplete']) ? ' data-autocomplete' : '';
// Hidden attribute
$hidden = $this->config['hidden'] ? ' hidden' : '';
ob_start();
?>
>
renderSelectedTerm($termId);
endforeach;
$selectedItems = ob_get_clean();
endif;
?>
=$selectedItems?>
= $extra ?>
%s%s',
JVB_TAXONOMY[$this->name]['icon'],
$this->name,
$this->singular,
$this->plural,
$this->singular,
jvbIcon($this->config['icon']),
$this->singular
);
}
/**
* Build data attributes string for the toggle button
*/
private function buildDataAttributes(array $selected): string {
$attrs = [];
// Update behavior
if (!$this->config['update']) {
$attrs[] = 'data-update="false"';
}
// Max selection
if ($this->config['max'] > 0) {
$attrs[] = 'data-max="' . esc_attr($this->config['max']) . '"';
}
// Search capability
if ($this->config['search']) {
$attrs[] = 'data-search';
}
// Create new capability
if ($this->config['createNew']) {
$attrs[] = 'data-creatable';
}
// Required
if ($this->config['required']) {
$attrs[] = 'data-required';
}
// Post types filter (for feed blocks)
if ($this->config['types'] && is_array($this->config['types'])) {
$attrs[] = 'data-for="' . esc_attr(implode(',', $this->config['types'])) . '"';
}
// Selected items
if (!empty($selected)) {
$attrs[] = 'data-selected="' . esc_attr(implode(',', $selected)) . '"';
}
return implode(' ', $attrs);
}
/**
* Render a single selected term
*/
private function renderSelectedTerm(int $termId): void {
$term = get_term($termId, $this->taxonomy);
if (!$term || is_wp_error($term)) {
return;
}
$termPath = self::getTermPath($term);
?>
= esc_html($termPath) ?>