Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1425933
HandleParsoidSectionLinks.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
5 KB
Referenced Files
None
Subscribers
None
HandleParsoidSectionLinks.php
View Options
<?php
// Suppress UnusedPluginSuppression because Phan on PHP 8.1 needs more
// suppressions than PHP 7.x due to tighter types on Element::insertBefore()
// and Element::appendChild(): see comments marked PHP81 below. The
// Unused*Suppression can be removed once MW moves to >= PHP 8.1.
// @phan-file-suppress UnusedPluginSuppression,UnusedPluginFileSuppression
namespace
MediaWiki\OutputTransform\Stages
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\Context\RequestContext
;
use
MediaWiki\OutputTransform\ContentDOMTransformStage
;
use
MediaWiki\Parser\ParserOptions
;
use
MediaWiki\Parser\ParserOutput
;
use
MediaWiki\Parser\ParserOutputFlags
;
use
MediaWiki\Title\TitleFactory
;
use
Psr\Log\LoggerInterface
;
use
Skin
;
use
Wikimedia\Parsoid\DOM\Document
;
use
Wikimedia\Parsoid\Utils\DOMCompat
;
/**
* Add anchors and other heading formatting, and replace the section link placeholders.
* @internal
*/
class
HandleParsoidSectionLinks
extends
ContentDOMTransformStage
{
private
TitleFactory
$titleFactory
;
public
function
__construct
(
ServiceOptions
$options
,
LoggerInterface
$logger
,
TitleFactory
$titleFactory
)
{
parent
::
__construct
(
$options
,
$logger
);
$this
->
titleFactory
=
$titleFactory
;
}
public
function
shouldRun
(
ParserOutput
$po
,
?
ParserOptions
$popts
,
array
$options
=
[]
):
bool
{
// Only run this stage if it is parsoid content
return
(
$options
[
'isParsoidContent'
]
??
false
);
}
public
function
transformDOM
(
Document
$dom
,
ParserOutput
$po
,
?
ParserOptions
$popts
,
array
&
$options
):
Document
{
$skin
=
$this
->
resolveSkin
(
$options
);
$titleText
=
$po
->
getTitleText
();
// Transform:
// <section data-mw-section-id=...>
// <h2 id=...><span id=... typeof="mw:FallbackId"></span> ... </h2>
// ...section contents..
// To:
// <section data-mw-section-id=...>
// <div class="mw-heading mw-heading2">
// <h2 id=...><span id=... typeof="mw:FallbackId"></span> ... </h2>
// <span class="mw-editsection">...section edit link...</span>
// </div>
// That is, we're wrapping a <div> around the <h2> generated by
// Parsoid, and then (assuming section edit links are enabled)
// adding a <span> with the section edit link
// inside that <div>
//
// If COLLAPSIBLE_SECTIONS is set, then we also wrap a <div>
// around the section *contents*.
$toc
=
$po
->
getTOCData
();
$sections
=
(
$toc
!==
null
)
?
$toc
->
getSections
()
:
[];
// use the TOC data to extract the headings:
foreach
(
$sections
as
$section
)
{
$fromTitle
=
$section
->
fromTitle
;
if
(
$fromTitle
===
null
)
{
// T353489: don't wrap bare <h> tags
continue
;
}
$h
=
$dom
->
getElementById
(
$section
->
anchor
);
if
(
$h
===
null
)
{
$this
->
logger
->
error
(
__METHOD__
.
': Heading missing for anchor'
,
$section
->
toLegacy
()
);
continue
;
}
$div
=
$dom
->
createElement
(
'div'
);
if
(
(
$options
[
'enableSectionEditLinks'
]
??
true
)
&&
!
$po
->
getOutputFlag
(
ParserOutputFlags
::
NO_SECTION_EDIT_LINKS
)
)
{
$editPage
=
$this
->
titleFactory
->
newFromTextThrow
(
$fromTitle
);
$html
=
$skin
->
doEditSectionLink
(
$editPage
,
$section
->
index
,
$h
->
textContent
,
$skin
->
getLanguage
()
);
DOMCompat
::
setInnerHTML
(
$div
,
$html
);
}
// Reuse existing wrapper if present.
$maybeWrapper
=
$h
->
parentNode
;
'@phan-var
\W
ikimedia
\P
arsoid
\D
OM
\E
lement $maybeWrapper'
;
if
(
DOMCompat
::
nodeName
(
$maybeWrapper
)
===
'div'
&&
DOMCompat
::
getClassList
(
$maybeWrapper
)->
contains
(
'mw-heading'
)
)
{
// Transfer section edit link children to existing wrapper
// All contents of the div (the section edit link) will be
// inserted immediately following the <h> tag
$ref
=
$h
->
nextSibling
;
while
(
$div
->
firstChild
!==
null
)
{
// @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal firstChild is non-null (PHP81)
$maybeWrapper
->
insertBefore
(
$div
->
firstChild
,
$ref
);
}
$div
=
$maybeWrapper
;
// for use below
}
else
{
// Move <hX> to new wrapper: the div contents are currently
// the section edit link. We first replace the h with the
// div, then insert the <h> as the first child of the div
// so the section edit link is immediately following the <h>.
$div
->
setAttribute
(
'class'
,
'mw-heading mw-heading'
.
$section
->
hLevel
);
$h
->
parentNode
->
replaceChild
(
$div
,
$h
);
// Work around bug in phan (https://github.com/phan/phan/pull/4837)
// by asserting that $div->firstChild is non-null here. Actually,
// ::insertBefore will work fine if $div->firstChild is null (if
// "doEditSectionLink" returned nothing, for instance), but
// phan incorrectly thinks the second argument must be non-null.
$divFirstChild
=
$div
->
firstChild
;
'@phan-var
\D
OMNode $divFirstChild'
;
// asserting non-null (PHP81)
$div
->
insertBefore
(
$h
,
$divFirstChild
);
}
// Create collapsible section wrapper if requested.
if
(
$po
->
getOutputFlag
(
ParserOutputFlags
::
COLLAPSIBLE_SECTIONS
)
)
{
$contentsDiv
=
$dom
->
createElement
(
'div'
);
while
(
$div
->
nextSibling
!==
null
)
{
// @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal
$contentsDiv
->
appendChild
(
$div
->
nextSibling
);
}
$div
->
parentNode
->
appendChild
(
$contentsDiv
);
}
}
return
$dom
;
}
/**
* Extracts the skin from the $options array, with a fallback on request context skin
* @param array $options
* @return Skin
*/
private
function
resolveSkin
(
array
$options
):
Skin
{
$skin
=
$options
[
'skin'
]
??
null
;
if
(
!
$skin
)
{
// T348853 passing $skin will be mandatory in the future
$skin
=
RequestContext
::
getMain
()->
getSkin
();
}
return
$skin
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 12:15 (1 d, 1 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0d/40/e907a58e142f6d44be2f0c96d7d8
Default Alt Text
HandleParsoidSectionLinks.php (5 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment