PluginProbe
Redirection / 5.0.1
Redirection v5.0.1
5.10.0 5.9.0 5.8.1 5.8.0 3.7.2 3.7.3 4.0 4.0.1 4.1 4.1.1 4.2 4.2.1 4.2.2 4.2.3 4.3 4.3.1 4.3.2 4.3.3 4.4 4.4.1 4.4.2 4.5 4.5.1 4.6.2 4.7.1 All 130 releases
redirection / models / url / url-query.php

url-query.php in Redirection 5.0.1, at models/url/url-query.php

304 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Query parameter martching
5 */
6 class Red_Url_Query {
7 /**
8 * @type Integer
9 */
10 const RECURSION_LIMIT = 10;
11
12 /**
13 * Query parameters
14 *
15 * @var array
16 */
17 private $query = [];
18
19 /**
20 * Is this an exact match?
21 *
22 * @var boolean|string
23 */
24 private $match_exact = false;
25
26 /**
27 * Constructor
28 *
29 * @param String $url URL.
30 * @param Red_Source_Flags $flags URL flags.
31 */
32 public function __construct( $url, $flags ) {
33 if ( $flags->is_ignore_case() ) {
34 $url = Red_Url_Path::to_lower( $url );
35 }
36
37 $this->query = $this->get_url_query( $url );
38 }
39
40 /**
41 * Does this object match the URL?
42 *
43 * @param String $url URL to match.
44 * @param Red_Source_Flags $flags Source flags.
45 * @return boolean
46 */
47 public function is_match( $url, Red_Source_Flags $flags ) {
48 if ( $flags->is_ignore_case() ) {
49 $url = Red_Url_Path::to_lower( $url );
50 }
51
52 // If we can't parse the query params then match the params exactly
53 if ( $this->match_exact !== false ) {
54 return $this->get_query_after( $url ) === $this->match_exact;
55 }
56
57 $target = $this->get_url_query( $url );
58
59 // All params in the source have to exist in the request, but in any order
60 $matched = $this->get_query_same( $this->query, $target );
61
62 if ( count( $matched ) !== count( $this->query ) ) {
63 // Source params arent matched exactly
64 return false;
65 };
66
67 // Get list of whatever is left over
68 $query_diff = $this->get_query_diff( $this->query, $target );
69 $query_diff = array_merge( $query_diff, $this->get_query_diff( $target, $this->query ) );
70
71 if ( $flags->is_query_ignore() || $flags->is_query_pass() ) {
72 return true; // This ignores all other query params
73 }
74
75 // In an exact match there shouldn't be any more params
76 return count( $query_diff ) === 0;
77 }
78
79 /**
80 * Pass query params from one URL to another URL, ignoring any params that already exist on the target.
81 *
82 * @param string $target_url The target URL to add params to.
83 * @param string $requested_url The source URL to pass params from.
84 * @param Red_Source_Flags $flags Any URL flags.
85 * @return string URL, modified or not.
86 */
87 public static function add_to_target( $target_url, $requested_url, Red_Source_Flags $flags ) {
88 if ( $flags->is_query_pass() && $target_url ) {
89 $source_query = new Red_Url_Query( $target_url, $flags );
90 $request_query = new Red_Url_Query( $requested_url, $flags );
91
92 // Now add any remaining params
93 $query_diff = $source_query->get_query_diff( $source_query->query, $request_query->query );
94 $request_diff = $request_query->get_query_diff( $request_query->query, $source_query->query );
95
96 foreach ( $request_diff as $key => $value ) {
97 $query_diff[ $key ] = $value;
98 }
99
100 // Remove any params from $source that are present in $request - we dont allow
101 // predefined params to be overridden
102 foreach ( array_keys( $query_diff ) as $key ) {
103 if ( isset( $source_query->query[ $key ] ) ) {
104 unset( $query_diff[ $key ] );
105 }
106 }
107
108 return self::build_url( $target_url, $query_diff );
109 }
110
111 return $target_url;
112 }
113
114 /**
115 * Build a URL from a base and query parameters
116 *
117 * @param String $url Base URL.
118 * @param Array $query Query parameters.
119 * @return String
120 */
121 public static function build_url( $url, $query ) {
122 $query = http_build_query( $query );
123 $query = preg_replace( '@%5B\d*%5D@', '[]', $query ); // Make these look like []
124
125 if ( $query ) {
126 // Get any fragment
127 $target_fragment = wp_parse_url( $url, PHP_URL_FRAGMENT );
128
129 // If we have a fragment we need to ensure it comes after the query parameters, not before
130 if ( $target_fragment ) {
131 // Remove fragment
132 $url = str_replace( '#' . $target_fragment, '', $url );
133
134 // Add to the end of the query
135 $query .= '#' . $target_fragment;
136 }
137
138 return $url . ( strpos( $url, '?' ) === false ? '?' : '&' ) . $query;
139 }
140
141 return $url;
142 }
143
144 /**
145 * Get a URL with the given base and query parameters from this Url_Query
146 *
147 * @param String $url Base URL.
148 * @return String
149 */
150 public function get_url_with_query( $url ) {
151 return self::build_url( $url, $this->query );
152 }
153
154 /**
155 * Get the query parameters
156 *
157 * @return Array
158 */
159 public function get() {
160 return $this->query;
161 }
162
163 /**
164 * Does the URL and the query params contain no parameters?
165 *
166 * @param String $url URL.
167 * @param Array $params Query params.
168 * @return boolean
169 */
170 private function is_exact_match( $url, $params ) {
171 // No parsed query params but we have query params on the URL - some parsing error with wp_parse_str
172 if ( count( $params ) === 0 && $this->has_query_params( $url ) ) {
173 return true;
174 }
175
176 return false;
177 }
178
179 /**
180 * Get query parameters from a URL
181 *
182 * @param String $url URL.
183 * @return array
184 */
185 private function get_url_query( $url ) {
186 $params = [];
187 $query = $this->get_query_after( $url );
188
189 wp_parse_str( $query ? $query : '', $params );
190
191 if ( $this->is_exact_match( $url, $params ) ) {
192 $this->match_exact = $query;
193 }
194
195 return $params;
196 }
197
198 /**
199 * Does the URL contain query parameters?
200 *
201 * @param String $url URL.
202 * @return boolean
203 */
204 public function has_query_params( $url ) {
205 $qpos = strpos( $url, '?' );
206
207 if ( $qpos === false ) {
208 return false;
209 }
210
211 return true;
212 }
213
214 /**
215 * Get parameters after the ?
216 *
217 * @param String $url URL.
218 * @return String
219 */
220 public function get_query_after( $url ) {
221 $qpos = strpos( $url, '?' );
222 $qrpos = strpos( $url, '\\?' );
223
224 // No ? anywhere - no query
225 if ( $qpos === false ) {
226 return '';
227 }
228
229 // Found an escaped ? and it comes before the non-escaped ?
230 if ( $qrpos !== false && $qrpos < $qpos ) {
231 return substr( $url, $qrpos + 2 );
232 }
233
234 // Standard query param
235 return substr( $url, $qpos + 1 );
236 }
237
238 /**
239 * Get query parameters that are the same in both query arrays
240 *
241 * @param array $source_query Source query params.
242 * @param array $target_query Target query params.
243 * @param integer $depth Current recursion depth.
244 * @return array
245 */
246 public function get_query_same( array $source_query, array $target_query, $depth = 0 ) {
247 if ( $depth > self::RECURSION_LIMIT ) {
248 return [];
249 }
250
251 $same = [];
252 foreach ( $source_query as $key => $value ) {
253 if ( isset( $target_query[ $key ] ) ) {
254 $add = false;
255
256 if ( is_array( $value ) && is_array( $target_query[ $key ] ) ) {
257 $add = $this->get_query_same( $source_query[ $key ], $target_query[ $key ], $depth + 1 );
258
259 if ( count( $add ) !== count( $source_query[ $key ] ) ) {
260 $add = false;
261 }
262 } elseif ( is_string( $value ) && is_string( $target_query[ $key ] ) ) {
263 $add = $value === $target_query[ $key ] ? $value : false;
264 }
265
266 if ( ! empty( $add ) || is_numeric( $add ) || $add === '' ) {
267 $same[ $key ] = $add;
268 }
269 }
270 }
271
272 return $same;
273 }
274
275 /**
276 * Get the difference in query parameters
277 *
278 * @param array $source_query Source query params.
279 * @param array $target_query Target query params.
280 * @param integer $depth Current recursion depth.
281 * @return array
282 */
283 public function get_query_diff( array $source_query, array $target_query, $depth = 0 ) {
284 if ( $depth > self::RECURSION_LIMIT ) {
285 return [];
286 }
287
288 $diff = [];
289 foreach ( $source_query as $key => $value ) {
290 if ( isset( $target_query[ $key ] ) && is_array( $value ) && is_array( $target_query[ $key ] ) ) {
291 $add = $this->get_query_diff( $source_query[ $key ], $target_query[ $key ], $depth + 1 );
292
293 if ( ! empty( $add ) ) {
294 $diff[ $key ] = $add;
295 }
296 } elseif ( ! isset( $target_query[ $key ] ) || ! is_string( $value ) || ! is_string( $target_query[ $key ] ) || $target_query[ $key ] !== $source_query[ $key ] ) {
297 $diff[ $key ] = $value;
298 }
299 }
300
301 return $diff;
302 }
303 }
304