Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4125389
DataAccess.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
17 KB
Referenced Files
None
Subscribers
None
DataAccess.php
View Options
<?php
declare
(
strict_types
=
1
);
/**
* Copyright (C) 2011-2022 Wikimedia Foundation and others.
*
* @license GPL-2.0-or-later
*/
namespace
MediaWiki\Parser\Parsoid\Config
;
use
MediaTransformError
;
use
MediaWiki\Cache\LinkBatchFactory
;
use
MediaWiki\Category\TrackingCategories
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\Content\Transform\ContentTransformer
;
use
MediaWiki\FileRepo\File\File
;
use
MediaWiki\FileRepo\RepoGroup
;
use
MediaWiki\HookContainer\HookContainer
;
use
MediaWiki\HookContainer\HookRunner
;
use
MediaWiki\Html\Html
;
use
MediaWiki\Language\LanguageCode
;
use
MediaWiki\Linker\Linker
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Page\File\BadFileLookup
;
use
MediaWiki\Parser\Parser
;
use
MediaWiki\Parser\ParserFactory
;
use
MediaWiki\Parser\ParserOptions
;
use
MediaWiki\Parser\ParserOutput
;
use
MediaWiki\Parser\PPFrame
;
use
MediaWiki\Title\Title
;
use
Wikimedia\Assert\UnreachableException
;
use
Wikimedia\Parsoid\Config\DataAccess
as
IDataAccess
;
use
Wikimedia\Parsoid\Config\PageConfig
as
IPageConfig
;
use
Wikimedia\Parsoid\Config\PageContent
as
IPageContent
;
use
Wikimedia\Parsoid\Core\ContentMetadataCollector
;
use
Wikimedia\Parsoid\Core\LinkTarget
as
ParsoidLinkTarget
;
use
Wikimedia\Parsoid\Fragments\HtmlPFragment
;
use
Wikimedia\Parsoid\Fragments\PFragment
;
use
Wikimedia\Rdbms\ReadOnlyMode
;
/**
* Implement Parsoid's abstract class for data access.
*
* @since 1.39
* @internal
*/
class
DataAccess
extends
IDataAccess
{
public
const
CONSTRUCTOR_OPTIONS
=
[
MainConfigNames
::
SVGMaxSize
,
];
private
readonly
HookRunner
$hookRunner
;
/** Lazy-created via self::prepareParser() */
private
?
Parser
$parser
=
null
;
private
PPFrame
$ppFrame
;
private
?
PageConfig
$previousPageConfig
=
null
;
private
int
$markerIndex
=
0
;
/**
* @param ServiceOptions $config MediaWiki main configuration object
* @param RepoGroup $repoGroup
* @param BadFileLookup $badFileLookup
* @param HookContainer $hookContainer
* @param ContentTransformer $contentTransformer
* @param TrackingCategories $trackingCategories
* @param ReadOnlyMode $readOnlyMode used to disable linting when the
* database is read-only.
* @param ParserFactory $parserFactory A legacy parser factory,
* for PST/preprocessing/extension handling
* @param LinkBatchFactory $linkBatchFactory
*/
public
function
__construct
(
private
readonly
ServiceOptions
$config
,
private
readonly
RepoGroup
$repoGroup
,
private
readonly
BadFileLookup
$badFileLookup
,
private
readonly
HookContainer
$hookContainer
,
private
readonly
ContentTransformer
$contentTransformer
,
private
readonly
TrackingCategories
$trackingCategories
,
private
readonly
ReadOnlyMode
$readOnlyMode
,
private
readonly
ParserFactory
$parserFactory
,
private
readonly
LinkBatchFactory
$linkBatchFactory
,
)
{
$config
->
assertRequiredOptions
(
self
::
CONSTRUCTOR_OPTIONS
);
$this
->
hookRunner
=
new
HookRunner
(
$hookContainer
);
$this
->
previousPageConfig
=
null
;
// ensure we initialize parser options
}
/**
* @param IPageConfig $pageConfig
* @param File $file
* @param array $hp
* @return array
*/
private
function
makeTransformOptions
(
IPageConfig
$pageConfig
,
$file
,
array
$hp
):
array
{
// Validate the input parameters like Parser::makeImage()
$handler
=
$file
->
getHandler
();
if
(
!
$handler
)
{
return
[];
// will get iconThumb()
}
foreach
(
$hp
as
$name
=>
$value
)
{
if
(
!
$handler
->
validateParam
(
$name
,
$value
)
)
{
unset
(
$hp
[
$name
]
);
}
}
// This part is similar to Linker::makeImageLink(). If there is no width,
// set one based on the source file size.
$page
=
$hp
[
'page'
]
??
0
;
if
(
!
isset
(
$hp
[
'width'
]
)
)
{
if
(
isset
(
$hp
[
'height'
]
)
&&
$file
->
isVectorized
()
)
{
// If it's a vector image, and user only specifies height
// we don't want it to be limited by its "normal" width.
$hp
[
'width'
]
=
$this
->
config
->
get
(
MainConfigNames
::
SVGMaxSize
);
}
else
{
$hp
[
'width'
]
=
$file
->
getWidth
(
$page
);
}
// We don't need to fill in a default thumbnail width here, since
// that is done by Parsoid. Parsoid always sets the width parameter
// for thumbnails.
}
// Parser::makeImage() always sets this
$hp
[
'targetlang'
]
=
LanguageCode
::
bcp47ToInternal
(
$pageConfig
->
getPageLanguageBcp47
()
);
return
$hp
;
}
/** @inheritDoc */
public
function
getPageInfo
(
$pageConfigOrTitle
,
array
$titles
):
array
{
if
(
$pageConfigOrTitle
instanceof
IPageConfig
)
{
$context_title
=
Title
::
newFromLinkTarget
(
$pageConfigOrTitle
->
getLinkTarget
()
);
}
elseif
(
is_string
(
$pageConfigOrTitle
)
)
{
// Temporary, deprecated.
$context_title
=
Title
::
newFromTextThrow
(
$pageConfigOrTitle
);
}
elseif
(
$pageConfigOrTitle
instanceof
ParsoidLinkTarget
)
{
$context_title
=
Title
::
newFromLinkTarget
(
$pageConfigOrTitle
);
}
else
{
throw
new
UnreachableException
(
"Bad type for argument 1"
);
}
$titleObjs
=
[];
$pagemap
=
[];
$classes
=
[];
$ret
=
[];
foreach
(
$titles
as
$name
)
{
$t
=
Title
::
newFromText
(
$name
);
// Filter out invalid titles. Title::newFromText in core (not our bespoke
// version in src/Utils/Title.php) can return null for invalid titles.
if
(
!
$t
)
{
// FIXME: This is a bandaid to patch up the fact that Env::makeTitle treats
// this as a valid title, but Title::newFromText treats it as invalid.
// T237535
// This matches what ApiQuery::outputGeneralPageInfo() would
// return for an invalid title.
$ret
[
$name
]
=
[
'pageId'
=>
-
1
,
'revId'
=>
-
1
,
'invalid'
=>
true
,
'invalidreason'
=>
'The requested page title is invalid'
,
];
}
else
{
$titleObjs
[
$name
]
=
$t
;
}
}
$this
->
linkBatchFactory
->
newLinkBatch
(
$titleObjs
)
->
setCaller
(
__METHOD__
)
->
execute
();
foreach
(
$titleObjs
as
$obj
)
{
$pdbk
=
$obj
->
getPrefixedDBkey
();
$pagemap
[
$obj
->
getArticleID
()]
=
$pdbk
;
$classes
[
$pdbk
]
=
$obj
->
isRedirect
()
?
'mw-redirect'
:
''
;
}
$this
->
hookRunner
->
onGetLinkColours
(
# $classes is passed by reference and mutated
$pagemap
,
$classes
,
$context_title
);
foreach
(
$titleObjs
as
$name
=>
$obj
)
{
/** @var Title $obj */
$pdbk
=
$obj
->
getPrefixedDBkey
();
$c
=
preg_split
(
'/
\s
+/'
,
$classes
[
$pdbk
]
??
''
,
-
1
,
PREG_SPLIT_NO_EMPTY
);
$ret
[
$name
]
=
[
'pageId'
=>
$obj
->
getArticleID
(),
'revId'
=>
$obj
->
getLatestRevID
(),
'missing'
=>
!
$obj
->
exists
(),
'known'
=>
$obj
->
isKnown
(),
'redirect'
=>
$obj
->
isRedirect
(),
'linkclasses'
=>
$c
,
# See ApiQueryInfo::getLinkClasses() in core
];
}
return
$ret
;
}
/** @inheritDoc */
public
function
getFileInfo
(
IPageConfig
$pageConfig
,
array
$files
):
array
{
$page
=
Title
::
newFromLinkTarget
(
$pageConfig
->
getLinkTarget
()
);
$keys
=
[];
foreach
(
$files
as
$f
)
{
$keys
[]
=
$f
[
0
];
}
$fileObjs
=
$this
->
repoGroup
->
findFiles
(
$keys
);
$ret
=
[];
foreach
(
$files
as
$f
)
{
$filename
=
$f
[
0
];
$dims
=
$f
[
1
];
/** @var File $file */
$file
=
$fileObjs
[
$filename
]
??
null
;
if
(
!
$file
)
{
$ret
[]
=
null
;
continue
;
}
// See Linker::makeImageLink; 'page' is a key in $handlerParams
// core uses 'false' as the default then casts to (int) => 0
$pageNum
=
$dims
[
'page'
]
??
0
;
$result
=
[
'width'
=>
$file
->
getWidth
(
$pageNum
),
'height'
=>
$file
->
getHeight
(
$pageNum
),
'size'
=>
$file
->
getSize
(),
'mediatype'
=>
$file
->
getMediaType
(),
'mime'
=>
$file
->
getMimeType
(),
'url'
=>
$file
->
getFullUrl
(),
'mustRender'
=>
$file
->
mustRender
(),
'isVectorized'
=>
$file
->
isVectorized
(),
'badFile'
=>
$this
->
badFileLookup
->
isBadFile
(
$filename
,
$page
),
'timestamp'
=>
$file
->
getTimestamp
(),
'sha1'
=>
$file
->
getSha1
(),
];
$length
=
$file
->
getLength
();
if
(
$length
)
{
$result
[
'duration'
]
=
(
float
)
$length
;
}
if
(
isset
(
$dims
[
'seek'
]
)
)
{
$dims
[
'thumbtime'
]
=
$dims
[
'seek'
];
}
$txopts
=
$this
->
makeTransformOptions
(
$pageConfig
,
$file
,
$dims
);
$mto
=
$file
->
transform
(
$txopts
);
if
(
$mto
)
{
if
(
$mto
->
isError
()
&&
$mto
instanceof
MediaTransformError
)
{
$result
[
'thumberror'
]
=
$mto
->
toText
();
}
else
{
if
(
$txopts
)
{
// Do srcset scaling
Linker
::
processResponsiveImages
(
$file
,
$mto
,
$txopts
);
if
(
count
(
$mto
->
responsiveUrls
)
)
{
$result
[
'responsiveUrls'
]
=
[];
foreach
(
$mto
->
responsiveUrls
as
$density
=>
$url
)
{
$result
[
'responsiveUrls'
][
$density
]
=
$url
;
}
}
}
// Proposed MediaTransformOutput serialization method for T51896 etc.
// Note that getAPIData(['fullurl']) would return
// UrlUtils::expand(), which wouldn't respect the wiki's
// protocol preferences -- instead it would use the
// protocol used for the API request.
if
(
is_callable
(
[
$mto
,
'getAPIData'
]
)
)
{
$result
[
'thumbdata'
]
=
$mto
->
getAPIData
(
[
'withhash'
]
);
}
$result
[
'thumburl'
]
=
$mto
->
getUrl
();
$result
[
'thumbwidth'
]
=
$mto
->
getWidth
();
$result
[
'thumbheight'
]
=
$mto
->
getHeight
();
}
}
else
{
$result
[
'thumberror'
]
=
"Presumably, invalid parameters, despite validation."
;
}
$ret
[]
=
$result
;
}
return
$ret
;
}
/**
* Prepare MediaWiki's parser for preprocessing or extension tag parsing,
* clearing its state if necessary.
*
* @note The caller is expected to call Parser::resetOutput() and
* reset the watcher if needed on $pageConfig->getParserOptions()
* as needed.
*
* @param IPageConfig $pageConfig
* @param int $outputType
* @return Parser
*/
private
function
prepareParser
(
IPageConfig
$pageConfig
,
int
$outputType
)
{
'@phan-var PageConfig $pageConfig'
;
// @var PageConfig $pageConfig
// Clear the state only when the PageConfig changes, so that Parser's internal caches can
// be retained. This should also provide better compatibility with extension tags.
$clearState
=
$this
->
previousPageConfig
!==
$pageConfig
;
$this
->
previousPageConfig
=
$pageConfig
;
$parserOptions
=
$pageConfig
->
getParserOptions
();
$oldWatcher
=
$parserOptions
->
registerWatcher
(
null
);
// Use the same legacy parser object for all calls to extension tag
// processing, for greater compatibility.
$this
->
parser
??=
$this
->
parserFactory
->
create
();
$this
->
parser
->
setStripExtTags
(
false
);
$this
->
parser
->
startExternalParse
(
Title
::
newFromLinkTarget
(
$pageConfig
->
getLinkTarget
()
),
$parserOptions
,
$outputType
,
$clearState
,
$pageConfig
->
getRevisionId
()
);
// Retain a PPFrame object between preprocess requests since it contains
// some useful caches.
if
(
$clearState
)
{
$this
->
ppFrame
=
$this
->
parser
->
getPreprocessor
()->
newFrame
();
// If $clearState is true, then we've reset the parser output and
// clobbered the watcher on the parser options; restore the old
// one.
$parserOptions
->
registerWatcher
(
$oldWatcher
);
}
return
$this
->
parser
;
}
/** @internal */
public
function
makeLimitReport
(
IPageConfig
$pageConfig
,
ParserOptions
$parserOptions
,
ParserOutput
$parserOutput
)
{
$parser
=
$this
->
prepareParser
(
$pageConfig
,
Parser
::
OT_HTML
);
// This next call doesn't touch $parser::$mParserOutput so we
// don't need to call Parser::resetOutput() here.
$parser
->
makeLimitReport
(
$parserOptions
,
$parserOutput
);
}
/** @inheritDoc */
public
function
parseWikitext
(
IPageConfig
$pageConfig
,
ContentMetadataCollector
$metadata
,
string
$wikitext
):
string
{
'@phan-var PageConfig $pageConfig'
;
// @var PageConfig $pageConfig
$parser
=
$this
->
prepareParser
(
$pageConfig
,
Parser
::
OT_HTML
);
// XXX: Ideally we will eventually have the legacy parser use our
// ContentMetadataCollector instead of having a new ParserOutput
// created (in Parser::resetOutput() here) which we then have to
// manually merge. On the other hand, this will let us precisely
// identify metadata added by $wikitext.
$parserOptions
=
$pageConfig
->
getParserOptions
();
$oldWatcher
=
$parserOptions
->
registerWatcher
(
null
);
$parser
->
resetOutput
();
$html
=
$parser
->
parseExtensionTagAsTopLevelDoc
(
$wikitext
);
$out
=
$parser
->
getOutput
();
$out
->
collectMetadata
(
$metadata
);
# merges $out into $metadata
$parserOptions
->
registerWatcher
(
$oldWatcher
);
return
Parser
::
extractBody
(
$html
);
}
/** @inheritDoc */
public
function
preprocessWikitext
(
IPageConfig
$pageConfig
,
ContentMetadataCollector
$metadata
,
$wikitext
)
{
'@phan-var PageConfig $pageConfig'
;
// @var PageConfig $pageConfig
$parser
=
$this
->
prepareParser
(
$pageConfig
,
Parser
::
OT_PREPROCESS
);
// XXX: Ideally we will eventually have the legacy parser use our
// ContentMetadataCollector instead of having a new ParserOutput
// created (in Parser::resetOutput() here) which we then have to
// manually merge. On the other hand, this will let us precisely
// identify metadata added by $wikitext.
$parserOptions
=
$pageConfig
->
getParserOptions
();
$oldWatcher
=
$parserOptions
->
registerWatcher
(
null
);
$parser
->
resetOutput
();
if
(
$wikitext
instanceof
PFragment
)
{
$result
=
[];
$index
=
1
;
foreach
(
$wikitext
->
split
()
as
$fragment
)
{
if
(
is_string
(
$fragment
)
)
{
$result
[]
=
$fragment
;
}
else
{
$marker
=
Parser
::
MARKER_PREFIX
.
'-parsoid-'
.
sprintf
(
'%08X'
,
$this
->
markerIndex
++
)
.
Parser
::
MARKER_SUFFIX
;
$parser
->
getStripState
()->
addParsoidOpaque
(
$marker
,
$fragment
);
$result
[]
=
$marker
;
}
}
$wikitext
=
implode
(
$result
);
}
$this
->
hookRunner
->
onParserBeforePreprocess
(
# $wikitext is passed by reference and mutated
$parser
,
$wikitext
,
$parser
->
getStripState
()
);
// New PFragment-based support (T374616)
$wikitext
=
$parser
->
replaceVariables
(
$wikitext
,
$this
->
ppFrame
,
false
,
[
'parsoidTopLevelCall'
=>
true
,
// This is implied by stripExtTags=false and
// probably doesn't need to be explicitly passed
// any more.
'processNowiki'
=>
true
,
]
);
// Where the result has strip state markers, tunnel this content
// through Parsoid as a PFragment type.
$pieces
=
$parser
->
getStripState
()->
split
(
$wikitext
);
if
(
count
(
$pieces
)
>
1
||
(
$pieces
[
0
][
'type'
]
??
null
)
!==
'string'
)
{
for
(
$i
=
0
;
$i
<
count
(
$pieces
);
$i
++
)
{
[
'type'
=>
$type
,
'content'
=>
$content
]
=
$pieces
[
$i
];
if
(
$type
===
'string'
)
{
// wikitext (could include extension tag snippets like <tag..>...</tag>)
$pieces
[
$i
]
=
$content
;
}
elseif
(
$type
===
'parsoid'
)
{
// @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
$pieces
[
$i
]
=
$pieces
[
$i
][
'extra'
];
// replace w/ fragment
}
elseif
(
$type
===
'nowiki'
)
{
$extra
=
$pieces
[
$i
][
'extra'
]
??
null
;
// T388819: If this is from an actual <nowiki>, we
// wrap <span typeof="mw:Nowiki"> around $contents.
if
(
$extra
===
'nowiki'
)
{
$content
=
Html
::
rawElement
(
'span'
,
[
'typeof'
=>
'mw:Nowiki'
,
],
$content
);
}
$pieces
[
$i
]
=
$content
?
HtmlPFragment
::
newFromHtmlString
(
$content
,
null
)
:
''
;
}
else
{
// T381709: technically this fragment should
// be subject to language conversion and some
// additional processing
$pieces
[
$i
]
=
$content
?
HtmlPFragment
::
newFromHtmlString
(
$content
,
null
)
:
''
;
}
}
// Concatenate wikitext strings generated by extension tags,
// so that PFragment doesn't try to add <nowiki>s between
// the pieces to prevent token-gluing.
$result
=
[];
$wt
=
''
;
foreach
(
$pieces
as
$p
)
{
if
(
is_string
(
$p
)
)
{
$wt
.=
$p
;
}
else
{
$result
[]
=
$wt
;
$result
[]
=
$p
;
$wt
=
''
;
}
}
$result
[]
=
$wt
;
// result will be a PFragment, no longer a string.
$wikitext
=
PFragment
::
fromSplitWt
(
$result
);
}
$out
=
$parser
->
getOutput
();
$out
->
collectMetadata
(
$metadata
);
# merges $out into $metadata
$parserOptions
->
registerWatcher
(
$oldWatcher
);
return
$wikitext
;
}
/** @inheritDoc */
public
function
fetchTemplateSource
(
IPageConfig
$pageConfig
,
$title
):
?
IPageContent
{
'@phan-var PageConfig $pageConfig'
;
// @var PageConfig $pageConfig
if
(
is_string
(
$title
)
)
{
$titleObj
=
Title
::
newFromTextThrow
(
$title
);
}
else
{
$titleObj
=
Title
::
newFromLinkTarget
(
$title
);
}
// Use the PageConfig to take advantage of custom template
// fetch hooks like FlaggedRevisions, etc.
$revRecord
=
$pageConfig
->
fetchRevisionRecordOfTemplate
(
$titleObj
);
return
$revRecord
?
new
PageContent
(
$revRecord
)
:
null
;
}
/** @inheritDoc */
public
function
fetchTemplateData
(
IPageConfig
$pageConfig
,
$title
):
?
array
{
$ret
=
[];
if
(
!
is_string
(
$title
)
)
{
$titleObj
=
Title
::
newFromLinkTarget
(
$title
);
$title
=
$titleObj
->
getPrefixedText
();
}
// @todo: This hook needs some clean up: T304899
$this
->
hookRunner
->
onParserFetchTemplateData
(
[
$title
],
$ret
# value returned by reference
);
// Cast value to array since the hook returns this as a stdclass
$tplData
=
$ret
[
$title
]
??
null
;
if
(
$tplData
)
{
// Deep convert to associative array
$tplData
=
json_decode
(
json_encode
(
$tplData
),
true
);
}
return
$tplData
;
}
/**
* Add a tracking category with the given key to the metadata for the page.
* @param IPageConfig $pageConfig the page on which the tracking category
* is to be added
* @param ContentMetadataCollector $metadata The metadata for the page
* @param string $key Message key (not localized)
*/
public
function
addTrackingCategory
(
IPageConfig
$pageConfig
,
ContentMetadataCollector
$metadata
,
string
$key
):
void
{
$page
=
Title
::
newFromLinkTarget
(
$pageConfig
->
getLinkTarget
()
);
$this
->
trackingCategories
->
addTrackingCategory
(
$metadata
,
$key
,
$page
);
}
/** @inheritDoc */
public
function
logLinterData
(
IPageConfig
$pageConfig
,
array
$lints
):
void
{
if
(
$this
->
readOnlyMode
->
isReadOnly
()
)
{
return
;
}
$revId
=
$pageConfig
->
getRevisionId
();
$title
=
Title
::
newFromLinkTarget
(
$pageConfig
->
getLinkTarget
()
)->
getPrefixedText
();
$pageInfo
=
$this
->
getPageInfo
(
$pageConfig
,
[
$title
]
);
$latest
=
$pageInfo
[
$title
][
'revId'
];
// Only send the request if it the latest revision
if
(
$revId
!==
null
&&
$revId
===
$latest
)
{
$this
->
hookRunner
->
onParserLogLinterData
(
$title
,
$revId
,
$lints
);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Wed, Aug 19, 07:27 (2 w, 2 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
36/08/84c815f53e6f45b3886f9c3d9b7d
Default Alt Text
DataAccess.php (17 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment