Añadir un Filtro de Taxonomía a la Lista de Administración para un Tipo de Publicación Personalizado
He creado un Tipo de Publicación Personalizado llamado 'listing'
y añadido una Taxonomía Personalizada llamada 'businesses'
. Me gustaría añadir una lista desplegable de Businesses a la lista de administración para los Listings.
Esto es lo que esta funcionalidad muestra en la lista de administración para Posts (Me gustaría lo mismo para mi Tipo de Publicación Personalizado):
Aquí está mi código actual (Y aquí está el mismo código en Gist.):
<?php
/*
Plugin Name: Listing Content Item
Plugin URI:
Description:
Author:
Version: 1.0
Author URI:
*/
class Listing {
var $meta_fields = array("list-address1","list-address2","list-country","list-province","list-city","list-postcode","list-firstname","list-lastname","list-website","list-mobile","list-phone","list-fax","list-email", "list-profile", "list-distributionrange", "list-distributionarea");
public function loadStyleScripts() {
$eventsURL = trailingslashit( WP_PLUGIN_URL ) . trailingslashit( plugin_basename( dirname( __FILE__ ) ) ) . 'css/';
wp_enqueue_style('listing-style', $eventsURL.'listing.css');
}
function Listing() {
// Register custom post types
register_post_type('listing', array(
'labels' => array(
'name' => __('Listings'), 'singular_name' => __( 'Listing' ),
'add_new' => __( 'Add Listing' ),
'add_new_item' => __( 'Add New Listing' ),
'edit' => __( 'Edit' ),
'edit_item' => __( 'Edit Listing' ),
'new_item' => __( 'New Listing' ),
'view' => __( 'View Listing' ),
'view_item' => __( 'View Listing' ),
'search_items' => __( 'Search Listings' ),
'not_found' => __( 'No listings found' ),
'not_found_in_trash' => __( 'No listings found in Trash' ),
'parent' => __( 'Parent Listing' ),
),
'singular_label' => __('Listing'),
'public' => true,
'show_ui' => true, // UI in admin panel
'_builtin' => false, // It's a custom post type, not built in
'_edit_link' => 'post.php?post=%d',
'capability_type' => 'post',
'hierarchical' => false,
'rewrite' => array("slug" => "listings"), // Permalinks
'query_var' => "listings", // This goes to the WP_Query schema
'supports' => array('title','editor')
));
add_filter("manage_edit-listing_columns", array(&$this, "edit_columns"));
add_action("manage_posts_custom_column", array(&$this, "custom_columns"));
// Register custom taxonomy
#Businesses
register_taxonomy("businesses", array("listing"), array(
"hierarchical" => true,
"label" => "Listing Categories",
"singular_label" => "Listing Categorie",
"rewrite" => true,
));
# Region
register_taxonomy("regions", array("listing"), array(
'labels' => array(
'search_items' => __( 'Search Regions' ),
'popular_items' => __( 'Popular Regions' ),
'all_items' => __( 'All Regions' ),
'parent_item' => null,
'parent_item_colon' => null,
'edit_item' => __( 'Edit Region' ),
'update_item' => __( 'Update Region' ),
'add_new_item' => __( 'Add New Region' ),
'new_item_name' => __( 'New Region Name' ),
'separate_items_with_commas' => __( 'Separate regions with commas' ),
'add_or_remove_items' => __( 'Add or remove regions' ),
'choose_from_most_used' => __( 'Choose from the most used regions' ),
),
"hierarchical" => false,
"label" => "Listing Regions",
"singular_label" => "Listing Region",
"rewrite" => true,
));
# Member Organizations
register_taxonomy("organizations", array("listing"), array(
'labels' => array(
'search_items' => __( 'Search Member Organizations' ),
'popular_items' => __( 'Popular Member Organizations' ),
'all_items' => __( 'All Member Organizations' ),
'parent_item' => null,
'parent_item_colon' => null,
'edit_item' => __( 'Edit Member Organization' ),
'update_item' => __( 'Update Member Organization' ),
'add_new_item' => __( 'Add New Member Organization' ),
'new_item_name' => __( 'New Member Organization Name' ),
'separate_items_with_commas' => __( 'Separate member organizations with commas' ),
'add_or_remove_items' => __( 'Add or remove member organizations' ),
'choose_from_most_used' => __( 'Choose from the most used member organizations' ),
),
"hierarchical" => false,
"label" => "Member Organizations",
"singular_label" => "Member Organization",
"rewrite" => true,
));
# Retail Products
register_taxonomy("retails", array("listing"), array(
'labels' => array(
'search_items' => __( 'Search Retail Products' ),
'popular_items' => __( 'Popular Retail Products' ),
'all_items' => __( 'All Retail Products' ),
'parent_item' => null,
'parent_item_colon' => null,
'edit_item' => __( 'Edit Retail Product' ),
'update_item' => __( 'Update Retail Product' ),
'add_new_item' => __( 'Add New Retail Product' ),
'new_item_name' => __( 'New Retail Product Name' ),
'separate_items_with_commas' => __( 'Separate retail products with commas' ),
'add_or_remove_items' => __( 'Add or remove retail products' ),
'choose_from_most_used' => __( 'Choose from the most used retail products' ),
),
"hierarchical" => false,
"label" => "Retail Products",
"singular_label" => "Retail Product",
"rewrite" => true,
));
# Farming Practices
register_taxonomy("practices", array("listing"), array(
'labels' => array(
'search_items' => __( 'Search Farming Practices' ),
'popular_items' => __( 'Popular Farming Practices' ),
'all_items' => __( 'All Farming Practices' ),
'parent_item' => null,
'parent_item_colon' => null,
'edit_item' => __( 'Edit Farming Practice' ),
'update_item' => __( 'Update Farming Practice' ),
'add_new_item' => __( 'Add New Farming Practice' ),
'new_item_name' => __( 'New Farming Practice Name' ),
'separate_items_with_commas' => __( 'Separate farming practices with commas' ),
'add_or_remove_items' => __( 'Add or remove farming practices' ),
'choose_from_most_used' => __( 'Choose from the most used farming practices' ),
),
"hierarchical" => false,
"label" => "Farming Practices",
"singular_label" => "Farming Practice",
"rewrite" => true,
));
# Products
register_taxonomy("products", array("listing"), array(
'labels' => array(
'search_items' => __( 'Search Products' ),
'popular_items' => __( 'Popular Products' ),
'all_items' => __( 'All Products' ),
'parent_item' => null,
'parent_item_colon' => null,
'edit_item' => __( 'Edit Product' ),
'update_item' => __( 'Update Product' ),
'add_new_item' => __( 'Add New Product' ),
'new_item_name' => __( 'New Product Name' ),
'separate_items_with_commas' => __( 'Separate products with commas' ),
'add_or_remove_items' => __( 'Add or remove products' ),
'choose_from_most_used' => __( 'Choose from the most used products' ),
),
"hierarchical" => false,
"label" => "Products",
"singular_label" => "Product",
"rewrite" => true,
));
// Admin interface init
add_action("admin_init", array(&$this, "admin_init"));
add_action("template_redirect", array(&$this, 'template_redirect'));
// Insert post hook
add_action("wp_insert_post", array(&$this, "wp_insert_post"), 10, 2);
}
function edit_columns($columns) {
$columns = array(
"cb" => "<input type=\"checkbox\" />",
"title" => "Business Name",
"description" => "Description",
"list-personal" => "Personal Information",
"list-location" => "Location",
"list-categorie" => "Categorie",
);
return $columns;
}
function custom_columns($column) {
global $post;
switch ($column) {
case "description":
the_excerpt();
break;
case "list-personal":
$custom = get_post_custom();
if(isset($custom["list-firstname"][0])) echo $custom["list-firstname"][0]."<br />";
if(isset($custom["list-lastname"][0])) echo $custom["list-lastname"][0]."<br />";
if(isset($custom["list-email"][0])) echo $custom["list-email"][0]."<br />";
if(isset($custom["list-website"][0])) echo $custom["list-website"][0]."<br />";
if(isset($custom["list-phone"][0])) echo $custom["list-phone"][0]."<br />";
if(isset($custom["list-mobile"][0])) echo $custom["list-mobile"][0]."<br />";
if(isset($custom["list-fax"][0])) echo $custom["list-fax"][0];
break;
case "list-location":
$custom = get_post_custom();
if(isset($custom["list-address1"][0])) echo $custom["list-address1"][0]."<br />";
if(isset($custom["list-address2"][0])) echo $custom["list-address2"][0]."<br />";
if(isset($custom["list-city"][0])) echo $custom["list-city"][0]."<br />";
if(isset($custom["list-province"][0])) echo $custom["list-province"][0]."<br />";
if(isset($custom["list-postcode"][0])) echo $custom["list-postcode"][0]."<br />";
if(isset($custom["list-country"][0])) echo $custom["list-country"][0]."<br />";
if(isset($custom["list-profile"][0])) echo $custom["list-profile"][0]."<br />";
if(isset($custom["list-distributionrange"][0])) echo $custom["list-distributionrange"][0]."<br />";
if(isset($custom["list-distributionarea"][0])) echo $custom["list-distributionarea"][0];
break;
case "list-categorie":
$speakers = get_the_terms(0, "businesses");
$speakers_html = array();
if(is_array($speakers)) {
foreach ($speakers as $speaker)
array_push($speakers_html, '<a href="' . get_term_link($speaker->slug, 'businesses') . '">' . $speaker->name . '</a>');
echo implode($speakers_html, ", ");
}
break;
}
}
// Template selection
function template_redirect() {
global $wp;
if (isset($wp->query_vars["post_type"]) && ($wp->query_vars["post_type"] == "listing")) {
include(STYLESHEETPATH . "/listing.php");
die();
}
}
// When a post is inserted or updated
function wp_insert_post($post_id, $post = null) {
if ($post->post_type == "listing") {
// Loop through the POST data
foreach ($this->meta_fields as $key) {
$value = @$_POST[$key];
if (empty($value)) {
delete_post_meta($post_id, $key);
continue;
}
// If value is a string it should be unique
if (!is_array($value)) {
// Update meta
if (!update_post_meta($post_id, $key, $value)) {
// Or add the meta data
add_post_meta($post_id, $key, $value);
}
}
else
{
// If passed along is an array, we should remove all previous data
delete_post_meta($post_id, $key);
// Loop through the array adding new values to the post meta as different entries with the same name
foreach ($value as $entry)
add_post_meta($post_id, $key, $entry);
}
}
}
}
function admin_init() {
// Custom meta boxes for the edit listing screen
add_meta_box("list-pers-meta", "Personal Information", array(&$this, "meta_personal"), "listing", "normal", "low");
add_meta_box("list-meta", "Location", array(&$this, "meta_location"), "listing", "normal", "low");
}
function meta_personal() {
global $post;
$custom = get_post_custom($post->ID);
if(isset($custom["list-firstname"][0])) $first_name = $custom["list-firstname"][0];else $first_name = '';
if(isset($custom["list-lastname"][0])) $last_name = $custom["list-lastname"][0];else $last_name = '';
if(isset($custom["list-website"][0])) $website = $custom["list-website"][0];else $website = '';
if(isset($custom["list-phone"][0])) $phone = $custom["list-phone"][0];else $phone = '';
if(isset($custom["list-mobile"][0])) $mobile = $custom["list-mobile"][0];else $mobile = '';
if(isset($custom["list-fax"][0])) $fax = $custom["list-fax"][0];else $fax = '';
if(isset($custom["list-email"][0])) $email = $custom["list-email"][0];else $email = '';
?>
<div class="personal">
<table border="0" id="personal">
<tr><td class="personal_field"><label>Firstname:</label></td><td class="personal_input"><input name="list-firstname" value="<?php echo $first_name; ?>" /></td></tr>
<tr><td class="personal_field"><label>Lastname:</label></td><td class="personal_input"><input name="list-lastname" value="<?php echo $last_name; ?>" /></td></tr>
<tr><td class="personal_field"><label>Email:</label></td><td class="personal_input"><input name="list-email" value="<?php echo $email; ?>" size="40"/></td></tr>
<tr><td class="personal_field"><label>Website:</label></td><td class="personal_input"><input name="list-website" value="<?php echo $website; ?>" size="40"/></td></tr>
<tr><td class="personal_field"><label>Phone:</label></td><td class="personal_input"><input name="list-phone" value="<?php echo $phone; ?>" /></td></tr>
<tr><td class="personal_field"><label>Mobile:</label></td><td class="personal_input"><input name="list-mobile" value="<?php echo $mobile; ?>" /></td></tr>
<tr><td class="personal_field"><label>Fax:</label></td><td class="personal_input"><input name="list-fax" value="<?php echo $fax; ?>" /></td></tr>
</table>
</div>
<?php
}
// Admin post meta contents
function meta_location() {
global $post;
$custom = get_post_custom($post->ID);
if(isset($custom["list-address1"])) $address1 = $custom["list-address1"][0];else $address1 = '';
if(isset($custom["list-address2"])) $address2 = $custom["list-address2"][0];else $address2 = '';
if(isset($custom["list-country"])) $country = $custom["list-country"][0];else $country = '';
if(isset($custom["list-province"])) $province = $custom["list-province"][0];else $province = '';
if(isset($custom["list-city"])) $city = $custom["list-city"][0];else $city = '';
if(isset($custom["list-postcode"])) $post_code = $custom["list-postcode"][0];else $post_code = '';
if(isset($custom["list-profile"])) $profile = $custom["list-profile"][0];else $profile = '';
if(isset($custom["list-distributionrange"])) $distribution_range = $custom["list-distributionrange"][0];else $distribution_range = '';
if(isset($custom["list-distributionarea"])) $distribution_area = $custom["list-distributionarea"][0];else $ddistribution_area = '';
?>
<div class="location">
<table border="0" id="location">
<tr><td class="location_field"><label>Address 1:</label></td><td class="location_input"><input name="list-address1" value="<?php echo $address1; ?>" size="60" /></td></tr>
<tr><td class="location_field"><label>Address 2:</label></td><td class="location_input"><input name="list-address2" value="<?php echo $address2; ?>" size="60" /></td></tr>
<tr><td class="location_field"><label>City:</label></td><td class="location_input"><input name="list-city" value="<?php echo $city; ?>" /></td></tr>
<tr><td class="location_field"><label>Province:</label></td><td class="location_input"><input name="list-province" value="Ontario" readonly /></td></tr>
<tr><td class="location_field"><label>Postal Code:</label></td><td class="location_input"><input name="list-postcode" value="<?php echo $post_code; ?>" /></td></tr>
<tr><td class="location_field"><label>Country:</label></td><td class="location_input"><input name="list-country" value="Canada" readonly /></td></tr>
<tr><td class="location_field"><label>Profile:</label></td><td class="location_input"><input name="list-profile" value="<?php echo $profile; ?>" size="60" /></td></tr>
<tr><td class="location_field"><label>Distribution Range:</label></td><td class="location_input"><input name="list-distributionrange" value="<?php echo $distribution_range; ?>" size="60" /></td></tr>
<tr><td class="location_field"><label>Distribution Area:</label></td><td class="location_input"><input name="list-distributionarea" value="<?php echo $distribution_area; ?>" size="60" /></td></tr>
</table>
</div>
<?php
}
}
// Initiate the plugin
add_action("init", "ListingInit");
function ListingInit() {
global $listing;
$listing = new Listing();
$add_css = $listing->loadStyleScripts();
}
¿Cómo puedo añadir una lista desplegable de Businesses a la lista de administración para los Listings?

