: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* If the `wp_loaded` action has not occurred yet, will postpone saving to the database.
private function refresh_rewrite_rules() {
$this->matches = 'matches';
if ( ! did_action( 'wp_loaded' ) ) {
* Is not safe to save the results right now, as the rules may be partial.
* Need to give all rules the chance to register.
add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
update_option( 'rewrite_rules', $this->rules );
* Retrieves mod_rewrite-formatted rewrite rules to write to .htaccess.
* Does not actually write to the .htaccess file, but creates the rules for
* Will add the non_wp_rules property rules to the .htaccess file before
* the WordPress rewrite rules one.
public function mod_rewrite_rules() {
if ( ! $this->using_permalinks() ) {
$site_root = parse_url( site_url() );
if ( isset( $site_root['path'] ) ) {
$site_root = trailingslashit( $site_root['path'] );
$home_root = parse_url( home_url() );
if ( isset( $home_root['path'] ) ) {
$home_root = trailingslashit( $home_root['path'] );
$rules = "<IfModule mod_rewrite.c>\n";
$rules .= "RewriteEngine On\n";
$rules .= "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n";
$rules .= "RewriteBase $home_root\n";
// Prevent -f checks on index.php.
$rules .= "RewriteRule ^index\.php$ - [L]\n";
// Add in the rules that don't redirect to WP's index.php (and thus shouldn't be handled by WP at all).
foreach ( (array) $this->non_wp_rules as $match => $query ) {
// Apache 1.3 does not support the reluctant (non-greedy) modifier.
$match = str_replace( '.+?', '.+', $match );
$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
if ( $this->use_verbose_rules ) {
$rewrite = $this->rewrite_rules();
$num_rules = count( $rewrite );
$rules .= "RewriteCond %{REQUEST_FILENAME} -f [OR]\n" .
"RewriteCond %{REQUEST_FILENAME} -d\n" .
"RewriteRule ^.*$ - [S=$num_rules]\n";
foreach ( (array) $rewrite as $match => $query ) {
// Apache 1.3 does not support the reluctant (non-greedy) modifier.
$match = str_replace( '.+?', '.+', $match );
if ( str_contains( $query, $this->index ) ) {
$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
$rules .= 'RewriteRule ^' . $match . ' ' . $site_root . $query . " [QSA,L]\n";
$rules .= "RewriteCond %{REQUEST_FILENAME} !-f\n" .
"RewriteCond %{REQUEST_FILENAME} !-d\n" .
"RewriteRule . {$home_root}{$this->index} [L]\n";
$rules .= "</IfModule>\n";
* Filters the list of rewrite rules formatted for output to an .htaccess file.
* @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
$rules = apply_filters( 'mod_rewrite_rules', $rules );
* Filters the list of rewrite rules formatted for output to an .htaccess file.
* @deprecated 1.5.0 Use the {@see 'mod_rewrite_rules'} filter instead.
* @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
return apply_filters_deprecated( 'rewrite_rules', array( $rules ), '1.5.0', 'mod_rewrite_rules' );
* Retrieves IIS7 URL Rewrite formatted rewrite rules to write to web.config file.
* Does not actually write to the web.config file, but creates the rules for
* @param bool $add_parent_tags Optional. Whether to add parent tags to the rewrite rule sets.
* @return string IIS7 URL rewrite rule sets.
public function iis7_url_rewrite_rules( $add_parent_tags = false ) {
if ( ! $this->using_permalinks() ) {
if ( $add_parent_tags ) {
$rules .= '<configuration>
<rule name="WordPress: ' . esc_attr( home_url() ) . '" patternSyntax="Wildcard">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<action type="Rewrite" url="index.php" />
if ( $add_parent_tags ) {
* Filters the list of rewrite rules formatted for output to a web.config.
* @param string $rules Rewrite rules formatted for IIS web.config.
return apply_filters( 'iis7_url_rewrite_rules', $rules );
* Adds a rewrite rule that transforms a URL structure to a set of query vars.
* Any value in the $after parameter that isn't 'bottom' will result in the rule
* being placed at the top of the rewrite rules.
* @since 4.4.0 Array support was added to the `$query` parameter.
* @param string $regex Regular expression to match request against.
* @param string|array $query The corresponding query vars for this rewrite rule.
* @param string $after Optional. Priority of the new rule. Accepts 'top'
* or 'bottom'. Default 'bottom'.
public function add_rule( $regex, $query, $after = 'bottom' ) {
if ( is_array( $query ) ) {
$query = add_query_arg( $query, 'index.php' );
$index = ! str_contains( $query, '?' ) ? strlen( $query ) : strpos( $query, '?' );
$front = substr( $query, 0, $index );
$external = $front !== $this->index;
// "external" = it doesn't correspond to index.php.
$this->add_external_rule( $regex, $query );
if ( 'bottom' === $after ) {
$this->extra_rules = array_merge( $this->extra_rules, array( $regex => $query ) );
$this->extra_rules_top = array_merge( $this->extra_rules_top, array( $regex => $query ) );
* Adds a rewrite rule that doesn't correspond to index.php.
* @param string $regex Regular expression to match request against.
* @param string $query The corresponding query vars for this rewrite rule.
public function add_external_rule( $regex, $query ) {
$this->non_wp_rules[ $regex ] = $query;
* Adds an endpoint, like /trackback/.
* @since 3.9.0 $query_var parameter added.
* @since 4.3.0 Added support for skipping query var registration by passing `false` to `$query_var`.
* @see add_rewrite_endpoint() for full documentation.
* @global WP $wp Current WordPress environment instance.
* @param string $name Name of the endpoint.
* @param int $places Endpoint mask describing the places the endpoint should be added.
* @param string|bool $query_var Optional. Name of the corresponding query variable. Pass `false` to
* skip registering a query_var for this endpoint. Defaults to the
public function add_endpoint( $name, $places, $query_var = true ) {
// For backward compatibility, if null has explicitly been passed as `$query_var`, assume `true`.
if ( true === $query_var || null === $query_var ) {
$this->endpoints[] = array( $places, $name, $query_var );
$wp->add_query_var( $query_var );
* Adds a new permalink structure.
* A permalink structure (permastruct) is an abstract definition of a set of rewrite rules;
* it is an easy way of expressing a set of regular expressions that rewrite to a set of
* query strings. The new permastruct is added to the WP_Rewrite::$extra_permastructs array.
* When the rewrite rules are built by WP_Rewrite::rewrite_rules(), all of these extra
* permastructs are passed to WP_Rewrite::generate_rewrite_rules() which transforms them
* into the regular expressions that many love to hate.
* The `$args` parameter gives you control over how WP_Rewrite::generate_rewrite_rules()
* works on the new permastruct.
* @param string $name Name for permalink structure.
* @param string $struct Permalink structure (e.g. category/%category%)
* Optional. Arguments for building rewrite rules based on the permalink structure.
* @type bool $with_front Whether the structure should be prepended with `WP_Rewrite::$front`.
* @type int $ep_mask The endpoint mask defining which endpoints are added to the structure.
* @type bool $paged Whether archive pagination rules should be added for the structure.
* @type bool $feed Whether feed rewrite rules should be added for the structure. Default true.
* @type bool $forcomments Whether the feed rules should be a query for a comments feed. Default false.
* @type bool $walk_dirs Whether the 'directories' making up the structure should be walked over
* and rewrite rules built for each in-turn. Default true.
* @type bool $endpoints Whether endpoints should be applied to the generated rules. Default true.
public function add_permastruct( $name, $struct, $args = array() ) {
// Back-compat for the old parameters: $with_front and $ep_mask.
if ( ! is_array( $args ) ) {
$args = array( 'with_front' => $args );
if ( func_num_args() === 4 ) {
$args['ep_mask'] = func_get_arg( 3 );
$args = array_intersect_key( $args, $defaults );
$args = wp_parse_args( $args, $defaults );
if ( $args['with_front'] ) {
$struct = $this->front . $struct;
$struct = $this->root . $struct;
$args['struct'] = $struct;
$this->extra_permastructs[ $name ] = $args;
* Removes a permalink structure.
* @param string $name Name for permalink structure.
public function remove_permastruct( $name ) {
unset( $this->extra_permastructs[ $name ] );
* Removes rewrite rules and then recreate rewrite rules.
* Calls WP_Rewrite::wp_rewrite_rules() after removing the 'rewrite_rules' option.
* If the function named 'save_mod_rewrite_rules' exists, it will be called.
* @param bool $hard Whether to update .htaccess (hard flush) or just update rewrite_rules option (soft flush). Default is true (hard).
public function flush_rules( $hard = true ) {
static $do_hard_later = null;
// Prevent this action from running before everyone has registered their rewrites.
if ( ! did_action( 'wp_loaded' ) ) {
add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
$do_hard_later = ( isset( $do_hard_later ) ) ? $do_hard_later || $hard : $hard;
if ( isset( $do_hard_later ) ) {
$this->refresh_rewrite_rules();
* Filters whether a "hard" rewrite rule flush should be performed when requested.
* A "hard" flush updates .htaccess (Apache) or web.config (IIS).
* @param bool $hard Whether to flush rewrite rules "hard". Default true.
if ( ! $hard || ! apply_filters( 'flush_rewrite_rules_hard', true ) ) {
if ( function_exists( 'save_mod_rewrite_rules' ) ) {
save_mod_rewrite_rules();
if ( function_exists( 'iis7_save_url_rewrite_rules' ) ) {
iis7_save_url_rewrite_rules();
* Sets up the object's properties.
* The 'use_verbose_page_rules' object property will be set to true if the
* permalink structure begins with one of the following: '%postname%', '%category%',
* '%tag%', or '%author%'.
$this->extra_rules = array();
$this->non_wp_rules = array();
$this->endpoints = array();
$this->permalink_structure = get_option( 'permalink_structure' );
$this->front = substr( $this->permalink_structure, 0, strpos( $this->permalink_structure, '%' ) );
if ( $this->using_index_permalinks() ) {
$this->root = $this->index . '/';
unset( $this->author_structure );
unset( $this->date_structure );
unset( $this->page_structure );
unset( $this->search_structure );
unset( $this->feed_structure );
unset( $this->comment_feed_structure );
$this->use_trailing_slashes = str_ends_with( $this->permalink_structure, '/' );
// Enable generic rules for pages if permalink structure doesn't begin with a wildcard.
if ( preg_match( '/^[^%]*%(?:postname|category|tag|author)%/', $this->permalink_structure ) ) {
$this->use_verbose_page_rules = true;
$this->use_verbose_page_rules = false;
* Sets the main permalink structure for the site.
* Will update the 'permalink_structure' option, if there is a difference
* between the current permalink structure and the parameter value. Calls
* WP_Rewrite::init() after the option is updated.
* Fires the {@see 'permalink_structure_changed'} action once the init call has
* processed passing the old and new values
* @param string $permalink_structure Permalink structure.
public function set_permalink_structure( $permalink_structure ) {
if ( $this->permalink_structure !== $permalink_structure ) {
$old_permalink_structure = $this->permalink_structure;
update_option( 'permalink_structure', $permalink_structure );
* Fires after the permalink structure is updated.
* @param string $old_permalink_structure The previous permalink structure.
* @param string $permalink_structure The new permalink structure.
do_action( 'permalink_structure_changed', $old_permalink_structure, $permalink_structure );
* Sets the category base for the category permalink.
* Will update the 'category_base' option, if there is a difference between
* the current category base and the parameter value. Calls WP_Rewrite::init()
* after the option is updated.
* @param string $category_base Category permalink structure base.
public function set_category_base( $category_base ) {
if ( get_option( 'category_base' ) !== $category_base ) {
update_option( 'category_base', $category_base );
* Sets the tag base for the tag permalink.
* Will update the 'tag_base' option, if there is a difference between the
* current tag base and the parameter value. Calls WP_Rewrite::init() after