|
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 } |