ACTUALIZACIÓN Julio 2024:
Este código ya no funciona. Probado en la versión actual de WP al momento de escribir (6.5.5). Para hacerlo funcionar nuevamente, se requieren algunos cambios en el código del Paso 1:
Parámetro name
En wp_dropdown_categories()
el parámetro name
debe ser el slug de la taxonomía. Aunque en este caso no cambia, no es obvio qué deberían cambiar otras personas. Especialmente porque la documentación oficial no lo menciona:
name
string
Valor para el atributo
'name'
del elemento select. Por defecto'cat'
.
Esto podría llevar a pensar que es una cadena personalizada como "my_select_name"
. Para eliminar la confusión, podemos simplemente usar la variable.
wp_dropdown_categories(array(
'name' => $taxonomy,
...
));
Parámetro selected
El parámetro selected
debe cambiarse a:
wp_dropdown_categories(array(
'selected' => $wp_query->query_vars['term']?? 0,
...
));
Esto se debe a que term
está definido en query_vars
en lugar de query
. Si el término no existe en query_vars
, lo más probable es que no se haya seleccionado un término, por lo que podemos usar el operador de fusión nula para probar eso y si es null|undefined
simplemente seleccionar la opción "mostrar todo" con el valor 0
.
Parámetro value_field
Se agregó un nuevo parámetro en WP 4.2.0: value_field
.
Usando este parámetro, ahora podemos indicar a WP que use un "campo de término" específico para llenar los valores de las opciones. De la documentación:
value_field
string
Campo de término que debe usarse para llenar el atributo
'value'
de los elementos option. Acepta cualquier campo de término válido: 'term_id'
,'name'
,'slug'
,'term_group'
,'term_taxonomy_id'
,'taxonomy'
,'description'
,'parent'
,'count'
. Por defecto'term_id'
.
Agregar slug
a este parámetro llenará los valores de las opciones con los slugs de los términos. Como el slug es necesario para que la función de filtro funcione, este parámetro básicamente hace que los filtros funcionen solo con el código del Paso 1 sin necesidad del Paso 2.
Entonces, el Paso 2 puede omitirse si se usa value_field
.
wp_dropdown_categories(array(
'value_field' => 'slug',
...
));
He dejado el código original a continuación, así que haz estos cambios como se detalló arriba al código de abajo.
ACTUALIZACIÓN: He incluido una nueva respuesta completa, pero aún así he dejado mi respuesta original al final a la que hacen referencia los primeros comentarios.
Hola @tarasm:
Aunque dije que no debería ser difícil, es un poco complicado. Pero antes de entrar en el código...
Las capturas de pantalla:
...veamos algunas capturas del producto terminado:
Página de listados sin filtrado:
(fuente: mikeschinkel.com)
Página de listados con filtrado:
(fuente: mikeschinkel.com)
El código
Así que aquí vamos... (Nota: Usé la forma singular para el nombre de la taxonomía business
; espero que coincida con el tuyo. Por mucha experiencia tanto con WordPress como con desarrollo de bases de datos en el pasado, creo que es mejor hacerlo así.)
Paso #1: El hook de acción restrict_manage_posts
.
Lo primero que necesitas hacer es enganchar la acción restrict_manage_posts
que no tiene parámetros y se llama desde /wp-admin/edit.php
(en v3.0.1 esa llamada está en la línea 378). Esto te permitirá generar el menú desplegable en la ubicación adecuada encima de la lista de posts de Listados.
<?php
add_action('restrict_manage_posts','restrict_listings_by_business');
function restrict_listings_by_business() {
global $typenow;
global $wp_query;
if ($typenow=='listing') {
$taxonomy = 'business';
$business_taxonomy = get_taxonomy($taxonomy);
wp_dropdown_categories(array(
'show_option_all' => __("Mostrar todos los {$business_taxonomy->label}"),
'taxonomy' => $taxonomy,
'name' => 'business',
'orderby' => 'name',
'selected' => $wp_query->query['term'],
'hierarchical' => true,
'depth' => 3,
'show_count' => true, // Mostrar # listados entre paréntesis
'hide_empty' => true, // No mostrar negocios sin listados
));
}
}
Comenzamos verificando la variable $typenow
para asegurarnos de que estamos en un post_type
de listing
. Si no lo haces, obtendrás este menú desplegable para todos los tipos de posts, lo que en algunos casos es lo que quieres, pero no en este.
Luego cargamos información sobre la taxonomía business usando get_taxonomy()
. Lo necesitamos para recuperar la etiqueta de la taxonomía (es decir, "Negocios"; podríamos haberlo codificado, pero eso no es muy bueno si necesitas internacionalizar más tarde). Luego llamamos a wp_dropdown_categories()
con todos los argumentos apropiados en el array $args
para generar el menú desplegable.
Pero, ¿cuáles son los argumentos apropiados? Veamos cada uno individualmente:
show_optional_all
- Bastante claro, es lo que se muestra en el menú desplegable al principio y cuando no se ha aplicado ningún filtro. En nuestro caso será "Mostrar todos los negocios" pero podríamos haberlo llamado "Listados para todos los negocios" o lo que prefieras.taxonomy
- Este argumento le dice a la función de qué taxonomía extraer los términos, aunque la función tengacategories
en su nombre. En v2.8 y anteriores, WordPress no tenía taxonomías personalizadas, pero cuando se agregaron, el equipo decidió que sería más fácil agregar un argumento de taxonomía a esta función que crear otra función con otro nombre.name
- Este argumento te permite especificar el valor que WordPress usará para el atributoname
del elemento <select> generado para el menú desplegable. Por si no es obvio, este también es el valor que se usará en la URL al filtrar.orderby
- Este argumento le dice a WordPress cómo ordenar los resultados alfabéticamente. En nuestro caso, especificamos ordenar por elname
de los términos en la taxonomía, es decir, los nombres de los negocios en este caso.selected
- Este argumento es necesario para que el menú desplegable pueda mostrar el filtro actual. Debe ser elterm_id
del término de taxonomía seleccionado. En nuestro caso, podría ser elterm_id
de "Negocio #2". ¿De dónde obtenemos este valor? De la variable global de WordPress$wp_query
; tiene una propiedadquery
que contiene un array de todos los parámetros de URL y sus valores (a menos que algún plugin descarriado lo haya modificado, por supuesto). Dado cómo WordPress procesa las cosas, habrá un parámetro de URLterm
pasado en la URL cuando el usuario haga clic en el botón de filtro si el usuario seleccionó un término válido (es decir, uno de los negocios listados).hierarchical
- Al establecer esto entrue
, le dices a la función que respete la naturaleza jerárquica de la taxonomía y los muestre en una vista de árbol si los términos (negocios) tienen hijos. Para ver una captura de pantalla de cómo se ve esto, consulta más abajo.depth
- Este argumento colabora con el argumentohierarchical
para determinar cuántos niveles de profundidad debe recorrer la función al mostrar hijos.show_count
- Si estrue
, este argumento mostrará un recuento de posts entre paréntesis a la izquierda del nombre del término dentro del menú desplegable. En este caso, mostraría un recuento de listados asociados con un negocio. Para ver una captura de pantalla de cómo se ve esto, consulta más abajo.hide_empty
- Finalmente, si hay términos en la taxonomía que no están asociados con un post (es decir, negocios no asociados con un listado), entonces establecer esto entrue
los omitirá de la inclusión en el menú desplegable.
(fuente: mikeschinkel.com)
Paso #2: El hook de filtro parse_query
.
A continuación, prestamos atención al hook de filtro parse_query
que tiene un parámetro ($query
) y se llama desde /wp-includes/query.php
(en v3.0.1 esa llamada está en la línea 1549). Se llama cuando WordPress ha terminado de inspeccionar la URL y ha establecido todos los valores apropiados en el $wp_query
actualmente activo, incluidas cosas como $wp_query->is_home
y $wp_query->is_author
, etc.
Después de que se ejecuta el hook de filtro parse_query
, WordPress llamará a get_posts()
y cargará una lista de posts según lo especificado en el $wp_query
actualmente activo. Por lo tanto, parse_query
es a menudo un gran lugar para hacer que WordPress cambie de opinión sobre qué posts va a cargar.
En tu caso de uso, queremos que WordPress filtre según los negocios seleccionados; es decir, mostrar solo aquellos Listados que se hayan asociado con el negocio seleccionado (diría "...solo aquellos Listados que se han "categorizado" por el negocio seleccionado" pero eso no es técnicamente correcto; category
es su propia taxonomía al mismo nivel que business
excepto que category
está integrado en WordPress y business
es personalizado. Pero para aquellos familiarizados con la categorización de posts, esto puede ayudarte a entender, ya que funcionan casi de manera idéntica. Pero me desvío...)
Al código. Lo primero que hacemos es obtener una referencia al array query_vars
del $wp_query
actualmente activo para que sea más conveniente trabajar con él, al igual que se hace dentro de la propia función parse_query()
de WordPress. A diferencia de $wp_query->query
, que se usa para reflejar los parámetros pasados en la URL, el array $wp_query->query_vars
se usa para controlar la consulta que ejecuta WordPress y se espera que sea modificado. Entonces, si necesitas modificar uno, ese sería el indicado (¡al menos creo que esa es la diferencia entre los dos; si alguien sabe lo contrario, por favor házmelo saber para que pueda actualizar esto!)
<?php
add_filter('parse_query','convert_business_id_to_taxonomy_term_in_query');
function convert_business_id_to_taxonomy_term_in_query($query) {
global $pagenow;
$qv = &$query->query_vars;
if ($pagenow=='edit.php' &&
isset($qv['taxonomy']) && $qv['taxonomy']=='business' &&
isset($qv['term']) && is_numeric($qv['term'])) {
$term = get_term_by('id',$qv['term'],'business');
$qv['term'] = $term->slug;
}
}
A continuación, verificamos $pagenow
para asegurarnos de que realmente estamos cargando WordPress desde la ruta de URL /wp-admin/edit.php
. Hacemos esto para evitar alterar accidentalmente consultas en otras páginas. También verificamos que tengamos tanto business
como un elemento taxonomy
y un elemento term
también. (Nota: taxonomy
y term
son un par; se usan juntos para permitir la consulta de un término de taxonomía; hay que tener ambos o WordPress no sabe qué taxonomía inspeccionar.)
Podrías preguntarte cómo apareció business
en el elemento taxonomy
del array query_vars
. Lo que escribimos en nuestro hook parse_query
activó la magia interna de WordPress que estaba esperando cuando registraste la taxonomía "business
" al establecer query_var
como verdadero (register_taxonomy()
copia el nombre de la taxonomía como su query_var
; puedes cambiarlo, por supuesto, pero a menos que tengas un conflicto, es mejor seguir con lo mismo):
<?php
add_action('init','register_business_taxonomy');
function register_business_taxonomy() {
register_taxonomy('business',array('listing'),array(
'label' => 'Negocios',
'public'=>true,
'hierarchical'=>true,
'show_ui'=>true,
'query_var'=>true
));
}
Ahora, el $wp_query de WordPress estaba escrito para usar slugs para consultas de filtrado de taxonomía estándar, no IDs de términos de taxonomía. Para este caso de uso, lo que realmente necesitamos para que funcione nuestra consulta de filtrado son estos:
taxonomy
: business
term
: business-1 (es decir, elslug
)
No estos:
taxonomy
: business
term
: 27 (es decir, elterm_id
)
Curiosamente y desafortunadamente, el menú desplegable generado por wp_dropdown_categories()
establece el atributo value
de los <option>
en el term_id
del término(/negocio), no el slug
del término. Por lo tanto, necesitamos convertir $wp_query->query_vars['term']
de un term_id
numérico a su slug
de cadena como se muestra en el fragmento tomado de arriba (Nota: esta no es la forma más eficiente de consultar una base de datos, ¡pero hasta que WordPress agregue soporte para term_ids en su consulta, es lo mejor que podemos hacer!):
<?php
$term = get_term_by('id',$qv['term'],'business');
$qv['term'] = $term->slug;
¡Y eso es todo! Con esas dos funciones obtienes el filtrado que deseas.
¡PERO ESPERA, HAY MÁS! :-)
Agregué una columna de "Negocios" a tu lista de Listados porque, bueno, sabía que sería tu próxima pregunta. Sin tener una columna para lo que filtras, puede ser muy confuso para el usuario final. (¡Yo mismo tuve problemas con eso, y yo era el programador!) Por supuesto, ya puedes ver la columna "Negocios" en las capturas de pantalla anteriores.
Paso #3: El hook de filtro manage_posts_columns
.
Para agregar una columna a la lista de posts, se necesitan llamar a dos (2) hooks más. El primero es manage_posts_columns
o la versión específica del tipo de post manage_listing_posts_columns
que llamé en su lugar. Acepta un parámetro (posts_columns
) y se llama desde /wp-admin/includes/template.php
(en v3.0.1 esa llamada está en la línea 623):
<?php
add_action('manage_listing_posts_columns', 'add_businesses_column_to_listing_list');
function add_businesses_column_to_listing_list( $posts_columns ) {
if (!isset($posts_columns['author'])) {
$new_posts_columns = $posts_columns;
} else {
$new_posts_columns = array();
$index = 0;
foreach($posts_columns as $key => $posts_column) {
if ($key=='author')
$new_posts_columns['businesses'] = null;
$new_posts_columns[$key] = $posts_column;
}
}
$new_posts_columns['businesses'] = 'Negocios';
return $new_posts_columns;
}
Tu función de hook manage_posts_columns
recibe un array de columnas donde el valor es el encabezado de columna mostrado y la clave es el identificador interno de la columna. Los identificadores de columna estándar pueden incluir estos y más: 'cb'
, 'title
', 'author'
, ``'date'`, etc.
'cb'
, es la columna de checkbox
y tanto 'title'
como 'date'
se refieren a post_title
y post_date
de la tabla wp_posts
, respectivamente. 'author'
por supuesto es el campo post_author
después de que se recupera el nombre del autor de la tabla wp_users
.
(fuente: mikeschinkel.com)
Para el hook manage_posts_columns
, simplemente queremos insertar nuestra columna businesses
en el array $posts_columns
antes de 'author'
, ¡asumiendo que algún otro plugin no haya eliminado author
de la lista todavía!
$new_posts_columns['businesses'] = 'Negocios';
(Nota mientras escribía add_businesses_column_to_listing_list()
se me ocurrió que PHP debe tener una forma más fácil de insertar un valor en un array asociativo en el orden correcto. ¿O al menos debe haber una función en el núcleo de WordPress para hacerlo? Pero como Google me falló, seguí con lo que funcionaba. ¡Si alguien tiene sugerencias alternativas, estaré atento y agradecido de antemano!)
Lo que finalmente nos lleva a...
Paso #4: El hook de acción manage_posts_custom_column
La segunda cosa de dos (2) que necesitamos hacer para que nuestros negocios se muestren en la columna es realmente mostrar el nombre de cada uno de los negocios asociados usando el hook de acción manage_posts_custom_column
. Este hook acepta dos (2) parámetros (column_id
y post_id
) y también se llama desde /wp-admin/includes/template.php
(en v3.0.1 esa llamada está en la línea 1459.):
<?php
add_action('manage_posts_custom_column', 'show_businesses_column_for_listing_list',10,2);
function show_businesses_column_for_listing_list( $column_id,$post_id ) {
global $typenow;
if ($typenow=='listing') {
$taxonomy = 'business';
switch ($column_name) {
case 'businesses':
$businesses = get_the_terms($post_id,$taxonomy);
if (is_array($businesses)) {
foreach($businesses as $key => $business) {
$edit_link = get_term_link($business,$taxonomy);
$businesses[$key] = '<a href="'.$edit_link.'">' . $business->name . '</a>';
}
//echo implode("<br/>",$businesses);
echo implode(' | ',$businesses);
}
break;
}
}
}
Este hook se llama para cada columna de cada fila de post(/negocio). Primero verificamos que realmente estamos trabajando solo con el tipo de post personalizado listing
y luego usamos una declaración switch
para probar contra el column_id
. Elegí switch
porque este hook a menudo se usa para generar salida para muchas columnas diferentes, especialmente si usamos una función para muchos tipos de posts diferentes que podría verse así:
<?php
add_action('manage_posts_custom_column', 'my_manage_posts_custom_column',10,2);
function my_manage_posts_custom_column( $column_id,$post_id ) {
global $typenow;
switch ("{$typenow}:{$column_id}") {
case 'listing:business':
echo '...lo que sea...';
break;
case 'listing:property':
echo '...lo que sea...';
break;
case 'agent:listing':
echo '...lo que sea...';
break;
}
}
Inspeccionando nuestro caso de uso un poco más de cerca, ves la función get_the_terms()
que simplemente devuelve la lista de términos para esta taxonomía (es decir, negocios para este listado). Aquí obtenemos el enlace permanente para la página frontal del término que normalmente enumera los posts que están asociados con el término, pero, por supuesto, podría funcionar de manera diferente según el tema y/o los plugins instalados.
Usamos el enlace permanente para hipervincular el término solo porque me gusta hipervincular cosas. Luego fusionamos todos los términos(/negocios) hipervinculados juntos separados con el carácter de tubería ('|
') y lo enviamos al búfer de PHP que lo envía al navegador/cliente HTTP del usuario:
<?php
$businesses = get_the_terms($post_id,$taxonomy);
if (is_array($businesses)) {
foreach($businesses as $key => $business) {
$edit_link = get_term_link($business,$taxonomy);
$businesses[$key] = '<a href="'.$edit_link.'">' . $business->name . '</a>';
}
//echo implode("<br/>",$businesses);
echo implode(' | ',$businesses);
}
AHORA finalmente hemos terminado.
Resumen
Entonces, en resumen, necesitas usar los siguientes cuatro (4) hooks para obtener tanto un filtro como una columna relacionada en la página de lista de posts personalizados (Oh, también funcionará con Posts y Páginas). Son:
- Paso #1: El hook de acción
restrict_manage_posts
. - Paso #2: El hook de filtro
parse_query
. - Paso #3: El hook de filtro
manage_posts_columns
. - Paso #4: El hook de acción
manage_posts_custom_column
Dónde descargar el código
¡Pero si te obligara a leer todo lo anterior, ciertamente no sería una persona muy agradable si también te hiciera buscar el código solo para poder probarlo! Pero, contrario a lo que dicen algunas personas, soy agradable. Así que aquí tienes:
- Descarga el código en: http://gist.github.com/541505
NOTA para @tarasm: Incluí hooks para un register_post_type()
y register_taxonomy()
para que otros pudieran probar esto sin tener que recrearlos. Probablemente quieras eliminar esas dos llamadas a funciones antes de probar esto.
FIN
Respuesta original:
Hola @tarasm:
¿Estás buscando un menú desplegable en la parte superior como en esta captura o buscas un menú desplegable por registro de post y, de ser así, ¿cómo esperarías que funcionara esto último?
(fuente: mikeschinkel.com)
Si es lo primero, echa un vistazo a esta respuesta a la pregunta ¿Cómo ordenar el área de administración de un tipo de post personalizado de WordPress por un campo personalizado? Si eso es lo que necesitas, puedo proporcionar más detalles relacionados con la taxonomía.

Estoy buscando un menú desplegable en la parte superior que muestre el filtro de Categorías. Me preguntaba si hay una forma estándar de hacer esto sin tener que escribir código personalizado.

A primera vista, no creo que puedas hacerlo sin código personalizado, pero tampoco creo que el código personalizado sea muy significativo. Tengo que preparar una llamada con un cliente, así que tendrá que ser más tarde hoy.

En realidad, ambas soluciones (somatic y MikeSchinkel) no funcionan cuando intentas filtrar 2 taxonomías diferentes en el mismo filtro :-/
Siempre filtra la última taxonomía cuando intentas filtrar 2+ al mismo tiempo.

@Ünsal La versión actual de WordPress (3.0) no soporta consultas de Taxonomía múltiple, pero por lo que he escuchado eso cambiará en la versión 3.1. Para hacer que este ejemplo funcione con múltiples taxonomías, tendrías que agregar algunos joins y condiciones where a la consulta usando los filtros Posts_join y posts_where.

gracias por la gran respuesta - pero hay un pequeño error tipográfico en el ejemplo de código del paso #4: debería ser $column_id
y no column_name
. La versión en github es correcta sin embargo.

En WP 3.1+, los pasos uno y dos están mejor explicados en la respuesta de @drew-gourley (de hecho, tu paso 2 no funcionó para mí, creo que hubo cambios en este filtrado en las nuevas versiones de WordPress).

Mike, creo que podrías mejorar la claridad de este (increíble) tutorial, cambiando los argumentos de wp_dropdown_categories para que 'name' => $taxonomy, y así dejar claro que el atributo name del campo select debe ser el slug de la taxonomía registrada (o más específicamente, el slug de query_var). Tal como está, parece que 'taxonomy' debe ser el slug de la taxonomía, pero 'name' puede ser cualquier cosa arbitraria, lo cual creo que no es del todo cierto, a menos que estés dispuesto a analizar la cadena de consulta tú mismo en parse_query

una respuesta muy brillante y completa, pero ¿no necesitas devolver la variable $query en tu función convert_business_id_to_taxonomy_term_in_query()?

¡Me sumo a la gran lista de elogios anteriores! Las lecciones de Mike son (con bastante frecuencia) como una clase en la universidad. . . . Y me gustaría señalar a cualquiera que investigue el tema que la respuesta de @DrewGourley más abajo es oro puro :)

Algo que no es inmediatamente claro es que get_term_link() enlazará al archivo público de una taxonomía, en lugar de mostrar los resultados filtrados en WP-Admin. No estoy seguro si existe una función para generar este enlace, por lo que debe crearse manualmente, ej: <a href="edit.php?CUSTOM_TAXONOMY=FOO&post_type=CUSTOM_POST_TYPE">NOMBRE_TAXONOMIA</a> pero aparte de ese pequeño detalle, ¡esto es genial!

Solo quería compartir una implementación alternativa. No tenía el increíble tutorial de Mike cuando estaba resolviendo esto, así que mi solución es un poco diferente. Específicamente, voy a simplificar el paso #1 de Mike y eliminar el paso #2 - los otros pasos siguen siendo aplicables.
En el tutorial de Mike, usar wp_dropdown_categories()
nos ahorra algo de construcción manual de listas, pero requiere una modificación condicional complicada de la consulta (paso #2) para manejar su uso de ID en lugar de slug. Sin mencionar la dificultad de modificar ese código para manejar otros escenarios, como múltiples filtros de taxonomía.
Otro enfoque es simplemente no usar el defectuoso wp_dropdown_categories()
en absoluto, sino construir nuestras propias listas desplegables desde cero. No es tan complicado, toma menos de 30 líneas de código y no requiere enganchar parse_query
en absoluto:
add_action( 'restrict_manage_posts', 'my_restrict_manage_posts' );
function my_restrict_manage_posts() {
// solo mostrar estos filtros de taxonomía en los listados de tipos de publicación personalizados deseados
global $typenow;
if ($typenow == 'photos' || $typenow == 'videos') {
// crear un array de slugs de taxonomía que deseas filtrar - si quieres recuperar todas las taxonomías, podrías usar get_taxonomies() para construir la lista
$filters = array('plants', 'animals', 'insects');
foreach ($filters as $tax_slug) {
// recuperar el objeto de taxonomía
$tax_obj = get_taxonomy($tax_slug);
$tax_name = $tax_obj->labels->name;
// recuperar array de objetos de término por taxonomía
$terms = get_terms($tax_slug);
// generar html para el filtro desplegable de taxonomía
echo "<select name='$tax_slug' id='$tax_slug' class='postform'>";
echo "<option value=''>Mostrar todos $tax_name</option>";
foreach ($terms as $term) {
// generar cada opción del select, verificando contra el último $_GET para mostrar la opción actual seleccionada
echo '<option value='. $term->slug, $_GET[$tax_slug] == $term->slug ? ' selected="selected"' : '','>' . $term->name .' (' . $term->count .')</option>';
}
echo "</select>";
}
}
}
Simplemente conectando las taxonomías deseadas en el array $filters
, puedes generar rápidamente múltiples filtros de taxonomía. Aparecen exactamente igual que en las capturas de pantalla de Mike. Luego puedes continuar con el paso #3 y #4.

@somatic - ¡Buena actualización! Sí, usar wp_dropdown_categories()
requiere muchas soluciones alternativas. Intento usar funciones principales cuando es posible, pero como señalas, a veces eso requiere más trabajo. Esto demuestra que en WordPress a menudo hay más de una buena manera de resolver un problema. ¡Buen trabajo!

Dejó de funcionar para mí en WordPress 3.1. Estoy tratando de descubrir qué cambió exactamente. Parece que debería seguir funcionando: los slugs de taxonomía y términos aparecen como valores GET en la URL, pero solo resulta en 0 resultados.

He estado intentando que esto funcione pero la única forma en que pude fue usando el hook parse_query, verificando la variable de consulta de la taxonomía y estableciendo las variables de consulta de taxonomía y término basadas en eso. Usando WP 3.1. ¿Deberían aparecer taxonomía y término en la URL cuando se envía el filtro?

¡Funciona de maravilla para mí! Una solución muy elegante sin duda. Te debo una cerveza :)

@somatic Esto funciona genial, pero ¿hay alguna manera de que el $term->count solo cuente los términos para ese tipo de post? Por ejemplo, si tengo una taxonomía personalizada tanto para fotos como para videos, al mirar el tipo de post personalizado de videos me mostrará el número total de posts para ese término de ambos tipos de posts personalizados, en lugar de solo el número total de posts de video que usan ese término.

Aquí hay una versión que crea y aplica automáticamente filtros de todas las taxonomías que se aplican a todos los tipos de entradas personalizadas que las utilizan. (qué trabalenguas) De todos modos, también lo ajusté para que funcione con wp_dropdown_categories() y WordPress 3.1. El proyecto en el que estoy trabajando se llama ToDo, puedes renombrar las funciones a algo que tenga sentido para ti, pero esto debería funcionar para casi todo automáticamente.
function todo_restrict_manage_posts() {
global $typenow;
$args=array( 'public' => true, '_builtin' => false );
$post_types = get_post_types($args);
if ( in_array($typenow, $post_types) ) {
$filters = get_object_taxonomies($typenow);
foreach ($filters as $tax_slug) {
$tax_obj = get_taxonomy($tax_slug);
wp_dropdown_categories(array(
'show_option_all' => __('Mostrar todos los '.$tax_obj->label ),
'taxonomy' => $tax_slug,
'name' => $tax_obj->name,
'orderby' => 'term_order',
'selected' => $_GET[$tax_obj->query_var],
'hierarchical' => $tax_obj->hierarchical,
'show_count' => false,
'hide_empty' => true
));
}
}
}
function todo_convert_restrict($query) {
global $pagenow;
global $typenow;
if ($pagenow=='edit.php') {
$filters = get_object_taxonomies($typenow);
foreach ($filters as $tax_slug) {
$var = &$query->query_vars[$tax_slug];
if ( isset($var) ) {
$term = get_term_by('id',$var,$tax_slug);
$var = $term->slug;
}
}
}
return $query;
}
add_action( 'restrict_manage_posts', 'todo_restrict_manage_posts' );
add_filter('parse_query','todo_convert_restrict');
Ten en cuenta que estoy usando un plugin que añade 'term_order' como una forma de ordenar términos, tendrás que cambiar eso, o eliminar ese argumento para volver al valor por defecto.

muy sexy de hecho. Estaba recibiendo notificaciones de error, así que cambié if ( isset($var)) por if ( isset($var) && $var>0) para evitar intentar encontrar términos asociados con el valor 0 de los "Ver todos". Ah, y tuve que devolver $query en la función todo_convert_restrict

Respuesta tardía
Editar
He escrito Filterama, un plugin que añadirá esta funcionalidad de la manera más sencilla posible.
Actualización para WordPress 3.5+
Ahora que las cosas son mucho más fáciles, aquí hay una solución muy simple como plugin o mu-plugin.
Utiliza los mínimos recursos posibles, se carga solo en las pantallas necesarias y añade Columnas + Filtros para cada taxonomía personalizada.
add_action( 'plugins_loaded', array( 'WCM_Admin_PT_List_Tax_Filter', 'init' ) );
class WCM_Admin_PT_List_Tax_Filter
{
private static $instance;
public $post_type;
public $taxonomies;
static function init()
{
null === self::$instance AND self::$instance = new self;
return self::$instance;
}
public function __construct()
{
add_action( 'load-edit.php', array( $this, 'setup' ) );
}
public function setup()
{
add_action( current_filter(), array( $this, 'setup_vars' ), 20 );
add_action( 'restrict_manage_posts', array( $this, 'get_select' ) );
add_filter( "manage_taxonomies_for_{$this->post_type}_columns", array( $this, 'add_columns' ) );
}
public function setup_vars()
{
$this->post_type = get_current_screen()->post_type;
$this->taxonomies = array_diff(
get_object_taxonomies( $this->post_type ),
get_taxonomies( array( 'show_admin_column' => 'false' ) )
);
}
public function add_columns( $taxonomies )
{
return array_merge( taxonomies, $this->taxonomies );
}
public function get_select()
{
$walker = new WCMF_walker;
foreach ( $this->taxonomies as $tax )
{
wp_dropdown_categories( array(
'taxonomy' => $tax,
'hide_if_empty' => true,
'show_option_all' => sprintf(
get_taxonomy( $tax )->labels->all_items
),
'hide_empty' => true,
'hierarchical' => is_taxonomy_hierarchical( $tax ),
'show_count' => true,
'orderby' => 'name',
'selected' => '0' !== get_query_var( $tax )
? get_query_var( $tax )
: false,
'name' => $tax,
'id' => $tax,
'walker' => $walker,
) );
}
}
}
Y luego solo necesitas una clase Walker personalizada.
class WCMF_walker extends Walker_CategoryDropdown
{
public $tree_type = 'category';
public $db_fields = array(
'parent' => 'parent',
'id' => 'term_id',
);
public $tax_name;
public function start_el( &$output, $term, $depth, $args, $id = 0 )
{
$pad = str_repeat( ' ', $depth * 3 );
$cat_name = apply_filters( 'list_cats', $term->name, $term );
$output .= sprintf(
'<option class="level-%s" value="%s" %s>%s%s</option>',
$depth,
$term->slug,
selected(
$args['selected'],
$term->slug,
false
),
$pad.$cat_name,
$args['show_count']
? " ({$term->count})"
: ''
);
}
}

@goto10 Tenías razón. Actualizado. Por cierto: Es más fácil simplemente descargar el plugin vinculado. Estará disponible en el repositorio de plugins en una o dos semanas. (Ya confirmado).

Tuve que usar $this->setup_vars();
al principio de public function setup()
para que "manage_taxonomies_for_{$this->post_type}_columns"
funcionara

Pero podría ser porque lo uso en un function.php del Tema con add_action( 'init', array( 'WCM_Admin_PT_List_Tax_Filter', 'init' ) );

@Christian Esto no es material de tema. Esto pertenece a un plugin y, tal como está el código actualmente, se carga mucho antes de que se carguen los Temas.

@BadHorsie Por favor, prueba la dev
–Rama. Comencé la refactorización, pero no pude encontrar el tiempo para terminarla.

Solo quería hacer una nota rápida. En versiones más recientes de WP, los listados de entradas en el administrador son manejados por la clase WP_Posts_List_Table. El código apply_filters ahora es el siguiente:
if ( 'page' == $post_type )
$posts_columns = apply_filters( 'manage_pages_columns', $posts_columns );
else
$posts_columns = apply_filters( 'manage_posts_columns', $posts_columns, $post_type );
$posts_columns = apply_filters( "manage_{$post_type}_posts_columns", $posts_columns );
Así que para añadir nuevas columnas, un hook add_filter debería ser así:
add_filter( 'manage_posts_columns', 'my_add_columns', 10, 2);
Aquí va un ejemplo:
function my_add_columns($posts_columns, $post_type)
{
if ('myposttype' == $post_type) {
$posts_columns = array(
"cb" => "<input type=\"checkbox\" />",
"title" => "Título",
"anothercolumn" => "Bacon",
"date" => __( 'Fecha' )
);
return $posts_columns;
}
}
Ahora, para las filas de entradas. Este es el código que maneja los datos de columnas en los listados:
default:
?>
<td <?php echo $attributes ?>><?php
if ( is_post_type_hierarchical( $post->post_type ) )
do_action( 'manage_pages_custom_column', $column_name, $post->ID );
else
do_action( 'manage_posts_custom_column', $column_name, $post->ID );
do_action( "manage_{$post->post_type}_posts_custom_column", $column_name, $post->ID );
?></td>
<?php
Para recuperar los datos de nuestras entradas, debemos añadir un action hook así:
add_action( "manage_(aquí_va_tu_tipo_de_entrada)_posts_custom_column", "my_posttype_add_column", 10, 2);
Ejemplo (este ejemplo usa taxonomías, pero podrías consultar cualquier otra cosa):
function my_posttype_add_column($column_name, $post_id)
{
switch ($column_name) {
case 'anothercolumn':
$flavours = get_the_terms($post_id, 'flavour');
if (is_array($flavours)) {
foreach($flavours as $key => $flavour) {
$edit_link = get_term_link($flavour, 'flavour');
$flavours[$key] = '<a href="'.$edit_link.'">' . $flavour->name . '</a>';
}
echo implode(' | ',$flavours);
}
break;
default:
break;
}
}

FUNCIONA EN WP 3.2!
Tipo de entrada personalizada: libros Taxonomía personalizada: género
Solo modifica donde dice: // cambia AQUÍ
function restrict_books_by_genre() {
global $typenow;
$post_type = 'libros'; // cambia AQUÍ
$taxonomy = 'género'; // cambia AQUÍ
if ($typenow == $post_type) {
$selected = isset($_GET[$taxonomy]) ? $_GET[$taxonomy] : '';
$info_taxonomy = get_taxonomy($taxonomy);
wp_dropdown_categories(array(
'show_option_all' => __("Mostrar todos los {$info_taxonomy->label}"),
'taxonomy' => $taxonomy,
'name' => $taxonomy,
'orderby' => 'name',
'selected' => $selected,
'show_count' => true,
'hide_empty' => true,
));
};
}
add_action('restrict_manage_posts', 'restrict_books_by_genre');
function convert_id_to_term_in_query($query) {
global $pagenow;
$post_type = 'libros'; // cambia AQUÍ
$taxonomy = 'género'; // cambia AQUÍ
$q_vars = &$query->query_vars;
if ($pagenow == 'edit.php' && isset($q_vars['post_type']) && $q_vars['post_type'] == $post_type && isset($q_vars[$taxonomy]) && is_numeric($q_vars[$taxonomy]) && $q_vars[$taxonomy] != 0) {
$term = get_term_by('id', $q_vars[$taxonomy], $taxonomy);
$q_vars[$taxonomy] = $term->slug;
}
}
add_filter('parse_query', 'convert_id_to_term_in_query');

Tuve que trabajar en una característica similar no hace mucho tiempo, como no estaba satisfecho con las respuestas, aquí está mi alternativa. Año 2021.
Quería algo sencillo y simple de implementar. Obtener rápida y eficientemente todas las taxonomías personalizadas de cada tipo de entrada personalizada y añadirlas como opciones de filtro.
Había algunos requisitos previos:
- Quería evitar el uso de variables globales.
- El código debía ser portable, sin variables codificadas, para ser reutilizable.
- Tener algún tipo de jerarquía visible, perfecto para taxonomías con términos relacionados complejos.
- Necesitaba respetar la elección de visibilidad de la taxonomía. (ej: parámetro de argumento
show_admin_column
de la funciónregister_taxonomy
. - Mostrar el conteo de entradas.
- Que la apariencia general coincidiera con WordPress.
Lo convertí en un plugin para una integración más fácil, puedes obtener la última versión en https://wordpress.org/plugins/cpt-admin-taxonomy-filtering/.
Filtro con jerarquía de términos y conteo de entradas | Filtros múltiples para tipos de entrada personalizados |
---|---|
![]() |
![]() |
Si solo quieres añadirlo a tu function.php
, copia y pega el siguiente script.
Requiere al menos WordPress
4.8.0
y PHP:4.0
.
<?php
add_action( 'restrict_manage_posts', 'cpt_admin_taxonomy_filtering' );
if ( ! function_exists( 'cpt_admin_taxonomy_filtering' ) ) {
function cpt_admin_taxonomy_filtering() {
/**
* Obtiene el objeto de la pantalla actual.
*
* @link https://developer.wordpress.org/reference/functions/get_current_screen/
*/
$screen = get_current_screen();
// Filtrar los tipos de entrada predeterminados de WordPress
$restricted_post_types = array(
'post',
'page',
'attachment',
'revision',
'nav_menu_item',
);
if ( 'edit' === $screen->base && ! in_array( $screen->post_type, $restricted_post_types ) ) {
/**
* Devuelve los nombres u objetos de las taxonomías registradas para el objeto o tipo de objeto solicitado, como un objeto de entrada o nombre de tipo de entrada.
*
* @link https://developer.wordpress.org/reference/functions/get_object_taxonomies/
*/
$taxonomies = get_object_taxonomies( $screen->post_type, 'objects' );
// Iterar a través de cada taxonomía
foreach ( $taxonomies as $taxonomy ) {
if ( $taxonomy->show_admin_column ) {
/**
* Muestra o recupera la lista desplegable HTML de categorías.
*
* @link https://developer.wordpress.org/reference/functions/wp_dropdown_categories/
*/
wp_dropdown_categories(
array(
'show_option_all' => $taxonomy->labels->all_items,
'pad_counts' => true,
'show_count' => true,
'hierarchical' => true,
'name' => $taxonomy->query_var,
'id' => 'filter-by-' . $taxonomy->query_var,
'class' => '',
'value_field' => 'slug',
'taxonomy' => $taxonomy->query_var,
'hide_if_empty' => true,
)
);
};
};
};
};
};

Esto no es muy conocido, supongo, pero desde WordPress 3.5, puedes pasar 'show_admin_column' => true
a register_taxonomy
. Esto hace 2 cosas:
- Añade la columna de la taxonomía a la vista de lista del tipo de contenido en el admin
- Al hacer clic en el nombre del término en la columna de taxonomía, filtrará la lista para mostrar solo ese término.
Así que no es exactamente lo mismo que tener un select, pero casi la misma funcionalidad, con solo una línea de código.
https://make.wordpress.org/core/2012/12/11/wordpress-3-5-admin-columns-for-custom-taxonomies/
Además, como puedes leer, hay un nuevo filtro diseñado para añadir manualmente la columna de taxonomía (si realmente lo necesitas).

Gracias a todos por las respuestas. Sé que es una pregunta antigua, pero me gustaría añadir la solución más simple que se puede usar hoy en día. Solo requerirá 2 pasos y aprovechará más las funciones principales disponibles. Espero que esto ayude a alguien.
Paso 1: añadir 'show_admin_column' => true
a tu función register_taxonomy
.
Esto añadirá una columna a la vista de lista que contiene enlaces para filtrar la lista por ese término. Se puede aplicar tanto para taxonomías jerárquicas como no jerárquicas.
Ejemplo:
register_taxonomy( 'custom_cat', array( 'custom_post_type' ), array(
'hierarchical' => true,
'labels' => array(
'name' => __( 'Categorías', 'iside' ),
'singular_name' => __( 'Categoría', 'iside' ),
'search_items' => __( 'Buscar Categorías', 'iside' ),
'all_items' => __( 'Todas las Categorías', 'iside' ),
'parent_item' => __( 'Categoría Padre', 'iside' ),
'parent_item_colon' => __( 'Categoría Padre:', 'iside' ),
'edit_item' => __( 'Editar Categoría', 'iside' ),
'update_item' => __( 'Actualizar Categoría', 'iside' ),
'add_new_item' => __( 'Añadir Nueva Categoría', 'iside' ),
'new_item_name' => __( 'Nueva Categoría', 'iside' ),
'menu_name' => __( 'Categorías', 'iside' ),
),
'public' => true,
'publicly_queryable'=> true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'show_in_rest' => true,
'show_tagcloud' => false,
'show_in_quick_edit'=> true,
'show_admin_column' => true, // <-- la magia aplicada aquí
'query_var' => true,
) );
Paso 2: añadir el desplegable de taxonomía mediante el filtro restrict_manage_posts
Esto añadirá un desplegable de selección a las opciones de filtro en la parte superior. Usará la función predeterminada wp_dropdown_categories
para hacerlo más fácil. Ten en cuenta que necesitamos establecer el nombre del desplegable al query_var que usamos para la taxonomía. También nota que el value_field
debe establecerse al slug (por defecto será el term_id).
Ejemplo:
add_action( 'restrict_manage_posts', 'iside_add_filter_to_admin', 10, 2 );
function iside_add_filter_to_admin( $post_type = 'post', $which = 'top' ) {
if( $post_type == 'custom_post_type' ) {
$selected = isset( $_GET['custom_cat'] ) ? esc_attr($_GET['custom_cat']) : 0;
wp_dropdown_categories(array(
'show_option_all' => __( 'Mostrar Todas las Categorías', 'iside' ),
'taxonomy' => 'custom_cat',
'name' => 'custom_cat', // <-- asegúrate que coincida con el query_var
'orderby' => 'name',
'selected' => $selected,
'hierarchical' => true,
'depth' => 3,
'show_count' => true,
'hide_empty' => true,
'value_field' => 'slug', // <-- usa slug en lugar de term_id
));
}
}
Eso es todo. No hace falta añadir más :-)

Muy bonito y sencillo, gracias. Un detalle a tener en cuenta - tienes un error en tu sintaxis aquí (hay un paréntesis de más): $selected = isset( $_GET['custom_cat'] ) ? esc_attr($_GET['custom_cat') ]) : 0; lo cual debería ser de la siguiente manera: $selected = isset( $_GET['custom_cat'] ) ? esc_attr($_GET['custom_cat' ]) : 0;

Aquí hay una forma de hacerlo usando la acción restrict_manage_posts. Parece funcionar bien para mí y añade la capacidad de filtrar por taxonomía para todos los tipos de post y sus taxonomías relacionadas.
// Registra cada uno de los desplegables de filtro por taxonomía
function sunrise_fbt_add_taxonomy_filters() {
global $typenow; // el tipo de post actual
$taxonomies = get_taxonomies('','objects');
foreach($taxonomies as $taxName => $tax) {
if(in_array($typenow,$tax->object_type) && $taxName != 'category' && $taxName != 'tags') {
$terms = get_terms($taxName);
if(count($terms) > 0) {
// Comprueba si es jerárquico - si es así construye un desplegable jerárquico
if($tax->hierarchical) {
$args = array(
'show_option_all' => 'Todos los '.$tax->labels->name,
'show_option_none' => 'Seleccionar '.$tax->labels->name,
'show_count' => 1,
'hide_empty' => 0,
'echo' => 1,
'hierarchical' => 1,
'depth' => 3,
'name' => $tax->rewrite['slug'],
'id' => $tax->rewrite['slug'],
'class' => 'postform',
'depth' => 0,
'tab_index' => 0,
'taxonomy' => $taxName,
'hide_if_empty' => false);
$args['walker'] = new Walker_FilterByTaxonomy;
wp_dropdown_categories($args);
} else {
echo "<select name='".$tax->rewrite['slug']."' id='".$tax->rewrite['slug']."' class='postform'>";
echo "<option value=''>Mostrar todos los ".$tax->labels->name."</option>";
foreach ($terms as $term) {
echo '<option value="' . $term->slug . '"', $_GET[$taxName] == $term->slug ? ' selected="selected"' : '','>' . $term->name .' (' . $term->count .')</option>';
}
echo "</select>";
}
}
}
}
}
add_action( 'restrict_manage_posts', 'sunrise_fbt_add_taxonomy_filters', 100 );
/**
* Crea una lista desplegable HTML de Categorías.
*
* @package WordPress
* @since 2.1.0
* @uses Walker
*/
class Walker_FilterByTaxonomy extends Walker {
var $tree_type = 'category';
var $db_fields = array ('parent' => 'parent', 'id' => 'term_id');
function start_el(&$output, $category, $depth, $args) {
$args['selected'] = get_query_var( $args['taxonomy'] );
$pad = str_repeat(' ', $depth * 3);
$cat_name = apply_filters('list_cats', $category->name, $category);
$output .= "\t<option class=\"level-$depth\" value=\"".$category->slug."\"";
if ( $category->slug == $args['selected'] )
$output .= ' selected="selected"';
$output .= '>';
$output .= $pad.$cat_name;
if ( $args['show_count'] )
$output .= ' ('. $category->count .')';
if ( $args['show_last_update'] ) {
$format = 'Y-m-d';
$output .= ' ' . gmdate($format, $category->last_update_timestamp);
}
$output .= "</option>\n";
}
}
Una nota - Intenté restringir la profundidad porque algunas de mis taxonomías jerárquicas son bastante grandes pero no funcionó - ¿podría ser un error en la función wp_dropdown_categories?

Actualización de la respuesta de @Drew Gourley para WP 3.3.1 (e incorporando código de http://wordpress.org/support/topic/wp_dropdown_categories-generating-url-id-number-instead-of-slug?replies=6#post-2529115):
add_action('restrict_manage_posts', 'xyz_restrict_manage_posts');
function xyz_restrict_manage_posts() {
global $typenow;
$args = array('public'=>true, '_builtin'=>false);
$post_types = get_post_types($args);
if(in_array($typenow, $post_types)) {
$filters = get_object_taxonomies($typenow);
foreach ($filters as $tax_slug) {
$tax_obj = get_taxonomy($tax_slug);
$term = get_term_by('slug', $_GET[$tax_obj->query_var], $tax_slug);
wp_dropdown_categories(array(
'show_option_all' => __('Mostrar todos los '.$tax_obj->label ),
'taxonomy' => $tax_slug,
'name' => $tax_obj->name,
'orderby' => 'term_order',
'selected' => $term->term_id,
'hierarchical' => $tax_obj->hierarchical,
'show_count' => false,
// 'hide_empty' => true,
'hide_empty' => false,
'walker' => new DropdownSlugWalker()
));
}
}
}
//Clase para el filtro desplegable. Se usa con wp_dropdown_categories() para que el desplegable resultante use slugs de términos en lugar de IDs.
class DropdownSlugWalker extends Walker_CategoryDropdown {
function start_el(&$output, $category, $depth, $args) {
$pad = str_repeat(' ', $depth * 3);
$cat_name = apply_filters('list_cats', $category->name, $category);
$output .= "\t<option class=\"level-$depth\" value=\"".$category->slug."\"";
if($category->term_id == $args['selected'])
$output .= ' selected="selected"';
$output .= '>';
$output .= $pad.$cat_name;
$output .= "</option>\n";
}
}

Versión jerárquica de la respuesta de @somatic, como solicitó @kevin:
<?php
add_action( 'restrict_manage_posts', 'my_restrict_manage_posts' );
function my_restrict_manage_posts() {
// solo mostrar estos filtros de taxonomía en los listados de custom post_type deseados
global $typenow;
if ($typenow == 'photos' || $typenow == 'videos') {
// crear un array de slugs de taxonomías que deseas filtrar - si quieres recuperar todas las taxonomías, podrías usar get_taxonomies() para construir la lista
$filters = array('plants', 'animals', 'insects');
foreach ($filters as $tax_slug) {
// recuperar el objeto de taxonomía
$tax_obj = get_taxonomy($tax_slug);
$tax_name = $tax_obj->labels->name;
// generar html para el dropdown de filtro de taxonomía
echo "<select name='$tax_slug' id='$tax_slug' class='postform'>";
echo "<option value=''>Mostrar todos $tax_name</option>";
generate_taxonomy_options($tax_slug,0,0);
echo "</select>";
}
}
}
function generate_taxonomy_options($tax_slug, $parent = '', $level = 0) {
$args = array('show_empty' => 1);
if(!is_null($parent)) {
$args = array('parent' => $parent);
}
$terms = get_terms($tax_slug,$args);
$tab='';
for($i=0;$i<$level;$i++){
$tab.='--';
}
foreach ($terms as $term) {
// generar cada opción del select, verifica contra el último $_GET para mostrar la opción actual seleccionada
echo '<option value='. $term->slug, $_GET[$tax_slug] == $term->slug ? ' selected="selected"' : '','>' .$tab. $term->name .' (' . $term->count .')</option>';
generate_taxonomy_options($tax_slug, $term->term_id, $level+1);
}
}
?>
Básicamente eliminé el código que creaba las opciones y lo puse en su propia función. La función 'generate_taxonomy_options', además de tomar el tax_slug, también toma parámetros de parent y level. La función asume que está creando opciones para el parent 0, lo que seleccionará todos los términos de nivel raíz. En el bucle, la función se llamará a sí misma recursivamente, usando el término actual como padre e incrementando el nivel en uno. Automáticamente agrega guiones al costado mientras más profundo vayas en el árbol y ¡voilá!

Simplemente añade esto a tu functions.php o código de plugin (y reemplaza YOURCUSTOMTYPE y YOURCUSTOMTAXONOMY). (Obtuve el código de generatewp)
add_action( 'restrict_manage_posts', 'filter_backend_by_taxonomies' , 99, 2);
function filter_backend_by_taxonomies( $post_type, $which ) {
// Aplicar esto a un CPT específico
if ( 'YOURCUSTOMTYPE' !== $post_type )
return;
// Una lista de taxonomías personalizadas para filtrar
$taxonomies = array( 'YOURCUSTOMTAXONOMY' );
foreach ( $taxonomies as $taxonomy_slug ) {
// Obtener datos de la taxonomía
$taxonomy_obj = get_taxonomy( $taxonomy_slug );
$taxonomy_name = $taxonomy_obj->labels->name;
// Obtener términos de la taxonomía
$terms = get_terms( $taxonomy_slug );
// Mostrar HTML del filtro
echo "<select name='{$taxonomy_slug}' id='{$taxonomy_slug}' class='postform'>";
echo '<option value="">' . sprintf( esc_html__( 'Categoría', 'text_domain' ), $taxonomy_name ) . '</option>';
foreach ( $terms as $term ) {
printf(
'<option value="%1$s" %2$s>%3$s (%4$s)</option>',
$term->slug,
( ( isset( $_GET[$taxonomy_slug] ) && ( $_GET[$taxonomy_slug] == $term->slug ) ) ? ' selected="selected"' : '' ),
$term->name,
$term->count
);
}
echo '</select>';
}
}

¡El tutorial de Mike sobre esto es genial! Probablemente no me habría molestado en agregar esta funcionalidad a mi plugin de Categorías de Medios si hubiera tenido que averiguarlo por mí mismo.
Dicho esto, creo que usar parse_query
y luego obtener la consulta del término no es necesario. Es más limpio crear tu propia clase personalizada de walker. Quizás eso no era posible cuando escribió su publicación, tiene 3 años al momento de escribir esto.
Echa un vistazo a este gran fragmento en github. Funciona de maravilla, cambia los IDs en los valores del dropdown por slugs, así que funciona de forma nativa sin modificar la consulta.

Acabo de probar ambos códigos, el de Mike y el de somatic, y me preguntaba cómo obtener una cosa de cada técnica:
Con el código de Mike, muestra la lista desplegable con la opción jerárquica, lo cual ayuda mucho. Pero para mostrar dos listas desplegables, tuve que duplicar la declaración if ($typenow=='produtos') {...}
en la función restrict_listings_by_business()
y también el if ($pagenow=='edit.php' && ... }
en la función convert_business_id_to_taxonomy_term_in_query($query)
, lo que ahora genera mucho código.
Con el código de somatic, solo necesito especificar las taxonomías que me gustaría ver como listas desplegables y listo, funciona: $filters = array('taxo1', 'taxo2');
Pregunta: ¿puedo adoptar el enfoque de somatic y también tener la opción jerárquica?
¡Muchas gracias de todos modos por este tutorial, me ayudó mucho!

Este es un fragmento de código potente y pequeño que permite filtrar tus entradas o tipos de contenido personalizados por un término de taxonomía específico en el administrador de WordPress.
/* Filtrar CPT por Taxonomía Personalizada */
function rave_core_backend_by_portfolio_taxonomies( $post_type, $which ) {
// Aplicar esto a un CPT específico
if ( 'portfolio' !== $post_type )
return;
// Una lista de slugs de taxonomías personalizadas para filtrar
$taxonomies = array( 'portfolio_cat' );
foreach ( $taxonomies as $taxonomy_slug ) {
// Recuperar datos de la taxonomía
$taxonomy_obj = get_taxonomy( $taxonomy_slug );
$taxonomy_name = $taxonomy_obj->labels->name;
// Recuperar términos de la taxonomía
$terms = get_terms( $taxonomy_slug );
// Mostrar HTML del filtro
echo "<select name='{$taxonomy_slug}' id='{$taxonomy_slug}' class='postform'>";
echo '<option value="">' . sprintf( esc_html__( 'Todos los %s', 'rave-core' ), $taxonomy_name ) . '</option>';
foreach ( $terms as $term ) {
printf(
'<option value="%1$s" %2$s>%3$s (%4$s)</option>',
$term->slug,
( ( isset( $_GET[$taxonomy_slug] ) && ( $_GET[$taxonomy_slug] == $term->slug ) ) ? ' selected="selected"' : '' ),
$term->name,
$term->count
);
}
echo '</select>';
}
}
add_action( 'restrict_manage_posts', 'rave_core_backend_by_portfolio_taxonomies' , 99, 2);

Disculpas por el hecho de que, como usuario nuevo, no puedo publicar comentarios pero sí puedo publicar una respuesta...
A partir de WordPress 3.1 (RC 1) la respuesta de Mike (que me había funcionado tan bien durante los últimos meses) ya no me funciona; restringir por cualquier taxonomía hija devuelve un resultado vacío.
Probé la actualización de Somatic y funcionó genial; aún mejor, funciona con consultas de taxonomía múltiple lo cual ha sido incorporado en esta versión.
