MediaWiki  1.34.0
Hooks.php
Go to the documentation of this file.
1 <?php
24 use UtfNormal\Validator;
25 use Wikimedia\PSquare;
26 
31 
35  public static function onRegistration() {
36  define( 'CONTENT_MODEL_SCRIBUNTO', 'Scribunto' );
37  }
38 
45  public static function getSoftwareInfo( array &$software ) {
46  $engine = Scribunto::newDefaultEngine();
47  $engine->setTitle( Title::makeTitle( NS_SPECIAL, 'Version' ) );
48  $engine->getSoftwareInfo( $software );
49  return true;
50  }
51 
58  public static function setupParserHook( Parser &$parser ) {
59  $parser->setFunctionHook( 'invoke', 'ScribuntoHooks::invokeHook', Parser::SFH_OBJECT_ARGS );
60  return true;
61  }
62 
69  public static function clearState( Parser &$parser ) {
71  return true;
72  }
73 
80  public static function parserCloned( Parser $parser ) {
81  $parser->scribunto_engine = null;
82  return true;
83  }
84 
95  public static function invokeHook( Parser &$parser, PPFrame $frame, array $args ) {
96  global $wgScribuntoGatherFunctionStats;
97 
98  try {
99  if ( count( $args ) < 2 ) {
100  throw new ScribuntoException( 'scribunto-common-nofunction' );
101  }
102  $moduleName = trim( $frame->expand( $args[0] ) );
103  $engine = Scribunto::getParserEngine( $parser );
104 
105  $title = Title::makeTitleSafe( NS_MODULE, $moduleName );
106  if ( !$title || !$title->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
107  throw new ScribuntoException( 'scribunto-common-nosuchmodule',
108  [ 'args' => [ $moduleName ] ] );
109  }
110  $module = $engine->fetchModuleFromParser( $title );
111  if ( !$module ) {
112  throw new ScribuntoException( 'scribunto-common-nosuchmodule',
113  [ 'args' => [ $moduleName ] ] );
114  }
115  $functionName = trim( $frame->expand( $args[1] ) );
116 
117  $bits = $args[1]->splitArg();
118  unset( $args[0] );
119  unset( $args[1] );
120 
121  // If $bits['index'] is empty, then the function name was parsed as a
122  // key=value pair (because of an equals sign in it), and since it didn't
123  // have an index, we don't need the index offset.
124  $childFrame = $frame->newChild( $args, $title, $bits['index'] === '' ? 0 : 1 );
125 
126  if ( $wgScribuntoGatherFunctionStats ) {
127  $u0 = $engine->getResourceUsage( $engine::CPU_SECONDS );
128  $result = $module->invoke( $functionName, $childFrame );
129  $u1 = $engine->getResourceUsage( $engine::CPU_SECONDS );
130 
131  if ( $u1 > $u0 ) {
132  $timingMs = (int)( 1000 * ( $u1 - $u0 ) );
133  // Since the overhead of stats is worst when when #invoke
134  // calls are very short, don't process measurements <= 20ms.
135  if ( $timingMs > 20 ) {
136  self::reportTiming( $moduleName, $functionName, $timingMs );
137  }
138  }
139  } else {
140  $result = $module->invoke( $functionName, $childFrame );
141  }
142 
143  return Validator::cleanUp( strval( $result ) );
144  } catch ( ScribuntoException $e ) {
145  $trace = $e->getScriptTraceHtml( [ 'msgOptions' => [ 'content' ] ] );
146  $html = Html::element( 'p', [], $e->getMessage() );
147  if ( $trace !== false ) {
148  $html .= Html::element( 'p',
149  [],
150  wfMessage( 'scribunto-common-backtrace' )->inContentLanguage()->text()
151  ) . $trace;
152  } else {
153  $html .= Html::element( 'p',
154  [],
155  wfMessage( 'scribunto-common-no-details' )->inContentLanguage()->text()
156  );
157  }
158  $out = $parser->getOutput();
159  $errors = $out->getExtensionData( 'ScribuntoErrors' );
160  if ( $errors === null ) {
161  // On first hook use, set up error array and output
162  $errors = [];
163  $parser->addTrackingCategory( 'scribunto-common-error-category' );
164  $out->addModules( 'ext.scribunto.errors' );
165  }
166  $errors[] = $html;
167  $out->setExtensionData( 'ScribuntoErrors', $errors );
168  $out->addJsConfigVars( 'ScribuntoErrors', $errors );
169  $id = 'mw-scribunto-error-' . ( count( $errors ) - 1 );
170  $parserError = htmlspecialchars( $e->getMessage() );
171 
172  // #iferror-compatible error element
173  return "<strong class=\"error\"><span class=\"scribunto-error\" id=\"$id\">" .
174  $parserError . "</span></strong>";
175  }
176  }
177 
185  public static function reportTiming( $moduleName, $functionName, $timing ) {
186  global $wgScribuntoGatherFunctionStats, $wgScribuntoSlowFunctionThreshold;
187 
188  if ( !$wgScribuntoGatherFunctionStats ) {
189  return;
190  }
191 
192  $threshold = $wgScribuntoSlowFunctionThreshold;
193  if ( !( is_float( $threshold ) && $threshold > 0 && $threshold < 1 ) ) {
194  return;
195  }
196 
197  static $cache;
198 
199  if ( !$cache ) {
201 
202  }
203 
204  // To control the sampling rate, we keep a compact histogram of
205  // observations in APC, and extract the Nth percentile (specified
206  // via $wgScribuntoSlowFunctionThreshold; defaults to 0.90).
207  // We need APC and \Wikimedia\PSquare to do that.
208  if ( !class_exists( PSquare::class ) || $cache instanceof EmptyBagOStuff ) {
209  return;
210  }
211 
212  $cacheVersion = '1';
213  $key = $cache->makeGlobalKey( __METHOD__, $cacheVersion, $threshold );
214 
215  // This is a classic "read-update-write" critical section with no
216  // mutual exclusion, but the only consequence is that some samples
217  // will be dropped. We only need enough samples to estimate the
218  // the shape of the data, so that's fine.
219  $ps = $cache->get( $key ) ?: new PSquare( $threshold );
220  $ps->addObservation( $timing );
221  $cache->set( $key, $ps, 60 );
222 
223  if ( $ps->getCount() < 1000 || $timing < $ps->getValue() ) {
224  return;
225  }
226 
227  static $stats;
228 
229  if ( !$stats ) {
230  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
231  }
232 
233  $metricKey = sprintf( 'scribunto.traces.%s__%s__%s', wfWikiId(), $moduleName, $functionName );
234  $stats->timing( $metricKey, $timing );
235  }
236 
242  public static function getCodeLanguage( Title $title, &$languageCode ) {
243  global $wgScribuntoUseCodeEditor;
244  if ( $wgScribuntoUseCodeEditor && $title->hasContentModel( CONTENT_MODEL_SCRIBUNTO )
245  ) {
246  $engine = Scribunto::newDefaultEngine();
247  if ( $engine->getCodeEditorLanguage() ) {
248  $languageCode = $engine->getCodeEditorLanguage();
249  return false;
250  }
251  }
252 
253  return true;
254  }
255 
263  public static function contentHandlerDefaultModelFor( Title $title, &$model ) {
264  if ( $model === 'sanitized-css' ) {
265  // Let TemplateStyles override Scribunto
266  return true;
267  }
268  if ( $title->getNamespace() == NS_MODULE && !Scribunto::isDocPage( $title ) ) {
269  $model = CONTENT_MODEL_SCRIBUNTO;
270  return true;
271  }
272  return true;
273  }
274 
282  public static function reportLimitData( Parser $parser, ParserOutput $output ) {
283  if ( Scribunto::isParserEnginePresent( $parser ) ) {
284  $engine = Scribunto::getParserEngine( $parser );
285  $engine->reportLimitData( $output );
286  }
287  return true;
288  }
289 
300  public static function formatLimitData( $key, &$value, &$report, $isHTML, $localize ) {
301  $engine = Scribunto::newDefaultEngine();
302  return $engine->formatLimitData( $key, $value, $report, $isHTML, $localize );
303  }
304 
313  public static function showStandardInputsOptions( EditPage $editor, OutputPage $output, &$tab ) {
314  if ( $editor->getTitle()->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
315  $output->addModules( 'ext.scribunto.edit' );
316  $editor->editFormTextAfterTools .= '<div id="mw-scribunto-console"></div>';
317  }
318  return true;
319  }
320 
328  public static function showReadOnlyFormInitial( EditPage $editor, OutputPage $output ) {
329  if ( $editor->getTitle()->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
330  $output->addModules( 'ext.scribunto.edit' );
331  $editor->editFormTextAfterContent .= '<div id="mw-scribunto-console"></div>';
332  }
333  return true;
334  }
335 
344  public static function beforeEditButtons( EditPage &$editor, array &$buttons, &$tabindex ) {
345  if ( $editor->getTitle()->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
346  unset( $buttons['preview'] );
347  }
348  return true;
349  }
350 
359  ) {
360  $title = $context->getTitle();
361 
362  if ( !$content instanceof ScribuntoContent ) {
363  return true;
364  }
365 
366  $validateStatus = $content->validate( $title );
367  if ( $validateStatus->isOK() ) {
368  return true;
369  }
370 
371  $status->merge( $validateStatus );
372 
373  if ( isset( $validateStatus->scribunto_error->params['module'] ) ) {
374  $module = $validateStatus->scribunto_error->params['module'];
375  $line = $validateStatus->scribunto_error->params['line'];
376  if ( $module === $title->getPrefixedDBkey() && preg_match( '/^\d+$/', $line ) ) {
377  $out = $context->getOutput();
378  $out->addInlineScript( 'window.location.hash = ' . Xml::encodeJsVar( "#mw-ce-l$line" ) );
379  }
380  }
381 
382  return true;
383  }
384 
391  public static function showDocPageHeader( Article &$article, &$outputDone, &$pcache ) {
392  $title = $article->getTitle();
393  if ( Scribunto::isDocPage( $title, $forModule ) ) {
394  $article->getContext()->getOutput()->addHTML(
395  wfMessage( 'scribunto-doc-page-header', $forModule->getPrefixedText() )->parseAsBlock()
396  );
397  }
398  return true;
399  }
400 }
ScribuntoHooks\validateScript
static validateScript(IContextSource $context, Content $content, Status $status)
Definition: Hooks.php:357
ParserOutput
Definition: ParserOutput.php:25
ScribuntoHooks
Hooks for the Scribunto extension.
Definition: Hooks.php:30
EmptyBagOStuff
A BagOStuff object with no objects in it.
Definition: EmptyBagOStuff.php:29
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
CACHE_NONE
const CACHE_NONE
Definition: Defines.php:82
SFH_OBJECT_ARGS
const SFH_OBJECT_ARGS
Definition: Defines.php:178
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1264
Scribunto\isParserEnginePresent
static isParserEnginePresent(Parser $parser)
Check if an engine instance is present in the given parser.
Definition: Common.php:70
Xml\encodeJsVar
static encodeJsVar( $value, $pretty=false)
Encode a variable of arbitrary type to JavaScript.
Definition: Xml.php:659
PPFrame\newChild
newChild( $args=false, $title=false, $indexOffset=0)
Create a child frame.
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
ScribuntoHooks\reportTiming
static reportTiming( $moduleName, $functionName, $timing)
Record stats on slow function calls.
Definition: Hooks.php:185
ScribuntoHooks\getCodeLanguage
static getCodeLanguage(Title $title, &$languageCode)
Definition: Hooks.php:242
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:49
Scribunto\newDefaultEngine
static newDefaultEngine( $extraOptions=[])
Create a new engine object with default parameters.
Definition: Common.php:32
Scribunto\getParserEngine
static getParserEngine(Parser $parser)
Get an engine instance for the given parser, and cache it in the parser so that subsequent calls to t...
Definition: Common.php:56
ScribuntoContent
Represents the content of a Scribunto script page.
Definition: ScribuntoContent.php:15
Article\getTitle
getTitle()
Get the title object of the article.
Definition: Article.php:221
ScribuntoHooks\onRegistration
static onRegistration()
Define content handler constant upon extension registration.
Definition: Hooks.php:35
NS_MODULE
const NS_MODULE
Definition: Scribunto.constants.php:5
ScribuntoHooks\showStandardInputsOptions
static showStandardInputsOptions(EditPage $editor, OutputPage $output, &$tab)
EditPage::showStandardInputs:options hook.
Definition: Hooks.php:313
Article\getContext
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2267
$title
$title
Definition: testCompression.php:34
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
$output
$output
Definition: SyntaxHighlight.php:335
ScribuntoException\getScriptTraceHtml
getScriptTraceHtml( $options=[])
Get the backtrace as HTML, or false if there is none available.
Definition: Common.php:206
ScribuntoHooks\clearState
static clearState(Parser &$parser)
Called when the interpreter is to be reset.
Definition: Hooks.php:69
ScribuntoHooks\getSoftwareInfo
static getSoftwareInfo(array &$software)
Get software information for Special:Version.
Definition: Hooks.php:45
ScribuntoHooks\parserCloned
static parserCloned(Parser $parser)
Called when the parser is cloned.
Definition: Hooks.php:80
$line
$line
Definition: cdb.php:59
Scribunto\resetParserEngine
static resetParserEngine(Parser $parser)
Remove the current engine instance from the parser.
Definition: Common.php:78
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
$content
$content
Definition: router.php:78
PPFrame\expand
expand( $root, $flags=0)
Expand a document tree node.
PPFrame
Definition: PPFrame.php:28
ScribuntoHooks\invokeHook
static invokeHook(Parser &$parser, PPFrame $frame, array $args)
Hook function for {{#invoke:module|func}}.
Definition: Hooks.php:95
ScribuntoException
An exception class which represents an error in the script.
Definition: Common.php:136
EditPage
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition: EditPage.php:46
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
$context
$context
Definition: load.php:45
Content
Base interface for content objects.
Definition: Content.php:34
ScribuntoHooks\setupParserHook
static setupParserHook(Parser &$parser)
Register parser hooks.
Definition: Hooks.php:58
$args
if( $line===false) $args
Definition: cdb.php:64
Title
Represents a title within MediaWiki.
Definition: Title.php:42
$status
return $status
Definition: SyntaxHighlight.php:347
$cache
$cache
Definition: mcc.php:33
ScribuntoHooks\showDocPageHeader
static showDocPageHeader(Article &$article, &$outputDone, &$pcache)
Definition: Hooks.php:391
EditPage\getTitle
getTitle()
Definition: EditPage.php:517
ScribuntoHooks\beforeEditButtons
static beforeEditButtons(EditPage &$editor, array &$buttons, &$tabindex)
EditPageBeforeEditButtons hook.
Definition: Hooks.php:344
ScribuntoHooks\reportLimitData
static reportLimitData(Parser $parser, ParserOutput $output)
Adds report of number of evaluations by the single wikitext page.
Definition: Hooks.php:282
ScribuntoHooks\contentHandlerDefaultModelFor
static contentHandlerDefaultModelFor(Title $title, &$model)
Set the Scribunto content handler for modules.
Definition: Hooks.php:263
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:38
Scribunto\isDocPage
static isDocPage(Title $title, Title &$forModule=null)
Test whether the page should be considered a documentation page.
Definition: Common.php:92
ScribuntoHooks\formatLimitData
static formatLimitData( $key, &$value, &$report, $isHTML, $localize)
Formats the limit report data.
Definition: Hooks.php:300
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:268
ScribuntoHooks\showReadOnlyFormInitial
static showReadOnlyFormInitial(EditPage $editor, OutputPage $output)
EditPage::showReadOnlyForm:initial hook.
Definition: Hooks.php:328