wp/wp-includes/class-wp-rewrite.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    20  * meaning you can't define how the template files load based on the rewrite
    20  * meaning you can't define how the template files load based on the rewrite
    21  * rules.
    21  * rules.
    22  *
    22  *
    23  * @since 1.5.0
    23  * @since 1.5.0
    24  */
    24  */
       
    25 #[AllowDynamicProperties]
    25 class WP_Rewrite {
    26 class WP_Rewrite {
    26 	/**
    27 	/**
    27 	 * Permalink structure for posts.
    28 	 * Permalink structure for posts.
    28 	 *
    29 	 *
    29 	 * @since 1.5.0
    30 	 * @since 1.5.0
   182 
   183 
   183 	/**
   184 	/**
   184 	 * Rewrite rules to match against the request to find the redirect or query.
   185 	 * Rewrite rules to match against the request to find the redirect or query.
   185 	 *
   186 	 *
   186 	 * @since 1.5.0
   187 	 * @since 1.5.0
   187 	 * @var array
   188 	 * @var string[]
   188 	 */
   189 	 */
   189 	public $rules;
   190 	public $rules;
   190 
   191 
   191 	/**
   192 	/**
   192 	 * Additional rules added external to the rewrite class.
   193 	 * Additional rules added external to the rewrite class.
   193 	 *
   194 	 *
   194 	 * Those not generated by the class, see add_rewrite_rule().
   195 	 * Those not generated by the class, see add_rewrite_rule().
   195 	 *
   196 	 *
   196 	 * @since 2.1.0
   197 	 * @since 2.1.0
   197 	 * @var array
   198 	 * @var string[]
   198 	 */
   199 	 */
   199 	public $extra_rules = array();
   200 	public $extra_rules = array();
   200 
   201 
   201 	/**
   202 	/**
   202 	 * Additional rules that belong at the beginning to match first.
   203 	 * Additional rules that belong at the beginning to match first.
   203 	 *
   204 	 *
   204 	 * Those not generated by the class, see add_rewrite_rule().
   205 	 * Those not generated by the class, see add_rewrite_rule().
   205 	 *
   206 	 *
   206 	 * @since 2.3.0
   207 	 * @since 2.3.0
   207 	 * @var array
   208 	 * @var string[]
   208 	 */
   209 	 */
   209 	public $extra_rules_top = array();
   210 	public $extra_rules_top = array();
   210 
   211 
   211 	/**
   212 	/**
   212 	 * Rules that don't redirect to WordPress' index.php.
   213 	 * Rules that don't redirect to WordPress' index.php.
   213 	 *
   214 	 *
   214 	 * These rules are written to the mod_rewrite portion of the .htaccess,
   215 	 * These rules are written to the mod_rewrite portion of the .htaccess,
   215 	 * and are added by add_external_rule().
   216 	 * and are added by add_external_rule().
   216 	 *
   217 	 *
   217 	 * @since 2.1.0
   218 	 * @since 2.1.0
   218 	 * @var array
   219 	 * @var string[]
   219 	 */
   220 	 */
   220 	public $non_wp_rules = array();
   221 	public $non_wp_rules = array();
   221 
   222 
   222 	/**
   223 	/**
   223 	 * Extra permalink structures, e.g. categories, added by add_permastruct().
   224 	 * Extra permalink structures, e.g. categories, added by add_permastruct().
   224 	 *
   225 	 *
   225 	 * @since 2.1.0
   226 	 * @since 2.1.0
   226 	 * @var array
   227 	 * @var array[]
   227 	 */
   228 	 */
   228 	public $extra_permastructs = array();
   229 	public $extra_permastructs = array();
   229 
   230 
   230 	/**
   231 	/**
   231 	 * Endpoints (like /trackback/) added by add_rewrite_endpoint().
   232 	 * Endpoints (like /trackback/) added by add_rewrite_endpoint().
   232 	 *
   233 	 *
   233 	 * @since 2.1.0
   234 	 * @since 2.1.0
   234 	 * @var array
   235 	 * @var array[]
   235 	 */
   236 	 */
   236 	public $endpoints;
   237 	public $endpoints;
   237 
   238 
   238 	/**
   239 	/**
   239 	 * Whether to write every mod_rewrite rule for WordPress into the .htaccess file.
   240 	 * Whether to write every mod_rewrite rule for WordPress into the .htaccess file.
   410 
   411 
   411 		return "$match_prefix$number$match_suffix";
   412 		return "$match_prefix$number$match_suffix";
   412 	}
   413 	}
   413 
   414 
   414 	/**
   415 	/**
   415 	 * Retrieves all page and attachments for pages URIs.
   416 	 * Retrieves all pages and attachments for pages URIs.
   416 	 *
   417 	 *
   417 	 * The attachments are for those that have pages as parents and will be
   418 	 * The attachments are for those that have pages as parents and will be
   418 	 * retrieved.
   419 	 * retrieved.
   419 	 *
   420 	 *
   420 	 * @since 2.5.0
   421 	 * @since 2.5.0
   506 
   507 
   507 		$this->date_structure = '';
   508 		$this->date_structure = '';
   508 		$date_endian          = '';
   509 		$date_endian          = '';
   509 
   510 
   510 		foreach ( $endians as $endian ) {
   511 		foreach ( $endians as $endian ) {
   511 			if ( false !== strpos( $this->permalink_structure, $endian ) ) {
   512 			if ( str_contains( $this->permalink_structure, $endian ) ) {
   512 				$date_endian = $endian;
   513 				$date_endian = $endian;
   513 				break;
   514 				break;
   514 			}
   515 			}
   515 		}
   516 		}
   516 
   517 
   528 		foreach ( (array) $tokens[0] as $token ) {
   529 		foreach ( (array) $tokens[0] as $token ) {
   529 			if ( '%post_id%' === $token && ( $tok_index <= 3 ) ) {
   530 			if ( '%post_id%' === $token && ( $tok_index <= 3 ) ) {
   530 				$front = $front . 'date/';
   531 				$front = $front . 'date/';
   531 				break;
   532 				break;
   532 			}
   533 			}
   533 			$tok_index++;
   534 			++$tok_index;
   534 		}
   535 		}
   535 
   536 
   536 		$this->date_structure = $front . $date_endian;
   537 		$this->date_structure = $front . $date_endian;
   537 
   538 
   538 		return $this->date_structure;
   539 		return $this->date_structure;
   613 	public function get_category_permastruct() {
   614 	public function get_category_permastruct() {
   614 		return $this->get_extra_permastruct( 'category' );
   615 		return $this->get_extra_permastruct( 'category' );
   615 	}
   616 	}
   616 
   617 
   617 	/**
   618 	/**
   618 	 * Retrieve the permalink structure for tags.
   619 	 * Retrieves the permalink structure for tags.
   619 	 *
   620 	 *
   620 	 * If the tag_base property has no value, then the tag structure will have
   621 	 * 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 	 * 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 	 * does, then the root property will be used, along with the tag_base
   623 	 * property value.
   624 	 * property value.
   858 	 *                                    - `EP_TAGS`
   859 	 *                                    - `EP_TAGS`
   859 	 *                                    - `EP_YEAR`
   860 	 *                                    - `EP_YEAR`
   860 	 *                                    Default `EP_NONE`.
   861 	 *                                    Default `EP_NONE`.
   861 	 * @param bool   $paged               Optional. Whether archive pagination rules should be added for the structure.
   862 	 * @param bool   $paged               Optional. Whether archive pagination rules should be added for the structure.
   862 	 *                                    Default true.
   863 	 *                                    Default true.
   863 	 * @param bool   $feed                Optional Whether feed rewrite rules should be added for the structure.
   864 	 * @param bool   $feed                Optional. Whether feed rewrite rules should be added for the structure.
   864 	 *                                    Default true.
   865 	 *                                    Default true.
   865 	 * @param bool   $forcomments         Optional. Whether the feed rules should be a query for a comments feed.
   866 	 * @param bool   $forcomments         Optional. Whether the feed rules should be a query for a comments feed.
   866 	 *                                    Default false.
   867 	 *                                    Default false.
   867 	 * @param bool   $walk_dirs           Optional. Whether the 'directories' making up the structure should be walked
   868 	 * @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 	 *                                    over and rewrite rules built for each in-turn. Default true.
  1057 				 * Check to see if this dir is permalink-level: i.e. the structure specifies an
  1058 				 * 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 				 * 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 				 * 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 				 * minute all present). Set these flags now as we need them for the endpoints.
  1061 				 */
  1062 				 */
  1062 				if ( strpos( $struct, '%postname%' ) !== false
  1063 				if ( str_contains( $struct, '%postname%' )
  1063 						|| strpos( $struct, '%post_id%' ) !== false
  1064 					|| str_contains( $struct, '%post_id%' )
  1064 						|| strpos( $struct, '%pagename%' ) !== false
  1065 					|| str_contains( $struct, '%pagename%' )
  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 					|| ( str_contains( $struct, '%year%' )
  1066 						) {
  1067 						&& str_contains( $struct, '%monthnum%' )
       
  1068 						&& str_contains( $struct, '%day%' )
       
  1069 						&& str_contains( $struct, '%hour%' )
       
  1070 						&& str_contains( $struct, '%minute%' )
       
  1071 						&& str_contains( $struct, '%second%' ) )
       
  1072 				) {
  1067 					$post = true;
  1073 					$post = true;
  1068 					if ( strpos( $struct, '%pagename%' ) !== false ) {
  1074 					if ( str_contains( $struct, '%pagename%' ) ) {
  1069 						$page = true;
  1075 						$page = true;
  1070 					}
  1076 					}
  1071 				}
  1077 				}
  1072 
  1078 
  1073 				if ( ! $post ) {
  1079 				if ( ! $post ) {
  1074 					// For custom post types, we need to add on endpoints as well.
  1080 					// For custom post types, we need to add on endpoints as well.
  1075 					foreach ( get_post_types( array( '_builtin' => false ) ) as $ptype ) {
  1081 					foreach ( get_post_types( array( '_builtin' => false ) ) as $ptype ) {
  1076 						if ( strpos( $struct, "%$ptype%" ) !== false ) {
  1082 						if ( str_contains( $struct, "%$ptype%" ) ) {
  1077 							$post = true;
  1083 							$post = true;
  1078 
  1084 
  1079 							// This is for page style attachment URLs.
  1085 							// This is for page style attachment URLs.
  1080 							$page = is_post_type_hierarchical( $ptype );
  1086 							$page = is_post_type_hierarchical( $ptype );
  1081 							break;
  1087 							break;
  1242 	 * @see WP_Rewrite::generate_rewrite_rules() See for long description and rest of parameters.
  1248 	 * @see WP_Rewrite::generate_rewrite_rules() See for long description and rest of parameters.
  1243 	 *
  1249 	 *
  1244 	 * @param string $permalink_structure The permalink structure to generate rules.
  1250 	 * @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.
  1251 	 * @param bool   $walk_dirs           Optional. Whether to create list of directories to walk over.
  1246 	 *                                    Default false.
  1252 	 *                                    Default false.
  1247 	 * @return array
  1253 	 * @return array An array of rewrite rules keyed by their regex pattern.
  1248 	 */
  1254 	 */
  1249 	public function generate_rewrite_rule( $permalink_structure, $walk_dirs = false ) {
  1255 	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 );
  1256 		return $this->generate_rewrite_rules( $permalink_structure, EP_NONE, false, false, false, $walk_dirs );
  1251 	}
  1257 	}
  1252 
  1258 
  1396 		$page_rewrite = apply_filters( 'page_rewrite_rules', $page_rewrite );
  1402 		$page_rewrite = apply_filters( 'page_rewrite_rules', $page_rewrite );
  1397 
  1403 
  1398 		// Extra permastructs.
  1404 		// Extra permastructs.
  1399 		foreach ( $this->extra_permastructs as $permastructname => $struct ) {
  1405 		foreach ( $this->extra_permastructs as $permastructname => $struct ) {
  1400 			if ( is_array( $struct ) ) {
  1406 			if ( is_array( $struct ) ) {
  1401 				if ( count( $struct ) == 2 ) {
  1407 				if ( count( $struct ) === 2 ) {
  1402 					$rules = $this->generate_rewrite_rules( $struct[0], $struct[1] );
  1408 					$rules = $this->generate_rewrite_rules( $struct[0], $struct[1] );
  1403 				} else {
  1409 				} else {
  1404 					$rules = $this->generate_rewrite_rules( $struct['struct'], $struct['ep_mask'], $struct['paged'], $struct['feed'], $struct['forcomments'], $struct['walk_dirs'], $struct['endpoints'] );
  1410 					$rules = $this->generate_rewrite_rules( $struct['struct'], $struct['ep_mask'], $struct['paged'], $struct['feed'], $struct['forcomments'], $struct['walk_dirs'], $struct['endpoints'] );
  1405 				}
  1411 				}
  1406 			} else {
  1412 			} else {
  1482 	 * @return string[] Array of rewrite rules keyed by their regex pattern.
  1488 	 * @return string[] Array of rewrite rules keyed by their regex pattern.
  1483 	 */
  1489 	 */
  1484 	public function wp_rewrite_rules() {
  1490 	public function wp_rewrite_rules() {
  1485 		$this->rules = get_option( 'rewrite_rules' );
  1491 		$this->rules = get_option( 'rewrite_rules' );
  1486 		if ( empty( $this->rules ) ) {
  1492 		if ( empty( $this->rules ) ) {
  1487 			$this->matches = 'matches';
  1493 			$this->refresh_rewrite_rules();
  1488 			$this->rewrite_rules();
  1494 		}
  1489 			if ( ! did_action( 'wp_loaded' ) ) {
  1495 
  1490 				add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
  1496 		return $this->rules;
  1491 				return $this->rules;
  1497 	}
  1492 			}
  1498 
       
  1499 	/**
       
  1500 	 * Refreshes the rewrite rules, saving the fresh value to the database.
       
  1501 	 * If the `wp_loaded` action has not occurred yet, will postpone saving to the database.
       
  1502 	 *
       
  1503 	 * @since 6.4.0
       
  1504 	 */
       
  1505 	private function refresh_rewrite_rules() {
       
  1506 		$this->rules   = '';
       
  1507 		$this->matches = 'matches';
       
  1508 
       
  1509 		$this->rewrite_rules();
       
  1510 
       
  1511 		if ( ! did_action( 'wp_loaded' ) ) {
       
  1512 			/*
       
  1513 			 * Is not safe to save the results right now, as the rules may be partial.
       
  1514 			 * Need to give all rules the chance to register.
       
  1515 			 */
       
  1516 			add_action( 'wp_loaded', array( $this, 'flush_rules' ) );
       
  1517 		} else {
  1493 			update_option( 'rewrite_rules', $this->rules );
  1518 			update_option( 'rewrite_rules', $this->rules );
  1494 		}
  1519 		}
  1495 
       
  1496 		return $this->rules;
       
  1497 	}
  1520 	}
  1498 
  1521 
  1499 	/**
  1522 	/**
  1500 	 * Retrieves mod_rewrite-formatted rewrite rules to write to .htaccess.
  1523 	 * Retrieves mod_rewrite-formatted rewrite rules to write to .htaccess.
  1501 	 *
  1524 	 *
  1552 
  1575 
  1553 			foreach ( (array) $rewrite as $match => $query ) {
  1576 			foreach ( (array) $rewrite as $match => $query ) {
  1554 				// Apache 1.3 does not support the reluctant (non-greedy) modifier.
  1577 				// Apache 1.3 does not support the reluctant (non-greedy) modifier.
  1555 				$match = str_replace( '.+?', '.+', $match );
  1578 				$match = str_replace( '.+?', '.+', $match );
  1556 
  1579 
  1557 				if ( strpos( $query, $this->index ) !== false ) {
  1580 				if ( str_contains( $query, $this->index ) ) {
  1558 					$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
  1581 					$rules .= 'RewriteRule ^' . $match . ' ' . $home_root . $query . " [QSA,L]\n";
  1559 				} else {
  1582 				} else {
  1560 					$rules .= 'RewriteRule ^' . $match . ' ' . $site_root . $query . " [QSA,L]\n";
  1583 					$rules .= 'RewriteRule ^' . $match . ' ' . $site_root . $query . " [QSA,L]\n";
  1561 				}
  1584 				}
  1562 			}
  1585 			}
  1657 	public function add_rule( $regex, $query, $after = 'bottom' ) {
  1680 	public function add_rule( $regex, $query, $after = 'bottom' ) {
  1658 		if ( is_array( $query ) ) {
  1681 		if ( is_array( $query ) ) {
  1659 			$external = false;
  1682 			$external = false;
  1660 			$query    = add_query_arg( $query, 'index.php' );
  1683 			$query    = add_query_arg( $query, 'index.php' );
  1661 		} else {
  1684 		} else {
  1662 			$index = false === strpos( $query, '?' ) ? strlen( $query ) : strpos( $query, '?' );
  1685 			$index = ! str_contains( $query, '?' ) ? strlen( $query ) : strpos( $query, '?' );
  1663 			$front = substr( $query, 0, $index );
  1686 			$front = substr( $query, 0, $index );
  1664 
  1687 
  1665 			$external = $front != $this->index;
  1688 			$external = $front !== $this->index;
  1666 		}
  1689 		}
  1667 
  1690 
  1668 		// "external" = it doesn't correspond to index.php.
  1691 		// "external" = it doesn't correspond to index.php.
  1669 		if ( $external ) {
  1692 		if ( $external ) {
  1670 			$this->add_external_rule( $regex, $query );
  1693 			$this->add_external_rule( $regex, $query );
  1791 	public function add_permastruct( $name, $struct, $args = array() ) {
  1814 	public function add_permastruct( $name, $struct, $args = array() ) {
  1792 		// Back-compat for the old parameters: $with_front and $ep_mask.
  1815 		// Back-compat for the old parameters: $with_front and $ep_mask.
  1793 		if ( ! is_array( $args ) ) {
  1816 		if ( ! is_array( $args ) ) {
  1794 			$args = array( 'with_front' => $args );
  1817 			$args = array( 'with_front' => $args );
  1795 		}
  1818 		}
  1796 		if ( func_num_args() == 4 ) {
  1819 
       
  1820 		if ( func_num_args() === 4 ) {
  1797 			$args['ep_mask'] = func_get_arg( 3 );
  1821 			$args['ep_mask'] = func_get_arg( 3 );
  1798 		}
  1822 		}
  1799 
  1823 
  1800 		$defaults = array(
  1824 		$defaults = array(
  1801 			'with_front'  => true,
  1825 			'with_front'  => true,
  1804 			'feed'        => true,
  1828 			'feed'        => true,
  1805 			'forcomments' => false,
  1829 			'forcomments' => false,
  1806 			'walk_dirs'   => true,
  1830 			'walk_dirs'   => true,
  1807 			'endpoints'   => true,
  1831 			'endpoints'   => true,
  1808 		);
  1832 		);
  1809 		$args     = array_intersect_key( $args, $defaults );
  1833 
  1810 		$args     = wp_parse_args( $args, $defaults );
  1834 		$args = array_intersect_key( $args, $defaults );
       
  1835 		$args = wp_parse_args( $args, $defaults );
  1811 
  1836 
  1812 		if ( $args['with_front'] ) {
  1837 		if ( $args['with_front'] ) {
  1813 			$struct = $this->front . $struct;
  1838 			$struct = $this->front . $struct;
  1814 		} else {
  1839 		} else {
  1815 			$struct = $this->root . $struct;
  1840 			$struct = $this->root . $struct;
  1816 		}
  1841 		}
       
  1842 
  1817 		$args['struct'] = $struct;
  1843 		$args['struct'] = $struct;
  1818 
  1844 
  1819 		$this->extra_permastructs[ $name ] = $args;
  1845 		$this->extra_permastructs[ $name ] = $args;
  1820 	}
  1846 	}
  1821 
  1847 
  1853 		if ( isset( $do_hard_later ) ) {
  1879 		if ( isset( $do_hard_later ) ) {
  1854 			$hard = $do_hard_later;
  1880 			$hard = $do_hard_later;
  1855 			unset( $do_hard_later );
  1881 			unset( $do_hard_later );
  1856 		}
  1882 		}
  1857 
  1883 
  1858 		update_option( 'rewrite_rules', '' );
  1884 		$this->refresh_rewrite_rules();
  1859 		$this->wp_rewrite_rules();
       
  1860 
  1885 
  1861 		/**
  1886 		/**
  1862 		 * Filters whether a "hard" rewrite rule flush should be performed when requested.
  1887 		 * Filters whether a "hard" rewrite rule flush should be performed when requested.
  1863 		 *
  1888 		 *
  1864 		 * A "hard" flush updates .htaccess (Apache) or web.config (IIS).
  1889 		 * A "hard" flush updates .htaccess (Apache) or web.config (IIS).
  1904 		unset( $this->page_structure );
  1929 		unset( $this->page_structure );
  1905 		unset( $this->search_structure );
  1930 		unset( $this->search_structure );
  1906 		unset( $this->feed_structure );
  1931 		unset( $this->feed_structure );
  1907 		unset( $this->comment_feed_structure );
  1932 		unset( $this->comment_feed_structure );
  1908 
  1933 
  1909 		$this->use_trailing_slashes = ( '/' === substr( $this->permalink_structure, -1, 1 ) );
  1934 		$this->use_trailing_slashes = str_ends_with( $this->permalink_structure, '/' );
  1910 
  1935 
  1911 		// Enable generic rules for pages if permalink structure doesn't begin with a wildcard.
  1936 		// 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 ) ) {
  1937 		if ( preg_match( '/^[^%]*%(?:postname|category|tag|author)%/', $this->permalink_structure ) ) {
  1913 			$this->use_verbose_page_rules = true;
  1938 			$this->use_verbose_page_rules = true;
  1914 		} else {
  1939 		} else {
  1929 	 * @since 1.5.0
  1954 	 * @since 1.5.0
  1930 	 *
  1955 	 *
  1931 	 * @param string $permalink_structure Permalink structure.
  1956 	 * @param string $permalink_structure Permalink structure.
  1932 	 */
  1957 	 */
  1933 	public function set_permalink_structure( $permalink_structure ) {
  1958 	public function set_permalink_structure( $permalink_structure ) {
  1934 		if ( $permalink_structure != $this->permalink_structure ) {
  1959 		if ( $this->permalink_structure !== $permalink_structure ) {
  1935 			$old_permalink_structure = $this->permalink_structure;
  1960 			$old_permalink_structure = $this->permalink_structure;
  1936 			update_option( 'permalink_structure', $permalink_structure );
  1961 			update_option( 'permalink_structure', $permalink_structure );
  1937 
  1962 
  1938 			$this->init();
  1963 			$this->init();
  1939 
  1964