139

I have created a Custom Post Type called 'listing' and added a Custom Taxonomy called 'businesses'. I would like to add a dropdown list of Businesses to the admin list for the Listings.

Here is what this functionality looks like in admin list for Posts (I would like the same for my Custom Post Type):

Categories Dropdown in Posts

Here is my current code (And here is the same code on 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();
}

How can I do add a dropdown list of Businesses to the admin list for the Listings?

2

17 Answers 17

149

UPDATE: I've included a new complete answer but even so I've left my original response at the bottom to which the first few comments reference.


Hi @tarasm:

Although I said it shouldn't be hard it is a little involved. But before we dig into the code...

The Screenshots:

...let's check out some screen shots for the finished product:

Listings list page with No Filtering:

Listings list page with No Filtering
(source: mikeschinkel.com)

Listings list page With Filtering:

Listings list page With Filtering
(source: mikeschinkel.com)

The Code

So here we go... (Note: I used a singular form for the taxonomy name of business; I hope that matches your. From lots of experience with both WordPress and database development in the past I believe it is best to do it this way.)

Step #1: The restrict_manage_posts action hook.

First thing you need to do is to hook the restrict_manage_posts action which has no parameters and is called from /wp-admin/edit.php (in v3.0.1 that call is on line 378.) This will allow you to generate the drop down select at the appropriate location above the list of Listing posts.

<?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' =>  __("Show All {$business_taxonomy->label}"),
            'taxonomy'        =>  $taxonomy,
            'name'            =>  'business',
            'orderby'         =>  'name',
            'selected'        =>  $wp_query->query['term'],
            'hierarchical'    =>  true,
            'depth'           =>  3,
            'show_count'      =>  true, // Show # listings in parens
            'hide_empty'      =>  true, // Don't show businesses w/o listings
        ));
    }
}

We start by checking the $typenow variable to ensure we are in fact on a post_type of listing. If you don't you'll get this drop down for all post types which in some cases is what you want, but not this case.

Next we load information about the business taxonomy using get_taxonomy(). We need it to retrieve the label for the taxonomy (i.e. "Businesses"; we could have hard-coded, but that's not very good if you need to internationalize later.) Then we call wp_dropdown_categories() with all the appropriate arguments in the $args array to generate the drop down

<?php
return wp_dropdown_categories(array(
    'show_option_all' =>  __("Show All {$business_taxonomy->label}"),
    'taxonomy'        =>  $taxonomy,
    'name'            =>  'business',
    'orderby'         =>  'name',
    'selected'        =>  $wp_query->query['term'],
    'hierarchical'    =>  true,
    'depth'           =>  3,
    'show_count'      =>  true, // Show # listings in parens
    'hide_empty'      =>  true, // Don't show businesses w/o listings
));

But what are the appropriate arguments? Let's look at each individually:

  • show_optional_all - Pretty straightforward, it's what is displayed in the drop down at first and when there has been no filtering applied. In our case it's going so be "Show All Businesses" but we could have called it "Listings for All Businesses" or whatever you like.

  • taxonomy - This arguments tells the function what taxonomy to pull terms from even though the function has categories in its name. In v2.8 and earlier WordPress didn't have custom taxonomies but when they were added the team decided it would just be easier to add an taxonomy argument to this function than to create another function with another name.

  • name - This argument allows you to specify the value that WordPress with use for the name attribute of the <select> element generated for the drop down. Just in case it isn't obvious this is also the value that will be used in the URL when filtering.

  • orderby - This argument tells WordPress how to order the results alphabetically. In our case we specified to order buy the name of the terms in the taxonomy, i.e. the business names in this case.

  • selected - This argument is needed so that the drop down can show the current filter in the drop down. It should be the term_id from the selected taxonomy term. In our case it might be the term_id from "Business #2". Where do we get this value? From WordPress' global variable $wp_query; it has a property query that contains an array of all the URL parameters and their values (unless some wayward plugin modified it already, of course.) Given how WordPress processes things there will be a term URL parameter passed on the URL when the user clicks the filter button if the user selected a valid term (i.e. one of the listed businesses).

  • hierarchical - By setting this to true you tell the function to respect the hierarchical nature of the taxonomy and display them in a tree view if the terms (businesses) in fact have children. For a screen shot to see what this looks like, see below.

  • depth - This argument collaborates with the hierarchical argument to determine how many levels deep the function should go in displaying children.

  • show_count - If true this argument will display a count posts within parentheses to the left of the term name within the drop down. In this case it would display a count of listings associated with a businesses. For a screen shot to see what this looks like, see below.

  • hide_empty - Finally, if there are terms in the taxonomy that are not associated with a post (i.e. businesses not associated with a listing) then setting this to true will omit them from being included in the drop down.

Taxonomy Drop Down should hierarchy and counts
(source: mikeschinkel.com)

Step #2: The parse_query filter hook.

Next we call our attentions to the parse_query filter hook which has one parameter ($query) and is called from /wp-includes/query.php (in v3.0.1 that call is on line 1549.) It is called when WordPress has finished inspecting the URL and setting all appropriate values in the currently active $wp_query including things like $wp_query->is_home and $wp_query->is_author, etc.

After the parse_query filter hook runs WordPress will call get_posts() and load up a list of posts based on what is specified in the currently active $wp_query. So parse_query is often a great place to get WordPress to change it's mind about which posts it is going to load.

In your use-case we want to get WordPress to filter based on the businesses selected; i.e. to display only those Listings which have been associated with the the selected business (I'd say "...only those Listings that have been "categorized" by the selected business" but that's not technical correct; category is it's own taxonomy on peer with business except that category is built into WordPress and business is custom. But for those familiar with categorizing posts this may help you understand as they work almost identically. But I digress...)

On to the code. The first thing we do is grab a reference to the currently active $wp_query's query_vars so that it's more convenient to work with, just like how its done within WordPress' own parse_query() function. Unlike $wp_query->query which is used to mirror the parameters passed on the URL the $wp_query->query_vars array is used to control the query that WordPress runs and is expected to be modified. So if you need to modify one, that'd be the one (at least I think that is the different between the two; if anyone knows otherwise please let me know so I can update this!)

<?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;
    }
}

Next we test $pagenow to ensure that we are indeed loading WordPress from the URL path /wp-admin/edit.php. We do this to keep from accidentally screwing up queries on other pages. We also check to make sure that we have both business as a taxonomy element and a term element too. (Note taxonomy and term are a pair; they are used together to allow querying of a taxonomy term; gotta have both or WordPress doesn't know which taxonomy to inspect.)

You might wonder how business turned up in the taxonomy element of the query_vars array. What we wrote in our parse_query hook triggered WordPress' internal magic that was laid in waiting when you registered the "business" taxonomy by setting query_var to be true (register_taxonomy() copies the name of the taxonomy as its query_var; you can change it of course but unless you have a conflict it's best to stick with the same):

<?php
add_action('init','register_business_taxonomy');
    function register_business_taxonomy() {
        register_taxonomy('business',array('listing'),array(
        'label' => 'Businesses',
        'public'=>true,
        'hierarchical'=>true,
        'show_ui'=>true,
        'query_var'=>true
    ));
}

Now WordPress' $wp_query was written to use slugs for standard taxonomy filtered queries, not taxonomy term IDs. For this use-case what we really need to make our filtering query work are these:

taxonomy: business

term: business-1 (i.e. the slug)

Not these:

taxonomy: business

term: 27 (i.e. the term_id)

