wp/wp-includes/class-wp-url-pattern-prefixer.php
changeset 22 8c2e4d02f4ef
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
       
     1 <?php
       
     2 /**
       
     3  * Class 'WP_URL_Pattern_Prefixer'.
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage Speculative Loading
       
     7  * @since 6.8.0
       
     8  */
       
     9 
       
    10 /**
       
    11  * Class for prefixing URL patterns.
       
    12  *
       
    13  * This class is intended primarily for use as part of the speculative loading feature.
       
    14  *
       
    15  * @since 6.8.0
       
    16  * @access private
       
    17  */
       
    18 class WP_URL_Pattern_Prefixer {
       
    19 
       
    20 	/**
       
    21 	 * Map of `$context_string => $base_path` pairs.
       
    22 	 *
       
    23 	 * @since 6.8.0
       
    24 	 * @var array<string, string>
       
    25 	 */
       
    26 	private $contexts;
       
    27 
       
    28 	/**
       
    29 	 * Constructor.
       
    30 	 *
       
    31 	 * @since 6.8.0
       
    32 	 *
       
    33 	 * @param array<string, string> $contexts Optional. Map of `$context_string => $base_path` pairs. Default is the
       
    34 	 *                                        contexts returned by the
       
    35 	 *                                        {@see WP_URL_Pattern_Prefixer::get_default_contexts()} method.
       
    36 	 */
       
    37 	public function __construct( array $contexts = array() ) {
       
    38 		if ( count( $contexts ) > 0 ) {
       
    39 			$this->contexts = array_map(
       
    40 				static function ( string $str ): string {
       
    41 					return self::escape_pattern_string( trailingslashit( $str ) );
       
    42 				},
       
    43 				$contexts
       
    44 			);
       
    45 		} else {
       
    46 			$this->contexts = self::get_default_contexts();
       
    47 		}
       
    48 	}
       
    49 
       
    50 	/**
       
    51 	 * Prefixes the given URL path pattern with the base path for the given context.
       
    52 	 *
       
    53 	 * This ensures that these path patterns work correctly on WordPress subdirectory sites, for example in a multisite
       
    54 	 * network, or when WordPress itself is installed in a subdirectory of the hostname.
       
    55 	 *
       
    56 	 * The given URL path pattern is only prefixed if it does not already include the expected prefix.
       
    57 	 *
       
    58 	 * @since 6.8.0
       
    59 	 *
       
    60 	 * @param string $path_pattern URL pattern starting with the path segment.
       
    61 	 * @param string $context      Optional. Context to use for prefixing the path pattern. Default 'home'.
       
    62 	 * @return string URL pattern, prefixed as necessary.
       
    63 	 */
       
    64 	public function prefix_path_pattern( string $path_pattern, string $context = 'home' ): string {
       
    65 		// If context path does not exist, the context is invalid.
       
    66 		if ( ! isset( $this->contexts[ $context ] ) ) {
       
    67 			_doing_it_wrong(
       
    68 				__FUNCTION__,
       
    69 				esc_html(
       
    70 					sprintf(
       
    71 						/* translators: %s: context string */
       
    72 						__( 'Invalid URL pattern context %s.' ),
       
    73 						$context
       
    74 					)
       
    75 				),
       
    76 				'6.8.0'
       
    77 			);
       
    78 			return $path_pattern;
       
    79 		}
       
    80 
       
    81 		/*
       
    82 		 * In the event that the context path contains a :, ? or # (which can cause the URL pattern parser to switch to
       
    83 		 * another state, though only the latter two should be percent encoded anyway), it additionally needs to be
       
    84 		 * enclosed in grouping braces. The final forward slash (trailingslashit ensures there is one) affects the
       
    85 		 * meaning of the * wildcard, so is left outside the braces.
       
    86 		 */
       
    87 		$context_path         = $this->contexts[ $context ];
       
    88 		$escaped_context_path = $context_path;
       
    89 		if ( strcspn( $context_path, ':?#' ) !== strlen( $context_path ) ) {
       
    90 			$escaped_context_path = '{' . substr( $context_path, 0, -1 ) . '}/';
       
    91 		}
       
    92 
       
    93 		/*
       
    94 		 * If the path already starts with the context path (including '/'), remove it first
       
    95 		 * since it is about to be added back.
       
    96 		 */
       
    97 		if ( str_starts_with( $path_pattern, $context_path ) ) {
       
    98 			$path_pattern = substr( $path_pattern, strlen( $context_path ) );
       
    99 		}
       
   100 
       
   101 		return $escaped_context_path . ltrim( $path_pattern, '/' );
       
   102 	}
       
   103 
       
   104 	/**
       
   105 	 * Returns the default contexts used by the class.
       
   106 	 *
       
   107 	 * @since 6.8.0
       
   108 	 *
       
   109 	 * @return array<string, string> Map of `$context_string => $base_path` pairs.
       
   110 	 */
       
   111 	public static function get_default_contexts(): array {
       
   112 		return array(
       
   113 			'home'       => self::escape_pattern_string( trailingslashit( (string) wp_parse_url( home_url( '/' ), PHP_URL_PATH ) ) ),
       
   114 			'site'       => self::escape_pattern_string( trailingslashit( (string) wp_parse_url( site_url( '/' ), PHP_URL_PATH ) ) ),
       
   115 			'uploads'    => self::escape_pattern_string( trailingslashit( (string) wp_parse_url( wp_upload_dir( null, false )['baseurl'], PHP_URL_PATH ) ) ),
       
   116 			'content'    => self::escape_pattern_string( trailingslashit( (string) wp_parse_url( content_url(), PHP_URL_PATH ) ) ),
       
   117 			'plugins'    => self::escape_pattern_string( trailingslashit( (string) wp_parse_url( plugins_url(), PHP_URL_PATH ) ) ),
       
   118 			'template'   => self::escape_pattern_string( trailingslashit( (string) wp_parse_url( get_stylesheet_directory_uri(), PHP_URL_PATH ) ) ),
       
   119 			'stylesheet' => self::escape_pattern_string( trailingslashit( (string) wp_parse_url( get_template_directory_uri(), PHP_URL_PATH ) ) ),
       
   120 		);
       
   121 	}
       
   122 
       
   123 	/**
       
   124 	 * Escapes a string for use in a URL pattern component.
       
   125 	 *
       
   126 	 * @since 6.8.0
       
   127 	 * @see https://urlpattern.spec.whatwg.org/#escape-a-pattern-string
       
   128 	 *
       
   129 	 * @param string $str String to be escaped.
       
   130 	 * @return string String with backslashes added where required.
       
   131 	 */
       
   132 	private static function escape_pattern_string( string $str ): string {
       
   133 		return addcslashes( $str, '+*?:{}()\\' );
       
   134 	}
       
   135 }