Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5959993
ParserFunctions.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
27 KB
Referenced Files
None
Subscribers
None
ParserFunctions.php
View Options
<?php
namespace
MediaWiki\Extension\ParserFunctions
;
use
DateTime
;
use
DateTimeZone
;
use
Exception
;
use
MediaWiki\Cache\LinkCache
;
use
MediaWiki\Config\Config
;
use
MediaWiki\FileRepo\RepoGroup
;
use
MediaWiki\HookContainer\HookContainer
;
use
MediaWiki\Languages\LanguageConverterFactory
;
use
MediaWiki\Languages\LanguageFactory
;
use
MediaWiki\Languages\LanguageNameUtils
;
use
MediaWiki\Parser\Parser
;
use
MediaWiki\Parser\PPFrame
;
use
MediaWiki\Parser\PPNode
;
use
MediaWiki\Parser\Sanitizer
;
use
MediaWiki\SpecialPage\SpecialPageFactory
;
use
MediaWiki\Title\Title
;
use
MediaWiki\Utils\MWTimestamp
;
use
StringUtils
;
use
Wikimedia\RequestTimeout\TimeoutException
;
/**
* Parser function handlers
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions
*/
class
ParserFunctions
{
private
static
?
ExprParser
$mExprParser
=
null
;
/** @var array[][][][] */
private
static
array
$mTimeCache
=
[];
private
static
int
$mTimeChars
=
0
;
/** ~10 seconds */
private
const
MAX_TIME_CHARS
=
6000
;
private
Config
$config
;
private
HookContainer
$hookContainer
;
private
LanguageConverterFactory
$languageConverterFactory
;
private
LanguageFactory
$languageFactory
;
private
LanguageNameUtils
$languageNameUtils
;
private
LinkCache
$linkCache
;
private
RepoGroup
$repoGroup
;
private
SpecialPageFactory
$specialPageFactory
;
public
function
__construct
(
Config
$config
,
HookContainer
$hookContainer
,
LanguageConverterFactory
$languageConverterFactory
,
LanguageFactory
$languageFactory
,
LanguageNameUtils
$languageNameUtils
,
LinkCache
$linkCache
,
RepoGroup
$repoGroup
,
SpecialPageFactory
$specialPageFactory
)
{
$this
->
config
=
$config
;
$this
->
hookContainer
=
$hookContainer
;
$this
->
languageConverterFactory
=
$languageConverterFactory
;
$this
->
languageFactory
=
$languageFactory
;
$this
->
languageNameUtils
=
$languageNameUtils
;
$this
->
linkCache
=
$linkCache
;
$this
->
repoGroup
=
$repoGroup
;
$this
->
specialPageFactory
=
$specialPageFactory
;
}
private
static
function
&
getExprParser
():
ExprParser
{
if
(
self
::
$mExprParser
===
null
)
{
self
::
$mExprParser
=
new
ExprParser
;
}
return
self
::
$mExprParser
;
}
/**
* {{#expr: expression }}
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##expr
*
* @param Parser $parser
* @param string $expr
* @return string
*/
public
function
expr
(
Parser
$parser
,
$expr
=
''
)
{
try
{
return
self
::
getExprParser
()->
doExpression
(
$expr
);
}
catch
(
ExprError
$e
)
{
return
'<strong class="error">'
.
htmlspecialchars
(
$e
->
getUserFriendlyMessage
()
)
.
'</strong>'
;
}
}
/**
* {{#ifexpr: expression | value if true | value if false }}
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##ifexpr
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
ifexpr
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
$expr
=
isset
(
$args
[
0
]
)
?
trim
(
$frame
->
expand
(
$args
[
0
]
)
)
:
''
;
$then
=
$args
[
1
]
??
''
;
$else
=
$args
[
2
]
??
''
;
try
{
$result
=
self
::
getExprParser
()->
doExpression
(
$expr
);
if
(
is_numeric
(
$result
)
)
{
$result
=
(
float
)
$result
;
}
$result
=
$result
?
$then
:
$else
;
}
catch
(
ExprError
$e
)
{
return
'<strong class="error">'
.
htmlspecialchars
(
$e
->
getUserFriendlyMessage
()
)
.
'</strong>'
;
}
if
(
is_object
(
$result
)
)
{
$result
=
trim
(
$frame
->
expand
(
$result
)
);
}
return
$result
;
}
/**
* {{#if: test string | value if test string is not empty | value if test string is empty }}
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##if
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
if
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
$test
=
isset
(
$args
[
0
]
)
?
trim
(
$frame
->
expand
(
$args
[
0
]
)
)
:
''
;
if
(
$test
!==
''
)
{
return
isset
(
$args
[
1
]
)
?
trim
(
$frame
->
expand
(
$args
[
1
]
)
)
:
''
;
}
else
{
return
isset
(
$args
[
2
]
)
?
trim
(
$frame
->
expand
(
$args
[
2
]
)
)
:
''
;
}
}
/**
* {{#ifeq: string 1 | string 2 | value if identical | value if different }}
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##ifeq
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
ifeq
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
$left
=
isset
(
$args
[
0
]
)
?
self
::
decodeTrimExpand
(
$args
[
0
],
$frame
)
:
''
;
$right
=
isset
(
$args
[
1
]
)
?
self
::
decodeTrimExpand
(
$args
[
1
],
$frame
)
:
''
;
// Strict compare is not possible here. 01 should equal 1 for example.
/** @noinspection TypeUnsafeComparisonInspection */
if
(
$left
==
$right
)
{
return
isset
(
$args
[
2
]
)
?
trim
(
$frame
->
expand
(
$args
[
2
]
)
)
:
''
;
}
else
{
return
isset
(
$args
[
3
]
)
?
trim
(
$frame
->
expand
(
$args
[
3
]
)
)
:
''
;
}
}
/**
* {{#iferror: test string | value if error | value if no error }}
*
* Error is when the input string contains an HTML object with class="error", as
* generated by other parser functions such as #expr, #time and #rel2abs.
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##iferror
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
iferror
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
$test
=
isset
(
$args
[
0
]
)
?
trim
(
$frame
->
expand
(
$args
[
0
]
)
)
:
''
;
$then
=
$args
[
1
]
??
false
;
$else
=
$args
[
2
]
??
false
;
if
(
preg_match
(
'/<(?:strong|span|p|div)
\s
(?:[^
\s
>]*
\s
+)*?class="(?:[^"
\s
>]*
\s
+)*?error(?:
\s
[^">]*)?"/'
,
$test
)
)
{
$result
=
$then
;
}
elseif
(
$else
===
false
)
{
$result
=
$test
;
}
else
{
$result
=
$else
;
}
if
(
$result
===
false
)
{
return
''
;
}
return
trim
(
$frame
->
expand
(
$result
)
);
}
/**
* {{#switch: comparison string
* | case = result
* | case = result
* | ...
* | default result
* }}
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##switch
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
switch
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
if
(
count
(
$args
)
===
0
)
{
return
''
;
}
$primary
=
self
::
decodeTrimExpand
(
array_shift
(
$args
),
$frame
);
$found
=
$defaultFound
=
false
;
$default
=
null
;
$lastItemHadNoEquals
=
false
;
$lastItem
=
''
;
$mwDefault
=
$parser
->
getMagicWordFactory
()->
get
(
'default'
);
foreach
(
$args
as
$arg
)
{
$bits
=
$arg
->
splitArg
();
$nameNode
=
$bits
[
'name'
];
$index
=
$bits
[
'index'
];
$valueNode
=
$bits
[
'value'
];
if
(
$index
===
''
)
{
# Found "="
$lastItemHadNoEquals
=
false
;
if
(
$found
)
{
# Multiple input match
return
trim
(
$frame
->
expand
(
$valueNode
)
);
}
$test
=
self
::
decodeTrimExpand
(
$nameNode
,
$frame
);
/** @noinspection TypeUnsafeComparisonInspection */
if
(
$test
==
$primary
)
{
# Found a match, return now
return
trim
(
$frame
->
expand
(
$valueNode
)
);
}
if
(
$defaultFound
||
$mwDefault
->
matchStartToEnd
(
$test
)
)
{
$default
=
$valueNode
;
$defaultFound
=
false
;
}
# else wrong case, continue
}
else
{
# Multiple input, single output
# If the value matches, set a flag and continue
$lastItemHadNoEquals
=
true
;
// $lastItem is an "out" variable
$decodedTest
=
self
::
decodeTrimExpand
(
$valueNode
,
$frame
,
$lastItem
);
/** @noinspection TypeUnsafeComparisonInspection */
if
(
$decodedTest
==
$primary
)
{
$found
=
true
;
}
elseif
(
$mwDefault
->
matchStartToEnd
(
$decodedTest
)
)
{
$defaultFound
=
true
;
}
}
}
# Default case
# Check if the last item had no = sign, thus specifying the default case
if
(
$lastItemHadNoEquals
)
{
return
$lastItem
;
}
if
(
$default
===
null
)
{
return
''
;
}
return
trim
(
$frame
->
expand
(
$default
)
);
}
/**
* {{#rel2abs: path }} or {{#rel2abs: path | base path }}
*
* Returns the absolute path to a subpage, relative to the current article
* title. Treats titles as slash-separated paths.
*
* Following subpage link syntax instead of standard path syntax, an
* initial slash is treated as a relative path, and vice versa.
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##rel2abs
*
* @param Parser $parser
* @param string $to
* @param string $from
*
* @return string
*/
public
function
rel2abs
(
Parser
$parser
,
$to
=
''
,
$from
=
''
)
{
$from
=
trim
(
$from
);
if
(
$from
===
''
)
{
$from
=
$parser
->
getTitle
()->
getPrefixedText
();
}
$to
=
rtrim
(
$to
,
' /'
);
// if we have an empty path, or just one containing a dot
if
(
$to
===
''
||
$to
===
'.'
)
{
return
$from
;
}
// if the path isn't relative
if
(
substr
(
$to
,
0
,
1
)
!==
'/'
&&
substr
(
$to
,
0
,
2
)
!==
'./'
&&
substr
(
$to
,
0
,
3
)
!==
'../'
&&
$to
!==
'..'
)
{
$from
=
''
;
}
// Make a long path, containing both, enclose it in /.../
$fullPath
=
'/'
.
$from
.
'/'
.
$to
.
'/'
;
// remove redundant current path dots
$fullPath
=
preg_replace
(
'!/(
\.
/)+!'
,
'/'
,
$fullPath
);
// remove double slashes
$fullPath
=
preg_replace
(
'!/{2,}!'
,
'/'
,
$fullPath
);
// remove the enclosing slashes now
$fullPath
=
trim
(
$fullPath
,
'/'
);
$exploded
=
explode
(
'/'
,
$fullPath
);
$newExploded
=
[];
foreach
(
$exploded
as
$current
)
{
// removing one level
if
(
$current
===
'..'
)
{
if
(
!
count
(
$newExploded
)
)
{
// attempted to access a node above root node
$msg
=
$parser
->
msg
(
'pfunc_rel2abs_invalid_depth'
,
$fullPath
)
->
escaped
();
return
'<strong class="error">'
.
$msg
.
'</strong>'
;
}
// remove last level from the stack
array_pop
(
$newExploded
);
}
else
{
// add the current level to the stack
$newExploded
[]
=
$current
;
}
}
// we can now join it again
return
implode
(
'/'
,
$newExploded
);
}
private
function
ifexistInternal
(
Parser
$parser
,
string
$titletext
):
bool
{
$title
=
Title
::
newFromText
(
$titletext
);
$this
->
languageConverterFactory
->
getLanguageConverter
(
$parser
->
getContentLanguage
()
)
->
findVariantLink
(
$titletext
,
$title
,
true
);
if
(
!
$title
)
{
return
false
;
}
if
(
$title
->
getNamespace
()
===
NS_MEDIA
)
{
/* If namespace is specified as NS_MEDIA, then we want to
* check the physical file, not the "description" page.
*/
if
(
!
$parser
->
incrementExpensiveFunctionCount
()
)
{
return
false
;
}
$file
=
$this
->
repoGroup
->
findFile
(
$title
);
if
(
!
$file
)
{
$parser
->
getOutput
()->
addImage
(
$title
->
getDBKey
(),
false
,
false
);
return
false
;
}
$parser
->
getOutput
()->
addImage
(
$file
->
getName
(),
$file
->
getTimestamp
(),
$file
->
getSha1
()
);
return
$file
->
exists
();
}
if
(
$title
->
isSpecialPage
()
)
{
/* Don't bother with the count for special pages,
* since their existence can be checked without
* accessing the database.
*/
return
$this
->
specialPageFactory
->
exists
(
$title
->
getDBkey
()
);
}
if
(
$title
->
isExternal
()
)
{
/* Can't check the existence of pages on other sites,
* so just return false. Makes a sort of sense, since
* they don't exist _locally_.
*/
return
false
;
}
$pdbk
=
$title
->
getPrefixedDBkey
();
$id
=
$this
->
linkCache
->
getGoodLinkID
(
$pdbk
);
if
(
$id
!==
0
)
{
$parser
->
getOutput
()->
addLink
(
$title
,
$id
);
return
true
;
}
if
(
$this
->
linkCache
->
isBadLink
(
$pdbk
)
)
{
$parser
->
getOutput
()->
addLink
(
$title
,
0
);
return
false
;
}
if
(
!
$parser
->
incrementExpensiveFunctionCount
()
)
{
return
false
;
}
$id
=
$title
->
getArticleID
();
$parser
->
getOutput
()->
addLink
(
$title
,
$id
);
// bug 70495: don't just check whether the ID != 0
return
$title
->
exists
();
}
/**
* {{#ifexist: page title | value if exists | value if doesn't exist }}
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##ifexist
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
ifexist
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
$title
=
isset
(
$args
[
0
]
)
?
trim
(
$frame
->
expand
(
$args
[
0
]
)
)
:
''
;
$then
=
$args
[
1
]
??
null
;
$else
=
$args
[
2
]
??
null
;
$result
=
$this
->
ifexistInternal
(
$parser
,
$title
)
?
$then
:
$else
;
if
(
$result
===
null
)
{
return
''
;
}
return
trim
(
$frame
->
expand
(
$result
)
);
}
/**
* Used by time() and localTime()
*/
private
function
timeCommon
(
Parser
$parser
,
PPFrame
$frame
,
string
$format
,
string
$date
,
string
$language
,
bool
$local
):
string
{
$this
->
hookContainer
->
register
(
'ParserClearState'
,
static
function
()
{
self
::
$mTimeChars
=
0
;
}
);
if
(
$date
===
''
)
{
$cacheKey
=
$parser
->
getOptions
()->
getTimestamp
();
$timestamp
=
new
MWTimestamp
(
$cacheKey
);
$date
=
$timestamp
->
getTimestamp
(
TS_ISO_8601
);
$useTTL
=
true
;
}
else
{
$cacheKey
=
$date
;
$useTTL
=
false
;
}
if
(
isset
(
self
::
$mTimeCache
[
$format
][
$cacheKey
][
$language
][(
int
)
$local
]
)
)
{
$cachedVal
=
self
::
$mTimeCache
[
$format
][
$cacheKey
][
$language
][(
int
)
$local
];
if
(
$useTTL
&&
$cachedVal
[
1
]
!==
null
)
{
$frame
->
setTTL
(
$cachedVal
[
1
]
);
}
return
$cachedVal
[
0
];
}
# compute the timestamp string $ts
# PHP >= 5.2 can handle dates before 1970 or after 2038 using the DateTime object
$invalidTime
=
false
;
# the DateTime constructor must be used because it throws exceptions
# when errors occur, whereas date_create appears to just output a warning
# that can't really be detected from within the code
try
{
# Default input timezone is UTC.
$utc
=
new
DateTimeZone
(
'UTC'
);
# Correct for DateTime interpreting 'XXXX' as XX:XX o'clock
if
(
preg_match
(
'/^[0-9]{4}$/'
,
$date
)
)
{
$date
=
'00:00 '
.
$date
;
}
# Parse date
# UTC is a default input timezone.
$dateObject
=
new
DateTime
(
$date
,
$utc
);
# Set output timezone.
if
(
$local
)
{
$tz
=
new
DateTimeZone
(
$this
->
config
->
get
(
'Localtimezone'
)
??
date_default_timezone_get
()
);
}
else
{
$tz
=
$utc
;
}
$dateObject
->
setTimezone
(
$tz
);
# Generate timestamp
$ts
=
$dateObject
->
format
(
'YmdHis'
);
}
catch
(
TimeoutException
$ex
)
{
// Unfortunately DateTime throws a generic Exception, but we can't
// ignore an exception generated by the RequestTimeout library.
throw
$ex
;
}
catch
(
Exception
$ex
)
{
$invalidTime
=
true
;
}
$ttl
=
null
;
# format the timestamp and return the result
if
(
$invalidTime
)
{
$result
=
'<strong class="error">'
.
$parser
->
msg
(
'pfunc_time_error'
)->
escaped
()
.
'</strong>'
;
}
else
{
self
::
$mTimeChars
+=
strlen
(
$format
);
if
(
self
::
$mTimeChars
>
self
::
MAX_TIME_CHARS
)
{
return
'<strong class="error">'
.
$parser
->
msg
(
'pfunc_time_too_long'
)->
escaped
()
.
'</strong>'
;
}
// Language can't deal with BC years
if
(
$ts
<
0
)
{
return
'<strong class="error">'
.
$parser
->
msg
(
'pfunc_time_too_small'
)->
escaped
()
.
'</strong>'
;
}
// Language can't deal with years after 9999
if
(
$ts
>=
100000000000000
)
{
return
'<strong class="error">'
.
$parser
->
msg
(
'pfunc_time_too_big'
)->
escaped
()
.
'</strong>'
;
}
$langObject
=
$this
->
languageFactory
->
getLanguage
(
$this
->
normalizeLangCode
(
$parser
,
$language
)
);
$result
=
$langObject
->
sprintfDate
(
$format
,
$ts
,
$tz
,
$ttl
);
}
self
::
$mTimeCache
[
$format
][
$cacheKey
][
$language
][(
int
)
$local
]
=
[
$result
,
$ttl
];
if
(
$useTTL
&&
$ttl
!==
null
)
{
$frame
->
setTTL
(
$ttl
);
}
return
$result
;
}
/**
* Convert an input string to a known language code for time formatting
*/
private
function
normalizeLangCode
(
Parser
$parser
,
string
$langCode
):
string
{
if
(
$langCode
!==
''
&&
$this
->
languageNameUtils
->
isKnownLanguageTag
(
$langCode
)
)
{
return
$langCode
;
}
else
{
return
$parser
->
getTargetLanguage
()->
getCode
();
}
}
/**
* {{#time: format string }}
* {{#time: format string | date/time object }}
* {{#time: format string | date/time object | language code }}
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
time
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
$format
=
isset
(
$args
[
0
]
)
?
trim
(
$frame
->
expand
(
$args
[
0
]
)
)
:
''
;
$date
=
isset
(
$args
[
1
]
)
?
trim
(
$frame
->
expand
(
$args
[
1
]
)
)
:
''
;
$language
=
isset
(
$args
[
2
]
)
?
trim
(
$frame
->
expand
(
$args
[
2
]
)
)
:
''
;
$local
=
isset
(
$args
[
3
]
)
&&
trim
(
$frame
->
expand
(
$args
[
3
]
)
);
return
$this
->
timeCommon
(
$parser
,
$frame
,
$format
,
$date
,
$language
,
$local
);
}
/**
* {{#timel: ... }}
*
* Identical to {{#time: ... }}, except that it uses the local time of the wiki
* (as set in $wgLocaltimezone) when no date is given.
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##timel
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
localTime
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
$format
=
isset
(
$args
[
0
]
)
?
trim
(
$frame
->
expand
(
$args
[
0
]
)
)
:
''
;
$date
=
isset
(
$args
[
1
]
)
?
trim
(
$frame
->
expand
(
$args
[
1
]
)
)
:
''
;
$language
=
isset
(
$args
[
2
]
)
?
trim
(
$frame
->
expand
(
$args
[
2
]
)
)
:
''
;
return
$this
->
timeCommon
(
$parser
,
$frame
,
$format
,
$date
,
$language
,
true
);
}
/**
* Formatted time -- time with a symbolic rather than explicit format
*
* @param Parser $parser
* @param PPFrame $frame
* @param array $args
* @return string
*/
public
function
timef
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
return
$this
->
timefCommon
(
$parser
,
$frame
,
$args
,
false
);
}
/**
* Formatted time -- time with a symbolic rather than explicit format
* Using the local timezone of the wiki.
*
* @param Parser $parser
* @param PPFrame $frame
* @param array $args
* @return string
*/
public
function
timefl
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
return
$this
->
timefCommon
(
$parser
,
$frame
,
$args
,
true
);
}
/**
* Helper for timef and timefl
*/
private
function
timefCommon
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
,
bool
$local
):
string
{
$date
=
isset
(
$args
[
0
]
)
?
trim
(
$frame
->
expand
(
$args
[
0
]
)
)
:
''
;
$inputType
=
isset
(
$args
[
1
]
)
?
trim
(
$frame
->
expand
(
$args
[
1
]
)
)
:
''
;
if
(
$inputType
!==
''
)
{
$types
=
$parser
->
getMagicWordFactory
()->
newArray
(
[
'timef-time'
,
'timef-date'
,
'timef-both'
,
'timef-pretty'
]
);
$id
=
$types
->
matchStartToEnd
(
$inputType
);
if
(
$id
===
false
)
{
return
'<strong class="error">'
.
wfMessage
(
'pfunc_timef_bad_format'
)
.
'</strong>'
;
}
$type
=
str_replace
(
'timef-'
,
''
,
$id
);
}
else
{
$type
=
'both'
;
}
$langCode
=
isset
(
$args
[
2
]
)
?
trim
(
$frame
->
expand
(
$args
[
2
]
)
)
:
''
;
$langCode
=
$this
->
normalizeLangCode
(
$parser
,
$langCode
);
$lang
=
$this
->
languageFactory
->
getLanguage
(
$langCode
);
$format
=
$lang
->
getDateFormatString
(
$type
,
'default'
);
return
$this
->
timeCommon
(
$parser
,
$frame
,
$format
,
$date
,
$langCode
,
$local
);
}
/**
* Obtain a specified number of slash-separated parts of a title,
* e.g. {{#titleparts:Hello/World|1}} => "Hello"
*
* @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##titleparts
*
* @param Parser $parser Parent parser
* @param string $title Title to split
* @param string|int $parts Number of parts to keep
* @param string|int $offset Offset starting at 1
* @return string
*/
public
function
titleparts
(
Parser
$parser
,
$title
=
''
,
$parts
=
0
,
$offset
=
0
)
{
$parts
=
(
int
)
$parts
;
$offset
=
(
int
)
$offset
;
$ntitle
=
Title
::
newFromText
(
$title
);
if
(
!
$ntitle
)
{
return
$title
;
}
$bits
=
explode
(
'/'
,
$ntitle
->
getPrefixedText
(),
25
);
if
(
$offset
>
0
)
{
--
$offset
;
}
return
implode
(
'/'
,
array_slice
(
$bits
,
$offset
,
$parts
?:
null
)
);
}
/**
* Verifies parameter is less than max string length.
*/
private
function
checkLength
(
string
$text
):
bool
{
return
mb_strlen
(
$text
)
<
$this
->
config
->
get
(
'PFStringLengthLimit'
);
}
/**
* Generates error message. Called when string is too long.
*/
private
function
tooLongError
(
Parser
$parser
):
string
{
$msg
=
$parser
->
msg
(
'pfunc_string_too_long'
)
->
numParams
(
$this
->
config
->
get
(
'PFStringLengthLimit'
)
);
return
'<strong class="error">'
.
$msg
->
escaped
()
.
'</strong>'
;
}
/**
* {{#len:string}}
*
* Reports number of characters in string.
*
* @param Parser $parser
* @param string $inStr
* @return int
*/
public
function
runLen
(
Parser
$parser
,
$inStr
=
''
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
return
mb_strlen
(
$inStr
);
}
/**
* {{#pos: string | needle | offset}}
*
* Finds first occurrence of "needle" in "string" starting at "offset".
*
* Note: If the needle is an empty string, single space is used instead.
* Note: If the needle is not found, empty string is returned.
* @param Parser $parser
* @param string $inStr
* @param string $inNeedle
* @param string|int $inOffset
* @return int|string
*/
public
function
runPos
(
Parser
$parser
,
$inStr
=
''
,
$inNeedle
=
''
,
$inOffset
=
0
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
$inNeedle
=
$parser
->
killMarkers
(
(
string
)
$inNeedle
);
if
(
!
$this
->
checkLength
(
$inStr
)
||
!
$this
->
checkLength
(
$inNeedle
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
if
(
$inNeedle
===
''
)
{
$inNeedle
=
' '
;
}
$pos
=
mb_strpos
(
$inStr
,
$inNeedle
,
min
(
(
int
)
$inOffset
,
mb_strlen
(
$inStr
)
)
);
if
(
$pos
===
false
)
{
$pos
=
''
;
}
return
$pos
;
}
/**
* {{#rpos: string | needle}}
*
* Finds last occurrence of "needle" in "string".
*
* Note: If the needle is an empty string, single space is used instead.
* Note: If the needle is not found, -1 is returned.
* @param Parser $parser
* @param string $inStr
* @param string $inNeedle
* @return int|string
*/
public
function
runRPos
(
Parser
$parser
,
$inStr
=
''
,
$inNeedle
=
''
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
$inNeedle
=
$parser
->
killMarkers
(
(
string
)
$inNeedle
);
if
(
!
$this
->
checkLength
(
$inStr
)
||
!
$this
->
checkLength
(
$inNeedle
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
if
(
$inNeedle
===
''
)
{
$inNeedle
=
' '
;
}
$pos
=
mb_strrpos
(
$inStr
,
$inNeedle
);
if
(
$pos
===
false
)
{
$pos
=
-
1
;
}
return
$pos
;
}
/**
* {{#sub: string | start | length }}
*
* Returns substring of "string" starting at "start" and having
* "length" characters.
*
* Note: If length is zero, the rest of the input is returned.
* Note: A negative value for "start" operates from the end of the
* "string".
* Note: A negative value for "length" returns a string reduced in
* length by that amount.
*
* @param Parser $parser
* @param string $inStr
* @param string|int $inStart
* @param string|int $inLength
* @return string
*/
public
function
runSub
(
Parser
$parser
,
$inStr
=
''
,
$inStart
=
0
,
$inLength
=
0
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
if
(
!
$this
->
checkLength
(
$inStr
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
if
(
(
int
)
$inLength
===
0
)
{
$result
=
mb_substr
(
$inStr
,
(
int
)
$inStart
);
}
else
{
$result
=
mb_substr
(
$inStr
,
(
int
)
$inStart
,
(
int
)
$inLength
);
}
return
$result
;
}
/**
* {{#count: string | substr }}
*
* Returns number of occurrences of "substr" in "string".
*
* Note: If "substr" is empty, a single space is used.
*
* @param Parser $parser
* @param string $inStr
* @param string $inSubStr
* @return int|string
*/
public
function
runCount
(
Parser
$parser
,
$inStr
=
''
,
$inSubStr
=
''
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
$inSubStr
=
$parser
->
killMarkers
(
(
string
)
$inSubStr
);
if
(
!
$this
->
checkLength
(
$inStr
)
||
!
$this
->
checkLength
(
$inSubStr
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
if
(
$inSubStr
===
''
)
{
$inSubStr
=
' '
;
}
$result
=
mb_substr_count
(
$inStr
,
$inSubStr
);
return
$result
;
}
/**
* {{#replace:string | from | to | limit }}
*
* Replaces each occurrence of "from" in "string" with "to".
* At most "limit" replacements are performed.
*
* Note: Armored against replacements that would generate huge strings.
* Note: If "from" is an empty string, single space is used instead.
*
* @param Parser $parser
* @param string $inStr
* @param string $inReplaceFrom
* @param string $inReplaceTo
* @param string|int $inLimit
* @return string
*/
public
function
runReplace
(
Parser
$parser
,
$inStr
=
''
,
$inReplaceFrom
=
''
,
$inReplaceTo
=
''
,
$inLimit
=
-
1
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
$inReplaceFrom
=
$parser
->
killMarkers
(
(
string
)
$inReplaceFrom
);
$inReplaceTo
=
$parser
->
killMarkers
(
(
string
)
$inReplaceTo
);
if
(
!
$this
->
checkLength
(
$inStr
)
||
!
$this
->
checkLength
(
$inReplaceFrom
)
||
!
$this
->
checkLength
(
$inReplaceTo
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
if
(
$inReplaceFrom
===
''
)
{
$inReplaceFrom
=
' '
;
}
// Precompute limit to avoid generating enormous string:
$diff
=
mb_strlen
(
$inReplaceTo
)
-
mb_strlen
(
$inReplaceFrom
);
if
(
$diff
>
0
)
{
$limit
=
(
int
)(
(
$this
->
config
->
get
(
'PFStringLengthLimit'
)
-
mb_strlen
(
$inStr
)
)
/
$diff
)
+
1
;
}
else
{
$limit
=
-
1
;
}
$inLimit
=
(
int
)
$inLimit
;
if
(
$inLimit
>=
0
)
{
if
(
$limit
>
$inLimit
||
$limit
==
-
1
)
{
$limit
=
$inLimit
;
}
}
// Use regex to allow limit and handle UTF-8 correctly.
$inReplaceFrom
=
preg_quote
(
$inReplaceFrom
,
'/'
);
$inReplaceTo
=
StringUtils
::
escapeRegexReplacement
(
$inReplaceTo
);
$result
=
preg_replace
(
'/'
.
$inReplaceFrom
.
'/u'
,
$inReplaceTo
,
$inStr
,
$limit
);
if
(
!
$this
->
checkLength
(
$result
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
return
$result
;
}
/**
* {{#explode:string | delimiter | position | limit}}
*
* Breaks "string" into chunks separated by "delimiter" and returns the
* chunk identified by "position".
*
* Note: Negative position can be used to specify tokens from the end.
* Note: If the divider is an empty string, single space is used instead.
* Note: Empty string is returned if there are not enough exploded chunks.
*
* @param Parser $parser
* @param string $inStr
* @param string $inDiv
* @param string|int $inPos
* @param string|null $inLim
* @return string
*/
public
function
runExplode
(
Parser
$parser
,
$inStr
=
''
,
$inDiv
=
''
,
$inPos
=
0
,
$inLim
=
null
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
$inDiv
=
$parser
->
killMarkers
(
(
string
)
$inDiv
);
if
(
$inDiv
===
''
)
{
$inDiv
=
' '
;
}
if
(
!
$this
->
checkLength
(
$inStr
)
||
!
$this
->
checkLength
(
$inDiv
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
$inDiv
=
preg_quote
(
$inDiv
,
'/'
);
$matches
=
preg_split
(
'/'
.
$inDiv
.
'/u'
,
$inStr
,
(
int
)
$inLim
);
if
(
$inPos
>=
0
&&
isset
(
$matches
[
$inPos
]
)
)
{
$result
=
$matches
[
$inPos
];
}
elseif
(
$inPos
<
0
&&
isset
(
$matches
[
count
(
$matches
)
+
(
int
)
$inPos
]
)
)
{
$result
=
$matches
[
count
(
$matches
)
+
(
int
)
$inPos
];
}
else
{
$result
=
''
;
}
return
$result
;
}
/**
* {{#urldecode:string}}
*
* Decodes URL-encoded (like%20that) strings.
*
* @param Parser $parser
* @param string $inStr
* @return string
*/
public
function
runUrlDecode
(
Parser
$parser
,
$inStr
=
''
)
{
$inStr
=
$parser
->
killMarkers
(
(
string
)
$inStr
);
if
(
!
$this
->
checkLength
(
$inStr
)
)
{
return
$this
->
tooLongError
(
$parser
);
}
return
urldecode
(
$inStr
);
}
/**
* Take a PPNode (-ish thing), expand it, remove entities, and trim.
*
* For use when doing string comparisions, where user expects entities
* to be equal for what they stand for (e.g. comparisions with {{PAGENAME}})
*
* @param PPNode|string $obj Thing to expand
* @param PPFrame $frame
* @param string &$trimExpanded @phan-output-reference Expanded and trimmed version of PPNode,
* but with char refs intact
* @return string The trimmed, expanded and entity reference decoded version of the PPNode
*/
private
static
function
decodeTrimExpand
(
$obj
,
PPFrame
$frame
,
string
&
$trimExpanded
=
''
):
string
{
$expanded
=
$frame
->
expand
(
$obj
);
$trimExpanded
=
trim
(
$expanded
);
return
trim
(
Sanitizer
::
decodeCharReferences
(
$expanded
)
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Oct 3, 06:59 (2 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
5b/ac/2ad0755ce9fc04c741ba93f18be0
Default Alt Text
ParserFunctions.php (27 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment