Define permalinks for custom post type by taxonomy

rgdigi picture rgdigi · Nov 25, 2012 · Viewed 19.4k times · Source

I want to create canonical permalinks for products and product types. I have the custom post types and custom taxonomy figured out, but I don't know if its possible to define permalinks with taxonomies. So my workflow, for example, would be this...

  1. I create a custom post type called products.
  2. Then I create a custom taxonomy for product types.
  3. Then I add a product type called 'Chairs' and add a product called 'Red Chair' to this category.

Once I create this product, the desired permalink structure to view this product would be formatted like so ->

http://shop.com/products/chairs/red-chair

Is this possible in wordpress 3.4? The meta boxes in my custom post type allow for selecting of the product types defined for my custom taxonomy, and there will only ever be one type per product.

If possible, I would also like to include any parents of the selected product category if possible (For example, if the 'chairs' category was a child of the 'lounge' category, the permalink structure would be as follows ->

http://shop.com/products/lounge/chairs/red-chair

Here is how I create the custom post type and the custom taxonomy, I just need help defining rewrite / slug rules to include product type in the permalink.

/* Custom Post Type - Products ------- */
function products_init() {
    $args = array(
        'public' => true,
        'label' => 'Products'
    );
    register_post_type( 'products', $args );
}
add_action( 'init', 'products_init' );

/* Custom Taxonomy - Product Type ------- */
add_action( 'init', 'create_prodtype' );
function create_prodtype() {
    $labels = array(
        'name' => _x( 'Product Type', 'products' ),
        'singular_name' => _x( 'Product Category', 'product' ),
        'search_items' =>  __( 'Search Product Types' ),
        'all_items' => __( 'All Product Types' ),
        'parent_item' => __( 'Products' ),
        'parent_item_colon' => __( 'Products:' ),
        'edit_item' => __( 'Edit Product Type' ),
        'update_item' => __( 'Update Product Type' ),
        'add_new_item' => __( 'Add New Product Type' ),
        'new_item_name' => __( 'New Product Type' ),
    );
    register_taxonomy(
        'products',
        array('products'),
        array(
            'rewrite' => array(
                'slug' => 'products',
                'hierarchical' => true
            ), 
            'with_front' => false,
            'labels' => $labels
    ));
}

Answer

rgdigi picture rgdigi · Nov 26, 2012

Solution

I have figured it out with the help of these posts by Jan Fabry ->

https://wordpress.stackexchange.com/a/5478/10350 https://wordpress.stackexchange.com/a/22490/10350

I have set up the custom post type as follows ->

  • Custom post type (register_post_type) -> "product"
  • Taxonomy (register_taxonomy) -> "product-type"

Back-end function

I rewrite the permalink structure that is saved in the back-end, so that the permalink is saved to include the custom taxonomy type - "product-type"

add_filter('post_type_link', 'product_type_permalink', 10, 4);
function product_type_permalink($post_link, $post, $leavename, $sample) {
    //If is custom post type "product"
    if ($post->post_type == 'product') {
        // Get current post object
        global $post;
        // Get current value from custom taxonomy "product-type"
        $terms = get_the_terms($post->id, 'product-type');
        // Define category from "slug" of taxonomy object
        $term = $terms[0]->slug;
        // Re-structure permalink with string replace to include taxonomy value and post name
        $permalink = str_replace('product/', 'product/' . $term . '/', $post_link);
    }
    return $permalink;
}

Set the permalink setting to 'Post name' and save. If you add a product to a category and save, it should re-write the permalink to include the customy taxonomy definition, in this case - "product-type". So if you add "Red Chair" to the "Chairs" category, the URL will be formatted as follows ->

http://website.com/product/chairs/red-chair/

But if you try to go to this page, you will get a 404 error. That is because wordpress doesn't yet know how to query the database with this URL, so you have to write it.

Front-end function

We need to add rewrite rules so that wordpress can take our url and query the database. We use the wordpress function add_rewrite_rule to translate the permalink given into a query string.

add_rewrite_rule(
    // The regex to match the incoming URL
    'product/([^/]+)/([^/]+)/?',
    // The resulting internal URL: `index.php` because we still use WordPress
    // `pagename` because we use this WordPress page
    // `designer_slug` because we assign the first captured regex part to this variable
    'index.php?product-type=$matches[1]&product=$matches[2]',
    // This is a rather specific URL, so we add it to the top of the list
    // Otherwise, the "catch-all" rules at the bottom (for pages and attachments) will "win"
    'top'
);

In this function, the matches array is defined by Wordpress exploding the given string at each slash. So in this example, the regular expression ([^/]+) is used to match anything in between each slash. In this example there are 2 levels, so it matches product type, then the product, and adds them to the matches array, where product-type = $matches1, and product = $matches[2].

This rewrite rule translates this ->

product/chairs/red-chair/

Into this ->

index.php?product-type=chair&product=red-chair

Which our database can use to query the database and return the correct product page with the formatted permalink.

This throws off product-type pages as there will only be one level in the url, product-type. This means that the rewrite rule will currently always try and identify the product name as defined in the query string. So for this we also write a single level rewrite rule:

add_rewrite_rule('^product/([^/]*)?$','index.php?product-type=$matches[1]','top');

This will now also query for product type pages so we can just loop through the taxonomy as normal if we want to display the various product types, without throwing 404 errors when we try and link to them.

Downside

This will currently only take a single level of taxonomy, so the custom taxonomy structure can not be hierarchical. If you specify more than one taxonomy, it will use the most one with the first ID to define the permalink. A potential workaround for this is to hide the custom taxonomy menu that appears in the side bar of custom post types, and add a meta box for taxonomy where only a select box can be used. I use the Meta Box plugin for this. (nb, this plugin doesn't have any admin menus, it allows you to write meta boxes for custom post types in your functions.php just by creating arrays - highly recommended!)