MediaWiki  1.32.5
Parser.php
Go to the documentation of this file.
1 <?php
26 use Wikimedia\ScopedCallback;
27 
68 class Parser {
74  const VERSION = '1.6.4';
75 
80  const HALF_PARSED_VERSION = 2;
81 
82  # Flags for Parser::setFunctionHook
83  const SFH_NO_HASH = 1;
84  const SFH_OBJECT_ARGS = 2;
85 
86  # Constants needed for external link processing
87  # Everything except bracket, space, or control characters
88  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
89  # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
90  # \x{FFFD} is the Unicode replacement character, which Preprocessor_DOM
91  # uses to replace invalid HTML characters.
92  const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]';
93  # Simplified expression to match an IPv4 or IPv6 address, or
94  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
95  const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
96  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
97  // phpcs:ignore Generic.Files.LineLength
98  const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
99  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
100 
101  # Regular expression for a non-newline space
102  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
103 
104  # Flags for preprocessToDom
105  const PTD_FOR_INCLUSION = 1;
106 
107  # Allowed values for $this->mOutputType
108  # Parameter to startExternalParse().
109  const OT_HTML = 1; # like parse()
110  const OT_WIKI = 2; # like preSaveTransform()
111  const OT_PREPROCESS = 3; # like preprocess()
112  const OT_MSG = 3;
113  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
114 
132  const MARKER_SUFFIX = "-QINU`\"'\x7f";
133  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
134 
135  # Markers used for wrapping the table of contents
136  const TOC_START = '<mw:toc>';
137  const TOC_END = '</mw:toc>';
138 
140  const MAX_TTS = 900;
141 
142  # Persistent:
143  public $mTagHooks = [];
144  public $mTransparentTagHooks = [];
145  public $mFunctionHooks = [];
146  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
147  public $mFunctionTagHooks = [];
148  public $mStripList = [];
149  public $mDefaultStripList = [];
150  public $mVarCache = [];
151  public $mImageParams = [];
152  public $mImageParamsMagicArray = [];
153  public $mMarkerIndex = 0;
157  public $mFirstCall = true;
158 
159  # Initialised by initialiseVariables()
160 
164  public $mVariables;
165 
169  public $mSubstWords;
170  # Initialised in constructor
171  public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
172 
173  # Initialized in getPreprocessor()
174 
175  public $mPreprocessor;
176 
177  # Cleared with clearState():
178 
181  public $mOutput;
182  public $mAutonumber;
183 
187  public $mStripState;
188 
189  public $mIncludeCount;
193  public $mLinkHolders;
194 
195  public $mLinkID;
196  public $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth;
197  public $mDefaultSort;
198  public $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
199  public $mExpensiveFunctionCount; # number of expensive parser function calls
200  public $mShowToc, $mForceTocPosition;
201 
205  public $mUser; # User object; only used when doing pre-save transform
206 
207  # Temporary
208  # These are variables reset at least once per parse regardless of $clearState
209 
213  public $mOptions;
214 
218  public $mTitle; # Title context, used for self-link rendering and similar things
219  public $mOutputType; # Output type, one of the OT_xxx constants
220  public $ot; # Shortcut alias, see setOutputType()
221  public $mRevisionObject; # The revision object of the specified revision ID
222  public $mRevisionId; # ID to display in {{REVISIONID}} tags
223  public $mRevisionTimestamp; # The timestamp of the specified revision ID
224  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
225  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
226  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
227  public $mInputSize = false; # For {{PAGESIZE}} on current page.
228 
233  public $mUniqPrefix = self::MARKER_PREFIX;
234 
240  public $mLangLinkLanguages;
241 
248  public $currentRevisionCache;
249 
254  public $mInParse = false;
255 
257  protected $mProfiler;
258 
262  protected $mLinkRenderer;
263 
265  private $magicWordFactory;
266 
268  private $contLang;
269 
271  private $factory;
272 
274  private $specialPageFactory;
275 
284  public function __construct(
285  array $conf = [], MagicWordFactory $magicWordFactory = null, Language $contLang = null,
286  ParserFactory $factory = null, $urlProtocols = null, SpecialPageFactory $spFactory = null
287  ) {
288  $this->mConf = $conf;
289  $this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
290  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
291  self::EXT_LINK_ADDR .
292  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
293  if ( isset( $conf['preprocessorClass'] ) ) {
294  $this->mPreprocessorClass = $conf['preprocessorClass'];
295  } elseif ( wfIsHHVM() ) {
296  # Under HHVM Preprocessor_Hash is much faster than Preprocessor_DOM
297  $this->mPreprocessorClass = Preprocessor_Hash::class;
298  } elseif ( extension_loaded( 'domxml' ) ) {
299  # PECL extension that conflicts with the core DOM extension (T15770)
300  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
301  $this->mPreprocessorClass = Preprocessor_Hash::class;
302  } elseif ( extension_loaded( 'dom' ) ) {
303  $this->mPreprocessorClass = Preprocessor_DOM::class;
304  } else {
305  $this->mPreprocessorClass = Preprocessor_Hash::class;
306  }
307  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
308 
309  $services = MediaWikiServices::getInstance();
310  $this->magicWordFactory = $magicWordFactory ??
311  $services->getMagicWordFactory();
312 
313  $this->contLang = $contLang ?? $services->getContentLanguage();
314 
315  $this->factory = $factory ?? $services->getParserFactory();
316  $this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
317  }
318 
322  public function __destruct() {
323  if ( isset( $this->mLinkHolders ) ) {
324  unset( $this->mLinkHolders );
325  }
326  foreach ( $this as $name => $value ) {
327  unset( $this->$name );
328  }
329  }
330 
334  public function __clone() {
335  $this->mInParse = false;
336 
337  // T58226: When you create a reference "to" an object field, that
338  // makes the object field itself be a reference too (until the other
339  // reference goes out of scope). When cloning, any field that's a
340  // reference is copied as a reference in the new object. Both of these
341  // are defined PHP5 behaviors, as inconvenient as it is for us when old
342  // hooks from PHP4 days are passing fields by reference.
343  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
344  // Make a non-reference copy of the field, then rebind the field to
345  // reference the new copy.
346  $tmp = $this->$k;
347  $this->$k =& $tmp;
348  unset( $tmp );
349  }
350 
351  Hooks::run( 'ParserCloned', [ $this ] );
352  }
353 
357  public function firstCallInit() {
358  if ( !$this->mFirstCall ) {
359  return;
360  }
361  $this->mFirstCall = false;
362 
364  CoreTagHooks::register( $this );
365  $this->initialiseVariables();
366 
367  // Avoid PHP 7.1 warning from passing $this by reference
368  $parser = $this;
369  Hooks::run( 'ParserFirstCallInit', [ &$parser ] );
370  }
371 
377  public function clearState() {
378  $this->firstCallInit();
379  $this->mOutput = new ParserOutput;
380  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
381  $this->mAutonumber = 0;
382  $this->mIncludeCount = [];
383  $this->mLinkHolders = new LinkHolderArray( $this );
384  $this->mLinkID = 0;
385  $this->mRevisionObject = $this->mRevisionTimestamp =
386  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
387  $this->mVarCache = [];
388  $this->mUser = null;
389  $this->mLangLinkLanguages = [];
390  $this->currentRevisionCache = null;
391 
392  $this->mStripState = new StripState( $this );
393 
394  # Clear these on every parse, T6549
395  $this->mTplRedirCache = $this->mTplDomCache = [];
396 
397  $this->mShowToc = true;
398  $this->mForceTocPosition = false;
399  $this->mIncludeSizes = [
400  'post-expand' => 0,
401  'arg' => 0,
402  ];
403  $this->mPPNodeCount = 0;
404  $this->mGeneratedPPNodeCount = 0;
405  $this->mHighestExpansionDepth = 0;
406  $this->mDefaultSort = false;
407  $this->mHeadings = [];
408  $this->mDoubleUnderscores = [];
409  $this->mExpensiveFunctionCount = 0;
410 
411  # Fix cloning
412  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
413  $this->mPreprocessor = null;
414  }
415 
416  $this->mProfiler = new SectionProfiler();
417 
418  // Avoid PHP 7.1 warning from passing $this by reference
419  $parser = $this;
420  Hooks::run( 'ParserClearState', [ &$parser ] );
421  }
422 
437  public function parse(
439  $linestart = true, $clearState = true, $revid = null
440  ) {
441  if ( $clearState ) {
442  // We use U+007F DELETE to construct strip markers, so we have to make
443  // sure that this character does not occur in the input text.
444  $text = strtr( $text, "\x7f", "?" );
445  $magicScopeVariable = $this->lock();
446  }
447  // Strip U+0000 NULL (T159174)
448  $text = str_replace( "\000", '', $text );
449 
450  $this->startParse( $title, $options, self::OT_HTML, $clearState );
451 
452  $this->currentRevisionCache = null;
453  $this->mInputSize = strlen( $text );
454  if ( $this->mOptions->getEnableLimitReport() ) {
455  $this->mOutput->resetParseStartTime();
456  }
457 
458  $oldRevisionId = $this->mRevisionId;
459  $oldRevisionObject = $this->mRevisionObject;
460  $oldRevisionTimestamp = $this->mRevisionTimestamp;
461  $oldRevisionUser = $this->mRevisionUser;
462  $oldRevisionSize = $this->mRevisionSize;
463  if ( $revid !== null ) {
464  $this->mRevisionId = $revid;
465  $this->mRevisionObject = null;
466  $this->mRevisionTimestamp = null;
467  $this->mRevisionUser = null;
468  $this->mRevisionSize = null;
469  }
470 
471  // Avoid PHP 7.1 warning from passing $this by reference
472  $parser = $this;
473  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
474  # No more strip!
475  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
476  $text = $this->internalParse( $text );
477  Hooks::run( 'ParserAfterParse', [ &$parser, &$text, &$this->mStripState ] );
478 
479  $text = $this->internalParseHalfParsed( $text, true, $linestart );
480 
488  if ( !( $options->getDisableTitleConversion()
489  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
490  || isset( $this->mDoubleUnderscores['notitleconvert'] )
491  || $this->mOutput->getDisplayTitle() !== false )
492  ) {
493  $convruletitle = $this->getTargetLanguage()->getConvRuleTitle();
494  if ( $convruletitle ) {
495  $this->mOutput->setTitleText( $convruletitle );
496  } else {
497  $titleText = $this->getTargetLanguage()->convertTitle( $title );
498  $this->mOutput->setTitleText( $titleText );
499  }
500  }
501 
502  # Compute runtime adaptive expiry if set
503  $this->mOutput->finalizeAdaptiveCacheExpiry();
504 
505  # Warn if too many heavyweight parser functions were used
506  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
507  $this->limitationWarn( 'expensive-parserfunction',
508  $this->mExpensiveFunctionCount,
509  $this->mOptions->getExpensiveParserFunctionLimit()
510  );
511  }
512 
513  # Information on limits, for the benefit of users who try to skirt them
514  if ( $this->mOptions->getEnableLimitReport() ) {
515  $text .= $this->makeLimitReport();
516  }
517 
518  # Wrap non-interface parser output in a <div> so it can be targeted
519  # with CSS (T37247)
520  $class = $this->mOptions->getWrapOutputClass();
521  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
522  $this->mOutput->addWrapperDivClass( $class );
523  }
524 
525  $this->mOutput->setText( $text );
526 
527  $this->mRevisionId = $oldRevisionId;
528  $this->mRevisionObject = $oldRevisionObject;
529  $this->mRevisionTimestamp = $oldRevisionTimestamp;
530  $this->mRevisionUser = $oldRevisionUser;
531  $this->mRevisionSize = $oldRevisionSize;
532  $this->mInputSize = false;
533  $this->currentRevisionCache = null;
534 
535  return $this->mOutput;
536  }
537 
544  protected function makeLimitReport() {
545  global $wgShowHostnames;
546 
547  $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
548 
549  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
550  if ( $cpuTime !== null ) {
551  $this->mOutput->setLimitReportData( 'limitreport-cputime',
552  sprintf( "%.3f", $cpuTime )
553  );
554  }
555 
556  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
557  $this->mOutput->setLimitReportData( 'limitreport-walltime',
558  sprintf( "%.3f", $wallTime )
559  );
560 
561  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
562  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
563  );
564  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
565  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
566  );
567  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
568  [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
569  );
570  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
571  [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
572  );
573  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
574  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
575  );
576  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
577  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
578  );
579 
580  foreach ( $this->mStripState->getLimitReport() as list( $key, $value ) ) {
581  $this->mOutput->setLimitReportData( $key, $value );
582  }
583 
584  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
585 
586  $limitReport = "NewPP limit report\n";
587  if ( $wgShowHostnames ) {
588  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
589  }
590  $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
591  $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
592  $limitReport .= 'Dynamic content: ' .
593  ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
594  "\n";
595 
596  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
597  if ( Hooks::run( 'ParserLimitReportFormat',
598  [ $key, &$value, &$limitReport, false, false ]
599  ) ) {
600  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
601  $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
602  ->inLanguage( 'en' )->useDatabase( false );
603  if ( !$valueMsg->exists() ) {
604  $valueMsg = new RawMessage( '$1' );
605  }
606  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
607  $valueMsg->params( $value );
608  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
609  }
610  }
611  }
612  // Since we're not really outputting HTML, decode the entities and
613  // then re-encode the things that need hiding inside HTML comments.
614  $limitReport = htmlspecialchars_decode( $limitReport );
615 
616  // Sanitize for comment. Note '‐' in the replacement is U+2010,
617  // which looks much like the problematic '-'.
618  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
619  $text = "\n<!-- \n$limitReport-->\n";
620 
621  // Add on template profiling data in human/machine readable way
622  $dataByFunc = $this->mProfiler->getFunctionStats();
623  uasort( $dataByFunc, function ( $a, $b ) {
624  return $b['real'] <=> $a['real']; // descending order
625  } );
626  $profileReport = [];
627  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
628  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
629  $item['%real'], $item['real'], $item['calls'],
630  htmlspecialchars( $item['name'] ) );
631  }
632  $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
633  $text .= implode( "\n", $profileReport ) . "\n-->\n";
634 
635  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
636 
637  // Add other cache related metadata
638  if ( $wgShowHostnames ) {
639  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
640  }
641  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
642  $this->mOutput->getCacheTime() );
643  $this->mOutput->setLimitReportData( 'cachereport-ttl',
644  $this->mOutput->getCacheExpiry() );
645  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
646  $this->mOutput->hasDynamicContent() );
647 
648  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
649  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
650  $this->mTitle->getPrefixedDBkey() );
651  }
652  return $text;
653  }
654 
679  public function recursiveTagParse( $text, $frame = false ) {
680  // Avoid PHP 7.1 warning from passing $this by reference
681  $parser = $this;
682  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
683  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
684  $text = $this->internalParse( $text, false, $frame );
685  return $text;
686  }
687 
707  public function recursiveTagParseFully( $text, $frame = false ) {
708  $text = $this->recursiveTagParse( $text, $frame );
709  $text = $this->internalParseHalfParsed( $text, false );
710  return $text;
711  }
712 
724  public function preprocess( $text, Title $title = null,
725  ParserOptions $options, $revid = null, $frame = false
726  ) {
727  $magicScopeVariable = $this->lock();
728  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
729  if ( $revid !== null ) {
730  $this->mRevisionId = $revid;
731  }
732  // Avoid PHP 7.1 warning from passing $this by reference
733  $parser = $this;
734  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
735  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
736  $text = $this->replaceVariables( $text, $frame );
737  $text = $this->mStripState->unstripBoth( $text );
738  return $text;
739  }
740 
750  public function recursivePreprocess( $text, $frame = false ) {
751  $text = $this->replaceVariables( $text, $frame );
752  $text = $this->mStripState->unstripBoth( $text );
753  return $text;
754  }
755 
769  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
770  $msg = new RawMessage( $text );
771  $text = $msg->params( $params )->plain();
772 
773  # Parser (re)initialisation
774  $magicScopeVariable = $this->lock();
775  $this->startParse( $title, $options, self::OT_PLAIN, true );
776 
778  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
779  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
780  $text = $this->mStripState->unstripBoth( $text );
781  return $text;
782  }
783 
790  public function setUser( $user ) {
791  $this->mUser = $user;
792  }
793 
799  public function setTitle( $t ) {
800  if ( !$t ) {
801  $t = Title::newFromText( 'NO TITLE' );
802  }
803 
804  if ( $t->hasFragment() ) {
805  # Strip the fragment to avoid various odd effects
806  $this->mTitle = $t->createFragmentTarget( '' );
807  } else {
808  $this->mTitle = $t;
809  }
810  }
811 
817  public function getTitle() {
818  return $this->mTitle;
819  }
820 
827  public function Title( $x = null ) {
828  return wfSetVar( $this->mTitle, $x );
829  }
830 
836  public function setOutputType( $ot ) {
837  $this->mOutputType = $ot;
838  # Shortcut alias
839  $this->ot = [
840  'html' => $ot == self::OT_HTML,
841  'wiki' => $ot == self::OT_WIKI,
842  'pre' => $ot == self::OT_PREPROCESS,
843  'plain' => $ot == self::OT_PLAIN,
844  ];
845  }
846 
853  public function OutputType( $x = null ) {
854  return wfSetVar( $this->mOutputType, $x );
855  }
856 
862  public function getOutput() {
863  return $this->mOutput;
864  }
865 
871  public function getOptions() {
872  return $this->mOptions;
873  }
874 
881  public function Options( $x = null ) {
882  return wfSetVar( $this->mOptions, $x );
883  }
884 
888  public function nextLinkID() {
889  return $this->mLinkID++;
890  }
891 
895  public function setLinkID( $id ) {
896  $this->mLinkID = $id;
897  }
898 
903  public function getFunctionLang() {
904  return $this->getTargetLanguage();
905  }
906 
916  public function getTargetLanguage() {
917  $target = $this->mOptions->getTargetLanguage();
918 
919  if ( $target !== null ) {
920  return $target;
921  } elseif ( $this->mOptions->getInterfaceMessage() ) {
922  return $this->mOptions->getUserLangObj();
923  } elseif ( is_null( $this->mTitle ) ) {
924  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
925  }
926 
927  return $this->mTitle->getPageLanguage();
928  }
929 
935  public function getConverterLanguage() {
936  return $this->getTargetLanguage();
937  }
938 
945  public function getUser() {
946  if ( !is_null( $this->mUser ) ) {
947  return $this->mUser;
948  }
949  return $this->mOptions->getUser();
950  }
951 
957  public function getPreprocessor() {
958  if ( !isset( $this->mPreprocessor ) ) {
959  $class = $this->mPreprocessorClass;
960  $this->mPreprocessor = new $class( $this );
961  }
962  return $this->mPreprocessor;
963  }
964 
971  public function getLinkRenderer() {
972  if ( !$this->mLinkRenderer ) {
973  $this->mLinkRenderer = MediaWikiServices::getInstance()
974  ->getLinkRendererFactory()->create();
975  $this->mLinkRenderer->setStubThreshold(
976  $this->getOptions()->getStubThreshold()
977  );
978  }
979 
980  return $this->mLinkRenderer;
981  }
982 
989  public function getMagicWordFactory() {
990  return $this->magicWordFactory;
991  }
992 
999  public function getContentLanguage() {
1000  return $this->contLang;
1001  }
1002 
1022  public static function extractTagsAndParams( $elements, $text, &$matches ) {
1023  static $n = 1;
1024  $stripped = '';
1025  $matches = [];
1026 
1027  $taglist = implode( '|', $elements );
1028  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
1029 
1030  while ( $text != '' ) {
1031  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
1032  $stripped .= $p[0];
1033  if ( count( $p ) < 5 ) {
1034  break;
1035  }
1036  if ( count( $p ) > 5 ) {
1037  # comment
1038  $element = $p[4];
1039  $attributes = '';
1040  $close = '';
1041  $inside = $p[5];
1042  } else {
1043  # tag
1044  $element = $p[1];
1045  $attributes = $p[2];
1046  $close = $p[3];
1047  $inside = $p[4];
1048  }
1049 
1050  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1051  $stripped .= $marker;
1052 
1053  if ( $close === '/>' ) {
1054  # Empty element tag, <tag />
1055  $content = null;
1056  $text = $inside;
1057  $tail = null;
1058  } else {
1059  if ( $element === '!--' ) {
1060  $end = '/(-->)/';
1061  } else {
1062  $end = "/(<\\/$element\\s*>)/i";
1063  }
1064  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1065  $content = $q[0];
1066  if ( count( $q ) < 3 ) {
1067  # No end tag -- let it run out to the end of the text.
1068  $tail = '';
1069  $text = '';
1070  } else {
1071  $tail = $q[1];
1072  $text = $q[2];
1073  }
1074  }
1075 
1076  $matches[$marker] = [ $element,
1077  $content,
1078  Sanitizer::decodeTagAttributes( $attributes ),
1079  "<$element$attributes$close$content$tail" ];
1080  }
1081  return $stripped;
1082  }
1083 
1089  public function getStripList() {
1090  return $this->mStripList;
1091  }
1092 
1102  public function insertStripItem( $text ) {
1103  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1104  $this->mMarkerIndex++;
1105  $this->mStripState->addGeneral( $marker, $text );
1106  return $marker;
1107  }
1108 
1116  public function doTableStuff( $text ) {
1117  $lines = StringUtils::explode( "\n", $text );
1118  $out = '';
1119  $td_history = []; # Is currently a td tag open?
1120  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1121  $tr_history = []; # Is currently a tr tag open?
1122  $tr_attributes = []; # history of tr attributes
1123  $has_opened_tr = []; # Did this table open a <tr> element?
1124  $indent_level = 0; # indent level of the table
1125 
1126  foreach ( $lines as $outLine ) {
1127  $line = trim( $outLine );
1128 
1129  if ( $line === '' ) { # empty line, go to next line
1130  $out .= $outLine . "\n";
1131  continue;
1132  }
1133 
1134  $first_character = $line[0];
1135  $first_two = substr( $line, 0, 2 );
1136  $matches = [];
1137 
1138  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1139  # First check if we are starting a new table
1140  $indent_level = strlen( $matches[1] );
1141 
1142  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1143  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1144 
1145  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1146  array_push( $td_history, false );
1147  array_push( $last_tag_history, '' );
1148  array_push( $tr_history, false );
1149  array_push( $tr_attributes, '' );
1150  array_push( $has_opened_tr, false );
1151  } elseif ( count( $td_history ) == 0 ) {
1152  # Don't do any of the following
1153  $out .= $outLine . "\n";
1154  continue;
1155  } elseif ( $first_two === '|}' ) {
1156  # We are ending a table
1157  $line = '</table>' . substr( $line, 2 );
1158  $last_tag = array_pop( $last_tag_history );
1159 
1160  if ( !array_pop( $has_opened_tr ) ) {
1161  $line = "<tr><td></td></tr>{$line}";
1162  }
1163 
1164  if ( array_pop( $tr_history ) ) {
1165  $line = "</tr>{$line}";
1166  }
1167 
1168  if ( array_pop( $td_history ) ) {
1169  $line = "</{$last_tag}>{$line}";
1170  }
1171  array_pop( $tr_attributes );
1172  if ( $indent_level > 0 ) {
1173  $outLine = rtrim( $line ) . str_repeat( '</dd></dl>', $indent_level );
1174  } else {
1175  $outLine = $line;
1176  }
1177  } elseif ( $first_two === '|-' ) {
1178  # Now we have a table row
1179  $line = preg_replace( '#^\|-+#', '', $line );
1180 
1181  # Whats after the tag is now only attributes
1182  $attributes = $this->mStripState->unstripBoth( $line );
1183  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1184  array_pop( $tr_attributes );
1185  array_push( $tr_attributes, $attributes );
1186 
1187  $line = '';
1188  $last_tag = array_pop( $last_tag_history );
1189  array_pop( $has_opened_tr );
1190  array_push( $has_opened_tr, true );
1191 
1192  if ( array_pop( $tr_history ) ) {
1193  $line = '</tr>';
1194  }
1195 
1196  if ( array_pop( $td_history ) ) {
1197  $line = "</{$last_tag}>{$line}";
1198  }
1199 
1200  $outLine = $line;
1201  array_push( $tr_history, false );
1202  array_push( $td_history, false );
1203  array_push( $last_tag_history, '' );
1204  } elseif ( $first_character === '|'
1205  || $first_character === '!'
1206  || $first_two === '|+'
1207  ) {
1208  # This might be cell elements, td, th or captions
1209  if ( $first_two === '|+' ) {
1210  $first_character = '+';
1211  $line = substr( $line, 2 );
1212  } else {
1213  $line = substr( $line, 1 );
1214  }
1215 
1216  // Implies both are valid for table headings.
1217  if ( $first_character === '!' ) {
1218  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1219  }
1220 
1221  # Split up multiple cells on the same line.
1222  # FIXME : This can result in improper nesting of tags processed
1223  # by earlier parser steps.
1224  $cells = explode( '||', $line );
1225 
1226  $outLine = '';
1227 
1228  # Loop through each table cell
1229  foreach ( $cells as $cell ) {
1230  $previous = '';
1231  if ( $first_character !== '+' ) {
1232  $tr_after = array_pop( $tr_attributes );
1233  if ( !array_pop( $tr_history ) ) {
1234  $previous = "<tr{$tr_after}>\n";
1235  }
1236  array_push( $tr_history, true );
1237  array_push( $tr_attributes, '' );
1238  array_pop( $has_opened_tr );
1239  array_push( $has_opened_tr, true );
1240  }
1241 
1242  $last_tag = array_pop( $last_tag_history );
1243 
1244  if ( array_pop( $td_history ) ) {
1245  $previous = "</{$last_tag}>\n{$previous}";
1246  }
1247 
1248  if ( $first_character === '|' ) {
1249  $last_tag = 'td';
1250  } elseif ( $first_character === '!' ) {
1251  $last_tag = 'th';
1252  } elseif ( $first_character === '+' ) {
1253  $last_tag = 'caption';
1254  } else {
1255  $last_tag = '';
1256  }
1257 
1258  array_push( $last_tag_history, $last_tag );
1259 
1260  # A cell could contain both parameters and data
1261  $cell_data = explode( '|', $cell, 2 );
1262 
1263  # T2553: Note that a '|' inside an invalid link should not
1264  # be mistaken as delimiting cell parameters
1265  # Bug T153140: Neither should language converter markup.
1266  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1267  $cell = "{$previous}<{$last_tag}>" . trim( $cell );
1268  } elseif ( count( $cell_data ) == 1 ) {
1269  // Whitespace in cells is trimmed
1270  $cell = "{$previous}<{$last_tag}>" . trim( $cell_data[0] );
1271  } else {
1272  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1273  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1274  // Whitespace in cells is trimmed
1275  $cell = "{$previous}<{$last_tag}{$attributes}>" . trim( $cell_data[1] );
1276  }
1277 
1278  $outLine .= $cell;
1279  array_push( $td_history, true );
1280  }
1281  }
1282  $out .= $outLine . "\n";
1283  }
1284 
1285  # Closing open td, tr && table
1286  while ( count( $td_history ) > 0 ) {
1287  if ( array_pop( $td_history ) ) {
1288  $out .= "</td>\n";
1289  }
1290  if ( array_pop( $tr_history ) ) {
1291  $out .= "</tr>\n";
1292  }
1293  if ( !array_pop( $has_opened_tr ) ) {
1294  $out .= "<tr><td></td></tr>\n";
1295  }
1296 
1297  $out .= "</table>\n";
1298  }
1299 
1300  # Remove trailing line-ending (b/c)
1301  if ( substr( $out, -1 ) === "\n" ) {
1302  $out = substr( $out, 0, -1 );
1303  }
1304 
1305  # special case: don't return empty table
1306  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1307  $out = '';
1308  }
1309 
1310  return $out;
1311  }
1312 
1326  public function internalParse( $text, $isMain = true, $frame = false ) {
1327  $origText = $text;
1328 
1329  // Avoid PHP 7.1 warning from passing $this by reference
1330  $parser = $this;
1331 
1332  # Hook to suspend the parser in this state
1333  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1334  return $text;
1335  }
1336 
1337  # if $frame is provided, then use $frame for replacing any variables
1338  if ( $frame ) {
1339  # use frame depth to infer how include/noinclude tags should be handled
1340  # depth=0 means this is the top-level document; otherwise it's an included document
1341  if ( !$frame->depth ) {
1342  $flag = 0;
1343  } else {
1344  $flag = self::PTD_FOR_INCLUSION;
1345  }
1346  $dom = $this->preprocessToDom( $text, $flag );
1347  $text = $frame->expand( $dom );
1348  } else {
1349  # if $frame is not provided, then use old-style replaceVariables
1350  $text = $this->replaceVariables( $text );
1351  }
1352 
1353  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1354  $text = Sanitizer::removeHTMLtags(
1355  $text,
1356  [ $this, 'attributeStripCallback' ],
1357  false,
1358  array_keys( $this->mTransparentTagHooks ),
1359  [],
1360  [ $this, 'addTrackingCategory' ]
1361  );
1362  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1363 
1364  # Tables need to come after variable replacement for things to work
1365  # properly; putting them before other transformations should keep
1366  # exciting things like link expansions from showing up in surprising
1367  # places.
1368  $text = $this->doTableStuff( $text );
1369 
1370  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1371 
1372  $text = $this->doDoubleUnderscore( $text );
1373 
1374  $text = $this->doHeadings( $text );
1375  $text = $this->replaceInternalLinks( $text );
1376  $text = $this->doAllQuotes( $text );
1377  $text = $this->replaceExternalLinks( $text );
1378 
1379  # replaceInternalLinks may sometimes leave behind
1380  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1381  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1382 
1383  $text = $this->doMagicLinks( $text );
1384  $text = $this->formatHeadings( $text, $origText, $isMain );
1385 
1386  return $text;
1387  }
1388 
1398  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1399  $text = $this->mStripState->unstripGeneral( $text );
1400 
1401  // Avoid PHP 7.1 warning from passing $this by reference
1402  $parser = $this;
1403 
1404  if ( $isMain ) {
1405  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1406  }
1407 
1408  # Clean up special characters, only run once, next-to-last before doBlockLevels
1409  $text = Sanitizer::armorFrenchSpaces( $text );
1410 
1411  $text = $this->doBlockLevels( $text, $linestart );
1412 
1413  $this->replaceLinkHolders( $text );
1414 
1422  if ( !( $this->mOptions->getDisableContentConversion()
1423  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1424  ) {
1425  if ( !$this->mOptions->getInterfaceMessage() ) {
1426  # The position of the convert() call should not be changed. it
1427  # assumes that the links are all replaced and the only thing left
1428  # is the <nowiki> mark.
1429  $text = $this->getTargetLanguage()->convert( $text );
1430  }
1431  }
1432 
1433  $text = $this->mStripState->unstripNoWiki( $text );
1434 
1435  if ( $isMain ) {
1436  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1437  }
1438 
1439  $text = $this->replaceTransparentTags( $text );
1440  $text = $this->mStripState->unstripGeneral( $text );
1441 
1442  $text = Sanitizer::normalizeCharReferences( $text );
1443 
1444  if ( MWTidy::isEnabled() ) {
1445  if ( $this->mOptions->getTidy() ) {
1446  $text = MWTidy::tidy( $text );
1447  }
1448  } else {
1449  # attempt to sanitize at least some nesting problems
1450  # (T4702 and quite a few others)
1451  $tidyregs = [
1452  # ''Something [http://www.cool.com cool''] -->
1453  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1454  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1455  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1456  # fix up an anchor inside another anchor, only
1457  # at least for a single single nested link (T5695)
1458  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1459  '\\1\\2</a>\\3</a>\\1\\4</a>',
1460  # fix div inside inline elements- doBlockLevels won't wrap a line which
1461  # contains a div, so fix it up here; replace
1462  # div with escaped text
1463  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1464  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1465  # remove empty italic or bold tag pairs, some
1466  # introduced by rules above
1467  '/<([bi])><\/\\1>/' => '',
1468  ];
1469 
1470  $text = preg_replace(
1471  array_keys( $tidyregs ),
1472  array_values( $tidyregs ),
1473  $text );
1474  }
1475 
1476  if ( $isMain ) {
1477  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1478  }
1479 
1480  return $text;
1481  }
1482 
1494  public function doMagicLinks( $text ) {
1495  $prots = wfUrlProtocolsWithoutProtRel();
1496  $urlChar = self::EXT_LINK_URL_CLASS;
1497  $addr = self::EXT_LINK_ADDR;
1498  $space = self::SPACE_NOT_NL; # non-newline space
1499  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1500  $spaces = "$space++"; # possessive match of 1 or more spaces
1501  $text = preg_replace_callback(
1502  '!(?: # Start cases
1503  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1504  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1505  (\b # m[3]: Free external links
1506  (?i:$prots)
1507  ($addr$urlChar*) # m[4]: Post-protocol path
1508  ) |
1509  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1510  ([0-9]+)\b |
1511  \bISBN $spaces ( # m[6]: ISBN, capture number
1512  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1513  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1514  [0-9Xx] # check digit
1515  )\b
1516  )!xu", [ $this, 'magicLinkCallback' ], $text );
1517  return $text;
1518  }
1519 
1525  public function magicLinkCallback( $m ) {
1526  if ( isset( $m[1] ) && $m[1] !== '' ) {
1527  # Skip anchor
1528  return $m[0];
1529  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1530  # Skip HTML element
1531  return $m[0];
1532  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1533  # Free external link
1534  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1535  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1536  # RFC or PMID
1537  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1538  if ( !$this->mOptions->getMagicRFCLinks() ) {
1539  return $m[0];
1540  }
1541  $keyword = 'RFC';
1542  $urlmsg = 'rfcurl';
1543  $cssClass = 'mw-magiclink-rfc';
1544  $trackingCat = 'magiclink-tracking-rfc';
1545  $id = $m[5];
1546  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1547  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1548  return $m[0];
1549  }
1550  $keyword = 'PMID';
1551  $urlmsg = 'pubmedurl';
1552  $cssClass = 'mw-magiclink-pmid';
1553  $trackingCat = 'magiclink-tracking-pmid';
1554  $id = $m[5];
1555  } else {
1556  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1557  substr( $m[0], 0, 20 ) . '"' );
1558  }
1559  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1560  $this->addTrackingCategory( $trackingCat );
1561  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1562  } elseif ( isset( $m[6] ) && $m[6] !== ''
1563  && $this->mOptions->getMagicISBNLinks()
1564  ) {
1565  # ISBN
1566  $isbn = $m[6];
1567  $space = self::SPACE_NOT_NL; # non-newline space
1568  $isbn = preg_replace( "/$space/", ' ', $isbn );
1569  $num = strtr( $isbn, [
1570  '-' => '',
1571  ' ' => '',
1572  'x' => 'X',
1573  ] );
1574  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1575  return $this->getLinkRenderer()->makeKnownLink(
1576  SpecialPage::getTitleFor( 'Booksources', $num ),
1577  "ISBN $isbn",
1578  [
1579  'class' => 'internal mw-magiclink-isbn',
1580  'title' => false // suppress title attribute
1581  ]
1582  );
1583  } else {
1584  return $m[0];
1585  }
1586  }
1587 
1597  public function makeFreeExternalLink( $url, $numPostProto ) {
1598  $trail = '';
1599 
1600  # The characters '<' and '>' (which were escaped by
1601  # removeHTMLtags()) should not be included in
1602  # URLs, per RFC 2396.
1603  # Make &nbsp; terminate a URL as well (bug T84937)
1604  $m2 = [];
1605  if ( preg_match(
1606  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1607  $url,
1608  $m2,
1609  PREG_OFFSET_CAPTURE
1610  ) ) {
1611  $trail = substr( $url, $m2[0][1] ) . $trail;
1612  $url = substr( $url, 0, $m2[0][1] );
1613  }
1614 
1615  # Move trailing punctuation to $trail
1616  $sep = ',;\.:!?';
1617  # If there is no left bracket, then consider right brackets fair game too
1618  if ( strpos( $url, '(' ) === false ) {
1619  $sep .= ')';
1620  }
1621 
1622  $urlRev = strrev( $url );
1623  $numSepChars = strspn( $urlRev, $sep );
1624  # Don't break a trailing HTML entity by moving the ; into $trail
1625  # This is in hot code, so use substr_compare to avoid having to
1626  # create a new string object for the comparison
1627  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1628  # more optimization: instead of running preg_match with a $
1629  # anchor, which can be slow, do the match on the reversed
1630  # string starting at the desired offset.
1631  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1632  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1633  $numSepChars--;
1634  }
1635  }
1636  if ( $numSepChars ) {
1637  $trail = substr( $url, -$numSepChars ) . $trail;
1638  $url = substr( $url, 0, -$numSepChars );
1639  }
1640 
1641  # Verify that we still have a real URL after trail removal, and
1642  # not just lone protocol
1643  if ( strlen( $trail ) >= $numPostProto ) {
1644  return $url . $trail;
1645  }
1646 
1647  $url = Sanitizer::cleanUrl( $url );
1648 
1649  # Is this an external image?
1650  $text = $this->maybeMakeExternalImage( $url );
1651  if ( $text === false ) {
1652  # Not an image, make a link
1653  $text = Linker::makeExternalLink( $url,
1654  $this->getTargetLanguage()->getConverter()->markNoConversion( $url ),
1655  true, 'free',
1656  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1657  # Register it in the output object...
1658  $this->mOutput->addExternalLink( $url );
1659  }
1660  return $text . $trail;
1661  }
1662 
1672  public function doHeadings( $text ) {
1673  for ( $i = 6; $i >= 1; --$i ) {
1674  $h = str_repeat( '=', $i );
1675  // Trim non-newline whitespace from headings
1676  // Using \s* will break for: "==\n===\n" and parse as <h2>=</h2>
1677  $text = preg_replace( "/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m", "<h$i>\\1</h$i>", $text );
1678  }
1679  return $text;
1680  }
1681 
1690  public function doAllQuotes( $text ) {
1691  $outtext = '';
1692  $lines = StringUtils::explode( "\n", $text );
1693  foreach ( $lines as $line ) {
1694  $outtext .= $this->doQuotes( $line ) . "\n";
1695  }
1696  $outtext = substr( $outtext, 0, -1 );
1697  return $outtext;
1698  }
1699 
1707  public function doQuotes( $text ) {
1708  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1709  $countarr = count( $arr );
1710  if ( $countarr == 1 ) {
1711  return $text;
1712  }
1713 
1714  // First, do some preliminary work. This may shift some apostrophes from
1715  // being mark-up to being text. It also counts the number of occurrences
1716  // of bold and italics mark-ups.
1717  $numbold = 0;
1718  $numitalics = 0;
1719  for ( $i = 1; $i < $countarr; $i += 2 ) {
1720  $thislen = strlen( $arr[$i] );
1721  // If there are ever four apostrophes, assume the first is supposed to
1722  // be text, and the remaining three constitute mark-up for bold text.
1723  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1724  if ( $thislen == 4 ) {
1725  $arr[$i - 1] .= "'";
1726  $arr[$i] = "'''";
1727  $thislen = 3;
1728  } elseif ( $thislen > 5 ) {
1729  // If there are more than 5 apostrophes in a row, assume they're all
1730  // text except for the last 5.
1731  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1732  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1733  $arr[$i] = "'''''";
1734  $thislen = 5;
1735  }
1736  // Count the number of occurrences of bold and italics mark-ups.
1737  if ( $thislen == 2 ) {
1738  $numitalics++;
1739  } elseif ( $thislen == 3 ) {
1740  $numbold++;
1741  } elseif ( $thislen == 5 ) {
1742  $numitalics++;
1743  $numbold++;
1744  }
1745  }
1746 
1747  // If there is an odd number of both bold and italics, it is likely
1748  // that one of the bold ones was meant to be an apostrophe followed
1749  // by italics. Which one we cannot know for certain, but it is more
1750  // likely to be one that has a single-letter word before it.
1751  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1752  $firstsingleletterword = -1;
1753  $firstmultiletterword = -1;
1754  $firstspace = -1;
1755  for ( $i = 1; $i < $countarr; $i += 2 ) {
1756  if ( strlen( $arr[$i] ) == 3 ) {
1757  $x1 = substr( $arr[$i - 1], -1 );
1758  $x2 = substr( $arr[$i - 1], -2, 1 );
1759  if ( $x1 === ' ' ) {
1760  if ( $firstspace == -1 ) {
1761  $firstspace = $i;
1762  }
1763  } elseif ( $x2 === ' ' ) {
1764  $firstsingleletterword = $i;
1765  // if $firstsingleletterword is set, we don't
1766  // look at the other options, so we can bail early.
1767  break;
1768  } else {
1769  if ( $firstmultiletterword == -1 ) {
1770  $firstmultiletterword = $i;
1771  }
1772  }
1773  }
1774  }
1775 
1776  // If there is a single-letter word, use it!
1777  if ( $firstsingleletterword > -1 ) {
1778  $arr[$firstsingleletterword] = "''";
1779  $arr[$firstsingleletterword - 1] .= "'";
1780  } elseif ( $firstmultiletterword > -1 ) {
1781  // If not, but there's a multi-letter word, use that one.
1782  $arr[$firstmultiletterword] = "''";
1783  $arr[$firstmultiletterword - 1] .= "'";
1784  } elseif ( $firstspace > -1 ) {
1785  // ... otherwise use the first one that has neither.
1786  // (notice that it is possible for all three to be -1 if, for example,
1787  // there is only one pentuple-apostrophe in the line)
1788  $arr[$firstspace] = "''";
1789  $arr[$firstspace - 1] .= "'";
1790  }
1791  }
1792 
1793  // Now let's actually convert our apostrophic mush to HTML!
1794  $output = '';
1795  $buffer = '';
1796  $state = '';
1797  $i = 0;
1798  foreach ( $arr as $r ) {
1799  if ( ( $i % 2 ) == 0 ) {
1800  if ( $state === 'both' ) {
1801  $buffer .= $r;
1802  } else {
1803  $output .= $r;
1804  }
1805  } else {
1806  $thislen = strlen( $r );
1807  if ( $thislen == 2 ) {
1808  if ( $state === 'i' ) {
1809  $output .= '</i>';
1810  $state = '';
1811  } elseif ( $state === 'bi' ) {
1812  $output .= '</i>';
1813  $state = 'b';
1814  } elseif ( $state === 'ib' ) {
1815  $output .= '</b></i><b>';
1816  $state = 'b';
1817  } elseif ( $state === 'both' ) {
1818  $output .= '<b><i>' . $buffer . '</i>';
1819  $state = 'b';
1820  } else { // $state can be 'b' or ''
1821  $output .= '<i>';
1822  $state .= 'i';
1823  }
1824  } elseif ( $thislen == 3 ) {
1825  if ( $state === 'b' ) {
1826  $output .= '</b>';
1827  $state = '';
1828  } elseif ( $state === 'bi' ) {
1829  $output .= '</i></b><i>';
1830  $state = 'i';
1831  } elseif ( $state === 'ib' ) {
1832  $output .= '</b>';
1833  $state = 'i';
1834  } elseif ( $state === 'both' ) {
1835  $output .= '<i><b>' . $buffer . '</b>';
1836  $state = 'i';
1837  } else { // $state can be 'i' or ''
1838  $output .= '<b>';
1839  $state .= 'b';
1840  }
1841  } elseif ( $thislen == 5 ) {
1842  if ( $state === 'b' ) {
1843  $output .= '</b><i>';
1844  $state = 'i';
1845  } elseif ( $state === 'i' ) {
1846  $output .= '</i><b>';
1847  $state = 'b';
1848  } elseif ( $state === 'bi' ) {
1849  $output .= '</i></b>';
1850  $state = '';
1851  } elseif ( $state === 'ib' ) {
1852  $output .= '</b></i>';
1853  $state = '';
1854  } elseif ( $state === 'both' ) {
1855  $output .= '<i><b>' . $buffer . '</b></i>';
1856  $state = '';
1857  } else { // ($state == '')
1858  $buffer = '';
1859  $state = 'both';
1860  }
1861  }
1862  }
1863  $i++;
1864  }
1865  // Now close all remaining tags. Notice that the order is important.
1866  if ( $state === 'b' || $state === 'ib' ) {
1867  $output .= '</b>';
1868  }
1869  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1870  $output .= '</i>';
1871  }
1872  if ( $state === 'bi' ) {
1873  $output .= '</b>';
1874  }
1875  // There might be lonely ''''', so make sure we have a buffer
1876  if ( $state === 'both' && $buffer ) {
1877  $output .= '<b><i>' . $buffer . '</i></b>';
1878  }
1879  return $output;
1880  }
1881 
1895  public function replaceExternalLinks( $text ) {
1896  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1897  if ( $bits === false ) {
1898  throw new MWException( "PCRE needs to be compiled with "
1899  . "--enable-unicode-properties in order for MediaWiki to function" );
1900  }
1901  $s = array_shift( $bits );
1902 
1903  $i = 0;
1904  while ( $i < count( $bits ) ) {
1905  $url = $bits[$i++];
1906  $i++; // protocol
1907  $text = $bits[$i++];
1908  $trail = $bits[$i++];
1909 
1910  # The characters '<' and '>' (which were escaped by
1911  # removeHTMLtags()) should not be included in
1912  # URLs, per RFC 2396.
1913  $m2 = [];
1914  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1915  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1916  $url = substr( $url, 0, $m2[0][1] );
1917  }
1918 
1919  # If the link text is an image URL, replace it with an <img> tag
1920  # This happened by accident in the original parser, but some people used it extensively
1921  $img = $this->maybeMakeExternalImage( $text );
1922  if ( $img !== false ) {
1923  $text = $img;
1924  }
1925 
1926  $dtrail = '';
1927 
1928  # Set linktype for CSS
1929  $linktype = 'text';
1930 
1931  # No link text, e.g. [http://domain.tld/some.link]
1932  if ( $text == '' ) {
1933  # Autonumber
1934  $langObj = $this->getTargetLanguage();
1935  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1936  $linktype = 'autonumber';
1937  } else {
1938  # Have link text, e.g. [http://domain.tld/some.link text]s
1939  # Check for trail
1940  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1941  }
1942 
1943  // Excluding protocol-relative URLs may avoid many false positives.
1944  if ( preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
1945  $text = $this->getTargetLanguage()->getConverter()->markNoConversion( $text );
1946  }
1947 
1948  $url = Sanitizer::cleanUrl( $url );
1949 
1950  # Use the encoded URL
1951  # This means that users can paste URLs directly into the text
1952  # Funny characters like ö aren't valid in URLs anyway
1953  # This was changed in August 2004
1954  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1955  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1956 
1957  # Register link in the output object.
1958  $this->mOutput->addExternalLink( $url );
1959  }
1960 
1961  return $s;
1962  }
1963 
1973  public static function getExternalLinkRel( $url = false, $title = null ) {
1975  $ns = $title ? $title->getNamespace() : false;
1976  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1978  ) {
1979  return 'nofollow';
1980  }
1981  return null;
1982  }
1983 
1994  public function getExternalLinkAttribs( $url ) {
1995  $attribs = [];
1996  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1997 
1998  $target = $this->mOptions->getExternalLinkTarget();
1999  if ( $target ) {
2000  $attribs['target'] = $target;
2001  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
2002  // T133507. New windows can navigate parent cross-origin.
2003  // Including noreferrer due to lacking browser
2004  // support of noopener. Eventually noreferrer should be removed.
2005  if ( $rel !== '' ) {
2006  $rel .= ' ';
2007  }
2008  $rel .= 'noreferrer noopener';
2009  }
2010  }
2011  $attribs['rel'] = $rel;
2012  return $attribs;
2013  }
2014 
2024  public static function normalizeLinkUrl( $url ) {
2025  # First, make sure unsafe characters are encoded
2026  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
2027  function ( $m ) {
2028  return rawurlencode( $m[0] );
2029  },
2030  $url
2031  );
2032 
2033  $ret = '';
2034  $end = strlen( $url );
2035 
2036  # Fragment part - 'fragment'
2037  $start = strpos( $url, '#' );
2038  if ( $start !== false && $start < $end ) {
2039  $ret = self::normalizeUrlComponent(
2040  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
2041  $end = $start;
2042  }
2043 
2044  # Query part - 'query' minus &=+;
2045  $start = strpos( $url, '?' );
2046  if ( $start !== false && $start < $end ) {
2047  $ret = self::normalizeUrlComponent(
2048  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2049  $end = $start;
2050  }
2051 
2052  # Scheme and path part - 'pchar'
2053  # (we assume no userinfo or encoded colons in the host)
2054  $ret = self::normalizeUrlComponent(
2055  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2056 
2057  return $ret;
2058  }
2059 
2060  private static function normalizeUrlComponent( $component, $unsafe ) {
2061  $callback = function ( $matches ) use ( $unsafe ) {
2062  $char = urldecode( $matches[0] );
2063  $ord = ord( $char );
2064  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2065  # Unescape it
2066  return $char;
2067  } else {
2068  # Leave it escaped, but use uppercase for a-f
2069  return strtoupper( $matches[0] );
2070  }
2071  };
2072  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2073  }
2074 
2083  private function maybeMakeExternalImage( $url ) {
2084  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2085  $imagesexception = !empty( $imagesfrom );
2086  $text = false;
2087  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2088  if ( $imagesexception && is_array( $imagesfrom ) ) {
2089  $imagematch = false;
2090  foreach ( $imagesfrom as $match ) {
2091  if ( strpos( $url, $match ) === 0 ) {
2092  $imagematch = true;
2093  break;
2094  }
2095  }
2096  } elseif ( $imagesexception ) {
2097  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2098  } else {
2099  $imagematch = false;
2100  }
2101 
2102  if ( $this->mOptions->getAllowExternalImages()
2103  || ( $imagesexception && $imagematch )
2104  ) {
2105  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2106  # Image found
2107  $text = Linker::makeExternalImage( $url );
2108  }
2109  }
2110  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2111  && preg_match( self::EXT_IMAGE_REGEX, $url )
2112  ) {
2113  $whitelist = explode(
2114  "\n",
2115  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2116  );
2117 
2118  foreach ( $whitelist as $entry ) {
2119  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2120  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2121  continue;
2122  }
2123  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2124  # Image matches a whitelist entry
2125  $text = Linker::makeExternalImage( $url );
2126  break;
2127  }
2128  }
2129  }
2130  return $text;
2131  }
2132 
2142  public function replaceInternalLinks( $s ) {
2143  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2144  return $s;
2145  }
2146 
2155  public function replaceInternalLinks2( &$s ) {
2157 
2158  static $tc = false, $e1, $e1_img;
2159  # the % is needed to support urlencoded titles as well
2160  if ( !$tc ) {
2161  $tc = Title::legalChars() . '#%';
2162  # Match a link having the form [[namespace:link|alternate]]trail
2163  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2164  # Match cases where there is no "]]", which might still be images
2165  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2166  }
2167 
2168  $holders = new LinkHolderArray( $this );
2169 
2170  # split the entire text string on occurrences of [[
2171  $a = StringUtils::explode( '[[', ' ' . $s );
2172  # get the first element (all text up to first [[), and remove the space we added
2173  $s = $a->current();
2174  $a->next();
2175  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2176  $s = substr( $s, 1 );
2177 
2178  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2179  $e2 = null;
2180  if ( $useLinkPrefixExtension ) {
2181  # Match the end of a line for a word that's not followed by whitespace,
2182  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2183  $charset = $this->contLang->linkPrefixCharset();
2184  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2185  }
2186 
2187  if ( is_null( $this->mTitle ) ) {
2188  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2189  }
2190  $nottalk = !$this->mTitle->isTalkPage();
2191 
2192  if ( $useLinkPrefixExtension ) {
2193  $m = [];
2194  if ( preg_match( $e2, $s, $m ) ) {
2195  $first_prefix = $m[2];
2196  } else {
2197  $first_prefix = false;
2198  }
2199  } else {
2200  $prefix = '';
2201  }
2202 
2203  $useSubpages = $this->areSubpagesAllowed();
2204 
2205  # Loop for each link
2206  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2207  # Check for excessive memory usage
2208  if ( $holders->isBig() ) {
2209  # Too big
2210  # Do the existence check, replace the link holders and clear the array
2211  $holders->replace( $s );
2212  $holders->clear();
2213  }
2214 
2215  if ( $useLinkPrefixExtension ) {
2216  if ( preg_match( $e2, $s, $m ) ) {
2217  $prefix = $m[2];
2218  $s = $m[1];
2219  } else {
2220  $prefix = '';
2221  }
2222  # first link
2223  if ( $first_prefix ) {
2224  $prefix = $first_prefix;
2225  $first_prefix = false;
2226  }
2227  }
2228 
2229  $might_be_img = false;
2230 
2231  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2232  $text = $m[2];
2233  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2234  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2235  # the real problem is with the $e1 regex
2236  # See T1500.
2237  # Still some problems for cases where the ] is meant to be outside punctuation,
2238  # and no image is in sight. See T4095.
2239  if ( $text !== ''
2240  && substr( $m[3], 0, 1 ) === ']'
2241  && strpos( $text, '[' ) !== false
2242  ) {
2243  $text .= ']'; # so that replaceExternalLinks($text) works later
2244  $m[3] = substr( $m[3], 1 );
2245  }
2246  # fix up urlencoded title texts
2247  if ( strpos( $m[1], '%' ) !== false ) {
2248  # Should anchors '#' also be rejected?
2249  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2250  }
2251  $trail = $m[3];
2252  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2253  # Invalid, but might be an image with a link in its caption
2254  $might_be_img = true;
2255  $text = $m[2];
2256  if ( strpos( $m[1], '%' ) !== false ) {
2257  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2258  }
2259  $trail = "";
2260  } else { # Invalid form; output directly
2261  $s .= $prefix . '[[' . $line;
2262  continue;
2263  }
2264 
2265  $origLink = ltrim( $m[1], ' ' );
2266 
2267  # Don't allow internal links to pages containing
2268  # PROTO: where PROTO is a valid URL protocol; these
2269  # should be external links.
2270  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2271  $s .= $prefix . '[[' . $line;
2272  continue;
2273  }
2274 
2275  # Make subpage if necessary
2276  if ( $useSubpages ) {
2277  $link = $this->maybeDoSubpageLink( $origLink, $text );
2278  } else {
2279  $link = $origLink;
2280  }
2281 
2282  // \x7f isn't a default legal title char, so most likely strip
2283  // markers will force us into the "invalid form" path above. But,
2284  // just in case, let's assert that xmlish tags aren't valid in
2285  // the title position.
2286  $unstrip = $this->mStripState->killMarkers( $link );
2287  $noMarkers = ( $unstrip === $link );
2288 
2289  $nt = $noMarkers ? Title::newFromText( $link ) : null;
2290  if ( $nt === null ) {
2291  $s .= $prefix . '[[' . $line;
2292  continue;
2293  }
2294 
2295  $ns = $nt->getNamespace();
2296  $iw = $nt->getInterwiki();
2297 
2298  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2299 
2300  if ( $might_be_img ) { # if this is actually an invalid link
2301  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2302  $found = false;
2303  while ( true ) {
2304  # look at the next 'line' to see if we can close it there
2305  $a->next();
2306  $next_line = $a->current();
2307  if ( $next_line === false || $next_line === null ) {
2308  break;
2309  }
2310  $m = explode( ']]', $next_line, 3 );
2311  if ( count( $m ) == 3 ) {
2312  # the first ]] closes the inner link, the second the image
2313  $found = true;
2314  $text .= "[[{$m[0]}]]{$m[1]}";
2315  $trail = $m[2];
2316  break;
2317  } elseif ( count( $m ) == 2 ) {
2318  # if there's exactly one ]] that's fine, we'll keep looking
2319  $text .= "[[{$m[0]}]]{$m[1]}";
2320  } else {
2321  # if $next_line is invalid too, we need look no further
2322  $text .= '[[' . $next_line;
2323  break;
2324  }
2325  }
2326  if ( !$found ) {
2327  # we couldn't find the end of this imageLink, so output it raw
2328  # but don't ignore what might be perfectly normal links in the text we've examined
2329  $holders->merge( $this->replaceInternalLinks2( $text ) );
2330  $s .= "{$prefix}[[$link|$text";
2331  # note: no $trail, because without an end, there *is* no trail
2332  continue;
2333  }
2334  } else { # it's not an image, so output it raw
2335  $s .= "{$prefix}[[$link|$text";
2336  # note: no $trail, because without an end, there *is* no trail
2337  continue;
2338  }
2339  }
2340 
2341  $wasblank = ( $text == '' );
2342  if ( $wasblank ) {
2343  $text = $link;
2344  if ( !$noforce ) {
2345  # Strip off leading ':'
2346  $text = substr( $text, 1 );
2347  }
2348  } else {
2349  # T6598 madness. Handle the quotes only if they come from the alternate part
2350  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2351  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2352  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2353  $text = $this->doQuotes( $text );
2354  }
2355 
2356  # Link not escaped by : , create the various objects
2357  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2358  # Interwikis
2359  if (
2360  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2361  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2362  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2363  )
2364  ) {
2365  # T26502: filter duplicates
2366  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2367  $this->mLangLinkLanguages[$iw] = true;
2368  $this->mOutput->addLanguageLink( $nt->getFullText() );
2369  }
2370 
2374  $s = rtrim( $s . $prefix ) . $trail; # T175416
2375  continue;
2376  }
2377 
2378  if ( $ns == NS_FILE ) {
2379  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2380  if ( $wasblank ) {
2381  # if no parameters were passed, $text
2382  # becomes something like "File:Foo.png",
2383  # which we don't want to pass on to the
2384  # image generator
2385  $text = '';
2386  } else {
2387  # recursively parse links inside the image caption
2388  # actually, this will parse them in any other parameters, too,
2389  # but it might be hard to fix that, and it doesn't matter ATM
2390  $text = $this->replaceExternalLinks( $text );
2391  $holders->merge( $this->replaceInternalLinks2( $text ) );
2392  }
2393  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2394  $s .= $prefix . $this->armorLinks(
2395  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2396  continue;
2397  }
2398  } elseif ( $ns == NS_CATEGORY ) {
2402  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2403 
2404  if ( $wasblank ) {
2405  $sortkey = $this->getDefaultSort();
2406  } else {
2407  $sortkey = $text;
2408  }
2409  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2410  $sortkey = str_replace( "\n", '', $sortkey );
2411  $sortkey = $this->getTargetLanguage()->convertCategoryKey( $sortkey );
2412  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2413 
2414  continue;
2415  }
2416  }
2417 
2418  # Self-link checking. For some languages, variants of the title are checked in
2419  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2420  # for linking to a different variant.
2421  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2422  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2423  continue;
2424  }
2425 
2426  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2427  # @todo FIXME: Should do batch file existence checks, see comment below
2428  if ( $ns == NS_MEDIA ) {
2429  # Give extensions a chance to select the file revision for us
2430  $options = [];
2431  $descQuery = false;
2432  Hooks::run( 'BeforeParserFetchFileAndTitle',
2433  [ $this, $nt, &$options, &$descQuery ] );
2434  # Fetch and register the file (file title may be different via hooks)
2435  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2436  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2437  $s .= $prefix . $this->armorLinks(
2438  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2439  continue;
2440  }
2441 
2442  # Some titles, such as valid special pages or files in foreign repos, should
2443  # be shown as bluelinks even though they're not included in the page table
2444  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2445  # batch file existence checks for NS_FILE and NS_MEDIA
2446  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2447  $this->mOutput->addLink( $nt );
2448  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2449  } else {
2450  # Links will be added to the output link list after checking
2451  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2452  }
2453  }
2454  return $holders;
2455  }
2456 
2470  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2471  list( $inside, $trail ) = Linker::splitTrail( $trail );
2472 
2473  if ( $text == '' ) {
2474  $text = htmlspecialchars( $nt->getPrefixedText() );
2475  }
2476 
2477  $link = $this->getLinkRenderer()->makeKnownLink(
2478  $nt, new HtmlArmor( "$prefix$text$inside" )
2479  );
2480 
2481  return $this->armorLinks( $link ) . $trail;
2482  }
2483 
2494  public function armorLinks( $text ) {
2495  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2496  self::MARKER_PREFIX . "NOPARSE$1", $text );
2497  }
2498 
2503  public function areSubpagesAllowed() {
2504  # Some namespaces don't allow subpages
2505  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2506  }
2507 
2516  public function maybeDoSubpageLink( $target, &$text ) {
2517  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2518  }
2519 
2528  public function doBlockLevels( $text, $linestart ) {
2529  return BlockLevelPass::doBlockLevels( $text, $linestart );
2530  }
2531 
2543  public function getVariableValue( $index, $frame = false ) {
2546 
2547  if ( is_null( $this->mTitle ) ) {
2548  // If no title set, bad things are going to happen
2549  // later. Title should always be set since this
2550  // should only be called in the middle of a parse
2551  // operation (but the unit-tests do funky stuff)
2552  throw new MWException( __METHOD__ . ' Should only be '
2553  . ' called while parsing (no title set)' );
2554  }
2555 
2556  // Avoid PHP 7.1 warning from passing $this by reference
2557  $parser = $this;
2558 
2563  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2564  if ( isset( $this->mVarCache[$index] ) ) {
2565  return $this->mVarCache[$index];
2566  }
2567  }
2568 
2569  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2570  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2571 
2572  $pageLang = $this->getFunctionLang();
2573 
2574  switch ( $index ) {
2575  case '!':
2576  $value = '|';
2577  break;
2578  case 'currentmonth':
2579  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ), true );
2580  break;
2581  case 'currentmonth1':
2582  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ), true );
2583  break;
2584  case 'currentmonthname':
2585  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2586  break;
2587  case 'currentmonthnamegen':
2588  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2589  break;
2590  case 'currentmonthabbrev':
2591  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2592  break;
2593  case 'currentday':
2594  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ), true );
2595  break;
2596  case 'currentday2':
2597  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ), true );
2598  break;
2599  case 'localmonth':
2600  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ), true );
2601  break;
2602  case 'localmonth1':
2603  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ), true );
2604  break;
2605  case 'localmonthname':
2606  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2607  break;
2608  case 'localmonthnamegen':
2609  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2610  break;
2611  case 'localmonthabbrev':
2612  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2613  break;
2614  case 'localday':
2615  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ), true );
2616  break;
2617  case 'localday2':
2618  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ), true );
2619  break;
2620  case 'pagename':
2621  $value = wfEscapeWikiText( $this->mTitle->getText() );
2622  break;
2623  case 'pagenamee':
2624  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2625  break;
2626  case 'fullpagename':
2627  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2628  break;
2629  case 'fullpagenamee':
2630  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2631  break;
2632  case 'subpagename':
2633  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2634  break;
2635  case 'subpagenamee':
2636  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2637  break;
2638  case 'rootpagename':
2639  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2640  break;
2641  case 'rootpagenamee':
2642  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2643  ' ',
2644  '_',
2645  $this->mTitle->getRootText()
2646  ) ) );
2647  break;
2648  case 'basepagename':
2649  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2650  break;
2651  case 'basepagenamee':
2652  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2653  ' ',
2654  '_',
2655  $this->mTitle->getBaseText()
2656  ) ) );
2657  break;
2658  case 'talkpagename':
2659  if ( $this->mTitle->canHaveTalkPage() ) {
2660  $talkPage = $this->mTitle->getTalkPage();
2661  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2662  } else {
2663  $value = '';
2664  }
2665  break;
2666  case 'talkpagenamee':
2667  if ( $this->mTitle->canHaveTalkPage() ) {
2668  $talkPage = $this->mTitle->getTalkPage();
2669  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2670  } else {
2671  $value = '';
2672  }
2673  break;
2674  case 'subjectpagename':
2675  $subjPage = $this->mTitle->getSubjectPage();
2676  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2677  break;
2678  case 'subjectpagenamee':
2679  $subjPage = $this->mTitle->getSubjectPage();
2680  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2681  break;
2682  case 'pageid': // requested in T25427
2683  $pageid = $this->getTitle()->getArticleID();
2684  if ( $pageid == 0 ) {
2685  # 0 means the page doesn't exist in the database,
2686  # which means the user is previewing a new page.
2687  # The vary-revision flag must be set, because the magic word
2688  # will have a different value once the page is saved.
2689  $this->mOutput->setFlag( 'vary-revision' );
2690  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2691  }
2692  $value = $pageid ?: null;
2693  break;
2694  case 'revisionid':
2695  # Let the edit saving system know we should parse the page
2696  # *after* a revision ID has been assigned.
2697  $this->mOutput->setFlag( 'vary-revision-id' );
2698  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2699  $value = $this->mRevisionId;
2700 
2701  if ( !$value ) {
2702  $rev = $this->getRevisionObject();
2703  if ( $rev ) {
2704  $value = $rev->getId();
2705  }
2706  }
2707 
2708  if ( !$value ) {
2709  $value = $this->mOptions->getSpeculativeRevId();
2710  if ( $value ) {
2711  $this->mOutput->setSpeculativeRevIdUsed( $value );
2712  }
2713  }
2714  break;
2715  case 'revisionday':
2716  $value = (int)$this->getRevisionTimestampSubstring( 6, 2, self::MAX_TTS, $index );
2717  break;
2718  case 'revisionday2':
2719  $value = $this->getRevisionTimestampSubstring( 6, 2, self::MAX_TTS, $index );
2720  break;
2721  case 'revisionmonth':
2722  $value = $this->getRevisionTimestampSubstring( 4, 2, self::MAX_TTS, $index );
2723  break;
2724  case 'revisionmonth1':
2725  $value = (int)$this->getRevisionTimestampSubstring( 4, 2, self::MAX_TTS, $index );
2726  break;
2727  case 'revisionyear':
2728  $value = $this->getRevisionTimestampSubstring( 0, 4, self::MAX_TTS, $index );
2729  break;
2730  case 'revisiontimestamp':
2731  # Let the edit saving system know we should parse the page
2732  # *after* a revision ID has been assigned. This is for null edits.
2733  $this->mOutput->setFlag( 'vary-revision' );
2734  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2735  $value = $this->getRevisionTimestamp();
2736  break;
2737  case 'revisionuser':
2738  # Let the edit saving system know we should parse the page
2739  # *after* a revision ID has been assigned for null edits.
2740  $this->mOutput->setFlag( 'vary-user' );
2741  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2742  $value = $this->getRevisionUser();
2743  break;
2744  case 'revisionsize':
2745  $value = $this->getRevisionSize();
2746  break;
2747  case 'namespace':
2748  $value = str_replace( '_', ' ',
2749  $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
2750  break;
2751  case 'namespacee':
2752  $value = wfUrlencode( $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
2753  break;
2754  case 'namespacenumber':
2755  $value = $this->mTitle->getNamespace();
2756  break;
2757  case 'talkspace':
2758  $value = $this->mTitle->canHaveTalkPage()
2759  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2760  : '';
2761  break;
2762  case 'talkspacee':
2763  $value = $this->mTitle->canHaveTalkPage() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2764  break;
2765  case 'subjectspace':
2766  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2767  break;
2768  case 'subjectspacee':
2769  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2770  break;
2771  case 'currentdayname':
2772  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2773  break;
2774  case 'currentyear':
2775  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2776  break;
2777  case 'currenttime':
2778  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2779  break;
2780  case 'currenthour':
2781  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2782  break;
2783  case 'currentweek':
2784  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2785  # int to remove the padding
2786  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2787  break;
2788  case 'currentdow':
2789  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2790  break;
2791  case 'localdayname':
2792  $value = $pageLang->getWeekdayName(
2793  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2794  );
2795  break;
2796  case 'localyear':
2797  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2798  break;
2799  case 'localtime':
2800  $value = $pageLang->time(
2801  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2802  false,
2803  false
2804  );
2805  break;
2806  case 'localhour':
2807  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2808  break;
2809  case 'localweek':
2810  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2811  # int to remove the padding
2812  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2813  break;
2814  case 'localdow':
2815  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2816  break;
2817  case 'numberofarticles':
2818  $value = $pageLang->formatNum( SiteStats::articles() );
2819  break;
2820  case 'numberoffiles':
2821  $value = $pageLang->formatNum( SiteStats::images() );
2822  break;
2823  case 'numberofusers':
2824  $value = $pageLang->formatNum( SiteStats::users() );
2825  break;
2826  case 'numberofactiveusers':
2827  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2828  break;
2829  case 'numberofpages':
2830  $value = $pageLang->formatNum( SiteStats::pages() );
2831  break;
2832  case 'numberofadmins':
2833  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2834  break;
2835  case 'numberofedits':
2836  $value = $pageLang->formatNum( SiteStats::edits() );
2837  break;
2838  case 'currenttimestamp':
2839  $value = wfTimestamp( TS_MW, $ts );
2840  break;
2841  case 'localtimestamp':
2842  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2843  break;
2844  case 'currentversion':
2846  break;
2847  case 'articlepath':
2848  return $wgArticlePath;
2849  case 'sitename':
2850  return $wgSitename;
2851  case 'server':
2852  return $wgServer;
2853  case 'servername':
2854  return $wgServerName;
2855  case 'scriptpath':
2856  return $wgScriptPath;
2857  case 'stylepath':
2858  return $wgStylePath;
2859  case 'directionmark':
2860  return $pageLang->getDirMark();
2861  case 'contentlanguage':
2862  global $wgLanguageCode;
2863  return $wgLanguageCode;
2864  case 'pagelanguage':
2865  $value = $pageLang->getCode();
2866  break;
2867  case 'cascadingsources':
2869  break;
2870  default:
2871  $ret = null;
2872  Hooks::run(
2873  'ParserGetVariableValueSwitch',
2874  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2875  );
2876 
2877  return $ret;
2878  }
2879 
2880  if ( $index ) {
2881  $this->mVarCache[$index] = $value;
2882  }
2883 
2884  return $value;
2885  }
2886 
2894  private function getRevisionTimestampSubstring( $start, $len, $mtts, $variable ) {
2895  # Get the timezone-adjusted timestamp to be used for this revision
2896  $resNow = substr( $this->getRevisionTimestamp(), $start, $len );
2897  # Possibly set vary-revision if there is not yet an associated revision
2898  if ( !$this->getRevisionObject() ) {
2899  # Get the timezone-adjusted timestamp $mtts seconds in the future
2900  $resThen = substr(
2901  $this->contLang->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
2902  $start,
2903  $len
2904  );
2905 
2906  if ( $resNow !== $resThen ) {
2907  # Let the edit saving system know we should parse the page
2908  # *after* a revision ID has been assigned. This is for null edits.
2909  $this->mOutput->setFlag( 'vary-revision' );
2910  wfDebug( __METHOD__ . ": $variable used, setting vary-revision...\n" );
2911  }
2912  }
2913 
2914  return $resNow;
2915  }
2916 
2922  public function initialiseVariables() {
2923  $variableIDs = $this->magicWordFactory->getVariableIDs();
2924  $substIDs = $this->magicWordFactory->getSubstIDs();
2925 
2926  $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
2927  $this->mSubstWords = $this->magicWordFactory->newArray( $substIDs );
2928  }
2929 
2952  public function preprocessToDom( $text, $flags = 0 ) {
2953  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2954  return $dom;
2955  }
2956 
2964  public static function splitWhitespace( $s ) {
2965  $ltrimmed = ltrim( $s );
2966  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2967  $trimmed = rtrim( $ltrimmed );
2968  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2969  if ( $diff > 0 ) {
2970  $w2 = substr( $ltrimmed, -$diff );
2971  } else {
2972  $w2 = '';
2973  }
2974  return [ $w1, $trimmed, $w2 ];
2975  }
2976 
2997  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2998  # Is there any text? Also, Prevent too big inclusions!
2999  $textSize = strlen( $text );
3000  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
3001  return $text;
3002  }
3003 
3004  if ( $frame === false ) {
3005  $frame = $this->getPreprocessor()->newFrame();
3006  } elseif ( !( $frame instanceof PPFrame ) ) {
3007  wfDebug( __METHOD__ . " called using plain parameters instead of "
3008  . "a PPFrame instance. Creating custom frame.\n" );
3009  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
3010  }
3011 
3012  $dom = $this->preprocessToDom( $text );
3013  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
3014  $text = $frame->expand( $dom, $flags );
3015 
3016  return $text;
3017  }
3018 
3026  public static function createAssocArgs( $args ) {
3027  $assocArgs = [];
3028  $index = 1;
3029  foreach ( $args as $arg ) {
3030  $eqpos = strpos( $arg, '=' );
3031  if ( $eqpos === false ) {
3032  $assocArgs[$index++] = $arg;
3033  } else {
3034  $name = trim( substr( $arg, 0, $eqpos ) );
3035  $value = trim( substr( $arg, $eqpos + 1 ) );
3036  if ( $value === false ) {
3037  $value = '';
3038  }
3039  if ( $name !== false ) {
3040  $assocArgs[$name] = $value;
3041  }
3042  }
3043  }
3044 
3045  return $assocArgs;
3046  }
3047 
3074  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
3075  # does no harm if $current and $max are present but are unnecessary for the message
3076  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
3077  # only during preview, and that would split the parser cache unnecessarily.
3078  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
3079  ->text();
3080  $this->mOutput->addWarning( $warning );
3081  $this->addTrackingCategory( "$limitationType-category" );
3082  }
3083 
3096  public function braceSubstitution( $piece, $frame ) {
3097  // Flags
3098 
3099  // $text has been filled
3100  $found = false;
3101  // wiki markup in $text should be escaped
3102  $nowiki = false;
3103  // $text is HTML, armour it against wikitext transformation
3104  $isHTML = false;
3105  // Force interwiki transclusion to be done in raw mode not rendered
3106  $forceRawInterwiki = false;
3107  // $text is a DOM node needing expansion in a child frame
3108  $isChildObj = false;
3109  // $text is a DOM node needing expansion in the current frame
3110  $isLocalObj = false;
3111 
3112  # Title object, where $text came from
3113  $title = false;
3114 
3115  # $part1 is the bit before the first |, and must contain only title characters.
3116  # Various prefixes will be stripped from it later.
3117  $titleWithSpaces = $frame->expand( $piece['title'] );
3118  $part1 = trim( $titleWithSpaces );
3119  $titleText = false;
3120 
3121  # Original title text preserved for various purposes
3122  $originalTitle = $part1;
3123 
3124  # $args is a list of argument nodes, starting from index 0, not including $part1
3125  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3126  # below won't work b/c this $args isn't an object
3127  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3128 
3129  $profileSection = null; // profile templates
3130 
3131  # SUBST
3132  if ( !$found ) {
3133  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3134 
3135  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3136  # Decide whether to expand template or keep wikitext as-is.
3137  if ( $this->ot['wiki'] ) {
3138  if ( $substMatch === false ) {
3139  $literal = true; # literal when in PST with no prefix
3140  } else {
3141  $literal = false; # expand when in PST with subst: or safesubst:
3142  }
3143  } else {
3144  if ( $substMatch == 'subst' ) {
3145  $literal = true; # literal when not in PST with plain subst:
3146  } else {
3147  $literal = false; # expand when not in PST with safesubst: or no prefix
3148  }
3149  }
3150  if ( $literal ) {
3151  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3152  $isLocalObj = true;
3153  $found = true;
3154  }
3155  }
3156 
3157  # Variables
3158  if ( !$found && $args->getLength() == 0 ) {
3159  $id = $this->mVariables->matchStartToEnd( $part1 );
3160  if ( $id !== false ) {
3161  $text = $this->getVariableValue( $id, $frame );
3162  if ( $this->magicWordFactory->getCacheTTL( $id ) > -1 ) {
3163  $this->mOutput->updateCacheExpiry(
3164  $this->magicWordFactory->getCacheTTL( $id ) );
3165  }
3166  $found = true;
3167  }
3168  }
3169 
3170  # MSG, MSGNW and RAW
3171  if ( !$found ) {
3172  # Check for MSGNW:
3173  $mwMsgnw = $this->magicWordFactory->get( 'msgnw' );
3174  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3175  $nowiki = true;
3176  } else {
3177  # Remove obsolete MSG:
3178  $mwMsg = $this->magicWordFactory->get( 'msg' );
3179  $mwMsg->matchStartAndRemove( $part1 );
3180  }
3181 
3182  # Check for RAW:
3183  $mwRaw = $this->magicWordFactory->get( 'raw' );
3184  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3185  $forceRawInterwiki = true;
3186  }
3187  }
3188 
3189  # Parser functions
3190  if ( !$found ) {
3191  $colonPos = strpos( $part1, ':' );
3192  if ( $colonPos !== false ) {
3193  $func = substr( $part1, 0, $colonPos );
3194  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3195  $argsLength = $args->getLength();
3196  for ( $i = 0; $i < $argsLength; $i++ ) {
3197  $funcArgs[] = $args->item( $i );
3198  }
3199 
3200  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3201 
3202  // Extract any forwarded flags
3203  if ( isset( $result['title'] ) ) {
3204  $title = $result['title'];
3205  }
3206  if ( isset( $result['found'] ) ) {
3207  $found = $result['found'];
3208  }
3209  if ( array_key_exists( 'text', $result ) ) {
3210  // a string or null
3211  $text = $result['text'];
3212  }
3213  if ( isset( $result['nowiki'] ) ) {
3214  $nowiki = $result['nowiki'];
3215  }
3216  if ( isset( $result['isHTML'] ) ) {
3217  $isHTML = $result['isHTML'];
3218  }
3219  if ( isset( $result['forceRawInterwiki'] ) ) {
3220  $forceRawInterwiki = $result['forceRawInterwiki'];
3221  }
3222  if ( isset( $result['isChildObj'] ) ) {
3223  $isChildObj = $result['isChildObj'];
3224  }
3225  if ( isset( $result['isLocalObj'] ) ) {
3226  $isLocalObj = $result['isLocalObj'];
3227  }
3228  }
3229  }
3230 
3231  # Finish mangling title and then check for loops.
3232  # Set $title to a Title object and $titleText to the PDBK
3233  if ( !$found ) {
3234  $ns = NS_TEMPLATE;
3235  # Split the title into page and subpage
3236  $subpage = '';
3237  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3238  if ( $part1 !== $relative ) {
3239  $part1 = $relative;
3240  $ns = $this->mTitle->getNamespace();
3241  }
3242  $title = Title::newFromText( $part1, $ns );
3243  if ( $title ) {
3244  $titleText = $title->getPrefixedText();
3245  # Check for language variants if the template is not found
3246  if ( $this->getTargetLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3247  $this->getTargetLanguage()->findVariantLink( $part1, $title, true );
3248  }
3249  # Do recursion depth check
3250  $limit = $this->mOptions->getMaxTemplateDepth();
3251  if ( $frame->depth >= $limit ) {
3252  $found = true;
3253  $text = '<span class="error">'
3254  . wfMessage( 'parser-template-recursion-depth-warning' )
3255  ->numParams( $limit )->inContentLanguage()->text()
3256  . '</span>';
3257  }
3258  }
3259  }
3260 
3261  # Load from database
3262  if ( !$found && $title ) {
3263  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3264  if ( !$title->isExternal() ) {
3265  if ( $title->isSpecialPage()
3266  && $this->mOptions->getAllowSpecialInclusion()
3267  && $this->ot['html']
3268  ) {
3269  $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
3270  // Pass the template arguments as URL parameters.
3271  // "uselang" will have no effect since the Language object
3272  // is forced to the one defined in ParserOptions.
3273  $pageArgs = [];
3274  $argsLength = $args->getLength();
3275  for ( $i = 0; $i < $argsLength; $i++ ) {
3276  $bits = $args->item( $i )->splitArg();
3277  if ( strval( $bits['index'] ) === '' ) {
3278  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3279  $value = trim( $frame->expand( $bits['value'] ) );
3280  $pageArgs[$name] = $value;
3281  }
3282  }
3283 
3284  // Create a new context to execute the special page
3285  $context = new RequestContext;
3286  $context->setTitle( $title );
3287  $context->setRequest( new FauxRequest( $pageArgs ) );
3288  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3289  $context->setUser( $this->getUser() );
3290  } else {
3291  // If this page is cached, then we better not be per user.
3292  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3293  }
3294  $context->setLanguage( $this->mOptions->getUserLangObj() );
3295  $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() );
3296  if ( $ret ) {
3297  $text = $context->getOutput()->getHTML();
3298  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3299  $found = true;
3300  $isHTML = true;
3301  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3302  $this->mOutput->updateRuntimeAdaptiveExpiry(
3303  $specialPage->maxIncludeCacheTime()
3304  );
3305  }
3306  }
3307  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3308  $found = false; # access denied
3309  wfDebug( __METHOD__ . ": template inclusion denied for " .
3310  $title->getPrefixedDBkey() . "\n" );
3311  } else {
3312  list( $text, $title ) = $this->getTemplateDom( $title );
3313  if ( $text !== false ) {
3314  $found = true;
3315  $isChildObj = true;
3316  }
3317  }
3318 
3319  # If the title is valid but undisplayable, make a link to it
3320  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3321  $text = "[[:$titleText]]";
3322  $found = true;
3323  }
3324  } elseif ( $title->isTrans() ) {
3325  # Interwiki transclusion
3326  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3327  $text = $this->interwikiTransclude( $title, 'render' );
3328  $isHTML = true;
3329  } else {
3330  $text = $this->interwikiTransclude( $title, 'raw' );
3331  # Preprocess it like a template
3332  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3333  $isChildObj = true;
3334  }
3335  $found = true;
3336  }
3337 
3338  # Do infinite loop check
3339  # This has to be done after redirect resolution to avoid infinite loops via redirects
3340  if ( !$frame->loopCheck( $title ) ) {
3341  $found = true;
3342  $text = '<span class="error">'
3343  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3344  . '</span>';
3345  $this->addTrackingCategory( 'template-loop-category' );
3346  $this->mOutput->addWarning( wfMessage( 'template-loop-warning',
3347  wfEscapeWikiText( $titleText ) )->text() );
3348  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3349  }
3350  }
3351 
3352  # If we haven't found text to substitute by now, we're done
3353  # Recover the source wikitext and return it
3354  if ( !$found ) {
3355  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3356  if ( $profileSection ) {
3357  $this->mProfiler->scopedProfileOut( $profileSection );
3358  }
3359  return [ 'object' => $text ];
3360  }
3361 
3362  # Expand DOM-style return values in a child frame
3363  if ( $isChildObj ) {
3364  # Clean up argument array
3365  $newFrame = $frame->newChild( $args, $title );
3366 
3367  if ( $nowiki ) {
3368  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3369  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3370  # Expansion is eligible for the empty-frame cache
3371  $text = $newFrame->cachedExpand( $titleText, $text );
3372  } else {
3373  # Uncached expansion
3374  $text = $newFrame->expand( $text );
3375  }
3376  }
3377  if ( $isLocalObj && $nowiki ) {
3378  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3379  $isLocalObj = false;
3380  }
3381 
3382  if ( $profileSection ) {
3383  $this->mProfiler->scopedProfileOut( $profileSection );
3384  }
3385 
3386  # Replace raw HTML by a placeholder
3387  if ( $isHTML ) {
3388  $text = $this->insertStripItem( $text );
3389  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3390  # Escape nowiki-style return values
3391  $text = wfEscapeWikiText( $text );
3392  } elseif ( is_string( $text )
3393  && !$piece['lineStart']
3394  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3395  ) {
3396  # T2529: if the template begins with a table or block-level
3397  # element, it should be treated as beginning a new line.
3398  # This behavior is somewhat controversial.
3399  $text = "\n" . $text;
3400  }
3401 
3402  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3403  # Error, oversize inclusion
3404  if ( $titleText !== false ) {
3405  # Make a working, properly escaped link if possible (T25588)
3406  $text = "[[:$titleText]]";
3407  } else {
3408  # This will probably not be a working link, but at least it may
3409  # provide some hint of where the problem is
3410  preg_replace( '/^:/', '', $originalTitle );
3411  $text = "[[:$originalTitle]]";
3412  }
3413  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3414  . 'post-expand include size too large -->' );
3415  $this->limitationWarn( 'post-expand-template-inclusion' );
3416  }
3417 
3418  if ( $isLocalObj ) {
3419  $ret = [ 'object' => $text ];
3420  } else {
3421  $ret = [ 'text' => $text ];
3422  }
3423 
3424  return $ret;
3425  }
3426 
3446  public function callParserFunction( $frame, $function, array $args = [] ) {
3447  # Case sensitive functions
3448  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3449  $function = $this->mFunctionSynonyms[1][$function];
3450  } else {
3451  # Case insensitive functions
3452  $function = $this->contLang->lc( $function );
3453  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3454  $function = $this->mFunctionSynonyms[0][$function];
3455  } else {
3456  return [ 'found' => false ];
3457  }
3458  }
3459 
3460  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3461 
3462  // Avoid PHP 7.1 warning from passing $this by reference
3463  $parser = $this;
3464 
3465  $allArgs = [ &$parser ];
3466  if ( $flags & self::SFH_OBJECT_ARGS ) {
3467  # Convert arguments to PPNodes and collect for appending to $allArgs
3468  $funcArgs = [];
3469  foreach ( $args as $k => $v ) {
3470  if ( $v instanceof PPNode || $k === 0 ) {
3471  $funcArgs[] = $v;
3472  } else {
3473  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3474  }
3475  }
3476 
3477  # Add a frame parameter, and pass the arguments as an array
3478  $allArgs[] = $frame;
3479  $allArgs[] = $funcArgs;
3480  } else {
3481  # Convert arguments to plain text and append to $allArgs
3482  foreach ( $args as $k => $v ) {
3483  if ( $v instanceof PPNode ) {
3484  $allArgs[] = trim( $frame->expand( $v ) );
3485  } elseif ( is_int( $k ) && $k >= 0 ) {
3486  $allArgs[] = trim( $v );
3487  } else {
3488  $allArgs[] = trim( "$k=$v" );
3489  }
3490  }
3491  }
3492 
3493  $result = $callback( ...$allArgs );
3494 
3495  # The interface for function hooks allows them to return a wikitext
3496  # string or an array containing the string and any flags. This mungs
3497  # things around to match what this method should return.
3498  if ( !is_array( $result ) ) {
3499  $result = [
3500  'found' => true,
3501  'text' => $result,
3502  ];
3503  } else {
3504  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3505  $result['text'] = $result[0];
3506  }
3507  unset( $result[0] );
3508  $result += [
3509  'found' => true,
3510  ];
3511  }
3512 
3513  $noparse = true;
3514  $preprocessFlags = 0;
3515  if ( isset( $result['noparse'] ) ) {
3516  $noparse = $result['noparse'];
3517  }
3518  if ( isset( $result['preprocessFlags'] ) ) {
3519  $preprocessFlags = $result['preprocessFlags'];
3520  }
3521 
3522  if ( !$noparse ) {
3523  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3524  $result['isChildObj'] = true;
3525  }
3526 
3527  return $result;
3528  }
3529 
3538  public function getTemplateDom( $title ) {
3539  $cacheTitle = $title;
3540  $titleText = $title->getPrefixedDBkey();
3541 
3542  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3543  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3544  $title = Title::makeTitle( $ns, $dbk );
3545  $titleText = $title->getPrefixedDBkey();
3546  }
3547  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3548  return [ $this->mTplDomCache[$titleText], $title ];
3549  }
3550 
3551  # Cache miss, go to the database
3552  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3553 
3554  if ( $text === false ) {
3555  $this->mTplDomCache[$titleText] = false;
3556  return [ false, $title ];
3557  }
3558 
3559  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3560  $this->mTplDomCache[$titleText] = $dom;
3561 
3562  if ( !$title->equals( $cacheTitle ) ) {
3563  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3564  [ $title->getNamespace(), $title->getDBkey() ];
3565  }
3566 
3567  return [ $dom, $title ];
3568  }
3569 
3581  public function fetchCurrentRevisionOfTitle( $title ) {
3582  $cacheKey = $title->getPrefixedDBkey();
3583  if ( !$this->currentRevisionCache ) {
3584  $this->currentRevisionCache = new MapCacheLRU( 100 );
3585  }
3586  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3587  $this->currentRevisionCache->set( $cacheKey,
3588  // Defaults to Parser::statelessFetchRevision()
3589  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3590  );
3591  }
3592  return $this->currentRevisionCache->get( $cacheKey );
3593  }
3594 
3604  public static function statelessFetchRevision( Title $title, $parser = false ) {
3606 
3607  return $rev;
3608  }
3609 
3615  public function fetchTemplateAndTitle( $title ) {
3616  // Defaults to Parser::statelessFetchTemplate()
3617  $templateCb = $this->mOptions->getTemplateCallback();
3618  $stuff = call_user_func( $templateCb, $title, $this );
3619  // We use U+007F DELETE to distinguish strip markers from regular text.
3620  $text = $stuff['text'];
3621  if ( is_string( $stuff['text'] ) ) {
3622  $text = strtr( $text, "\x7f", "?" );
3623  }
3624  $finalTitle = $stuff['finalTitle'] ?? $title;
3625  if ( isset( $stuff['deps'] ) ) {
3626  foreach ( $stuff['deps'] as $dep ) {
3627  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3628  if ( $dep['title']->equals( $this->getTitle() ) ) {
3629  // If we transclude ourselves, the final result
3630  // will change based on the new version of the page
3631  $this->mOutput->setFlag( 'vary-revision' );
3632  }
3633  }
3634  }
3635  return [ $text, $finalTitle ];
3636  }
3637 
3643  public function fetchTemplate( $title ) {
3644  return $this->fetchTemplateAndTitle( $title )[0];
3645  }
3646 
3656  public static function statelessFetchTemplate( $title, $parser = false ) {
3657  $text = $skip = false;
3658  $finalTitle = $title;
3659  $deps = [];
3660 
3661  # Loop to fetch the article, with up to 1 redirect
3662  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall
3663  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3664  # Give extensions a chance to select the revision instead
3665  $id = false; # Assume current
3666  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3667  [ $parser, $title, &$skip, &$id ] );
3668 
3669  if ( $skip ) {
3670  $text = false;
3671  $deps[] = [
3672  'title' => $title,
3673  'page_id' => $title->getArticleID(),
3674  'rev_id' => null
3675  ];
3676  break;
3677  }
3678  # Get the revision
3679  if ( $id ) {
3680  $rev = Revision::newFromId( $id );
3681  } elseif ( $parser ) {
3682  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3683  } else {
3685  }
3686  $rev_id = $rev ? $rev->getId() : 0;
3687  # If there is no current revision, there is no page
3688  if ( $id === false && !$rev ) {
3689  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3690  $linkCache->addBadLinkObj( $title );
3691  }
3692 
3693  $deps[] = [
3694  'title' => $title,
3695  'page_id' => $title->getArticleID(),
3696  'rev_id' => $rev_id ];
3697  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3698  # We fetched a rev from a different title; register it too...
3699  $deps[] = [
3700  'title' => $rev->getTitle(),
3701  'page_id' => $rev->getPage(),
3702  'rev_id' => $rev_id ];
3703  }
3704 
3705  if ( $rev ) {
3706  $content = $rev->getContent();
3707  $text = $content ? $content->getWikitextForTransclusion() : null;
3708 
3709  Hooks::run( 'ParserFetchTemplate',
3710  [ $parser, $title, $rev, &$text, &$deps ] );
3711 
3712  if ( $text === false || $text === null ) {
3713  $text = false;
3714  break;
3715  }
3716  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3717  $message = wfMessage( MediaWikiServices::getInstance()->getContentLanguage()->
3718  lcfirst( $title->getText() ) )->inContentLanguage();
3719  if ( !$message->exists() ) {
3720  $text = false;
3721  break;
3722  }
3723  $content = $message->content();
3724  $text = $message->plain();
3725  } else {
3726  break;
3727  }
3728  if ( !$content ) {
3729  break;
3730  }
3731  # Redirect?
3732  $finalTitle = $title;
3733  $title = $content->getRedirectTarget();
3734  }
3735  return [
3736  'text' => $text,
3737  'finalTitle' => $finalTitle,
3738  'deps' => $deps ];
3739  }
3740 
3749  public function fetchFile( $title, $options = [] ) {
3750  wfDeprecated( __METHOD__, '1.32' );
3751  return $this->fetchFileAndTitle( $title, $options )[0];
3752  }
3753 
3761  public function fetchFileAndTitle( $title, $options = [] ) {
3762  $file = $this->fetchFileNoRegister( $title, $options );
3763 
3764  $time = $file ? $file->getTimestamp() : false;
3765  $sha1 = $file ? $file->getSha1() : false;
3766  # Register the file as a dependency...
3767  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3768  if ( $file && !$title->equals( $file->getTitle() ) ) {
3769  # Update fetched file title
3770  $title = $file->getTitle();
3771  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3772  }
3773  return [ $file, $title ];
3774  }
3775 
3786  protected function fetchFileNoRegister( $title, $options = [] ) {
3787  if ( isset( $options['broken'] ) ) {
3788  $file = false; // broken thumbnail forced by hook
3789  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3790  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3791  } else { // get by (name,timestamp)
3792  $file = wfFindFile( $title, $options );
3793  }
3794  return $file;
3795  }
3796 
3805  public function interwikiTransclude( $title, $action ) {
3807 
3808  if ( !$wgEnableScaryTranscluding ) {
3809  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3810  }
3811 
3812  $url = $title->getFullURL( [ 'action' => $action ] );
3813  if ( strlen( $url ) > 1024 ) {
3814  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3815  }
3816 
3817  $wikiId = $title->getTransWikiID(); // remote wiki ID or false
3818 
3819  $fname = __METHOD__;
3820  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3821 
3822  $data = $cache->getWithSetCallback(
3823  $cache->makeGlobalKey(
3824  'interwiki-transclude',
3825  ( $wikiId !== false ) ? $wikiId : 'external',
3826  sha1( $url )
3827  ),
3829  function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
3830  $req = MWHttpRequest::factory( $url, [], $fname );
3831 
3832  $status = $req->execute(); // Status object
3833  if ( !$status->isOK() ) {
3834  $ttl = $cache::TTL_UNCACHEABLE;
3835  } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
3836  $ttl = min( $cache::TTL_LAGGED, $ttl );
3837  }
3838 
3839  return [
3840  'text' => $status->isOK() ? $req->getContent() : null,
3841  'code' => $req->getStatus()
3842  ];
3843  },
3844  [
3845  'checkKeys' => ( $wikiId !== false )
3846  ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
3847  : [],
3848  'pcGroup' => 'interwiki-transclude:5',
3849  'pcTTL' => $cache::TTL_PROC_LONG
3850  ]
3851  );
3852 
3853  if ( is_string( $data['text'] ) ) {
3854  $text = $data['text'];
3855  } elseif ( $data['code'] != 200 ) {
3856  // Though we failed to fetch the content, this status is useless.
3857  $text = wfMessage( 'scarytranscludefailed-httpstatus' )
3858  ->params( $url, $data['code'] )->inContentLanguage()->text();
3859  } else {
3860  $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3861  }
3862 
3863  return $text;
3864  }
3865 
3875  public function argSubstitution( $piece, $frame ) {
3876  $error = false;
3877  $parts = $piece['parts'];
3878  $nameWithSpaces = $frame->expand( $piece['title'] );
3879  $argName = trim( $nameWithSpaces );
3880  $object = false;
3881  $text = $frame->getArgument( $argName );
3882  if ( $text === false && $parts->getLength() > 0
3883  && ( $this->ot['html']
3884  || $this->ot['pre']
3885  || ( $this->ot['wiki'] && $frame->isTemplate() )
3886  )
3887  ) {
3888  # No match in frame, use the supplied default
3889  $object = $parts->item( 0 )->getChildren();
3890  }
3891  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3892  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3893  $this->limitationWarn( 'post-expand-template-argument' );
3894  }
3895 
3896  if ( $text === false && $object === false ) {
3897  # No match anywhere
3898  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3899  }
3900  if ( $error !== false ) {
3901  $text .= $error;
3902  }
3903  if ( $object !== false ) {
3904  $ret = [ 'object' => $object ];
3905  } else {
3906  $ret = [ 'text' => $text ];
3907  }
3908 
3909  return $ret;
3910  }
3911 
3927  public function extensionSubstitution( $params, $frame ) {
3928  static $errorStr = '<span class="error">';
3929  static $errorLen = 20;
3930 
3931  $name = $frame->expand( $params['name'] );
3932  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3933  // Probably expansion depth or node count exceeded. Just punt the
3934  // error up.
3935  return $name;
3936  }
3937 
3938  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3939  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3940  // See above
3941  return $attrText;
3942  }
3943 
3944  // We can't safely check if the expansion for $content resulted in an
3945  // error, because the content could happen to be the error string
3946  // (T149622).
3947  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3948 
3949  $marker = self::MARKER_PREFIX . "-$name-"
3950  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3951 
3952  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3953  ( $this->ot['html'] || $this->ot['pre'] );
3954  if ( $isFunctionTag ) {
3955  $markerType = 'none';
3956  } else {
3957  $markerType = 'general';
3958  }
3959  if ( $this->ot['html'] || $isFunctionTag ) {
3960  $name = strtolower( $name );
3961  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3962  if ( isset( $params['attributes'] ) ) {
3963  $attributes = $attributes + $params['attributes'];
3964  }
3965 
3966  if ( isset( $this->mTagHooks[$name] ) ) {
3967  $output = call_user_func_array( $this->mTagHooks[$name],
3968  [ $content, $attributes, $this, $frame ] );
3969  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3970  list( $callback, ) = $this->mFunctionTagHooks[$name];
3971 
3972  // Avoid PHP 7.1 warning from passing $this by reference
3973  $parser = $this;
3974  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3975  } else {
3976  $output = '<span class="error">Invalid tag extension name: ' .
3977  htmlspecialchars( $name ) . '</span>';
3978  }
3979 
3980  if ( is_array( $output ) ) {
3981  // Extract flags
3982  $flags = $output;
3983  $output = $flags[0];
3984  if ( isset( $flags['markerType'] ) ) {
3985  $markerType = $flags['markerType'];
3986  }
3987  }
3988  } else {
3989  if ( is_null( $attrText ) ) {
3990  $attrText = '';
3991  }
3992  if ( isset( $params['attributes'] ) ) {
3993  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3994  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3995  htmlspecialchars( $attrValue ) . '"';
3996  }
3997  }
3998  if ( $content === null ) {
3999  $output = "<$name$attrText/>";
4000  } else {
4001  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
4002  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
4003  // See above
4004  return $close;
4005  }
4006  $output = "<$name$attrText>$content$close";
4007  }
4008  }
4009 
4010  if ( $markerType === 'none' ) {
4011  return $output;
4012  } elseif ( $markerType === 'nowiki' ) {
4013  $this->mStripState->addNoWiki( $marker, $output );
4014  } elseif ( $markerType === 'general' ) {
4015  $this->mStripState->addGeneral( $marker, $output );
4016  } else {
4017  throw new MWException( __METHOD__ . ': invalid marker type' );
4018  }
4019  return $marker;
4020  }
4021 
4029  public function incrementIncludeSize( $type, $size ) {
4030  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4031  return false;
4032  } else {
4033  $this->mIncludeSizes[$type] += $size;
4034  return true;
4035  }
4036  }
4037 
4043  public function incrementExpensiveFunctionCount() {
4044  $this->mExpensiveFunctionCount++;
4045  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4046  }
4047 
4056  public function doDoubleUnderscore( $text ) {
4057  # The position of __TOC__ needs to be recorded
4058  $mw = $this->magicWordFactory->get( 'toc' );
4059  if ( $mw->match( $text ) ) {
4060  $this->mShowToc = true;
4061  $this->mForceTocPosition = true;
4062 
4063  # Set a placeholder. At the end we'll fill it in with the TOC.
4064  $text = $mw->replace( '<!--MWTOC\'"-->', $text, 1 );
4065 
4066  # Only keep the first one.
4067  $text = $mw->replace( '', $text );
4068  }
4069 
4070  # Now match and remove the rest of them
4071  $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
4072  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
4073 
4074  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
4075  $this->mOutput->mNoGallery = true;
4076  }
4077  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
4078  $this->mShowToc = false;
4079  }
4080  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
4081  && $this->mTitle->getNamespace() == NS_CATEGORY
4082  ) {
4083  $this->addTrackingCategory( 'hidden-category-category' );
4084  }
4085  # (T10068) Allow control over whether robots index a page.
4086  # __INDEX__ always overrides __NOINDEX__, see T16899
4087  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
4088  $this->mOutput->setIndexPolicy( 'noindex' );
4089  $this->addTrackingCategory( 'noindex-category' );
4090  }
4091  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4092  $this->mOutput->setIndexPolicy( 'index' );
4093  $this->addTrackingCategory( 'index-category' );
4094  }
4095 
4096  # Cache all double underscores in the database
4097  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4098  $this->mOutput->setProperty( $key, '' );
4099  }
4100 
4101  return $text;
4102  }
4103 
4109  public function addTrackingCategory( $msg ) {
4110  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4111  }
4112 
4129  public function formatHeadings( $text, $origText, $isMain = true ) {
4130  global $wgMaxTocLevel;
4131 
4132  # Inhibit editsection links if requested in the page
4133  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4134  $maybeShowEditLink = false;
4135  } else {
4136  $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
4137  }
4138 
4139  # Get all headlines for numbering them and adding funky stuff like [edit]
4140  # links - this is for later, but we need the number of headlines right now
4141  # NOTE: white space in headings have been trimmed in doHeadings. They shouldn't
4142  # be trimmed here since whitespace in HTML headings is significant.
4143  $matches = [];
4144  $numMatches = preg_match_all(
4145  '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i',
4146  $text,
4147  $matches
4148  );
4149 
4150  # if there are fewer than 4 headlines in the article, do not show TOC
4151  # unless it's been explicitly enabled.
4152  $enoughToc = $this->mShowToc &&
4153  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4154 
4155  # Allow user to stipulate that a page should have a "new section"
4156  # link added via __NEWSECTIONLINK__
4157  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4158  $this->mOutput->setNewSection( true );
4159  }
4160 
4161  # Allow user to remove the "new section"
4162  # link via __NONEWSECTIONLINK__
4163  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4164  $this->mOutput->hideNewSection( true );
4165  }
4166 
4167  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4168  # override above conditions and always show TOC above first header
4169  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4170  $this->mShowToc = true;
4171  $enoughToc = true;
4172  }
4173 
4174  # headline counter
4175  $headlineCount = 0;
4176  $numVisible = 0;
4177 
4178  # Ugh .. the TOC should have neat indentation levels which can be
4179  # passed to the skin functions. These are determined here
4180  $toc = '';
4181  $full = '';
4182  $head = [];
4183  $sublevelCount = [];
4184  $levelCount = [];
4185  $level = 0;
4186  $prevlevel = 0;
4187  $toclevel = 0;
4188  $prevtoclevel = 0;
4189  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4190  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4191  $oldType = $this->mOutputType;
4192  $this->setOutputType( self::OT_WIKI );
4193  $frame = $this->getPreprocessor()->newFrame();
4194  $root = $this->preprocessToDom( $origText );
4195  $node = $root->getFirstChild();
4196  $byteOffset = 0;
4197  $tocraw = [];
4198  $refers = [];
4199 
4200  $headlines = $numMatches !== false ? $matches[3] : [];
4201 
4202  foreach ( $headlines as $headline ) {
4203  $isTemplate = false;
4204  $titleText = false;
4205  $sectionIndex = false;
4206  $numbering = '';
4207  $markerMatches = [];
4208  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4209  $serial = $markerMatches[1];
4210  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4211  $isTemplate = ( $titleText != $baseTitleText );
4212  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4213  }
4214 
4215  if ( $toclevel ) {
4216  $prevlevel = $level;
4217  }
4218  $level = $matches[1][$headlineCount];
4219 
4220  if ( $level > $prevlevel ) {
4221  # Increase TOC level
4222  $toclevel++;
4223  $sublevelCount[$toclevel] = 0;
4224  if ( $toclevel < $wgMaxTocLevel ) {
4225  $prevtoclevel = $toclevel;
4226  $toc .= Linker::tocIndent();
4227  $numVisible++;
4228  }
4229  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4230  # Decrease TOC level, find level to jump to
4231 
4232  for ( $i = $toclevel; $i > 0; $i-- ) {
4233  if ( $levelCount[$i] == $level ) {
4234  # Found last matching level
4235  $toclevel = $i;
4236  break;
4237  } elseif ( $levelCount[$i] < $level ) {
4238  # Found first matching level below current level
4239  $toclevel = $i + 1;
4240  break;
4241  }
4242  }
4243  if ( $i == 0 ) {
4244  $toclevel = 1;
4245  }
4246  if ( $toclevel < $wgMaxTocLevel ) {
4247  if ( $prevtoclevel < $wgMaxTocLevel ) {
4248  # Unindent only if the previous toc level was shown :p
4249  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4250  $prevtoclevel = $toclevel;
4251  } else {
4252  $toc .= Linker::tocLineEnd();
4253  }
4254  }
4255  } else {
4256  # No change in level, end TOC line
4257  if ( $toclevel < $wgMaxTocLevel ) {
4258  $toc .= Linker::tocLineEnd();
4259  }
4260  }
4261 
4262  $levelCount[$toclevel] = $level;
4263 
4264  # count number of headlines for each level
4265  $sublevelCount[$toclevel]++;
4266  $dot = 0;
4267  for ( $i = 1; $i <= $toclevel; $i++ ) {
4268  if ( !empty( $sublevelCount[$i] ) ) {
4269  if ( $dot ) {
4270  $numbering .= '.';
4271  }
4272  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4273  $dot = 1;
4274  }
4275  }
4276 
4277  # The safe header is a version of the header text safe to use for links
4278 
4279  # Remove link placeholders by the link text.
4280  # <!--LINK number-->
4281  # turns into
4282  # link text with suffix
4283  # Do this before unstrip since link text can contain strip markers
4284  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4285 
4286  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4287  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4288 
4289  # Remove any <style> or <script> tags (T198618)
4290  $safeHeadline = preg_replace(
4291  '#<(style|script)(?: [^>]*[^>/])?>.*?</\1>#is',
4292  '',
4293  $safeHeadline
4294  );
4295 
4296  # Strip out HTML (first regex removes any tag not allowed)
4297  # Allowed tags are:
4298  # * <sup> and <sub> (T10393)
4299  # * <i> (T28375)
4300  # * <b> (r105284)
4301  # * <bdi> (T74884)
4302  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4303  # * <s> and <strike> (T35715)
4304  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4305  # to allow setting directionality in toc items.
4306  $tocline = preg_replace(
4307  [
4308  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4309  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4310  ],
4311  [ '', '<$1>' ],
4312  $safeHeadline
4313  );
4314 
4315  # Strip '<span></span>', which is the result from the above if
4316  # <span id="foo"></span> is used to produce an additional anchor
4317  # for a section.
4318  $tocline = str_replace( '<span></span>', '', $tocline );
4319 
4320  $tocline = trim( $tocline );
4321 
4322  # For the anchor, strip out HTML-y stuff period
4323  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4324  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4325 
4326  # Save headline for section edit hint before it's escaped
4327  $headlineHint = $safeHeadline;
4328 
4329  # Decode HTML entities
4330  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4331 
4332  $safeHeadline = self::normalizeSectionName( $safeHeadline );
4333 
4334  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4335  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4336  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4337  if ( $fallbackHeadline === $safeHeadline ) {
4338  # No reason to have both (in fact, we can't)
4339  $fallbackHeadline = false;
4340  }
4341 
4342  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4343  # @todo FIXME: We may be changing them depending on the current locale.
4344  $arrayKey = strtolower( $safeHeadline );
4345  if ( $fallbackHeadline === false ) {
4346  $fallbackArrayKey = false;
4347  } else {
4348  $fallbackArrayKey = strtolower( $fallbackHeadline );
4349  }
4350 
4351  # Create the anchor for linking from the TOC to the section
4352  $anchor = $safeHeadline;
4353  $fallbackAnchor = $fallbackHeadline;
4354  if ( isset( $refers[$arrayKey] ) ) {
4355  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall,Generic.Formatting.DisallowMultipleStatements
4356  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4357  $anchor .= "_$i";
4358  $linkAnchor .= "_$i";
4359  $refers["${arrayKey}_$i"] = true;
4360  } else {
4361  $refers[$arrayKey] = true;
4362  }
4363  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4364  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall,Generic.Formatting.DisallowMultipleStatements
4365  for ( $i = 2; isset( $refers["${fallbackArrayKey}_$i"] ); ++$i );
4366  $fallbackAnchor .= "_$i";
4367  $refers["${fallbackArrayKey}_$i"] = true;
4368  } else {
4369  $refers[$fallbackArrayKey] = true;
4370  }
4371 
4372  # Don't number the heading if it is the only one (looks silly)
4373  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4374  # the two are different if the line contains a link
4375  $headline = Html::element(
4376  'span',
4377  [ 'class' => 'mw-headline-number' ],
4378  $numbering
4379  ) . ' ' . $headline;
4380  }
4381 
4382  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4383  $toc .= Linker::tocLine( $linkAnchor, $tocline,
4384  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4385  }
4386 
4387  # Add the section to the section tree
4388  # Find the DOM node for this header
4389  $noOffset = ( $isTemplate || $sectionIndex === false );
4390  while ( $node && !$noOffset ) {
4391  if ( $node->getName() === 'h' ) {
4392  $bits = $node->splitHeading();
4393  if ( $bits['i'] == $sectionIndex ) {
4394  break;
4395  }
4396  }
4397  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4398  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4399  $node = $node->getNextSibling();
4400  }
4401  $tocraw[] = [
4402  'toclevel' => $toclevel,
4403  'level' => $level,
4404  'line' => $tocline,
4405  'number' => $numbering,
4406  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4407  'fromtitle' => $titleText,
4408  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4409  'anchor' => $anchor,
4410  ];
4411 
4412  # give headline the correct <h#> tag
4413  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4414  // Output edit section links as markers with styles that can be customized by skins
4415  if ( $isTemplate ) {
4416  # Put a T flag in the section identifier, to indicate to extractSections()
4417  # that sections inside <includeonly> should be counted.
4418  $editsectionPage = $titleText;
4419  $editsectionSection = "T-$sectionIndex";
4420  $editsectionContent = null;
4421  } else {
4422  $editsectionPage = $this->mTitle->getPrefixedText();
4423  $editsectionSection = $sectionIndex;
4424  $editsectionContent = $headlineHint;
4425  }
4426  // We use a bit of pesudo-xml for editsection markers. The
4427  // language converter is run later on. Using a UNIQ style marker
4428  // leads to the converter screwing up the tokens when it
4429  // converts stuff. And trying to insert strip tags fails too. At
4430  // this point all real inputted tags have already been escaped,
4431  // so we don't have to worry about a user trying to input one of
4432  // these markers directly. We use a page and section attribute
4433  // to stop the language converter from converting these
4434  // important bits of data, but put the headline hint inside a
4435  // content block because the language converter is supposed to
4436  // be able to convert that piece of data.
4437  // Gets replaced with html in ParserOutput::getText
4438  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4439  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4440  if ( $editsectionContent !== null ) {
4441  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4442  } else {
4443  $editlink .= '/>';
4444  }
4445  } else {
4446  $editlink = '';
4447  }
4448  $head[$headlineCount] = Linker::makeHeadline( $level,
4449  $matches['attrib'][$headlineCount], $anchor, $headline,
4450  $editlink, $fallbackAnchor );
4451 
4452  $headlineCount++;
4453  }
4454 
4455  $this->setOutputType( $oldType );
4456 
4457  # Never ever show TOC if no headers
4458  if ( $numVisible < 1 ) {
4459  $enoughToc = false;
4460  }
4461 
4462  if ( $enoughToc ) {
4463  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4464  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4465  }
4466  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4467  $this->mOutput->setTOCHTML( $toc );
4468  $toc = self::TOC_START . $toc . self::TOC_END;
4469  }
4470 
4471  if ( $isMain ) {
4472  $this->mOutput->setSections( $tocraw );
4473  }
4474 
4475  # split up and insert constructed headlines
4476  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4477  $i = 0;
4478 
4479  // build an array of document sections
4480  $sections = [];
4481  foreach ( $blocks as $block ) {
4482  // $head is zero-based, sections aren't.
4483  if ( empty( $head[$i - 1] ) ) {
4484  $sections[$i] = $block;
4485  } else {
4486  $sections[$i] = $head[$i - 1] . $block;
4487  }
4488 
4499  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $maybeShowEditLink ] );
4500 
4501  $i++;
4502  }
4503 
4504  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4505  // append the TOC at the beginning
4506  // Top anchor now in skin
4507  $sections[0] = $sections[0] . $toc . "\n";
4508  }
4509 
4510  $full .= implode( '', $sections );
4511 
4512  if ( $this->mForceTocPosition ) {
4513  return str_replace( '<!--MWTOC\'"-->', $toc, $full );
4514  } else {
4515  return $full;
4516  }
4517  }
4518 
4530  public function preSaveTransform( $text, Title $title, User $user,
4531  ParserOptions $options, $clearState = true
4532  ) {
4533  if ( $clearState ) {
4534  $magicScopeVariable = $this->lock();
4535  }
4536  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4537  $this->setUser( $user );
4538 
4539  // Strip U+0000 NULL (T159174)
4540  $text = str_replace( "\000", '', $text );
4541 
4542  // We still normalize line endings for backwards-compatibility
4543  // with other code that just calls PST, but this should already
4544  // be handled in TextContent subclasses
4545  $text = TextContent::normalizeLineEndings( $text );
4546 
4547  if ( $options->getPreSaveTransform() ) {
4548  $text = $this->pstPass2( $text, $user );
4549  }
4550  $text = $this->mStripState->unstripBoth( $text );
4551 
4552  $this->setUser( null ); # Reset
4553 
4554  return $text;
4555  }
4556 
4565  private function pstPass2( $text, $user ) {
4566  # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
4567  # $this->contLang here in order to give everyone the same signature and use the default one
4568  # rather than the one selected in each user's preferences. (see also T14815)
4569  $ts = $this->mOptions->getTimestamp();
4570  $timestamp = MWTimestamp::getLocalInstance( $ts );
4571  $ts = $timestamp->format( 'YmdHis' );
4572  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4573 
4574  $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4575 
4576  # Variable replacement
4577  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4578  $text = $this->replaceVariables( $text );
4579 
4580  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4581  # which may corrupt this parser instance via its wfMessage()->text() call-
4582 
4583  # Signatures
4584  if ( strpos( $text, '~~~' ) !== false ) {
4585  $sigText = $this->getUserSig( $user );
4586  $text = strtr( $text, [
4587  '~~~~~' => $d,
4588  '~~~~' => "$sigText $d",
4589  '~~~' => $sigText
4590  ] );
4591  # The main two signature forms used above are time-sensitive
4592  $this->mOutput->setFlag( 'user-signature' );
4593  }
4594 
4595  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4596  $tc = '[' . Title::legalChars() . ']';
4597  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4598 
4599  // [[ns:page (context)|]]
4600  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4601  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4602  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4603  // [[ns:page (context), context|]] (using either single or double-width comma)
4604  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4605  // [[|page]] (reverse pipe trick: add context from page title)
4606  $p2 = "/\[\[\\|($tc+)]]/";
4607 
4608  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4609  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4610  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4611  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4612 
4613  $t = $this->mTitle->getText();
4614  $m = [];
4615  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4616  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4617  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4618  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4619  } else {
4620  # if there's no context, don't bother duplicating the title
4621  $text = preg_replace( $p2, '[[\\1]]', $text );
4622  }
4623 
4624  return $text;
4625  }
4626 
4641  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4642  global $wgMaxSigChars;
4643 
4644  $username = $user->getName();
4645 
4646  # If not given, retrieve from the user object.
4647  if ( $nickname === false ) {
4648  $nickname = $user->getOption( 'nickname' );
4649  }
4650 
4651  if ( is_null( $fancySig ) ) {
4652  $fancySig = $user->getBoolOption( 'fancysig' );
4653  }
4654 
4655  $nickname = $nickname == null ? $username : $nickname;
4656 
4657  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4658  $nickname = $username;
4659  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4660  } elseif ( $fancySig !== false ) {
4661  # Sig. might contain markup; validate this
4662  if ( $this->validateSig( $nickname ) !== false ) {
4663  # Validated; clean up (if needed) and return it
4664  return $this->cleanSig( $nickname, true );
4665  } else {
4666  # Failed to validate; fall back to the default
4667  $nickname = $username;
4668  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4669  }
4670  }
4671 
4672  # Make sure nickname doesnt get a sig in a sig
4673  $nickname = self::cleanSigInSig( $nickname );
4674 
4675  # If we're still here, make it a link to the user page
4676  $userText = wfEscapeWikiText( $username );
4677  $nickText = wfEscapeWikiText( $nickname );
4678  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4679 
4680  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4681  ->title( $this->getTitle() )->text();
4682  }
4683 
4690  public function validateSig( $text ) {
4691  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4692  }
4693 
4704  public function cleanSig( $text, $parsing = false ) {
4705  if ( !$parsing ) {
4706  global $wgTitle;
4707  $magicScopeVariable = $this->lock();
4708  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4709  }
4710 
4711  # Option to disable this feature
4712  if ( !$this->mOptions->getCleanSignatures() ) {
4713  return $text;
4714  }
4715 
4716  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4717  # => Move this logic to braceSubstitution()
4718  $substWord = $this->magicWordFactory->get( 'subst' );
4719  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4720  $substText = '{{' . $substWord->getSynonym( 0 );
4721 
4722  $text = preg_replace( $substRegex, $substText, $text );
4723  $text = self::cleanSigInSig( $text );
4724  $dom = $this->preprocessToDom( $text );
4725  $frame = $this->getPreprocessor()->newFrame();
4726  $text = $frame->expand( $dom );
4727 
4728  if ( !$parsing ) {
4729  $text = $this->mStripState->unstripBoth( $text );
4730  }
4731 
4732  return $text;
4733  }
4734 
4741  public static function cleanSigInSig( $text ) {
4742  $text = preg_replace( '/~{3,5}/', '', $text );
4743  return $text;
4744  }
4745 
4755  public function startExternalParse( Title $title = null, ParserOptions $options,
4756  $outputType, $clearState = true
4757  ) {
4758  $this->startParse( $title, $options, $outputType, $clearState );
4759  }
4760 
4767  private function startParse( Title $title = null, ParserOptions $options,
4768  $outputType, $clearState = true
4769  ) {
4770  $this->setTitle( $title );
4771  $this->mOptions = $options;
4772  $this->setOutputType( $outputType );
4773  if ( $clearState ) {
4774  $this->clearState();
4775  }
4776  }
4777 
4786  public function transformMsg( $text, $options, $title = null ) {
4787  static $executing = false;
4788 
4789  # Guard against infinite recursion
4790  if ( $executing ) {
4791  return $text;
4792  }
4793  $executing = true;
4794 
4795  if ( !$title ) {
4796  global $wgTitle;
4797  $title = $wgTitle;
4798  }
4799 
4800  $text = $this->preprocess( $text, $title, $options );
4801 
4802  $executing = false;
4803  return $text;
4804  }
4805 
4830  public function setHook( $tag, callable $callback ) {
4831  $tag = strtolower( $tag );
4832  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4833  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4834  }
4835  $oldVal = $this->mTagHooks[$tag] ?? null;
4836  $this->mTagHooks[$tag] = $callback;
4837  if ( !in_array( $tag, $this->mStripList ) ) {
4838  $this->mStripList[] = $tag;
4839  }
4840 
4841  return $oldVal;
4842  }
4843 
4861  public function setTransparentTagHook( $tag, callable $callback ) {
4862  $tag = strtolower( $tag );
4863  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4864  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4865  }
4866  $oldVal = $this->mTransparentTagHooks[$tag] ?? null;
4867  $this->mTransparentTagHooks[$tag] = $callback;
4868 
4869  return $oldVal;
4870  }
4871 
4875  public function clearTagHooks() {
4876  $this->mTagHooks = [];
4877  $this->mFunctionTagHooks = [];
4878  $this->mStripList = $this->mDefaultStripList;
4879  }
4880 
4924  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4925  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4926  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4927 
4928  # Add to function cache
4929  $mw = $this->magicWordFactory->get( $id );
4930  if ( !$mw ) {
4931  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4932  }
4933 
4934  $synonyms = $mw->getSynonyms();
4935  $sensitive = intval( $mw->isCaseSensitive() );
4936 
4937  foreach ( $synonyms as $syn ) {
4938  # Case
4939  if ( !$sensitive ) {
4940  $syn = $this->contLang->lc( $syn );
4941  }
4942  # Add leading hash
4943  if ( !( $flags & self::SFH_NO_HASH ) ) {
4944  $syn = '#' . $syn;
4945  }
4946  # Remove trailing colon
4947  if ( substr( $syn, -1, 1 ) === ':' ) {
4948  $syn = substr( $syn, 0, -1 );
4949  }
4950  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4951  }
4952  return $oldVal;
4953  }
4954 
4960  public function getFunctionHooks() {
4961  $this->firstCallInit();
4962  return array_keys( $this->mFunctionHooks );
4963  }
4964 
4975  public function setFunctionTagHook( $tag, callable $callback, $flags ) {
4976  $tag = strtolower( $tag );
4977  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4978  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4979  }
4980  $old = $this->mFunctionTagHooks[$tag] ?? null;
4981  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4982 
4983  if ( !in_array( $tag, $this->mStripList ) ) {
4984  $this->mStripList[] = $tag;
4985  }
4986 
4987  return $old;
4988  }
4989 
4997  public function replaceLinkHolders( &$text, $options = 0 ) {
4998  $this->mLinkHolders->replace( $text );
4999  }
5000 
5008  public function replaceLinkHoldersText( $text ) {
5009  return $this->mLinkHolders->replaceText( $text );
5010  }
5011 
5025  public function renderImageGallery( $text, $params ) {
5026  $mode = false;
5027  if ( isset( $params['mode'] ) ) {
5028  $mode = $params['mode'];
5029  }
5030 
5031  try {
5032  $ig = ImageGalleryBase::factory( $mode );
5033  } catch ( Exception $e ) {
5034  // If invalid type set, fallback to default.
5035  $ig = ImageGalleryBase::factory( false );
5036  }
5037 
5038  $ig->setContextTitle( $this->mTitle );
5039  $ig->setShowBytes( false );
5040  $ig->setShowDimensions( false );
5041  $ig->setShowFilename( false );
5042  $ig->setParser( $this );
5043  $ig->setHideBadImages();
5044  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
5045 
5046  if ( isset( $params['showfilename'] ) ) {
5047  $ig->setShowFilename( true );
5048  } else {
5049  $ig->setShowFilename( false );
5050  }
5051  if ( isset( $params['caption'] ) ) {
5052  $caption = $params['caption'];
5053  $caption = htmlspecialchars( $caption );
5054  $caption = $this->replaceInternalLinks( $caption );
5055  $ig->setCaptionHtml( $caption );
5056  }
5057  if ( isset( $params['perrow'] ) ) {
5058  $ig->setPerRow( $params['perrow'] );
5059  }
5060  if ( isset( $params['widths'] ) ) {
5061  $ig->setWidths( $params['widths'] );
5062  }
5063  if ( isset( $params['heights'] ) ) {
5064  $ig->setHeights( $params['heights'] );
5065  }
5066  $ig->setAdditionalOptions( $params );
5067 
5068  // Avoid PHP 7.1 warning from passing $this by reference
5069  $parser = $this;
5070  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
5071 
5072  $lines = StringUtils::explode( "\n", $text );
5073  foreach ( $lines as $line ) {
5074  # match lines like these:
5075  # Image:someimage.jpg|This is some image
5076  $matches = [];
5077  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5078  # Skip empty lines
5079  if ( count( $matches ) == 0 ) {
5080  continue;
5081  }
5082 
5083  if ( strpos( $matches[0], '%' ) !== false ) {
5084  $matches[1] = rawurldecode( $matches[1] );
5085  }
5087  if ( is_null( $title ) ) {
5088  # Bogus title. Ignore these so we don't bomb out later.
5089  continue;
5090  }
5091 
5092  # We need to get what handler the file uses, to figure out parameters.
5093  # Note, a hook can overide the file name, and chose an entirely different
5094  # file (which potentially could be of a different type and have different handler).
5095  $options = [];
5096  $descQuery = false;
5097  Hooks::run( 'BeforeParserFetchFileAndTitle',
5098  [ $this, $title, &$options, &$descQuery ] );
5099  # Don't register it now, as TraditionalImageGallery does that later.
5100  $file = $this->fetchFileNoRegister( $title, $options );
5101  $handler = $file ? $file->getHandler() : false;
5102 
5103  $paramMap = [
5104  'img_alt' => 'gallery-internal-alt',
5105  'img_link' => 'gallery-internal-link',
5106  ];
5107  if ( $handler ) {
5108  $paramMap = $paramMap + $handler->getParamMap();
5109  // We don't want people to specify per-image widths.
5110  // Additionally the width parameter would need special casing anyhow.
5111  unset( $paramMap['img_width'] );
5112  }
5113 
5114  $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5115 
5116  $label = '';
5117  $alt = '';
5118  $link = '';
5119  $handlerOptions = [];
5120  if ( isset( $matches[3] ) ) {
5121  // look for an |alt= definition while trying not to break existing
5122  // captions with multiple pipes (|) in it, until a more sensible grammar
5123  // is defined for images in galleries
5124 
5125  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5126  // splitting on '|' is a bit odd, and different from makeImage.
5127  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5128  // Protect LanguageConverter markup
5129  $parameterMatches = StringUtils::delimiterExplode(
5130  '-{', '}-', '|', $matches[3], true /* nested */
5131  );
5132 
5133  foreach ( $parameterMatches as $parameterMatch ) {
5134  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5135  if ( $magicName ) {
5136  $paramName = $paramMap[$magicName];
5137 
5138  switch ( $paramName ) {
5139  case 'gallery-internal-alt':
5140  $alt = $this->stripAltText( $match, false );
5141  break;
5142  case 'gallery-internal-link':
5143  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5144  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5145  // Result of LanguageConverter::markNoConversion
5146  // invoked on an external link.
5147  $linkValue = substr( $linkValue, 4, -2 );
5148  }
5149  list( $type, $target ) = $this->parseLinkParameter( $linkValue );
5150  if ( $type === 'link-url' ) {
5151  $link = $target;
5152  $this->mOutput->addExternalLink( $target );
5153  } elseif ( $type === 'link-title' ) {
5154  $link = $target->getLinkURL();
5155  $this->mOutput->addLink( $target );
5156  }
5157  break;
5158  default:
5159  // Must be a handler specific parameter.
5160  if ( $handler->validateParam( $paramName, $match ) ) {
5161  $handlerOptions[$paramName] = $match;
5162  } else {
5163  // Guess not, consider it as caption.
5164  wfDebug( "$parameterMatch failed parameter validation\n" );
5165  $label = $parameterMatch;
5166  }
5167  }
5168 
5169  } else {
5170  // Last pipe wins.
5171  $label = $parameterMatch;
5172  }
5173  }
5174  }
5175 
5176  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5177  }
5178  $html = $ig->toHTML();
5179  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5180  return $html;
5181  }
5182 
5187  public function getImageParams( $handler ) {
5188  if ( $handler ) {
5189  $handlerClass = get_class( $handler );
5190  } else {
5191  $handlerClass = '';
5192  }
5193  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5194  # Initialise static lists
5195  static $internalParamNames = [
5196  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5197  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5198  'bottom', 'text-bottom' ],
5199  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5200  'upright', 'border', 'link', 'alt', 'class' ],
5201  ];
5202  static $internalParamMap;
5203  if ( !$internalParamMap ) {
5204  $internalParamMap = [];
5205  foreach ( $internalParamNames as $type => $names ) {
5206  foreach ( $names as $name ) {
5207  // For grep: img_left, img_right, img_center, img_none,
5208  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5209  // img_bottom, img_text_bottom,
5210  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5211  // img_border, img_link, img_alt, img_class
5212  $magicName = str_replace( '-', '_', "img_$name" );
5213  $internalParamMap[$magicName] = [ $type, $name ];
5214  }
5215  }
5216  }
5217 
5218  # Add handler params
5219  $paramMap = $internalParamMap;
5220  if ( $handler ) {
5221  $handlerParamMap = $handler->getParamMap();
5222  foreach ( $handlerParamMap as $magic => $paramName ) {
5223  $paramMap[$magic] = [ 'handler', $paramName ];
5224  }
5225  }
5226  $this->mImageParams[$handlerClass] = $paramMap;
5227  $this->mImageParamsMagicArray[$handlerClass] =
5228  $this->magicWordFactory->newArray( array_keys( $paramMap ) );
5229  }
5230  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5231  }
5232 
5241  public function makeImage( $title, $options, $holders = false ) {
5242  # Check if the options text is of the form "options|alt text"
5243  # Options are:
5244  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5245  # * left no resizing, just left align. label is used for alt= only
5246  # * right same, but right aligned
5247  # * none same, but not aligned
5248  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5249  # * center center the image
5250  # * frame Keep original image size, no magnify-button.
5251  # * framed Same as "frame"
5252  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5253  # * upright reduce width for upright images, rounded to full __0 px
5254  # * border draw a 1px border around the image
5255  # * alt Text for HTML alt attribute (defaults to empty)
5256  # * class Set a class for img node
5257  # * link Set the target of the image link. Can be external, interwiki, or local
5258  # vertical-align values (no % or length right now):
5259  # * baseline
5260  # * sub
5261  # * super
5262  # * top
5263  # * text-top
5264  # * middle
5265  # * bottom
5266  # * text-bottom
5267 
5268  # Protect LanguageConverter markup when splitting into parts
5270  '-{', '}-', '|', $options, true /* allow nesting */
5271  );
5272 
5273  # Give extensions a chance to select the file revision for us
5274  $options = [];
5275  $descQuery = false;
5276  Hooks::run( 'BeforeParserFetchFileAndTitle',
5277  [ $this, $title, &$options, &$descQuery ] );
5278  # Fetch and register the file (file title may be different via hooks)
5279  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5280 
5281  # Get parameter map
5282  $handler = $file ? $file->getHandler() : false;
5283 
5284  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5285 
5286  if ( !$file ) {
5287  $this->addTrackingCategory( 'broken-file-category' );
5288  }
5289 
5290  # Process the input parameters
5291  $caption = '';
5292  $params = [ 'frame' => [], 'handler' => [],
5293  'horizAlign' => [], 'vertAlign' => [] ];
5294  $seenformat = false;
5295  foreach ( $parts as $part ) {
5296  $part = trim( $part );
5297  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5298  $validated = false;
5299  if ( isset( $paramMap[$magicName] ) ) {
5300  list( $type, $paramName ) = $paramMap[$magicName];
5301 
5302  # Special case; width and height come in one variable together
5303  if ( $type === 'handler' && $paramName === 'width' ) {
5304  $parsedWidthParam = self::parseWidthParam( $value );
5305  if ( isset( $parsedWidthParam['width'] ) ) {
5306  $width = $parsedWidthParam['width'];
5307  if ( $handler->validateParam( 'width', $width ) ) {
5308  $params[$type]['width'] = $width;
5309  $validated = true;
5310  }
5311  }
5312  if ( isset( $parsedWidthParam['height'] ) ) {
5313  $height = $parsedWidthParam['height'];
5314  if ( $handler->validateParam( 'height', $height ) ) {
5315  $params[$type]['height'] = $height;
5316  $validated = true;
5317  }
5318  }
5319  # else no validation -- T15436
5320  } else {
5321  if ( $type === 'handler' ) {
5322  # Validate handler parameter
5323  $validated = $handler->validateParam( $paramName, $value );
5324  } else {
5325  # Validate internal parameters
5326  switch ( $paramName ) {
5327  case 'manualthumb':
5328  case 'alt':
5329  case 'class':
5330  # @todo FIXME: Possibly check validity here for
5331  # manualthumb? downstream behavior seems odd with
5332  # missing manual thumbs.
5333  $validated = true;
5334  $value = $this->stripAltText( $value, $holders );
5335  break;
5336  case 'link':
5337  list( $paramName, $value ) = $this->parseLinkParameter( $value );
5338  if ( $paramName ) {
5339  $validated = true;
5340  if ( $paramName === 'no-link' ) {
5341  $value = true;
5342  }
5343  if ( $paramName === 'link-url' ) {
5344  if ( $this->mOptions->getExternalLinkTarget() ) {
5345  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5346  }
5347  }
5348  }
5349  break;
5350  case 'frameless':
5351  case 'framed':
5352  case 'thumbnail':
5353  // use first appearing option, discard others.
5354  $validated = !$seenformat;
5355  $seenformat = true;
5356  break;
5357  default:
5358  # Most other things appear to be empty or numeric...
5359  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5360  }
5361  }
5362 
5363  if ( $validated ) {
5364  $params[$type][$paramName] = $value;
5365  }
5366  }
5367  }
5368  if ( !$validated ) {
5369  $caption = $part;
5370  }
5371  }
5372 
5373  # Process alignment parameters
5374  if ( $params['horizAlign'] ) {
5375  $params['frame']['align'] = key( $params['horizAlign'] );
5376  }
5377  if ( $params['vertAlign'] ) {
5378  $params['frame']['valign'] = key( $params['vertAlign'] );
5379  }
5380 
5381  $params['frame']['caption'] = $caption;
5382 
5383  # Will the image be presented in a frame, with the caption below?
5384  $imageIsFramed = isset( $params['frame']['frame'] )
5385  || isset( $params['frame']['framed'] )
5386  || isset( $params['frame']['thumbnail'] )
5387  || isset( $params['frame']['manualthumb'] );
5388 
5389  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5390  # came to also set the caption, ordinary text after the image -- which
5391  # makes no sense, because that just repeats the text multiple times in
5392  # screen readers. It *also* came to set the title attribute.
5393  # Now that we have an alt attribute, we should not set the alt text to
5394  # equal the caption: that's worse than useless, it just repeats the
5395  # text. This is the framed/thumbnail case. If there's no caption, we
5396  # use the unnamed parameter for alt text as well, just for the time be-
5397  # ing, if the unnamed param is set and the alt param is not.
5398  # For the future, we need to figure out if we want to tweak this more,
5399  # e.g., introducing a title= parameter for the title; ignoring the un-
5400  # named parameter entirely for images without a caption; adding an ex-
5401  # plicit caption= parameter and preserving the old magic unnamed para-
5402  # meter for BC; ...
5403  if ( $imageIsFramed ) { # Framed image
5404  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5405  # No caption or alt text, add the filename as the alt text so
5406  # that screen readers at least get some description of the image
5407  $params['frame']['alt'] = $title->getText();
5408  }
5409  # Do not set $params['frame']['title'] because tooltips don't make sense
5410  # for framed images
5411  } else { # Inline image
5412  if ( !isset( $params['frame']['alt'] ) ) {
5413  # No alt text, use the "caption" for the alt text
5414  if ( $caption !== '' ) {
5415  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5416  } else {
5417  # No caption, fall back to using the filename for the
5418  # alt text
5419  $params['frame']['alt'] = $title->getText();
5420  }
5421  }
5422  # Use the "caption" for the tooltip text
5423  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5424  }
5425 
5426  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5427 
5428  # Linker does the rest
5429  $time = $options['time'] ?? false;
5430  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5431  $time, $descQuery, $this->mOptions->getThumbSize() );
5432 
5433  # Give the handler a chance to modify the parser object
5434  if ( $handler ) {
5435  $handler->parserTransformHook( $this, $file );
5436  }
5437 
5438  return $ret;
5439  }
5440 
5458  public function parseLinkParameter( $value ) {
5459  $chars = self::EXT_LINK_URL_CLASS;
5460  $addr = self::EXT_LINK_ADDR;
5461  $prots = $this->mUrlProtocols;
5462  $type = null;
5463  $target = false;
5464  if ( $value === '' ) {
5465  $type = 'no-link';
5466  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5467  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5468  $this->mOutput->addExternalLink( $value );
5469  $type = 'link-url';
5470  $target = $value;
5471  }
5472  } else {
5473  $linkTitle = Title::newFromText( $value );
5474  if ( $linkTitle ) {
5475  $this->mOutput->addLink( $linkTitle );
5476  $type = 'link-title';
5477  $target = $linkTitle;
5478  }
5479  }
5480  return [ $type, $target ];
5481  }
5482 
5488  protected function stripAltText( $caption, $holders ) {
5489  # Strip bad stuff out of the title (tooltip). We can't just use
5490  # replaceLinkHoldersText() here, because if this function is called
5491  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5492  if ( $holders ) {
5493  $tooltip = $holders->replaceText( $caption );
5494  } else {
5495  $tooltip = $this->replaceLinkHoldersText( $caption );
5496  }
5497 
5498  # make sure there are no placeholders in thumbnail attributes
5499  # that are later expanded to html- so expand them now and
5500  # remove the tags
5501  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5502  $tooltip = Sanitizer::stripAllTags( $tooltip );
5503 
5504  return $tooltip;
5505  }
5506 
5512  public function disableCache() {
5513  wfDebug( "Parser output marked as uncacheable.\n" );
5514  if ( !$this->mOutput ) {
5515  throw new MWException( __METHOD__ .
5516  " can only be called when actually parsing something" );
5517  }
5518  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5519  }
5520 
5529  public function attributeStripCallback( &$text, $frame = false ) {
5530  $text = $this->replaceVariables( $text, $frame );
5531  $text = $this->mStripState->unstripBoth( $text );
5532  return $text;
5533  }
5534 
5540  public function getTags() {
5541  $this->firstCallInit();
5542  return array_merge(
5543  array_keys( $this->mTransparentTagHooks ),
5544  array_keys( $this->mTagHooks ),
5545  array_keys( $this->mFunctionTagHooks )
5546  );
5547  }
5548 
5553  public function getFunctionSynonyms() {
5554  $this->firstCallInit();
5555  return $this->mFunctionSynonyms;
5556  }
5557 
5562  public function getUrlProtocols() {
5563  return $this->mUrlProtocols;
5564  }
5565 
5576  public function replaceTransparentTags( $text ) {
5577  $matches = [];
5578  $elements = array_keys( $this->mTransparentTagHooks );
5579  $text = self::extractTagsAndParams( $elements, $text, $matches );
5580  $replacements = [];
5581 
5582  foreach ( $matches as $marker => $data ) {
5583  list( $element, $content, $params, $tag ) = $data;
5584  $tagName = strtolower( $element );
5585  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5586  $output = call_user_func_array(
5587  $this->mTransparentTagHooks[$tagName],
5588  [ $content, $params, $this ]
5589  );
5590  } else {
5591  $output = $tag;
5592  }
5593  $replacements[$marker] = $output;
5594  }
5595  return strtr( $text, $replacements );
5596  }
5597 
5627  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5628  global $wgTitle; # not generally used but removes an ugly failure mode
5629 
5630  $magicScopeVariable = $this->lock();
5631  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5632  $outText = '';
5633  $frame = $this->getPreprocessor()->newFrame();
5634 
5635  # Process section extraction flags
5636  $flags = 0;
5637  $sectionParts = explode( '-', $sectionId );
5638  $sectionIndex = array_pop( $sectionParts );
5639  foreach ( $sectionParts as $part ) {
5640  if ( $part === 'T' ) {
5641  $flags |= self::PTD_FOR_INCLUSION;
5642  }
5643  }
5644 
5645  # Check for empty input
5646  if ( strval( $text ) === '' ) {
5647  # Only sections 0 and T-0 exist in an empty document
5648  if ( $sectionIndex == 0 ) {
5649  if ( $mode === 'get' ) {
5650  return '';
5651  } else {
5652  return $newText;
5653  }
5654  } else {
5655  if ( $mode === 'get' ) {
5656  return $newText;
5657  } else {
5658  return $text;
5659  }
5660  }
5661  }
5662 
5663  # Preprocess the text
5664  $root = $this->preprocessToDom( $text, $flags );
5665 
5666  # <h> nodes indicate section breaks
5667  # They can only occur at the top level, so we can find them by iterating the root's children
5668  $node = $root->getFirstChild();
5669 
5670  # Find the target section
5671  if ( $sectionIndex == 0 ) {
5672  # Section zero doesn't nest, level=big
5673  $targetLevel = 1000;
5674  } else {
5675  while ( $node ) {
5676  if ( $node->getName() === 'h' ) {
5677  $bits = $node->splitHeading();
5678  if ( $bits['i'] == $sectionIndex ) {
5679  $targetLevel = $bits['level'];
5680  break;
5681  }
5682  }
5683  if ( $mode === 'replace' ) {
5684  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5685  }
5686  $node = $node->getNextSibling();
5687  }
5688  }
5689 
5690  if ( !$node ) {
5691  # Not found
5692  if ( $mode === 'get' ) {
5693  return $newText;
5694  } else {
5695  return $text;
5696  }
5697  }
5698 
5699  # Find the end of the section, including nested sections
5700  do {
5701  if ( $node->getName() === 'h' ) {
5702  $bits = $node->splitHeading();
5703  $curLevel = $bits['level'];
5704  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5705  break;
5706  }
5707  }
5708  if ( $mode === 'get' ) {
5709  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5710  }
5711  $node = $node->getNextSibling();
5712  } while ( $node );
5713 
5714  # Write out the remainder (in replace mode only)
5715  if ( $mode === 'replace' ) {
5716  # Output the replacement text
5717  # Add two newlines on -- trailing whitespace in $newText is conventionally
5718  # stripped by the editor, so we need both newlines to restore the paragraph gap
5719  # Only add trailing whitespace if there is newText
5720  if ( $newText != "" ) {
5721  $outText .= $newText . "\n\n";
5722  }
5723 
5724  while ( $node ) {
5725  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5726  $node = $node->getNextSibling();
5727  }
5728  }
5729 
5730  if ( is_string( $outText ) ) {
5731  # Re-insert stripped tags
5732  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5733  }
5734 
5735  return $outText;
5736  }
5737 
5752  public function getSection( $text, $sectionId, $defaultText = '' ) {
5753  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5754  }
5755 
5768  public function replaceSection( $oldText, $sectionId, $newText ) {
5769  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5770  }
5771 
5777  public function getRevisionId() {
5778  return $this->mRevisionId;
5779  }
5780 
5787  public function getRevisionObject() {
5788  if ( !is_null( $this->mRevisionObject ) ) {
5789  return $this->mRevisionObject;
5790  }
5791 
5792  // NOTE: try to get the RevisionObject even if mRevisionId is null.
5793  // This is useful when parsing revision that has not yet been saved.
5794  // However, if we get back a saved revision even though we are in
5795  // preview mode, we'll have to ignore it, see below.
5796  // NOTE: This callback may be used to inject an OLD revision that was
5797  // already loaded, so "current" is a bit of a misnomer. We can't just
5798  // skip it if mRevisionId is set.
5799  $rev = call_user_func(
5800  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5801  );
5802 
5803  if ( $this->mRevisionId === null && $rev && $rev->getId() ) {
5804  // We are in preview mode (mRevisionId is null), and the current revision callback
5805  // returned an existing revision. Ignore it and return null, it's probably the page's
5806  // current revision, which is not what we want here. Note that we do want to call the
5807  // callback to allow the unsaved revision to be injected here, e.g. for
5808  // self-transclusion previews.
5809  return null;
5810  }
5811 
5812  // If the parse is for a new revision, then the callback should have
5813  // already been set to force the object and should match mRevisionId.
5814  // If not, try to fetch by mRevisionId for sanity.
5815  if ( $this->mRevisionId && $rev && $rev->getId() != $this->mRevisionId ) {
5816  $rev = Revision::newFromId( $this->mRevisionId );
5817  }
5818 
5819  $this->mRevisionObject = $rev;
5820 
5821  return $this->mRevisionObject;
5822  }
5823 
5829  public function getRevisionTimestamp() {
5830  if ( is_null( $this->mRevisionTimestamp ) ) {
5831  $revObject = $this->getRevisionObject();
5832  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5833 
5834  # The cryptic '' timezone parameter tells to use the site-default
5835  # timezone offset instead of the user settings.
5836  # Since this value will be saved into the parser cache, served
5837  # to other users, and potentially even used inside links and such,
5838  # it needs to be consistent for all visitors.
5839  $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
5840  }
5841  return $this->mRevisionTimestamp;
5842  }
5843 
5849  public function getRevisionUser() {
5850  if ( is_null( $this->mRevisionUser ) ) {
5851  $revObject = $this->getRevisionObject();
5852 
5853  # if this template is subst: the revision id will be blank,
5854  # so just use the current user's name
5855  if ( $revObject ) {
5856  $this->mRevisionUser = $revObject->getUserText();
5857  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5858  $this->mRevisionUser = $this->getUser()->getName();
5859  }
5860  }
5861  return $this->mRevisionUser;
5862  }
5863 
5869  public function getRevisionSize() {
5870  if ( is_null( $this->mRevisionSize ) ) {
5871  $revObject = $this->getRevisionObject();
5872 
5873  # if this variable is subst: the revision id will be blank,
5874  # so just use the parser input size, because the own substituation
5875  # will change the size.
5876  if ( $revObject ) {
5877  $this->mRevisionSize = $revObject->getSize();
5878  } else {
5879  $this->mRevisionSize = $this->mInputSize;
5880  }
5881  }
5882  return $this->mRevisionSize;
5883  }
5884 
5890  public function setDefaultSort( $sort ) {
5891  $this->mDefaultSort = $sort;
5892  $this->mOutput->setProperty( 'defaultsort', $sort );
5893  }
5894 
5905  public function getDefaultSort() {
5906  if ( $this->mDefaultSort !== false ) {
5907  return $this->mDefaultSort;
5908  } else {
5909  return '';
5910  }
5911  }
5912 
5919  public function getCustomDefaultSort() {
5920  return $this->mDefaultSort;
5921  }
5922 
5923  private static function getSectionNameFromStrippedText( $text ) {
5924  $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5925  $text = Sanitizer::decodeCharReferences( $text );
5926  $text = self::normalizeSectionName( $text );
5927  return $text;
5928  }
5929 
5930  private static function makeAnchor( $sectionName ) {
5931  return '#' . Sanitizer::escapeIdForLink( $sectionName );
5932  }
5933 
5934  private static function makeLegacyAnchor( $sectionName ) {
5935  global $wgFragmentMode;
5936  if ( isset( $wgFragmentMode[1] ) && $wgFragmentMode[1] === 'legacy' ) {
5937  // ForAttribute() and ForLink() are the same for legacy encoding
5938  $id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );
5939  } else {
5940  $id = Sanitizer::escapeIdForLink( $sectionName );
5941  }
5942 
5943  return "#$id";
5944  }
5945 
5954  public function guessSectionNameFromWikiText( $text ) {
5955  # Strip out wikitext links(they break the anchor)
5956  $text = $this->stripSectionName( $text );
5957  $sectionName = self::getSectionNameFromStrippedText( $text );
5958  return self::makeAnchor( $sectionName );
5959  }
5960 
5970  public function guessLegacySectionNameFromWikiText( $text ) {
5971  # Strip out wikitext links(they break the anchor)
5972  $text = $this->stripSectionName( $text );
5973  $sectionName = self::getSectionNameFromStrippedText( $text );
5974  return self::makeLegacyAnchor( $sectionName );
5975  }
5976 
5982  public static function guessSectionNameFromStrippedText( $text ) {
5983  $sectionName = self::getSectionNameFromStrippedText( $text );
5984  return self::makeAnchor( $sectionName );
5985  }
5986 
5993  private static function normalizeSectionName( $text ) {
5994  # T90902: ensure the same normalization is applied for IDs as to links
5995  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
5996  try {
5997 
5998  $parts = $titleParser->splitTitleString( "#$text" );
5999  } catch ( MalformedTitleException $ex ) {
6000  return $text;
6001  }
6002  return $parts['fragment'];
6003  }
6004 
6019  public function stripSectionName( $text ) {
6020  # Strip internal link markup
6021  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
6022  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
6023 
6024  # Strip external link markup
6025  # @todo FIXME: Not tolerant to blank link text
6026  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
6027  # on how many empty links there are on the page - need to figure that out.
6028  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
6029 
6030  # Parse wikitext quotes (italics & bold)
6031  $text = $this->doQuotes( $text );
6032 
6033  # Strip HTML tags
6034  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
6035  return $text;
6036  }
6037 
6048  public function testSrvus( $text, Title $title, ParserOptions $options,
6049  $outputType = self::OT_HTML
6050  ) {
6051  $magicScopeVariable = $this->lock();
6052  $this->startParse( $title, $options, $outputType, true );
6053 
6054  $text = $this->replaceVariables( $text );
6055  $text = $this->mStripState->unstripBoth( $text );
6056  $text = Sanitizer::removeHTMLtags( $text );
6057  return $text;
6058  }
6059 
6066  public function testPst( $text, Title $title, ParserOptions $options ) {
6067  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
6068  }
6069 
6076  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
6077  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
6078  }
6079 
6096  public function markerSkipCallback( $s, $callback ) {
6097  $i = 0;
6098  $out = '';
6099  while ( $i < strlen( $s ) ) {
6100  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
6101  if ( $markerStart === false ) {
6102  $out .= call_user_func( $callback, substr( $s, $i ) );
6103  break;
6104  } else {
6105  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
6106  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6107  if ( $markerEnd === false ) {
6108  $out .= substr( $s, $markerStart );
6109  break;
6110  } else {
6111  $markerEnd += strlen( self::MARKER_SUFFIX );
6112  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6113  $i = $markerEnd;
6114  }
6115  }
6116  }
6117  return $out;
6118  }
6119 
6126  public function killMarkers( $text ) {
6127  return $this->mStripState->killMarkers( $text );
6128  }
6129 
6147  public function serializeHalfParsedText( $text ) {
6148  wfDeprecated( __METHOD__, '1.31' );
6149  $data = [
6150  'text' => $text,
6151  'version' => self::HALF_PARSED_VERSION,
6152  'stripState' => $this->mStripState->getSubState( $text ),
6153  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
6154  ];
6155  return $data;
6156  }
6157 
6174  public function unserializeHalfParsedText( $data ) {
6175  wfDeprecated( __METHOD__, '1.31' );
6176  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
6177  throw new MWException( __METHOD__ . ': invalid version' );
6178  }
6179 
6180  # First, extract the strip state.
6181  $texts = [ $data['text'] ];
6182  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6183 
6184  # Now renumber links
6185  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6186 
6187  # Should be good to go.
6188  return $texts[0];
6189  }
6190 
6201  public function isValidHalfParsedText( $data ) {
6202  wfDeprecated( __METHOD__, '1.31' );
6203  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6204  }
6205 
6215  public static function parseWidthParam( $value, $parseHeight = true ) {
6216  $parsedWidthParam = [];
6217  if ( $value === '' ) {
6218  return $parsedWidthParam;
6219  }
6220  $m = [];
6221  # (T15500) In both cases (width/height and width only),
6222  # permit trailing "px" for backward compatibility.
6223  if ( $parseHeight && preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6224  $width = intval( $m[1] );
6225  $height = intval( $m[2] );
6226  $parsedWidthParam['width'] = $width;
6227  $parsedWidthParam['height'] = $height;
6228  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6229  $width = intval( $value );
6230  $parsedWidthParam['width'] = $width;
6231  }
6232  return $parsedWidthParam;
6233  }
6234 
6244  protected function lock() {
6245  if ( $this->mInParse ) {
6246  throw new MWException( "Parser state cleared while parsing. "
6247  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6248  }
6249 
6250  // Save the backtrace when locking, so that if some code tries locking again,
6251  // we can print the lock owner's backtrace for easier debugging
6252  $e = new Exception;
6253  $this->mInParse = $e->getTraceAsString();
6254 
6255  $recursiveCheck = new ScopedCallback( function () {
6256  $this->mInParse = false;
6257  } );
6258 
6259  return $recursiveCheck;
6260  }
6261 
6272  public static function stripOuterParagraph( $html ) {
6273  $m = [];
6274  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6275  if ( strpos( $m[1], '</p>' ) === false ) {
6276  $html = $m[1];
6277  }
6278  }
6279 
6280  return $html;
6281  }
6282 
6293  public function getFreshParser() {
6294  if ( $this->mInParse ) {
6295  return $this->factory->create();
6296  } else {
6297  return $this;
6298  }
6299  }
6300 
6307  public function enableOOUI() {
6308  OutputPage::setupOOUI();
6309  $this->mOutput->setEnableOOUI( true );
6310  }
6311 }
OT_MSG
const OT_MSG
Definition: Defines.php:187
SiteStats\articles
static articles()
Definition: SiteStats.php:103
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1305
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:42
save
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
MagicWordArray
Class for handling an array of magic words.
Definition: MagicWordArray.php:32
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
broken
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped broken
Definition: hooks.txt:2044
if
if($IP===false)
Definition: cleanupArchiveUserText.php:4
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
Revision\newKnownCurrent
static newKnownCurrent(IDatabase $db, $pageIdOrTitle, $revId=0)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1299
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:280
PPFrame\STRIP_COMMENTS
const STRIP_COMMENTS
Definition: Preprocessor.php:169
MWNamespace\isNonincludable
static isNonincludable( $index)
It is not possible to use pages from this namespace as template?
Definition: MWNamespace.php:456
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
ParserOutput
Definition: ParserOutput.php:25
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:114
SiteStats\users
static users()
Definition: SiteStats.php:121
MagicWordFactory
A factory that stores information about MagicWords, and creates them on demand with caching.
Definition: MagicWordFactory.php:34
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2683
SiteStats\activeUsers
static activeUsers()
Definition: SiteStats.php:130
Linker\makeSelfLinkObj
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition: Linker.php:163
PPFrame\NO_ARGS
const NO_ARGS
Definition: Preprocessor.php:167
wfSetVar
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
Definition: GlobalFunctions.php:1673
captcha-old.count
count
Definition: captcha-old.py:249
Linker\tocIndent
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1505
$wgTranscludeCacheExpiry
$wgTranscludeCacheExpiry
Expiry time for transcluded templates cached in object cache.
Definition: DefaultSettings.php:4413
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED since 1.16! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:2042
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1954
SiteStats\pages
static pages()
Definition: SiteStats.php:112
$wgNoFollowDomainExceptions
$wgNoFollowDomainExceptions
If this is set to an array of domains, external links to these domain names (or any subdomains) will ...
Definition: DefaultSettings.php:4378
$wgShowHostnames
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.
Definition: DefaultSettings.php:6343
wfUrlencode
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
Definition: GlobalFunctions.php:331
SiteStats\numberingroup
static numberingroup( $group)
Find the number of users in a given user group.
Definition: SiteStats.php:150
$req
this hook is for auditing only $req
Definition: hooks.txt:1018
it
=Architecture==Two class hierarchies are used to provide the functionality associated with the different content models:*Content interface(and AbstractContent base class) define functionality that acts on the concrete content of a page, and *ContentHandler base class provides functionality specific to a content model, but not acting on concrete content. The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation and analysis of page content must be done via the appropriate methods of the Content object. For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler object for a given content model can be obtained using ContentHandler::getForModelID($id). Also Title, WikiPage and Revision now have getContentHandler() methods for convenience. ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp. Revision::getContent() to get a page 's content as a Content object. These two methods should be the ONLY way in which page content is accessed. Another important function of ContentHandler objects is to define custom action handlers for a content model, see ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing.==Serialization==With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface are used to represent and handle the content internally. For storage and data exchange, each content model supports at least one serialization format via ContentHandler::serializeContent($content). The list of supported formats for a given content model can be accessed using ContentHandler::getSupportedFormats(). Content serialization formats are identified using MIME type like strings. The following formats are built in:*text/x-wiki - wikitext *text/javascript - for js pages *text/css - for css pages *text/plain - for future use, e.g. with plain text messages. *text/html - for future use, e.g. with plain html messages. *application/vnd.php.serialized - for future use with the api and for extensions *application/json - for future use with the api, and for use by extensions *application/xml - for future use with the api, and for use by extensions In PHP, use the corresponding CONTENT_FORMAT_XXX constant. Note that when using the API to access page content, especially action=edit, action=parse and action=query &prop=revisions, the model and format of the content should always be handled explicitly. Without that information, interpretation of the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or Special:Export. Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and contentformat is also json(or rather, application/json), the page content is represented as a string containing an escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API modules that allow access to that content in a more natural form.==Compatibility==The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least for pages that contain wikitext or other text based content. However, a number of functions and hooks have been deprecated in favor of new versions that are aware of the page 's content model, and will now generate warnings when used. Most importantly, the following functions have been deprecated:*Revisions::getText() is deprecated in favor Revisions::getContent() *WikiPage::getText() is deprecated in favor WikiPage::getContent() Also, the old Article::getContent()(which returns text) is superceded by Article::getContentObject(). However, both methods should be avoided since they do not provide clean access to the page 's actual content. For instance, they may return a system message for non-existing pages. Use WikiPage::getContent() instead. Code that relies on a textual representation of the page content should eventually be rewritten. However, ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled by $wgContentHandlerTextFallback it
Definition: contenthandler.txt:104
normal
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of normal(non-web) applications -- they might conflict with distributors' policies
SFH_OBJECT_ARGS
const SFH_OBJECT_ARGS
Definition: Defines.php:198
OT_PREPROCESS
const OT_PREPROCESS
Definition: Defines.php:186
NS_FILE
const NS_FILE
Definition: Defines.php:70
OT_PLAIN
const OT_PLAIN
Definition: Defines.php:188
$params
$params
Definition: styleTest.css.php:44
wfHostname
wfHostname()
Fetch server name for use in error reporting etc.
Definition: GlobalFunctions.php:1392
NS_TEMPLATE
const NS_TEMPLATE
Definition: Defines.php:74
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:592
link
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template to be included in the link
Definition: hooks.txt:3098
$s
$s
Definition: mergeMessageFileList.php:187
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
a
</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre >< span ></span >< span class="kd"> var</span >< span class="nx"> a</span >< span class="p"></span ></pre ></div > ! end ! test Multiline< source/> in lists !input *< source > a b</source > *foo< source > a b</source > ! html< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! html tidy< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! end ! test Custom attributes !input< source lang="javascript" id="foo" class="bar" dir="rtl" style="font-size: larger;"> var a
Definition: parserTests.txt:89
$wgFragmentMode
$wgFragmentMode
How should section IDs be encoded? This array can contain 1 or 2 elements, each of them can be one of...
Definition: DefaultSettings.php:3452
page
target page
Definition: All_system_messages.txt:1267
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition: api.php:57
MWTidy\isEnabled
static isEnabled()
Definition: MWTidy.php:58
so
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is and will automatically terminate your rights under this License parties who have received or from you under this License will not have their licenses terminated so long as such parties remain in full compliance You are not required to accept this since you have not signed it nothing else grants you permission to modify or distribute the Program or its derivative works These actions are prohibited by law if you do not accept this License by modifying or distributing the you indicate your acceptance of this License to do so
Definition: COPYING.txt:185
StripState
Definition: StripState.php:28
Makefile.open
open
Definition: Makefile.py:18
is
This document provides an overview of the usage of PageUpdater and that is
Definition: pageupdater.txt:3
Linker\tocLine
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1531
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1082
$wgStylePath
$wgStylePath
The URL path of the skins directory.
Definition: DefaultSettings.php:200
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
PPFrame\NO_TEMPLATES
const NO_TEMPLATES
Definition: Preprocessor.php:168
specified
! hooks source ! endhooks ! test Non existent language !input< source lang="doesnotexist"> foobar</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > foobar</pre ></div > ! end ! test No language specified ! wikitext< source > foo</source > ! html< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > foo</pre ></div > ! end ! test No language specified(no wellformed xml) !! config !! wikitext< source > bar</source > !! html< div class
Preprocessor
Definition: Preprocessor.php:29
later
If you want to remove the page from your watchlist later
Definition: All_system_messages.txt:361
SiteStats\images
static images()
Definition: SiteStats.php:139
StringUtils\replaceMarkup
static replaceMarkup( $search, $replace, $text)
More or less "markup-safe" str_replace() Ignores any instances of the separator inside <....
Definition: StringUtils.php:294
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:133
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:53
$wgEnableScaryTranscluding
$wgEnableScaryTranscluding
Enable interwiki transcluding.
Definition: DefaultSettings.php:4407
$html
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:2044
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1118
table
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if so it s not worth the trouble Since there is a job queue in the jobs table
Definition: deferred.txt:11
BlockLevelPass\doBlockLevels
static doBlockLevels( $text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: BlockLevelPass.php:50
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2693
wfUrlProtocolsWithoutProtRel
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
Definition: GlobalFunctions.php:785
Linker\tocList
static tocList( $toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
Definition: Linker.php:1567
$matches
$matches
Definition: NoLocalSettings.php:24
in
null for the wiki Added in
Definition: hooks.txt:1627
CoreTagHooks\register
static register( $parser)
Definition: CoreTagHooks.php:33
mode
if write to the Free Software Franklin Fifth MA USA Also add information on how to contact you by electronic and paper mail If the program is make it output a short notice like this when it starts in an interactive mode
Definition: COPYING.txt:307
StringUtils\explode
static explode( $separator, $subject)
Workalike for explode() with limited memory usage.
Definition: StringUtils.php:336
PPNode
There are three types of nodes:
Definition: Preprocessor.php:360
LinkHolderArray
Definition: LinkHolderArray.php:29
PPFrame\RECOVER_ORIG
const RECOVER_ORIG
Definition: Preprocessor.php:174
$attribs
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:2044
Linker\makeHeadline
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1641
not
if not
Definition: COPYING.txt:307
Linker\tocLineEnd
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1555
MapCacheLRU
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:37
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
MWNamespace\hasSubpages
static hasSubpages( $index)
Does the namespace allow subpages?
Definition: MWNamespace.php:364
form
null means default in associative array form
Definition: hooks.txt:2044
$lines
$lines
Definition: router.php:61
$parser
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition: hooks.txt:1841
MWTimestamp\getInstance
static getInstance( $ts=false)
Get a timestamp instance in GMT.
Definition: MWTimestamp.php:39
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:826
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1841
OT_WIKI
const OT_WIKI
Definition: Defines.php:185
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:545
$output
$output
Definition: SyntaxHighlight.php:334
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
SectionProfiler
Custom PHP profiler for parser/DB type section names that xhprof/xdebug can't handle.
Definition: SectionProfiler.php:30
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1983
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
RequestContext
Group all the pieces relevant to the context of a request into one instance.
Definition: RequestContext.php:32
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:988
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
$sort
$sort
Definition: profileinfo.php:328
Linker\splitTrail
static splitTrail( $trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition: Linker.php:1664
or
or
Definition: COPYING.txt:140
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:121
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
key
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message key
Definition: hooks.txt:2213
MediaWiki\Special\SpecialPageFactory
Factory for handling the special page list and generating SpecialPage objects.
Definition: SpecialPageFactory.php:63
wfUrlProtocols
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
Definition: GlobalFunctions.php:740
SpecialVersion\getVersion
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
Definition: SpecialVersion.php:271
$line
$line
Definition: cdb.php:59
ParserFactory
Definition: ParserFactory.php:27
CoreParserFunctions\register
static register( $parser)
Definition: CoreParserFunctions.php:34
see
Some information about database access in MediaWiki By Tim January Database layout For information about the MediaWiki database such as a description of the tables and their please see
Definition: database.txt:2
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2221
$value
$value
Definition: styleTest.css.php:49
$wgNoFollowNsExceptions
$wgNoFollowNsExceptions
Namespaces in which $wgNoFollowLinks doesn't apply.
Definition: DefaultSettings.php:4363
$wgNoFollowLinks
$wgNoFollowLinks
If true, external URL links in wiki text will be given the rel="nofollow" attribute as a hint to sear...
Definition: DefaultSettings.php:4357
$wgServerName
$wgServerName
Server name.
Definition: DefaultSettings.php:121
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:52
$wgServer
$wgServer
URL of the server.
Definition: DefaultSettings.php:105
PPFrame
Definition: Preprocessor.php:166
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:2942
$wgSitename
$wgSitename
Name of the site.
Definition: DefaultSettings.php:79
on
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:77
StringUtils\delimiterExplode
static delimiterExplode( $startDelim, $endDelim, $separator, $subject, $nested=false)
Explode a string, but ignore any instances of the separator inside the given start and end delimiters...
Definition: StringUtils.php:56
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1617
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:2044
display
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this submitted means any form of or written communication sent to the Licensor or its including but not limited to communication on electronic mailing source code control and issue tracking systems that are managed or on behalf the Licensor for the purpose of discussing and improving the but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work Grant of Copyright License Subject to the terms and conditions of this each Contributor hereby grants to You a non no royalty irrevocable copyright license to prepare Derivative Works publicly display
Definition: APACHE-LICENSE-2.0.txt:49
Linker\makeImageLink
static makeImageLink(Parser $parser, Title $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
Definition: Linker.php:301
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:813
format
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1683
tag
</code > tag
Definition: citeParserTests.txt:225
plain
either a plain
Definition: hooks.txt:2105
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:2734
SFH_NO_HASH
const SFH_NO_HASH
Definition: Defines.php:197
$wgExtraInterlanguageLinkPrefixes
$wgExtraInterlanguageLinkPrefixes
List of additional interwiki prefixes that should be treated as interlanguage links (i....
Definition: DefaultSettings.php:2984
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:46
text
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Definition: All_system_messages.txt:1267
$args
if( $line===false) $args
Definition: cdb.php:64
OT_HTML
const OT_HTML
Definition: Defines.php:184
Title
Represents a title within MediaWiki.
Definition: Title.php:39
CoreParserFunctions\cascadingsources
static cascadingsources( $parser, $title='')
Returns the sources of any cascading protection acting on a specified page.
Definition: CoreParserFunctions.php:1341
and
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
like
For a write use something like
Definition: database.txt:26
wfMatchesDomainList
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
Definition: GlobalFunctions.php:954
$cache
$cache
Definition: mcc.php:33
MalformedTitleException
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Definition: MalformedTitleException.php:25
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2044
Xml\isWellFormedXmlFragment
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:732
used
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:115
history
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc next in line in page history
Definition: hooks.txt:1808
things
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
Definition: magicword.txt:4
$rev
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1808
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Linker\tocUnindent
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1516
public
we sometimes make exceptions for this Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF THERE IS NO WARRANTY FOR THE TO THE EXTENT PERMITTED BY APPLICABLE LAW EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND OR OTHER PARTIES PROVIDE THE PROGRAM AS IS WITHOUT WARRANTY OF ANY EITHER EXPRESSED OR BUT NOT LIMITED THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU SHOULD THE PROGRAM PROVE YOU ASSUME THE COST OF ALL NECESSARY REPAIR OR CORRECTION IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT OR ANY OTHER PARTY WHO MAY MODIFY AND OR REDISTRIBUTE THE PROGRAM AS PERMITTED BE LIABLE TO YOU FOR INCLUDING ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new and you want it to be of the greatest possible use to the public
Definition: COPYING.txt:284
Linker\makeMediaLinkFile
static makeMediaLinkFile(Title $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition: Linker.php:766
StringUtils\delimiterReplace
static delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags='')
Perform an operation equivalent to preg_replace() with flags.
Definition: StringUtils.php:245
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3098
revision
In both all secondary updates will be triggered handle like object that caches derived data representing a revision
Definition: pageupdater.txt:78
TextContent\normalizeLineEndings
static normalizeLineEndings( $text)
Do a "\\r\\n" -> "\\n" and "\\r" -> "\\n" transformation as well as trim trailing whitespace.
Definition: TextContent.php:167
$content
$content
Definition: pageupdater.txt:72
captcha-old.parser
parser
Definition: captcha-old.py:210
Linker\normalizeSubpageLink
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition: Linker.php:1355
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
$wgMaxTocLevel
$wgMaxTocLevel
Maximum indent level of toc.
Definition: DefaultSettings.php:4166
ImageGalleryBase\factory
static factory( $mode=false, IContextSource $context=null)
Get a new image gallery.
Definition: ImageGalleryBase.php:103
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
that
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if that
Definition: deferred.txt:11
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
$wgMaxSigChars
$wgMaxSigChars
Maximum number of Unicode characters in signature.
Definition: DefaultSettings.php:4827
$t
$t
Definition: testCompression.php:69
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:634
object
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:25
$services
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2278
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
captcha-old.output
output
Definition: captcha-old.py:240
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
RawMessage
Variant of the Message class.
Definition: RawMessage.php:34
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
wfIsHHVM
wfIsHHVM()
Check if we are running under HHVM.
Definition: GlobalFunctions.php:2006
$wgScriptPath
$wgScriptPath
The path we should point to.
Definition: DefaultSettings.php:137
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:47
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
type
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition: postgres.txt:22
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:813
MWTimestamp\getLocalInstance
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Definition: MWTimestamp.php:204
Linker\makeExternalImage
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition: Linker.php:248
SiteStats\edits
static edits()
Definition: SiteStats.php:94
Language
Internationalisation code.
Definition: Language.php:35
$buffer
$buffer
Definition: mwdoc-filter.php:49
MWHttpRequest\factory
static factory( $url, array $options=null, $caller=__METHOD__)
Generate a new request object Deprecated:
Definition: MWHttpRequest.php:184
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:813
$type
$type
Definition: testCompression.php:48
MWTidy\tidy
static tidy( $text)
Interface with html tidy.
Definition: MWTidy.php:46