Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4127570
Hooks.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
Hooks.php
View Options
<?php
namespace
MediaWiki\Extension\TemplateData
;
use
MediaWiki\CommentStore\CommentStoreComment
;
use
MediaWiki\Config\Config
;
use
MediaWiki\Context\RequestContext
;
use
MediaWiki\EditPage\EditPage
;
use
MediaWiki\Extension\EventLogging\EventLogging
;
use
MediaWiki\Hook\EditPage__showEditForm_fieldsHook
;
use
MediaWiki\Hook\EditPage__showEditForm_initialHook
;
use
MediaWiki\Hook\ParserFetchTemplateDataHook
;
use
MediaWiki\Hook\ParserFirstCallInitHook
;
use
MediaWiki\Html\Html
;
use
MediaWiki\MediaWikiServices
;
use
MediaWiki\Output\Hook\OutputPageBeforeHTMLHook
;
use
MediaWiki\Output\OutputPage
;
use
MediaWiki\Parser\Parser
;
use
MediaWiki\Parser\PPFrame
;
use
MediaWiki\Registration\ExtensionRegistry
;
use
MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook
;
use
MediaWiki\ResourceLoader\ResourceLoader
;
use
MediaWiki\Revision\RenderedRevision
;
use
MediaWiki\Revision\RevisionRecord
;
use
MediaWiki\Revision\SlotRecord
;
use
MediaWiki\Status\Status
;
use
MediaWiki\Storage\Hook\MultiContentSaveHook
;
use
MediaWiki\Title\Title
;
use
MediaWiki\User\UserIdentity
;
/**
* @license GPL-2.0-or-later
* phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName
*/
class
Hooks
implements
EditPage__showEditForm_fieldsHook
,
ParserFirstCallInitHook
,
MultiContentSaveHook
,
ResourceLoaderRegisterModulesHook
,
EditPage__showEditForm_initialHook
,
ParserFetchTemplateDataHook
,
OutputPageBeforeHTMLHook
{
private
Config
$config
;
public
function
__construct
(
Config
$mainConfig
)
{
$this
->
config
=
$mainConfig
;
}
/**
* @param EditPage $editPage
* @param OutputPage $out
*/
public
function
onEditPage__showEditForm_fields
(
$editPage
,
$out
)
{
// TODO: Remove when not needed any more, see T267926
if
(
$out
->
getRequest
()->
getBool
(
'TemplateDataGeneratorUsed'
)
)
{
// Recreate the dynamically created field after the user clicked "preview"
$out
->
addHTML
(
Html
::
hidden
(
'TemplateDataGeneratorUsed'
,
true
)
);
}
}
/**
* Register parser hooks
* @param Parser $parser
*/
public
function
onParserFirstCallInit
(
$parser
)
{
$parser
->
setHook
(
'templatedata'
,
[
__CLASS__
,
'render'
]
);
}
/**
* Conditionally register the jquery.uls.data module, in case they've already been
* registered by the UniversalLanguageSelector extension or the VisualEditor extension.
*
* @param ResourceLoader $resourceLoader
*/
public
function
onResourceLoaderRegisterModules
(
ResourceLoader
$resourceLoader
):
void
{
$resourceModules
=
$resourceLoader
->
getConfig
()->
get
(
'ResourceModules'
);
$name
=
'jquery.uls.data'
;
if
(
!
isset
(
$resourceModules
[
$name
]
)
&&
!
$resourceLoader
->
isModuleRegistered
(
$name
)
)
{
$resourceLoader
->
register
(
[
$name
=>
[
'localBasePath'
=>
dirname
(
__DIR__
),
'remoteExtPath'
=>
'TemplateData'
,
'scripts'
=>
[
'lib/jquery.uls/src/jquery.uls.data.js'
,
'lib/jquery.uls/src/jquery.uls.data.utils.js'
,
],
]
]
);
}
}
/**
* @param RenderedRevision $renderedRevision
* @param UserIdentity $user
* @param CommentStoreComment $summary
* @param int $flags
* @param Status $hookStatus
* @return bool
*/
public
function
onMultiContentSave
(
$renderedRevision
,
$user
,
$summary
,
$flags
,
$hookStatus
)
{
$revisionRecord
=
$renderedRevision
->
getRevision
();
$contentModel
=
$revisionRecord
->
getContent
(
SlotRecord
::
MAIN
)
->
getModel
();
if
(
$contentModel
!==
CONTENT_MODEL_WIKITEXT
)
{
return
true
;
}
// Revision hasn't been parsed yet, so parse to know if self::render got a
// valid tag (via inclusion and transclusion) and abort save if it didn't
$parserOutput
=
$renderedRevision
->
getRevisionParserOutput
(
[
'generate-html'
=>
false
]
);
$status
=
TemplateDataStatus
::
newFromJson
(
$parserOutput
->
getExtensionData
(
'TemplateDataStatus'
)
);
if
(
$status
&&
!
$status
->
isOK
()
)
{
// Abort edit, show error message from TemplateDataBlob::getStatus
$hookStatus
->
merge
(
$status
);
return
false
;
}
// TODO: Remove when not needed any more, see T267926
self
::
logChangeEvent
(
$revisionRecord
,
$parserOutput
->
getPageProperty
(
'templatedata'
),
$user
);
return
true
;
}
private
static
function
logChangeEvent
(
RevisionRecord
$revisionRecord
,
?
string
$newPageProperty
,
UserIdentity
$user
):
void
{
if
(
!
ExtensionRegistry
::
getInstance
()->
isLoaded
(
'EventLogging'
)
)
{
return
;
}
$services
=
MediaWikiServices
::
getInstance
();
$page
=
$revisionRecord
->
getPage
();
$props
=
$services
->
getPageProps
()->
getProperties
(
$page
,
'templatedata'
);
$pageId
=
$page
->
getId
();
// The JSON strings here are guaranteed to be normalized (and possibly compressed) the same
// way. No need to normalize them again for this comparison.
if
(
$newPageProperty
===
(
$props
[
$pageId
]
??
null
)
)
{
return
;
}
$generatorUsed
=
RequestContext
::
getMain
()->
getRequest
()->
getBool
(
'TemplateDataGeneratorUsed'
);
$userEditCount
=
$services
->
getUserEditTracker
()->
getUserEditCount
(
$user
);
$userId
=
$services
->
getUserIdentityUtils
()->
isTemp
(
$user
)
?
0
:
$user
->
getId
();
// Note: We know that irrelevant changes (e.g. whitespace changes) aren't logged here
EventLogging
::
submit
(
'eventlogging_TemplateDataEditor'
,
[
'$schema'
=>
'/analytics/legacy/templatedataeditor/1.0.0'
,
'event'
=>
[
// Note: The "Done" button is disabled unless something changed, which means it's
// very likely (but not guaranteed) the generator was used to make the changes
'action'
=>
$generatorUsed
?
'save-tag-edit-generator-used'
:
'save-tag-edit-no-generator'
,
'page_id'
=>
$pageId
,
'page_namespace'
=>
$page
->
getNamespace
(),
'page_title'
=>
$page
->
getDBkey
(),
'rev_id'
=>
$revisionRecord
->
getId
()
??
0
,
'user_edit_count'
=>
$userEditCount
??
0
,
'user_id'
=>
$userId
,
],
]
);
}
/**
* Hook to load the GUI module only on edit action.
*
* @param EditPage $editPage
* @param OutputPage $output
*/
public
function
onEditPage__showEditForm_initial
(
$editPage
,
$output
)
{
if
(
$this
->
config
->
get
(
'TemplateDataUseGUI'
)
)
{
$isTemplate
=
$output
->
getTitle
()->
inNamespace
(
NS_TEMPLATE
);
if
(
!
$isTemplate
)
{
// If we're outside the Template namespace, allow access to GUI
// if it's an existing page with <templatedate> (e.g. User template sandbox,
// or some other page that's intended to be transcluded for any reason).
$services
=
MediaWikiServices
::
getInstance
();
$props
=
$services
->
getPageProps
()->
getProperties
(
$editPage
->
getTitle
(),
'templatedata'
);
$isTemplate
=
(
bool
)
$props
;
}
if
(
$isTemplate
)
{
$output
->
addModuleStyles
(
'ext.templateDataGenerator.editTemplatePage.loading'
);
$output
->
addHTML
(
'<div class="tdg-editscreen-placeholder"></div>'
);
$output
->
addModules
(
'ext.templateDataGenerator.editTemplatePage'
);
}
}
}
/**
* Parser hook for <templatedata>.
* If there is any JSON provided, render the template documentation on the page.
*
* @param string|null $input The content of the tag.
* @param array $args The attributes of the tag.
* @param Parser $parser Parser instance available to render
* wikitext into html, or parser methods.
* @param PPFrame $frame Can be used to see what template parameters ("{{{1}}}", etc.)
* this hook was used with.
*
* @return string HTML to insert in the page.
*/
public
static
function
render
(
?
string
$input
,
array
$args
,
Parser
$parser
,
PPFrame
$frame
):
string
{
$parserOutput
=
$parser
->
getOutput
();
$dbr
=
MediaWikiServices
::
getInstance
()->
getConnectionProvider
()->
getReplicaDatabase
();
$ti
=
TemplateDataBlob
::
newFromJSON
(
$dbr
,
$input
??
''
);
$status
=
$ti
->
getStatus
();
if
(
!
$status
->
isOK
()
)
{
$parserOutput
->
setExtensionData
(
'TemplateDataStatus'
,
TemplateDataStatus
::
jsonSerialize
(
$status
)
);
return
Html
::
errorBox
(
$status
->
getHTML
()
);
}
// Store the blob as page property for retrieval by ApiTemplateData.
// But, don't store it if we're parsing a doc sub page, because:
// - The doc subpage should not appear in ApiTemplateData as a documented
// template itself, which is confusing to users (T54448).
// - The doc subpage should not appear at Special:PagesWithProp.
// - Storing the blob twice in the database is inefficient (T52512).
$title
=
$parser
->
getTitle
();
$docPage
=
wfMessage
(
'templatedata-doc-subpage'
)->
inContentLanguage
();
if
(
!
$title
->
isSubpage
()
||
$title
->
getSubpageText
()
!==
$docPage
->
plain
()
)
{
$parserOutput
->
setPageProperty
(
'templatedata'
,
$ti
->
getJSONForDatabase
()
);
}
$parserOutput
->
addModuleStyles
(
[
'ext.templateData'
,
'ext.templateData.images'
,
'jquery.tablesorter.styles'
,
]
);
$parserOutput
->
addModules
(
[
'jquery.tablesorter'
]
);
$parserOutput
->
setEnableOOUI
(
true
);
$userLang
=
$parser
->
getOptions
()->
getUserLangObj
();
// FIXME: this hard-codes default skin, but it is needed because
// ::getHtml() will need a theme singleton to be set.
OutputPage
::
setupOOUI
(
'bogus'
,
$userLang
->
getDir
()
);
$localizer
=
new
TemplateDataMessageLocalizer
(
$userLang
);
$formatter
=
new
TemplateDataHtmlFormatter
(
$localizer
,
$userLang
->
getCode
()
);
return
$formatter
->
getHtml
(
$ti
,
$frame
->
getTitle
(),
!
$parser
->
getOptions
()->
getIsPreview
()
);
}
/**
* @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageBeforeHTML
*
* @param OutputPage $output
* @param string &$text
*/
public
function
onOutputPageBeforeHTML
(
$output
,
&
$text
)
{
$services
=
MediaWikiServices
::
getInstance
();
$props
=
$services
->
getPageProps
()->
getProperties
(
$output
->
getTitle
(),
'templatedata'
);
if
(
$props
)
{
$lang
=
$output
->
getLanguage
();
$localizer
=
new
TemplateDataMessageLocalizer
(
$lang
);
$formatter
=
new
TemplateDataHtmlFormatter
(
$localizer
,
$lang
->
getCode
()
);
$formatter
->
replaceEditLink
(
$text
);
}
}
/**
* Fetch templatedata for an array of titles.
*
* @todo Document this hook
*
* The following questions are yet to be resolved.
* (a) Should we extend functionality to looking up an array of titles instead of one?
* The signature allows for an array of titles to be passed in, but the
* current implementation is not optimized for the multiple-title use case.
* (b) Should this be a lookup service instead of this faux hook?
* This will be resolved separately.
*
* @param array $tplTitles
* @param \stdClass[] &$tplData
* @return bool
*/
public
function
onParserFetchTemplateData
(
array
$tplTitles
,
array
&
$tplData
):
bool
{
$tplData
=
[];
$services
=
MediaWikiServices
::
getInstance
();
$pageProps
=
$services
->
getPageProps
();
$wikiPageFactory
=
$services
->
getWikiPageFactory
();
$dbr
=
$services
->
getConnectionProvider
()->
getReplicaDatabase
();
// This inefficient implementation is currently tuned for
// Parsoid's use case where it requests info for exactly one title.
// For a real batch use case, this code will need an overhaul.
foreach
(
$tplTitles
as
$tplTitle
)
{
$title
=
Title
::
newFromText
(
$tplTitle
);
if
(
!
$title
)
{
// Invalid title
$tplData
[
$tplTitle
]
=
null
;
continue
;
}
if
(
$title
->
isRedirect
()
)
{
$title
=
$wikiPageFactory
->
newFromTitle
(
$title
)->
getRedirectTarget
();
if
(
!
$title
)
{
// Invalid redirecting title
$tplData
[
$tplTitle
]
=
null
;
continue
;
}
}
if
(
!
$title
->
exists
()
)
{
$tplData
[
$tplTitle
]
=
(
object
)[
'missing'
=>
true
];
continue
;
}
// FIXME: PageProps returns takes titles but returns by page id.
// This means we need to do our own look up and hope it matches.
// Spoiler, sometimes it won't. When that happens, the user won't
// get any templatedata-based interfaces for that template.
// The fallback is to not serve data for that template, which
// the clients have to support anyway, so the impact is minimal.
// It is also expected that such race conditions resolve themselves
// after a few seconds so the old "try again later" should cover this.
$pageId
=
$title
->
getArticleID
();
$props
=
$pageProps
->
getProperties
(
$title
,
'templatedata'
);
if
(
!
isset
(
$props
[
$pageId
]
)
)
{
// No templatedata
$tplData
[
$tplTitle
]
=
(
object
)[
'notemplatedata'
=>
true
];
continue
;
}
$tdb
=
TemplateDataBlob
::
newFromDatabase
(
$dbr
,
$props
[
$pageId
]
);
$status
=
$tdb
->
getStatus
();
if
(
!
$status
->
isOK
()
)
{
// Invalid data. Parsoid has no use for the error.
$tplData
[
$tplTitle
]
=
(
object
)[
'notemplatedata'
=>
true
];
continue
;
}
$tplData
[
$tplTitle
]
=
$tdb
->
getData
();
}
return
true
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Wed, Aug 19, 08:43 (2 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e2/f1/bcdd09b1ee54991daa9af12eb0c1
Default Alt Text
Hooks.php (12 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment