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