Skip to content

Instantly share code, notes, and snippets.

@danlamanna
Created July 5, 2012 20:08
Show Gist options
  • Save danlamanna/3056144 to your computer and use it in GitHub Desktop.
Save danlamanna/3056144 to your computer and use it in GitHub Desktop.
Code is Poetry
/**
* Generate rewrite rules from a permalink structure.
*
* The main WP_Rewrite function for building the rewrite rule list. The
* contents of the function is a mix of black magic and regular expressions,
* so best just ignore the contents and move to the parameters.
*
* @since 1.5.0
* @access public
*
* @param string $permalink_structure The permalink structure.
* @param int $ep_mask Endpoint mask defining what endpoints are added to the structure. Default is EP_NONE.
* @param bool $paged Should archive pagination rules be added for the structure? Default is true.
* @param bool $feed Should feed rewrite rules be added for the structure? Default is true.
* @param bool $forcomments Should the feed rules be a query for a comments feed? Default is false.
* @param bool $walk_dirs Should the 'directories' making up the structure be walked over and rewrite rules
* built for each in turn? Default is true.
* @param bool $endpoints Should endpoints be applied to the generated rewrite rules? Default is true.
* @return array Rewrite rule list.
*/
function generate_rewrite_rules($permalink_structure, $ep_mask = EP_NONE, $paged = true, $feed = true, $forcomments = false, $walk_dirs = true, $endpoints = true) {
//build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/?
$feedregex2 = '';
foreach ( (array) $this->feeds as $feed_name)
$feedregex2 .= $feed_name . '|';
$feedregex2 = '(' . trim($feedregex2, '|') . ')/?$';
//$feedregex is identical but with /feed/ added on as well, so URLs like <permalink>/feed/atom
//and <permalink>/atom are both possible
$feedregex = $this->feed_base . '/' . $feedregex2;
//build a regex to match the trackback and page/xx parts of URLs
$trackbackregex = 'trackback/?$';
$pageregex = $this->pagination_base . '/?([0-9]{1,})/?$';
$commentregex = 'comment-page-([0-9]{1,})/?$';
//build up an array of endpoint regexes to append => queries to append
if ( $endpoints ) {
$ep_query_append = array ();
foreach ( (array) $this->endpoints as $endpoint) {
//match everything after the endpoint name, but allow for nothing to appear there
$epmatch = $endpoint[1] . '(/(.*))?/?$';
//this will be appended on to the rest of the query for each dir
$epquery = '&' . $endpoint[1] . '=';
$ep_query_append[$epmatch] = array ( $endpoint[0], $epquery );
}
}
//get everything up to the first rewrite tag
$front = substr($permalink_structure, 0, strpos($permalink_structure, '%'));
//build an array of the tags (note that said array ends up being in $tokens[0])
preg_match_all('/%.+?%/', $permalink_structure, $tokens);
$num_tokens = count($tokens[0]);
$index = $this->index; //probably 'index.php'
$feedindex = $index;
$trackbackindex = $index;
//build a list from the rewritecode and queryreplace arrays, that will look something like
//tagname=$matches[i] where i is the current $i
for ( $i = 0; $i < $num_tokens; ++$i ) {
if ( 0 < $i )
$queries[$i] = $queries[$i - 1] . '&';
else
$queries[$i] = '';
$query_token = str_replace($this->rewritecode, $this->queryreplace, $tokens[0][$i]) . $this->preg_index($i+1);
$queries[$i] .= $query_token;
}
//get the structure, minus any cruft (stuff that isn't tags) at the front
$structure = $permalink_structure;
if ( $front != '/' )
$structure = str_replace($front, '', $structure);
//create a list of dirs to walk over, making rewrite rules for each level
//so for example, a $structure of /%year%/%monthnum%/%postname% would create
//rewrite rules for /%year%/, /%year%/%monthnum%/ and /%year%/%monthnum%/%postname%
$structure = trim($structure, '/');
$dirs = $walk_dirs ? explode('/', $structure) : array( $structure );
$num_dirs = count($dirs);
//strip slashes from the front of $front
$front = preg_replace('|^/+|', '', $front);
//the main workhorse loop
$post_rewrite = array();
$struct = $front;
for ( $j = 0; $j < $num_dirs; ++$j ) {
//get the struct for this dir, and trim slashes off the front
$struct .= $dirs[$j] . '/'; //accumulate. see comment near explode('/', $structure) above
$struct = ltrim($struct, '/');
//replace tags with regexes
$match = str_replace($this->rewritecode, $this->rewritereplace, $struct);
//make a list of tags, and store how many there are in $num_toks
$num_toks = preg_match_all('/%.+?%/', $struct, $toks);
//get the 'tagname=$matches[i]'
$query = ( isset($queries) && is_array($queries) && !empty($num_toks) ) ? $queries[$num_toks - 1] : '';
//set up $ep_mask_specific which is used to match more specific URL types
switch ( $dirs[$j] ) {
case '%year%':
$ep_mask_specific = EP_YEAR;
break;
case '%monthnum%':
$ep_mask_specific = EP_MONTH;
break;
case '%day%':
$ep_mask_specific = EP_DAY;
break;
default:
$ep_mask_specific = EP_NONE;
}
//create query for /page/xx
$pagematch = $match . $pageregex;
$pagequery = $index . '?' . $query . '&paged=' . $this->preg_index($num_toks + 1);
//create query for /comment-page-xx
$commentmatch = $match . $commentregex;
$commentquery = $index . '?' . $query . '&cpage=' . $this->preg_index($num_toks + 1);
if ( get_option('page_on_front') ) {
//create query for Root /comment-page-xx
$rootcommentmatch = $match . $commentregex;
$rootcommentquery = $index . '?' . $query . '&page_id=' . get_option('page_on_front') . '&cpage=' . $this->preg_index($num_toks + 1);
}
//create query for /feed/(feed|atom|rss|rss2|rdf)
$feedmatch = $match . $feedregex;
$feedquery = $feedindex . '?' . $query . '&feed=' . $this->preg_index($num_toks + 1);
//create query for /(feed|atom|rss|rss2|rdf) (see comment near creation of $feedregex)
$feedmatch2 = $match . $feedregex2;
$feedquery2 = $feedindex . '?' . $query . '&feed=' . $this->preg_index($num_toks + 1);
//if asked to, turn the feed queries into comment feed ones
if ( $forcomments ) {
$feedquery .= '&withcomments=1';
$feedquery2 .= '&withcomments=1';
}
//start creating the array of rewrites for this dir
$rewrite = array();
if ( $feed ) //...adding on /feed/ regexes => queries
$rewrite = array($feedmatch => $feedquery, $feedmatch2 => $feedquery2);
if ( $paged ) //...and /page/xx ones
$rewrite = array_merge($rewrite, array($pagematch => $pagequery));
//only on pages with comments add ../comment-page-xx/
if ( EP_PAGES & $ep_mask || EP_PERMALINK & $ep_mask )
$rewrite = array_merge($rewrite, array($commentmatch => $commentquery));
else if ( EP_ROOT & $ep_mask && get_option('page_on_front') )
$rewrite = array_merge($rewrite, array($rootcommentmatch => $rootcommentquery));
//do endpoints
if ( $endpoints ) {
foreach ( (array) $ep_query_append as $regex => $ep) {
//add the endpoints on if the mask fits
if ( $ep[0] & $ep_mask || $ep[0] & $ep_mask_specific )
$rewrite[$match . $regex] = $index . '?' . $query . $ep[1] . $this->preg_index($num_toks + 2);
}
}
//if we've got some tags in this dir
if ( $num_toks ) {
$post = false;
$page = false;
//check to see if this dir is permalink-level: i.e. the structure specifies an
//individual post. Do this by checking it contains at least one of 1) post name,
//2) post ID, 3) page name, 4) timestamp (year, month, day, hour, second and
//minute all present). Set these flags now as we need them for the endpoints.
if ( strpos($struct, '%postname%') !== false
|| strpos($struct, '%post_id%') !== false
|| strpos($struct, '%pagename%') !== false
|| (strpos($struct, '%year%') !== false && strpos($struct, '%monthnum%') !== false && strpos($struct, '%day%') !== false && strpos($struct, '%hour%') !== false && strpos($struct, '%minute%') !== false && strpos($struct, '%second%') !== false)
) {
$post = true;
if ( strpos($struct, '%pagename%') !== false )
$page = true;
}
if ( ! $post ) {
// For custom post types, we need to add on endpoints as well.
foreach ( get_post_types( array('_builtin' => false ) ) as $ptype ) {
if ( strpos($struct, "%$ptype%") !== false ) {
$post = true;
$page = is_post_type_hierarchical( $ptype ); // This is for page style attachment url's
break;
}
}
}
//if we're creating rules for a permalink, do all the endpoints like attachments etc
if ( $post ) {
//create query and regex for trackback
$trackbackmatch = $match . $trackbackregex;
$trackbackquery = $trackbackindex . '?' . $query . '&tb=1';
//trim slashes from the end of the regex for this dir
$match = rtrim($match, '/');
//get rid of brackets
$submatchbase = str_replace( array('(', ')'), '', $match);
//add a rule for at attachments, which take the form of <permalink>/some-text
$sub1 = $submatchbase . '/([^/]+)/';
$sub1tb = $sub1 . $trackbackregex; //add trackback regex <permalink>/trackback/...
$sub1feed = $sub1 . $feedregex; //and <permalink>/feed/(atom|...)
$sub1feed2 = $sub1 . $feedregex2; //and <permalink>/(feed|atom...)
$sub1comment = $sub1 . $commentregex; //and <permalink>/comment-page-xx
//add another rule to match attachments in the explicit form:
//<permalink>/attachment/some-text
$sub2 = $submatchbase . '/attachment/([^/]+)/';
$sub2tb = $sub2 . $trackbackregex; //and add trackbacks <permalink>/attachment/trackback
$sub2feed = $sub2 . $feedregex; //feeds, <permalink>/attachment/feed/(atom|...)
$sub2feed2 = $sub2 . $feedregex2; //and feeds again on to this <permalink>/attachment/(feed|atom...)
$sub2comment = $sub2 . $commentregex; //and <permalink>/comment-page-xx
//create queries for these extra tag-ons we've just dealt with
$subquery = $index . '?attachment=' . $this->preg_index(1);
$subtbquery = $subquery . '&tb=1';
$subfeedquery = $subquery . '&feed=' . $this->preg_index(2);
$subcommentquery = $subquery . '&cpage=' . $this->preg_index(2);
//do endpoints for attachments
if ( !empty($endpoints) ) {
foreach ( (array) $ep_query_append as $regex => $ep ) {
if ( $ep[0] & EP_ATTACHMENT ) {
$rewrite[$sub1 . $regex] = $subquery . $ep[1] . $this->preg_index(2);
$rewrite[$sub2 . $regex] = $subquery . $ep[1] . $this->preg_index(2);
}
}
}
//now we've finished with endpoints, finish off the $sub1 and $sub2 matches
//add a ? as we don't have to match that last slash, and finally a $ so we
//match to the end of the URL
$sub1 .= '?$';
$sub2 .= '?$';
//post pagination, e.g. <permalink>/2/
$match = $match . '(/[0-9]+)?/?$';
$query = $index . '?' . $query . '&page=' . $this->preg_index($num_toks + 1);
} else { //not matching a permalink so this is a lot simpler
//close the match and finalise the query
$match .= '?$';
$query = $index . '?' . $query;
}
//create the final array for this dir by joining the $rewrite array (which currently
//only contains rules/queries for trackback, pages etc) to the main regex/query for
//this dir
$rewrite = array_merge($rewrite, array($match => $query));
//if we're matching a permalink, add those extras (attachments etc) on
if ( $post ) {
//add trackback
$rewrite = array_merge(array($trackbackmatch => $trackbackquery), $rewrite);
//add regexes/queries for attachments, attachment trackbacks and so on
if ( ! $page ) //require <permalink>/attachment/stuff form for pages because of confusion with subpages
$rewrite = array_merge($rewrite, array($sub1 => $subquery, $sub1tb => $subtbquery, $sub1feed => $subfeedquery, $sub1feed2 => $subfeedquery, $sub1comment => $subcommentquery));
$rewrite = array_merge(array($sub2 => $subquery, $sub2tb => $subtbquery, $sub2feed => $subfeedquery, $sub2feed2 => $subfeedquery, $sub2comment => $subcommentquery), $rewrite);
}
} //if($num_toks)
//add the rules for this dir to the accumulating $post_rewrite
$post_rewrite = array_merge($rewrite, $post_rewrite);
} //foreach ($dir)
return $post_rewrite; //the finished rules. phew!
}
@danlamanna
Copy link
Author

Making fun of WordPress.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment