SlideShare a Scribd company logo
1 of 92
1
Hardcore URL Routing
for WordPress
Mike Schinkel
@mikeschinkel
about.me/mikeschinkel
WordCamp Atlanta - March 15th, 2014
2
Me
• WordPress Platform Architect
• yada, yada
3
To Cover
• URLs We Love
• How Do URLs Get Routed?
• How to see your Rewrite Rules
• Review the Rewrite API Functions & Hooks
• Ways to Add Rewrite Rules
• flush_rewrite_rules() & generate_rewrite_rules()
• Full-control Manual URL Routing
• Link Generation: Beyond Routing
• Other Stuff we Won't Cover
4
Download the PDF!
Because there's SOOO much to cover
I'm going to be going fast & furious
So you'll need the PDF to follow along.
http://hardcorewp.com/2014/hardcore-url-routing-for-wordpress/
5
But First...!
People say using the
WordPress Rewrite API
is HARD!
Well...
6
There Be
Dragons!
The WordPress
Rewrite API: Not
for the faint of
heart! 6
7
But...
If you apply yourself,
If you work dillegently to
wrap your head around the
WordPress Rewrite API,
Then you can become...
8
Like These!
From my current favorite TV show: The Walking Dead8
9
SeriouslyThough:
The WordPress Rewrite API
is obtuse in many ways.
Your goal today will be to learn it.
And learn how to get around it.
10
• /products/{product_name}/details/
• Post type => 'product'
• /products/{prod_cat}/{product_name}/
• Post type => 'product', Taxonomy ('prod_cat') term
• /{permalink_path}/json/
• A JSON version of your post
• /comments/{year}/{monthnum}/{day}/
• Archive of all comments not related to a specific post
• /api/{api_request}/
• “Virtual” URL for Custom code
URLs We Love
11
• /{automaker}/
• Post type => 'automaker'
• /{automaker}/{model}/
• Post type => 'automaker', Post type => 'model'
• /{user_nicename}/
• Specified user
• Other?
• Taking requests....
More URLs We Love
12
• WP Matches URL Path to a Rewrite Pattern
• Rewrite Patterns are Regular Expressions
• Example URL Path:
• /2014/03/15/hello-world/
• Matching Regular Expression
• ([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)(/[0-9]+)?/?$
• Rewrite Pattern:
• index.php?year=$matches[1]&monthnum=$matches[2]&
day=$matches[3]&name=$matches[4]&page=$matches[5]
• Resultant Query String:
• index.php?year=2014&monthnum=03&day=15&name=hello-world&page=
How Do URLs Get Routed?
13
index.php in Rewrite Pattern?
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
See: .htaccess:*
* If on IIS then web.config which has a different format.
14
• Stored in $wp->query_vars
• Processed by WP_Query() for The Loop
The Resultant Query String
// Not exactly this, but similar
$query_vars = 'year=2014&monthnum=03&day=15&name=hello-world&page=';
$query= new WP_Query( $query_vars );
$query = new WP_Query( array(
'year' => 2014,
'monthnum' => 3,
'day' => 15,
'name' => 'hello-world',
'page' => '',
));
• Essentially the same as this:
15
See your Rewrite Rules
<?php
/*
* Filename: /rewrite-rules.php
* REMEMBER TO SET YOUR PERMALINKS! Settings > Permalinks
*/
include __DIR__ . '/wp-load.php';
$rewrite_rules = get_option( 'rewrite_rules' );
header( 'Content-type: text/plain;' );
print_r( $rewrite_rules );
16
Rewrite Rules, in aTable!
<?php
/*
* Filename: /rewrite-rules2.php
* REMEMBER TO SET YOUR PERMALINKS! Settings > Permalinks
*/
include __DIR__ . '/wp-load.php';
$rewrite_rules = get_option( 'rewrite_rules' );
?>
<html>
<head>
<title>Rewrite Rules</title>
<style type="text/css"> th { text-align: left; } </style>
</head>
<body>
<table>
<?php
foreach( $rewrite_rules as $regex => $pattern ) {
echo "<tr><th>{$regex}</th><td>{$pattern}</td></tr>";
}
?>
</table>
</body>
</html>
17
Best Way to SeeYour Rulez
• Monkeyman Rewrite Analyzer
• http://wordpress.org/plugins/monkeyman-rewrite-analyzer/
18
The WordPress Rewrite API
• Rewrite API Functions
• Related Functions
• Rewrite API Hooks
• Related Hooks
19
The Rewrite API Functions
• register_post_type()* and register_taxonomy()*
• flush_rewrite_rules()
• add_rewrite_rule() and add_rewrite_tag()
• get_query_var()*
• register_activation_hook() and
register_deactivation_hook()
*Related functions
20
The Rewrite API Hooks
• 'parse_request'
• 'request'
• 'after_switch_theme'*
• 'template_redirect'*
• 'template_include'*
• 'redirect_canonical'*
• 'do_parse_request'
• 'query_var'
*Related Hooks
21
Different Ways to Add Rules
• Adding Rewrite Rules Indirectly
• Adding Rewrite Tags & Rules
• Validating Rewritten URLs
• Adding EndPoints
• Adding Permastructs
• Adding External Rules
22
Adding Rules Indirectly
/products/{product_name}/
<?php // functions.php
add_action( 'init', 'x_init' );
function x_init() {
register_post_type( 'x_product', array(
'public' => true,
'label' => __( 'Products', 'X' ),
'rewrite' => array(
'slug' => 'products',
),
));
}
23
About the 'x_' Prefix
See: http://nacin.com/2010/05/11/in-wordpress-prefix-everything/
24
I just used 'x_' (FOR THIS TALK, ONLY) because it was short!
25
Always Remember to Flush!
flush_rewrite_rules( $hard_or_soft )
add_action( 'init', 'x_init' );
function x_init() {
// Call register_post_type() here
if ( WP_DEBUG ) {
if ( ! function_exists( 'save_mod_rewrite_rules' ) ) {
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/misc.php' );
}
flush_rewrite_rules( true );
}
}
• true (hard) - Writes changes to .htaccess/web.config
• false (soft) - Does not writes changes.
26
Flushing in a Plugin
<?php
/**
* Plugin Name: Hardcore URLs
*/
class Hardcore_Urls {
static function on_load() {
add_action( 'init', array( __CLASS__, '_init' ) );
register_activation_hook( __FILE__, array( __CLASS__, '_activate' ) );
register_deactivation_hook( __FILE__, array( __CLASS__, '_deactivate' ) );
}
static function _init() {
// Register post types, taxonomies here
// Also add rewrite rules, tags, permastructs, etc. here.
}
static function _activate() {
flush_rewrite_rules( true );
}
static function _deactivate() {
flush_rewrite_rules( true );
}
}
Hardcore_Urls::on_load();
27
Flushing in aTheme
<?php // functions.php
class Hardcore_Urls_Theme {
static function on_load() {
add_action( 'init', array( __CLASS__, '_init' ) );
add_action( 'after_switch_theme', '_after_switch_theme' );
}
static function _init() {
// Register post types, taxonomies here
// Also add rewrite rules, tags, permastructs, etc. here.
}
static function _after_switch_theme() {
flush_rewrite_rules( true );
}
}
Hardcore_Urls_Theme::on_load();
28
AddingTags and Rewrite Rules
/products/{product_name}/details/
register_post_type( 'x_product',
array(
'public' => true,
'label' => __( 'Products', 'X' ),
'query_var' => 'x_product',
'rewrite' => array(
'with_front' => false,
'slug' => 'products',
),
));
add_rewrite_tag( '%x_product_details%', 'details' );
add_rewrite_rule( 'products/([^/]+)/details/?$',
'index.php?x_product=$matches[1]&' .
'x_product_details=true',
'top'
);
29
Using in aTheme
<?php get_header(); ?>
<div id="primary" class="content-area">
<div id="content" class="site-content" role="main">
<?php
if ( get_query_var( 'x_product_details' ) ) {
echo '<h2>THIS IS A PRODUCT DETAILS PAGE</h2>';
}
while ( have_posts() ) : the_post();
get_template_part( 'content', get_post_format() );
if ( comments_open() || get_comments_number() ) {
comments_template();
}
endwhile;
?>
</div>
</div>
<?php get_footer();
Example: single-acw14_product.php
30
AddTaxonomyTerm to PostType URL
/products/{prod_cat}/{product_name}/
register_post_type( 'x_product', array(
'public' => true,
'label' => __( 'Products', 'X' ),
'rewrite' => array(
'with_front' => false,
'slug' => 'products/[^/]+',
),
));
/products/valid-cat/{product_name}/
/products/foo-bar/{product_name}/
Except! No {prod_cat} Validation:
31
ValidatingTaxonomyTerms
add_action( 'init', 'x_init' );
function x_init() { // First register post type and taxonomy here
add_rewrite_tag( '%x_prod_cat%', '([^/]+)' );
add_rewrite_rule(
'products/([^/]+)/([^/]+)/?$',
'index.php?x_prod_cat=$matches[1]&x_product=$matches[2]',
'top'
);
}
add_filter( 'parse_request', 'x_parse_request' );
function x_parse_request( $wp ) {
if ( ! empty( $wp->query_vars['x_prod_cat'] ) ) {
$term_slug = $wp->query_vars['x_prod_cat'];
$query = new WP_Query( $wp->query_vars );
if ( ! has_term( $term_slug, 'x_prod_cat', $query->post ) ) {
$wp->query_vars = array(
'error' => 404,
'post_type' => true
);
status_header( 404 );
nocache_headers();
}
}
}
/products/{prod_cat}/{product_name}/
32
More Rewrite Functions
• add_rewrite_endpoint()
• add_permastruct()
• WP_Rewrite Class
• $wp_rewrite->add_external_rule()
• generate_rewrite_rules() *
• save_mod_rewrite_rules() and
iis7_save_url_rewrite_rules()
• get_sample_permalink() *
• redirect_guess_404_permalink()!!!
*Called internally by WordPress
!!! Bad Actor!
33
Adding an EndPoint
add_filter( 'request', 'x_request' );
function x_request( $query_vars ) {
if( isset( $query_vars['json'] ) ) {
$query_vars['json'] = true;
}
return $query_vars;
}
add_filter( 'template_redirect', 'x_template_redirect' );
function x_template_redirect() {
if( get_query_var( 'json' ) ) {
global $post;
header( 'Content-type: application/json' );
echo json_encode( $post );
exit;
}
}
/products/{product_name}/json/
34
Adding a Permastruct
add_action( 'init', 'x_init' );
function x_init() {
// Register other things here
add_permastruct(
'x_comments',
'comments/%year%/%monthnum%/%day%',
array(
'with_front' => true,
'ep_mask' => EP_NONE,
'paged' => true,
'feed' => true,
'forcomments' => false,
'walk_dirs' => true,
'endpoints' => true,
)
);
// Flush rules if WP_DEBUG
}
/comments/{year}/{monthnum}/{day}/
35
Recognizing a Permastruct
add_filter( 'parse_request', 'x_parse_request' );
function x_parse_request( $wp ) {
$regex = '#^comments/?([0-9]{4}/?([0-9]{1,2}/?([0-9]{1,2}/?(.*))?)?)?$#';
if ( preg_match( $regex, $wp->request, $match ) ) {
$wp->query_vars += array(
'post_type' => true,
'x_comments_permastruct' => true,
);
}
}
/comments/{year}/{monthnum}/{day}/
* Why Yes! That is a rather ugly regular expression!
36
Preparing a SpecialTemplate
add_filter( 'template_include', 'x_template_include' );
function x_template_include( $template_file ) {
global $wp, $wp_the_query;
if( get_query_var( 'x_comments_permastruct' ) ) {
$wp_the_query->queried_object_id = null;
$wp_the_query->queried_object = $query = new WP_Comment_Query;
$date_query = array();
foreach( $wp->query_vars as $var_name => $var_value ) {
if ( preg_match( '#^(year|monthnum|day)$#', $var_name ) ) {
$date_query[] = array( $var_name => $var_value );
}
}
$query->comments = $query->query( array(
'date_query' => $date_query,
));
x_load_comments( get_stylesheet_directory() . '/comment.php' );
}
return $template_file;
}
/comments/{year}/{monthnum}/{day}/
37
Serving a SpecialTemplate
function x_load_comments( $_template_file ) {
global $post, $wp_rewrite, $wpdb, $wp_version, $wp, $user_ID;
global $comments, $comment, $wp_comment_query;
$wp_comment_query = get_queried_object();
$comments = $wp_comment_query->comments;
$comment = reset( $comments );
$post = get_post( $comment->comment_post_ID );
require( $_template_file );
exit;
}
/comments/{year}/{monthnum}/{day}/
38
A Special CommentTemplate
<?php // comment.php
get_header(); ?>
<div id="main-content" class="main-content">
<div id="primary" class="content-area">
<div id="content" class="site-content" role="main">
<?php
if ( count( $comments ) ) :
echo '<ul>';
foreach( $comments as $comment ):
$post = get_post( $comment->comment_post_ID );
$url = get_comment_link( $comment );
$excerpt = get_comment_excerpt( $comment );
$date_time = get_comment_date() . ' '. get_comment_time();
echo "<li>About: ";
the_title();
echo "<br>{$comment->comment_author} &lt;{$date_time}&gt;";
echo "<br><a href="{$url}">{$excerpt}</a><hr>";
endforeach;
echo '</ul>';
else:
get_template_part( 'content', 'none' );
endif;
?>
</div><!-- #content -->
</div><!-- #primary -->
<?php get_sidebar( 'content' ); ?>
</div><!-- #main-content --><?php
get_sidebar();
get_footer();
/comments/{year}/{monthnum}/{day}/
39
Rules Generated by Permastruct
/comments/{year}/{monthnum}/{day}/
comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1
comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1
comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]
comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4]
comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/json(/(.*))?/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&json=$matches[5]
comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]
comments/([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1
comments/([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1
comments/([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]
comments/([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3]
comments/([0-9]{4})/([0-9]{1,2})/json(/(.*))?/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]&json=$matches[4]
comments/([0-9]{4})/([0-9]{1,2})/?$
=> index.php?year=$matches[1]&monthnum=$matches[2]
comments/([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$
=> index.php?year=$matches[1]&feed=$matches[2]&withcomments=1
comments/([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$
=> index.php?year=$matches[1]&feed=$matches[2]&withcomments=1
comments/([0-9]{4})/page/?([0-9]{1,})/?$
=> index.php?year=$matches[1]&paged=$matches[2]
comments/([0-9]{4})/comment-page-([0-9]{1,})/?$
=> index.php?year=$matches[1]&cpage=$matches[2]
comments/([0-9]{4})/json(/(.*))?/?$
=> index.php?year=$matches[1]&json=$matches[3]
comments/([0-9]{4})/?$
=> index.php?year=$matches[1]
This was for all true &
'ep_mask' => EP_ALL
40
Adding an External Rule
/api/{whatevah}/
RewriteRule ^api/?.*$ /wp-content/themes/wp38/api.php [QSA,L]
Adds the following to .htaccess:
add_action( 'init', 'x_init' );
function x_init() {
global $wp_rewrite;
$local_path = substr( __DIR__ . '/api.php', strlen( ABSPATH ) );
$wp_rewrite->add_external_rule( 'api/?.*$', $local_path );
// Flush your Rewrite Rules!
}
41
Recognizing the External Rule
/api/{whatevah}/
<?php
/* Filename: api.php
* This is only if you need WordPress loaded.
* The "/../../.." assumes in a plugin directory.
*/
require( __DIR__ . '/../../../wp-load.php' );
$url_path = $_SERVER['REQUEST_URI'];
$request = preg_replace( '#^/api/(.*)$#', '$1', $url_path );
echo "Your API request was: {$request}";
42
The generate_rewrite_rules() Function
• This is where it gets really ugly.
• This can turn your brain to mush.
• You might even switch to Drupal!
• Not really. Who'd want Drupal
if they can haz WordPress?!?
• But I digress...
43
44
Hooks Used During Rule Generation
• 'delete_option_rewrite_rules'
• 'pre_get_option_rewrite_rules'
• 'default_option_rewrite_rules'
• 'post_rewrite_rules'
• 'date_rewrite_rules'
• 'root_rewrite_rules'
• 'comments_rewrite_rules'
• 'search_rewrite_rules'
• 'author_rewrite_rules'
• 'page_rewrite_rules'
• "{$permastructname}_rewrite_rules"
• 'tag_rewrite_rules'
• 'generate_rewrite_rules'
• 'rewrite_rules_array'
45
For 'post_rewrite_rules'
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachmenta/([^/]+)/?$] => index.php?attachment=$matches[1]
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/trackback/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&tb=1
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?
year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?
year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/page/?([0-9]{1,})/?$] => index.php?
year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&paged=$matches[5]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?
year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&cpage=$matches[5]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&json=$matches[6]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)(/[0-9]+)?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&page=$matches[5]
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/?$] => index.php?attachment=$matches[1]
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
[[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&json=$matches[5]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]
[([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]
[([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]
[([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]
[([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3]
[([0-9]{4})/([0-9]{1,2})/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&json=$matches[4]
[([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]
[([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]
[([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]
[([0-9]{4})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&paged=$matches[2]
[([0-9]{4})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&cpage=$matches[2]
[([0-9]{4})/json(/(.*))?/?$] => index.php?year=$matches[1]&json=$matches[3]
[([0-9]{4})/?$] => index.php?year=$matches[1]
46
For 'date_rewrite_rules'
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]
[([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]
[([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]
[([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]
[([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]
[([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]
[([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]
[([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]
[([0-9]{4})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&paged=$matches[2]
[([0-9]{4})/?$] => index.php?year=$matches[1]
47
For 'root_rewrite_rules'
[feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?&feed$matches[1]
[(feed|rdf|rss|rss2|atom)/?$] => index.php?&feed=$matches[1]
[page/?([0-9]{1,})/?$] => index.php?&paged=$matches[1]
48
For 'comments_rewrite_rules'
[comments/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?
&feed=$matches[1]&withcomments=1 [comments/(feed|rdf|rss|rss2|atom)/?$] =>
index.php?&feed=$matches[1]&withcomments=1
49
For 'search_rewrite_rules'
[search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?
s=$matches[1]&feed=$matches[2]
[search/(.+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?
s=$matches[1]&feed=$matches[2]
[search/(.+)/page/?([0-9]{1,})/?$] => index.php?s=$matches[1]&paged=$matches[2]
[search/(.+)/?$] => index.php?s=$matches[1]
50
For 'author_rewrite_rules'
[author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?
author_name=$matches[1]&feed=$matches[2]
[author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?
author_name=$matches[1]&feed=$matches[2]
[author/([^/]+)/page/?([0-9]{1,})/?$] => index.php?author_name=$matches[1]&paged=$matches[2]
[author/([^/]+)/?$] => index.php?author_name=$matches[1]
51
For 'page_rewrite_rules'
[.?.+?/attachment/([^/]+)/?$] => index.php?attachment=$matches[1]
[.?.+?/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
[.?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?attachment=$matches[1]&feed=$matches[2]
[.?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?attachment=$matches[1]&feed=$matches[2]
[.?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$]
=> index.php?attachment=$matches[1]&cpage=$matches[2]
[(.?.+?)/trackback/?$] => index.php?pagename=$matches[1]&tb=1
[(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?pagename=$matches[1]&feed=$matches[2]
[(.?.+?)/(feed|rdf|rss|rss2|atom)/?$] => index.php?pagename=$matches[1]&feed=$matches[2]
[(.?.+?)/page/?([0-9]{1,})/?$] => index.php?pagename=$matches[1]&paged=$matches[2]
[(.?.+?)/comment-page-([0-9]{1,})/?$] => index.php?pagename=$matches[1]&cpage=$matches[2]
[(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
52
For 'category_rewrite_rules'
[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]
53
For 'post_tag_rewrite_rules'
[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]
54
For 'tag_rewrite_rules'
[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]
55
For 'post_format_rewrite_rules'
[type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?
post_format=$matches[1]&feed=$matches[2] [type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?
post_format=$matches[1]&feed=$matches[2]
[type/([^/]+)/page/?([0-9]{1,})/?$] => index.php?post_format=$matches[1]&paged=$matches[2]
[type/([^/]+)/?$] => index.php?post_format=$matches[1]
56
For 'x_comments_rewrite_rules'
[comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1
[comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1
[comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]
[comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4]
[comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/json(/(.*))?/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&json=$matches[5]
[comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]
[comments/([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1
[comments/([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$]
=> index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1
[comments/([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]
[comments/([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$] => index.php?
year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3]
[comments/([0-9]{4})/([0-9]{1,2})/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&json=$matches[4]
[comments/([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]
[comments/([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]&withcomments=1
[comments/([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]&withcomments=1
[comments/([0-9]{4})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&paged=$matches[2]
[comments/([0-9]{4})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&cpage=$matches[2]
[comments/([0-9]{4})/json(/(.*))?/?$] => index.php?year=$matches[1]&json=$matches[3]
[comments/([0-9]{4})/?$] => index.php?year=$matches[1]
57
Whew!
• I hope you never have to use any of that!
• But if you do, you are now equipped.
• (Assumed your brain didn't turn to mush.)
58
The 'do_parse_request' Hook
• This is the strong magic.
• Using 'do_parse_request' is like....
• ...getting the Glengarry Leads!!!
• Seriously! It's omni-powerful!
• But Theme/Plugin Devs beware...
• The magic might overpower you.
59
Context Specific URLs
• WordPress only supports Context Free URLs
• But we often want Context Sensitive URLs
• 'do_parse_request' makes it possible
• But, it's not without risk
• Plugins and Themes won't expect it
• And you can have one URL hide another
• With Great Power Comes Great Responsibility
60
PostType Name in Root
/{product_name}/
add_filter( 'do_parse_request', 'x_do_parse_request', 10, 3 );
function x_do_parse_request( $continue, $wp, $extra_query_vars ) {
if ( $continue ) {
$url_path = trim( $_SERVER['REQUEST_URI'], '/' );
$url_path = sanitize_title_with_dashes( $url_path );
$query = new WP_Query( array(
'name' => $url_path,
'post_type' => 'x_product',
'post_status' => 'publish',
));
if ( 1 == $query->found_posts ) {
$wp->query_vars = array(
'post_type' => 'x_product',
'x_product' => $url_path,
'name' => $url_path,
'page' => '',
);
$continue = false; // IMPORTANT
}
}
return $continue;
}
61
Parent and Child PostTypes
Part 1 of 3
/{product_name}/{model_name}/
add_filter( 'do_parse_request', 'x_do_parse_request', 10, 3 );
function x_do_parse_request( $continue, $wp, $extra_query_vars ) {
if ( $continue && ! is_admin() ) {
$url_path = trim( $_SERVER['REQUEST_URI'], '/' );
if ( ! empty( $url_path ) && '?' != $url_path[0] ) {
$url_path_parts = array_map( 'sanitize_title_with_dashes', explode( '/', $url_path ) );
switch( count( $url_path_parts ) ) {
case 2:
if ( $model = x_match_post_name( 'x_model', $model_name = $url_path_parts[1] ) ) {
if ( $product = x_match_post_name( 'x_product', $url_path_parts[0] ) ) {
if ( $model->post_parent == $product->ID ) {
x_route_post_name( $wp, 'x_model', $model_name );
$continue = false;
}
}
}
break;
case 1:
$continue = ! x_match_post_name( 'x_product', $url_path_parts[0], $wp );
break;
}
}
}
return $continue;
}
62
Parent and Child PostTypes
Part 2 of 3
/{product_name}/{model_name}/
function x_route_post_name( $post_type, $post_name, $wp ) {
$wp->query_vars = array(
'post_type' => $post_type,
$post_type => $post_name,
'name' => $post_name,
'page' => '',
);
}
function x_match_post_name( $post_type, $post_name, $wp = false ) {
$query = new WP_Query( array(
'name' => $post_name,
'post_type' => $post_type,
'post_status' => 'publish',
));
if ( ( $matched = 1 == $query->found_posts ) && $wp ) {
x_route_post_name( $wp, $post_type, $post_name );
}
return $matched ? $query->post : false;
}
63
Parent and Child PostTypes
Part 3 of 3
/{product_name}/{model_name}/
add_action( 'add_meta_boxes', 'x_add_meta_boxes' );
function x_add_meta_boxes( $post_type ) {
if ( 'x_model' == $post_type ) {
add_meta_box( 'product_parent_box', // $id
__( 'Parent Product', 'X' ), // $title
'x_model_parent_metabox', // $callback
'x_model', // $post_type
'side', // $context
'low' // $priority
);
}
}
function x_model_parent_metabox( $post ) {
$post_type_object = get_post_type_object( 'x_product' );
$post_type_object->hierarchical = true;
wp_dropdown_pages( array(
'name' => 'parent_id',
'post_type' => 'x_product',
'selected' => $post->post_parent,
'show_option_none' => __( 'Select a Parent', 'X' ),
'option_none_value' => 0,
));
$post_type_object->hierarchical = false;
}
64
Stopping Core's "Helpful" Guess
/{product_name}/{model_name}/ =>
?post_type=x_model&p={post_id}
add_action( 'redirect_canonical', 'x_redirect_canonical', 10, 2 );
function x_redirect_canonical( $redirect_url, $requested_url ) {
list( $url, $query ) = explode( '?', "{$redirect_url}?" );
if ( $url == home_url('/') ) {
parse_str( $query, $params );
if ( isset( $params['post_type'] ) && 'x_model' == $params['post_type'] ) {
// Dangit! redirect_guess_404_permalink() interfered; set it back!!!
$redirect_url = $requested_url;
}
}
return $redirect_url;
}
65
Reveal the QueryVars
add_action( 'shutdown', 'x_shutdown' );
function x_shutdown() {
if ( ! is_admin() ) {
global $wp;
echo '<pre><code><font size="7">';
echo '$wp->query_vars: ';
print_r( $wp->query_vars );
echo '</font></code></pre>';
}
}
66
Performance Optimization
• Cache Top N URLs in wp_options
• In array; key=URL, value=$wp->query_vars
• Periodically Recalculate Top N
• Even Better if using Memcache
• This is your Homework!
67
Warning:
May Be Incompatible
• Highly Custom Sites:
• A-OK
• Normal Websites or Blog:
• Plugins and themes might be incompatible
• Plugins or Themes for Distribution:
• Be very careful; this is non-standard routing
• Also...
• Very easy to create ambiguity in routing
68
The Dispatch Library
• Using URI Templates as Patterns
• Inspecting URLs by Segment
• Plan to do lots of caching
• Still PRE-ALPHA
• github.com/newclarity/dispatch
69
But Routing Ain't Enough
• Link Generation
• Allow Editing the Slug
• Changing the Permalink Sample
70
Generate the Link
add_filter( 'post_type_link', 'x_post_type_link', 10, 2 );
function x_post_type_link( $post_link, $post ) {
switch ( $post->post_type ) {
case 'x_product':
$post_link = home_url( "/{$post->post_name}/" );
break;
case 'x_model':
$product_link = x_post_type_link( false, get_post( $post->post_parent )
);
$post_link = "{$product_link}{$post->post_name}/";
break;
}
return $post_link;
}
/{product_name}/{model_name}/
71
Make the Slug Editable
add_filter( 'post_type_link', 'x_post_type_link', 10, 3 );
function x_post_type_link( $post_link, $post, $leavename = false ) {
$slug = $leavename ? "%{$post->post_type}%" : $post->post_name;
switch ( $post->post_type ) {
case 'x_product':
$post_link = home_url( "/{$slug}/" );
break;
case 'x_model':
$product_link = x_post_type_link( false, get_post( $post->post_parent )
);
$post_link = "{$product_link}{$slug}/";
break;
}
return $post_link;
}
/{product_name}/ {model_name} /{model_name}
72
Changing the Sample Permalink
add_filter( 'post_type_link', 'x_post_type_link', 10, 3 );
function x_post_type_link( $post_link, $post, $leavename = false ) {
$slug = $leavename ? "%{$post->post_type}%" : $post->post_name;
switch ( $post->post_type ) {
case 'x_product':
$post_link = home_url( "/{$slug}/" );
break;
case 'x_model':
$product_link = x_post_type_link( false, get_post( $post->post_parent )
);
$post_link = "{$product_link}{$slug}/";
break;
}
if ( $sample ) {
// NO CLUE WHY YOU'D WANT TO DO THIS... BUT YOU CAN!
$post_link = "----->[ %{$post->post_type}% ]<-----";
}
return $post_link;
}
----->[ ]<-----{model_name}
73
74
In Review
• How WordPress Routes URLs
• index.php and .htaccess
• That URLs ultimately create $args for WP_Query()
• Rewrite Rules and how to see/test them.
• How and why to Flush Rules
• The Rewrite API Functions
• Especially add_rewrite_rule() and add_rewrite_tag()
• The Rewrite API Hooks
• Especially 'parse_request'and 'do_parse_request'
What We Learned Today
75
In Review (cont'd)
• About Endpoints, Permastructs and External Rules
• How abysmal generate_rewrite_rules() is
• All them crazii hooks when generating.
• The Strong Magic: do_parse_request
• Provides Context Sensitive URLs
• How to discover the query vars needed to route URLs
• Generating links, editing elugs and changing samples.
• And finally the logic flow for URL rewriting/routing.
What Else We Learned Today
76
And Stuff I Wrote for the Intro
• But I decided it didn't flow.
• Like how scenes gets left...
• on the cutting room floor.
• So I added to the DVD. ;-)
77
WordPress' URL Patterns
• Post & Page URL Patterns
• Archive URL Patterns
• Feed URL Patterns
• Other URL Patterns
78
Post & Page URL Patterns
Posts /{year}/{month}/{day}/{postname}/
Pages /{pagename}/
Hierarchical Pages /{rootname}/../{leafname}/
Custom Post Types /{post_type_slug}/{postname}/
Attachments
/{y}/{m}/{d}/attachment/{attachment}/
/{whatever}/attachment/{attachment}/
/{whatever}/{attachment}/
79
Archive URL Patterns
Tag Archive /tag/{tag}/
Category Archive /category/{category}/
Author Archive /author/{author_name}/
Search Results
/search/{s}/ and /s?={s}
Post Format Archive /type/{post_format}/
Custom Post Type Archive /{post_type_slug}/
Also Day, Month and Year Archives.
80
Feed URL Patterns
Feed* /feed/{feed|rdf|rss|rss2|atom}/
Category Feed* /category/{category}/feed/{feed_type}/
Tag Feed* /tag/{tag}/feed/{feed_type}/
Comment Feed* /comment/feed/{feed_type}/
Search Results Feed* /search/{s}/feed/{feed_type}/
Author Feed* /author/{author_name}/feed/{feed_type}/
* {feed_type} = feed, rss, rdf, rss2, atom
Also Attachment, Post, Page, Day, Month and Year Feeds. (Why?)
81
Other URL Patterns
Robots /robots.txt
Register /{whatever}/wp-register.php
Comments /{post_url}/comment-page-{cpage}/
Trackback /{post_url}/trackback/{whatever}
And some... I’m sure I forgot about.
82
• The mapping of a URL Path of an HTTP Request
identifying the Resource on yourWordPress site
for which you want an
HTTP Response to return a Representation.
• Whew! What a mouthful!
What is URL Routing?
83
•Because we want to control what
Representation is returned for a
given URL Path.
•So let’s define those terms...
Why Do We Care?
84
What is a URL Path?
• Example URL:
• http://example.com/2014/03/15/hello-world/
• The URL Path is:
• /2014/03/15/hello-world/
85
What is an HTTP Request?
It’s when you type a URL into your browser and press Enter!
86
What is a Resource?
• It’s the idea of the thing you want, the thing located
at the URL you type into your browser.
• Don’t worry, nerd types have argued over this
definition for years!
• A Representation is simply the HTML file,
image or other file you want when you enter a URL.
87
What is an HTTP Response?
• It is what your WordPress website packages up and
sends back to your browser and is based on the URL
path you request.
88
It’s what gets downloaded when you
request a URL with your browser.
What is a Representation?
89
So in review:
URL Routing is the mapping of a URL Path
of an HTTP Request identifying the Resource
on yourWordPress site for which you want an
HTTP Response to return a Representation.
See,That Wasn’t Hard!
90
Future Reading
The Rewrite API:The Basics
http://code.tutsplus.com/tutorials/the-rewrite-api-the-basics--wp-25474
The Rewrite API: PostTypes &Taxonomies
http://code.tutsplus.com/articles/the-rewrite-api-post-types-taxonomies--wp-25488
A (Mostly) Complete Guide to the WordPress Rewrite API
http://o.pmg.co/a-mostly-complete-guide-to-the-wordpress-rewrite-api
Some background on rewrite rules
http://wordpress.stackexchange.com/a/5478/89
91
Me, in More Detail
President, NewClarity LLC
Boutique WordPress consulting firm with a small but expert team.
Specialize in complex sites & vertical market products
Offering: Back-end architecture, implementation, training, code reviews.
375+ Answers at WordPress Answers
Was a founding moderator
Have blogged at HardcoreWP.com
Hope to find time again (doh!)
Several "Libraries" for WordPress at github.com/newclarity
Exo - "MVC that doesn't changeWordPress, it just makes it stronger."
Sunrise - Code-based Forms and Fields
Dispatch - Hardcore URL Routing forWordPress
Contact us for more of the "Strong Magic"
mike@newclarity.net
92
Thanks for Attending
May all your URLs be pretty
and may they all match
their intended patterns
Mike Schinkel
@mikeschinkel
mike@newclarity.net

More Related Content

What's hot

Introduction to plugin development
Introduction to plugin developmentIntroduction to plugin development
Introduction to plugin developmentCaldera Labs
 
Hooked on WordPress: WordCamp Columbus
Hooked on WordPress: WordCamp ColumbusHooked on WordPress: WordCamp Columbus
Hooked on WordPress: WordCamp ColumbusShawn Hooper
 
How does get template part works in twenty ten theme
How does get template part works in twenty ten themeHow does get template part works in twenty ten theme
How does get template part works in twenty ten thememohd rozani abd ghani
 
Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
 Connecting Content Silos: One CMS, Many Sites With The WordPress REST API Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
Connecting Content Silos: One CMS, Many Sites With The WordPress REST APICaldera Labs
 
Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)Kris Wallsmith
 
Django - 次の一歩 gumiStudy#3
Django - 次の一歩 gumiStudy#3Django - 次の一歩 gumiStudy#3
Django - 次の一歩 gumiStudy#3makoto tsuyuki
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overviewYehuda Katz
 
Action Controller Overview, Season 2
Action Controller Overview, Season 2Action Controller Overview, Season 2
Action Controller Overview, Season 2RORLAB
 
The effective use of Django ORM
The effective use of Django ORMThe effective use of Django ORM
The effective use of Django ORMYaroslav Muravskyi
 
Rails 3 Beautiful Code
Rails 3 Beautiful CodeRails 3 Beautiful Code
Rails 3 Beautiful CodeGreggPollack
 
Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3Kris Wallsmith
 
Oracle APEX Performance
Oracle APEX PerformanceOracle APEX Performance
Oracle APEX PerformanceScott Wesley
 
Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)Jacob Kaplan-Moss
 
Django REST Framework
Django REST FrameworkDjango REST Framework
Django REST FrameworkLoad Impact
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasminePaulo Ragonha
 
Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)
Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)
Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)Michael Wales
 
Action View Form Helpers - 1, Season 2
Action View Form Helpers - 1, Season 2Action View Form Helpers - 1, Season 2
Action View Form Helpers - 1, Season 2RORLAB
 
Maintainable JavaScript 2012
Maintainable JavaScript 2012Maintainable JavaScript 2012
Maintainable JavaScript 2012Nicholas Zakas
 

What's hot (20)

Introduction to plugin development
Introduction to plugin developmentIntroduction to plugin development
Introduction to plugin development
 
Django Heresies
Django HeresiesDjango Heresies
Django Heresies
 
Hooked on WordPress: WordCamp Columbus
Hooked on WordPress: WordCamp ColumbusHooked on WordPress: WordCamp Columbus
Hooked on WordPress: WordCamp Columbus
 
How does get template part works in twenty ten theme
How does get template part works in twenty ten themeHow does get template part works in twenty ten theme
How does get template part works in twenty ten theme
 
Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
 Connecting Content Silos: One CMS, Many Sites With The WordPress REST API Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
Connecting Content Silos: One CMS, Many Sites With The WordPress REST API
 
Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)Assetic (Symfony Live Paris)
Assetic (Symfony Live Paris)
 
Django - 次の一歩 gumiStudy#3
Django - 次の一歩 gumiStudy#3Django - 次の一歩 gumiStudy#3
Django - 次の一歩 gumiStudy#3
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Action Controller Overview, Season 2
Action Controller Overview, Season 2Action Controller Overview, Season 2
Action Controller Overview, Season 2
 
I Love codeigniter, You?
I Love codeigniter, You?I Love codeigniter, You?
I Love codeigniter, You?
 
The effective use of Django ORM
The effective use of Django ORMThe effective use of Django ORM
The effective use of Django ORM
 
Rails 3 Beautiful Code
Rails 3 Beautiful CodeRails 3 Beautiful Code
Rails 3 Beautiful Code
 
Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3Introducing Assetic: Asset Management for PHP 5.3
Introducing Assetic: Asset Management for PHP 5.3
 
Oracle APEX Performance
Oracle APEX PerformanceOracle APEX Performance
Oracle APEX Performance
 
Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)Introduction To Django (Strange Loop 2011)
Introduction To Django (Strange Loop 2011)
 
Django REST Framework
Django REST FrameworkDjango REST Framework
Django REST Framework
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
 
Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)
Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)
Introduction to CodeIgniter (RefreshAugusta, 20 May 2009)
 
Action View Form Helpers - 1, Season 2
Action View Form Helpers - 1, Season 2Action View Form Helpers - 1, Season 2
Action View Form Helpers - 1, Season 2
 
Maintainable JavaScript 2012
Maintainable JavaScript 2012Maintainable JavaScript 2012
Maintainable JavaScript 2012
 

Similar to Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)

[Bristol WordPress] Supercharging WordPress Development
[Bristol WordPress] Supercharging WordPress Development[Bristol WordPress] Supercharging WordPress Development
[Bristol WordPress] Supercharging WordPress DevelopmentAdam Tomat
 
Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019Adam Tomat
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkBo-Yi Wu
 
Childthemes ottawa-word camp-1919
Childthemes ottawa-word camp-1919Childthemes ottawa-word camp-1919
Childthemes ottawa-word camp-1919Paul Bearne
 
Becoming a better WordPress Developer
Becoming a better WordPress DeveloperBecoming a better WordPress Developer
Becoming a better WordPress DeveloperJoey Kudish
 
The Way to Theme Enlightenment
The Way to Theme EnlightenmentThe Way to Theme Enlightenment
The Way to Theme EnlightenmentAmanda Giles
 
WordPress Plugin development
WordPress Plugin developmentWordPress Plugin development
WordPress Plugin developmentMostafa Soufi
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)arcware
 
Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Yevhen Kotelnytskyi
 
You're Doing it Wrong - WordCamp Atlanta
You're Doing it Wrong - WordCamp AtlantaYou're Doing it Wrong - WordCamp Atlanta
You're Doing it Wrong - WordCamp AtlantaChris Scott
 
PSD to WordPress
PSD to WordPressPSD to WordPress
PSD to WordPressNile Flores
 
Intro to WordPress Plugin Development
Intro to WordPress Plugin DevelopmentIntro to WordPress Plugin Development
Intro to WordPress Plugin DevelopmentBrad Williams
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐいHisateru Tanaka
 
[WLDN] Supercharging word press development in 2018
[WLDN] Supercharging word press development in 2018[WLDN] Supercharging word press development in 2018
[WLDN] Supercharging word press development in 2018Adam Tomat
 
The Way to Theme Enlightenment 2017
The Way to Theme Enlightenment 2017The Way to Theme Enlightenment 2017
The Way to Theme Enlightenment 2017Amanda Giles
 
Getting to The Loop - London Wordpress Meetup July 28th
Getting to The Loop - London Wordpress Meetup  July 28thGetting to The Loop - London Wordpress Meetup  July 28th
Getting to The Loop - London Wordpress Meetup July 28thChris Adams
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Michelangelo van Dam
 

Similar to Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT) (20)

[Bristol WordPress] Supercharging WordPress Development
[Bristol WordPress] Supercharging WordPress Development[Bristol WordPress] Supercharging WordPress Development
[Bristol WordPress] Supercharging WordPress Development
 
Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019Supercharging WordPress Development - Wordcamp Brighton 2019
Supercharging WordPress Development - Wordcamp Brighton 2019
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC Framework
 
Childthemes ottawa-word camp-1919
Childthemes ottawa-word camp-1919Childthemes ottawa-word camp-1919
Childthemes ottawa-word camp-1919
 
Becoming a better WordPress Developer
Becoming a better WordPress DeveloperBecoming a better WordPress Developer
Becoming a better WordPress Developer
 
The Way to Theme Enlightenment
The Way to Theme EnlightenmentThe Way to Theme Enlightenment
The Way to Theme Enlightenment
 
WordPress Plugin development
WordPress Plugin developmentWordPress Plugin development
WordPress Plugin development
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
 
Agile Wordpress
Agile WordpressAgile Wordpress
Agile Wordpress
 
Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?
 
You're Doing it Wrong - WordCamp Atlanta
You're Doing it Wrong - WordCamp AtlantaYou're Doing it Wrong - WordCamp Atlanta
You're Doing it Wrong - WordCamp Atlanta
 
PSD to WordPress
PSD to WordPressPSD to WordPress
PSD to WordPress
 
Intro to WordPress Plugin Development
Intro to WordPress Plugin DevelopmentIntro to WordPress Plugin Development
Intro to WordPress Plugin Development
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
[WLDN] Supercharging word press development in 2018
[WLDN] Supercharging word press development in 2018[WLDN] Supercharging word press development in 2018
[WLDN] Supercharging word press development in 2018
 
The Way to Theme Enlightenment 2017
The Way to Theme Enlightenment 2017The Way to Theme Enlightenment 2017
The Way to Theme Enlightenment 2017
 
QA for PHP projects
QA for PHP projectsQA for PHP projects
QA for PHP projects
 
Getting to The Loop - London Wordpress Meetup July 28th
Getting to The Loop - London Wordpress Meetup  July 28thGetting to The Loop - London Wordpress Meetup  July 28th
Getting to The Loop - London Wordpress Meetup July 28th
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
 

Recently uploaded

PHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 DocumentationPHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 DocumentationLinaWolf1
 
SCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is prediSCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is predieusebiomeyer
 
Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170
Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170
Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170Sonam Pathan
 
Top 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxTop 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxDyna Gilbert
 
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作ys8omjxb
 
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书zdzoqco
 
NSX-T and Service Interfaces presentation
NSX-T and Service Interfaces presentationNSX-T and Service Interfaces presentation
NSX-T and Service Interfaces presentationMarko4394
 
Film cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasaFilm cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasa494f574xmv
 
Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170Sonam Pathan
 
Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24Paul Calvano
 
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一z xss
 
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书rnrncn29
 
Contact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New DelhiContact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New Delhimiss dipika
 
Q4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptxQ4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptxeditsforyah
 
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书rnrncn29
 

Recently uploaded (17)

PHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 DocumentationPHP-based rendering of TYPO3 Documentation
PHP-based rendering of TYPO3 Documentation
 
SCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is prediSCM Symposium PPT Format Customer loyalty is predi
SCM Symposium PPT Format Customer loyalty is predi
 
Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170
Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170
Call Girls In The Ocean Pearl Retreat Hotel New Delhi 9873777170
 
Top 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptxTop 10 Interactive Website Design Trends in 2024.pptx
Top 10 Interactive Website Design Trends in 2024.pptx
 
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
Potsdam FH学位证,波茨坦应用技术大学毕业证书1:1制作
 
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
办理多伦多大学毕业证成绩单|购买加拿大UTSG文凭证书
 
NSX-T and Service Interfaces presentation
NSX-T and Service Interfaces presentationNSX-T and Service Interfaces presentation
NSX-T and Service Interfaces presentation
 
Film cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasaFilm cover research (1).pptxsdasdasdasdasdasa
Film cover research (1).pptxsdasdasdasdasdasa
 
Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170Call Girls Near The Suryaa Hotel New Delhi 9873777170
Call Girls Near The Suryaa Hotel New Delhi 9873777170
 
Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24Font Performance - NYC WebPerf Meetup April '24
Font Performance - NYC WebPerf Meetup April '24
 
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
办理(UofR毕业证书)罗切斯特大学毕业证成绩单原版一比一
 
Hot Sexy call girls in Rk Puram 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in  Rk Puram 🔝 9953056974 🔝 Delhi escort ServiceHot Sexy call girls in  Rk Puram 🔝 9953056974 🔝 Delhi escort Service
Hot Sexy call girls in Rk Puram 🔝 9953056974 🔝 Delhi escort Service
 
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
『澳洲文凭』买拉筹伯大学毕业证书成绩单办理澳洲LTU文凭学位证书
 
Contact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New DelhiContact Rya Baby for Call Girls New Delhi
Contact Rya Baby for Call Girls New Delhi
 
young call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Service
young call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Serviceyoung call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Service
young call girls in Uttam Nagar🔝 9953056974 🔝 Delhi escort Service
 
Q4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptxQ4-1-Illustrating-Hypothesis-Testing.pptx
Q4-1-Illustrating-Hypothesis-Testing.pptx
 
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
『澳洲文凭』买詹姆士库克大学毕业证书成绩单办理澳洲JCU文凭学位证书
 

Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)

  • 1. 1 Hardcore URL Routing for WordPress Mike Schinkel @mikeschinkel about.me/mikeschinkel WordCamp Atlanta - March 15th, 2014
  • 2. 2 Me • WordPress Platform Architect • yada, yada
  • 3. 3 To Cover • URLs We Love • How Do URLs Get Routed? • How to see your Rewrite Rules • Review the Rewrite API Functions & Hooks • Ways to Add Rewrite Rules • flush_rewrite_rules() & generate_rewrite_rules() • Full-control Manual URL Routing • Link Generation: Beyond Routing • Other Stuff we Won't Cover
  • 4. 4 Download the PDF! Because there's SOOO much to cover I'm going to be going fast & furious So you'll need the PDF to follow along. http://hardcorewp.com/2014/hardcore-url-routing-for-wordpress/
  • 5. 5 But First...! People say using the WordPress Rewrite API is HARD! Well...
  • 6. 6 There Be Dragons! The WordPress Rewrite API: Not for the faint of heart! 6
  • 7. 7 But... If you apply yourself, If you work dillegently to wrap your head around the WordPress Rewrite API, Then you can become...
  • 8. 8 Like These! From my current favorite TV show: The Walking Dead8
  • 9. 9 SeriouslyThough: The WordPress Rewrite API is obtuse in many ways. Your goal today will be to learn it. And learn how to get around it.
  • 10. 10 • /products/{product_name}/details/ • Post type => 'product' • /products/{prod_cat}/{product_name}/ • Post type => 'product', Taxonomy ('prod_cat') term • /{permalink_path}/json/ • A JSON version of your post • /comments/{year}/{monthnum}/{day}/ • Archive of all comments not related to a specific post • /api/{api_request}/ • “Virtual” URL for Custom code URLs We Love
  • 11. 11 • /{automaker}/ • Post type => 'automaker' • /{automaker}/{model}/ • Post type => 'automaker', Post type => 'model' • /{user_nicename}/ • Specified user • Other? • Taking requests.... More URLs We Love
  • 12. 12 • WP Matches URL Path to a Rewrite Pattern • Rewrite Patterns are Regular Expressions • Example URL Path: • /2014/03/15/hello-world/ • Matching Regular Expression • ([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)(/[0-9]+)?/?$ • Rewrite Pattern: • index.php?year=$matches[1]&monthnum=$matches[2]& day=$matches[3]&name=$matches[4]&page=$matches[5] • Resultant Query String: • index.php?year=2014&monthnum=03&day=15&name=hello-world&page= How Do URLs Get Routed?
  • 13. 13 index.php in Rewrite Pattern? # BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase /RewriteRule ^index.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress See: .htaccess:* * If on IIS then web.config which has a different format.
  • 14. 14 • Stored in $wp->query_vars • Processed by WP_Query() for The Loop The Resultant Query String // Not exactly this, but similar $query_vars = 'year=2014&monthnum=03&day=15&name=hello-world&page='; $query= new WP_Query( $query_vars ); $query = new WP_Query( array( 'year' => 2014, 'monthnum' => 3, 'day' => 15, 'name' => 'hello-world', 'page' => '', )); • Essentially the same as this:
  • 15. 15 See your Rewrite Rules <?php /* * Filename: /rewrite-rules.php * REMEMBER TO SET YOUR PERMALINKS! Settings > Permalinks */ include __DIR__ . '/wp-load.php'; $rewrite_rules = get_option( 'rewrite_rules' ); header( 'Content-type: text/plain;' ); print_r( $rewrite_rules );
  • 16. 16 Rewrite Rules, in aTable! <?php /* * Filename: /rewrite-rules2.php * REMEMBER TO SET YOUR PERMALINKS! Settings > Permalinks */ include __DIR__ . '/wp-load.php'; $rewrite_rules = get_option( 'rewrite_rules' ); ?> <html> <head> <title>Rewrite Rules</title> <style type="text/css"> th { text-align: left; } </style> </head> <body> <table> <?php foreach( $rewrite_rules as $regex => $pattern ) { echo "<tr><th>{$regex}</th><td>{$pattern}</td></tr>"; } ?> </table> </body> </html>
  • 17. 17 Best Way to SeeYour Rulez • Monkeyman Rewrite Analyzer • http://wordpress.org/plugins/monkeyman-rewrite-analyzer/
  • 18. 18 The WordPress Rewrite API • Rewrite API Functions • Related Functions • Rewrite API Hooks • Related Hooks
  • 19. 19 The Rewrite API Functions • register_post_type()* and register_taxonomy()* • flush_rewrite_rules() • add_rewrite_rule() and add_rewrite_tag() • get_query_var()* • register_activation_hook() and register_deactivation_hook() *Related functions
  • 20. 20 The Rewrite API Hooks • 'parse_request' • 'request' • 'after_switch_theme'* • 'template_redirect'* • 'template_include'* • 'redirect_canonical'* • 'do_parse_request' • 'query_var' *Related Hooks
  • 21. 21 Different Ways to Add Rules • Adding Rewrite Rules Indirectly • Adding Rewrite Tags & Rules • Validating Rewritten URLs • Adding EndPoints • Adding Permastructs • Adding External Rules
  • 22. 22 Adding Rules Indirectly /products/{product_name}/ <?php // functions.php add_action( 'init', 'x_init' ); function x_init() { register_post_type( 'x_product', array( 'public' => true, 'label' => __( 'Products', 'X' ), 'rewrite' => array( 'slug' => 'products', ), )); }
  • 23. 23 About the 'x_' Prefix See: http://nacin.com/2010/05/11/in-wordpress-prefix-everything/
  • 24. 24 I just used 'x_' (FOR THIS TALK, ONLY) because it was short!
  • 25. 25 Always Remember to Flush! flush_rewrite_rules( $hard_or_soft ) add_action( 'init', 'x_init' ); function x_init() { // Call register_post_type() here if ( WP_DEBUG ) { if ( ! function_exists( 'save_mod_rewrite_rules' ) ) { require_once( ABSPATH . 'wp-admin/includes/file.php' ); require_once( ABSPATH . 'wp-admin/includes/misc.php' ); } flush_rewrite_rules( true ); } } • true (hard) - Writes changes to .htaccess/web.config • false (soft) - Does not writes changes.
  • 26. 26 Flushing in a Plugin <?php /** * Plugin Name: Hardcore URLs */ class Hardcore_Urls { static function on_load() { add_action( 'init', array( __CLASS__, '_init' ) ); register_activation_hook( __FILE__, array( __CLASS__, '_activate' ) ); register_deactivation_hook( __FILE__, array( __CLASS__, '_deactivate' ) ); } static function _init() { // Register post types, taxonomies here // Also add rewrite rules, tags, permastructs, etc. here. } static function _activate() { flush_rewrite_rules( true ); } static function _deactivate() { flush_rewrite_rules( true ); } } Hardcore_Urls::on_load();
  • 27. 27 Flushing in aTheme <?php // functions.php class Hardcore_Urls_Theme { static function on_load() { add_action( 'init', array( __CLASS__, '_init' ) ); add_action( 'after_switch_theme', '_after_switch_theme' ); } static function _init() { // Register post types, taxonomies here // Also add rewrite rules, tags, permastructs, etc. here. } static function _after_switch_theme() { flush_rewrite_rules( true ); } } Hardcore_Urls_Theme::on_load();
  • 28. 28 AddingTags and Rewrite Rules /products/{product_name}/details/ register_post_type( 'x_product', array( 'public' => true, 'label' => __( 'Products', 'X' ), 'query_var' => 'x_product', 'rewrite' => array( 'with_front' => false, 'slug' => 'products', ), )); add_rewrite_tag( '%x_product_details%', 'details' ); add_rewrite_rule( 'products/([^/]+)/details/?$', 'index.php?x_product=$matches[1]&' . 'x_product_details=true', 'top' );
  • 29. 29 Using in aTheme <?php get_header(); ?> <div id="primary" class="content-area"> <div id="content" class="site-content" role="main"> <?php if ( get_query_var( 'x_product_details' ) ) { echo '<h2>THIS IS A PRODUCT DETAILS PAGE</h2>'; } while ( have_posts() ) : the_post(); get_template_part( 'content', get_post_format() ); if ( comments_open() || get_comments_number() ) { comments_template(); } endwhile; ?> </div> </div> <?php get_footer(); Example: single-acw14_product.php
  • 30. 30 AddTaxonomyTerm to PostType URL /products/{prod_cat}/{product_name}/ register_post_type( 'x_product', array( 'public' => true, 'label' => __( 'Products', 'X' ), 'rewrite' => array( 'with_front' => false, 'slug' => 'products/[^/]+', ), )); /products/valid-cat/{product_name}/ /products/foo-bar/{product_name}/ Except! No {prod_cat} Validation:
  • 31. 31 ValidatingTaxonomyTerms add_action( 'init', 'x_init' ); function x_init() { // First register post type and taxonomy here add_rewrite_tag( '%x_prod_cat%', '([^/]+)' ); add_rewrite_rule( 'products/([^/]+)/([^/]+)/?$', 'index.php?x_prod_cat=$matches[1]&x_product=$matches[2]', 'top' ); } add_filter( 'parse_request', 'x_parse_request' ); function x_parse_request( $wp ) { if ( ! empty( $wp->query_vars['x_prod_cat'] ) ) { $term_slug = $wp->query_vars['x_prod_cat']; $query = new WP_Query( $wp->query_vars ); if ( ! has_term( $term_slug, 'x_prod_cat', $query->post ) ) { $wp->query_vars = array( 'error' => 404, 'post_type' => true ); status_header( 404 ); nocache_headers(); } } } /products/{prod_cat}/{product_name}/
  • 32. 32 More Rewrite Functions • add_rewrite_endpoint() • add_permastruct() • WP_Rewrite Class • $wp_rewrite->add_external_rule() • generate_rewrite_rules() * • save_mod_rewrite_rules() and iis7_save_url_rewrite_rules() • get_sample_permalink() * • redirect_guess_404_permalink()!!! *Called internally by WordPress !!! Bad Actor!
  • 33. 33 Adding an EndPoint add_filter( 'request', 'x_request' ); function x_request( $query_vars ) { if( isset( $query_vars['json'] ) ) { $query_vars['json'] = true; } return $query_vars; } add_filter( 'template_redirect', 'x_template_redirect' ); function x_template_redirect() { if( get_query_var( 'json' ) ) { global $post; header( 'Content-type: application/json' ); echo json_encode( $post ); exit; } } /products/{product_name}/json/
  • 34. 34 Adding a Permastruct add_action( 'init', 'x_init' ); function x_init() { // Register other things here add_permastruct( 'x_comments', 'comments/%year%/%monthnum%/%day%', array( 'with_front' => true, 'ep_mask' => EP_NONE, 'paged' => true, 'feed' => true, 'forcomments' => false, 'walk_dirs' => true, 'endpoints' => true, ) ); // Flush rules if WP_DEBUG } /comments/{year}/{monthnum}/{day}/
  • 35. 35 Recognizing a Permastruct add_filter( 'parse_request', 'x_parse_request' ); function x_parse_request( $wp ) { $regex = '#^comments/?([0-9]{4}/?([0-9]{1,2}/?([0-9]{1,2}/?(.*))?)?)?$#'; if ( preg_match( $regex, $wp->request, $match ) ) { $wp->query_vars += array( 'post_type' => true, 'x_comments_permastruct' => true, ); } } /comments/{year}/{monthnum}/{day}/ * Why Yes! That is a rather ugly regular expression!
  • 36. 36 Preparing a SpecialTemplate add_filter( 'template_include', 'x_template_include' ); function x_template_include( $template_file ) { global $wp, $wp_the_query; if( get_query_var( 'x_comments_permastruct' ) ) { $wp_the_query->queried_object_id = null; $wp_the_query->queried_object = $query = new WP_Comment_Query; $date_query = array(); foreach( $wp->query_vars as $var_name => $var_value ) { if ( preg_match( '#^(year|monthnum|day)$#', $var_name ) ) { $date_query[] = array( $var_name => $var_value ); } } $query->comments = $query->query( array( 'date_query' => $date_query, )); x_load_comments( get_stylesheet_directory() . '/comment.php' ); } return $template_file; } /comments/{year}/{monthnum}/{day}/
  • 37. 37 Serving a SpecialTemplate function x_load_comments( $_template_file ) { global $post, $wp_rewrite, $wpdb, $wp_version, $wp, $user_ID; global $comments, $comment, $wp_comment_query; $wp_comment_query = get_queried_object(); $comments = $wp_comment_query->comments; $comment = reset( $comments ); $post = get_post( $comment->comment_post_ID ); require( $_template_file ); exit; } /comments/{year}/{monthnum}/{day}/
  • 38. 38 A Special CommentTemplate <?php // comment.php get_header(); ?> <div id="main-content" class="main-content"> <div id="primary" class="content-area"> <div id="content" class="site-content" role="main"> <?php if ( count( $comments ) ) : echo '<ul>'; foreach( $comments as $comment ): $post = get_post( $comment->comment_post_ID ); $url = get_comment_link( $comment ); $excerpt = get_comment_excerpt( $comment ); $date_time = get_comment_date() . ' '. get_comment_time(); echo "<li>About: "; the_title(); echo "<br>{$comment->comment_author} &lt;{$date_time}&gt;"; echo "<br><a href="{$url}">{$excerpt}</a><hr>"; endforeach; echo '</ul>'; else: get_template_part( 'content', 'none' ); endif; ?> </div><!-- #content --> </div><!-- #primary --> <?php get_sidebar( 'content' ); ?> </div><!-- #main-content --><?php get_sidebar(); get_footer(); /comments/{year}/{monthnum}/{day}/
  • 39. 39 Rules Generated by Permastruct /comments/{year}/{monthnum}/{day}/ comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1 comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1 comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4] comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4] comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/json(/(.*))?/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&json=$matches[5] comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3] comments/([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1 comments/([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1 comments/([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3] comments/([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3] comments/([0-9]{4})/([0-9]{1,2})/json(/(.*))?/?$ => index.php?year=$matches[1]&monthnum=$matches[2]&json=$matches[4] comments/([0-9]{4})/([0-9]{1,2})/?$ => index.php?year=$matches[1]&monthnum=$matches[2] comments/([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$ => index.php?year=$matches[1]&feed=$matches[2]&withcomments=1 comments/([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$ => index.php?year=$matches[1]&feed=$matches[2]&withcomments=1 comments/([0-9]{4})/page/?([0-9]{1,})/?$ => index.php?year=$matches[1]&paged=$matches[2] comments/([0-9]{4})/comment-page-([0-9]{1,})/?$ => index.php?year=$matches[1]&cpage=$matches[2] comments/([0-9]{4})/json(/(.*))?/?$ => index.php?year=$matches[1]&json=$matches[3] comments/([0-9]{4})/?$ => index.php?year=$matches[1] This was for all true & 'ep_mask' => EP_ALL
  • 40. 40 Adding an External Rule /api/{whatevah}/ RewriteRule ^api/?.*$ /wp-content/themes/wp38/api.php [QSA,L] Adds the following to .htaccess: add_action( 'init', 'x_init' ); function x_init() { global $wp_rewrite; $local_path = substr( __DIR__ . '/api.php', strlen( ABSPATH ) ); $wp_rewrite->add_external_rule( 'api/?.*$', $local_path ); // Flush your Rewrite Rules! }
  • 41. 41 Recognizing the External Rule /api/{whatevah}/ <?php /* Filename: api.php * This is only if you need WordPress loaded. * The "/../../.." assumes in a plugin directory. */ require( __DIR__ . '/../../../wp-load.php' ); $url_path = $_SERVER['REQUEST_URI']; $request = preg_replace( '#^/api/(.*)$#', '$1', $url_path ); echo "Your API request was: {$request}";
  • 42. 42 The generate_rewrite_rules() Function • This is where it gets really ugly. • This can turn your brain to mush. • You might even switch to Drupal! • Not really. Who'd want Drupal if they can haz WordPress?!? • But I digress...
  • 43. 43
  • 44. 44 Hooks Used During Rule Generation • 'delete_option_rewrite_rules' • 'pre_get_option_rewrite_rules' • 'default_option_rewrite_rules' • 'post_rewrite_rules' • 'date_rewrite_rules' • 'root_rewrite_rules' • 'comments_rewrite_rules' • 'search_rewrite_rules' • 'author_rewrite_rules' • 'page_rewrite_rules' • "{$permastructname}_rewrite_rules" • 'tag_rewrite_rules' • 'generate_rewrite_rules' • 'rewrite_rules_array'
  • 45. 45 For 'post_rewrite_rules' [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachmenta/([^/]+)/?$] => index.php?attachment=$matches[1] [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1 [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2] [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2] [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/trackback/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&tb=1 [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php? year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php? year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/page/?([0-9]{1,})/?$] => index.php? year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&paged=$matches[5] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php? year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&cpage=$matches[5] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&json=$matches[6] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)(/[0-9]+)?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&page=$matches[5] [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/?$] => index.php?attachment=$matches[1] [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1 [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2] [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2] [[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&json=$matches[5] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3] [([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3] [([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3] [([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3] [([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3] [([0-9]{4})/([0-9]{1,2})/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&json=$matches[4] [([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2] [([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2] [([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2] [([0-9]{4})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&paged=$matches[2] [([0-9]{4})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&cpage=$matches[2] [([0-9]{4})/json(/(.*))?/?$] => index.php?year=$matches[1]&json=$matches[3] [([0-9]{4})/?$] => index.php?year=$matches[1]
  • 46. 46 For 'date_rewrite_rules' [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4] [([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3] [([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3] [([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3] [([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3] [([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2] [([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2] [([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2] [([0-9]{4})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&paged=$matches[2] [([0-9]{4})/?$] => index.php?year=$matches[1]
  • 47. 47 For 'root_rewrite_rules' [feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?&feed$matches[1] [(feed|rdf|rss|rss2|atom)/?$] => index.php?&feed=$matches[1] [page/?([0-9]{1,})/?$] => index.php?&paged=$matches[1]
  • 48. 48 For 'comments_rewrite_rules' [comments/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php? &feed=$matches[1]&withcomments=1 [comments/(feed|rdf|rss|rss2|atom)/?$] => index.php?&feed=$matches[1]&withcomments=1
  • 49. 49 For 'search_rewrite_rules' [search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php? s=$matches[1]&feed=$matches[2] [search/(.+)/(feed|rdf|rss|rss2|atom)/?$] => index.php? s=$matches[1]&feed=$matches[2] [search/(.+)/page/?([0-9]{1,})/?$] => index.php?s=$matches[1]&paged=$matches[2] [search/(.+)/?$] => index.php?s=$matches[1]
  • 50. 50 For 'author_rewrite_rules' [author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php? author_name=$matches[1]&feed=$matches[2] [author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php? author_name=$matches[1]&feed=$matches[2] [author/([^/]+)/page/?([0-9]{1,})/?$] => index.php?author_name=$matches[1]&paged=$matches[2] [author/([^/]+)/?$] => index.php?author_name=$matches[1]
  • 51. 51 For 'page_rewrite_rules' [.?.+?/attachment/([^/]+)/?$] => index.php?attachment=$matches[1] [.?.+?/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1 [.?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2] [.?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2] [.?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2] [(.?.+?)/trackback/?$] => index.php?pagename=$matches[1]&tb=1 [(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?pagename=$matches[1]&feed=$matches[2] [(.?.+?)/(feed|rdf|rss|rss2|atom)/?$] => index.php?pagename=$matches[1]&feed=$matches[2] [(.?.+?)/page/?([0-9]{1,})/?$] => index.php?pagename=$matches[1]&paged=$matches[2] [(.?.+?)/comment-page-([0-9]{1,})/?$] => index.php?pagename=$matches[1]&cpage=$matches[2] [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
  • 52. 52 For 'category_rewrite_rules' [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]
  • 53. 53 For 'post_tag_rewrite_rules' [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]
  • 54. 54 For 'tag_rewrite_rules' [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]
  • 55. 55 For 'post_format_rewrite_rules' [type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php? post_format=$matches[1]&feed=$matches[2] [type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php? post_format=$matches[1]&feed=$matches[2] [type/([^/]+)/page/?([0-9]{1,})/?$] => index.php?post_format=$matches[1]&paged=$matches[2] [type/([^/]+)/?$] => index.php?post_format=$matches[1]
  • 56. 56 For 'x_comments_rewrite_rules' [comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1 [comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]&withcomments=1 [comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4] [comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4] [comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&json=$matches[5] [comments/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3] [comments/([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1 [comments/([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]&withcomments=1 [comments/([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3] [comments/([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$] => index.php? year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3] [comments/([0-9]{4})/([0-9]{1,2})/json(/(.*))?/?$] => index.php?year=$matches[1]&monthnum=$matches[2]&json=$matches[4] [comments/([0-9]{4})/([0-9]{1,2})/?$] => index.php?year=$matches[1]&monthnum=$matches[2] [comments/([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]&withcomments=1 [comments/([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$] => index.php?year=$matches[1]&feed=$matches[2]&withcomments=1 [comments/([0-9]{4})/page/?([0-9]{1,})/?$] => index.php?year=$matches[1]&paged=$matches[2] [comments/([0-9]{4})/comment-page-([0-9]{1,})/?$] => index.php?year=$matches[1]&cpage=$matches[2] [comments/([0-9]{4})/json(/(.*))?/?$] => index.php?year=$matches[1]&json=$matches[3] [comments/([0-9]{4})/?$] => index.php?year=$matches[1]
  • 57. 57 Whew! • I hope you never have to use any of that! • But if you do, you are now equipped. • (Assumed your brain didn't turn to mush.)
  • 58. 58 The 'do_parse_request' Hook • This is the strong magic. • Using 'do_parse_request' is like.... • ...getting the Glengarry Leads!!! • Seriously! It's omni-powerful! • But Theme/Plugin Devs beware... • The magic might overpower you.
  • 59. 59 Context Specific URLs • WordPress only supports Context Free URLs • But we often want Context Sensitive URLs • 'do_parse_request' makes it possible • But, it's not without risk • Plugins and Themes won't expect it • And you can have one URL hide another • With Great Power Comes Great Responsibility
  • 60. 60 PostType Name in Root /{product_name}/ add_filter( 'do_parse_request', 'x_do_parse_request', 10, 3 ); function x_do_parse_request( $continue, $wp, $extra_query_vars ) { if ( $continue ) { $url_path = trim( $_SERVER['REQUEST_URI'], '/' ); $url_path = sanitize_title_with_dashes( $url_path ); $query = new WP_Query( array( 'name' => $url_path, 'post_type' => 'x_product', 'post_status' => 'publish', )); if ( 1 == $query->found_posts ) { $wp->query_vars = array( 'post_type' => 'x_product', 'x_product' => $url_path, 'name' => $url_path, 'page' => '', ); $continue = false; // IMPORTANT } } return $continue; }
  • 61. 61 Parent and Child PostTypes Part 1 of 3 /{product_name}/{model_name}/ add_filter( 'do_parse_request', 'x_do_parse_request', 10, 3 ); function x_do_parse_request( $continue, $wp, $extra_query_vars ) { if ( $continue && ! is_admin() ) { $url_path = trim( $_SERVER['REQUEST_URI'], '/' ); if ( ! empty( $url_path ) && '?' != $url_path[0] ) { $url_path_parts = array_map( 'sanitize_title_with_dashes', explode( '/', $url_path ) ); switch( count( $url_path_parts ) ) { case 2: if ( $model = x_match_post_name( 'x_model', $model_name = $url_path_parts[1] ) ) { if ( $product = x_match_post_name( 'x_product', $url_path_parts[0] ) ) { if ( $model->post_parent == $product->ID ) { x_route_post_name( $wp, 'x_model', $model_name ); $continue = false; } } } break; case 1: $continue = ! x_match_post_name( 'x_product', $url_path_parts[0], $wp ); break; } } } return $continue; }
  • 62. 62 Parent and Child PostTypes Part 2 of 3 /{product_name}/{model_name}/ function x_route_post_name( $post_type, $post_name, $wp ) { $wp->query_vars = array( 'post_type' => $post_type, $post_type => $post_name, 'name' => $post_name, 'page' => '', ); } function x_match_post_name( $post_type, $post_name, $wp = false ) { $query = new WP_Query( array( 'name' => $post_name, 'post_type' => $post_type, 'post_status' => 'publish', )); if ( ( $matched = 1 == $query->found_posts ) && $wp ) { x_route_post_name( $wp, $post_type, $post_name ); } return $matched ? $query->post : false; }
  • 63. 63 Parent and Child PostTypes Part 3 of 3 /{product_name}/{model_name}/ add_action( 'add_meta_boxes', 'x_add_meta_boxes' ); function x_add_meta_boxes( $post_type ) { if ( 'x_model' == $post_type ) { add_meta_box( 'product_parent_box', // $id __( 'Parent Product', 'X' ), // $title 'x_model_parent_metabox', // $callback 'x_model', // $post_type 'side', // $context 'low' // $priority ); } } function x_model_parent_metabox( $post ) { $post_type_object = get_post_type_object( 'x_product' ); $post_type_object->hierarchical = true; wp_dropdown_pages( array( 'name' => 'parent_id', 'post_type' => 'x_product', 'selected' => $post->post_parent, 'show_option_none' => __( 'Select a Parent', 'X' ), 'option_none_value' => 0, )); $post_type_object->hierarchical = false; }
  • 64. 64 Stopping Core's "Helpful" Guess /{product_name}/{model_name}/ => ?post_type=x_model&p={post_id} add_action( 'redirect_canonical', 'x_redirect_canonical', 10, 2 ); function x_redirect_canonical( $redirect_url, $requested_url ) { list( $url, $query ) = explode( '?', "{$redirect_url}?" ); if ( $url == home_url('/') ) { parse_str( $query, $params ); if ( isset( $params['post_type'] ) && 'x_model' == $params['post_type'] ) { // Dangit! redirect_guess_404_permalink() interfered; set it back!!! $redirect_url = $requested_url; } } return $redirect_url; }
  • 65. 65 Reveal the QueryVars add_action( 'shutdown', 'x_shutdown' ); function x_shutdown() { if ( ! is_admin() ) { global $wp; echo '<pre><code><font size="7">'; echo '$wp->query_vars: '; print_r( $wp->query_vars ); echo '</font></code></pre>'; } }
  • 66. 66 Performance Optimization • Cache Top N URLs in wp_options • In array; key=URL, value=$wp->query_vars • Periodically Recalculate Top N • Even Better if using Memcache • This is your Homework!
  • 67. 67 Warning: May Be Incompatible • Highly Custom Sites: • A-OK • Normal Websites or Blog: • Plugins and themes might be incompatible • Plugins or Themes for Distribution: • Be very careful; this is non-standard routing • Also... • Very easy to create ambiguity in routing
  • 68. 68 The Dispatch Library • Using URI Templates as Patterns • Inspecting URLs by Segment • Plan to do lots of caching • Still PRE-ALPHA • github.com/newclarity/dispatch
  • 69. 69 But Routing Ain't Enough • Link Generation • Allow Editing the Slug • Changing the Permalink Sample
  • 70. 70 Generate the Link add_filter( 'post_type_link', 'x_post_type_link', 10, 2 ); function x_post_type_link( $post_link, $post ) { switch ( $post->post_type ) { case 'x_product': $post_link = home_url( "/{$post->post_name}/" ); break; case 'x_model': $product_link = x_post_type_link( false, get_post( $post->post_parent ) ); $post_link = "{$product_link}{$post->post_name}/"; break; } return $post_link; } /{product_name}/{model_name}/
  • 71. 71 Make the Slug Editable add_filter( 'post_type_link', 'x_post_type_link', 10, 3 ); function x_post_type_link( $post_link, $post, $leavename = false ) { $slug = $leavename ? "%{$post->post_type}%" : $post->post_name; switch ( $post->post_type ) { case 'x_product': $post_link = home_url( "/{$slug}/" ); break; case 'x_model': $product_link = x_post_type_link( false, get_post( $post->post_parent ) ); $post_link = "{$product_link}{$slug}/"; break; } return $post_link; } /{product_name}/ {model_name} /{model_name}
  • 72. 72 Changing the Sample Permalink add_filter( 'post_type_link', 'x_post_type_link', 10, 3 ); function x_post_type_link( $post_link, $post, $leavename = false ) { $slug = $leavename ? "%{$post->post_type}%" : $post->post_name; switch ( $post->post_type ) { case 'x_product': $post_link = home_url( "/{$slug}/" ); break; case 'x_model': $product_link = x_post_type_link( false, get_post( $post->post_parent ) ); $post_link = "{$product_link}{$slug}/"; break; } if ( $sample ) { // NO CLUE WHY YOU'D WANT TO DO THIS... BUT YOU CAN! $post_link = "----->[ %{$post->post_type}% ]<-----"; } return $post_link; } ----->[ ]<-----{model_name}
  • 73. 73
  • 74. 74 In Review • How WordPress Routes URLs • index.php and .htaccess • That URLs ultimately create $args for WP_Query() • Rewrite Rules and how to see/test them. • How and why to Flush Rules • The Rewrite API Functions • Especially add_rewrite_rule() and add_rewrite_tag() • The Rewrite API Hooks • Especially 'parse_request'and 'do_parse_request' What We Learned Today
  • 75. 75 In Review (cont'd) • About Endpoints, Permastructs and External Rules • How abysmal generate_rewrite_rules() is • All them crazii hooks when generating. • The Strong Magic: do_parse_request • Provides Context Sensitive URLs • How to discover the query vars needed to route URLs • Generating links, editing elugs and changing samples. • And finally the logic flow for URL rewriting/routing. What Else We Learned Today
  • 76. 76 And Stuff I Wrote for the Intro • But I decided it didn't flow. • Like how scenes gets left... • on the cutting room floor. • So I added to the DVD. ;-)
  • 77. 77 WordPress' URL Patterns • Post & Page URL Patterns • Archive URL Patterns • Feed URL Patterns • Other URL Patterns
  • 78. 78 Post & Page URL Patterns Posts /{year}/{month}/{day}/{postname}/ Pages /{pagename}/ Hierarchical Pages /{rootname}/../{leafname}/ Custom Post Types /{post_type_slug}/{postname}/ Attachments /{y}/{m}/{d}/attachment/{attachment}/ /{whatever}/attachment/{attachment}/ /{whatever}/{attachment}/
  • 79. 79 Archive URL Patterns Tag Archive /tag/{tag}/ Category Archive /category/{category}/ Author Archive /author/{author_name}/ Search Results /search/{s}/ and /s?={s} Post Format Archive /type/{post_format}/ Custom Post Type Archive /{post_type_slug}/ Also Day, Month and Year Archives.
  • 80. 80 Feed URL Patterns Feed* /feed/{feed|rdf|rss|rss2|atom}/ Category Feed* /category/{category}/feed/{feed_type}/ Tag Feed* /tag/{tag}/feed/{feed_type}/ Comment Feed* /comment/feed/{feed_type}/ Search Results Feed* /search/{s}/feed/{feed_type}/ Author Feed* /author/{author_name}/feed/{feed_type}/ * {feed_type} = feed, rss, rdf, rss2, atom Also Attachment, Post, Page, Day, Month and Year Feeds. (Why?)
  • 81. 81 Other URL Patterns Robots /robots.txt Register /{whatever}/wp-register.php Comments /{post_url}/comment-page-{cpage}/ Trackback /{post_url}/trackback/{whatever} And some... I’m sure I forgot about.
  • 82. 82 • The mapping of a URL Path of an HTTP Request identifying the Resource on yourWordPress site for which you want an HTTP Response to return a Representation. • Whew! What a mouthful! What is URL Routing?
  • 83. 83 •Because we want to control what Representation is returned for a given URL Path. •So let’s define those terms... Why Do We Care?
  • 84. 84 What is a URL Path? • Example URL: • http://example.com/2014/03/15/hello-world/ • The URL Path is: • /2014/03/15/hello-world/
  • 85. 85 What is an HTTP Request? It’s when you type a URL into your browser and press Enter!
  • 86. 86 What is a Resource? • It’s the idea of the thing you want, the thing located at the URL you type into your browser. • Don’t worry, nerd types have argued over this definition for years! • A Representation is simply the HTML file, image or other file you want when you enter a URL.
  • 87. 87 What is an HTTP Response? • It is what your WordPress website packages up and sends back to your browser and is based on the URL path you request.
  • 88. 88 It’s what gets downloaded when you request a URL with your browser. What is a Representation?
  • 89. 89 So in review: URL Routing is the mapping of a URL Path of an HTTP Request identifying the Resource on yourWordPress site for which you want an HTTP Response to return a Representation. See,That Wasn’t Hard!
  • 90. 90 Future Reading The Rewrite API:The Basics http://code.tutsplus.com/tutorials/the-rewrite-api-the-basics--wp-25474 The Rewrite API: PostTypes &Taxonomies http://code.tutsplus.com/articles/the-rewrite-api-post-types-taxonomies--wp-25488 A (Mostly) Complete Guide to the WordPress Rewrite API http://o.pmg.co/a-mostly-complete-guide-to-the-wordpress-rewrite-api Some background on rewrite rules http://wordpress.stackexchange.com/a/5478/89
  • 91. 91 Me, in More Detail President, NewClarity LLC Boutique WordPress consulting firm with a small but expert team. Specialize in complex sites & vertical market products Offering: Back-end architecture, implementation, training, code reviews. 375+ Answers at WordPress Answers Was a founding moderator Have blogged at HardcoreWP.com Hope to find time again (doh!) Several "Libraries" for WordPress at github.com/newclarity Exo - "MVC that doesn't changeWordPress, it just makes it stronger." Sunrise - Code-based Forms and Fields Dispatch - Hardcore URL Routing forWordPress Contact us for more of the "Strong Magic" mike@newclarity.net
  • 92. 92 Thanks for Attending May all your URLs be pretty and may they all match their intended patterns Mike Schinkel @mikeschinkel mike@newclarity.net

Editor's Notes

  1. T
  2. If I started talking about myself I&amp;apos;d not know where to stop and bore you to death!
  3. REALLY DIFFICULT. Problem = WHAT to include. A WEEKS of potential material 2nd choice. Knew Much, Assumed More. Learned LOTS. Best way to learn; teach it!
  4. In DESCENDING ORDER OF POTENTIAL SUCCESS
  5. Mention root usability, like Twitter. Mention the WordPress Rewrite API can&amp;apos;t handle. Ask for any burning needs for URL routing.
  6. Marvel at the regular expression! Ask if any write that correctly on the first try? POINT OUT index.php before the next slide!
  7. Mention Permalinks must be SAVED and not ?p={post_id} to have this. Mention that some servers don&amp;apos;t have permissions to save. Mention Multisite differs. Also /blog/ differs.
  8. Use: http://wp38.dev/wp-admin/tools.php?page=monkeyman-rewrite-analyzer
  9. Tell story about training examples and code review 6 months later! Recommend Classes vs. prefixing functions
  10. Your Mom&amp;apos;s taught you good hygiene, I&amp;apos;m sure! Rules are expensive to generate. Can&amp;apos;t easily tell if they need to be generated. You all use WP_DEBUG, right?!?!? SHOW Settings &amp;gt; Permalinks.
  11. Make sure to always register and/or add rewrites BEFORE flushing. Otherwise ... it&amp;apos;s a waste.
  12. Setting $wp-&amp;gt;query_vars( &amp;apos;post_type&amp;apos; =&amp;gt; true ) &amp;quot;fails&amp;quot; query. Setting $wp-&amp;gt;query_vars( &amp;apos;error&amp;apos; =&amp;gt; &amp;apos;404&amp;apos; ) forces a 404. Still need status_header( 404 ) and nocache_headers().
  13. Parameters shown are all defaults. IMO, added to support register_post_type(), register_taxonomy(). Permastruct looks easier then add_rewrite_rule(); hah!
  14. WordPress (AFAIK) doesn&amp;apos;t provide a way to recognize a permastruct
  15. Show http://wp38.dev/comments/2014/
  16. Show http://wp38.dev/rewrite-rules2.php Change $args passed and show differences. Mention conflicts with other rules, conflicts with it&amp;apos;s own rules.
  17. Show .htaccess
  18. These are the $regexes I like
  19. BEWARE because the WordPress rewrite system doesn&amp;apos;t know about it. Also easy to create conflicts
  20. Tell the story of Otto and Andy SkeltonKEY POINTS: 1) $_SERVER[&amp;apos;REQUEST_URI&amp;apos;] contains URL. 2.) $wp-&amp;gt;query_vars gets same thing that WordPress would set. 3.) $continue must be set to false. Also, set rewrite =&amp;gt; false and query_var=&amp;gt; false to disable default rewrite. SHOULD have checked for empty( $url_path )
  21. KEY POINTS: 1) $_SERVER[&amp;apos;REQUEST_URI&amp;apos;] contains URL. 2.) $wp-&amp;gt;query_vars gets same thing that WordPress would set. 3.) $continue must be set to false. Also, set rewrite =&amp;gt; false and query_var=&amp;gt; false to disable default rewrite. SHOULD have checked for empty( $url_path )
  22. Not URL rewriting, but need to associate child to parent
  23. redirect_guess_404_permalink() is called by redirect_canonical() when is_404(). Looks for post_name for any post type. UGH!
  24. Homework because I didn&amp;apos;t finish Dispatch in time for presentation
  25. Note when a Model I&amp;apos;m not passing $leavename to Product
  26. Actually the reason you&amp;apos;d want to is to bypass WordPress&amp;apos; abbreviation of the URL in the sample.
  27. These are just reference
  28. This is just background
  29. Great other source of information about URL Rewriting/Routing
  30. Interested in a Hardcore WordPress Bootcamp? 3-5 days? $600/day How many people are hiring developers?