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