Interestingly and unfortunately the drop-down generated by wp_dropdown_categories() set the <option>'s value attribute to the term's(/business') term_id, not the term slug. So we need to convert $wp_query->query_vars['term'] from a numeric term_id to it's string slug as following in the snippet snagged from above (Note this is not the most performant way to query a database but until WordPress adds support for term_ids into its query that's the best we can do!):

<?php
$term = get_term_by('id',$qv['term'],'business');
$qv['term'] = $term->slug;

And that's it! With those two functions you get the filtering you desire.

BUT WAIT, THERE'S MORE! :-)

I went ahead and added a "Businesses" column to your Listing list because, well, I knew it was going to be your next question. Without having a column for what you filter it can be very confusing for the end user. (I struggled with it myself, and I was the coder!) You can of course already see the "Businesses" column in the prior screen shots above.

Step #3: The manage_posts_columns filter hook.

To add a column to the post list takes calling two (2) more hooks. The first one is manage_posts_columns or the post type-specific version manage_listing_posts_columns that I called instead. It accepts one parameter (posts_columns) and is called from /wp-admin/includes/template.php (in v3.0.1 that call is on line 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'] = 'Businesses';
    return $new_posts_columns;
}

Your manage_posts_columns hook function gets passed an array of columns where the value is the displayed column header and the key is the internal column identifier. Standard column identifiers can include these and more: 'cb', 'title', 'author', ``'date'`, etc.

'cb', is the checkbox column and both 'title' and 'date' refer to post_title and post_date from the wp_posts table, respectively. 'author' of course is the post_author field after the author name is retrieved from the wp_users table.

Screenshot of the 'cb' posts column as a checkbox.
(source: mikeschinkel.com)

For the manage_posts_columns hook we are simply wanting to insert our column businesses into the $posts_columns array before 'author', assuming some other plugin hasn't removed author from the list yet!

$new_posts_columns['businesses'] = 'Businesses';

(Note as I wrote add_businesses_column_to_listing_list() it occurred to me that PHP must have an easier way to insert a value into an associative array in the proper order?!? Or at least there's got to be a function in WordPress core to do it? But since Google let me down so I went with what worked. If anyone has any suggested alternatives I'll be all ears and appreciative in advance!)

Which finally brings us to...

Step #4: The manage_posts_custom_column action hook

The second thing of two (2) we need to do to make our businesses display in the column is to actually output the name of each of the associated businesses using the manage_posts_custom_column action hook. This hook accepts two (2) parameters (column_id and post_id) and is also called from /wp-admin/includes/template.php (in v3.0.1 that call is on line 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;
        }
    }
}

This hook is called for each column for each post(/business) row. We first verify that we are indeed working with only the listing custom post type and then we use a switch statement to test against the column_id. I chose switch because this hook is often used to generate output for many different columns, especially if we use one function for many different post types which might look something like this:

<?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 '...whatever...';
        break;
    case 'listing:property':
        echo '...whatever...';
        break;
    case 'agent:listing':
        echo '...whatever...';
        break;
    }
}

Inspecting our use-case just a little closer you see the get_the_terms() function which simply returns the list of terms for this taxonomy (i.e. businesses for this listing.) Here get the permalink for the term's front-end web page that normally lists posts that are associated with the term but of course could might differently depending on the theme and/or plugins installed.

We use the permalink to hyperlink the term just because I like to hyperlink things. We then merge all the hyperlinked terms(/businesses) together separated with the pipe ('|') character and output to the PHP buffer which sends it to the user's browser/HTTP client:

<?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);
}

NOW we are finally done.

Summary

So in summary you need to use the following four (4) hooks to get both a filter and a related column in the custom posts list page (Oh, it will also work with Posts and Pages.) They are:

  • Step #1: The restrict_manage_posts action hook.
  • Step #2: The parse_query filter hook.
  • Step #3: The manage_posts_columns filter hook.
  • Step #4: The manage_posts_custom_column action hook

Where to download the code

But if I forced you to read through all the above then I would certainly not be a very nice person if I also made you dig out the code just to be able to try it out! But contrary to what some people say, I am nice. So here ya go:

NOTE to @tarasm: I included hooks for a register_post_type() and register_taxonomy() so that others could try this out without having to recreate them. You'll probably want to delete those two function calls before you test this out.

THE END


Original Response:

Hi @tarasm:

Are you looking for one drop down at the top like this screen or are you looking for one drop down per post record and if so how would you expect the latter to work?

How to Create Sort By functionality for a Custom Post Type in the WordPress Admin
(source: mikeschinkel.com)

If the former, take a look at this answer to the question How to sort the admin area of a Wordpress custom post type by a custom field? If that is what you need I can provide more specifics related to taxonomy.

11
  • I'm looking for 1 dropdown at the top that would show Categories filter. I was wander if there is a standard way of doing this without having to write custom code. Aug 19, 2010 at 17:04
  • 2
    Actually both solution(somatic and MikeSchinkel) is not working when you try to filter 2 different taxonomy in same filter :-/ Always filtering latest taxonomy when try to filter 2+ at same time. Nov 3, 2010 at 3:24
  • 1
    @Ünsal The current version of WordPress (3.0) doesn't support multiple Taxonomy queries but from what I've heard that'll change with version 3.1. To make this example work with multiple taxonomies, you'd have to add some joins and wheres to the query via the Posts_join and posts_where filter hooks. Jan 5, 2011 at 16:07
  • 1
    In WP 3.1+, steps one and two are better in @drew-gourley answer (actually, your step 2 didn't work for me, I think there are changes in this filtering in new WordPress). Dec 3, 2011 at 10:10
  • 1
    I would point out that as of wordpress v3.5, you can add taxonomy columns by simply add 'show_admin_column' => true to register_taxonomy params. Sep 15, 2017 at 9:06
46

Just wanted to share an alternative implementation. I didn't have Mike's incredible tutorial when I was figuring this out, so my solution is a little different. Specifically, I'm going to simplify Mike's step #1 and eliminate step #2 - the other steps are still applicable.

In Mike's tutorial, using wp_dropdown_categories() saves us some manual list-building, but requires some complicated conditional query modification (step #2) to handle its use of ID instead of slug. Not to mention the difficulty of modifying that code to deal with other scenarios, like multiple taxonomy filters..

Another approach is to simply not use the flawed wp_dropdown_categories() at all, but instead to build our own dropdown select lists from scratch. It's not that complicated, takes less than 30 lines of code, and doesn't require hooking parse_query at all:

add_action( 'restrict_manage_posts', 'my_restrict_manage_posts' );
function my_restrict_manage_posts() {

    // only display these taxonomy filters on desired custom post_type listings
    global $typenow;
    if ($typenow == 'photos' || $typenow == 'videos') {

        // create an array of taxonomy slugs you want to filter by - if you want to retrieve all taxonomies, could use get_taxonomies() to build the list
        $filters = array('plants', 'animals', 'insects');

        foreach ($filters as $tax_slug) {
            // retrieve the taxonomy object
            $tax_obj = get_taxonomy($tax_slug);
            $tax_name = $tax_obj->labels->name;
            // retrieve array of term objects per taxonomy
            $terms = get_terms($tax_slug);

            // output html for taxonomy dropdown filter
            echo "<select name='$tax_slug' id='$tax_slug' class='postform'>";
            echo "<option value=''>Show All $tax_name</option>";
            foreach ($terms as $term) {
                // output each select option line, check against the last $_GET to show the current option selected
                echo '<option value='. $term->slug, $_GET[$tax_slug] == $term->slug ? ' selected="selected"' : '','>' . $term->name .' (' . $term->count .')</option>';
            }
            echo "</select>";
        }
    }
}

By simply plugging the desired taxonomies into the $filters array, you can quickly output multiple taxonomy filters. They appear exactly the same as those in Mike's screenshots. Then you can follow through with step #3 and #4.

6
  • 4
    @somatic - Nice update! Yes, using wp_dropdown_categories() does require a lot of workarounds. I try to stick with core functions when possible but as you point out sometimes it takes more work that way. Just goes to prove that with WordPress there is often more than one good way to solve a problem. Good job! Oct 23, 2010 at 9:53
  • Just stopped working for me on WordPress 3.1. Trying to figure out what exactly changed. It looks like it should still work: the taxonomy and term slugs show up as GET values in the url, but all it results in is 0 results Feb 24, 2011 at 6:40
  • I've been trying to get this working but the only way I could was to use the parse_query hook, check for the taxonomy's query var and set the taxonomy and term query vars based on that. Using WP 3.1. Should taxonomy & term be appearing in the URL when the filter is submitted? Mar 22, 2011 at 17:36
  • 2
    Works like a charm for me! Very elegant solution indeed. I owe you a beer :)
    – Michal Mau
    Apr 26, 2011 at 11:31
  • 1
    @somatic This works great but is there a way to have the $term->count only count the terms for that post type? For example if I have a custom taxonomy for both photos and videos it will show me when looking at the videos custom post type the total number of posts for that term from both custom post types instead of just the total number of video posts that use that term.
    – Greenhoe
    Jun 11, 2015 at 13:40
13

Here is a version of this that automatically creates and applies filters from all taxonomies that apply to all custom post types which use them. (what a mouthful) Anyways, I also tweaked it so it works with wp_dropdown_categories() and wordpress 3.1. The project I'm working on is called ToDo, you can rename the functions to something that makes sense for you, but this should pretty much work for everything automatically.

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' => __('Show All '.$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');

Note that I'm using a plugin that adds 'term_order' as a way to order terms, you'll have to change that, or remove that argument to fallback to the default.

1
  • very sexy indeed. i was getting error notices, so i changed if ( isset($var)) to if ( isset($var) && $var>0) to avoid trying to find terms associated with the View all's 0 value. oh, and i had to return $query in the todo_convert_restrict function Jan 7, 2012 at 22:17
11

Late answer

Edit

I've written Filterama, a plugin that will add this functionality the easiest possible way.

Update for WordPress 3.5+

Now that things are much easier, here's a very simple solution as plugin or mu-plugin.

It uses as less resources as possible, loads only on the needed screens and adds Columns + Filters for every custom taxonomy.

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,
            ) );
        }

    }

}

And then you just need a customized Walker class.

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( '&nbsp;', $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']
                ? "&nbsp;&nbsp;({$term->count})"
                : ''
        );
    }
}
7
  • Gave this a whirl, but the get_select() method seems to be missing. Mar 15, 2013 at 15:40
  • @goto10 You were right. Updated. Btw: It's easier to just grab the linked plugin. It'll be available in the plugin repository in one or two weeks. (Already confirmed).
    – kaiser
    Mar 16, 2013 at 11:09
  • I had to use $this->setup_vars(); at the beginning of public function setup() in order to have "manage_taxonomies_for_{$this->post_type}_columns" working
    – Christian
    Jul 2, 2013 at 11:44
  • But it could be because I use it in a Theme function.php with add_action( 'init', array( 'WCM_Admin_PT_List_Tax_Filter', 'init' ) );
    – Christian
    Jul 2, 2013 at 11:52
  • @Christian This isn't theme material. This belongs into a plugin and as code above currently stands it loaded far before Themes are loaded.
    – kaiser
    Jul 2, 2013 at 12:49
7

I just wanted to make a quick note. On newer versions of WP, post listings on admin are handled by the WP_Posts_List_Table class. The apply_filters code is now as follows:

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 );

So to add new columns an add_filter hook should be like this:

add_filter( 'manage_posts_columns', 'my_add_columns', 10, 2);

Here goes an example:

function my_add_columns($posts_columns, $post_type)
{
  if ('myposttype' == $post_type) {
    $posts_columns = array(
      "cb"            => "<input type=\"checkbox\" />",
      "title"         => "Title",
      "anothercolumn" => "Bacon",
      "date"          => __( 'Date' )
    );
    return $posts_columns;
  }
} 

Now, for the post rows. This is the code that handles column data on the listings:

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

In order to retrieve our posts data, we have to add an action hook like this:

add_action( "manage_(here_goes_your_post_type)_posts_custom_column", "my_posttype_add_column", 10, 2);

Example (this example uses taxonomies, but you could query any other stuff):

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;
  }
}
7

WORKS IN WP 3.2!

custom_post_type: books custom_taxonomy: genre

Only modify were it says: // change HERE

function restrict_books_by_genre() {
    global $typenow;
    $post_type = 'books'; // change HERE
    $taxonomy = 'genre'; // change HERE
    if ($typenow == $post_type) {
        $selected = isset($_GET[$taxonomy]) ? $_GET[$taxonomy] : '';
        $info_taxonomy = get_taxonomy($taxonomy);
        wp_dropdown_categories(array(
            'show_option_all' => __("Show All {$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 = 'books'; // change HERE
    $taxonomy = 'genre'; // change HERE
    $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');
2
  • That a nice and easy solution for WP 3.2+. Jul 30, 2015 at 10:27
  • it works but __("Show All {$info_taxonomy->label}") is a wrong way to use translatable strings. Aug 23, 2017 at 5:24
4

This is not well known I guess, but as of wordpress 3.5, you can pass 'show_admin_column' => true to register_taxonomy. This does 2 things:

  1. Adds the taxonomy column to the admin post type list view
  2. By clicking the name of the term on the taxonomy column, it will in fact filter the list to that term.

So, not exactly the same as having a select, but almost the same functionality, width just one row of code.

https://make.wordpress.org/core/2012/12/11/wordpress-3-5-admin-columns-for-custom-taxonomies/

Also, as you can read, there is a new filter tailored for manual adding taxonomy column (if you really need to).

4

I had to work on a similar feature not so long ago, as I wasn't satisfied with the answers, here is my alternative. Year is 2021.

I wanted something dumb and simple to implement. Quickly and efficiently getting all custom taxonomies from each custom post type and adding them as filters options.

There was a few prerequisite:

  • I wanted to step away from using global variables.
  • Code needed to be portative, no hardcoded variables, to be reusable.
  • Having some kind visible hierarchy, perfect for heavy terms related taxonomies.
  • Needed to respect taxonomy visibility choice. (eg: show_admin_column argument parameter from the register_taxonomy function.
  • Displaying post count.
  • The overall appearance to be matching with WordPress.

I've made it into a plugin, for an easier integration, you can get the latest version @ https://wordpress.org/plugins/cpt-admin-taxonomy-filtering/.

Filter with term hierarchy & posts count Multiple custom post type filters
x500-1 x500-2

If you just want to add it to your function.php, copy and paste the following script.

Requires at least WordPress 4.8.0 & 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() {

        /**
         * Get the current screen object.
         * 
         * @link https://developer.wordpress.org/reference/functions/get_current_screen/
         */
        $screen = get_current_screen();

        // Single out WordPress default posts types
        $restricted_post_types = array(
            'post',
            'page',
            'attachment',
            'revision',
            'nav_menu_item',
        );

        if ( 'edit' === $screen->base && ! in_array( $screen->post_type, $restricted_post_types ) ) {

            /**
             * Return the names or objects of the taxonomies which are registered for the requested object or object type, such as a post object or post type name.
             * 
             * @link https://developer.wordpress.org/reference/functions/get_object_taxonomies/
             */
            $taxonomies = get_object_taxonomies( $screen->post_type, 'objects' );

            // Loop through each taxonomy
            foreach ( $taxonomies as $taxonomy ) {

                if ( $taxonomy->show_admin_column ) {

                    /**
                     * Displays or retrieves the HTML dropdown list of categories. 
                     * 
                     * @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,
                        )
                    );

                };

            };

        };

    };

};
1
  • 1
    This works really well, thanks!
    – Gavin
    Jul 17, 2021 at 2:25
3

Thank you guys for all the answers. I know this an old question, but I would like to add the more simple solution that can be used nowadays. This will only require 2 steps and will use more of the available core features. Hope this will help someone.

Step 1: add 'show_admin_column' => true to your register_taxonomy function.

This will add a column to the list view containing links to filter the list by that term. This can be applied for both hierarchical and non-hierarchical taxonomies.

Example:

register_taxonomy( 'custom_cat', array( 'custom_post_type' ), array(
    'hierarchical'      => true,
    'labels'            => array(
        'name'              => __( 'Categories', 'iside' ),
        'singular_name'     => __( 'Category', 'iside' ),
        'search_items'      => __( 'Search Categories', 'iside' ),
        'all_items'         => __( 'All Categories', 'iside' ),
        'parent_item'       => __( 'Parent Category', 'iside' ),
        'parent_item_colon' => __( 'Parent Category:', 'iside' ),
        'edit_item'         => __( 'Edit Category', 'iside' ),
        'update_item'       => __( 'Update Category', 'iside' ),
        'add_new_item'      => __( 'Add New Category', 'iside' ),
        'new_item_name'     => __( 'New Category', 'iside' ),
        'menu_name'         => __( 'Categories', '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, // <-- the magic applied here
    'query_var'         => true,
) );

Step 2: add the taxonomy dropdown through the restrict_manage_posts filter

This will add a select dropdown to the filter options on top. It will use the default wp_dropdown_categories function to make it easier. Please note that we need to set the name of the dropdown to the query_var we use for the taxonomy. Also note that the value_field needs to be set to the slug (by default this will be the term_id).

Example:

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' =>  __( 'Show All Categories', 'iside' ),
            'taxonomy'        =>  'custom_cat',
            'name'            =>  'custom_cat', // <-- make sure it will match the query_var
            'orderby'         =>  'name',
            'selected'        =>  $selected,
            'hierarchical'    =>  true,
            'depth'           =>  3,
            'show_count'      =>  true,
            'hide_empty'      =>  true,
            'value_field'     => 'slug', // <-- use slug instead of term_id
        ));
    }
}

That's it. No need to add more :-)

2
  • 1
    Very nice and simple, thanks. One point to note - you have an error in your syntax here (there's an extra bracket) : $selected = isset( $_GET['custom_cat'] ) ? esc_attr($_GET['custom_cat') ]) : 0; which should instead be as follows: $selected = isset( $_GET['custom_cat'] ) ? esc_attr($_GET['custom_cat' ]) : 0; May 27, 2023 at 6:05
  • Thanks @SolaceBeforeDawn. I have edited my answer
    – leendertvb
    May 27, 2023 at 20:30
2

Here's a way to do it using the restrict_manage_posts action. It seems to work well for me and adds the ability to filter by taxonomy for all post types and there related taxonomies.

// registers each of the taxonomy filter drop downs
function sunrise_fbt_add_taxonomy_filters() {
    global $typenow;            // the current post type
    $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) {
              //Check if hierarchical - if so build hierarchical drop-down
              if($tax->hierarchical) {
                $args = array(
                      'show_option_all'    => 'All '.$tax->labels->name,
                      'show_option_none'   => 'Select '.$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=''>Show All ".$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 );

/**
 * Create HTML dropdown list of Categories.
 *
 * @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('&nbsp;', $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 .= '&nbsp;&nbsp;('. $category->count .')';
        if ( $args['show_last_update'] ) {
            $format = 'Y-m-d';
            $output .= '&nbsp;&nbsp;' . gmdate($format, $category->last_update_timestamp);
        }
        $output .= "</option>\n";
        }
} 

One note - I tried restricting the depth because some of my hierarchical taxonomies are quite large but it didn't work - could be a bug in the wp_dropdown_categories function?

1

Hierarchical version of @somatic's answer, as requested by @kevin:

<?php
add_action( 'restrict_manage_posts', 'my_restrict_manage_posts' );
function my_restrict_manage_posts() {

    // only display these taxonomy filters on desired custom post_type listings
    global $typenow;
    if ($typenow == 'photos' || $typenow == 'videos') {

        // create an array of taxonomy slugs you want to filter by - if you want to retrieve all taxonomies, could use get_taxonomies() to build the list
        $filters = array('plants', 'animals', 'insects');

        foreach ($filters as $tax_slug) {
            // retrieve the taxonomy object
            $tax_obj = get_taxonomy($tax_slug);
            $tax_name = $tax_obj->labels->name;

            // output html for taxonomy dropdown filter
            echo "<select name='$tax_slug' id='$tax_slug' class='postform'>";
            echo "<option value=''>Show All $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) {
        // output each select option line, check against the last $_GET to show the current option selected
        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);
    }

}
?>

I basically removed the code that created the options and put that in it's own function. The function 'generate_taxonomy_options', in addition of taking the tax_slug, also takes a parent and level parameter. The function assumes that its creating options for the parent 0, which will select all the root level terms. In the loop the function will recursively call itself, using that current term as a parent and increasing the level by one. It automatically adds ticks to the side the deeper you go down the tree and voila!

0
1

Update of @Drew Gourley's answer for WP 3.3.1 (and incorporating code from 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' => __('Show All '.$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()
            ));
        }
    }
}


//Dropdown filter class.  Used with wp_dropdown_categories() to cause the resulting dropdown to use term slugs instead of ids.
class DropdownSlugWalker extends Walker_CategoryDropdown {

    function start_el(&$output, $category, $depth, $args) {
        $pad = str_repeat('&nbsp;', $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";
    }
}
1

Simply add this to your functions.php or plugin code (and replace YOURCUSTOMTYPE and YOURCUSTOMTAXONOMY). (I got the code from generatewp)

add_action( 'restrict_manage_posts', 'filter_backend_by_taxonomies' , 99, 2);

function filter_backend_by_taxonomies( $post_type, $which ) {
    // Apply this to a specific CPT
    if ( 'YOURCUSTOMTYPE' !== $post_type )
        return;
    // A list of custom taxonomy slugs to filter by
    $taxonomies = array( 'YOURCUSTOMTAXONOMY' );
    foreach ( $taxonomies as $taxonomy_slug ) {
        // Retrieve taxonomy data
        $taxonomy_obj = get_taxonomy( $taxonomy_slug );
        $taxonomy_name = $taxonomy_obj->labels->name;
        // Retrieve taxonomy terms
        $terms = get_terms( $taxonomy_slug );
        // Display filter HTML
        echo "<select name='{$taxonomy_slug}' id='{$taxonomy_slug}' class='postform'>";
        echo '<option value="">' . sprintf( esc_html__( 'Category', '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>';
    }
}
1
0

Apologies for the fact that, as a new user, I can't post comments but I can post an answer...

As of WordPress 3.1 (RC 1) Mike's answer (which has served me so well for the past couple for months) no longer works for me; restricting by any taxonomy child gives an empty result. I tried Somatic's update and it worked great; even better, it works with multiple taxonomy queries which has been worked into this release.

1
  • For some reason somatic's version doesn't work either for 3.1 Feb 24, 2011 at 6:17
0

Just tried both codes, from Mike and somatic and was wondering on how to get one thing from each technique:

With Mike's code, it shows the dropdown list with the hierarchical option, which helps a lot. But in order to display two dropdowns i had to duplicate the if ($typenow=='produtos') {...} statement in the function restrict_listings_by_business() and also the if ($pagenow=='edit.php' && ... } in the convert_business_id_to_taxonomy_term_in_query($query) function which now gives a lot of code.

With somatic's code i just need to specify the taxonomies i'd like to see as dropdowns and bam, works; $filters = array('taxo1', 'taxo2');

Question: can i get somatic's approach and also have the hierarchical option?

Thanks a lot anyway for this tutorial, helped a lot!

1
  • Se my answer for a hierarchical solution Jan 8, 2011 at 6:02
0

Mike's tutorial on this is great! I probably would not have bothered adding this functionality to my Media Categories plugin if I would have had to figure it out on my own.

That said, I think using parse_query and then get then query for the term isn't necessary. It's cleaner for create your own custom walker class. Maybe that wasn't possible when he wrote his post - its 3 years old at the time of my writing this.

Checkout this great snippet on github. Works like a charm, changes the ID's in the dropdown values into slugs, so it just works natively without modifying the query.

https://gist.github.com/stephenh1988/2902509

0

This is a powerful little snippet to allow filtering your post or custom post type by a specific taxonomy term in the WordPress admin.

/* Filter CPT via Custom Taxonomy */
function rave_core_backend_by_portfolio_taxonomies( $post_type, $which ) {
    // Apply this to a specific CPT
    if ( 'portfolio' !== $post_type )
        return;

    // A list of custom taxonomy slugs to filter by
    $taxonomies = array( 'portfolio_cat' );

    foreach ( $taxonomies as $taxonomy_slug ) {

        // Retrieve taxonomy data
        $taxonomy_obj = get_taxonomy( $taxonomy_slug );
        $taxonomy_name = $taxonomy_obj->labels->name;

        // Retrieve taxonomy terms
        $terms = get_terms( $taxonomy_slug );

        // Display filter HTML
        echo "<select name='{$taxonomy_slug}' id='{$taxonomy_slug}' class='postform'>";
        echo '<option value="">' . sprintf( esc_html__( 'All %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);

Not the answer you're looking for? Browse other questions tagged or ask your own question.