: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$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.
$feedquery .= '&withcomments=1';
$feedquery2 .= '&withcomments=1';
// Start creating the array of rewrites for this dir.
// ...adding on /feed/ regexes => queries.
$feedmatch => $feedquery,
$feedmatch2 => $feedquery2,
$embedmatch => $embedquery,
$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 ) );
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.
* 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%' ) )
if ( str_contains( $struct, '%pagename%' ) ) {
// 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%" ) ) {
// This is for page style attachment URLs.
$page = is_post_type_hierarchical( $ptype );
// If creating rules for a permalink, do all the endpoints like attachments etc.
// 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, '/' );
$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
* 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.
// Close the match and finalize the query.
$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
$rewrite = array_merge( $rewrite, array( $match => $query ) );
// If we're matching a permalink, add those extras (attachments etc) on.
$rewrite = array_merge( array( $trackbackmatch => $trackbackquery ), $rewrite );
$rewrite = array_merge( array( $embedmatch => $embedquery ), $rewrite );
// Add regexes/queries for attachments, attachment trackbacks and so on.
// Require <permalink>/attachment/stuff form for pages because of confusion with subpages.
$sub1feed => $subfeedquery,
$sub1feed2 => $subfeedquery,
$sub1comment => $subcommentquery,
$sub1embed => $subembedquery,
$sub2feed => $subfeedquery,
$sub2feed2 => $subfeedquery,
$sub2comment => $subcommentquery,
$sub2embed => $subembedquery,
// Add the rules for this dir to the accumulating $post_rewrite.
$post_rewrite = array_merge( $rewrite, $post_rewrite );
// The finished rules. phew!
* Generates rewrite rules with permalink structure and walking directory only.
* Shorten version of WP_Rewrite::generate_rewrite_rules() that allows for shorter
* list of parameters. See the method for longer description of what generating
* @see WP_Rewrite::generate_rewrite_rules() See for long description and rest of parameters.
* @param string $permalink_structure The permalink structure to generate rules.
* @param bool $walk_dirs Optional. Whether to create list of directories to walk over.
* @return array An array of rewrite rules keyed by their regex pattern.
public function generate_rewrite_rule( $permalink_structure, $walk_dirs = false ) {
return $this->generate_rewrite_rules( $permalink_structure, EP_NONE, false, false, false, $walk_dirs );
* Constructs rewrite matches and queries from permalink structure.
* Runs the action {@see 'generate_rewrite_rules'} with the parameter that is an
* reference to the current WP_Rewrite instance to further manipulate the
* permalink structures and rewrite rules. Runs the {@see 'rewrite_rules_array'}
* filter on the full rewrite rule array.
* There are two ways to manipulate the rewrite rules, one by hooking into
* the {@see 'generate_rewrite_rules'} action and gaining full control of the
* object or just manipulating the rewrite rule array before it is passed
* @return string[] An associative array of matches and queries.
public function rewrite_rules() {
if ( empty( $this->permalink_structure ) ) {
// robots.txt -- only if installed at the root.
$home_path = parse_url( home_url() );
$robots_rewrite = ( empty( $home_path['path'] ) || '/' === $home_path['path'] ) ? array( 'robots\.txt$' => $this->index . '?robots=1' ) : array();
// favicon.ico -- only if installed at the root.
$favicon_rewrite = ( empty( $home_path['path'] ) || '/' === $home_path['path'] ) ? array( 'favicon\.ico$' => $this->index . '?favicon=1' ) : array();
// Old feed and service files.
$deprecated_files = array(
'.*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\.php$' => $this->index . '?feed=old',
'.*wp-app\.php(/.*)?$' => $this->index . '?error=403',
$registration_pages = array();
if ( is_multisite() && is_main_site() ) {
$registration_pages['.*wp-signup.php$'] = $this->index . '?signup=true';
$registration_pages['.*wp-activate.php$'] = $this->index . '?activate=true';
$registration_pages['.*wp-register.php$'] = $this->index . '?register=true';
$post_rewrite = $this->generate_rewrite_rules( $this->permalink_structure, EP_PERMALINK );
* Filters rewrite rules used for "post" archives.
* @param string[] $post_rewrite Array of rewrite rules for posts, keyed by their regex pattern.
$post_rewrite = apply_filters( 'post_rewrite_rules', $post_rewrite );
$date_rewrite = $this->generate_rewrite_rules( $this->get_date_permastruct(), EP_DATE );
* Filters rewrite rules used for date archives.
* Likely date archives would include `/yyyy/`, `/yyyy/mm/`, and `/yyyy/mm/dd/`.
* @param string[] $date_rewrite Array of rewrite rules for date archives, keyed by their regex pattern.
$date_rewrite = apply_filters( 'date_rewrite_rules', $date_rewrite );
// Root-level rewrite rules.
$root_rewrite = $this->generate_rewrite_rules( $this->root . '/', EP_ROOT );
* Filters rewrite rules used for root-level archives.
* Likely root-level archives would include pagination rules for the homepage
* as well as site-wide post feeds (e.g. `/feed/`, and `/feed/atom/`).
* @param string[] $root_rewrite Array of root-level rewrite rules, keyed by their regex pattern.
$root_rewrite = apply_filters( 'root_rewrite_rules', $root_rewrite );
// Comments rewrite rules.
$comments_rewrite = $this->generate_rewrite_rules( $this->root . $this->comments_base, EP_COMMENTS, false, true, true, false );
* Filters rewrite rules used for comment feed archives.
* Likely comments feed archives include `/comments/feed/` and `/comments/feed/atom/`.
* @param string[] $comments_rewrite Array of rewrite rules for the site-wide comments feeds, keyed by their regex pattern.
$comments_rewrite = apply_filters( 'comments_rewrite_rules', $comments_rewrite );
$search_structure = $this->get_search_permastruct();
$search_rewrite = $this->generate_rewrite_rules( $search_structure, EP_SEARCH );
* Filters rewrite rules used for search archives.
* Likely search-related archives include `/search/search+query/` as well as
* pagination and feed paths for a search.
* @param string[] $search_rewrite Array of rewrite rules for search queries, keyed by their regex pattern.
$search_rewrite = apply_filters( 'search_rewrite_rules', $search_rewrite );
$author_rewrite = $this->generate_rewrite_rules( $this->get_author_permastruct(), EP_AUTHORS );
* Filters rewrite rules used for author archives.
* Likely author archives would include `/author/author-name/`, as well as
* pagination and feed paths for author archives.
* @param string[] $author_rewrite Array of rewrite rules for author archives, keyed by their regex pattern.
$author_rewrite = apply_filters( 'author_rewrite_rules', $author_rewrite );
$page_rewrite = $this->page_rewrite_rules();
* Filters rewrite rules used for "page" post type archives.
* @param string[] $page_rewrite Array of rewrite rules for the "page" post type, keyed by their regex pattern.
$page_rewrite = apply_filters( 'page_rewrite_rules', $page_rewrite );
foreach ( $this->extra_permastructs as $permastructname => $struct ) {
if ( is_array( $struct ) ) {
if ( count( $struct ) === 2 ) {
$rules = $this->generate_rewrite_rules( $struct[0], $struct[1] );
$rules = $this->generate_rewrite_rules( $struct['struct'], $struct['ep_mask'], $struct['paged'], $struct['feed'], $struct['forcomments'], $struct['walk_dirs'], $struct['endpoints'] );
$rules = $this->generate_rewrite_rules( $struct );
* Filters rewrite rules used for individual permastructs.
* The dynamic portion of the hook name, `$permastructname`, refers
* to the name of the registered permastruct.
* Possible hook names include:
* - `category_rewrite_rules`
* - `post_format_rewrite_rules`
* - `post_tag_rewrite_rules`
* @param string[] $rules Array of rewrite rules generated for the current permastruct, keyed by their regex pattern.
$rules = apply_filters( "{$permastructname}_rewrite_rules", $rules );
if ( 'post_tag' === $permastructname ) {
* Filters rewrite rules used specifically for Tags.
* @deprecated 3.1.0 Use {@see 'post_tag_rewrite_rules'} instead.
* @param string[] $rules Array of rewrite rules generated for tags, keyed by their regex pattern.
$rules = apply_filters_deprecated( 'tag_rewrite_rules', array( $rules ), '3.1.0', 'post_tag_rewrite_rules' );
$this->extra_rules_top = array_merge( $this->extra_rules_top, $rules );
if ( $this->use_verbose_page_rules ) {
$this->rules = array_merge( $this->extra_rules_top, $robots_rewrite, $favicon_rewrite, $deprecated_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite, $author_rewrite, $date_rewrite, $page_rewrite, $post_rewrite, $this->extra_rules );
$this->rules = array_merge( $this->extra_rules_top, $robots_rewrite, $favicon_rewrite, $deprecated_files, $registration_pages, $root_rewrite, $comments_rewrite, $search_rewrite, $author_rewrite, $date_rewrite, $post_rewrite, $page_rewrite, $this->extra_rules );
* Fires after the rewrite rules are generated.
* @param WP_Rewrite $wp_rewrite Current WP_Rewrite instance (passed by reference).
do_action_ref_array( 'generate_rewrite_rules', array( &$this ) );
* Filters the full set of generated rewrite rules.
* @param string[] $rules The compiled array of rewrite rules, keyed by their regex pattern.
$this->rules = apply_filters( 'rewrite_rules_array', $this->rules );
* Retrieves the rewrite rules.
* The difference between this method and WP_Rewrite::rewrite_rules() is that
* this method stores the rewrite rules in the 'rewrite_rules' option and retrieves
* it. This prevents having to process all of the permalinks to get the rewrite rules
* in the form of caching.
* @return string[] Array of rewrite rules keyed by their regex pattern.
public function wp_rewrite_rules() {
$this->rules = get_option( 'rewrite_rules' );
if ( empty( $this->rules ) ) {
$this->refresh_rewrite_rules();
* Refreshes the rewrite rules, saving the fresh value to the database.