Advanced Permalinks and Rewrites by Example
by Matthew Boynes / @senyob / m@boyn.es
Alley Interactive / alleyinteractive.com
Website for car dealership
register_taxonomy( 'make', 'model', array(
'rewrite' => array(
'slug' => 'inventory'
)
) );
register_post_type( 'model', array(
'public' => true,
'rewrite' => array(
'slug' => 'inventory/%make%'
)
) );
Right now, the custom post type links
have the literal %make%
in them.
function modify_model_links( $link, $post ) {
if ( 'model' == $post->post_type ) {
if ( $makes = get_the_terms( $post->ID, 'make' ) ) {
return str_replace( '%make%', array_pop( $makes )->slug, $link );
}
}
return $link;
}
add_filter( 'post_type_link', 'modify_model_links', 10, 2 );
Any changes to rewrite rules require flushing them.
Psst, Matt, that's you.
Array key => value
Regular expression (the rule) => Redirect (default URL)
Since this is an array, note that you can't have the same rule twice
array(
'category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$'
=> 'index.php?category_name=$matches[1]&feed=$matches[2]',
'category/(.+?)/(feed|rdf|rss|rss2|atom)/?$'
=> 'index.php?category_name=$matches[1]&feed=$matches[2]',
'category/(.+?)/page/?([0-9]{1,})/?$'
=> 'index.php?category_name=$matches[1]&paged=$matches[2]',
'category/(.+?)/?$'
=> 'index.php?category_name=$matches[1]',
'tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$'
=> 'index.php?tag=$matches[1]&feed=$matches[2]',
'tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$'
=> 'index.php?tag=$matches[1]&feed=$matches[2]',
'tag/([^/]+)/page/?([0-9]{1,})/?$'
=> 'index.php?tag=$matches[1]&paged=$matches[2]',
'tag/([^/]+)/?$'
=> 'index.php?tag=$matches[1]',
'robots\.txt$'
=> 'index.php?robots=1',
'.*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\.php$'
=> 'index.php?feed=old',
# ...
)
What if we wanted to separate by body type?
Let's make body type a taxonomy, too.
register_taxonomy( 'body_type', 'model', array(
'rewrite' => array( 'slug' => 'inventory' )
) );
register_taxonomy( 'make', 'model', array(
'rewrite' => array( 'slug' => 'inventory/%body_type%' )
) );
register_post_type( 'model', array(
'public' => true,
'rewrite' => array( 'slug' => 'inventory/%body_type%/%make%' )
) );
function modify_model_links( $link, $post ) {
if ( 'model' == $post->post_type ) {
if ( $body_types = get_the_terms( $post->ID, 'body_type' ) ) {
$link = str_replace( '%body_type%', array_pop( $body_types )->slug, $link );
}
if ( $makes = get_the_terms( $post->ID, 'make' ) ) {
$link = str_replace( '%make%', array_pop( $makes )->slug, $link );
}
}
return $link;
}
add_filter( 'post_type_link', 'modify_model_links', 10, 2 );
Need to replace %body_type%
in the make URIs,
e.g. /inventory/%body_type%/.../
This is a little trickier... Why?
Which body type do we want to be the default?
Enter add_permastruct and our first real contact with the Rewrite API!
WordPress makes this super easy!
function add_permastruct( $name, $struct, $args = array() ) { }
add_permastruct( "all_makes", "inventory/all/%make%" );
register_taxonomy( 'make', 'model', array(
...
Caveat: Order is everything!
Must come before 'make' taxonomy
function modify_make_links( $termlink, $term, $taxonomy ) {
if ( 'make' == $taxonomy ) {
return str_replace( '%body_type%', 'all', $termlink );
}
return $termlink;
}
add_filter( 'term_link', 'modify_make_links', 10, 3 );
Always. Flush. Your. Rewrites.
Could we have simply done the following?
register_taxonomy( 'make', array(
'rewrite' => array(
'slug' => 'inventory/all'
)
) );
Yes and no. It would work in showing all body types, but wouldn't allow us to restrict by them.
Let's look at the last set of rewrite rules.
Website for WordCamps.
Each WordCamp has the same sections:
function add_rewrite_tag( $tag, $regex, $query = '' ) { }
add_rewrite_tag(
'%wc_section%',
'(schedule|speakers|sponsors|tickets|location)'
);
function add_rewrite_rule( $regex, $redirect, $after = 'bottom' ) { }
add_rewrite_rule(
'wordcamp/([^/]+)/(schedule|speakers|sponsors|tickets|location)/?$',
'index.php?wordcamp=$matches[1]&wc_section=$matches[2]',
'top'
);
wordcamp.php
wordcamp-schedule.php
wordcamp-speakers.php
wordcamp-sponsors.php
wordcamp-tickets.php
wordcamp-location.php
single-wordcamp.php:
<?php
get_template_part( 'wordcamp', get_query_var( 'wc_section' ) );
?>
Not 404 ways to break things
Or 418 ways to make tea
Change core permalinks, what happens to old ones?
Demo
Some permalink changes will automatically 301 old structures!
When? When the post name ends the old permalinks (some exceptions)
How? By selling your soul to the devil.
This magical, very cool function is what handles the auto 301s.
Page rewrite matches
Gets the "basename" of the page
e.g. /2013/07/21/something/
SELECT ID
FROM wp_posts
WHERE post_name LIKE 'something%'
AND post_status = 'publish'
Have you ever had WordPress redirect a URL to a post or page that made no sense at all?
This was probably why.
function recipe_4_2() {
if ( is_404() ) {
$redirects = wcpdx_get_redirects();
$matched_rule = false;
$request = wcpdx_get_reqest();
foreach ( (array) $redirects as $match => $query ) {
if ( preg_match("#^$match#", $request, $matches) ||
preg_match("#^$match#", urldecode($request), $matches) ) {
// Got a match.
$matched_rule = true;
break;
}
}
if ( $matched_rule ) {
$query = preg_replace("!^.+\?!", '', $query);
$redirect = addslashes(WP_MatchesMapRegex::apply($query, $matches));
if ( $redirect ) {
wp_redirect( home_url( "?$redirect" ), 301 );
exit;
}
}
}
}
add_action( 'template_redirect', 'recipe_4_2', 5 );