ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-rewrite.php (62847B)


      1 <?php
      2 /**
      3  * Rewrite API: WP_Rewrite class
      4  *
      5  * @package WordPress
      6  * @subpackage Rewrite
      7  * @since 1.5.0
      8  */
      9 
     10 /**
     11  * Core class used to implement a rewrite component API.
     12  *
     13  * The WordPress Rewrite class writes the rewrite module rules to the .htaccess
     14  * file. It also handles parsing the request to get the correct setup for the
     15  * WordPress Query class.
     16  *
     17  * The Rewrite along with WP class function as a front controller for WordPress.
     18  * You can add rules to trigger your page view and processing using this
     19  * component. The full functionality of a front controller does not exist,
     20  * meaning you can't define how the template files load based on the rewrite
     21  * rules.
     22  *
     23  * @since 1.5.0
     24  */
     25 class WP_Rewrite {
     26 	/**
     27 	 * Permalink structure for posts.
     28 	 *
     29 	 * @since 1.5.0
     30 	 * @var string
     31 	 */
     32 	public $permalink_structure;
     33 
     34 	/**
     35 	 * Whether to add trailing slashes.
     36 	 *
     37 	 * @since 2.2.0
     38 	 * @var bool
     39 	 */
     40 	public $use_trailing_slashes;
     41 
     42 	/**
     43 	 * Base for the author permalink structure (example.com/$author_base/authorname).
     44 	 *
     45 	 * @since 1.5.0
     46 	 * @var string
     47 	 */
     48 	public $author_base = 'author';
     49 
     50 	/**
     51 	 * Permalink structure for author archives.
     52 	 *
     53 	 * @since 1.5.0
     54 	 * @var string
     55 	 */
     56 	public $author_structure;
     57 
     58 	/**
     59 	 * Permalink structure for date archives.
     60 	 *
     61 	 * @since 1.5.0
     62 	 * @var string
     63 	 */
     64 	public $date_structure;
     65 
     66 	/**
     67 	 * Permalink structure for pages.
     68 	 *
     69 	 * @since 1.5.0
     70 	 * @var string
     71 	 */
     72 	public $page_structure;
     73 
     74 	/**
     75 	 * Base of the search permalink structure (example.com/$search_base/query).
     76 	 *
     77 	 * @since 1.5.0
     78 	 * @var string
     79 	 */
     80 	public $search_base = 'search';
     81 
     82 	/**
     83 	 * Permalink structure for searches.
     84 	 *
     85 	 * @since 1.5.0
     86 	 * @var string
     87 	 */
     88 	public $search_structure;
     89 
     90 	/**
     91 	 * Comments permalink base.
     92 	 *
     93 	 * @since 1.5.0
     94 	 * @var string
     95 	 */
     96 	public $comments_base = 'comments';
     97 
     98 	/**
     99 	 * Pagination permalink base.
    100 	 *
    101 	 * @since 3.1.0
    102 	 * @var string
    103 	 */
    104 	public $pagination_base = 'page';
    105 
    106 	/**
    107 	 * Comments pagination permalink base.
    108 	 *
    109 	 * @since 4.2.0
    110 	 * @var string
    111 	 */
    112 	public $comments_pagination_base = 'comment-page';
    113 
    114 	/**
    115 	 * Feed permalink base.
    116 	 *
    117 	 * @since 1.5.0
    118 	 * @var string
    119 	 */
    120 	public $feed_base = 'feed';
    121 
    122 	/**
    123 	 * Comments feed permalink structure.
    124 	 *
    125 	 * @since 1.5.0
    126 	 * @var string
    127 	 */
    128 	public $comment_feed_structure;
    129 
    130 	/**
    131 	 * Feed request permalink structure.
    132 	 *
    133 	 * @since 1.5.0
    134 	 * @var string
    135 	 */
    136 	public $feed_structure;
    137 
    138 	/**
    139 	 * The static portion of the post permalink structure.
    140 	 *
    141 	 * If the permalink structure is "/archive/%post_id%" then the front
    142 	 * is "/archive/". If the permalink structure is "/%year%/%postname%/"
    143 	 * then the front is "/".
    144 	 *
    145 	 * @since 1.5.0
    146 	 * @var string
    147 	 *
    148 	 * @see WP_Rewrite::init()
    149 	 */
    150 	public $front;
    151 
    152 	/**
    153 	 * The prefix for all permalink structures.
    154 	 *
    155 	 * If PATHINFO/index permalinks are in use then the root is the value of
    156 	 * `WP_Rewrite::$index` with a trailing slash appended. Otherwise the root
    157 	 * will be empty.
    158 	 *
    159 	 * @since 1.5.0
    160 	 * @var string
    161 	 *
    162 	 * @see WP_Rewrite::init()
    163 	 * @see WP_Rewrite::using_index_permalinks()
    164 	 */
    165 	public $root = '';
    166 
    167 	/**
    168 	 * The name of the index file which is the entry point to all requests.
    169 	 *
    170 	 * @since 1.5.0
    171 	 * @var string
    172 	 */
    173 	public $index = 'index.php';
    174 
    175 	/**
    176 	 * Variable name to use for regex matches in the rewritten query.
    177 	 *
    178 	 * @since 1.5.0
    179 	 * @var string
    180 	 */
    181 	public $matches = '';
    182 
    183 	/**
    184 	 * Rewrite rules to match against the request to find the redirect or query.
    185 	 *
    186 	 * @since 1.5.0
    187 	 * @var array
    188 	 */
    189 	public $rules;
    190 
    191 	/**
    192 	 * Additional rules added external to the rewrite class.
    193 	 *
    194 	 * Those not generated by the class, see add_rewrite_rule().
    195 	 *
    196 	 * @since 2.1.0
    197 	 * @var array
    198 	 */
    199 	public $extra_rules = array();
    200 
    201 	/**
    202 	 * Additional rules that belong at the beginning to match first.
    203 	 *
    204 	 * Those not generated by the class, see add_rewrite_rule().
    205 	 *
    206 	 * @since 2.3.0
    207 	 * @var array
    208 	 */
    209 	public $extra_rules_top = array();
    210 
    211 	/**
    212 	 * Rules that don't redirect to WordPress' index.php.
    213 	 *
    214 	 * These rules are written to the mod_rewrite portion of the .htaccess,
    215 	 * and are added by add_external_rule().
    216 	 *
    217 	 * @since 2.1.0
    218 	 * @var array
    219 	 */
    220 	public $non_wp_rules = array();
    221 
    222 	/**
    223 	 * Extra permalink structures, e.g. categories, added by add_permastruct().
    224 	 *
    225 	 * @since 2.1.0
    226 	 * @var array
    227 	 */
    228 	public $extra_permastructs = array();
    229 
    230 	/**
    231 	 * Endpoints (like /trackback/) added by add_rewrite_endpoint().
    232 	 *
    233 	 * @since 2.1.0
    234 	 * @var array
    235 	 */
    236 	public $endpoints;
    237 
    238 	/**
    239 	 * Whether to write every mod_rewrite rule for WordPress into the .htaccess file.
    240 	 *
    241 	 * This is off by default, turning it on might print a lot of rewrite rules
    242 	 * to the .htaccess file.
    243 	 *
    244 	 * @since 2.0.0
    245 	 * @var bool
    246 	 *
    247 	 * @see WP_Rewrite::mod_rewrite_rules()
    248 	 */
    249 	public $use_verbose_rules = false;
    250 
    251 	/**
    252 	 * Could post permalinks be confused with those of pages?
    253 	 *
    254 	 * If the first rewrite tag in the post permalink structure is one that could
    255 	 * also match a page name (e.g. %postname% or %author%) then this flag is
    256 	 * set to true. Prior to WordPress 3.3 this flag indicated that every page
    257 	 * would have a set of rules added to the top of the rewrite rules array.
    258 	 * Now it tells WP::parse_request() to check if a URL matching the page
    259 	 * permastruct is actually a page before accepting it.
    260 	 *
    261 	 * @since 2.5.0
    262 	 * @var bool
    263 	 *
    264 	 * @see WP_Rewrite::init()
    265 	 */
    266 	public $use_verbose_page_rules = true;
    267 
    268 	/**
    269 	 * Rewrite tags that can be used in permalink structures.
    270 	 *
    271 	 * These are translated into the regular expressions stored in
    272 	 * `WP_Rewrite::$rewritereplace` and are rewritten to the query
    273 	 * variables listed in WP_Rewrite::$queryreplace.
    274 	 *
    275 	 * Additional tags can be added with add_rewrite_tag().
    276 	 *
    277 	 * @since 1.5.0
    278 	 * @var array
    279 	 */
    280 	public $rewritecode = array(
    281 		'%year%',
    282 		'%monthnum%',
    283 		'%day%',
    284 		'%hour%',
    285 		'%minute%',
    286 		'%second%',
    287 		'%postname%',
    288 		'%post_id%',
    289 		'%author%',
    290 		'%pagename%',
    291 		'%search%',
    292 	);
    293 
    294 	/**
    295 	 * Regular expressions to be substituted into rewrite rules in place
    296 	 * of rewrite tags, see WP_Rewrite::$rewritecode.
    297 	 *
    298 	 * @since 1.5.0
    299 	 * @var array
    300 	 */
    301 	public $rewritereplace = array(
    302 		'([0-9]{4})',
    303 		'([0-9]{1,2})',
    304 		'([0-9]{1,2})',
    305 		'([0-9]{1,2})',
    306 		'([0-9]{1,2})',
    307 		'([0-9]{1,2})',
    308 		'([^/]+)',
    309 		'([0-9]+)',
    310 		'([^/]+)',
    311 		'([^/]+?)',
    312 		'(.+)',
    313 	);
    314 
    315 	/**
    316 	 * Query variables that rewrite tags map to, see WP_Rewrite::$rewritecode.
    317 	 *
    318 	 * @since 1.5.0
    319 	 * @var array
    320 	 */
    321 	public $queryreplace = array(
    322 		'year=',
    323 		'monthnum=',
    324 		'day=',
    325 		'hour=',
    326 		'minute=',
    327 		'second=',
    328 		'name=',
    329 		'p=',
    330 		'author_name=',
    331 		'pagename=',
    332 		's=',
    333 	);
    334 
    335 	/**
    336 	 * Supported default feeds.
    337 	 *
    338 	 * @since 1.5.0
    339 	 * @var array
    340 	 */
    341 	public $feeds = array( 'feed', 'rdf', 'rss', 'rss2', 'atom' );
    342 
    343 	/**
    344 	 * Determines whether permalinks are being used.
    345 	 *
    346 	 * This can be either rewrite module or permalink in the HTTP query string.
    347 	 *
    348 	 * @since 1.5.0
    349 	 *
    350 	 * @return bool True, if permalinks are enabled.
    351 	 */
    352 	public function using_permalinks() {
    353 		return ! empty( $this->permalink_structure );
    354 	}
    355 
    356 	/**
    357 	 * Determines whether permalinks are being used and rewrite module is not enabled.
    358 	 *
    359 	 * Means that permalink links are enabled and index.php is in the URL.
    360 	 *
    361 	 * @since 1.5.0
    362 	 *
    363 	 * @return bool Whether permalink links are enabled and index.php is in the URL.
    364 	 */
    365 	public function using_index_permalinks() {
    366 		if ( empty( $this->permalink_structure ) ) {
    367 			return false;
    368 		}
    369 
    370 		// If the index is not in the permalink, we're using mod_rewrite.
    371 		return preg_match( '#^/*' . $this->index . '#', $this->permalink_structure );
    372 	}
    373 
    374 	/**
    375 	 * Determines whether permalinks are being used and rewrite module is enabled.
    376 	 *
    377 	 * Using permalinks and index.php is not in the URL.
    378 	 *
    379 	 * @since 1.5.0
    380 	 *
    381 	 * @return bool Whether permalink links are enabled and index.php is NOT in the URL.
    382 	 */
    383 	public function using_mod_rewrite_permalinks() {
    384 		return $this->using_permalinks() && ! $this->using_index_permalinks();
    385 	}
    386 
    387 	/**
    388 	 * Indexes for matches for usage in preg_*() functions.
    389 	 *
    390 	 * The format of the string is, with empty matches property value, '$NUM'.
    391 	 * The 'NUM' will be replaced with the value in the $number parameter. With
    392 	 * the matches property not empty, the value of the returned string will
    393 	 * contain that value of the matches property. The format then will be
    394 	 * '$MATCHES[NUM]', with MATCHES as the value in the property and NUM the
    395 	 * value of the $number parameter.
    396 	 *
    397 	 * @since 1.5.0
    398 	 *
    399 	 * @param int $number Index number.
    400 	 * @return string
    401 	 */
    402 	public function preg_index( $number ) {
    403 		$match_prefix = '$';
    404 		$match_suffix = '';
    405 
    406 		if ( ! empty( $this->matches ) ) {
    407 			$match_prefix = '$' . $this->matches . '[';
    408 			$match_suffix = ']';
    409 		}
    410 
    411 		return "$match_prefix$number$match_suffix";
    412 	}
    413 
    414 	/**
    415 	 * Retrieves all page and attachments for pages URIs.
    416 	 *
    417 	 * The attachments are for those that have pages as parents and will be
    418 	 * retrieved.
    419 	 *
    420 	 * @since 2.5.0
    421 	 *
    422 	 * @global wpdb $wpdb WordPress database abstraction object.
    423 	 *
    424 	 * @return array Array of page URIs as first element and attachment URIs as second element.
    425 	 */
    426 	public function page_uri_index() {
    427 		global $wpdb;
    428 
    429 		// Get pages in order of hierarchy, i.e. children after parents.
    430 		$pages = $wpdb->get_results( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'page' AND post_status != 'auto-draft'" );
    431 		$posts = get_page_hierarchy( $pages );
    432 
    433 		// If we have no pages get out quick.
    434 		if ( ! $posts ) {
    435 			return array( array(), array() );
    436 		}
    437 
    438 		// Now reverse it, because we need parents after children for rewrite rules to work properly.
    439 		$posts = array_reverse( $posts, true );
    440 
    441 		$page_uris            = array();
    442 		$page_attachment_uris = array();
    443 
    444 		foreach ( $posts as $id => $post ) {
    445 			// URL => page name.
    446 			$uri         = get_page_uri( $id );
    447 			$attachments = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_name, post_parent FROM $wpdb->posts WHERE post_type = 'attachment' AND post_parent = %d", $id ) );
    448 			if ( ! empty( $attachments ) ) {
    449 				foreach ( $attachments as $attachment ) {
    450 					$attach_uri                          = get_page_uri( $attachment->ID );
    451 					$page_attachment_uris[ $attach_uri ] = $attachment->ID;
    452 				}
    453 			}
    454 
    455 			$page_uris[ $uri ] = $id;
    456 		}
    457 
    458 		return array( $page_uris, $page_attachment_uris );
    459 	}
    460 
    461 	/**
    462 	 * Retrieves all of the rewrite rules for pages.
    463 	 *
    464 	 * @since 1.5.0
    465 	 *
    466 	 * @return string[] Page rewrite rules.
    467 	 */
    468 	public function page_rewrite_rules() {
    469 		// The extra .? at the beginning prevents clashes with other regular expressions in the rules array.
    470 		$this->add_rewrite_tag( '%pagename%', '(.?.+?)', 'pagename=' );
    471 
    472 		return $this->generate_rewrite_rules( $this->get_page_permastruct(), EP_PAGES, true, true, false, false );
    473 	}
    474 
    475 	/**
    476 	 * Retrieves date permalink structure, with year, month, and day.
    477 	 *
    478 	 * The permalink structure for the date, if not set already depends on the
    479 	 * permalink structure. It can be one of three formats. The first is year,
    480 	 * month, day; the second is day, month, year; and the last format is month,
    481 	 * day, year. These are matched against the permalink structure for which
    482 	 * one is used. If none matches, then the default will be used, which is
    483 	 * year, month, day.
    484 	 *
    485 	 * Prevents post ID and date permalinks from overlapping. In the case of
    486 	 * post_id, the date permalink will be prepended with front permalink with
    487 	 * 'date/' before the actual permalink to form the complete date permalink
    488 	 * structure.
    489 	 *
    490 	 * @since 1.5.0
    491 	 *
    492 	 * @return string|false Date permalink structure on success, false on failure.
    493 	 */
    494 	public function get_date_permastruct() {
    495 		if ( isset( $this->date_structure ) ) {
    496 			return $this->date_structure;
    497 		}
    498 
    499 		if ( empty( $this->permalink_structure ) ) {
    500 			$this->date_structure = '';
    501 			return false;
    502 		}
    503 
    504 		// The date permalink must have year, month, and day separated by slashes.
    505 		$endians = array( '%year%/%monthnum%/%day%', '%day%/%monthnum%/%year%', '%monthnum%/%day%/%year%' );
    506 
    507 		$this->date_structure = '';
    508 		$date_endian          = '';
    509 
    510 		foreach ( $endians as $endian ) {
    511 			if ( false !== strpos( $this->permalink_structure, $endian ) ) {
    512 				$date_endian = $endian;
    513 				break;
    514 			}
    515 		}
    516 
    517 		if ( empty( $date_endian ) ) {
    518 			$date_endian = '%year%/%monthnum%/%day%';
    519 		}
    520 
    521 		/*
    522 		 * Do not allow the date tags and %post_id% to overlap in the permalink
    523 		 * structure. If they do, move the date tags to $front/date/.
    524 		 */
    525 		$front = $this->front;
    526 		preg_match_all( '/%.+?%/', $this->permalink_structure, $tokens );
    527 		$tok_index = 1;
    528 		foreach ( (array) $tokens[0] as $token ) {
    529 			if ( '%post_id%' === $token && ( $tok_index <= 3 ) ) {
    530 				$front = $front . 'date/';
    531 				break;
    532 			}
    533 			$tok_index++;
    534 		}
    535 
    536 		$this->date_structure = $front . $date_endian;
    537 
    538 		return $this->date_structure;
    539 	}
    540 
    541 	/**
    542 	 * Retrieves the year permalink structure without month and day.
    543 	 *
    544 	 * Gets the date permalink structure and strips out the month and day
    545 	 * permalink structures.
    546 	 *
    547 	 * @since 1.5.0
    548 	 *
    549 	 * @return string|false Year permalink structure on success, false on failure.
    550 	 */
    551 	public function get_year_permastruct() {
    552 		$structure = $this->get_date_permastruct();
    553 
    554 		if ( empty( $structure ) ) {
    555 			return false;
    556 		}
    557 
    558 		$structure = str_replace( '%monthnum%', '', $structure );
    559 		$structure = str_replace( '%day%', '', $structure );
    560 		$structure = preg_replace( '#/+#', '/', $structure );
    561 
    562 		return $structure;
    563 	}
    564 
    565 	/**
    566 	 * Retrieves the month permalink structure without day and with year.
    567 	 *
    568 	 * Gets the date permalink structure and strips out the day permalink
    569 	 * structures. Keeps the year permalink structure.
    570 	 *
    571 	 * @since 1.5.0
    572 	 *
    573 	 * @return string|false Year/Month permalink structure on success, false on failure.
    574 	 */
    575 	public function get_month_permastruct() {
    576 		$structure = $this->get_date_permastruct();
    577 
    578 		if ( empty( $structure ) ) {
    579 			return false;
    580 		}
    581 
    582 		$structure = str_replace( '%day%', '', $structure );
    583 		$structure = preg_replace( '#/+#', '/', $structure );
    584 
    585 		return $structure;
    586 	}
    587 
    588 	/**
    589 	 * Retrieves the day permalink structure with month and year.
    590 	 *
    591 	 * Keeps date permalink structure with all year, month, and day.
    592 	 *
    593 	 * @since 1.5.0
    594 	 *
    595 	 * @return string|false Year/Month/Day permalink structure on success, false on failure.
    596 	 */
    597 	public function get_day_permastruct() {
    598 		return $this->get_date_permastruct();
    599 	}
    600 
    601 	/**
    602 	 * Retrieves the permalink structure for categories.
    603 	 *
    604 	 * If the category_base property has no value, then the category structure
    605 	 * will have the front property value, followed by 'category', and finally
    606 	 * '%category%'. If it does, then the root property will be used, along with
    607 	 * the category_base property value.
    608 	 *
    609 	 * @since 1.5.0
    610 	 *
    611 	 * @return string|false Category permalink structure on success, false on failure.
    612 	 */
    613 	public function get_category_permastruct() {
    614 		return $this->get_extra_permastruct( 'category' );
    615 	}
    616 
    617 	/**
    618 	 * Retrieve the permalink structure for tags.
    619 	 *
    620 	 * If the tag_base property has no value, then the tag structure will have
    621 	 * the front property value, followed by 'tag', and finally '%tag%'. If it
    622 	 * does, then the root property will be used, along with the tag_base
    623 	 * property value.
    624 	 *
    625 	 * @since 2.3.0
    626 	 *
    627 	 * @return string|false Tag permalink structure on success, false on failure.
    628 	 */
    629 	public function get_tag_permastruct() {
    630 		return $this->get_extra_permastruct( 'post_tag' );
    631 	}
    632 
    633 	/**
    634 	 * Retrieves an extra permalink structure by name.
    635 	 *
    636 	 * @since 2.5.0
    637 	 *
    638 	 * @param string $name Permalink structure name.
    639 	 * @return string|false Permalink structure string on success, false on failure.
    640 	 */
    641 	public function get_extra_permastruct( $name ) {
    642 		if ( empty( $this->permalink_structure ) ) {
    643 			return false;
    644 		}
    645 
    646 		if ( isset( $this->extra_permastructs[ $name ] ) ) {
    647 			return $this->extra_permastructs[ $name ]['struct'];
    648 		}
    649 
    650 		return false;
    651 	}
    652 
    653 	/**
    654 	 * Retrieves the author permalink structure.
    655 	 *
    656 	 * The permalink structure is front property, author base, and finally
    657 	 * '/%author%'. Will set the author_structure property and then return it
    658 	 * without attempting to set the value again.
    659 	 *
    660 	 * @since 1.5.0
    661 	 *
    662 	 * @return string|false Author permalink structure on success, false on failure.
    663 	 */
    664 	public function get_author_permastruct() {
    665 		if ( isset( $this->author_structure ) ) {
    666 			return $this->author_structure;
    667 		}
    668 
    669 		if ( empty( $this->permalink_structure ) ) {
    670 			$this->author_structure = '';
    671 			return false;
    672 		}
    673 
    674 		$this->author_structure = $this->front . $this->author_base . '/%author%';
    675 
    676 		return $this->author_structure;
    677 	}
    678 
    679 	/**
    680 	 * Retrieves the search permalink structure.
    681 	 *
    682 	 * The permalink structure is root property, search base, and finally
    683 	 * '/%search%'. Will set the search_structure property and then return it
    684 	 * without attempting to set the value again.
    685 	 *
    686 	 * @since 1.5.0
    687 	 *
    688 	 * @return string|false Search permalink structure on success, false on failure.
    689 	 */
    690 	public function get_search_permastruct() {
    691 		if ( isset( $this->search_structure ) ) {
    692 			return $this->search_structure;
    693 		}
    694 
    695 		if ( empty( $this->permalink_structure ) ) {
    696 			$this->search_structure = '';
    697 			return false;
    698 		}
    699 
    700 		$this->search_structure = $this->root . $this->search_base . '/%search%';
    701 
    702 		return $this->search_structure;
    703 	}
    704 
    705 	/**
    706 	 * Retrieves the page permalink structure.
    707 	 *
    708 	 * The permalink structure is root property, and '%pagename%'. Will set the
    709 	 * page_structure property and then return it without attempting to set the
    710 	 * value again.
    711 	 *
    712 	 * @since 1.5.0
    713 	 *
    714 	 * @return string|false Page permalink structure on success, false on failure.
    715 	 */
    716 	public function get_page_permastruct() {
    717 		if ( isset( $this->page_structure ) ) {
    718 			return $this->page_structure;
    719 		}
    720 
    721 		if ( empty( $this->permalink_structure ) ) {
    722 			$this->page_structure = '';
    723 			return false;
    724 		}
    725 
    726 		$this->page_structure = $this->root . '%pagename%';
    727 
    728 		return $this->page_structure;
    729 	}
    730 
    731 	/**
    732 	 * Retrieves the feed permalink structure.
    733 	 *
    734 	 * The permalink structure is root property, feed base, and finally
    735 	 * '/%feed%'. Will set the feed_structure property and then return it
    736 	 * without attempting to set the value again.
    737 	 *
    738 	 * @since 1.5.0
    739 	 *
    740 	 * @return string|false Feed permalink structure on success, false on failure.
    741 	 */
    742 	public function get_feed_permastruct() {
    743 		if ( isset( $this->feed_structure ) ) {
    744 			return $this->feed_structure;
    745 		}
    746 
    747 		if ( empty( $this->permalink_structure ) ) {
    748 			$this->feed_structure = '';
    749 			return false;
    750 		}
    751 
    752 		$this->feed_structure = $this->root . $this->feed_base . '/%feed%';
    753 
    754 		return $this->feed_structure;
    755 	}
    756 
    757 	/**
    758 	 * Retrieves the comment feed permalink structure.
    759 	 *
    760 	 * The permalink structure is root property, comment base property, feed
    761 	 * base and finally '/%feed%'. Will set the comment_feed_structure property
    762 	 * and then return it without attempting to set the value again.
    763 	 *
    764 	 * @since 1.5.0
    765 	 *
    766 	 * @return string|false Comment feed permalink structure on success, false on failure.
    767 	 */
    768 	public function get_comment_feed_permastruct() {
    769 		if ( isset( $this->comment_feed_structure ) ) {
    770 			return $this->comment_feed_structure;
    771 		}
    772 
    773 		if ( empty( $this->permalink_structure ) ) {
    774 			$this->comment_feed_structure = '';
    775 			return false;
    776 		}
    777 
    778 		$this->comment_feed_structure = $this->root . $this->comments_base . '/' . $this->feed_base . '/%feed%';
    779 
    780 		return $this->comment_feed_structure;
    781 	}
    782 
    783 	/**
    784 	 * Adds or updates existing rewrite tags (e.g. %postname%).
    785 	 *
    786 	 * If the tag already exists, replace the existing pattern and query for
    787 	 * that tag, otherwise add the new tag.
    788 	 *
    789 	 * @since 1.5.0
    790 	 *
    791 	 * @see WP_Rewrite::$rewritecode
    792 	 * @see WP_Rewrite::$rewritereplace
    793 	 * @see WP_Rewrite::$queryreplace
    794 	 *
    795 	 * @param string $tag   Name of the rewrite tag to add or update.
    796 	 * @param string $regex Regular expression to substitute the tag for in rewrite rules.
    797 	 * @param string $query String to append to the rewritten query. Must end in '='.
    798 	 */
    799 	public function add_rewrite_tag( $tag, $regex, $query ) {
    800 		$position = array_search( $tag, $this->rewritecode, true );
    801 		if ( false !== $position && null !== $position ) {
    802 			$this->rewritereplace[ $position ] = $regex;
    803 			$this->queryreplace[ $position ]   = $query;
    804 		} else {
    805 			$this->rewritecode[]    = $tag;
    806 			$this->rewritereplace[] = $regex;
    807 			$this->queryreplace[]   = $query;
    808 		}
    809 	}
    810 
    811 
    812 	/**
    813 	 * Removes an existing rewrite tag.
    814 	 *
    815 	 * @since 4.5.0
    816 	 *
    817 	 * @see WP_Rewrite::$rewritecode
    818 	 * @see WP_Rewrite::$rewritereplace
    819 	 * @see WP_Rewrite::$queryreplace
    820 	 *
    821 	 * @param string $tag Name of the rewrite tag to remove.
    822 	 */
    823 	public function remove_rewrite_tag( $tag ) {
    824 		$position = array_search( $tag, $this->rewritecode, true );
    825 		if ( false !== $position && null !== $position ) {
    826 			unset( $this->rewritecode[ $position ] );
    827 			unset( $this->rewritereplace[ $position ] );
    828 			unset( $this->queryreplace[ $position ] );
    829 		}
    830 	}
    831 
    832 	/**
    833 	 * Generates rewrite rules from a permalink structure.
    834 	 *
    835 	 * The main WP_Rewrite function for building the rewrite rule list. The
    836 	 * contents of the function is a mix of black magic and regular expressions,
    837 	 * so best just ignore the contents and move to the parameters.
    838 	 *
    839 	 * @since 1.5.0
    840 	 *
    841 	 * @param string $permalink_structure The permalink structure.
    842 	 * @param int    $ep_mask             Optional. Endpoint mask defining what endpoints are added to the structure.
    843 	 *                                    Accepts a mask of:
    844 	 *                                    - `EP_ALL`
    845 	 *                                    - `EP_NONE`
    846 	 *                                    - `EP_ALL_ARCHIVES`
    847 	 *                                    - `EP_ATTACHMENT`
    848 	 *                                    - `EP_AUTHORS`
    849 	 *                                    - `EP_CATEGORIES`
    850 	 *                                    - `EP_COMMENTS`
    851 	 *                                    - `EP_DATE`
    852 	 *                                    - `EP_DAY`
    853 	 *                                    - `EP_MONTH`
    854 	 *                                    - `EP_PAGES`
    855 	 *                                    - `EP_PERMALINK`
    856 	 *                                    - `EP_ROOT`
    857 	 *                                    - `EP_SEARCH`
    858 	 *                                    - `EP_TAGS`
    859 	 *                                    - `EP_YEAR`
    860 	 *                                    Default `EP_NONE`.
    861 	 * @param bool   $paged               Optional. Whether archive pagination rules should be added for the structure.
    862 	 *                                    Default true.
    863 	 * @param bool   $feed                Optional Whether feed rewrite rules should be added for the structure.
    864 	 *                                    Default true.
    865 	 * @param bool   $forcomments         Optional. Whether the feed rules should be a query for a comments feed.
    866 	 *                                    Default false.
    867 	 * @param bool   $walk_dirs           Optional. Whether the 'directories' making up the structure should be walked
    868 	 *                                    over and rewrite rules built for each in-turn. Default true.
    869 	 * @param bool   $endpoints           Optional. Whether endpoints should be applied to the generated rewrite rules.
    870 	 *                                    Default true.
    871 	 * @return string[] Array of rewrite rules keyed by their regex pattern.
    872 	 */
    873 	public function generate_rewrite_rules( $permalink_structure, $ep_mask = EP_NONE, $paged = true, $feed = true, $forcomments = false, $walk_dirs = true, $endpoints = true ) {
    874 		// Build a regex to match the feed section of URLs, something like (feed|atom|rss|rss2)/?
    875 		$feedregex2 = '';
    876 		foreach ( (array) $this->feeds as $feed_name ) {
    877 			$feedregex2 .= $feed_name . '|';
    878 		}
    879 		$feedregex2 = '(' . trim( $feedregex2, '|' ) . ')/?$';
    880 
    881 		/*
    882 		 * $feedregex is identical but with /feed/ added on as well, so URLs like <permalink>/feed/atom
    883 		 * and <permalink>/atom are both possible
    884 		 */
    885 		$feedregex = $this->feed_base . '/' . $feedregex2;
    886 
    887 		// Build a regex to match the trackback and page/xx parts of URLs.
    888 		$trackbackregex = 'trackback/?$';
    889 		$pageregex      = $this->pagination_base . '/?([0-9]{1,})/?$';
    890 		$commentregex   = $this->comments_pagination_base . '-([0-9]{1,})/?$';
    891 		$embedregex     = 'embed/?$';
    892 
    893 		// Build up an array of endpoint regexes to append => queries to append.
    894 		if ( $endpoints ) {
    895 			$ep_query_append = array();
    896 			foreach ( (array) $this->endpoints as $endpoint ) {
    897 				// Match everything after the endpoint name, but allow for nothing to appear there.
    898 				$epmatch = $endpoint[1] . '(/(.*))?/?$';
    899 
    900 				// This will be appended on to the rest of the query for each dir.
    901 				$epquery                     = '&' . $endpoint[2] . '=';
    902 				$ep_query_append[ $epmatch ] = array( $endpoint[0], $epquery );
    903 			}
    904 		}
    905 
    906 		// Get everything up to the first rewrite tag.
    907 		$front = substr( $permalink_structure, 0, strpos( $permalink_structure, '%' ) );
    908 
    909 		// Build an array of the tags (note that said array ends up being in $tokens[0]).
    910 		preg_match_all( '/%.+?%/', $permalink_structure, $tokens );
    911 
    912 		$num_tokens = count( $tokens[0] );
    913 
    914 		$index          = $this->index; // Probably 'index.php'.
    915 		$feedindex      = $index;
    916 		$trackbackindex = $index;
    917 		$embedindex     = $index;
    918 
    919 		/*
    920 		 * Build a list from the rewritecode and queryreplace arrays, that will look something
    921 		 * like tagname=$matches[i] where i is the current $i.
    922 		 */
    923 		$queries = array();
    924 		for ( $i = 0; $i < $num_tokens; ++$i ) {
    925 			if ( 0 < $i ) {
    926 				$queries[ $i ] = $queries[ $i - 1 ] . '&';
    927 			} else {
    928 				$queries[ $i ] = '';
    929 			}
    930 
    931 			$query_token    = str_replace( $this->rewritecode, $this->queryreplace, $tokens[0][ $i ] ) . $this->preg_index( $i + 1 );
    932 			$queries[ $i ] .= $query_token;
    933 		}
    934 
    935 		// Get the structure, minus any cruft (stuff that isn't tags) at the front.
    936 		$structure = $permalink_structure;
    937 		if ( '/' !== $front ) {
    938 			$structure = str_replace( $front, '', $structure );
    939 		}
    940 
    941 		/*
    942 		 * Create a list of dirs to walk over, making rewrite rules for each level
    943 		 * so for example, a $structure of /%year%/%monthnum%/%postname% would create
    944 		 * rewrite rules for /%year%/, /%year%/%monthnum%/ and /%year%/%monthnum%/%postname%
    945 		 */
    946 		$structure = trim( $structure, '/' );
    947 		$dirs      = $walk_dirs ? explode( '/', $structure ) : array( $structure );
    948 		$num_dirs  = count( $dirs );
    949 
    950 		// Strip slashes from the front of $front.
    951 		$front = preg_replace( '|^/+|', '', $front );
    952 
    953 		// The main workhorse loop.
    954 		$post_rewrite = array();
    955 		$struct       = $front;
    956 		for ( $j = 0; $j < $num_dirs; ++$j ) {
    957 			// Get the struct for this dir, and trim slashes off the front.
    958 			$struct .= $dirs[ $j ] . '/'; // Accumulate. see comment near explode('/', $structure) above.
    959 			$struct  = ltrim( $struct, '/' );
    960 
    961 			// Replace tags with regexes.
    962 			$match = str_replace( $this->rewritecode, $this->rewritereplace, $struct );
    963 
    964 			// Make a list of tags, and store how many there are in $num_toks.
    965 			$num_toks = preg_match_all( '/%.+?%/', $struct, $toks );
    966 
    967 			// Get the 'tagname=$matches[i]'.
    968 			$query = ( ! empty( $num_toks ) && isset( $queries[ $num_toks - 1 ] ) ) ? $queries[ $num_toks - 1 ] : '';
    969 
    970 			// Set up $ep_mask_specific which is used to match more specific URL types.
    971 			switch ( $dirs[ $j ] ) {
    972 				case '%year%':
    973 					$ep_mask_specific = EP_YEAR;
    974 					break;
    975 				case '%monthnum%':
    976 					$ep_mask_specific = EP_MONTH;
    977 					break;
    978 				case '%day%':
    979 					$ep_mask_specific = EP_DAY;
    980 					break;
    981 				default:
    982 					$ep_mask_specific = EP_NONE;
    983 			}
    984 
    985 			// Create query for /page/xx.
    986 			$pagematch = $match . $pageregex;
    987 			$pagequery = $index . '?' . $query . '&paged=' . $this->preg_index( $num_toks + 1 );
    988 
    989 			// Create query for /comment-page-xx.
    990 			$commentmatch = $match . $commentregex;
    991 			$commentquery = $index . '?' . $query . '&cpage=' . $this->preg_index( $num_toks + 1 );
    992 
    993 			if ( get_option( 'page_on_front' ) ) {
    994 				// Create query for Root /comment-page-xx.
    995 				$rootcommentmatch = $match . $commentregex;
    996 				$rootcommentquery = $index . '?' . $query . '&page_id=' . get_option( 'page_on_front' ) . '&cpage=' . $this->preg_index( $num_toks + 1 );
    997 			}
    998 
    999 			// Create query for /feed/(feed|atom|rss|rss2|rdf).
   1000 			$feedmatch = $match . $feedregex;
   1001 			$feedquery = $feedindex . '?' . $query . '&feed=' . $this->preg_index( $num_toks + 1 );
   1002 
   1003 			// Create query for /(feed|atom|rss|rss2|rdf) (see comment near creation of $feedregex).
   1004 			$feedmatch2 = $match . $feedregex2;
   1005 			$feedquery2 = $feedindex . '?' . $query . '&feed=' . $this->preg_index( $num_toks + 1 );
   1006 
   1007 			// Create query and regex for embeds.
   1008 			$embedmatch = $match . $embedregex;
   1009 			$embedquery = $embedindex . '?' . $query . '&embed=true';
   1010 
   1011 			// If asked to, turn the feed queries into comment feed ones.
   1012 			if ( $forcomments ) {
   1013 				$feedquery  .= '&withcomments=1';
   1014 				$feedquery2 .= '&withcomments=1';
   1015 			}
   1016 
   1017 			// Start creating the array of rewrites for this dir.
   1018 			$rewrite = array();
   1019 
   1020 			// ...adding on /feed/ regexes => queries.
   1021 			if ( $feed ) {
   1022 				$rewrite = array(
   1023 					$feedmatch  => $feedquery,
   1024 					$feedmatch2 => $feedquery2,
   1025 					$embedmatch => $embedquery,
   1026 				);
   1027 			}
   1028 
   1029 			// ...and /page/xx ones.
   1030 			if ( $paged ) {
   1031 				$rewrite = array_merge( $rewrite, array( $pagematch => $pagequery ) );
   1032 			}
   1033 
   1034 			// Only on pages with comments add ../comment-page-xx/.
   1035 			if ( EP_PAGES & $ep_mask || EP_PERMALINK & $ep_mask ) {
   1036 				$rewrite = array_merge( $rewrite, array( $commentmatch => $commentquery ) );
   1037 			} elseif ( EP_ROOT & $ep_mask && get_option( 'page_on_front' ) ) {
   1038 				$rewrite = array_merge( $rewrite, array( $rootcommentmatch => $rootcommentquery ) );
   1039 			}
   1040 
   1041 			// Do endpoints.
   1042 			if ( $endpoints ) {
   1043 				foreach ( (array) $ep_query_append as $regex => $ep ) {
   1044 					// Add the endpoints on if the mask fits.
   1045 					if ( $ep[0] & $ep_mask || $ep[0] & $ep_mask_specific ) {
   1046 						$rewrite[ $match . $regex ] = $index . '?' . $query . $ep[1] . $this->preg_index( $num_toks + 2 );
   1047 					}
   1048 				}
   1049 			}
   1050 
   1051 			// If we've got some tags in this dir.
   1052 			if ( $num_toks ) {
   1053 				$post = false;
   1054 				$page = false;
   1055 
   1056 				/*
   1057 				 * Check to see if this dir is permalink-level: i.e. the structure specifies an
   1058 				 * individual post. Do this by checking it contains at least one of 1) post name,
   1059 				 * 2) post ID, 3) page name, 4) timestamp (year, month, day, hour, second and
   1060 				 * minute all present). Set these flags now as we need them for the endpoints.
   1061 				 */
   1062 				if ( strpos( $struct, '%postname%' ) !== false
   1063 						|| strpos( $struct, '%post_id%' ) !== false
   1064 						|| strpos( $struct, '%pagename%' ) !== false
   1065 						|| ( strpos( $struct, '%year%' ) !== false && strpos( $struct, '%monthnum%' ) !== false && strpos( $struct, '%day%' ) !== false && strpos( $struct, '%hour%' ) !== false && strpos( $struct, '%minute%' ) !== false && strpos( $struct, '%second%' ) !== false )
   1066 						) {
   1067 					$post = true;
   1068 					if ( strpos( $struct, '%pagename%' ) !== false ) {
   1069 						$page = true;
   1070 					}
   1071 				}
   1072 
   1073 				if ( ! $post ) {
   1074 					// For custom post types, we need to add on endpoints as well.
   1075 					foreach ( get_post_types( array( '_builtin' => false ) ) as $ptype ) {
   1076 						if ( strpos( $struct, "%$ptype%" ) !== false ) {
   1077 							$post = true;
   1078 
   1079 							// This is for page style attachment URLs.
   1080 							$page = is_post_type_hierarchical( $ptype );
   1081 							break;
   1082 						}
   1083 					}
   1084 				}
   1085 
   1086 				// If creating rules for a permalink, do all the endpoints like attachments etc.
   1087 				if ( $post ) {
   1088 					// Create query and regex for trackback.
   1089 					$trackbackmatch = $match . $trackbackregex;
   1090 					$trackbackquery = $trackbackindex . '?' . $query . '&tb=1';
   1091 
   1092 					// Create query and regex for embeds.
   1093 					$embedmatch = $match . $embedregex;
   1094 					$embedquery = $embedindex . '?' . $query . '&embed=true';
   1095 
   1096 					// Trim slashes from the end of the regex for this dir.
   1097 					$match = rtrim( $match, '/' );
   1098 
   1099 					// Get rid of brackets.
   1100 					$submatchbase = str_replace( array( '(', ')' ), '', $match );
   1101 
   1102 					// Add a rule for at attachments, which take the form of <permalink>/some-text.
   1103 					$sub1 = $submatchbase . '/([^/]+)/';
   1104 
   1105 					// Add trackback regex <permalink>/trackback/...
   1106 					$sub1tb = $sub1 . $trackbackregex;
   1107 
   1108 					// And <permalink>/feed/(atom|...)
   1109 					$sub1feed = $sub1 . $feedregex;
   1110 
   1111 					// And <permalink>/(feed|atom...)
   1112 					$sub1feed2 = $sub1 . $feedregex2;
   1113 
   1114 					// And <permalink>/comment-page-xx
   1115 					$sub1comment = $sub1 . $commentregex;
   1116 
   1117 					// And <permalink>/embed/...
   1118 					$sub1embed = $sub1 . $embedregex;
   1119 
   1120 					/*
   1121 					 * Add another rule to match attachments in the explicit form:
   1122 					 * <permalink>/attachment/some-text
   1123 					 */
   1124 					$sub2 = $submatchbase . '/attachment/([^/]+)/';
   1125 
   1126 					// And add trackbacks <permalink>/attachment/trackback.
   1127 					$sub2tb = $sub2 . $trackbackregex;
   1128 
   1129 					// Feeds, <permalink>/attachment/feed/(atom|...)
   1130 					$sub2feed = $sub2 . $feedregex;
   1131 
   1132 					// And feeds again on to this <permalink>/attachment/(feed|atom...)
   1133 					$sub2feed2 = $sub2 . $feedregex2;
   1134 
   1135 					// And <permalink>/comment-page-xx
   1136 					$sub2comment = $sub2 . $commentregex;
   1137 
   1138 					// And <permalink>/embed/...
   1139 					$sub2embed = $sub2 . $embedregex;
   1140 
   1141 					// Create queries for these extra tag-ons we've just dealt with.
   1142 					$subquery        = $index . '?attachment=' . $this->preg_index( 1 );
   1143 					$subtbquery      = $subquery . '&tb=1';
   1144 					$subfeedquery    = $subquery . '&feed=' . $this->preg_index( 2 );
   1145 					$subcommentquery = $subquery . '&cpage=' . $this->preg_index( 2 );
   1146 					$subembedquery   = $subquery . '&embed=true';
   1147 
   1148 					// Do endpoints for attachments.
   1149 					if ( ! empty( $endpoints ) ) {
   1150 						foreach ( (array) $ep_query_append as $regex => $ep ) {
   1151 							if ( $ep[0] & EP_ATTACHMENT ) {
   1152 								$rewrite[ $sub1 . $regex ] = $subquery . $ep[1] . $this->preg_index( 3 );
   1153 								$rewrite[ $sub2 . $regex ] = $subquery . $ep[1] . $this->preg_index( 3 );
   1154 							}
   1155 						}
   1156 					}
   1157 
   1158 					/*
   1159 					 * Now we've finished with endpoints, finish off the $sub1 and $sub2 matches
   1160 					 * add a ? as we don't have to match that last slash, and finally a $ so we
   1161 					 * match to the end of the URL
   1162 					 */
   1163 					$sub1 .= '?$';
   1164 					$sub2 .= '?$';
   1165 
   1166 					/*
   1167 					 * Post pagination, e.g. <permalink>/2/
   1168 					 * Previously: '(/[0-9]+)?/?$', which produced '/2' for page.
   1169 					 * When cast to int, returned 0.
   1170 					 */
   1171 					$match = $match . '(?:/([0-9]+))?/?$';
   1172 					$query = $index . '?' . $query . '&page=' . $this->preg_index( $num_toks + 1 );
   1173 
   1174 					// Not matching a permalink so this is a lot simpler.
   1175 				} else {
   1176 					// Close the match and finalise the query.
   1177 					$match .= '?$';
   1178 					$query  = $index . '?' . $query;
   1179 				}
   1180 
   1181 				/*
   1182 				 * Create the final array for this dir by joining the $rewrite array (which currently
   1183 				 * only contains rules/queries for trackback, pages etc) to the main regex/query for
   1184 				 * this dir
   1185 				 */
   1186 				$rewrite = array_merge( $rewrite, array( $match => $query ) );
   1187 
   1188 				// If we're matching a permalink, add those extras (attachments etc) on.
   1189 				if ( $post ) {
   1190 					// Add trackback.
   1191 					$rewrite = array_merge( array( $trackbackmatch => $trackbackquery ), $rewrite );
   1192 
   1193 					// Add embed.
   1194 					$rewrite = array_merge( array( $embedmatch => $embedquery ), $rewrite );
   1195 
   1196 					// Add regexes/queries for attachments, attachment trackbacks and so on.
   1197 					if ( ! $page ) {
   1198 						// Require <permalink>/attachment/stuff form for pages because of confusion with subpages.
   1199 						$rewrite = array_merge(
   1200 							$rewrite,
   1201 							array(
   1202 								$sub1        => $subquery,
   1203 								$sub1tb      => $subtbquery,
   1204 								$sub1feed    => $subfeedquery,
   1205 								$sub1feed2   => $subfeedquery,
   1206 								$sub1comment => $subcommentquery,
   1207 								$sub1embed   => $subembedquery,
   1208 							)
   1209 						);
   1210 					}
   1211 
   1212 					$rewrite = array_merge(
   1213 						array(
   1214 							$sub2        => $subquery,
   1215 							$sub2tb      => $subtbquery,
   1216 							$sub2feed    => $subfeedquery,
   1217 							$sub2feed2   => $subfeedquery,
   1218 							$sub2comment => $subcommentquery,
   1219 							$sub2embed   => $subembedquery,
   1220 						),
   1221 						$rewrite
   1222 					);
   1223 				}
   1224 			}
   1225 			// Add the rules for this dir to the accumulating $post_rewrite.
   1226 			$post_rewrite = array_merge( $rewrite, $post_rewrite );
   1227 		}
   1228 
   1229 		// The finished rules. phew!
   1230 		return $post_rewrite;
   1231 	}
   1232 
   1233 	/**
   1234 	 * Generates rewrite rules with permalink structure and walking directory only.
   1235 	 *
   1236 	 * Shorten version of WP_Rewrite::generate_rewrite_rules() that allows for shorter
   1237 	 * list of parameters. See the method for longer description of what generating
   1238 	 * rewrite rules does.
   1239 	 *
   1240 	 * @since 1.5.0
   1241 	 *
   1242 	 * @see WP_Rewrite::generate_rewrite_rules() See for long description and rest of parameters.
   1243 	 *
   1244 	 * @param string $permalink_structure The permalink structure to generate rules.
   1245 	 * @param bool   $walk_dirs           Optional. Whether to create list of directories to walk over.
   1246 	 *                                    Default false.
   1247 	 * @return array
   1248 	 */
   1249 	public function generate_rewrite_rule( $permalink_structure, $walk_dirs = false ) {
   1250 		return $this->generate_rewrite_rules( $permalink_structure, EP_NONE, false, false, false, $walk_dirs );
   1251 	}
   1252 
   1253 	/**
   1254 	 * Constructs rewrite matches and queries from permalink structure.
   1255 	 *
   1256 	 * Runs the action {@see 'generate_rewrite_rules'} with the parameter that is an
   1257 	 * reference to the current WP_Rewrite instance to further manipulate the
   1258 	 * permalink structures and rewrite rules. Runs the {@see 'rewrite_rules_array'}
   1259 	 * filter on the full rewrite rule array.
   1260 	 *
   1261 	 * There are two ways to manipulate the rewrite rules, one by hooking into
   1262 	 * the {@see 'generate_rewrite_rules'} action and gaining full control of the
   1263 	 * object or just manipulating the rewrite rule array before it is passed
   1264 	 * from the function.
   1265 	 *
   1266 	 * @since 1.5.0
   1267 	 *
   1268 	 * @return string[] An associative array of matches and queries.
   1269 	 */
   1270 	public function rewrite_rules() {
   1271 		$rewrite = array();
   1272 
   1273 		if ( empty( $this->permalink_structure ) ) {
   1274 			return $rewrite;
   1275 		}
   1276 
   1277 		// robots.txt -- only if installed at the root.
   1278 		$home_path      = parse_url( home_url() );
   1279 		$robots_rewrite = ( empty( $home_path['path'] ) || '/' === $home_path['path'] ) ? array( 'robots\.txt$' => $this->index . '?robots=1' ) : array();
   1280 
   1281 		// favicon.ico -- only if installed at the root.
   1282 		$favicon_rewrite = ( empty( $home_path['path'] ) || '/' === $home_path['path'] ) ? array( 'favicon\.ico$' => $this->index . '?favicon=1' ) : array();
   1283 
   1284 		// Old feed and service files.
   1285 		$deprecated_files = array(
   1286 			'.*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\.php$' => $this->index . '?feed=old',
   1287 			'.*wp-app\.php(/.*)?$' => $this->index . '?error=403',
   1288 		);
   1289 
   1290 		// Registration rules.
   1291 		$registration_pages = array();
   1292 		if ( is_multisite() && is_main_site() ) {
   1293 			$registration_pages['.*wp-signup.php$']   = $this->index . '?signup=true';
   1294 			$registration_pages['.*wp-activate.php$'] = $this->index . '?activate=true';
   1295 		}
   1296 
   1297 		// Deprecated.
   1298 		$registration_pages['.*wp-register.php$'] = $this->index . '?register=true';
   1299 
   1300 		// Post rewrite rules.
   1301 		$post_rewrite = $this->generate_rewrite_rules( $this->permalink_structure, EP_PERMALINK );
   1302 
   1303 		/**
   1304 		 * Filters rewrite rules used for "post" archives.
   1305 		 *
   1306 		 * @since 1.5.0
   1307 		 *
   1308 		 * @param string[] $post_rewrite Array of rewrite rules for posts, keyed by their regex pattern.
   1309 		 */
   1310 		$post_rewrite = apply_filters( 'post_rewrite_rules', $post_rewrite );
   1311 
   1312 		// Date rewrite rules.
   1313 		$date_rewrite = $this->generate_rewrite_rules( $this->get_date_permastruct(), EP_DATE );
   1314 
   1315 		/**
   1316 		 * Filters rewrite rules used for date archives.
   1317 		 *
   1318 		 * Likely date archives would include /yyyy/, /yyyy/mm/, and /yyyy/mm/dd/.
   1319 		 *
   1320 		 * @since 1.5.0
   1321 		 *
   1322 		 * @param string[] $date_rewrite Array of rewrite rules for date archives, keyed by their regex pattern.
   1323 		 */
   1324 		$date_rewrite = apply_filters( 'date_rewrite_rules', $date_rewrite );
   1325 
   1326 		// Root-level rewrite rules.
   1327 		$root_rewrite = $this->generate_rewrite_rules( $this->root . '/', EP_ROOT );
   1328 
   1329 		/**
   1330 		 * Filters rewrite rules used for root-level archives.
   1331 		 *
   1332 		 * Likely root-level archives would include pagination rules for the homepage
   1333 		 * as well as site-wide post feeds (e.g. /feed/, and /feed/atom/).
   1334 		 *
   1335 		 * @since 1.5.0
   1336 		 *
   1337 		 * @param string[] $root_rewrite Array of root-level rewrite rules, keyed by their regex pattern.
   1338 		 */
   1339 		$root_rewrite = apply_filters( 'root_rewrite_rules', $root_rewrite );
   1340 
   1341 		// Comments rewrite rules.
   1342 		$comments_rewrite = $this->generate_rewrite_rules( $this->root . $this->comments_base, EP_COMMENTS, false, true, true, false );
   1343 
   1344 		/**
   1345 		 * Filters rewrite rules used for comment feed archives.
   1346 		 *
   1347 		 * Likely comments feed archives include /comments/feed/, and /comments/feed/atom/.
   1348 		 *
   1349 		 * @since 1.5.0
   1350 		 *
   1351 		 * @param string[] $comments_rewrite Array of rewrite rules for the site-wide comments feeds, keyed by their regex pattern.
   1352 		 */
   1353 		$comments_rewrite = apply_filters( 'comments_rewrite_rules', $comments_rewrite );
   1354 
   1355 		// Search rewrite rules.
   1356 		$search_structure = $this->get_search_permastruct();
   1357 		$search_rewrite   = $this->generate_rewrite_rules( $search_structure, EP_SEARCH );
   1358 
   1359 		/**
   1360 		 * Filters rewrite rules used for search archives.
   1361 		 *
   1362 		 * Likely search-related archives include /search/search+query/ as well as
   1363 		 * pagination and feed paths for a search.
   1364 		 *
   1365 		 * @since 1.5.0
   1366 		 *
   1367 		 * @param string[] $search_rewrite Array of rewrite rules for search queries, keyed by their regex pattern.
   1368 		 */
   1369 		$search_rewrite = apply_filters( 'search_rewrite_rules', $search_rewrite );
   1370 
   1371 		// Author rewrite rules.
   1372 		$author_rewrite = $this->generate_rewrite_rules( $this->get_author_permastruct(), EP_AUTHORS );
   1373 
   1374 		/**
   1375 		 * Filters rewrite rules used for author archives.
   1376 		 *
   1377 		 * Likely author archives would include /author/author-name/, as well as
   1378 		 * pagination and feed paths for author archives.
   1379 		 *
   1380 		 * @since 1.5.0
   1381 		 *
   1382 		 * @param string[] $author_rewrite Array of rewrite rules for author archives, keyed by their regex pattern.
   1383 		 */
   1384 		$author_rewrite = apply_filters( 'author_rewrite_rules', $author_rewrite );
   1385 
   1386 		// Pages rewrite rules.
   1387 		$page_rewrite = $this->page_rewrite_rules();
   1388 
   1389 		/**
   1390 		 * Filters rewrite rules used for "page" post type archives.
   1391 		 *
   1392 		 * @since 1.5.0
   1393 		 *
   1394 		 * @param string[] $page_rewrite Array of rewrite rules for the "page" post type, keyed by their regex pattern.
   1395 		 */
   1396 		$page_rewrite = apply_filters( 'page_rewrite_rules', $page_rewrite );
   1397 
   1398 		// Extra permastructs.
   1399 		foreach ( $this->extra_permastructs as $permastructname => $struct ) {
   1400 			if ( is_array( $struct ) ) {
   1401 				if ( count( $struct ) == 2 ) {
   1402 					$rules = $this->generate_rewrite_rules( $struct[0], $struct[1] );
   1403 				} else {
   1404 					$rules = $this->generate_rewrite_rules( $struct['struct'], $struct['ep_mask'], $struct['paged'], $struct['feed'], $struct['forcomments'], $struct['walk_dirs'], $struct['endpoints'] );
   1405 				}
   1406 			} else {
   1407 				$rules = $this->generate_rewrite_rules( $struct );
   1408 			}
   1409 
   1410 			/**
   1411 			 * Filters rewrite rules used for individual permastructs.
   1412 			 *
   1413 			 * The dynamic portion of the hook name, `$permastructname`, refers
   1414 			 * to the name of the registered permastruct, e.g. 'post_tag' (tags),
   1415 			 * 'category' (categories), etc.
   1416 			 *
   1417 			 * @since 3.1.0
   1418 			 *
   1419 			 * @param string[] $rules Array of rewrite rules generated for the current permastruct, keyed by their regex pattern.
   1420 			 */
   1421 			$rules = apply_filters( "{$permastructname}_rewrite_rules", $rules );
   1422 
   1423 			if ( 'post_tag' === $permastructname ) {
   1424 
   1425 				/**
   1426 				 * Filters rewrite rules used specifically for Tags.
   1427 				 *
   1428 				 * @since 2.3.0
   1429 				 * @deprecated 3.1.0 Use {@see 'post_tag_rewrite_rules'} instead.
   1430 				 *
   1431 				 * @param string[] $rules Array of rewrite rules generated for tags, keyed by their regex pattern.
   1432 				 */
   1433 				$rules = apply_filters_deprecated( 'tag_rewrite_rules', array( $rules ), '3.1.0', 'post_tag_rewrite_rules' );
   1434 			}
   1435 
   1436 			$this->extra_rules_top = array_merge( $this->extra_rules_top, $rules );
   1437 		}
   1438 
   1439 		// Put them together.
   1440 		if ( $this->use_verbose_page_rules ) {
   1441 			$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 );
   1442 		} else {
   1443 			$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 );
   1444 		}
   1445 
   1446 		/**
   1447 		 * Fires after the rewrite rules are generated.
   1448 		 *
   1449 		 * @since 1.5.0
   1450 		 *
   1451 		 * @param WP_Rewrite $this Current WP_Rewrite instance (passed by reference).
   1452 		 */
   1453 		do_action_ref_array( 'generate_rewrite_rules', array( &$this ) );
   1454 
   1455 		/**
   1456 		 * Filters the full set of generated rewrite rules.
   1457 		 *
   1458 		 * @since 1.5.0
   1459 		 *
   1460 		 * @param string[] $rules The compiled array of rewrite rules, keyed by their regex pattern.
   1461 		 */
   1462 		$this->rules = apply_filters( 'rewrite_rules_array', $this->rules );
   1463 
   1464 		return $this->rules;
   1465 	}
   1466 
   1467 	/**
   1468 	 * Retrieves the rewrite rules.
   1469 	 *
   1470 	 * The difference between this method and WP_Rewrite::rewrite_rules() is that
   1471 	 * this method stores the rewrite rules in the 'rewrite_rules' option and retrieves
   1472 	 * it. This prevents having to process all of the permalinks to get the rewrite rules
   1473 	 * in the form of caching.
   1474 	 *
   1475 	 * @since 1.5.0
   1476 	 *
   1477 	 * @return string[] Array of rewrite rules keyed by their regex pattern.
   1478 	 */
   1479 	public function wp_rewrite_rules() {
   1480 		$this->rules = get_option( 'rewrite_rules' );
   1481 		if ( empty( $this->rules ) ) {
   1482 			$this->matches = 'matches';
   1483 			$this->rewrite_rules();
   1484 			if ( ! did_action( 'wp_loaded' ) ) {
   1485 				add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
   1486 				return $this->rules;
   1487 			}
   1488 			update_option( 'rewrite_rules', $this->rules );
   1489 		}
   1490 
   1491 		return $this->rules;
   1492 	}
   1493 
   1494 	/**
   1495 	 * Retrieves mod_rewrite-formatted rewrite rules to write to .htaccess.
   1496 	 *
   1497 	 * Does not actually write to the .htaccess file, but creates the rules for
   1498 	 * the process that will.
   1499 	 *
   1500 	 * Will add the non_wp_rules property rules to the .htaccess file before
   1501 	 * the WordPress rewrite rules one.
   1502 	 *
   1503 	 * @since 1.5.0
   1504 	 *
   1505 	 * @return string
   1506 	 */
   1507 	public function mod_rewrite_rules() {
   1508 		if ( ! $this->using_permalinks() ) {
   1509 			return '';
   1510 		}
   1511 
   1512 		$site_root = parse_url( site_url() );
   1513 		if ( isset( $site_root['path'] ) ) {
   1514 			$site_root = trailingslashit( $site_root['path'] );
   1515 		}
   1516 
   1517 		$home_root = parse_url( home_url() );
   1518 		if ( isset( $home_root['path'] ) ) {
   1519 			$home_root = trailingslashit( $home_root['path'] );
   1520 		} else {
   1521 			$home_root = '/';
   1522 		}
   1523 
   1524 		$rules  = "<IfModule mod_rewrite.c>\n";
   1525 		$rules .= "RewriteEngine On\n";
   1526 		$rules .= "RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]\n";
   1527 		$rules .= "RewriteBase $home_root\n";
   1528 
   1529 		// Prevent -f checks on index.php.
   1530 		$rules .= "RewriteRule ^index\.php$ - [L]\n";
   1531 
   1532 		// Add in the rules that don't redirect to WP's index.php (and thus shouldn't be handled by WP at all).
   1533 		foreach ( (array) $this->non_wp_rules as $match => $query ) {
   1534 			// Apache 1.3 does not support the reluctant (non-greedy) modifier.
   1535 			$match = str_replace( '.+?', '.+', $match );
   1536 
   1537 			$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
   1538 		}
   1539 
   1540 		if ( $this->use_verbose_rules ) {
   1541 			$this->matches = '';
   1542 			$rewrite       = $this->rewrite_rules();
   1543 			$num_rules     = count( $rewrite );
   1544 			$rules        .= "RewriteCond %{REQUEST_FILENAME} -f [OR]\n" .
   1545 				"RewriteCond %{REQUEST_FILENAME} -d\n" .
   1546 				"RewriteRule ^.*$ - [S=$num_rules]\n";
   1547 
   1548 			foreach ( (array) $rewrite as $match => $query ) {
   1549 				// Apache 1.3 does not support the reluctant (non-greedy) modifier.
   1550 				$match = str_replace( '.+?', '.+', $match );
   1551 
   1552 				if ( strpos( $query, $this->index ) !== false ) {
   1553 					$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
   1554 				} else {
   1555 					$rules .= 'RewriteRule ^' . $match . ' ' . $site_root . $query . " [QSA,L]\n";
   1556 				}
   1557 			}
   1558 		} else {
   1559 			$rules .= "RewriteCond %{REQUEST_FILENAME} !-f\n" .
   1560 				"RewriteCond %{REQUEST_FILENAME} !-d\n" .
   1561 				"RewriteRule . {$home_root}{$this->index} [L]\n";
   1562 		}
   1563 
   1564 		$rules .= "</IfModule>\n";
   1565 
   1566 		/**
   1567 		 * Filters the list of rewrite rules formatted for output to an .htaccess file.
   1568 		 *
   1569 		 * @since 1.5.0
   1570 		 *
   1571 		 * @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
   1572 		 */
   1573 		$rules = apply_filters( 'mod_rewrite_rules', $rules );
   1574 
   1575 		/**
   1576 		 * Filters the list of rewrite rules formatted for output to an .htaccess file.
   1577 		 *
   1578 		 * @since 1.5.0
   1579 		 * @deprecated 1.5.0 Use the {@see 'mod_rewrite_rules'} filter instead.
   1580 		 *
   1581 		 * @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
   1582 		 */
   1583 		return apply_filters_deprecated( 'rewrite_rules', array( $rules ), '1.5.0', 'mod_rewrite_rules' );
   1584 	}
   1585 
   1586 	/**
   1587 	 * Retrieves IIS7 URL Rewrite formatted rewrite rules to write to web.config file.
   1588 	 *
   1589 	 * Does not actually write to the web.config file, but creates the rules for
   1590 	 * the process that will.
   1591 	 *
   1592 	 * @since 2.8.0
   1593 	 *
   1594 	 * @param bool $add_parent_tags Optional. Whether to add parent tags to the rewrite rule sets.
   1595 	 *                              Default false.
   1596 	 * @return string IIS7 URL rewrite rule sets.
   1597 	 */
   1598 	public function iis7_url_rewrite_rules( $add_parent_tags = false ) {
   1599 		if ( ! $this->using_permalinks() ) {
   1600 			return '';
   1601 		}
   1602 		$rules = '';
   1603 		if ( $add_parent_tags ) {
   1604 			$rules .= '<configuration>
   1605 	<system.webServer>
   1606 		<rewrite>
   1607 			<rules>';
   1608 		}
   1609 
   1610 		$rules .= '
   1611 			<rule name="WordPress: ' . esc_attr( home_url() ) . '" patternSyntax="Wildcard">
   1612 				<match url="*" />
   1613 					<conditions>
   1614 						<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
   1615 						<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
   1616 					</conditions>
   1617 				<action type="Rewrite" url="index.php" />
   1618 			</rule>';
   1619 
   1620 		if ( $add_parent_tags ) {
   1621 			$rules .= '
   1622 			</rules>
   1623 		</rewrite>
   1624 	</system.webServer>
   1625 </configuration>';
   1626 		}
   1627 
   1628 		/**
   1629 		 * Filters the list of rewrite rules formatted for output to a web.config.
   1630 		 *
   1631 		 * @since 2.8.0
   1632 		 *
   1633 		 * @param string $rules Rewrite rules formatted for IIS web.config.
   1634 		 */
   1635 		return apply_filters( 'iis7_url_rewrite_rules', $rules );
   1636 	}
   1637 
   1638 	/**
   1639 	 * Adds a rewrite rule that transforms a URL structure to a set of query vars.
   1640 	 *
   1641 	 * Any value in the $after parameter that isn't 'bottom' will result in the rule
   1642 	 * being placed at the top of the rewrite rules.
   1643 	 *
   1644 	 * @since 2.1.0
   1645 	 * @since 4.4.0 Array support was added to the `$query` parameter.
   1646 	 *
   1647 	 * @param string       $regex Regular expression to match request against.
   1648 	 * @param string|array $query The corresponding query vars for this rewrite rule.
   1649 	 * @param string       $after Optional. Priority of the new rule. Accepts 'top'
   1650 	 *                            or 'bottom'. Default 'bottom'.
   1651 	 */
   1652 	public function add_rule( $regex, $query, $after = 'bottom' ) {
   1653 		if ( is_array( $query ) ) {
   1654 			$external = false;
   1655 			$query    = add_query_arg( $query, 'index.php' );
   1656 		} else {
   1657 			$index = false === strpos( $query, '?' ) ? strlen( $query ) : strpos( $query, '?' );
   1658 			$front = substr( $query, 0, $index );
   1659 
   1660 			$external = $front != $this->index;
   1661 		}
   1662 
   1663 		// "external" = it doesn't correspond to index.php.
   1664 		if ( $external ) {
   1665 			$this->add_external_rule( $regex, $query );
   1666 		} else {
   1667 			if ( 'bottom' === $after ) {
   1668 				$this->extra_rules = array_merge( $this->extra_rules, array( $regex => $query ) );
   1669 			} else {
   1670 				$this->extra_rules_top = array_merge( $this->extra_rules_top, array( $regex => $query ) );
   1671 			}
   1672 		}
   1673 	}
   1674 
   1675 	/**
   1676 	 * Adds a rewrite rule that doesn't correspond to index.php.
   1677 	 *
   1678 	 * @since 2.1.0
   1679 	 *
   1680 	 * @param string $regex Regular expression to match request against.
   1681 	 * @param string $query The corresponding query vars for this rewrite rule.
   1682 	 */
   1683 	public function add_external_rule( $regex, $query ) {
   1684 		$this->non_wp_rules[ $regex ] = $query;
   1685 	}
   1686 
   1687 	/**
   1688 	 * Adds an endpoint, like /trackback/.
   1689 	 *
   1690 	 * @since 2.1.0
   1691 	 * @since 3.9.0 $query_var parameter added.
   1692 	 * @since 4.3.0 Added support for skipping query var registration by passing `false` to `$query_var`.
   1693 	 *
   1694 	 * @see add_rewrite_endpoint() for full documentation.
   1695 	 * @global WP $wp Current WordPress environment instance.
   1696 	 *
   1697 	 * @param string      $name      Name of the endpoint.
   1698 	 * @param int         $places    Endpoint mask describing the places the endpoint should be added.
   1699 	 *                               Accepts a mask of:
   1700 	 *                               - `EP_ALL`
   1701 	 *                               - `EP_NONE`
   1702 	 *                               - `EP_ALL_ARCHIVES`
   1703 	 *                               - `EP_ATTACHMENT`
   1704 	 *                               - `EP_AUTHORS`
   1705 	 *                               - `EP_CATEGORIES`
   1706 	 *                               - `EP_COMMENTS`
   1707 	 *                               - `EP_DATE`
   1708 	 *                               - `EP_DAY`
   1709 	 *                               - `EP_MONTH`
   1710 	 *                               - `EP_PAGES`
   1711 	 *                               - `EP_PERMALINK`
   1712 	 *                               - `EP_ROOT`
   1713 	 *                               - `EP_SEARCH`
   1714 	 *                               - `EP_TAGS`
   1715 	 *                               - `EP_YEAR`
   1716 	 * @param string|bool $query_var Optional. Name of the corresponding query variable. Pass `false` to
   1717 	 *                               skip registering a query_var for this endpoint. Defaults to the
   1718 	 *                               value of `$name`.
   1719 	 */
   1720 	public function add_endpoint( $name, $places, $query_var = true ) {
   1721 		global $wp;
   1722 
   1723 		// For backward compatibility, if null has explicitly been passed as `$query_var`, assume `true`.
   1724 		if ( true === $query_var || null === $query_var ) {
   1725 			$query_var = $name;
   1726 		}
   1727 		$this->endpoints[] = array( $places, $name, $query_var );
   1728 
   1729 		if ( $query_var ) {
   1730 			$wp->add_query_var( $query_var );
   1731 		}
   1732 	}
   1733 
   1734 	/**
   1735 	 * Adds a new permalink structure.
   1736 	 *
   1737 	 * A permalink structure (permastruct) is an abstract definition of a set of rewrite rules;
   1738 	 * it is an easy way of expressing a set of regular expressions that rewrite to a set of
   1739 	 * query strings. The new permastruct is added to the WP_Rewrite::$extra_permastructs array.
   1740 	 *
   1741 	 * When the rewrite rules are built by WP_Rewrite::rewrite_rules(), all of these extra
   1742 	 * permastructs are passed to WP_Rewrite::generate_rewrite_rules() which transforms them
   1743 	 * into the regular expressions that many love to hate.
   1744 	 *
   1745 	 * The `$args` parameter gives you control over how WP_Rewrite::generate_rewrite_rules()
   1746 	 * works on the new permastruct.
   1747 	 *
   1748 	 * @since 2.5.0
   1749 	 *
   1750 	 * @param string $name   Name for permalink structure.
   1751 	 * @param string $struct Permalink structure (e.g. category/%category%)
   1752 	 * @param array  $args   {
   1753 	 *     Optional. Arguments for building rewrite rules based on the permalink structure.
   1754 	 *     Default empty array.
   1755 	 *
   1756 	 *     @type bool $with_front  Whether the structure should be prepended with `WP_Rewrite::$front`.
   1757 	 *                             Default true.
   1758 	 *     @type int  $ep_mask     The endpoint mask defining which endpoints are added to the structure.
   1759 	 *                             Accepts a mask of:
   1760 	 *                             - `EP_ALL`
   1761 	 *                             - `EP_NONE`
   1762 	 *                             - `EP_ALL_ARCHIVES`
   1763 	 *                             - `EP_ATTACHMENT`
   1764 	 *                             - `EP_AUTHORS`
   1765 	 *                             - `EP_CATEGORIES`
   1766 	 *                             - `EP_COMMENTS`
   1767 	 *                             - `EP_DATE`
   1768 	 *                             - `EP_DAY`
   1769 	 *                             - `EP_MONTH`
   1770 	 *                             - `EP_PAGES`
   1771 	 *                             - `EP_PERMALINK`
   1772 	 *                             - `EP_ROOT`
   1773 	 *                             - `EP_SEARCH`
   1774 	 *                             - `EP_TAGS`
   1775 	 *                             - `EP_YEAR`
   1776 	 *                             Default `EP_NONE`.
   1777 	 *     @type bool $paged       Whether archive pagination rules should be added for the structure.
   1778 	 *                             Default true.
   1779 	 *     @type bool $feed        Whether feed rewrite rules should be added for the structure. Default true.
   1780 	 *     @type bool $forcomments Whether the feed rules should be a query for a comments feed. Default false.
   1781 	 *     @type bool $walk_dirs   Whether the 'directories' making up the structure should be walked over
   1782 	 *                             and rewrite rules built for each in-turn. Default true.
   1783 	 *     @type bool $endpoints   Whether endpoints should be applied to the generated rules. Default true.
   1784 	 * }
   1785 	 */
   1786 	public function add_permastruct( $name, $struct, $args = array() ) {
   1787 		// Back-compat for the old parameters: $with_front and $ep_mask.
   1788 		if ( ! is_array( $args ) ) {
   1789 			$args = array( 'with_front' => $args );
   1790 		}
   1791 		if ( func_num_args() == 4 ) {
   1792 			$args['ep_mask'] = func_get_arg( 3 );
   1793 		}
   1794 
   1795 		$defaults = array(
   1796 			'with_front'  => true,
   1797 			'ep_mask'     => EP_NONE,
   1798 			'paged'       => true,
   1799 			'feed'        => true,
   1800 			'forcomments' => false,
   1801 			'walk_dirs'   => true,
   1802 			'endpoints'   => true,
   1803 		);
   1804 		$args     = array_intersect_key( $args, $defaults );
   1805 		$args     = wp_parse_args( $args, $defaults );
   1806 
   1807 		if ( $args['with_front'] ) {
   1808 			$struct = $this->front . $struct;
   1809 		} else {
   1810 			$struct = $this->root . $struct;
   1811 		}
   1812 		$args['struct'] = $struct;
   1813 
   1814 		$this->extra_permastructs[ $name ] = $args;
   1815 	}
   1816 
   1817 	/**
   1818 	 * Removes a permalink structure.
   1819 	 *
   1820 	 * @since 4.5.0
   1821 	 *
   1822 	 * @param string $name Name for permalink structure.
   1823 	 */
   1824 	public function remove_permastruct( $name ) {
   1825 		unset( $this->extra_permastructs[ $name ] );
   1826 	}
   1827 
   1828 	/**
   1829 	 * Removes rewrite rules and then recreate rewrite rules.
   1830 	 *
   1831 	 * Calls WP_Rewrite::wp_rewrite_rules() after removing the 'rewrite_rules' option.
   1832 	 * If the function named 'save_mod_rewrite_rules' exists, it will be called.
   1833 	 *
   1834 	 * @since 2.0.1
   1835 	 *
   1836 	 * @param bool $hard Whether to update .htaccess (hard flush) or just update rewrite_rules option (soft flush). Default is true (hard).
   1837 	 */
   1838 	public function flush_rules( $hard = true ) {
   1839 		static $do_hard_later = null;
   1840 
   1841 		// Prevent this action from running before everyone has registered their rewrites.
   1842 		if ( ! did_action( 'wp_loaded' ) ) {
   1843 			add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
   1844 			$do_hard_later = ( isset( $do_hard_later ) ) ? $do_hard_later || $hard : $hard;
   1845 			return;
   1846 		}
   1847 
   1848 		if ( isset( $do_hard_later ) ) {
   1849 			$hard = $do_hard_later;
   1850 			unset( $do_hard_later );
   1851 		}
   1852 
   1853 		update_option( 'rewrite_rules', '' );
   1854 		$this->wp_rewrite_rules();
   1855 
   1856 		/**
   1857 		 * Filters whether a "hard" rewrite rule flush should be performed when requested.
   1858 		 *
   1859 		 * A "hard" flush updates .htaccess (Apache) or web.config (IIS).
   1860 		 *
   1861 		 * @since 3.7.0
   1862 		 *
   1863 		 * @param bool $hard Whether to flush rewrite rules "hard". Default true.
   1864 		 */
   1865 		if ( ! $hard || ! apply_filters( 'flush_rewrite_rules_hard', true ) ) {
   1866 			return;
   1867 		}
   1868 		if ( function_exists( 'save_mod_rewrite_rules' ) ) {
   1869 			save_mod_rewrite_rules();
   1870 		}
   1871 		if ( function_exists( 'iis7_save_url_rewrite_rules' ) ) {
   1872 			iis7_save_url_rewrite_rules();
   1873 		}
   1874 	}
   1875 
   1876 	/**
   1877 	 * Sets up the object's properties.
   1878 	 *
   1879 	 * The 'use_verbose_page_rules' object property will be set to true if the
   1880 	 * permalink structure begins with one of the following: '%postname%', '%category%',
   1881 	 * '%tag%', or '%author%'.
   1882 	 *
   1883 	 * @since 1.5.0
   1884 	 */
   1885 	public function init() {
   1886 		$this->extra_rules         = array();
   1887 		$this->non_wp_rules        = array();
   1888 		$this->endpoints           = array();
   1889 		$this->permalink_structure = get_option( 'permalink_structure' );
   1890 		$this->front               = substr( $this->permalink_structure, 0, strpos( $this->permalink_structure, '%' ) );
   1891 		$this->root                = '';
   1892 
   1893 		if ( $this->using_index_permalinks() ) {
   1894 			$this->root = $this->index . '/';
   1895 		}
   1896 
   1897 		unset( $this->author_structure );
   1898 		unset( $this->date_structure );
   1899 		unset( $this->page_structure );
   1900 		unset( $this->search_structure );
   1901 		unset( $this->feed_structure );
   1902 		unset( $this->comment_feed_structure );
   1903 
   1904 		$this->use_trailing_slashes = ( '/' === substr( $this->permalink_structure, -1, 1 ) );
   1905 
   1906 		// Enable generic rules for pages if permalink structure doesn't begin with a wildcard.
   1907 		if ( preg_match( '/^[^%]*%(?:postname|category|tag|author)%/', $this->permalink_structure ) ) {
   1908 			$this->use_verbose_page_rules = true;
   1909 		} else {
   1910 			$this->use_verbose_page_rules = false;
   1911 		}
   1912 	}
   1913 
   1914 	/**
   1915 	 * Sets the main permalink structure for the site.
   1916 	 *
   1917 	 * Will update the 'permalink_structure' option, if there is a difference
   1918 	 * between the current permalink structure and the parameter value. Calls
   1919 	 * WP_Rewrite::init() after the option is updated.
   1920 	 *
   1921 	 * Fires the {@see 'permalink_structure_changed'} action once the init call has
   1922 	 * processed passing the old and new values
   1923 	 *
   1924 	 * @since 1.5.0
   1925 	 *
   1926 	 * @param string $permalink_structure Permalink structure.
   1927 	 */
   1928 	public function set_permalink_structure( $permalink_structure ) {
   1929 		if ( $permalink_structure != $this->permalink_structure ) {
   1930 			$old_permalink_structure = $this->permalink_structure;
   1931 			update_option( 'permalink_structure', $permalink_structure );
   1932 
   1933 			$this->init();
   1934 
   1935 			/**
   1936 			 * Fires after the permalink structure is updated.
   1937 			 *
   1938 			 * @since 2.8.0
   1939 			 *
   1940 			 * @param string $old_permalink_structure The previous permalink structure.
   1941 			 * @param string $permalink_structure     The new permalink structure.
   1942 			 */
   1943 			do_action( 'permalink_structure_changed', $old_permalink_structure, $permalink_structure );
   1944 		}
   1945 	}
   1946 
   1947 	/**
   1948 	 * Sets the category base for the category permalink.
   1949 	 *
   1950 	 * Will update the 'category_base' option, if there is a difference between
   1951 	 * the current category base and the parameter value. Calls WP_Rewrite::init()
   1952 	 * after the option is updated.
   1953 	 *
   1954 	 * @since 1.5.0
   1955 	 *
   1956 	 * @param string $category_base Category permalink structure base.
   1957 	 */
   1958 	public function set_category_base( $category_base ) {
   1959 		if ( get_option( 'category_base' ) !== $category_base ) {
   1960 			update_option( 'category_base', $category_base );
   1961 			$this->init();
   1962 		}
   1963 	}
   1964 
   1965 	/**
   1966 	 * Sets the tag base for the tag permalink.
   1967 	 *
   1968 	 * Will update the 'tag_base' option, if there is a difference between the
   1969 	 * current tag base and the parameter value. Calls WP_Rewrite::init() after
   1970 	 * the option is updated.
   1971 	 *
   1972 	 * @since 2.3.0
   1973 	 *
   1974 	 * @param string $tag_base Tag permalink structure base.
   1975 	 */
   1976 	public function set_tag_base( $tag_base ) {
   1977 		if ( get_option( 'tag_base' ) !== $tag_base ) {
   1978 			update_option( 'tag_base', $tag_base );
   1979 			$this->init();
   1980 		}
   1981 	}
   1982 
   1983 	/**
   1984 	 * Constructor - Calls init(), which runs setup.
   1985 	 *
   1986 	 * @since 1.5.0
   1987 	 */
   1988 	public function __construct() {
   1989 		$this->init();
   1990 	}
   1991 }