Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4050081
Hooks.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
Hooks.php
View Options
<?php
/**
* Wikitext scripting infrastructure for MediaWiki: hooks.
* Copyright (C) 2009-2012 Victor Vasiliev <vasilvv@gmail.com>
* https://www.mediawiki.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
namespace
MediaWiki\Extension\Scribunto
;
use
Article
;
use
MediaWiki\Config\Config
;
use
MediaWiki\Content\Content
;
use
MediaWiki\Context\IContextSource
;
use
MediaWiki\EditPage\EditPage
;
use
MediaWiki\Hook\EditFilterMergedContentHook
;
use
MediaWiki\Hook\EditPage__showReadOnlyForm_initialHook
;
use
MediaWiki\Hook\EditPage__showStandardInputs_optionsHook
;
use
MediaWiki\Hook\EditPageBeforeEditButtonsHook
;
use
MediaWiki\Hook\ParserClearStateHook
;
use
MediaWiki\Hook\ParserClonedHook
;
use
MediaWiki\Hook\ParserFirstCallInitHook
;
use
MediaWiki\Hook\ParserLimitReportFormatHook
;
use
MediaWiki\Hook\ParserLimitReportPrepareHook
;
use
MediaWiki\Hook\SoftwareInfoHook
;
use
MediaWiki\Html\Html
;
use
MediaWiki\MediaWikiServices
;
use
MediaWiki\Output\OutputPage
;
use
MediaWiki\Page\Hook\ArticleViewHeaderHook
;
use
MediaWiki\Parser\Parser
;
use
MediaWiki\Parser\ParserOutput
;
use
MediaWiki\Parser\PPFrame
;
use
MediaWiki\Parser\PPNode
;
use
MediaWiki\Revision\Hook\ContentHandlerDefaultModelForHook
;
use
MediaWiki\Status\Status
;
use
MediaWiki\Title\Title
;
use
MediaWiki\User\User
;
use
MediaWiki\WikiMap\WikiMap
;
use
UtfNormal\Validator
;
use
Wikimedia\ObjectCache\EmptyBagOStuff
;
use
Wikimedia\PSquare
;
/**
* Hooks for the Scribunto extension.
*/
class
Hooks
implements
SoftwareInfoHook
,
ParserFirstCallInitHook
,
ParserLimitReportPrepareHook
,
ParserLimitReportFormatHook
,
ParserClearStateHook
,
ParserClonedHook
,
EditPage__showStandardInputs_optionsHook
,
EditPage__showReadOnlyForm_initialHook
,
EditPageBeforeEditButtonsHook
,
EditFilterMergedContentHook
,
ArticleViewHeaderHook
,
ContentHandlerDefaultModelForHook
{
private
Config
$config
;
public
function
__construct
(
Config
$config
)
{
$this
->
config
=
$config
;
}
/**
* Define content handler constant upon extension registration
*/
public
static
function
onRegistration
()
{
define
(
'CONTENT_MODEL_SCRIBUNTO'
,
'Scribunto'
);
}
/**
* Get software information for Special:Version
*
* @param array &$software
* @return bool
*/
public
function
onSoftwareInfo
(
&
$software
)
{
$engine
=
Scribunto
::
newDefaultEngine
();
$engine
->
setTitle
(
Title
::
makeTitle
(
NS_SPECIAL
,
'Version'
)
);
$engine
->
getSoftwareInfo
(
$software
);
return
true
;
}
/**
* Register parser hooks.
*
* @param Parser $parser
* @return void
*/
public
function
onParserFirstCallInit
(
$parser
)
{
$parser
->
setFunctionHook
(
'invoke'
,
[
$this
,
'invokeHook'
],
Parser
::
SFH_OBJECT_ARGS
);
}
/**
* Called when the interpreter is to be reset.
*
* @param Parser $parser
* @return void
*/
public
function
onParserClearState
(
$parser
)
{
Scribunto
::
resetParserEngine
(
$parser
);
}
/**
* Called when the parser is cloned
*
* @param Parser $parser
* @return void
*/
public
function
onParserCloned
(
$parser
)
{
$parser
->
scribunto_engine
=
null
;
}
/**
* Hook function for {{#invoke:module|func}}
*
* @param Parser $parser
* @param PPFrame $frame
* @param PPNode[] $args
* @return string
*/
public
function
invokeHook
(
Parser
$parser
,
PPFrame
$frame
,
array
$args
)
{
try
{
if
(
count
(
$args
)
<
2
)
{
throw
new
ScribuntoException
(
'scribunto-common-nofunction'
);
}
$moduleName
=
trim
(
$frame
->
expand
(
$args
[
0
]
)
);
$engine
=
Scribunto
::
getParserEngine
(
$parser
);
$title
=
Title
::
makeTitleSafe
(
NS_MODULE
,
$moduleName
);
if
(
!
$title
||
!
$title
->
hasContentModel
(
CONTENT_MODEL_SCRIBUNTO
)
)
{
throw
new
ScribuntoException
(
'scribunto-common-nosuchmodule'
,
[
'args'
=>
[
$moduleName
]
]
);
}
$module
=
$engine
->
fetchModuleFromParser
(
$title
);
if
(
!
$module
)
{
throw
new
ScribuntoException
(
'scribunto-common-nosuchmodule'
,
[
'args'
=>
[
$moduleName
]
]
);
}
$functionName
=
trim
(
$frame
->
expand
(
$args
[
1
]
)
);
$bits
=
$args
[
1
]->
splitArg
();
unset
(
$args
[
0
]
);
unset
(
$args
[
1
]
);
// If $bits['index'] is empty, then the function name was parsed as a
// key=value pair (because of an equals sign in it), and since it didn't
// have an index, we don't need the index offset.
$childFrame
=
$frame
->
newChild
(
$args
,
$title
,
$bits
[
'index'
]
===
''
?
0
:
1
);
if
(
$this
->
config
->
get
(
'ScribuntoGatherFunctionStats'
)
)
{
$u0
=
$engine
->
getResourceUsage
(
$engine
::
CPU_SECONDS
);
$result
=
$module
->
invoke
(
$functionName
,
$childFrame
);
$u1
=
$engine
->
getResourceUsage
(
$engine
::
CPU_SECONDS
);
if
(
$u1
>
$u0
)
{
$timingMs
=
(
int
)(
1000
*
(
$u1
-
$u0
)
);
// Since the overhead of stats is worst when #invoke
// calls are very short, don't process measurements <= 20ms.
if
(
$timingMs
>
20
)
{
$this
->
reportTiming
(
$moduleName
,
$functionName
,
$timingMs
);
}
}
}
else
{
$result
=
$module
->
invoke
(
$functionName
,
$childFrame
);
}
return
Validator
::
cleanUp
(
strval
(
$result
)
);
}
catch
(
ScribuntoException
$e
)
{
$trace
=
$e
->
getScriptTraceHtml
(
[
'msgOptions'
=>
[
'content'
]
]
);
$html
=
Html
::
element
(
'p'
,
[],
$e
->
getMessage
()
);
if
(
$trace
!==
false
)
{
$html
.=
Html
::
element
(
'p'
,
[],
wfMessage
(
'scribunto-common-backtrace'
)->
inContentLanguage
()->
text
()
)
.
$trace
;
}
else
{
$html
.=
Html
::
element
(
'p'
,
[],
wfMessage
(
'scribunto-common-no-details'
)->
inContentLanguage
()->
text
()
);
}
// Index this error by a uniq ID so that we are independent of
// page parse order. (T300979)
// (The only way this will conflict is if two exceptions have
// exactly the same backtrace, in which case we really only need
// one copy of the backtrace!)
$uuid
=
substr
(
sha1
(
$html
),
-
8
);
$parserOutput
=
$parser
->
getOutput
();
$parserOutput
->
appendExtensionData
(
'ScribuntoErrors'
,
$uuid
);
$parserOutput
->
setExtensionData
(
"ScribuntoErrors-$uuid"
,
$html
);
$parserOutput
->
appendJsConfigVar
(
'ScribuntoErrors'
,
$uuid
);
$parserOutput
->
setJsConfigVar
(
"ScribuntoErrors-$uuid"
,
$html
);
// These methods are idempotent; doesn't hurt to call them every
// time.
$parser
->
addTrackingCategory
(
'scribunto-common-error-category'
);
$parserOutput
->
addModules
(
[
'ext.scribunto.errors'
]
);
$id
=
"mw-scribunto-error-$uuid"
;
$parserError
=
htmlspecialchars
(
$e
->
getMessage
()
);
// #iferror-compatible error element
return
"<strong class=
\"
error
\"
><span class=
\"
scribunto-error $id
\"
>"
.
$parserError
.
"</span></strong>"
;
}
}
/**
* Record stats on slow function calls.
*
* @param string $moduleName
* @param string $functionName
* @param int $timing Function execution time in milliseconds.
*/
private
function
reportTiming
(
$moduleName
,
$functionName
,
$timing
)
{
if
(
!
$this
->
config
->
get
(
'ScribuntoGatherFunctionStats'
)
)
{
return
;
}
$threshold
=
$this
->
config
->
get
(
'ScribuntoSlowFunctionThreshold'
);
if
(
!(
is_float
(
$threshold
)
&&
$threshold
>
0
&&
$threshold
<
1
)
)
{
return
;
}
$objectcachefactory
=
MediaWikiServices
::
getInstance
()->
getObjectCacheFactory
();
static
$cache
;
if
(
!
$cache
)
{
$cache
=
$objectcachefactory
->
getLocalServerInstance
(
CACHE_NONE
);
}
// To control the sampling rate, we keep a compact histogram of
// observations in APC, and extract the Nth percentile (specified
// via $wgScribuntoSlowFunctionThreshold; defaults to 0.90).
// We need a non-empty local server cache for that (e.g. php-apcu).
if
(
$cache
instanceof
EmptyBagOStuff
)
{
return
;
}
$cacheVersion
=
'3'
;
$key
=
$cache
->
makeGlobalKey
(
'scribunto-stats'
,
$cacheVersion
,
(
string
)
$threshold
);
// This is a classic "read-update-write" critical section with no
// mutual exclusion, but the only consequence is that some samples
// will be dropped. We only need enough samples to estimate the
// shape of the data, so that's fine.
$ps
=
$cache
->
get
(
$key
)
?:
new
PSquare
(
$threshold
);
$ps
->
addObservation
(
$timing
);
$cache
->
set
(
$key
,
$ps
,
60
);
if
(
$ps
->
getCount
()
<
1000
||
$timing
<
$ps
->
getValue
()
)
{
return
;
}
static
$stats
;
if
(
!
$stats
)
{
$stats
=
MediaWikiServices
::
getInstance
()->
getStatsFactory
();
}
$statAction
=
WikiMap
::
getCurrentWikiId
()
.
'__'
.
$moduleName
.
'__'
.
$functionName
;
$stats
->
getTiming
(
'scribunto_traces_seconds'
)
->
setLabel
(
'action'
,
$statAction
)
->
copyToStatsdAt
(
'scribunto.traces.'
.
$statAction
)
->
observe
(
$timing
);
}
/**
* Set the Scribunto content handler for modules
*
* @param Title $title
* @param string &$model
* @return void
*/
public
function
onContentHandlerDefaultModelFor
(
$title
,
&
$model
)
{
if
(
$model
===
'sanitized-css'
)
{
// Let TemplateStyles override Scribunto
return
;
}
if
(
$title
->
getNamespace
()
===
NS_MODULE
)
{
if
(
str_ends_with
(
$title
->
getText
(),
'.json'
)
)
{
$model
=
CONTENT_MODEL_JSON
;
}
elseif
(
!
Scribunto
::
isDocPage
(
$title
)
)
{
$model
=
CONTENT_MODEL_SCRIBUNTO
;
}
}
}
/**
* Adds report of number of evaluations by the single wikitext page.
*
* @param Parser $parser
* @param ParserOutput $parserOutput
* @return void
*/
public
function
onParserLimitReportPrepare
(
$parser
,
$parserOutput
)
{
if
(
Scribunto
::
isParserEnginePresent
(
$parser
)
)
{
$engine
=
Scribunto
::
getParserEngine
(
$parser
);
$engine
->
reportLimitData
(
$parserOutput
);
}
}
/**
* Formats the limit report data
*
* @param string $key
* @param mixed &$value
* @param string &$report
* @param bool $isHTML
* @param bool $localize
* @return bool
*/
public
function
onParserLimitReportFormat
(
$key
,
&
$value
,
&
$report
,
$isHTML
,
$localize
)
{
$engine
=
Scribunto
::
newDefaultEngine
();
return
$engine
->
formatLimitData
(
$key
,
$value
,
$report
,
$isHTML
,
$localize
);
}
/**
* EditPage::showStandardInputs:options hook
*
* @param EditPage $editor
* @param OutputPage $output
* @param int &$tab Current tabindex
* @return void
*/
public
function
onEditPage__showStandardInputs_options
(
$editor
,
$output
,
&
$tab
)
{
if
(
$editor
->
getTitle
()->
hasContentModel
(
CONTENT_MODEL_SCRIBUNTO
)
)
{
$output
->
addModules
(
'ext.scribunto.edit'
);
$editor
->
editFormTextAfterTools
.=
'<div id="mw-scribunto-console"></div>'
;
}
}
/**
* EditPage::showReadOnlyForm:initial hook
*
* @param EditPage $editor
* @param OutputPage $output
* @return void
*/
public
function
onEditPage__showReadOnlyForm_initial
(
$editor
,
$output
)
{
if
(
$editor
->
getTitle
()->
hasContentModel
(
CONTENT_MODEL_SCRIBUNTO
)
)
{
$output
->
addModules
(
'ext.scribunto.edit'
);
$editor
->
editFormTextAfterContent
.=
'<div id="mw-scribunto-console"></div>'
;
}
}
/**
* EditPageBeforeEditButtons hook
*
* @param EditPage $editor
* @param array &$buttons Button array
* @param int &$tabindex Current tabindex
* @return void
*/
public
function
onEditPageBeforeEditButtons
(
$editor
,
&
$buttons
,
&
$tabindex
)
{
if
(
$editor
->
getTitle
()->
hasContentModel
(
CONTENT_MODEL_SCRIBUNTO
)
)
{
unset
(
$buttons
[
'preview'
]
);
}
}
/**
* @param IContextSource $context
* @param Content $content
* @param Status $status
* @param string $summary
* @param User $user
* @param bool $minoredit
* @return bool
*/
public
function
onEditFilterMergedContent
(
IContextSource
$context
,
Content
$content
,
Status
$status
,
$summary
,
User
$user
,
$minoredit
)
{
$title
=
$context
->
getTitle
();
if
(
!
$content
instanceof
ScribuntoContent
)
{
return
true
;
}
$contentHandlerFactory
=
MediaWikiServices
::
getInstance
()->
getContentHandlerFactory
();
$contentHandler
=
$contentHandlerFactory
->
getContentHandler
(
$content
->
getModel
()
);
'@phan-var ScribuntoContentHandler $contentHandler'
;
$validateStatus
=
$contentHandler
->
validate
(
$content
,
$title
);
if
(
$validateStatus
->
isOK
()
)
{
return
true
;
}
$status
->
merge
(
$validateStatus
);
if
(
isset
(
$validateStatus
->
value
->
params
[
'module'
]
)
)
{
$module
=
$validateStatus
->
value
->
params
[
'module'
];
$line
=
$validateStatus
->
value
->
params
[
'line'
];
if
(
$module
===
$title
->
getPrefixedDBkey
()
&&
preg_match
(
'/^
\d
+$/'
,
$line
)
)
{
$out
=
$context
->
getOutput
();
$out
->
addInlineScript
(
'window.location.hash = '
.
Html
::
encodeJsVar
(
"#mw-ce-l$line"
)
);
}
}
if
(
!
$status
->
isOK
()
)
{
// @todo Remove this line after this extension do not support mediawiki version 1.36 and before
$status
->
value
=
EditPage
::
AS_HOOK_ERROR_EXPECTED
;
return
false
;
}
return
true
;
}
/**
* @param Article $article
* @param bool|ParserOutput|null &$outputDone
* @param bool &$pcache
* @return void
*/
public
function
onArticleViewHeader
(
$article
,
&
$outputDone
,
&
$pcache
)
{
$title
=
$article
->
getTitle
();
if
(
Scribunto
::
isDocPage
(
$title
,
$forModule
)
)
{
$article
->
getContext
()->
getOutput
()->
addHTML
(
wfMessage
(
'scribunto-doc-page-header'
,
$forModule
->
getPrefixedText
()
)->
parseAsBlock
()
);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Aug 15 2026, 16:40 (12 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c2/a3/668fba237b8af5b6d33eca38f684
Default Alt Text
Hooks.php (13 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment