Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4048328
ReferenceListFormatter.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
ReferenceListFormatter.php
View Options
<?php
namespace
Cite
;
use
MediaWiki\Html\Html
;
use
MediaWiki\Parser\Parser
;
/**
* Renderer for the actual list of references in place of the <references /> tag at the end of an
* article.
*
* @license GPL-2.0-or-later
*/
class
ReferenceListFormatter
{
public
function
__construct
(
private
readonly
ErrorReporter
$errorReporter
,
private
readonly
AnchorFormatter
$anchorFormatter
,
private
readonly
BacklinkMarkRenderer
$backlinkMarkRenderer
,
private
readonly
ReferenceMessageLocalizer
$messageLocalizer
,
)
{
}
/**
* @param Parser $parser
* @param array<string|int,ReferenceStackItem> $groupRefs
* @param bool $responsive
* @return string HTML
*/
public
function
formatReferences
(
Parser
$parser
,
array
$groupRefs
,
bool
$responsive
):
string
{
if
(
!
$groupRefs
)
{
return
''
;
}
$wikitext
=
$this
->
formatRefsList
(
$groupRefs
);
$html
=
$parser
->
recursiveTagParse
(
$wikitext
);
$firstRef
=
reset
(
$groupRefs
);
$html
=
Html
::
rawElement
(
'ol'
,
[
'class'
=>
'references'
,
'data-mw-group'
=>
$firstRef
->
group
===
Cite
::
DEFAULT_GROUP
?
null
:
$firstRef
->
group
,
],
$html
);
if
(
$responsive
)
{
$wrapClasses
=
[
'mw-references-wrap'
];
if
(
count
(
$groupRefs
)
>
10
)
{
$wrapClasses
[]
=
'mw-references-columns'
;
}
// Use a DIV wrap because column-count on a list directly is broken in Chrome.
// See https://bugs.chromium.org/p/chromium/issues/detail?id=498730.
return
Html
::
rawElement
(
'div'
,
[
'class'
=>
$wrapClasses
],
$html
);
}
return
$html
;
}
/**
* @param non-empty-array<string|int,ReferenceStackItem> $groupRefs
* @return string Wikitext
*/
private
function
formatRefsList
(
array
$groupRefs
):
string
{
// After sorting the list, we can assume that references are in the same order as their
// numbering. Subreferences will come immediately after their parent.
uasort
(
$groupRefs
,
static
function
(
ReferenceStackItem
$a
,
ReferenceStackItem
$b
):
int
{
$cmp
=
(
$a
->
numberInGroup
??
0
)
-
(
$b
->
numberInGroup
??
0
);
return
$cmp
?:
(
$a
->
subrefIndex
??
0
)
-
(
$b
->
subrefIndex
??
0
);
}
);
// Add new lines between the list items (ref entries) to avoid confusing tidy (T15073).
// Note: This builds a string of wikitext, not html.
$parserInput
=
"
\n
"
;
/** @var string|bool $indented */
$indented
=
false
;
foreach
(
$groupRefs
as
$ref
)
{
if
(
!
$indented
&&
$ref
->
hasMainRef
)
{
// Create nested list before processing the first subref.
// The nested <ol> must be inside the parent's <li>
if
(
preg_match
(
'#</li>
\s
*$#D'
,
$parserInput
,
$matches
,
PREG_OFFSET_CAPTURE
)
)
{
$parserInput
=
substr
(
$parserInput
,
0
,
$matches
[
0
][
1
]
);
}
$parserInput
.=
Html
::
openElement
(
'ol'
,
[
'class'
=>
'mw-subreference-list'
]
);
$indented
=
$matches
[
0
][
0
]
??
true
;
}
elseif
(
$indented
&&
!
$ref
->
hasMainRef
)
{
// End nested list.
$parserInput
.=
$this
->
closeIndention
(
$indented
);
$indented
=
false
;
}
$parserInput
.=
$this
->
formatListItem
(
$ref
)
.
"
\n
"
;
}
$parserInput
.=
$this
->
closeIndention
(
$indented
);
return
$parserInput
;
}
/**
* @param string|bool $closingLi
*
* @return string
*/
private
function
closeIndention
(
$closingLi
):
string
{
if
(
!
$closingLi
)
{
return
''
;
}
return
Html
::
closeElement
(
'ol'
)
.
(
is_string
(
$closingLi
)
?
$closingLi
:
''
);
}
/**
* @param ReferenceStackItem $ref
* @return string Wikitext, wrapped in a single <li> element
*/
private
function
formatListItem
(
ReferenceStackItem
$ref
):
string
{
$text
=
$this
->
renderTextAndWarnings
(
$ref
);
// Special case for an incomplete follow="…". This is valid e.g. in the Page:… namespace on
// Wikisource. Note this returns a <p>, not an <li> as expected!
if
(
$ref
->
follow
!==
null
)
{
return
"<p>$text</p>"
;
}
// Parameter $4 in the cite_references_link_one and cite_references_link_many messages
$extraAttributes
=
''
;
if
(
$ref
->
dir
!==
null
)
{
// The following classes are generated here:
// * mw-cite-dir-ltr
// * mw-cite-dir-rtl
$extraAttributes
=
Html
::
expandAttributes
(
[
'class'
=>
'mw-cite-dir-'
.
$ref
->
dir
]
);
}
if
(
$ref
->
count
===
1
)
{
$backlinkId
=
$this
->
anchorFormatter
->
wikitextSafeBacklink
(
$ref
->
name
,
$ref
->
globalId
,
$ref
->
count
);
return
$this
->
messageLocalizer
->
msg
(
'cite_references_link_one'
,
$this
->
anchorFormatter
->
noteLinkTarget
(
$ref
->
name
,
$ref
->
globalId
),
$backlinkId
,
$text
,
$extraAttributes
)->
plain
();
}
$backlinks
=
[];
for
(
$i
=
0
;
$i
<
$ref
->
count
;
$i
++
)
{
if
(
$this
->
backlinkMarkRenderer
->
isLegacyMode
()
)
{
// FIXME: parent mark should be explicitly markSymbolRenderer'd if it
// stays here.
$parentLabel
=
$this
->
messageLocalizer
->
localizeDigits
(
(
string
)
$ref
->
numberInGroup
);
$backlinks
[]
=
$this
->
messageLocalizer
->
msg
(
'cite_references_link_many_format'
,
$this
->
anchorFormatter
->
wikitextSafeBacklink
(
$ref
->
name
,
$ref
->
globalId
,
$i
+
1
),
$this
->
backlinkMarkRenderer
->
getLegacyNumericMarker
(
$i
,
$ref
->
count
,
$parentLabel
),
$this
->
backlinkMarkRenderer
->
getLegacyAlphabeticMarker
(
$i
+
1
,
$ref
->
count
,
$parentLabel
)
)->
plain
();
}
else
{
$backlinkLabel
=
$this
->
backlinkMarkRenderer
->
getBacklinkMarker
(
$i
+
1
);
$backlinks
[]
=
$this
->
messageLocalizer
->
msg
(
'cite_references_link_many_format'
,
$this
->
anchorFormatter
->
wikitextSafeBacklink
(
$ref
->
name
,
$ref
->
globalId
,
$i
+
1
),
$backlinkLabel
,
$backlinkLabel
)->
plain
();
}
}
// The parent of a subref might actually be unused and therefore have zero backlinks
$linkTargetId
=
$ref
->
count
>
0
?
$this
->
anchorFormatter
->
noteLinkTarget
(
$ref
->
name
,
$ref
->
globalId
)
:
''
;
return
$this
->
messageLocalizer
->
msg
(
'cite_references_link_many'
,
$linkTargetId
,
$this
->
listToText
(
$backlinks
),
$text
,
$extraAttributes
)->
plain
();
}
/**
* @param ReferenceStackItem $ref
* @return string Wikitext
*/
private
function
renderTextAndWarnings
(
ReferenceStackItem
$ref
):
string
{
$text
=
$ref
->
text
??
''
;
foreach
(
$ref
->
warnings
as
$warning
)
{
// @phan-suppress-next-line PhanParamTooFewUnpack
$text
.=
' '
.
$this
->
errorReporter
->
plain
(
...
$warning
);
// FIXME: We could use a StatusValue object to get rid of duplicates
break
;
}
return
'<span class="reference-text">'
.
rtrim
(
$text
,
"
\n
"
)
.
"</span>
\n
"
;
}
/**
* This does approximately the same thing as
* Language::listToText() but due to this being used for a
* slightly different purpose (people might not want , as the
* first separator and not 'and' as the second, and this has to
* use messages from the content language) I'm rolling my own.
*
* @param string[] $arr The array to format
*
* @return string Wikitext
*/
private
function
listToText
(
array
$arr
):
string
{
$lastElement
=
array_pop
(
$arr
);
if
(
$arr
===
[]
)
{
return
(
string
)
$lastElement
;
}
$sep
=
$this
->
messageLocalizer
->
msg
(
'cite_references_link_many_sep'
)->
plain
();
$and
=
$this
->
messageLocalizer
->
msg
(
'cite_references_link_many_and'
)->
plain
();
return
implode
(
$sep
,
$arr
)
.
$and
.
$lastElement
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 15 2026, 13:57 (12 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
47/f3/5615b6a8fdac414bebbb93d63c5c
Default Alt Text
ReferenceListFormatter.php (7 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment