70private $patterns = [];
82protectedfunctiondoAdd(
$path, $params, $options, $key =
null ) {
83// Make sure all paths start with a / 84if (
$path[0] !==
'/' ) {
88if ( !isset( $options[
'strict'] ) || !$options[
'strict'] ) {
89// Unless this is a strict path make sure that the path has a $1 90if ( !str_contains(
$path,
'$1' ) ) {
91if (
$path[-1] !==
'/' ) {
98// If 'title' is not specified and our path pattern contains a $1 99// Add a default 'title' => '$1' rule to the parameters. 100if ( !isset( $params[
'title'] ) && str_contains(
$path,
'$1' ) ) {
101 $params[
'title'] =
'$1';
103// If the user explicitly marked 'title' as false then omit it from the matches 104if ( isset( $params[
'title'] ) && $params[
'title'] ===
false ) {
105 unset( $params[
'title'] );
108// Loop over our parameters and convert basic key => string 109// patterns into fully descriptive array form 110foreach ( $params as $paramName => $paramData ) {
111if ( is_string( $paramData ) ) {
112if ( preg_match(
'/\$(\d+|key)/u', $paramData ) ) {
113 $paramArrKey =
'pattern';
115// If there's no replacement use a value instead 116// of a pattern for a little more efficiency 117 $paramArrKey =
'value';
119 $params[$paramName] = [
120 $paramArrKey => $paramData
125// Loop over our options and convert any single value $# restrictions 126// into an array so we only have to do in_array tests. 127foreach ( $options as $optionName => $optionData ) {
128if ( preg_match(
'/^\$\d+$/u', $optionName ) && !is_array( $optionData ) ) {
129 $options[$optionName] = [ $optionData ];
136'options' => $options,
140 $this->patterns[] = $pattern;
150publicfunctionadd(
$path, $params = [], $options = [] ) {
151if ( is_array(
$path ) ) {
152foreach (
$path as $key => $onePath ) {
153 $this->
doAdd( $onePath, $params, $options, $key );
168if (
$path && !preg_match(
'/^(https?:\/\/|\/)/',
$path ) ) {
169// T48998: Bail out early if path is non-absolute 171"If you use a relative URL for \$$varName, it must start " .
172'with a slash (<code>/</code>).<br><br>See ' .
173"<a href=\"https://www.mediawiki.org/wiki/Manual:\$$varName\">" .
174"https://www.mediawiki.org/wiki/Manual:\$$varName</a>." 187 $options[
'strict'] =
true;
188 $this->
add(
$path, $params, $options );
197foreach ( $this->patterns as $key => $pattern ) {
198 $weights[$key] = $pattern->weight;
200 array_multisort( $weights, SORT_DESC, SORT_NUMERIC, $this->patterns );
208 # Start with a weight of 0 211// Explode the path to work with 212$path = explode(
'/', $pattern->path );
214 # For each level of the path 215foreach (
$path as $piece ) {
216if ( preg_match(
'/^\$(\d+|key)$/u', $piece ) ) {
217 # For a piece that is only a $1 variable add 1 points of weight 219 } elseif ( preg_match(
'/\$(\d+|key)/u', $piece ) ) {
220 # For a piece that simply contains a $1 variable add 2 points of weight 223 # For a solid piece add a full 3 points of weight 228foreach ( $pattern->options as $key => $option ) {
229if ( preg_match(
'/^\$\d+$/u', $key ) ) {
230 # Add 0.5 for restrictions to values 231 # This way given two separate "/$2/$1" patterns the 232 # one with a limited set of $2 values will dominate 233 # the one that'll match more loosely 248// Make sure our patterns are sorted by weight so the most specific 249// matches are tested first 254// Try with the normalized path (T100782) 260// We know the difference between null (no matches) and 261// [] (a match with no data) but our WebRequest caller 262// expects [] even when we have no matches so return 263// a [] when we have null 276foreach ( $this->patterns as $pattern ) {
291// Convert the path pattern into a regexp we can match with 292 $regexp = preg_quote( $pattern->path,
'#' );
294 $regexp = preg_replace(
'#\\\\\$1#u',
'(?P<par1>.*)', $regexp );
295// .+ for the rest of the parameter numbers 296 $regexp = preg_replace(
'#\\\\\$(\d+)#u',
'(?P<par$1>.+?)', $regexp );
297 $regexp =
"#^{$regexp}$#";
302// Try to match the path we were asked to parse with our regexp 303if ( preg_match( $regexp,
$path, $m ) ) {
304// Ensure that any $# restriction we have set in our {$option}s 305// matches properly here. 306foreach ( $pattern->options as $key => $option ) {
307if ( preg_match(
'/^\$\d+$/u', $key ) ) {
308 $n = intval( substr( $key, 1 ) );
309 $value = rawurldecode( $m[
"par{$n}"] );
310if ( !in_array( $value, $option ) ) {
311// If any restriction does not match return null 312// to signify that this rule did not match. 318// Give our $data array a copy of every $# that was matched 319foreach ( $m as $matchKey => $matchValue ) {
320if ( preg_match(
'/^par\d+$/u', $matchKey ) ) {
321 $n = intval( substr( $matchKey, 3 ) );
322 $data[
'$' . $n] = rawurldecode( $matchValue );
325// If present give our $data array a $key as well 326if ( isset( $pattern->key ) ) {
327 $data[
'$key'] = $pattern->key;
330// Go through our parameters for this match and add data to our matches and data arrays 331foreach ( $pattern->params as $paramName => $paramData ) {
333// Differentiate data: from normal parameters and keep the correct 334// array key around (ie: foo for data:foo) 335if ( preg_match(
'/^data:/u', $paramName ) ) {
337 $key = substr( $paramName, 5 );
343if ( isset( $paramData[
'value'] ) ) {
344// For basic values just set the raw data as the value 345 $value = $paramData[
'value'];
346 } elseif ( isset( $paramData[
'pattern'] ) ) {
347// For patterns we have to make value replacements on the string 349 $paramData[
'pattern'] );
350if ( $value ===
false ) {
351// Pattern required data that wasn't available, abort 356// Send things that start with data: to $data, the rest to $matches 358 $data[$key] = $value;
364// If this match includes a callback, execute it 365if ( isset( $pattern->options[
'callback'] ) ) {
366 $pattern->options[
'callback'](
$matches, $data );
369// Our regexp didn't match, return null to signify no match. 372// Fall through, everything went ok, return our matches array 387 $replacer =
staticfunction ( $m ) use ( $pathMatches, $key, &$error ) {
388if ( $m[1] ==
"key" ) {
398if ( !isset( $pathMatches[
"par$d"] ) ) {
404return rawurldecode( $pathMatches[
"par$d"] );
408 $value = preg_replace_callback(
'/\$(\d+|key)/u', $replacer, $value );
423if ( !$actionPaths ) {
426// Processing of urls for this feature requires that 'view' is set. 427// By default, set it to the pretty article path. 428if ( !isset( $actionPaths[
'view'] ) ) {
429 $actionPaths[
'view'] = $articlePath;
Abort the web request with a custom HTML string that will represent the entire response.
MediaWiki\Request\PathRouter class.
static getActionPaths(array $actionPaths, $articlePath)
add( $path, $params=[], $options=[])
Add a new path pattern to the path router.
static expandParamValue( $pathMatches, $key, $value)
Replace $key etc.
static extractTitle( $path, $pattern)
validateRoute( $path, $varName)
sortByWeight()
Protected helper to re-sort our patterns so that the most specific (most heavily weighted) patterns a...
doAdd( $path, $params, $options, $key=null)
Protected helper to do the actual bulk work of adding a single pattern.
static makeWeight( $pattern)
internalParse( $path)
Match a path against each defined pattern.
addStrict( $path, $params=[], $options=[])
Add a new path pattern to the path router with the strict option on.
parse( $path)
Parse a path and return the query matches for the path.
A service to expand, parse, and otherwise manipulate URLs.