WP_Rewrite::generate_rewrite_rules( string $permalink_structure, int $ep_mask = EP_NONE, bool $paged = true, bool $feed = true, bool $forcomments = false, bool $walk_dirs = true, bool $endpoints = true ): string[]

Generates rewrite rules from a permalink structure.

Description

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.

Parameters

$permalink_structurestringrequired
The permalink structure.
$ep_maskintoptional
Endpoint mask defining what endpoints are added to the structure.
Accepts a mask of:
  • EP_ALL
  • EP_NONE
  • EP_ALL_ARCHIVES
  • EP_ATTACHMENT
  • EP_AUTHORS
  • EP_CATEGORIES
  • EP_COMMENTS
  • EP_DATE
  • EP_DAY
  • EP_MONTH
  • EP_PAGES
  • EP_PERMALINK
  • EP_ROOT
  • EP_SEARCH
  • EP_TAGS
  • EP_YEAR Default EP_NONE.

Default:EP_NONE

$pagedbooloptional
Whether archive pagination rules should be added for the structure.

Default:true

$feedbooloptional
Whether feed rewrite rules should be added for the structure.

Default:true

$forcommentsbooloptional
Whether the feed rules should be a query for a comments feed.

Default:false

$walk_dirsbooloptional
Whether the 'directories' making up the structure should be walked over and rewrite rules built for each in-turn.

Default:true

$endpointsbooloptional
Whether endpoints should be applied to the generated rewrite rules.

Default:true

Return

string[] Array of rewrite rules keyed by their regex pattern.

More Information

A large method that generates the rewrite rules for a given structure, $permalink_structure.

If $page is true, an extra rewrite rule will be generated for accessing different pages (e.g. /category/tech/page/2 points to the second page of the ‘tech’ category archive).

If $feed is true, extra rewrite rules will be generated for obtaining a feed of the current page, and if $forcomments is true, this will be a comment feed.

If $walk_dirs is true, then a rewrite rule will be generated for each directory of the structure provided, e.g. if you provide it with ‘/%year%/%month%/%day/’, rewrite rules will be generated for ‘/%year%/’, /%year%/%month%/’ and ‘/%year%/%month%/%day%/’.

This returns an associative array using the regex part of the rewrite rule as the keys and redirect part of the rewrite rule as the value.

Source

public 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   = $this->comments_pagination_base . '-([0-9]{1,})/?$';
	$embedregex     = 'embed/?$';

	// 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[2] . '=';
			$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;
	$embedindex     = $index;

	/*
	 * Build a list from the rewritecode and queryreplace arrays, that will look something
	 * like tagname=$matches[i] where i is the current $i.
	 */
	$queries = array();
	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 = ( ! empty( $num_toks ) && isset( $queries[ $num_toks - 1 ] ) ) ? $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 );

		// Create query and regex for embeds.
		$embedmatch = $match . $embedregex;
		$embedquery = $embedindex . '?' . $query . '&embed=true';

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

		// ...adding on /feed/ regexes => queries.
		if ( $feed ) {
			$rewrite = array(
				$feedmatch  => $feedquery,
				$feedmatch2 => $feedquery2,
				$embedmatch => $embedquery,
			);
		}

		// ...and /page/xx ones.
		if ( $paged ) {
			$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 ) );
		} elseif ( 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 ( str_contains( $struct, '%postname%' )
				|| str_contains( $struct, '%post_id%' )
				|| str_contains( $struct, '%pagename%' )
				|| ( str_contains( $struct, '%year%' )
					&& str_contains( $struct, '%monthnum%' )
					&& str_contains( $struct, '%day%' )
					&& str_contains( $struct, '%hour%' )
					&& str_contains( $struct, '%minute%' )
					&& str_contains( $struct, '%second%' ) )
			) {
				$post = true;
				if ( str_contains( $struct, '%pagename%' ) ) {
					$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 ( str_contains( $struct, "%$ptype%" ) ) {
						$post = true;

						// This is for page style attachment URLs.
						$page = is_post_type_hierarchical( $ptype );
						break;
					}
				}
			}

			// If 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';

				// Create query and regex for embeds.
				$embedmatch = $match . $embedregex;
				$embedquery = $embedindex . '?' . $query . '&embed=true';

				// 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 . '/([^/]+)/';

				// Add trackback regex <permalink>/trackback/...
				$sub1tb = $sub1 . $trackbackregex;

				// And <permalink>/feed/(atom|...)
				$sub1feed = $sub1 . $feedregex;

				// And <permalink>/(feed|atom...)
				$sub1feed2 = $sub1 . $feedregex2;

				// And <permalink>/comment-page-xx
				$sub1comment = $sub1 . $commentregex;

				// And <permalink>/embed/...
				$sub1embed = $sub1 . $embedregex;

				/*
				 * Add another rule to match attachments in the explicit form:
				 * <permalink>/attachment/some-text
				 */
				$sub2 = $submatchbase . '/attachment/([^/]+)/';

				// And add trackbacks <permalink>/attachment/trackback.
				$sub2tb = $sub2 . $trackbackregex;

				// Feeds, <permalink>/attachment/feed/(atom|...)
				$sub2feed = $sub2 . $feedregex;

				// And feeds again on to this <permalink>/attachment/(feed|atom...)
				$sub2feed2 = $sub2 . $feedregex2;

				// And <permalink>/comment-page-xx
				$sub2comment = $sub2 . $commentregex;

				// And <permalink>/embed/...
				$sub2embed = $sub2 . $embedregex;

				// 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 );
				$subembedquery   = $subquery . '&embed=true';

				// 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( 3 );
							$rewrite[ $sub2 . $regex ] = $subquery . $ep[1] . $this->preg_index( 3 );
						}
					}
				}

				/*
				 * 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/
				 * Previously: '(/[0-9]+)?/?$', which produced '/2' for page.
				 * When cast to int, returned 0.
				 */
				$match = $match . '(?:/([0-9]+))?/?$';
				$query = $index . '?' . $query . '&page=' . $this->preg_index( $num_toks + 1 );

				// Not matching a permalink so this is a lot simpler.
			} else {
				// Close the match and finalize 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 embed.
				$rewrite = array_merge( array( $embedmatch => $embedquery ), $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,
							$sub1embed   => $subembedquery,
						)
					);
				}

				$rewrite = array_merge(
					array(
						$sub2        => $subquery,
						$sub2tb      => $subtbquery,
						$sub2feed    => $subfeedquery,
						$sub2feed2   => $subfeedquery,
						$sub2comment => $subcommentquery,
						$sub2embed   => $subembedquery,
					),
					$rewrite
				);
			}
		}
		// Add the rules for this dir to the accumulating $post_rewrite.
		$post_rewrite = array_merge( $rewrite, $post_rewrite );
	}

	// The finished rules. phew!
	return $post_rewrite;
}

Changelog

VersionDescription
1.5.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.