[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-rewrite.php (source)

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


Generated : Sun Oct 11 08:20:32 2026 Cross-referenced by PHPXref