MediaWiki  master
Parser.php
Go to the documentation of this file.
1 <?php
26 
70 class Parser {
76  const VERSION = '1.6.4';
77 
83 
84  # Flags for Parser::setFunctionHook
85  const SFH_NO_HASH = 1;
86  const SFH_OBJECT_ARGS = 2;
87 
88  # Constants needed for external link processing
89  # Everything except bracket, space, or control characters
90  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
91  # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
92  # \x{FFFD} is the Unicode replacement character, which Preprocessor_DOM
93  # uses to replace invalid HTML characters.
94  const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]';
95  # Simplified expression to match an IPv4 or IPv6 address, or
96  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
97  const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
98  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
99  // phpcs:ignore Generic.Files.LineLength
100  const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
101  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
102 
103  # Regular expression for a non-newline space
104  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
105 
106  # Flags for preprocessToDom
107  const PTD_FOR_INCLUSION = 1;
108 
109  # Allowed values for $this->mOutputType
110  # Parameter to startExternalParse().
111  const OT_HTML = 1; # like parse()
112  const OT_WIKI = 2; # like preSaveTransform()
114  const OT_MSG = 3;
115  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
116 
134  const MARKER_SUFFIX = "-QINU`\"'\x7f";
135  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
136 
137  # Markers used for wrapping the table of contents
138  const TOC_START = '<mw:toc>';
139  const TOC_END = '</mw:toc>';
140 
141  # Persistent:
142  public $mTagHooks = [];
144  public $mFunctionHooks = [];
145  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
146  public $mFunctionTagHooks = [];
147  public $mStripList = [];
148  public $mDefaultStripList = [];
149  public $mVarCache = [];
150  public $mImageParams = [];
152  public $mMarkerIndex = 0;
153  public $mFirstCall = true;
154 
155  # Initialised by initialiseVariables()
156 
160  public $mVariables;
161 
165  public $mSubstWords;
166  # Initialised in constructor
168 
169  # Initialized in getPreprocessor()
170 
172 
173  # Cleared with clearState():
174 
177  public $mOutput;
178  public $mAutonumber;
179 
183  public $mStripState;
184 
190 
191  public $mLinkID;
195  public $mExpensiveFunctionCount; # number of expensive parser function calls
197 
201  public $mUser; # User object; only used when doing pre-save transform
202 
203  # Temporary
204  # These are variables reset at least once per parse regardless of $clearState
205 
209  public $mOptions;
210 
214  public $mTitle; # Title context, used for self-link rendering and similar things
215  public $mOutputType; # Output type, one of the OT_xxx constants
216  public $ot; # Shortcut alias, see setOutputType()
217  public $mRevisionObject; # The revision object of the specified revision ID
218  public $mRevisionId; # ID to display in {{REVISIONID}} tags
219  public $mRevisionTimestamp; # The timestamp of the specified revision ID
220  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
221  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
222  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
223  public $mInputSize = false; # For {{PAGESIZE}} on current page.
224 
229  public $mUniqPrefix = self::MARKER_PREFIX;
230 
237 
245 
250  public $mInParse = false;
251 
253  protected $mProfiler;
254 
258  protected $mLinkRenderer;
259 
263  public function __construct( $conf = [] ) {
264  $this->mConf = $conf;
265  $this->mUrlProtocols = wfUrlProtocols();
266  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
267  self::EXT_LINK_ADDR .
268  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
269  if ( isset( $conf['preprocessorClass'] ) ) {
270  $this->mPreprocessorClass = $conf['preprocessorClass'];
271  } elseif ( defined( 'HPHP_VERSION' ) ) {
272  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
273  $this->mPreprocessorClass = Preprocessor_Hash::class;
274  } elseif ( extension_loaded( 'domxml' ) ) {
275  # PECL extension that conflicts with the core DOM extension (T15770)
276  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
277  $this->mPreprocessorClass = Preprocessor_Hash::class;
278  } elseif ( extension_loaded( 'dom' ) ) {
279  $this->mPreprocessorClass = Preprocessor_DOM::class;
280  } else {
281  $this->mPreprocessorClass = Preprocessor_Hash::class;
282  }
283  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
284  }
285 
289  public function __destruct() {
290  if ( isset( $this->mLinkHolders ) ) {
291  unset( $this->mLinkHolders );
292  }
293  foreach ( $this as $name => $value ) {
294  unset( $this->$name );
295  }
296  }
297 
301  public function __clone() {
302  $this->mInParse = false;
303 
304  // T58226: When you create a reference "to" an object field, that
305  // makes the object field itself be a reference too (until the other
306  // reference goes out of scope). When cloning, any field that's a
307  // reference is copied as a reference in the new object. Both of these
308  // are defined PHP5 behaviors, as inconvenient as it is for us when old
309  // hooks from PHP4 days are passing fields by reference.
310  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
311  // Make a non-reference copy of the field, then rebind the field to
312  // reference the new copy.
313  $tmp = $this->$k;
314  $this->$k =& $tmp;
315  unset( $tmp );
316  }
317 
318  Hooks::run( 'ParserCloned', [ $this ] );
319  }
320 
324  public function firstCallInit() {
325  if ( !$this->mFirstCall ) {
326  return;
327  }
328  $this->mFirstCall = false;
329 
331  CoreTagHooks::register( $this );
332  $this->initialiseVariables();
333 
334  // Avoid PHP 7.1 warning from passing $this by reference
335  $parser = $this;
336  Hooks::run( 'ParserFirstCallInit', [ &$parser ] );
337  }
338 
344  public function clearState() {
345  if ( $this->mFirstCall ) {
346  $this->firstCallInit();
347  }
348  $this->mOutput = new ParserOutput;
349  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
350  $this->mAutonumber = 0;
351  $this->mIncludeCount = [];
352  $this->mLinkHolders = new LinkHolderArray( $this );
353  $this->mLinkID = 0;
354  $this->mRevisionObject = $this->mRevisionTimestamp =
355  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
356  $this->mVarCache = [];
357  $this->mUser = null;
358  $this->mLangLinkLanguages = [];
359  $this->currentRevisionCache = null;
360 
361  $this->mStripState = new StripState;
362 
363  # Clear these on every parse, T6549
364  $this->mTplRedirCache = $this->mTplDomCache = [];
365 
366  $this->mShowToc = true;
367  $this->mForceTocPosition = false;
368  $this->mIncludeSizes = [
369  'post-expand' => 0,
370  'arg' => 0,
371  ];
372  $this->mPPNodeCount = 0;
373  $this->mGeneratedPPNodeCount = 0;
374  $this->mHighestExpansionDepth = 0;
375  $this->mDefaultSort = false;
376  $this->mHeadings = [];
377  $this->mDoubleUnderscores = [];
378  $this->mExpensiveFunctionCount = 0;
379 
380  # Fix cloning
381  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
382  $this->mPreprocessor = null;
383  }
384 
385  $this->mProfiler = new SectionProfiler();
386 
387  // Avoid PHP 7.1 warning from passing $this by reference
388  $parser = $this;
389  Hooks::run( 'ParserClearState', [ &$parser ] );
390  }
391 
404  public function parse(
406  $linestart = true, $clearState = true, $revid = null
407  ) {
408  if ( $clearState ) {
409  // We use U+007F DELETE to construct strip markers, so we have to make
410  // sure that this character does not occur in the input text.
411  $text = strtr( $text, "\x7f", "?" );
412  $magicScopeVariable = $this->lock();
413  }
414  // Strip U+0000 NULL (T159174)
415  $text = str_replace( "\000", '', $text );
416 
417  $this->startParse( $title, $options, self::OT_HTML, $clearState );
418 
419  $this->currentRevisionCache = null;
420  $this->mInputSize = strlen( $text );
421  if ( $this->mOptions->getEnableLimitReport() ) {
422  $this->mOutput->resetParseStartTime();
423  }
424 
425  $oldRevisionId = $this->mRevisionId;
426  $oldRevisionObject = $this->mRevisionObject;
427  $oldRevisionTimestamp = $this->mRevisionTimestamp;
428  $oldRevisionUser = $this->mRevisionUser;
429  $oldRevisionSize = $this->mRevisionSize;
430  if ( $revid !== null ) {
431  $this->mRevisionId = $revid;
432  $this->mRevisionObject = null;
433  $this->mRevisionTimestamp = null;
434  $this->mRevisionUser = null;
435  $this->mRevisionSize = null;
436  }
437 
438  // Avoid PHP 7.1 warning from passing $this by reference
439  $parser = $this;
440  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
441  # No more strip!
442  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
443  $text = $this->internalParse( $text );
444  Hooks::run( 'ParserAfterParse', [ &$parser, &$text, &$this->mStripState ] );
445 
446  $text = $this->internalParseHalfParsed( $text, true, $linestart );
447 
455  if ( !( $options->getDisableTitleConversion()
456  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
457  || isset( $this->mDoubleUnderscores['notitleconvert'] )
458  || $this->mOutput->getDisplayTitle() !== false )
459  ) {
460  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
461  if ( $convruletitle ) {
462  $this->mOutput->setTitleText( $convruletitle );
463  } else {
464  $titleText = $this->getConverterLanguage()->convertTitle( $title );
465  $this->mOutput->setTitleText( $titleText );
466  }
467  }
468 
469  # Compute runtime adaptive expiry if set
470  $this->mOutput->finalizeAdaptiveCacheExpiry();
471 
472  # Warn if too many heavyweight parser functions were used
473  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
474  $this->limitationWarn( 'expensive-parserfunction',
475  $this->mExpensiveFunctionCount,
476  $this->mOptions->getExpensiveParserFunctionLimit()
477  );
478  }
479 
480  # Information on limits, for the benefit of users who try to skirt them
481  if ( $this->mOptions->getEnableLimitReport() ) {
482  $text .= $this->makeLimitReport();
483  }
484 
485  # Wrap non-interface parser output in a <div> so it can be targeted
486  # with CSS (T37247)
487  $class = $this->mOptions->getWrapOutputClass();
488  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
489  $text = Html::rawElement( 'div', [ 'class' => $class ], $text );
490  }
491 
492  $this->mOutput->setText( $text );
493 
494  $this->mRevisionId = $oldRevisionId;
495  $this->mRevisionObject = $oldRevisionObject;
496  $this->mRevisionTimestamp = $oldRevisionTimestamp;
497  $this->mRevisionUser = $oldRevisionUser;
498  $this->mRevisionSize = $oldRevisionSize;
499  $this->mInputSize = false;
500  $this->currentRevisionCache = null;
501 
502  return $this->mOutput;
503  }
504 
511  protected function makeLimitReport() {
512  global $wgShowHostnames;
513 
514  $maxIncludeSize = $this->mOptions->getMaxIncludeSize();
515 
516  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
517  if ( $cpuTime !== null ) {
518  $this->mOutput->setLimitReportData( 'limitreport-cputime',
519  sprintf( "%.3f", $cpuTime )
520  );
521  }
522 
523  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
524  $this->mOutput->setLimitReportData( 'limitreport-walltime',
525  sprintf( "%.3f", $wallTime )
526  );
527 
528  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
529  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
530  );
531  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
532  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
533  );
534  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
535  [ $this->mIncludeSizes['post-expand'], $maxIncludeSize ]
536  );
537  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
538  [ $this->mIncludeSizes['arg'], $maxIncludeSize ]
539  );
540  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
541  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
542  );
543  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
544  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
545  );
546  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
547 
548  $limitReport = "NewPP limit report\n";
549  if ( $wgShowHostnames ) {
550  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
551  }
552  $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
553  $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
554  $limitReport .= 'Dynamic content: ' .
555  ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
556  "\n";
557 
558  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
559  if ( Hooks::run( 'ParserLimitReportFormat',
560  [ $key, &$value, &$limitReport, false, false ]
561  ) ) {
562  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
563  $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
564  ->inLanguage( 'en' )->useDatabase( false );
565  if ( !$valueMsg->exists() ) {
566  $valueMsg = new RawMessage( '$1' );
567  }
568  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
569  $valueMsg->params( $value );
570  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
571  }
572  }
573  }
574  // Since we're not really outputting HTML, decode the entities and
575  // then re-encode the things that need hiding inside HTML comments.
576  $limitReport = htmlspecialchars_decode( $limitReport );
577  // Run deprecated hook
578  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ], '1.22' );
579 
580  // Sanitize for comment. Note '‐' in the replacement is U+2010,
581  // which looks much like the problematic '-'.
582  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
583  $text = "\n<!-- \n$limitReport-->\n";
584 
585  // Add on template profiling data in human/machine readable way
586  $dataByFunc = $this->mProfiler->getFunctionStats();
587  uasort( $dataByFunc, function ( $a, $b ) {
588  return $a['real'] < $b['real']; // descending order
589  } );
590  $profileReport = [];
591  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
592  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
593  $item['%real'], $item['real'], $item['calls'],
594  htmlspecialchars( $item['name'] ) );
595  }
596  $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
597  $text .= implode( "\n", $profileReport ) . "\n-->\n";
598 
599  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
600 
601  // Add other cache related metadata
602  if ( $wgShowHostnames ) {
603  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
604  }
605  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
606  $this->mOutput->getCacheTime() );
607  $this->mOutput->setLimitReportData( 'cachereport-ttl',
608  $this->mOutput->getCacheExpiry() );
609  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
610  $this->mOutput->hasDynamicContent() );
611 
612  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
613  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
614  $this->mTitle->getPrefixedDBkey() );
615  }
616  return $text;
617  }
618 
641  public function recursiveTagParse( $text, $frame = false ) {
642  // Avoid PHP 7.1 warning from passing $this by reference
643  $parser = $this;
644  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
645  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
646  $text = $this->internalParse( $text, false, $frame );
647  return $text;
648  }
649 
667  public function recursiveTagParseFully( $text, $frame = false ) {
668  $text = $this->recursiveTagParse( $text, $frame );
669  $text = $this->internalParseHalfParsed( $text, false );
670  return $text;
671  }
672 
684  public function preprocess( $text, Title $title = null,
685  ParserOptions $options, $revid = null, $frame = false
686  ) {
687  $magicScopeVariable = $this->lock();
688  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
689  if ( $revid !== null ) {
690  $this->mRevisionId = $revid;
691  }
692  // Avoid PHP 7.1 warning from passing $this by reference
693  $parser = $this;
694  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
695  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
696  $text = $this->replaceVariables( $text, $frame );
697  $text = $this->mStripState->unstripBoth( $text );
698  return $text;
699  }
700 
710  public function recursivePreprocess( $text, $frame = false ) {
711  $text = $this->replaceVariables( $text, $frame );
712  $text = $this->mStripState->unstripBoth( $text );
713  return $text;
714  }
715 
729  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
730  $msg = new RawMessage( $text );
731  $text = $msg->params( $params )->plain();
732 
733  # Parser (re)initialisation
734  $magicScopeVariable = $this->lock();
735  $this->startParse( $title, $options, self::OT_PLAIN, true );
736 
738  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
739  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
740  $text = $this->mStripState->unstripBoth( $text );
741  return $text;
742  }
743 
750  public function setUser( $user ) {
751  $this->mUser = $user;
752  }
753 
759  public function setTitle( $t ) {
760  if ( !$t ) {
761  $t = Title::newFromText( 'NO TITLE' );
762  }
763 
764  if ( $t->hasFragment() ) {
765  # Strip the fragment to avoid various odd effects
766  $this->mTitle = $t->createFragmentTarget( '' );
767  } else {
768  $this->mTitle = $t;
769  }
770  }
771 
777  public function getTitle() {
778  return $this->mTitle;
779  }
780 
787  public function Title( $x = null ) {
788  return wfSetVar( $this->mTitle, $x );
789  }
790 
796  public function setOutputType( $ot ) {
797  $this->mOutputType = $ot;
798  # Shortcut alias
799  $this->ot = [
800  'html' => $ot == self::OT_HTML,
801  'wiki' => $ot == self::OT_WIKI,
802  'pre' => $ot == self::OT_PREPROCESS,
803  'plain' => $ot == self::OT_PLAIN,
804  ];
805  }
806 
813  public function OutputType( $x = null ) {
814  return wfSetVar( $this->mOutputType, $x );
815  }
816 
822  public function getOutput() {
823  return $this->mOutput;
824  }
825 
831  public function getOptions() {
832  return $this->mOptions;
833  }
834 
841  public function Options( $x = null ) {
842  return wfSetVar( $this->mOptions, $x );
843  }
844 
848  public function nextLinkID() {
849  return $this->mLinkID++;
850  }
851 
855  public function setLinkID( $id ) {
856  $this->mLinkID = $id;
857  }
858 
863  public function getFunctionLang() {
864  return $this->getTargetLanguage();
865  }
866 
876  public function getTargetLanguage() {
877  $target = $this->mOptions->getTargetLanguage();
878 
879  if ( $target !== null ) {
880  return $target;
881  } elseif ( $this->mOptions->getInterfaceMessage() ) {
882  return $this->mOptions->getUserLangObj();
883  } elseif ( is_null( $this->mTitle ) ) {
884  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
885  }
886 
887  return $this->mTitle->getPageLanguage();
888  }
889 
894  public function getConverterLanguage() {
895  return $this->getTargetLanguage();
896  }
897 
904  public function getUser() {
905  if ( !is_null( $this->mUser ) ) {
906  return $this->mUser;
907  }
908  return $this->mOptions->getUser();
909  }
910 
916  public function getPreprocessor() {
917  if ( !isset( $this->mPreprocessor ) ) {
918  $class = $this->mPreprocessorClass;
919  $this->mPreprocessor = new $class( $this );
920  }
921  return $this->mPreprocessor;
922  }
923 
930  public function getLinkRenderer() {
931  if ( !$this->mLinkRenderer ) {
932  $this->mLinkRenderer = MediaWikiServices::getInstance()
933  ->getLinkRendererFactory()->create();
934  $this->mLinkRenderer->setStubThreshold(
935  $this->getOptions()->getStubThreshold()
936  );
937  }
938 
939  return $this->mLinkRenderer;
940  }
941 
961  public static function extractTagsAndParams( $elements, $text, &$matches ) {
962  static $n = 1;
963  $stripped = '';
964  $matches = [];
965 
966  $taglist = implode( '|', $elements );
967  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
968 
969  while ( $text != '' ) {
970  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
971  $stripped .= $p[0];
972  if ( count( $p ) < 5 ) {
973  break;
974  }
975  if ( count( $p ) > 5 ) {
976  # comment
977  $element = $p[4];
978  $attributes = '';
979  $close = '';
980  $inside = $p[5];
981  } else {
982  # tag
983  $element = $p[1];
984  $attributes = $p[2];
985  $close = $p[3];
986  $inside = $p[4];
987  }
988 
989  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
990  $stripped .= $marker;
991 
992  if ( $close === '/>' ) {
993  # Empty element tag, <tag />
994  $content = null;
995  $text = $inside;
996  $tail = null;
997  } else {
998  if ( $element === '!--' ) {
999  $end = '/(-->)/';
1000  } else {
1001  $end = "/(<\\/$element\\s*>)/i";
1002  }
1003  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1004  $content = $q[0];
1005  if ( count( $q ) < 3 ) {
1006  # No end tag -- let it run out to the end of the text.
1007  $tail = '';
1008  $text = '';
1009  } else {
1010  $tail = $q[1];
1011  $text = $q[2];
1012  }
1013  }
1014 
1015  $matches[$marker] = [ $element,
1016  $content,
1017  Sanitizer::decodeTagAttributes( $attributes ),
1018  "<$element$attributes$close$content$tail" ];
1019  }
1020  return $stripped;
1021  }
1022 
1028  public function getStripList() {
1029  return $this->mStripList;
1030  }
1031 
1041  public function insertStripItem( $text ) {
1042  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1043  $this->mMarkerIndex++;
1044  $this->mStripState->addGeneral( $marker, $text );
1045  return $marker;
1046  }
1047 
1055  public function doTableStuff( $text ) {
1056  $lines = StringUtils::explode( "\n", $text );
1057  $out = '';
1058  $td_history = []; # Is currently a td tag open?
1059  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1060  $tr_history = []; # Is currently a tr tag open?
1061  $tr_attributes = []; # history of tr attributes
1062  $has_opened_tr = []; # Did this table open a <tr> element?
1063  $indent_level = 0; # indent level of the table
1064 
1065  foreach ( $lines as $outLine ) {
1066  $line = trim( $outLine );
1067 
1068  if ( $line === '' ) { # empty line, go to next line
1069  $out .= $outLine . "\n";
1070  continue;
1071  }
1072 
1073  $first_character = $line[0];
1074  $first_two = substr( $line, 0, 2 );
1075  $matches = [];
1076 
1077  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1078  # First check if we are starting a new table
1079  $indent_level = strlen( $matches[1] );
1080 
1081  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1082  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1083 
1084  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1085  array_push( $td_history, false );
1086  array_push( $last_tag_history, '' );
1087  array_push( $tr_history, false );
1088  array_push( $tr_attributes, '' );
1089  array_push( $has_opened_tr, false );
1090  } elseif ( count( $td_history ) == 0 ) {
1091  # Don't do any of the following
1092  $out .= $outLine . "\n";
1093  continue;
1094  } elseif ( $first_two === '|}' ) {
1095  # We are ending a table
1096  $line = '</table>' . substr( $line, 2 );
1097  $last_tag = array_pop( $last_tag_history );
1098 
1099  if ( !array_pop( $has_opened_tr ) ) {
1100  $line = "<tr><td></td></tr>{$line}";
1101  }
1102 
1103  if ( array_pop( $tr_history ) ) {
1104  $line = "</tr>{$line}";
1105  }
1106 
1107  if ( array_pop( $td_history ) ) {
1108  $line = "</{$last_tag}>{$line}";
1109  }
1110  array_pop( $tr_attributes );
1111  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1112  } elseif ( $first_two === '|-' ) {
1113  # Now we have a table row
1114  $line = preg_replace( '#^\|-+#', '', $line );
1115 
1116  # Whats after the tag is now only attributes
1117  $attributes = $this->mStripState->unstripBoth( $line );
1118  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1119  array_pop( $tr_attributes );
1120  array_push( $tr_attributes, $attributes );
1121 
1122  $line = '';
1123  $last_tag = array_pop( $last_tag_history );
1124  array_pop( $has_opened_tr );
1125  array_push( $has_opened_tr, true );
1126 
1127  if ( array_pop( $tr_history ) ) {
1128  $line = '</tr>';
1129  }
1130 
1131  if ( array_pop( $td_history ) ) {
1132  $line = "</{$last_tag}>{$line}";
1133  }
1134 
1135  $outLine = $line;
1136  array_push( $tr_history, false );
1137  array_push( $td_history, false );
1138  array_push( $last_tag_history, '' );
1139  } elseif ( $first_character === '|'
1140  || $first_character === '!'
1141  || $first_two === '|+'
1142  ) {
1143  # This might be cell elements, td, th or captions
1144  if ( $first_two === '|+' ) {
1145  $first_character = '+';
1146  $line = substr( $line, 2 );
1147  } else {
1148  $line = substr( $line, 1 );
1149  }
1150 
1151  // Implies both are valid for table headings.
1152  if ( $first_character === '!' ) {
1153  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1154  }
1155 
1156  # Split up multiple cells on the same line.
1157  # FIXME : This can result in improper nesting of tags processed
1158  # by earlier parser steps.
1159  $cells = explode( '||', $line );
1160 
1161  $outLine = '';
1162 
1163  # Loop through each table cell
1164  foreach ( $cells as $cell ) {
1165  $previous = '';
1166  if ( $first_character !== '+' ) {
1167  $tr_after = array_pop( $tr_attributes );
1168  if ( !array_pop( $tr_history ) ) {
1169  $previous = "<tr{$tr_after}>\n";
1170  }
1171  array_push( $tr_history, true );
1172  array_push( $tr_attributes, '' );
1173  array_pop( $has_opened_tr );
1174  array_push( $has_opened_tr, true );
1175  }
1176 
1177  $last_tag = array_pop( $last_tag_history );
1178 
1179  if ( array_pop( $td_history ) ) {
1180  $previous = "</{$last_tag}>\n{$previous}";
1181  }
1182 
1183  if ( $first_character === '|' ) {
1184  $last_tag = 'td';
1185  } elseif ( $first_character === '!' ) {
1186  $last_tag = 'th';
1187  } elseif ( $first_character === '+' ) {
1188  $last_tag = 'caption';
1189  } else {
1190  $last_tag = '';
1191  }
1192 
1193  array_push( $last_tag_history, $last_tag );
1194 
1195  # A cell could contain both parameters and data
1196  $cell_data = explode( '|', $cell, 2 );
1197 
1198  # T2553: Note that a '|' inside an invalid link should not
1199  # be mistaken as delimiting cell parameters
1200  # Bug T153140: Neither should language converter markup.
1201  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1202  $cell = "{$previous}<{$last_tag}>{$cell}";
1203  } elseif ( count( $cell_data ) == 1 ) {
1204  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1205  } else {
1206  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1207  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1208  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1209  }
1210 
1211  $outLine .= $cell;
1212  array_push( $td_history, true );
1213  }
1214  }
1215  $out .= $outLine . "\n";
1216  }
1217 
1218  # Closing open td, tr && table
1219  while ( count( $td_history ) > 0 ) {
1220  if ( array_pop( $td_history ) ) {
1221  $out .= "</td>\n";
1222  }
1223  if ( array_pop( $tr_history ) ) {
1224  $out .= "</tr>\n";
1225  }
1226  if ( !array_pop( $has_opened_tr ) ) {
1227  $out .= "<tr><td></td></tr>\n";
1228  }
1229 
1230  $out .= "</table>\n";
1231  }
1232 
1233  # Remove trailing line-ending (b/c)
1234  if ( substr( $out, -1 ) === "\n" ) {
1235  $out = substr( $out, 0, -1 );
1236  }
1237 
1238  # special case: don't return empty table
1239  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1240  $out = '';
1241  }
1242 
1243  return $out;
1244  }
1245 
1258  public function internalParse( $text, $isMain = true, $frame = false ) {
1259  $origText = $text;
1260 
1261  // Avoid PHP 7.1 warning from passing $this by reference
1262  $parser = $this;
1263 
1264  # Hook to suspend the parser in this state
1265  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1266  return $text;
1267  }
1268 
1269  # if $frame is provided, then use $frame for replacing any variables
1270  if ( $frame ) {
1271  # use frame depth to infer how include/noinclude tags should be handled
1272  # depth=0 means this is the top-level document; otherwise it's an included document
1273  if ( !$frame->depth ) {
1274  $flag = 0;
1275  } else {
1276  $flag = self::PTD_FOR_INCLUSION;
1277  }
1278  $dom = $this->preprocessToDom( $text, $flag );
1279  $text = $frame->expand( $dom );
1280  } else {
1281  # if $frame is not provided, then use old-style replaceVariables
1282  $text = $this->replaceVariables( $text );
1283  }
1284 
1285  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1286  $text = Sanitizer::removeHTMLtags(
1287  $text,
1288  [ $this, 'attributeStripCallback' ],
1289  false,
1290  array_keys( $this->mTransparentTagHooks ),
1291  [],
1292  [ $this, 'addTrackingCategory' ]
1293  );
1294  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1295 
1296  # Tables need to come after variable replacement for things to work
1297  # properly; putting them before other transformations should keep
1298  # exciting things like link expansions from showing up in surprising
1299  # places.
1300  $text = $this->doTableStuff( $text );
1301 
1302  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1303 
1304  $text = $this->doDoubleUnderscore( $text );
1305 
1306  $text = $this->doHeadings( $text );
1307  $text = $this->replaceInternalLinks( $text );
1308  $text = $this->doAllQuotes( $text );
1309  $text = $this->replaceExternalLinks( $text );
1310 
1311  # replaceInternalLinks may sometimes leave behind
1312  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1313  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1314 
1315  $text = $this->doMagicLinks( $text );
1316  $text = $this->formatHeadings( $text, $origText, $isMain );
1317 
1318  return $text;
1319  }
1320 
1330  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1331  $text = $this->mStripState->unstripGeneral( $text );
1332 
1333  // Avoid PHP 7.1 warning from passing $this by reference
1334  $parser = $this;
1335 
1336  if ( $isMain ) {
1337  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1338  }
1339 
1340  # Clean up special characters, only run once, next-to-last before doBlockLevels
1341  $fixtags = [
1342  # French spaces, last one Guillemet-left
1343  # only if there is something before the space
1344  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1345  # french spaces, Guillemet-right
1346  '/(\\302\\253) /' => '\\1&#160;',
1347  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1348  ];
1349  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1350 
1351  $text = $this->doBlockLevels( $text, $linestart );
1352 
1353  $this->replaceLinkHolders( $text );
1354 
1362  if ( !( $this->mOptions->getDisableContentConversion()
1363  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1364  ) {
1365  if ( !$this->mOptions->getInterfaceMessage() ) {
1366  # The position of the convert() call should not be changed. it
1367  # assumes that the links are all replaced and the only thing left
1368  # is the <nowiki> mark.
1369  $text = $this->getConverterLanguage()->convert( $text );
1370  }
1371  }
1372 
1373  $text = $this->mStripState->unstripNoWiki( $text );
1374 
1375  if ( $isMain ) {
1376  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1377  }
1378 
1379  $text = $this->replaceTransparentTags( $text );
1380  $text = $this->mStripState->unstripGeneral( $text );
1381 
1382  $text = Sanitizer::normalizeCharReferences( $text );
1383 
1384  if ( MWTidy::isEnabled() ) {
1385  if ( $this->mOptions->getTidy() ) {
1386  $text = MWTidy::tidy( $text );
1387  }
1388  } else {
1389  # attempt to sanitize at least some nesting problems
1390  # (T4702 and quite a few others)
1391  $tidyregs = [
1392  # ''Something [http://www.cool.com cool''] -->
1393  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1394  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1395  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1396  # fix up an anchor inside another anchor, only
1397  # at least for a single single nested link (T5695)
1398  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1399  '\\1\\2</a>\\3</a>\\1\\4</a>',
1400  # fix div inside inline elements- doBlockLevels won't wrap a line which
1401  # contains a div, so fix it up here; replace
1402  # div with escaped text
1403  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1404  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1405  # remove empty italic or bold tag pairs, some
1406  # introduced by rules above
1407  '/<([bi])><\/\\1>/' => '',
1408  ];
1409 
1410  $text = preg_replace(
1411  array_keys( $tidyregs ),
1412  array_values( $tidyregs ),
1413  $text );
1414  }
1415 
1416  if ( $isMain ) {
1417  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1418  }
1419 
1420  return $text;
1421  }
1422 
1434  public function doMagicLinks( $text ) {
1435  $prots = wfUrlProtocolsWithoutProtRel();
1436  $urlChar = self::EXT_LINK_URL_CLASS;
1437  $addr = self::EXT_LINK_ADDR;
1438  $space = self::SPACE_NOT_NL; # non-newline space
1439  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1440  $spaces = "$space++"; # possessive match of 1 or more spaces
1441  $text = preg_replace_callback(
1442  '!(?: # Start cases
1443  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1444  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1445  (\b # m[3]: Free external links
1446  (?i:$prots)
1447  ($addr$urlChar*) # m[4]: Post-protocol path
1448  ) |
1449  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1450  ([0-9]+)\b |
1451  \bISBN $spaces ( # m[6]: ISBN, capture number
1452  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1453  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1454  [0-9Xx] # check digit
1455  )\b
1456  )!xu", [ $this, 'magicLinkCallback' ], $text );
1457  return $text;
1458  }
1459 
1465  public function magicLinkCallback( $m ) {
1466  if ( isset( $m[1] ) && $m[1] !== '' ) {
1467  # Skip anchor
1468  return $m[0];
1469  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1470  # Skip HTML element
1471  return $m[0];
1472  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1473  # Free external link
1474  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1475  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1476  # RFC or PMID
1477  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1478  if ( !$this->mOptions->getMagicRFCLinks() ) {
1479  return $m[0];
1480  }
1481  $keyword = 'RFC';
1482  $urlmsg = 'rfcurl';
1483  $cssClass = 'mw-magiclink-rfc';
1484  $trackingCat = 'magiclink-tracking-rfc';
1485  $id = $m[5];
1486  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1487  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1488  return $m[0];
1489  }
1490  $keyword = 'PMID';
1491  $urlmsg = 'pubmedurl';
1492  $cssClass = 'mw-magiclink-pmid';
1493  $trackingCat = 'magiclink-tracking-pmid';
1494  $id = $m[5];
1495  } else {
1496  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1497  substr( $m[0], 0, 20 ) . '"' );
1498  }
1499  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1500  $this->addTrackingCategory( $trackingCat );
1501  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1502  } elseif ( isset( $m[6] ) && $m[6] !== ''
1503  && $this->mOptions->getMagicISBNLinks()
1504  ) {
1505  # ISBN
1506  $isbn = $m[6];
1507  $space = self::SPACE_NOT_NL; # non-newline space
1508  $isbn = preg_replace( "/$space/", ' ', $isbn );
1509  $num = strtr( $isbn, [
1510  '-' => '',
1511  ' ' => '',
1512  'x' => 'X',
1513  ] );
1514  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1515  return $this->getLinkRenderer()->makeKnownLink(
1516  SpecialPage::getTitleFor( 'Booksources', $num ),
1517  "ISBN $isbn",
1518  [
1519  'class' => 'internal mw-magiclink-isbn',
1520  'title' => false // suppress title attribute
1521  ]
1522  );
1523  } else {
1524  return $m[0];
1525  }
1526  }
1527 
1537  public function makeFreeExternalLink( $url, $numPostProto ) {
1538  $trail = '';
1539 
1540  # The characters '<' and '>' (which were escaped by
1541  # removeHTMLtags()) should not be included in
1542  # URLs, per RFC 2396.
1543  # Make &nbsp; terminate a URL as well (bug T84937)
1544  $m2 = [];
1545  if ( preg_match(
1546  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1547  $url,
1548  $m2,
1549  PREG_OFFSET_CAPTURE
1550  ) ) {
1551  $trail = substr( $url, $m2[0][1] ) . $trail;
1552  $url = substr( $url, 0, $m2[0][1] );
1553  }
1554 
1555  # Move trailing punctuation to $trail
1556  $sep = ',;\.:!?';
1557  # If there is no left bracket, then consider right brackets fair game too
1558  if ( strpos( $url, '(' ) === false ) {
1559  $sep .= ')';
1560  }
1561 
1562  $urlRev = strrev( $url );
1563  $numSepChars = strspn( $urlRev, $sep );
1564  # Don't break a trailing HTML entity by moving the ; into $trail
1565  # This is in hot code, so use substr_compare to avoid having to
1566  # create a new string object for the comparison
1567  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1568  # more optimization: instead of running preg_match with a $
1569  # anchor, which can be slow, do the match on the reversed
1570  # string starting at the desired offset.
1571  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1572  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1573  $numSepChars--;
1574  }
1575  }
1576  if ( $numSepChars ) {
1577  $trail = substr( $url, -$numSepChars ) . $trail;
1578  $url = substr( $url, 0, -$numSepChars );
1579  }
1580 
1581  # Verify that we still have a real URL after trail removal, and
1582  # not just lone protocol
1583  if ( strlen( $trail ) >= $numPostProto ) {
1584  return $url . $trail;
1585  }
1586 
1587  $url = Sanitizer::cleanUrl( $url );
1588 
1589  # Is this an external image?
1590  $text = $this->maybeMakeExternalImage( $url );
1591  if ( $text === false ) {
1592  # Not an image, make a link
1593  $text = Linker::makeExternalLink( $url,
1594  $this->getConverterLanguage()->markNoConversion( $url, true ),
1595  true, 'free',
1596  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1597  # Register it in the output object...
1598  $this->mOutput->addExternalLink( $url );
1599  }
1600  return $text . $trail;
1601  }
1602 
1612  public function doHeadings( $text ) {
1613  for ( $i = 6; $i >= 1; --$i ) {
1614  $h = str_repeat( '=', $i );
1615  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1616  }
1617  return $text;
1618  }
1619 
1628  public function doAllQuotes( $text ) {
1629  $outtext = '';
1630  $lines = StringUtils::explode( "\n", $text );
1631  foreach ( $lines as $line ) {
1632  $outtext .= $this->doQuotes( $line ) . "\n";
1633  }
1634  $outtext = substr( $outtext, 0, -1 );
1635  return $outtext;
1636  }
1637 
1645  public function doQuotes( $text ) {
1646  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1647  $countarr = count( $arr );
1648  if ( $countarr == 1 ) {
1649  return $text;
1650  }
1651 
1652  // First, do some preliminary work. This may shift some apostrophes from
1653  // being mark-up to being text. It also counts the number of occurrences
1654  // of bold and italics mark-ups.
1655  $numbold = 0;
1656  $numitalics = 0;
1657  for ( $i = 1; $i < $countarr; $i += 2 ) {
1658  $thislen = strlen( $arr[$i] );
1659  // If there are ever four apostrophes, assume the first is supposed to
1660  // be text, and the remaining three constitute mark-up for bold text.
1661  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1662  if ( $thislen == 4 ) {
1663  $arr[$i - 1] .= "'";
1664  $arr[$i] = "'''";
1665  $thislen = 3;
1666  } elseif ( $thislen > 5 ) {
1667  // If there are more than 5 apostrophes in a row, assume they're all
1668  // text except for the last 5.
1669  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1670  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1671  $arr[$i] = "'''''";
1672  $thislen = 5;
1673  }
1674  // Count the number of occurrences of bold and italics mark-ups.
1675  if ( $thislen == 2 ) {
1676  $numitalics++;
1677  } elseif ( $thislen == 3 ) {
1678  $numbold++;
1679  } elseif ( $thislen == 5 ) {
1680  $numitalics++;
1681  $numbold++;
1682  }
1683  }
1684 
1685  // If there is an odd number of both bold and italics, it is likely
1686  // that one of the bold ones was meant to be an apostrophe followed
1687  // by italics. Which one we cannot know for certain, but it is more
1688  // likely to be one that has a single-letter word before it.
1689  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1690  $firstsingleletterword = -1;
1691  $firstmultiletterword = -1;
1692  $firstspace = -1;
1693  for ( $i = 1; $i < $countarr; $i += 2 ) {
1694  if ( strlen( $arr[$i] ) == 3 ) {
1695  $x1 = substr( $arr[$i - 1], -1 );
1696  $x2 = substr( $arr[$i - 1], -2, 1 );
1697  if ( $x1 === ' ' ) {
1698  if ( $firstspace == -1 ) {
1699  $firstspace = $i;
1700  }
1701  } elseif ( $x2 === ' ' ) {
1702  $firstsingleletterword = $i;
1703  // if $firstsingleletterword is set, we don't
1704  // look at the other options, so we can bail early.
1705  break;
1706  } else {
1707  if ( $firstmultiletterword == -1 ) {
1708  $firstmultiletterword = $i;
1709  }
1710  }
1711  }
1712  }
1713 
1714  // If there is a single-letter word, use it!
1715  if ( $firstsingleletterword > -1 ) {
1716  $arr[$firstsingleletterword] = "''";
1717  $arr[$firstsingleletterword - 1] .= "'";
1718  } elseif ( $firstmultiletterword > -1 ) {
1719  // If not, but there's a multi-letter word, use that one.
1720  $arr[$firstmultiletterword] = "''";
1721  $arr[$firstmultiletterword - 1] .= "'";
1722  } elseif ( $firstspace > -1 ) {
1723  // ... otherwise use the first one that has neither.
1724  // (notice that it is possible for all three to be -1 if, for example,
1725  // there is only one pentuple-apostrophe in the line)
1726  $arr[$firstspace] = "''";
1727  $arr[$firstspace - 1] .= "'";
1728  }
1729  }
1730 
1731  // Now let's actually convert our apostrophic mush to HTML!
1732  $output = '';
1733  $buffer = '';
1734  $state = '';
1735  $i = 0;
1736  foreach ( $arr as $r ) {
1737  if ( ( $i % 2 ) == 0 ) {
1738  if ( $state === 'both' ) {
1739  $buffer .= $r;
1740  } else {
1741  $output .= $r;
1742  }
1743  } else {
1744  $thislen = strlen( $r );
1745  if ( $thislen == 2 ) {
1746  if ( $state === 'i' ) {
1747  $output .= '</i>';
1748  $state = '';
1749  } elseif ( $state === 'bi' ) {
1750  $output .= '</i>';
1751  $state = 'b';
1752  } elseif ( $state === 'ib' ) {
1753  $output .= '</b></i><b>';
1754  $state = 'b';
1755  } elseif ( $state === 'both' ) {
1756  $output .= '<b><i>' . $buffer . '</i>';
1757  $state = 'b';
1758  } else { // $state can be 'b' or ''
1759  $output .= '<i>';
1760  $state .= 'i';
1761  }
1762  } elseif ( $thislen == 3 ) {
1763  if ( $state === 'b' ) {
1764  $output .= '</b>';
1765  $state = '';
1766  } elseif ( $state === 'bi' ) {
1767  $output .= '</i></b><i>';
1768  $state = 'i';
1769  } elseif ( $state === 'ib' ) {
1770  $output .= '</b>';
1771  $state = 'i';
1772  } elseif ( $state === 'both' ) {
1773  $output .= '<i><b>' . $buffer . '</b>';
1774  $state = 'i';
1775  } else { // $state can be 'i' or ''
1776  $output .= '<b>';
1777  $state .= 'b';
1778  }
1779  } elseif ( $thislen == 5 ) {
1780  if ( $state === 'b' ) {
1781  $output .= '</b><i>';
1782  $state = 'i';
1783  } elseif ( $state === 'i' ) {
1784  $output .= '</i><b>';
1785  $state = 'b';
1786  } elseif ( $state === 'bi' ) {
1787  $output .= '</i></b>';
1788  $state = '';
1789  } elseif ( $state === 'ib' ) {
1790  $output .= '</b></i>';
1791  $state = '';
1792  } elseif ( $state === 'both' ) {
1793  $output .= '<i><b>' . $buffer . '</b></i>';
1794  $state = '';
1795  } else { // ($state == '')
1796  $buffer = '';
1797  $state = 'both';
1798  }
1799  }
1800  }
1801  $i++;
1802  }
1803  // Now close all remaining tags. Notice that the order is important.
1804  if ( $state === 'b' || $state === 'ib' ) {
1805  $output .= '</b>';
1806  }
1807  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1808  $output .= '</i>';
1809  }
1810  if ( $state === 'bi' ) {
1811  $output .= '</b>';
1812  }
1813  // There might be lonely ''''', so make sure we have a buffer
1814  if ( $state === 'both' && $buffer ) {
1815  $output .= '<b><i>' . $buffer . '</i></b>';
1816  }
1817  return $output;
1818  }
1819 
1833  public function replaceExternalLinks( $text ) {
1834  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1835  if ( $bits === false ) {
1836  throw new MWException( "PCRE needs to be compiled with "
1837  . "--enable-unicode-properties in order for MediaWiki to function" );
1838  }
1839  $s = array_shift( $bits );
1840 
1841  $i = 0;
1842  while ( $i < count( $bits ) ) {
1843  $url = $bits[$i++];
1844  $i++; // protocol
1845  $text = $bits[$i++];
1846  $trail = $bits[$i++];
1847 
1848  # The characters '<' and '>' (which were escaped by
1849  # removeHTMLtags()) should not be included in
1850  # URLs, per RFC 2396.
1851  $m2 = [];
1852  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1853  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1854  $url = substr( $url, 0, $m2[0][1] );
1855  }
1856 
1857  # If the link text is an image URL, replace it with an <img> tag
1858  # This happened by accident in the original parser, but some people used it extensively
1859  $img = $this->maybeMakeExternalImage( $text );
1860  if ( $img !== false ) {
1861  $text = $img;
1862  }
1863 
1864  $dtrail = '';
1865 
1866  # Set linktype for CSS - if URL==text, link is essentially free
1867  $linktype = ( $text === $url ) ? 'free' : 'text';
1868 
1869  # No link text, e.g. [http://domain.tld/some.link]
1870  if ( $text == '' ) {
1871  # Autonumber
1872  $langObj = $this->getTargetLanguage();
1873  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1874  $linktype = 'autonumber';
1875  } else {
1876  # Have link text, e.g. [http://domain.tld/some.link text]s
1877  # Check for trail
1878  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1879  }
1880 
1881  $text = $this->getConverterLanguage()->markNoConversion( $text );
1882 
1883  $url = Sanitizer::cleanUrl( $url );
1884 
1885  # Use the encoded URL
1886  # This means that users can paste URLs directly into the text
1887  # Funny characters like ö aren't valid in URLs anyway
1888  # This was changed in August 2004
1889  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1890  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1891 
1892  # Register link in the output object.
1893  $this->mOutput->addExternalLink( $url );
1894  }
1895 
1896  return $s;
1897  }
1898 
1908  public static function getExternalLinkRel( $url = false, $title = null ) {
1909  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1910  $ns = $title ? $title->getNamespace() : false;
1911  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1912  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1913  ) {
1914  return 'nofollow';
1915  }
1916  return null;
1917  }
1918 
1929  public function getExternalLinkAttribs( $url ) {
1930  $attribs = [];
1931  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1932 
1933  $target = $this->mOptions->getExternalLinkTarget();
1934  if ( $target ) {
1935  $attribs['target'] = $target;
1936  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1937  // T133507. New windows can navigate parent cross-origin.
1938  // Including noreferrer due to lacking browser
1939  // support of noopener. Eventually noreferrer should be removed.
1940  if ( $rel !== '' ) {
1941  $rel .= ' ';
1942  }
1943  $rel .= 'noreferrer noopener';
1944  }
1945  }
1946  $attribs['rel'] = $rel;
1947  return $attribs;
1948  }
1949 
1959  public static function normalizeLinkUrl( $url ) {
1960  # First, make sure unsafe characters are encoded
1961  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1962  function ( $m ) {
1963  return rawurlencode( $m[0] );
1964  },
1965  $url
1966  );
1967 
1968  $ret = '';
1969  $end = strlen( $url );
1970 
1971  # Fragment part - 'fragment'
1972  $start = strpos( $url, '#' );
1973  if ( $start !== false && $start < $end ) {
1974  $ret = self::normalizeUrlComponent(
1975  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1976  $end = $start;
1977  }
1978 
1979  # Query part - 'query' minus &=+;
1980  $start = strpos( $url, '?' );
1981  if ( $start !== false && $start < $end ) {
1982  $ret = self::normalizeUrlComponent(
1983  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
1984  $end = $start;
1985  }
1986 
1987  # Scheme and path part - 'pchar'
1988  # (we assume no userinfo or encoded colons in the host)
1989  $ret = self::normalizeUrlComponent(
1990  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
1991 
1992  return $ret;
1993  }
1994 
1995  private static function normalizeUrlComponent( $component, $unsafe ) {
1996  $callback = function ( $matches ) use ( $unsafe ) {
1997  $char = urldecode( $matches[0] );
1998  $ord = ord( $char );
1999  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2000  # Unescape it
2001  return $char;
2002  } else {
2003  # Leave it escaped, but use uppercase for a-f
2004  return strtoupper( $matches[0] );
2005  }
2006  };
2007  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2008  }
2009 
2018  private function maybeMakeExternalImage( $url ) {
2019  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2020  $imagesexception = !empty( $imagesfrom );
2021  $text = false;
2022  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2023  if ( $imagesexception && is_array( $imagesfrom ) ) {
2024  $imagematch = false;
2025  foreach ( $imagesfrom as $match ) {
2026  if ( strpos( $url, $match ) === 0 ) {
2027  $imagematch = true;
2028  break;
2029  }
2030  }
2031  } elseif ( $imagesexception ) {
2032  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2033  } else {
2034  $imagematch = false;
2035  }
2036 
2037  if ( $this->mOptions->getAllowExternalImages()
2038  || ( $imagesexception && $imagematch )
2039  ) {
2040  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2041  # Image found
2042  $text = Linker::makeExternalImage( $url );
2043  }
2044  }
2045  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2046  && preg_match( self::EXT_IMAGE_REGEX, $url )
2047  ) {
2048  $whitelist = explode(
2049  "\n",
2050  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2051  );
2052 
2053  foreach ( $whitelist as $entry ) {
2054  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2055  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2056  continue;
2057  }
2058  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2059  # Image matches a whitelist entry
2060  $text = Linker::makeExternalImage( $url );
2061  break;
2062  }
2063  }
2064  }
2065  return $text;
2066  }
2067 
2077  public function replaceInternalLinks( $s ) {
2078  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2079  return $s;
2080  }
2081 
2090  public function replaceInternalLinks2( &$s ) {
2092 
2093  static $tc = false, $e1, $e1_img;
2094  # the % is needed to support urlencoded titles as well
2095  if ( !$tc ) {
2096  $tc = Title::legalChars() . '#%';
2097  # Match a link having the form [[namespace:link|alternate]]trail
2098  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2099  # Match cases where there is no "]]", which might still be images
2100  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2101  }
2102 
2103  $holders = new LinkHolderArray( $this );
2104 
2105  # split the entire text string on occurrences of [[
2106  $a = StringUtils::explode( '[[', ' ' . $s );
2107  # get the first element (all text up to first [[), and remove the space we added
2108  $s = $a->current();
2109  $a->next();
2110  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2111  $s = substr( $s, 1 );
2112 
2113  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2114  $e2 = null;
2115  if ( $useLinkPrefixExtension ) {
2116  # Match the end of a line for a word that's not followed by whitespace,
2117  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2119  $charset = $wgContLang->linkPrefixCharset();
2120  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2121  }
2122 
2123  if ( is_null( $this->mTitle ) ) {
2124  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2125  }
2126  $nottalk = !$this->mTitle->isTalkPage();
2127 
2128  if ( $useLinkPrefixExtension ) {
2129  $m = [];
2130  if ( preg_match( $e2, $s, $m ) ) {
2131  $first_prefix = $m[2];
2132  } else {
2133  $first_prefix = false;
2134  }
2135  } else {
2136  $prefix = '';
2137  }
2138 
2139  $useSubpages = $this->areSubpagesAllowed();
2140 
2141  # Loop for each link
2142  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2143  # Check for excessive memory usage
2144  if ( $holders->isBig() ) {
2145  # Too big
2146  # Do the existence check, replace the link holders and clear the array
2147  $holders->replace( $s );
2148  $holders->clear();
2149  }
2150 
2151  if ( $useLinkPrefixExtension ) {
2152  if ( preg_match( $e2, $s, $m ) ) {
2153  $prefix = $m[2];
2154  $s = $m[1];
2155  } else {
2156  $prefix = '';
2157  }
2158  # first link
2159  if ( $first_prefix ) {
2160  $prefix = $first_prefix;
2161  $first_prefix = false;
2162  }
2163  }
2164 
2165  $might_be_img = false;
2166 
2167  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2168  $text = $m[2];
2169  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2170  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2171  # the real problem is with the $e1 regex
2172  # See T1500.
2173  # Still some problems for cases where the ] is meant to be outside punctuation,
2174  # and no image is in sight. See T4095.
2175  if ( $text !== ''
2176  && substr( $m[3], 0, 1 ) === ']'
2177  && strpos( $text, '[' ) !== false
2178  ) {
2179  $text .= ']'; # so that replaceExternalLinks($text) works later
2180  $m[3] = substr( $m[3], 1 );
2181  }
2182  # fix up urlencoded title texts
2183  if ( strpos( $m[1], '%' ) !== false ) {
2184  # Should anchors '#' also be rejected?
2185  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2186  }
2187  $trail = $m[3];
2188  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2189  # Invalid, but might be an image with a link in its caption
2190  $might_be_img = true;
2191  $text = $m[2];
2192  if ( strpos( $m[1], '%' ) !== false ) {
2193  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2194  }
2195  $trail = "";
2196  } else { # Invalid form; output directly
2197  $s .= $prefix . '[[' . $line;
2198  continue;
2199  }
2200 
2201  $origLink = ltrim( $m[1], ' ' );
2202 
2203  # Don't allow internal links to pages containing
2204  # PROTO: where PROTO is a valid URL protocol; these
2205  # should be external links.
2206  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2207  $s .= $prefix . '[[' . $line;
2208  continue;
2209  }
2210 
2211  # Make subpage if necessary
2212  if ( $useSubpages ) {
2213  $link = $this->maybeDoSubpageLink( $origLink, $text );
2214  } else {
2215  $link = $origLink;
2216  }
2217 
2218  $unstrip = $this->mStripState->unstripNoWiki( $link );
2219  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2220  if ( $nt === null ) {
2221  $s .= $prefix . '[[' . $line;
2222  continue;
2223  }
2224 
2225  $ns = $nt->getNamespace();
2226  $iw = $nt->getInterwiki();
2227 
2228  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2229 
2230  if ( $might_be_img ) { # if this is actually an invalid link
2231  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2232  $found = false;
2233  while ( true ) {
2234  # look at the next 'line' to see if we can close it there
2235  $a->next();
2236  $next_line = $a->current();
2237  if ( $next_line === false || $next_line === null ) {
2238  break;
2239  }
2240  $m = explode( ']]', $next_line, 3 );
2241  if ( count( $m ) == 3 ) {
2242  # the first ]] closes the inner link, the second the image
2243  $found = true;
2244  $text .= "[[{$m[0]}]]{$m[1]}";
2245  $trail = $m[2];
2246  break;
2247  } elseif ( count( $m ) == 2 ) {
2248  # if there's exactly one ]] that's fine, we'll keep looking
2249  $text .= "[[{$m[0]}]]{$m[1]}";
2250  } else {
2251  # if $next_line is invalid too, we need look no further
2252  $text .= '[[' . $next_line;
2253  break;
2254  }
2255  }
2256  if ( !$found ) {
2257  # we couldn't find the end of this imageLink, so output it raw
2258  # but don't ignore what might be perfectly normal links in the text we've examined
2259  $holders->merge( $this->replaceInternalLinks2( $text ) );
2260  $s .= "{$prefix}[[$link|$text";
2261  # note: no $trail, because without an end, there *is* no trail
2262  continue;
2263  }
2264  } else { # it's not an image, so output it raw
2265  $s .= "{$prefix}[[$link|$text";
2266  # note: no $trail, because without an end, there *is* no trail
2267  continue;
2268  }
2269  }
2270 
2271  $wasblank = ( $text == '' );
2272  if ( $wasblank ) {
2273  $text = $link;
2274  if ( !$noforce ) {
2275  # Strip off leading ':'
2276  $text = substr( $text, 1 );
2277  }
2278  } else {
2279  # T6598 madness. Handle the quotes only if they come from the alternate part
2280  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2281  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2282  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2283  $text = $this->doQuotes( $text );
2284  }
2285 
2286  # Link not escaped by : , create the various objects
2287  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2288  # Interwikis
2289  if (
2290  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2291  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2292  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2293  )
2294  ) {
2295  # T26502: filter duplicates
2296  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2297  $this->mLangLinkLanguages[$iw] = true;
2298  $this->mOutput->addLanguageLink( $nt->getFullText() );
2299  }
2300 
2304  $s = rtrim( $s . $prefix ) . $trail; # T175416
2305  continue;
2306  }
2307 
2308  if ( $ns == NS_FILE ) {
2309  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2310  if ( $wasblank ) {
2311  # if no parameters were passed, $text
2312  # becomes something like "File:Foo.png",
2313  # which we don't want to pass on to the
2314  # image generator
2315  $text = '';
2316  } else {
2317  # recursively parse links inside the image caption
2318  # actually, this will parse them in any other parameters, too,
2319  # but it might be hard to fix that, and it doesn't matter ATM
2320  $text = $this->replaceExternalLinks( $text );
2321  $holders->merge( $this->replaceInternalLinks2( $text ) );
2322  }
2323  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2324  $s .= $prefix . $this->armorLinks(
2325  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2326  continue;
2327  }
2328  } elseif ( $ns == NS_CATEGORY ) {
2332  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2333 
2334  if ( $wasblank ) {
2335  $sortkey = $this->getDefaultSort();
2336  } else {
2337  $sortkey = $text;
2338  }
2339  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2340  $sortkey = str_replace( "\n", '', $sortkey );
2341  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2342  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2343 
2344  continue;
2345  }
2346  }
2347 
2348  # Self-link checking. For some languages, variants of the title are checked in
2349  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2350  # for linking to a different variant.
2351  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2352  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2353  continue;
2354  }
2355 
2356  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2357  # @todo FIXME: Should do batch file existence checks, see comment below
2358  if ( $ns == NS_MEDIA ) {
2359  # Give extensions a chance to select the file revision for us
2360  $options = [];
2361  $descQuery = false;
2362  Hooks::run( 'BeforeParserFetchFileAndTitle',
2363  [ $this, $nt, &$options, &$descQuery ] );
2364  # Fetch and register the file (file title may be different via hooks)
2365  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2366  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2367  $s .= $prefix . $this->armorLinks(
2368  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2369  continue;
2370  }
2371 
2372  # Some titles, such as valid special pages or files in foreign repos, should
2373  # be shown as bluelinks even though they're not included in the page table
2374  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2375  # batch file existence checks for NS_FILE and NS_MEDIA
2376  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2377  $this->mOutput->addLink( $nt );
2378  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2379  } else {
2380  # Links will be added to the output link list after checking
2381  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2382  }
2383  }
2384  return $holders;
2385  }
2386 
2400  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2401  list( $inside, $trail ) = Linker::splitTrail( $trail );
2402 
2403  if ( $text == '' ) {
2404  $text = htmlspecialchars( $nt->getPrefixedText() );
2405  }
2406 
2407  $link = $this->getLinkRenderer()->makeKnownLink(
2408  $nt, new HtmlArmor( "$prefix$text$inside" )
2409  );
2410 
2411  return $this->armorLinks( $link ) . $trail;
2412  }
2413 
2424  public function armorLinks( $text ) {
2425  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2426  self::MARKER_PREFIX . "NOPARSE$1", $text );
2427  }
2428 
2433  public function areSubpagesAllowed() {
2434  # Some namespaces don't allow subpages
2435  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2436  }
2437 
2446  public function maybeDoSubpageLink( $target, &$text ) {
2447  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2448  }
2449 
2458  public function doBlockLevels( $text, $linestart ) {
2459  return BlockLevelPass::doBlockLevels( $text, $linestart );
2460  }
2461 
2473  public function getVariableValue( $index, $frame = false ) {
2476 
2477  if ( is_null( $this->mTitle ) ) {
2478  // If no title set, bad things are going to happen
2479  // later. Title should always be set since this
2480  // should only be called in the middle of a parse
2481  // operation (but the unit-tests do funky stuff)
2482  throw new MWException( __METHOD__ . ' Should only be '
2483  . ' called while parsing (no title set)' );
2484  }
2485 
2486  // Avoid PHP 7.1 warning from passing $this by reference
2487  $parser = $this;
2488 
2493  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2494  if ( isset( $this->mVarCache[$index] ) ) {
2495  return $this->mVarCache[$index];
2496  }
2497  }
2498 
2499  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2500  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2501 
2502  $pageLang = $this->getFunctionLang();
2503 
2504  switch ( $index ) {
2505  case '!':
2506  $value = '|';
2507  break;
2508  case 'currentmonth':
2509  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ), true );
2510  break;
2511  case 'currentmonth1':
2512  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ), true );
2513  break;
2514  case 'currentmonthname':
2515  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2516  break;
2517  case 'currentmonthnamegen':
2518  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2519  break;
2520  case 'currentmonthabbrev':
2521  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2522  break;
2523  case 'currentday':
2524  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ), true );
2525  break;
2526  case 'currentday2':
2527  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ), true );
2528  break;
2529  case 'localmonth':
2530  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ), true );
2531  break;
2532  case 'localmonth1':
2533  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ), true );
2534  break;
2535  case 'localmonthname':
2536  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2537  break;
2538  case 'localmonthnamegen':
2539  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2540  break;
2541  case 'localmonthabbrev':
2542  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2543  break;
2544  case 'localday':
2545  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ), true );
2546  break;
2547  case 'localday2':
2548  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ), true );
2549  break;
2550  case 'pagename':
2551  $value = wfEscapeWikiText( $this->mTitle->getText() );
2552  break;
2553  case 'pagenamee':
2554  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2555  break;
2556  case 'fullpagename':
2557  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2558  break;
2559  case 'fullpagenamee':
2560  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2561  break;
2562  case 'subpagename':
2563  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2564  break;
2565  case 'subpagenamee':
2566  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2567  break;
2568  case 'rootpagename':
2569  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2570  break;
2571  case 'rootpagenamee':
2572  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2573  ' ',
2574  '_',
2575  $this->mTitle->getRootText()
2576  ) ) );
2577  break;
2578  case 'basepagename':
2579  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2580  break;
2581  case 'basepagenamee':
2582  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2583  ' ',
2584  '_',
2585  $this->mTitle->getBaseText()
2586  ) ) );
2587  break;
2588  case 'talkpagename':
2589  if ( $this->mTitle->canHaveTalkPage() ) {
2590  $talkPage = $this->mTitle->getTalkPage();
2591  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2592  } else {
2593  $value = '';
2594  }
2595  break;
2596  case 'talkpagenamee':
2597  if ( $this->mTitle->canHaveTalkPage() ) {
2598  $talkPage = $this->mTitle->getTalkPage();
2599  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2600  } else {
2601  $value = '';
2602  }
2603  break;
2604  case 'subjectpagename':
2605  $subjPage = $this->mTitle->getSubjectPage();
2606  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2607  break;
2608  case 'subjectpagenamee':
2609  $subjPage = $this->mTitle->getSubjectPage();
2610  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2611  break;
2612  case 'pageid': // requested in T25427
2613  $pageid = $this->getTitle()->getArticleID();
2614  if ( $pageid == 0 ) {
2615  # 0 means the page doesn't exist in the database,
2616  # which means the user is previewing a new page.
2617  # The vary-revision flag must be set, because the magic word
2618  # will have a different value once the page is saved.
2619  $this->mOutput->setFlag( 'vary-revision' );
2620  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2621  }
2622  $value = $pageid ? $pageid : null;
2623  break;
2624  case 'revisionid':
2625  # Let the edit saving system know we should parse the page
2626  # *after* a revision ID has been assigned.
2627  $this->mOutput->setFlag( 'vary-revision-id' );
2628  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2629  $value = $this->mRevisionId;
2630  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2631  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2632  $this->mOutput->setSpeculativeRevIdUsed( $value );
2633  }
2634  break;
2635  case 'revisionday':
2636  # Let the edit saving system know we should parse the page
2637  # *after* a revision ID has been assigned. This is for null edits.
2638  $this->mOutput->setFlag( 'vary-revision' );
2639  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2640  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2641  break;
2642  case 'revisionday2':
2643  # Let the edit saving system know we should parse the page
2644  # *after* a revision ID has been assigned. This is for null edits.
2645  $this->mOutput->setFlag( 'vary-revision' );
2646  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2647  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2648  break;
2649  case 'revisionmonth':
2650  # Let the edit saving system know we should parse the page
2651  # *after* a revision ID has been assigned. This is for null edits.
2652  $this->mOutput->setFlag( 'vary-revision' );
2653  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2654  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2655  break;
2656  case 'revisionmonth1':
2657  # Let the edit saving system know we should parse the page
2658  # *after* a revision ID has been assigned. This is for null edits.
2659  $this->mOutput->setFlag( 'vary-revision' );
2660  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2661  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2662  break;
2663  case 'revisionyear':
2664  # Let the edit saving system know we should parse the page
2665  # *after* a revision ID has been assigned. This is for null edits.
2666  $this->mOutput->setFlag( 'vary-revision' );
2667  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2668  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2669  break;
2670  case 'revisiontimestamp':
2671  # Let the edit saving system know we should parse the page
2672  # *after* a revision ID has been assigned. This is for null edits.
2673  $this->mOutput->setFlag( 'vary-revision' );
2674  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2675  $value = $this->getRevisionTimestamp();
2676  break;
2677  case 'revisionuser':
2678  # Let the edit saving system know we should parse the page
2679  # *after* a revision ID has been assigned for null edits.
2680  $this->mOutput->setFlag( 'vary-user' );
2681  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2682  $value = $this->getRevisionUser();
2683  break;
2684  case 'revisionsize':
2685  $value = $this->getRevisionSize();
2686  break;
2687  case 'namespace':
2688  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2689  break;
2690  case 'namespacee':
2691  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2692  break;
2693  case 'namespacenumber':
2694  $value = $this->mTitle->getNamespace();
2695  break;
2696  case 'talkspace':
2697  $value = $this->mTitle->canHaveTalkPage()
2698  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2699  : '';
2700  break;
2701  case 'talkspacee':
2702  $value = $this->mTitle->canHaveTalkPage() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2703  break;
2704  case 'subjectspace':
2705  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2706  break;
2707  case 'subjectspacee':
2708  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2709  break;
2710  case 'currentdayname':
2711  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2712  break;
2713  case 'currentyear':
2714  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2715  break;
2716  case 'currenttime':
2717  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2718  break;
2719  case 'currenthour':
2720  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2721  break;
2722  case 'currentweek':
2723  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2724  # int to remove the padding
2725  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2726  break;
2727  case 'currentdow':
2728  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2729  break;
2730  case 'localdayname':
2731  $value = $pageLang->getWeekdayName(
2732  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2733  );
2734  break;
2735  case 'localyear':
2736  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2737  break;
2738  case 'localtime':
2739  $value = $pageLang->time(
2740  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2741  false,
2742  false
2743  );
2744  break;
2745  case 'localhour':
2746  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2747  break;
2748  case 'localweek':
2749  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2750  # int to remove the padding
2751  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2752  break;
2753  case 'localdow':
2754  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2755  break;
2756  case 'numberofarticles':
2757  $value = $pageLang->formatNum( SiteStats::articles() );
2758  break;
2759  case 'numberoffiles':
2760  $value = $pageLang->formatNum( SiteStats::images() );
2761  break;
2762  case 'numberofusers':
2763  $value = $pageLang->formatNum( SiteStats::users() );
2764  break;
2765  case 'numberofactiveusers':
2766  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2767  break;
2768  case 'numberofpages':
2769  $value = $pageLang->formatNum( SiteStats::pages() );
2770  break;
2771  case 'numberofadmins':
2772  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2773  break;
2774  case 'numberofedits':
2775  $value = $pageLang->formatNum( SiteStats::edits() );
2776  break;
2777  case 'currenttimestamp':
2778  $value = wfTimestamp( TS_MW, $ts );
2779  break;
2780  case 'localtimestamp':
2781  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2782  break;
2783  case 'currentversion':
2785  break;
2786  case 'articlepath':
2787  return $wgArticlePath;
2788  case 'sitename':
2789  return $wgSitename;
2790  case 'server':
2791  return $wgServer;
2792  case 'servername':
2793  return $wgServerName;
2794  case 'scriptpath':
2795  return $wgScriptPath;
2796  case 'stylepath':
2797  return $wgStylePath;
2798  case 'directionmark':
2799  return $pageLang->getDirMark();
2800  case 'contentlanguage':
2802  return $wgLanguageCode;
2803  case 'pagelanguage':
2804  $value = $pageLang->getCode();
2805  break;
2806  case 'cascadingsources':
2808  break;
2809  default:
2810  $ret = null;
2811  Hooks::run(
2812  'ParserGetVariableValueSwitch',
2813  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2814  );
2815 
2816  return $ret;
2817  }
2818 
2819  if ( $index ) {
2820  $this->mVarCache[$index] = $value;
2821  }
2822 
2823  return $value;
2824  }
2825 
2831  public function initialiseVariables() {
2832  $variableIDs = MagicWord::getVariableIDs();
2833  $substIDs = MagicWord::getSubstIDs();
2834 
2835  $this->mVariables = new MagicWordArray( $variableIDs );
2836  $this->mSubstWords = new MagicWordArray( $substIDs );
2837  }
2838 
2861  public function preprocessToDom( $text, $flags = 0 ) {
2862  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2863  return $dom;
2864  }
2865 
2873  public static function splitWhitespace( $s ) {
2874  $ltrimmed = ltrim( $s );
2875  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2876  $trimmed = rtrim( $ltrimmed );
2877  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2878  if ( $diff > 0 ) {
2879  $w2 = substr( $ltrimmed, -$diff );
2880  } else {
2881  $w2 = '';
2882  }
2883  return [ $w1, $trimmed, $w2 ];
2884  }
2885 
2906  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2907  # Is there any text? Also, Prevent too big inclusions!
2908  $textSize = strlen( $text );
2909  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2910  return $text;
2911  }
2912 
2913  if ( $frame === false ) {
2914  $frame = $this->getPreprocessor()->newFrame();
2915  } elseif ( !( $frame instanceof PPFrame ) ) {
2916  wfDebug( __METHOD__ . " called using plain parameters instead of "
2917  . "a PPFrame instance. Creating custom frame.\n" );
2918  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2919  }
2920 
2921  $dom = $this->preprocessToDom( $text );
2922  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2923  $text = $frame->expand( $dom, $flags );
2924 
2925  return $text;
2926  }
2927 
2935  public static function createAssocArgs( $args ) {
2936  $assocArgs = [];
2937  $index = 1;
2938  foreach ( $args as $arg ) {
2939  $eqpos = strpos( $arg, '=' );
2940  if ( $eqpos === false ) {
2941  $assocArgs[$index++] = $arg;
2942  } else {
2943  $name = trim( substr( $arg, 0, $eqpos ) );
2944  $value = trim( substr( $arg, $eqpos + 1 ) );
2945  if ( $value === false ) {
2946  $value = '';
2947  }
2948  if ( $name !== false ) {
2949  $assocArgs[$name] = $value;
2950  }
2951  }
2952  }
2953 
2954  return $assocArgs;
2955  }
2956 
2983  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2984  # does no harm if $current and $max are present but are unnecessary for the message
2985  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2986  # only during preview, and that would split the parser cache unnecessarily.
2987  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
2988  ->text();
2989  $this->mOutput->addWarning( $warning );
2990  $this->addTrackingCategory( "$limitationType-category" );
2991  }
2992 
3005  public function braceSubstitution( $piece, $frame ) {
3006  // Flags
3007 
3008  // $text has been filled
3009  $found = false;
3010  // wiki markup in $text should be escaped
3011  $nowiki = false;
3012  // $text is HTML, armour it against wikitext transformation
3013  $isHTML = false;
3014  // Force interwiki transclusion to be done in raw mode not rendered
3015  $forceRawInterwiki = false;
3016  // $text is a DOM node needing expansion in a child frame
3017  $isChildObj = false;
3018  // $text is a DOM node needing expansion in the current frame
3019  $isLocalObj = false;
3020 
3021  # Title object, where $text came from
3022  $title = false;
3023 
3024  # $part1 is the bit before the first |, and must contain only title characters.
3025  # Various prefixes will be stripped from it later.
3026  $titleWithSpaces = $frame->expand( $piece['title'] );
3027  $part1 = trim( $titleWithSpaces );
3028  $titleText = false;
3029 
3030  # Original title text preserved for various purposes
3031  $originalTitle = $part1;
3032 
3033  # $args is a list of argument nodes, starting from index 0, not including $part1
3034  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3035  # below won't work b/c this $args isn't an object
3036  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3037 
3038  $profileSection = null; // profile templates
3039 
3040  # SUBST
3041  if ( !$found ) {
3042  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3043 
3044  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3045  # Decide whether to expand template or keep wikitext as-is.
3046  if ( $this->ot['wiki'] ) {
3047  if ( $substMatch === false ) {
3048  $literal = true; # literal when in PST with no prefix
3049  } else {
3050  $literal = false; # expand when in PST with subst: or safesubst:
3051  }
3052  } else {
3053  if ( $substMatch == 'subst' ) {
3054  $literal = true; # literal when not in PST with plain subst:
3055  } else {
3056  $literal = false; # expand when not in PST with safesubst: or no prefix
3057  }
3058  }
3059  if ( $literal ) {
3060  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3061  $isLocalObj = true;
3062  $found = true;
3063  }
3064  }
3065 
3066  # Variables
3067  if ( !$found && $args->getLength() == 0 ) {
3068  $id = $this->mVariables->matchStartToEnd( $part1 );
3069  if ( $id !== false ) {
3070  $text = $this->getVariableValue( $id, $frame );
3071  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3072  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3073  }
3074  $found = true;
3075  }
3076  }
3077 
3078  # MSG, MSGNW and RAW
3079  if ( !$found ) {
3080  # Check for MSGNW:
3081  $mwMsgnw = MagicWord::get( 'msgnw' );
3082  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3083  $nowiki = true;
3084  } else {
3085  # Remove obsolete MSG:
3086  $mwMsg = MagicWord::get( 'msg' );
3087  $mwMsg->matchStartAndRemove( $part1 );
3088  }
3089 
3090  # Check for RAW:
3091  $mwRaw = MagicWord::get( 'raw' );
3092  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3093  $forceRawInterwiki = true;
3094  }
3095  }
3096 
3097  # Parser functions
3098  if ( !$found ) {
3099  $colonPos = strpos( $part1, ':' );
3100  if ( $colonPos !== false ) {
3101  $func = substr( $part1, 0, $colonPos );
3102  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3103  $argsLength = $args->getLength();
3104  for ( $i = 0; $i < $argsLength; $i++ ) {
3105  $funcArgs[] = $args->item( $i );
3106  }
3107  try {
3108  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3109  } catch ( Exception $ex ) {
3110  throw $ex;
3111  }
3112 
3113  // Extract any forwarded flags
3114  if ( isset( $result['title'] ) ) {
3115  $title = $result['title'];
3116  }
3117  if ( isset( $result['found'] ) ) {
3118  $found = $result['found'];
3119  }
3120  if ( array_key_exists( 'text', $result ) ) {
3121  // a string or null
3122  $text = $result['text'];
3123  }
3124  if ( isset( $result['nowiki'] ) ) {
3125  $nowiki = $result['nowiki'];
3126  }
3127  if ( isset( $result['isHTML'] ) ) {
3128  $isHTML = $result['isHTML'];
3129  }
3130  if ( isset( $result['forceRawInterwiki'] ) ) {
3131  $forceRawInterwiki = $result['forceRawInterwiki'];
3132  }
3133  if ( isset( $result['isChildObj'] ) ) {
3134  $isChildObj = $result['isChildObj'];
3135  }
3136  if ( isset( $result['isLocalObj'] ) ) {
3137  $isLocalObj = $result['isLocalObj'];
3138  }
3139  }
3140  }
3141 
3142  # Finish mangling title and then check for loops.
3143  # Set $title to a Title object and $titleText to the PDBK
3144  if ( !$found ) {
3145  $ns = NS_TEMPLATE;
3146  # Split the title into page and subpage
3147  $subpage = '';
3148  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3149  if ( $part1 !== $relative ) {
3150  $part1 = $relative;
3151  $ns = $this->mTitle->getNamespace();
3152  }
3153  $title = Title::newFromText( $part1, $ns );
3154  if ( $title ) {
3155  $titleText = $title->getPrefixedText();
3156  # Check for language variants if the template is not found
3157  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3158  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3159  }
3160  # Do recursion depth check
3161  $limit = $this->mOptions->getMaxTemplateDepth();
3162  if ( $frame->depth >= $limit ) {
3163  $found = true;
3164  $text = '<span class="error">'
3165  . wfMessage( 'parser-template-recursion-depth-warning' )
3166  ->numParams( $limit )->inContentLanguage()->text()
3167  . '</span>';
3168  }
3169  }
3170  }
3171 
3172  # Load from database
3173  if ( !$found && $title ) {
3174  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3175  if ( !$title->isExternal() ) {
3176  if ( $title->isSpecialPage()
3177  && $this->mOptions->getAllowSpecialInclusion()
3178  && $this->ot['html']
3179  ) {
3180  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3181  // Pass the template arguments as URL parameters.
3182  // "uselang" will have no effect since the Language object
3183  // is forced to the one defined in ParserOptions.
3184  $pageArgs = [];
3185  $argsLength = $args->getLength();
3186  for ( $i = 0; $i < $argsLength; $i++ ) {
3187  $bits = $args->item( $i )->splitArg();
3188  if ( strval( $bits['index'] ) === '' ) {
3189  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3190  $value = trim( $frame->expand( $bits['value'] ) );
3191  $pageArgs[$name] = $value;
3192  }
3193  }
3194 
3195  // Create a new context to execute the special page
3196  $context = new RequestContext;
3197  $context->setTitle( $title );
3198  $context->setRequest( new FauxRequest( $pageArgs ) );
3199  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3200  $context->setUser( $this->getUser() );
3201  } else {
3202  // If this page is cached, then we better not be per user.
3203  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3204  }
3205  $context->setLanguage( $this->mOptions->getUserLangObj() );
3207  $title, $context, $this->getLinkRenderer() );
3208  if ( $ret ) {
3209  $text = $context->getOutput()->getHTML();
3210  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3211  $found = true;
3212  $isHTML = true;
3213  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3214  $this->mOutput->updateRuntimeAdaptiveExpiry(
3215  $specialPage->maxIncludeCacheTime()
3216  );
3217  }
3218  }
3219  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3220  $found = false; # access denied
3221  wfDebug( __METHOD__ . ": template inclusion denied for " .
3222  $title->getPrefixedDBkey() . "\n" );
3223  } else {
3224  list( $text, $title ) = $this->getTemplateDom( $title );
3225  if ( $text !== false ) {
3226  $found = true;
3227  $isChildObj = true;
3228  }
3229  }
3230 
3231  # If the title is valid but undisplayable, make a link to it
3232  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3233  $text = "[[:$titleText]]";
3234  $found = true;
3235  }
3236  } elseif ( $title->isTrans() ) {
3237  # Interwiki transclusion
3238  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3239  $text = $this->interwikiTransclude( $title, 'render' );
3240  $isHTML = true;
3241  } else {
3242  $text = $this->interwikiTransclude( $title, 'raw' );
3243  # Preprocess it like a template
3244  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3245  $isChildObj = true;
3246  }
3247  $found = true;
3248  }
3249 
3250  # Do infinite loop check
3251  # This has to be done after redirect resolution to avoid infinite loops via redirects
3252  if ( !$frame->loopCheck( $title ) ) {
3253  $found = true;
3254  $text = '<span class="error">'
3255  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3256  . '</span>';
3257  $this->addTrackingCategory( 'template-loop-category' );
3258  $this->mOutput->addWarning( wfMessage( 'template-loop-warning',
3259  wfEscapeWikiText( $titleText ) )->text() );
3260  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3261  }
3262  }
3263 
3264  # If we haven't found text to substitute by now, we're done
3265  # Recover the source wikitext and return it
3266  if ( !$found ) {
3267  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3268  if ( $profileSection ) {
3269  $this->mProfiler->scopedProfileOut( $profileSection );
3270  }
3271  return [ 'object' => $text ];
3272  }
3273 
3274  # Expand DOM-style return values in a child frame
3275  if ( $isChildObj ) {
3276  # Clean up argument array
3277  $newFrame = $frame->newChild( $args, $title );
3278 
3279  if ( $nowiki ) {
3280  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3281  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3282  # Expansion is eligible for the empty-frame cache
3283  $text = $newFrame->cachedExpand( $titleText, $text );
3284  } else {
3285  # Uncached expansion
3286  $text = $newFrame->expand( $text );
3287  }
3288  }
3289  if ( $isLocalObj && $nowiki ) {
3290  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3291  $isLocalObj = false;
3292  }
3293 
3294  if ( $profileSection ) {
3295  $this->mProfiler->scopedProfileOut( $profileSection );
3296  }
3297 
3298  # Replace raw HTML by a placeholder
3299  if ( $isHTML ) {
3300  $text = $this->insertStripItem( $text );
3301  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3302  # Escape nowiki-style return values
3303  $text = wfEscapeWikiText( $text );
3304  } elseif ( is_string( $text )
3305  && !$piece['lineStart']
3306  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3307  ) {
3308  # T2529: if the template begins with a table or block-level
3309  # element, it should be treated as beginning a new line.
3310  # This behavior is somewhat controversial.
3311  $text = "\n" . $text;
3312  }
3313 
3314  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3315  # Error, oversize inclusion
3316  if ( $titleText !== false ) {
3317  # Make a working, properly escaped link if possible (T25588)
3318  $text = "[[:$titleText]]";
3319  } else {
3320  # This will probably not be a working link, but at least it may
3321  # provide some hint of where the problem is
3322  preg_replace( '/^:/', '', $originalTitle );
3323  $text = "[[:$originalTitle]]";
3324  }
3325  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3326  . 'post-expand include size too large -->' );
3327  $this->limitationWarn( 'post-expand-template-inclusion' );
3328  }
3329 
3330  if ( $isLocalObj ) {
3331  $ret = [ 'object' => $text ];
3332  } else {
3333  $ret = [ 'text' => $text ];
3334  }
3335 
3336  return $ret;
3337  }
3338 
3358  public function callParserFunction( $frame, $function, array $args = [] ) {
3360 
3361  # Case sensitive functions
3362  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3363  $function = $this->mFunctionSynonyms[1][$function];
3364  } else {
3365  # Case insensitive functions
3366  $function = $wgContLang->lc( $function );
3367  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3368  $function = $this->mFunctionSynonyms[0][$function];
3369  } else {
3370  return [ 'found' => false ];
3371  }
3372  }
3373 
3374  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3375 
3376  // Avoid PHP 7.1 warning from passing $this by reference
3377  $parser = $this;
3378 
3379  $allArgs = [ &$parser ];
3380  if ( $flags & self::SFH_OBJECT_ARGS ) {
3381  # Convert arguments to PPNodes and collect for appending to $allArgs
3382  $funcArgs = [];
3383  foreach ( $args as $k => $v ) {
3384  if ( $v instanceof PPNode || $k === 0 ) {
3385  $funcArgs[] = $v;
3386  } else {
3387  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3388  }
3389  }
3390 
3391  # Add a frame parameter, and pass the arguments as an array
3392  $allArgs[] = $frame;
3393  $allArgs[] = $funcArgs;
3394  } else {
3395  # Convert arguments to plain text and append to $allArgs
3396  foreach ( $args as $k => $v ) {
3397  if ( $v instanceof PPNode ) {
3398  $allArgs[] = trim( $frame->expand( $v ) );
3399  } elseif ( is_int( $k ) && $k >= 0 ) {
3400  $allArgs[] = trim( $v );
3401  } else {
3402  $allArgs[] = trim( "$k=$v" );
3403  }
3404  }
3405  }
3406 
3407  $result = call_user_func_array( $callback, $allArgs );
3408 
3409  # The interface for function hooks allows them to return a wikitext
3410  # string or an array containing the string and any flags. This mungs
3411  # things around to match what this method should return.
3412  if ( !is_array( $result ) ) {
3413  $result = [
3414  'found' => true,
3415  'text' => $result,
3416  ];
3417  } else {
3418  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3419  $result['text'] = $result[0];
3420  }
3421  unset( $result[0] );
3422  $result += [
3423  'found' => true,
3424  ];
3425  }
3426 
3427  $noparse = true;
3428  $preprocessFlags = 0;
3429  if ( isset( $result['noparse'] ) ) {
3430  $noparse = $result['noparse'];
3431  }
3432  if ( isset( $result['preprocessFlags'] ) ) {
3433  $preprocessFlags = $result['preprocessFlags'];
3434  }
3435 
3436  if ( !$noparse ) {
3437  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3438  $result['isChildObj'] = true;
3439  }
3440 
3441  return $result;
3442  }
3443 
3452  public function getTemplateDom( $title ) {
3453  $cacheTitle = $title;
3454  $titleText = $title->getPrefixedDBkey();
3455 
3456  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3457  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3458  $title = Title::makeTitle( $ns, $dbk );
3459  $titleText = $title->getPrefixedDBkey();
3460  }
3461  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3462  return [ $this->mTplDomCache[$titleText], $title ];
3463  }
3464 
3465  # Cache miss, go to the database
3466  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3467 
3468  if ( $text === false ) {
3469  $this->mTplDomCache[$titleText] = false;
3470  return [ false, $title ];
3471  }
3472 
3473  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3474  $this->mTplDomCache[$titleText] = $dom;
3475 
3476  if ( !$title->equals( $cacheTitle ) ) {
3477  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3478  [ $title->getNamespace(), $title->getDBkey() ];
3479  }
3480 
3481  return [ $dom, $title ];
3482  }
3483 
3496  $cacheKey = $title->getPrefixedDBkey();
3497  if ( !$this->currentRevisionCache ) {
3498  $this->currentRevisionCache = new MapCacheLRU( 100 );
3499  }
3500  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3501  $this->currentRevisionCache->set( $cacheKey,
3502  // Defaults to Parser::statelessFetchRevision()
3503  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3504  );
3505  }
3506  return $this->currentRevisionCache->get( $cacheKey );
3507  }
3508 
3518  public static function statelessFetchRevision( Title $title, $parser = false ) {
3520 
3521  return $rev;
3522  }
3523 
3529  public function fetchTemplateAndTitle( $title ) {
3530  // Defaults to Parser::statelessFetchTemplate()
3531  $templateCb = $this->mOptions->getTemplateCallback();
3532  $stuff = call_user_func( $templateCb, $title, $this );
3533  // We use U+007F DELETE to distinguish strip markers from regular text.
3534  $text = $stuff['text'];
3535  if ( is_string( $stuff['text'] ) ) {
3536  $text = strtr( $text, "\x7f", "?" );
3537  }
3538  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3539  if ( isset( $stuff['deps'] ) ) {
3540  foreach ( $stuff['deps'] as $dep ) {
3541  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3542  if ( $dep['title']->equals( $this->getTitle() ) ) {
3543  // If we transclude ourselves, the final result
3544  // will change based on the new version of the page
3545  $this->mOutput->setFlag( 'vary-revision' );
3546  }
3547  }
3548  }
3549  return [ $text, $finalTitle ];
3550  }
3551 
3557  public function fetchTemplate( $title ) {
3558  return $this->fetchTemplateAndTitle( $title )[0];
3559  }
3560 
3570  public static function statelessFetchTemplate( $title, $parser = false ) {
3571  $text = $skip = false;
3572  $finalTitle = $title;
3573  $deps = [];
3574 
3575  # Loop to fetch the article, with up to 1 redirect
3576  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall
3577  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3578  # Give extensions a chance to select the revision instead
3579  $id = false; # Assume current
3580  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3581  [ $parser, $title, &$skip, &$id ] );
3582 
3583  if ( $skip ) {
3584  $text = false;
3585  $deps[] = [
3586  'title' => $title,
3587  'page_id' => $title->getArticleID(),
3588  'rev_id' => null
3589  ];
3590  break;
3591  }
3592  # Get the revision
3593  if ( $id ) {
3594  $rev = Revision::newFromId( $id );
3595  } elseif ( $parser ) {
3596  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3597  } else {
3599  }
3600  $rev_id = $rev ? $rev->getId() : 0;
3601  # If there is no current revision, there is no page
3602  if ( $id === false && !$rev ) {
3603  $linkCache = LinkCache::singleton();
3604  $linkCache->addBadLinkObj( $title );
3605  }
3606 
3607  $deps[] = [
3608  'title' => $title,
3609  'page_id' => $title->getArticleID(),
3610  'rev_id' => $rev_id ];
3611  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3612  # We fetched a rev from a different title; register it too...
3613  $deps[] = [
3614  'title' => $rev->getTitle(),
3615  'page_id' => $rev->getPage(),
3616  'rev_id' => $rev_id ];
3617  }
3618 
3619  if ( $rev ) {
3620  $content = $rev->getContent();
3621  $text = $content ? $content->getWikitextForTransclusion() : null;
3622 
3623  Hooks::run( 'ParserFetchTemplate',
3624  [ $parser, $title, $rev, &$text, &$deps ] );
3625 
3626  if ( $text === false || $text === null ) {
3627  $text = false;
3628  break;
3629  }
3630  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3632  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3633  if ( !$message->exists() ) {
3634  $text = false;
3635  break;
3636  }
3637  $content = $message->content();
3638  $text = $message->plain();
3639  } else {
3640  break;
3641  }
3642  if ( !$content ) {
3643  break;
3644  }
3645  # Redirect?
3646  $finalTitle = $title;
3647  $title = $content->getRedirectTarget();
3648  }
3649  return [
3650  'text' => $text,
3651  'finalTitle' => $finalTitle,
3652  'deps' => $deps ];
3653  }
3654 
3662  public function fetchFile( $title, $options = [] ) {
3663  return $this->fetchFileAndTitle( $title, $options )[0];
3664  }
3665 
3673  public function fetchFileAndTitle( $title, $options = [] ) {
3674  $file = $this->fetchFileNoRegister( $title, $options );
3675 
3676  $time = $file ? $file->getTimestamp() : false;
3677  $sha1 = $file ? $file->getSha1() : false;
3678  # Register the file as a dependency...
3679  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3680  if ( $file && !$title->equals( $file->getTitle() ) ) {
3681  # Update fetched file title
3682  $title = $file->getTitle();
3683  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3684  }
3685  return [ $file, $title ];
3686  }
3687 
3698  protected function fetchFileNoRegister( $title, $options = [] ) {
3699  if ( isset( $options['broken'] ) ) {
3700  $file = false; // broken thumbnail forced by hook
3701  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3702  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3703  } else { // get by (name,timestamp)
3704  $file = wfFindFile( $title, $options );
3705  }
3706  return $file;
3707  }
3708 
3717  public function interwikiTransclude( $title, $action ) {
3718  global $wgEnableScaryTranscluding;
3719 
3720  if ( !$wgEnableScaryTranscluding ) {
3721  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3722  }
3723 
3724  $url = $title->getFullURL( [ 'action' => $action ] );
3725 
3726  if ( strlen( $url ) > 255 ) {
3727  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3728  }
3729  return $this->fetchScaryTemplateMaybeFromCache( $url );
3730  }
3731 
3736  public function fetchScaryTemplateMaybeFromCache( $url ) {
3737  global $wgTranscludeCacheExpiry;
3738  $dbr = wfGetDB( DB_REPLICA );
3739  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3740  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3741  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3742  if ( $obj ) {
3743  return $obj->tc_contents;
3744  }
3745 
3746  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3747  $status = $req->execute(); // Status object
3748  if ( $status->isOK() ) {
3749  $text = $req->getContent();
3750  } elseif ( $req->getStatus() != 200 ) {
3751  // Though we failed to fetch the content, this status is useless.
3752  return wfMessage( 'scarytranscludefailed-httpstatus' )
3753  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3754  } else {
3755  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3756  }
3757 
3758  $dbw = wfGetDB( DB_MASTER );
3759  $dbw->replace( 'transcache', [ 'tc_url' ], [
3760  'tc_url' => $url,
3761  'tc_time' => $dbw->timestamp( time() ),
3762  'tc_contents' => $text
3763  ] );
3764  return $text;
3765  }
3766 
3776  public function argSubstitution( $piece, $frame ) {
3777  $error = false;
3778  $parts = $piece['parts'];
3779  $nameWithSpaces = $frame->expand( $piece['title'] );
3780  $argName = trim( $nameWithSpaces );
3781  $object = false;
3782  $text = $frame->getArgument( $argName );
3783  if ( $text === false && $parts->getLength() > 0
3784  && ( $this->ot['html']
3785  || $this->ot['pre']
3786  || ( $this->ot['wiki'] && $frame->isTemplate() )
3787  )
3788  ) {
3789  # No match in frame, use the supplied default
3790  $object = $parts->item( 0 )->getChildren();
3791  }
3792  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3793  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3794  $this->limitationWarn( 'post-expand-template-argument' );
3795  }
3796 
3797  if ( $text === false && $object === false ) {
3798  # No match anywhere
3799  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3800  }
3801  if ( $error !== false ) {
3802  $text .= $error;
3803  }
3804  if ( $object !== false ) {
3805  $ret = [ 'object' => $object ];
3806  } else {
3807  $ret = [ 'text' => $text ];
3808  }
3809 
3810  return $ret;
3811  }
3812 
3828  public function extensionSubstitution( $params, $frame ) {
3829  static $errorStr = '<span class="error">';
3830  static $errorLen = 20;
3831 
3832  $name = $frame->expand( $params['name'] );
3833  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3834  // Probably expansion depth or node count exceeded. Just punt the
3835  // error up.
3836  return $name;
3837  }
3838 
3839  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3840  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3841  // See above
3842  return $attrText;
3843  }
3844 
3845  // We can't safely check if the expansion for $content resulted in an
3846  // error, because the content could happen to be the error string
3847  // (T149622).
3848  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3849 
3850  $marker = self::MARKER_PREFIX . "-$name-"
3851  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3852 
3853  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3854  ( $this->ot['html'] || $this->ot['pre'] );
3855  if ( $isFunctionTag ) {
3856  $markerType = 'none';
3857  } else {
3858  $markerType = 'general';
3859  }
3860  if ( $this->ot['html'] || $isFunctionTag ) {
3861  $name = strtolower( $name );
3862  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3863  if ( isset( $params['attributes'] ) ) {
3864  $attributes = $attributes + $params['attributes'];
3865  }
3866 
3867  if ( isset( $this->mTagHooks[$name] ) ) {
3868  $output = call_user_func_array( $this->mTagHooks[$name],
3869  [ $content, $attributes, $this, $frame ] );
3870  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3871  list( $callback, ) = $this->mFunctionTagHooks[$name];
3872 
3873  // Avoid PHP 7.1 warning from passing $this by reference
3874  $parser = $this;
3875  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3876  } else {
3877  $output = '<span class="error">Invalid tag extension name: ' .
3878  htmlspecialchars( $name ) . '</span>';
3879  }
3880 
3881  if ( is_array( $output ) ) {
3882  // Extract flags
3883  $flags = $output;
3884  $output = $flags[0];
3885  if ( isset( $flags['markerType'] ) ) {
3886  $markerType = $flags['markerType'];
3887  }
3888  }
3889  } else {
3890  if ( is_null( $attrText ) ) {
3891  $attrText = '';
3892  }
3893  if ( isset( $params['attributes'] ) ) {
3894  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3895  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3896  htmlspecialchars( $attrValue ) . '"';
3897  }
3898  }
3899  if ( $content === null ) {
3900  $output = "<$name$attrText/>";
3901  } else {
3902  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3903  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3904  // See above
3905  return $close;
3906  }
3907  $output = "<$name$attrText>$content$close";
3908  }
3909  }
3910 
3911  if ( $markerType === 'none' ) {
3912  return $output;
3913  } elseif ( $markerType === 'nowiki' ) {
3914  $this->mStripState->addNoWiki( $marker, $output );
3915  } elseif ( $markerType === 'general' ) {
3916  $this->mStripState->addGeneral( $marker, $output );
3917  } else {
3918  throw new MWException( __METHOD__ . ': invalid marker type' );
3919  }
3920  return $marker;
3921  }
3922 
3930  public function incrementIncludeSize( $type, $size ) {
3931  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3932  return false;
3933  } else {
3934  $this->mIncludeSizes[$type] += $size;
3935  return true;
3936  }
3937  }
3938 
3945  $this->mExpensiveFunctionCount++;
3946  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3947  }
3948 
3957  public function doDoubleUnderscore( $text ) {
3958  # The position of __TOC__ needs to be recorded
3959  $mw = MagicWord::get( 'toc' );
3960  if ( $mw->match( $text ) ) {
3961  $this->mShowToc = true;
3962  $this->mForceTocPosition = true;
3963 
3964  # Set a placeholder. At the end we'll fill it in with the TOC.
3965  $text = $mw->replace( '<!--MWTOC\'"-->', $text, 1 );
3966 
3967  # Only keep the first one.
3968  $text = $mw->replace( '', $text );
3969  }
3970 
3971  # Now match and remove the rest of them
3973  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3974 
3975  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3976  $this->mOutput->mNoGallery = true;
3977  }
3978  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3979  $this->mShowToc = false;
3980  }
3981  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3982  && $this->mTitle->getNamespace() == NS_CATEGORY
3983  ) {
3984  $this->addTrackingCategory( 'hidden-category-category' );
3985  }
3986  # (T10068) Allow control over whether robots index a page.
3987  # __INDEX__ always overrides __NOINDEX__, see T16899
3988  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3989  $this->mOutput->setIndexPolicy( 'noindex' );
3990  $this->addTrackingCategory( 'noindex-category' );
3991  }
3992  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
3993  $this->mOutput->setIndexPolicy( 'index' );
3994  $this->addTrackingCategory( 'index-category' );
3995  }
3996 
3997  # Cache all double underscores in the database
3998  foreach ( $this->mDoubleUnderscores as $key => $val ) {
3999  $this->mOutput->setProperty( $key, '' );
4000  }
4001 
4002  return $text;
4003  }
4004 
4010  public function addTrackingCategory( $msg ) {
4011  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4012  }
4013 
4030  public function formatHeadings( $text, $origText, $isMain = true ) {
4031  global $wgMaxTocLevel;
4032 
4033  # Inhibit editsection links if requested in the page
4034  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4035  $maybeShowEditLink = false;
4036  } else {
4037  $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
4038  }
4039 
4040  # Get all headlines for numbering them and adding funky stuff like [edit]
4041  # links - this is for later, but we need the number of headlines right now
4042  $matches = [];
4043  $numMatches = preg_match_all(
4044  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4045  $text,
4046  $matches
4047  );
4048 
4049  # if there are fewer than 4 headlines in the article, do not show TOC
4050  # unless it's been explicitly enabled.
4051  $enoughToc = $this->mShowToc &&
4052  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4053 
4054  # Allow user to stipulate that a page should have a "new section"
4055  # link added via __NEWSECTIONLINK__
4056  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4057  $this->mOutput->setNewSection( true );
4058  }
4059 
4060  # Allow user to remove the "new section"
4061  # link via __NONEWSECTIONLINK__
4062  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4063  $this->mOutput->hideNewSection( true );
4064  }
4065 
4066  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4067  # override above conditions and always show TOC above first header
4068  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4069  $this->mShowToc = true;
4070  $enoughToc = true;
4071  }
4072 
4073  # headline counter
4074  $headlineCount = 0;
4075  $numVisible = 0;
4076 
4077  # Ugh .. the TOC should have neat indentation levels which can be
4078  # passed to the skin functions. These are determined here
4079  $toc = '';
4080  $full = '';
4081  $head = [];
4082  $sublevelCount = [];
4083  $levelCount = [];
4084  $level = 0;
4085  $prevlevel = 0;
4086  $toclevel = 0;
4087  $prevtoclevel = 0;
4088  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4089  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4090  $oldType = $this->mOutputType;
4091  $this->setOutputType( self::OT_WIKI );
4092  $frame = $this->getPreprocessor()->newFrame();
4093  $root = $this->preprocessToDom( $origText );
4094  $node = $root->getFirstChild();
4095  $byteOffset = 0;
4096  $tocraw = [];
4097  $refers = [];
4098 
4099  $headlines = $numMatches !== false ? $matches[3] : [];
4100 
4101  foreach ( $headlines as $headline ) {
4102  $isTemplate = false;
4103  $titleText = false;
4104  $sectionIndex = false;
4105  $numbering = '';
4106  $markerMatches = [];
4107  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4108  $serial = $markerMatches[1];
4109  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4110  $isTemplate = ( $titleText != $baseTitleText );
4111  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4112  }
4113 
4114  if ( $toclevel ) {
4115  $prevlevel = $level;
4116  }
4117  $level = $matches[1][$headlineCount];
4118 
4119  if ( $level > $prevlevel ) {
4120  # Increase TOC level
4121  $toclevel++;
4122  $sublevelCount[$toclevel] = 0;
4123  if ( $toclevel < $wgMaxTocLevel ) {
4124  $prevtoclevel = $toclevel;
4125  $toc .= Linker::tocIndent();
4126  $numVisible++;
4127  }
4128  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4129  # Decrease TOC level, find level to jump to
4130 
4131  for ( $i = $toclevel; $i > 0; $i-- ) {
4132  if ( $levelCount[$i] == $level ) {
4133  # Found last matching level
4134  $toclevel = $i;
4135  break;
4136  } elseif ( $levelCount[$i] < $level ) {
4137  # Found first matching level below current level
4138  $toclevel = $i + 1;
4139  break;
4140  }
4141  }
4142  if ( $i == 0 ) {
4143  $toclevel = 1;
4144  }
4145  if ( $toclevel < $wgMaxTocLevel ) {
4146  if ( $prevtoclevel < $wgMaxTocLevel ) {
4147  # Unindent only if the previous toc level was shown :p
4148  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4149  $prevtoclevel = $toclevel;
4150  } else {
4151  $toc .= Linker::tocLineEnd();
4152  }
4153  }
4154  } else {
4155  # No change in level, end TOC line
4156  if ( $toclevel < $wgMaxTocLevel ) {
4157  $toc .= Linker::tocLineEnd();
4158  }
4159  }
4160 
4161  $levelCount[$toclevel] = $level;
4162 
4163  # count number of headlines for each level
4164  $sublevelCount[$toclevel]++;
4165  $dot = 0;
4166  for ( $i = 1; $i <= $toclevel; $i++ ) {
4167  if ( !empty( $sublevelCount[$i] ) ) {
4168  if ( $dot ) {
4169  $numbering .= '.';
4170  }
4171  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4172  $dot = 1;
4173  }
4174  }
4175 
4176  # The safe header is a version of the header text safe to use for links
4177 
4178  # Remove link placeholders by the link text.
4179  # <!--LINK number-->
4180  # turns into
4181  # link text with suffix
4182  # Do this before unstrip since link text can contain strip markers
4183  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4184 
4185  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4186  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4187 
4188  # Strip out HTML (first regex removes any tag not allowed)
4189  # Allowed tags are:
4190  # * <sup> and <sub> (T10393)
4191  # * <i> (T28375)
4192  # * <b> (r105284)
4193  # * <bdi> (T74884)
4194  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4195  # * <s> and <strike> (T35715)
4196  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4197  # to allow setting directionality in toc items.
4198  $tocline = preg_replace(
4199  [
4200  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4201  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4202  ],
4203  [ '', '<$1>' ],
4204  $safeHeadline
4205  );
4206 
4207  # Strip '<span></span>', which is the result from the above if
4208  # <span id="foo"></span> is used to produce an additional anchor
4209  # for a section.
4210  $tocline = str_replace( '<span></span>', '', $tocline );
4211 
4212  $tocline = trim( $tocline );
4213 
4214  # For the anchor, strip out HTML-y stuff period
4215  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4216  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4217 
4218  # Save headline for section edit hint before it's escaped
4219  $headlineHint = $safeHeadline;
4220 
4221  # Decode HTML entities
4222  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4223 
4224  $safeHeadline = self::normalizeSectionName( $safeHeadline );
4225 
4226  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4227  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4228  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4229  if ( $fallbackHeadline === $safeHeadline ) {
4230  # No reason to have both (in fact, we can't)
4231  $fallbackHeadline = false;
4232  }
4233 
4234  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4235  # @todo FIXME: We may be changing them depending on the current locale.
4236  $arrayKey = strtolower( $safeHeadline );
4237  if ( $fallbackHeadline === false ) {
4238  $fallbackArrayKey = false;
4239  } else {
4240  $fallbackArrayKey = strtolower( $fallbackHeadline );
4241  }
4242 
4243  # Create the anchor for linking from the TOC to the section
4244  $anchor = $safeHeadline;
4245  $fallbackAnchor = $fallbackHeadline;
4246  if ( isset( $refers[$arrayKey] ) ) {
4247  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall,Generic.Formatting.DisallowMultipleStatements
4248  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4249  $anchor .= "_$i";
4250  $linkAnchor .= "_$i";
4251  $refers["${arrayKey}_$i"] = true;
4252  } else {
4253  $refers[$arrayKey] = true;
4254  }
4255  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4256  // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall,Generic.Formatting.DisallowMultipleStatements
4257  for ( $i = 2; isset( $refers["${fallbackArrayKey}_$i"] ); ++$i );
4258  $fallbackAnchor .= "_$i";
4259  $refers["${fallbackArrayKey}_$i"] = true;
4260  } else {
4261  $refers[$fallbackArrayKey] = true;
4262  }
4263 
4264  # Don't number the heading if it is the only one (looks silly)
4265  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4266  # the two are different if the line contains a link
4267  $headline = Html::element(
4268  'span',
4269  [ 'class' => 'mw-headline-number' ],
4270  $numbering
4271  ) . ' ' . $headline;
4272  }
4273 
4274  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4275  $toc .= Linker::tocLine( $linkAnchor, $tocline,
4276  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4277  }
4278 
4279  # Add the section to the section tree
4280  # Find the DOM node for this header
4281  $noOffset = ( $isTemplate || $sectionIndex === false );
4282  while ( $node && !$noOffset ) {
4283  if ( $node->getName() === 'h' ) {
4284  $bits = $node->splitHeading();
4285  if ( $bits['i'] == $sectionIndex ) {
4286  break;
4287  }
4288  }
4289  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4290  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4291  $node = $node->getNextSibling();
4292  }
4293  $tocraw[] = [
4294  'toclevel' => $toclevel,
4295  'level' => $level,
4296  'line' => $tocline,
4297  'number' => $numbering,
4298  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4299  'fromtitle' => $titleText,
4300  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4301  'anchor' => $anchor,
4302  ];
4303 
4304  # give headline the correct <h#> tag
4305  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4306  // Output edit section links as markers with styles that can be customized by skins
4307  if ( $isTemplate ) {
4308  # Put a T flag in the section identifier, to indicate to extractSections()
4309  # that sections inside <includeonly> should be counted.
4310  $editsectionPage = $titleText;
4311  $editsectionSection = "T-$sectionIndex";
4312  $editsectionContent = null;
4313  } else {
4314  $editsectionPage = $this->mTitle->getPrefixedText();
4315  $editsectionSection = $sectionIndex;
4316  $editsectionContent = $headlineHint;
4317  }
4318  // We use a bit of pesudo-xml for editsection markers. The
4319  // language converter is run later on. Using a UNIQ style marker
4320  // leads to the converter screwing up the tokens when it
4321  // converts stuff. And trying to insert strip tags fails too. At
4322  // this point all real inputted tags have already been escaped,
4323  // so we don't have to worry about a user trying to input one of
4324  // these markers directly. We use a page and section attribute
4325  // to stop the language converter from converting these
4326  // important bits of data, but put the headline hint inside a
4327  // content block because the language converter is supposed to
4328  // be able to convert that piece of data.
4329  // Gets replaced with html in ParserOutput::getText
4330  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4331  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4332  if ( $editsectionContent !== null ) {
4333  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4334  } else {
4335  $editlink .= '/>';
4336  }
4337  } else {
4338  $editlink = '';
4339  }
4340  $head[$headlineCount] = Linker::makeHeadline( $level,
4341  $matches['attrib'][$headlineCount], $anchor, $headline,
4342  $editlink, $fallbackAnchor );
4343 
4344  $headlineCount++;
4345  }
4346 
4347  $this->setOutputType( $oldType );
4348 
4349  # Never ever show TOC if no headers
4350  if ( $numVisible < 1 ) {
4351  $enoughToc = false;
4352  }
4353 
4354  if ( $enoughToc ) {
4355  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4356  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4357  }
4358  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4359  $this->mOutput->setTOCHTML( $toc );
4360  $toc = self::TOC_START . $toc . self::TOC_END;
4361  }
4362 
4363  if ( $isMain ) {
4364  $this->mOutput->setSections( $tocraw );
4365  }
4366 
4367  # split up and insert constructed headlines
4368  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4369  $i = 0;
4370 
4371  // build an array of document sections
4372  $sections = [];
4373  foreach ( $blocks as $block ) {
4374  // $head is zero-based, sections aren't.
4375  if ( empty( $head[$i - 1] ) ) {
4376  $sections[$i] = $block;
4377  } else {
4378  $sections[$i] = $head[$i - 1] . $block;
4379  }
4380 
4391  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $maybeShowEditLink ] );
4392 
4393  $i++;
4394  }
4395 
4396  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4397  // append the TOC at the beginning
4398  // Top anchor now in skin
4399  $sections[0] = $sections[0] . $toc . "\n";
4400  }
4401 
4402  $full .= implode( '', $sections );
4403 
4404  if ( $this->mForceTocPosition ) {
4405  return str_replace( '<!--MWTOC\'"-->', $toc, $full );
4406  } else {
4407  return $full;
4408  }
4409  }
4410 
4422  public function preSaveTransform( $text, Title $title, User $user,
4423  ParserOptions $options, $clearState = true
4424  ) {
4425  if ( $clearState ) {
4426  $magicScopeVariable = $this->lock();
4427  }
4428  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4429  $this->setUser( $user );
4430 
4431  // Strip U+0000 NULL (T159174)
4432  $text = str_replace( "\000", '', $text );
4433 
4434  // We still normalize line endings for backwards-compatibility
4435  // with other code that just calls PST, but this should already
4436  // be handled in TextContent subclasses
4437  $text = TextContent::normalizeLineEndings( $text );
4438 
4439  if ( $options->getPreSaveTransform() ) {
4440  $text = $this->pstPass2( $text, $user );
4441  }
4442  $text = $this->mStripState->unstripBoth( $text );
4443 
4444  $this->setUser( null ); # Reset
4445 
4446  return $text;
4447  }
4448 
4457  private function pstPass2( $text, $user ) {
4459 
4460  # Note: This is the timestamp saved as hardcoded wikitext to
4461  # the database, we use $wgContLang here in order to give
4462  # everyone the same signature and use the default one rather
4463  # than the one selected in each user's preferences.
4464  # (see also T14815)
4465  $ts = $this->mOptions->getTimestamp();
4466  $timestamp = MWTimestamp::getLocalInstance( $ts );
4467  $ts = $timestamp->format( 'YmdHis' );
4468  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4469 
4470  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4471 
4472  # Variable replacement
4473  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4474  $text = $this->replaceVariables( $text );
4475 
4476  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4477  # which may corrupt this parser instance via its wfMessage()->text() call-
4478 
4479  # Signatures
4480  if ( strpos( $text, '~~~' ) !== false ) {
4481  $sigText = $this->getUserSig( $user );
4482  $text = strtr( $text, [
4483  '~~~~~' => $d,
4484  '~~~~' => "$sigText $d",
4485  '~~~' => $sigText
4486  ] );
4487  # The main two signature forms used above are time-sensitive
4488  $this->mOutput->setFlag( 'user-signature' );
4489  }
4490 
4491  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4492  $tc = '[' . Title::legalChars() . ']';
4493  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4494 
4495  // [[ns:page (context)|]]
4496  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4497  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4498  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4499  // [[ns:page (context), context|]] (using either single or double-width comma)
4500  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4501  // [[|page]] (reverse pipe trick: add context from page title)
4502  $p2 = "/\[\[\\|($tc+)]]/";
4503 
4504  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4505  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4506  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4507  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4508 
4509  $t = $this->mTitle->getText();
4510  $m = [];
4511  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4512  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4513  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4514  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4515  } else {
4516  # if there's no context, don't bother duplicating the title
4517  $text = preg_replace( $p2, '[[\\1]]', $text );
4518  }
4519 
4520  return $text;
4521  }
4522 
4537  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4538  global $wgMaxSigChars;
4539 
4540  $username = $user->getName();
4541 
4542  # If not given, retrieve from the user object.
4543  if ( $nickname === false ) {
4544  $nickname = $user->getOption( 'nickname' );
4545  }
4546 
4547  if ( is_null( $fancySig ) ) {
4548  $fancySig = $user->getBoolOption( 'fancysig' );
4549  }
4550 
4551  $nickname = $nickname == null ? $username : $nickname;
4552 
4553  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4554  $nickname = $username;
4555  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4556  } elseif ( $fancySig !== false ) {
4557  # Sig. might contain markup; validate this
4558  if ( $this->validateSig( $nickname ) !== false ) {
4559  # Validated; clean up (if needed) and return it
4560  return $this->cleanSig( $nickname, true );
4561  } else {
4562  # Failed to validate; fall back to the default
4563  $nickname = $username;
4564  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4565  }
4566  }
4567 
4568  # Make sure nickname doesnt get a sig in a sig
4569  $nickname = self::cleanSigInSig( $nickname );
4570 
4571  # If we're still here, make it a link to the user page
4572  $userText = wfEscapeWikiText( $username );
4573  $nickText = wfEscapeWikiText( $nickname );
4574  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4575 
4576  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4577  ->title( $this->getTitle() )->text();
4578  }
4579 
4586  public function validateSig( $text ) {
4587  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4588  }
4589 
4600  public function cleanSig( $text, $parsing = false ) {
4601  if ( !$parsing ) {
4602  global $wgTitle;
4603  $magicScopeVariable = $this->lock();
4604  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4605  }
4606 
4607  # Option to disable this feature
4608  if ( !$this->mOptions->getCleanSignatures() ) {
4609  return $text;
4610  }
4611 
4612  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4613  # => Move this logic to braceSubstitution()
4614  $substWord = MagicWord::get( 'subst' );
4615  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4616  $substText = '{{' . $substWord->getSynonym( 0 );
4617 
4618  $text = preg_replace( $substRegex, $substText, $text );
4619  $text = self::cleanSigInSig( $text );
4620  $dom = $this->preprocessToDom( $text );
4621  $frame = $this->getPreprocessor()->newFrame();
4622  $text = $frame->expand( $dom );
4623 
4624  if ( !$parsing ) {
4625  $text = $this->mStripState->unstripBoth( $text );
4626  }
4627 
4628  return $text;
4629  }
4630 
4637  public static function cleanSigInSig( $text ) {
4638  $text = preg_replace( '/~{3,5}/', '', $text );
4639  return $text;
4640  }
4641 
4652  $outputType, $clearState = true
4653  ) {
4654  $this->startParse( $title, $options, $outputType, $clearState );
4655  }
4656 
4663  private function startParse( Title $title = null, ParserOptions $options,
4664  $outputType, $clearState = true
4665  ) {
4666  $this->setTitle( $title );
4667  $this->mOptions = $options;
4668  $this->setOutputType( $outputType );
4669  if ( $clearState ) {
4670  $this->clearState();
4671  }
4672  }
4673 
4682  public function transformMsg( $text, $options, $title = null ) {
4683  static $executing = false;
4684 
4685  # Guard against infinite recursion
4686  if ( $executing ) {
4687  return $text;
4688  }
4689  $executing = true;
4690 
4691  if ( !$title ) {
4692  global $wgTitle;
4693  $title = $wgTitle;
4694  }
4695 
4696  $text = $this->preprocess( $text, $title, $options );
4697 
4698  $executing = false;
4699  return $text;
4700  }
4701 
4726  public function setHook( $tag, callable $callback ) {
4727  $tag = strtolower( $tag );
4728  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4729  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4730  }
4731  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4732  $this->mTagHooks[$tag] = $callback;
4733  if ( !in_array( $tag, $this->mStripList ) ) {
4734  $this->mStripList[] = $tag;
4735  }
4736 
4737  return $oldVal;
4738  }
4739 
4757  public function setTransparentTagHook( $tag, callable $callback ) {
4758  $tag = strtolower( $tag );
4759  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4760  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4761  }
4762  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4763  $this->mTransparentTagHooks[$tag] = $callback;
4764 
4765  return $oldVal;
4766  }
4767 
4771  public function clearTagHooks() {
4772  $this->mTagHooks = [];
4773  $this->mFunctionTagHooks = [];
4774  $this->mStripList = $this->mDefaultStripList;
4775  }
4776 
4820  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4822 
4823  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4824  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4825 
4826  # Add to function cache
4827  $mw = MagicWord::get( $id );
4828  if ( !$mw ) {
4829  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4830  }
4831 
4832  $synonyms = $mw->getSynonyms();
4833  $sensitive = intval( $mw->isCaseSensitive() );
4834 
4835  foreach ( $synonyms as $syn ) {
4836  # Case
4837  if ( !$sensitive ) {
4838  $syn = $wgContLang->lc( $syn );
4839  }
4840  # Add leading hash
4841  if ( !( $flags & self::SFH_NO_HASH ) ) {
4842  $syn = '#' . $syn;
4843  }
4844  # Remove trailing colon
4845  if ( substr( $syn, -1, 1 ) === ':' ) {
4846  $syn = substr( $syn, 0, -1 );
4847  }
4848  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4849  }
4850  return $oldVal;
4851  }
4852 
4858  public function getFunctionHooks() {
4859  return array_keys( $this->mFunctionHooks );
4860  }
4861 
4872  public function setFunctionTagHook( $tag, callable $callback, $flags ) {
4873  $tag = strtolower( $tag );
4874  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4875  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4876  }
4877  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4878  $this->mFunctionTagHooks[$tag] : null;
4879  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4880 
4881  if ( !in_array( $tag, $this->mStripList ) ) {
4882  $this->mStripList[] = $tag;
4883  }
4884 
4885  return $old;
4886  }
4887 
4895  public function replaceLinkHolders( &$text, $options = 0 ) {
4896  $this->mLinkHolders->replace( $text );
4897  }
4898 
4906  public function replaceLinkHoldersText( $text ) {
4907  return $this->mLinkHolders->replaceText( $text );
4908  }
4909 
4923  public function renderImageGallery( $text, $params ) {
4924  $mode = false;
4925  if ( isset( $params['mode'] ) ) {
4926  $mode = $params['mode'];
4927  }
4928 
4929  try {
4930  $ig = ImageGalleryBase::factory( $mode );
4931  } catch ( Exception $e ) {
4932  // If invalid type set, fallback to default.
4933  $ig = ImageGalleryBase::factory( false );
4934  }
4935 
4936  $ig->setContextTitle( $this->mTitle );
4937  $ig->setShowBytes( false );
4938  $ig->setShowDimensions( false );
4939  $ig->setShowFilename( false );
4940  $ig->setParser( $this );
4941  $ig->setHideBadImages();
4942  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
4943 
4944  if ( isset( $params['showfilename'] ) ) {
4945  $ig->setShowFilename( true );
4946  } else {
4947  $ig->setShowFilename( false );
4948  }
4949  if ( isset( $params['caption'] ) ) {
4950  $caption = $params['caption'];
4951  $caption = htmlspecialchars( $caption );
4952  $caption = $this->replaceInternalLinks( $caption );
4953  $ig->setCaptionHtml( $caption );
4954  }
4955  if ( isset( $params['perrow'] ) ) {
4956  $ig->setPerRow( $params['perrow'] );
4957  }
4958  if ( isset( $params['widths'] ) ) {
4959  $ig->setWidths( $params['widths'] );
4960  }
4961  if ( isset( $params['heights'] ) ) {
4962  $ig->setHeights( $params['heights'] );
4963  }
4964  $ig->setAdditionalOptions( $params );
4965 
4966  // Avoid PHP 7.1 warning from passing $this by reference
4967  $parser = $this;
4968  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
4969 
4970  $lines = StringUtils::explode( "\n", $text );
4971  foreach ( $lines as $line ) {
4972  # match lines like these:
4973  # Image:someimage.jpg|This is some image
4974  $matches = [];
4975  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4976  # Skip empty lines
4977  if ( count( $matches ) == 0 ) {
4978  continue;
4979  }
4980 
4981  if ( strpos( $matches[0], '%' ) !== false ) {
4982  $matches[1] = rawurldecode( $matches[1] );
4983  }
4985  if ( is_null( $title ) ) {
4986  # Bogus title. Ignore these so we don't bomb out later.
4987  continue;
4988  }
4989 
4990  # We need to get what handler the file uses, to figure out parameters.
4991  # Note, a hook can overide the file name, and chose an entirely different
4992  # file (which potentially could be of a different type and have different handler).
4993  $options = [];
4994  $descQuery = false;
4995  Hooks::run( 'BeforeParserFetchFileAndTitle',
4996  [ $this, $title, &$options, &$descQuery ] );
4997  # Don't register it now, as TraditionalImageGallery does that later.
4998  $file = $this->fetchFileNoRegister( $title, $options );
4999  $handler = $file ? $file->getHandler() : false;
5000 
5001  $paramMap = [
5002  'img_alt' => 'gallery-internal-alt',
5003  'img_link' => 'gallery-internal-link',
5004  ];
5005  if ( $handler ) {
5006  $paramMap = $paramMap + $handler->getParamMap();
5007  // We don't want people to specify per-image widths.
5008  // Additionally the width parameter would need special casing anyhow.
5009  unset( $paramMap['img_width'] );
5010  }
5011 
5012  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5013 
5014  $label = '';
5015  $alt = '';
5016  $link = '';
5017  $handlerOptions = [];
5018  if ( isset( $matches[3] ) ) {
5019  // look for an |alt= definition while trying not to break existing
5020  // captions with multiple pipes (|) in it, until a more sensible grammar
5021  // is defined for images in galleries
5022 
5023  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5024  // splitting on '|' is a bit odd, and different from makeImage.
5025  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5026  // Protect LanguageConverter markup
5027  $parameterMatches = StringUtils::delimiterExplode(
5028  '-{', '}-', '|', $matches[3], true /* nested */
5029  );
5030 
5031  foreach ( $parameterMatches as $parameterMatch ) {
5032  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5033  if ( $magicName ) {
5034  $paramName = $paramMap[$magicName];
5035 
5036  switch ( $paramName ) {
5037  case 'gallery-internal-alt':
5038  $alt = $this->stripAltText( $match, false );
5039  break;
5040  case 'gallery-internal-link':
5041  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5042  $chars = self::EXT_LINK_URL_CLASS;
5043  $addr = self::EXT_LINK_ADDR;
5044  $prots = $this->mUrlProtocols;
5045  // check to see if link matches an absolute url, if not then it must be a wiki link.
5046  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5047  // Result of LanguageConverter::markNoConversion
5048  // invoked on an external link.
5049  $linkValue = substr( $linkValue, 4, -2 );
5050  }
5051  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5052  $link = $linkValue;
5053  $this->mOutput->addExternalLink( $link );
5054  } else {
5055  $localLinkTitle = Title::newFromText( $linkValue );
5056  if ( $localLinkTitle !== null ) {
5057  $this->mOutput->addLink( $localLinkTitle );
5058  $link = $localLinkTitle->getLinkURL();
5059  }
5060  }
5061  break;
5062  default:
5063  // Must be a handler specific parameter.
5064  if ( $handler->validateParam( $paramName, $match ) ) {
5065  $handlerOptions[$paramName] = $match;
5066  } else {
5067  // Guess not, consider it as caption.
5068  wfDebug( "$parameterMatch failed parameter validation\n" );
5069  $label = '|' . $parameterMatch;
5070  }
5071  }
5072 
5073  } else {
5074  // Last pipe wins.
5075  $label = '|' . $parameterMatch;
5076  }
5077  }
5078  // Remove the pipe.
5079  $label = substr( $label, 1 );
5080  }
5081 
5082  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5083  }
5084  $html = $ig->toHTML();
5085  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5086  return $html;
5087  }
5088 
5093  public function getImageParams( $handler ) {
5094  if ( $handler ) {
5095  $handlerClass = get_class( $handler );
5096  } else {
5097  $handlerClass = '';
5098  }
5099  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5100  # Initialise static lists
5101  static $internalParamNames = [
5102  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5103  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5104  'bottom', 'text-bottom' ],
5105  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5106  'upright', 'border', 'link', 'alt', 'class' ],
5107  ];
5108  static $internalParamMap;
5109  if ( !$internalParamMap ) {
5110  $internalParamMap = [];
5111  foreach ( $internalParamNames as $type => $names ) {
5112  foreach ( $names as $name ) {
5113  // For grep: img_left, img_right, img_center, img_none,
5114  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5115  // img_bottom, img_text_bottom,
5116  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5117  // img_border, img_link, img_alt, img_class
5118  $magicName = str_replace( '-', '_', "img_$name" );
5119  $internalParamMap[$magicName] = [ $type, $name ];
5120  }
5121  }
5122  }
5123 
5124  # Add handler params
5125  $paramMap = $internalParamMap;
5126  if ( $handler ) {
5127  $handlerParamMap = $handler->getParamMap();
5128  foreach ( $handlerParamMap as $magic => $paramName ) {
5129  $paramMap[$magic] = [ 'handler', $paramName ];
5130  }
5131  }
5132  $this->mImageParams[$handlerClass] = $paramMap;
5133  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5134  }
5135  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5136  }
5137 
5146  public function makeImage( $title, $options, $holders = false ) {
5147  # Check if the options text is of the form "options|alt text"
5148  # Options are:
5149  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5150  # * left no resizing, just left align. label is used for alt= only
5151  # * right same, but right aligned
5152  # * none same, but not aligned
5153  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5154  # * center center the image
5155  # * frame Keep original image size, no magnify-button.
5156  # * framed Same as "frame"
5157  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5158  # * upright reduce width for upright images, rounded to full __0 px
5159  # * border draw a 1px border around the image
5160  # * alt Text for HTML alt attribute (defaults to empty)
5161  # * class Set a class for img node
5162  # * link Set the target of the image link. Can be external, interwiki, or local
5163  # vertical-align values (no % or length right now):
5164  # * baseline
5165  # * sub
5166  # * super
5167  # * top
5168  # * text-top
5169  # * middle
5170  # * bottom
5171  # * text-bottom
5172 
5173  # Protect LanguageConverter markup when splitting into parts
5175  '-{', '}-', '|', $options, true /* allow nesting */
5176  );
5177 
5178  # Give extensions a chance to select the file revision for us
5179  $options = [];
5180  $descQuery = false;
5181  Hooks::run( 'BeforeParserFetchFileAndTitle',
5182  [ $this, $title, &$options, &$descQuery ] );
5183  # Fetch and register the file (file title may be different via hooks)
5184  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5185 
5186  # Get parameter map
5187  $handler = $file ? $file->getHandler() : false;
5188 
5189  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5190 
5191  if ( !$file ) {
5192  $this->addTrackingCategory( 'broken-file-category' );
5193  }
5194 
5195  # Process the input parameters
5196  $caption = '';
5197  $params = [ 'frame' => [], 'handler' => [],
5198  'horizAlign' => [], 'vertAlign' => [] ];
5199  $seenformat = false;
5200  foreach ( $parts as $part ) {
5201  $part = trim( $part );
5202  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5203  $validated = false;
5204  if ( isset( $paramMap[$magicName] ) ) {
5205  list( $type, $paramName ) = $paramMap[$magicName];
5206 
5207  # Special case; width and height come in one variable together
5208  if ( $type === 'handler' && $paramName === 'width' ) {
5209  $parsedWidthParam = self::parseWidthParam( $value );
5210  if ( isset( $parsedWidthParam['width'] ) ) {
5211  $width = $parsedWidthParam['width'];
5212  if ( $handler->validateParam( 'width', $width ) ) {
5213  $params[$type]['width'] = $width;
5214  $validated = true;
5215  }
5216  }
5217  if ( isset( $parsedWidthParam['height'] ) ) {
5218  $height = $parsedWidthParam['height'];
5219  if ( $handler->validateParam( 'height', $height ) ) {
5220  $params[$type]['height'] = $height;
5221  $validated = true;
5222  }
5223  }
5224  # else no validation -- T15436
5225  } else {
5226  if ( $type === 'handler' ) {
5227  # Validate handler parameter
5228  $validated = $handler->validateParam( $paramName, $value );
5229  } else {
5230  # Validate internal parameters
5231  switch ( $paramName ) {
5232  case 'manualthumb':
5233  case 'alt':
5234  case 'class':
5235  # @todo FIXME: Possibly check validity here for
5236  # manualthumb? downstream behavior seems odd with
5237  # missing manual thumbs.
5238  $validated = true;
5239  $value = $this->stripAltText( $value, $holders );
5240  break;
5241  case 'link':
5242  $chars = self::EXT_LINK_URL_CLASS;
5243  $addr = self::EXT_LINK_ADDR;
5244  $prots = $this->mUrlProtocols;
5245  if ( $value === '' ) {
5246  $paramName = 'no-link';
5247  $value = true;
5248  $validated = true;
5249  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5250  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5251  $paramName = 'link-url';
5252  $this->mOutput->addExternalLink( $value );
5253  if ( $this->mOptions->getExternalLinkTarget() ) {
5254  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5255  }
5256  $validated = true;
5257  }
5258  } else {
5259  $linkTitle = Title::newFromText( $value );
5260  if ( $linkTitle ) {
5261  $paramName = 'link-title';
5262  $value = $linkTitle;
5263  $this->mOutput->addLink( $linkTitle );
5264  $validated = true;
5265  }
5266  }
5267  break;
5268  case 'frameless':
5269  case 'framed':
5270  case 'thumbnail':
5271  // use first appearing option, discard others.
5272  $validated = !$seenformat;
5273  $seenformat = true;
5274  break;
5275  default:
5276  # Most other things appear to be empty or numeric...
5277  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5278  }
5279  }
5280 
5281  if ( $validated ) {
5282  $params[$type][$paramName] = $value;
5283  }
5284  }
5285  }
5286  if ( !$validated ) {
5287  $caption = $part;
5288  }
5289  }
5290 
5291  # Process alignment parameters
5292  if ( $params['horizAlign'] ) {
5293  $params['frame']['align'] = key( $params['horizAlign'] );
5294  }
5295  if ( $params['vertAlign'] ) {
5296  $params['frame']['valign'] = key( $params['vertAlign'] );
5297  }
5298 
5299  $params['frame']['caption'] = $caption;
5300 
5301  # Will the image be presented in a frame, with the caption below?
5302  $imageIsFramed = isset( $params['frame']['frame'] )
5303  || isset( $params['frame']['framed'] )
5304  || isset( $params['frame']['thumbnail'] )
5305  || isset( $params['frame']['manualthumb'] );
5306 
5307  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5308  # came to also set the caption, ordinary text after the image -- which
5309  # makes no sense, because that just repeats the text multiple times in
5310  # screen readers. It *also* came to set the title attribute.
5311  # Now that we have an alt attribute, we should not set the alt text to
5312  # equal the caption: that's worse than useless, it just repeats the
5313  # text. This is the framed/thumbnail case. If there's no caption, we
5314  # use the unnamed parameter for alt text as well, just for the time be-
5315  # ing, if the unnamed param is set and the alt param is not.
5316  # For the future, we need to figure out if we want to tweak this more,
5317  # e.g., introducing a title= parameter for the title; ignoring the un-
5318  # named parameter entirely for images without a caption; adding an ex-
5319  # plicit caption= parameter and preserving the old magic unnamed para-
5320  # meter for BC; ...
5321  if ( $imageIsFramed ) { # Framed image
5322  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5323  # No caption or alt text, add the filename as the alt text so
5324  # that screen readers at least get some description of the image
5325  $params['frame']['alt'] = $title->getText();
5326  }
5327  # Do not set $params['frame']['title'] because tooltips don't make sense
5328  # for framed images
5329  } else { # Inline image
5330  if ( !isset( $params['frame']['alt'] ) ) {
5331  # No alt text, use the "caption" for the alt text
5332  if ( $caption !== '' ) {
5333  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5334  } else {
5335  # No caption, fall back to using the filename for the
5336  # alt text
5337  $params['frame']['alt'] = $title->getText();
5338  }
5339  }
5340  # Use the "caption" for the tooltip text
5341  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5342  }
5343 
5344  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5345 
5346  # Linker does the rest
5347  $time = isset( $options['time'] ) ? $options['time'] : false;
5348  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5349  $time, $descQuery, $this->mOptions->getThumbSize() );
5350 
5351  # Give the handler a chance to modify the parser object
5352  if ( $handler ) {
5353  $handler->parserTransformHook( $this, $file );
5354  }
5355 
5356  return $ret;
5357  }
5358 
5364  protected function stripAltText( $caption, $holders ) {
5365  # Strip bad stuff out of the title (tooltip). We can't just use
5366  # replaceLinkHoldersText() here, because if this function is called
5367  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5368  if ( $holders ) {
5369  $tooltip = $holders->replaceText( $caption );
5370  } else {
5371  $tooltip = $this->replaceLinkHoldersText( $caption );
5372  }
5373 
5374  # make sure there are no placeholders in thumbnail attributes
5375  # that are later expanded to html- so expand them now and
5376  # remove the tags
5377  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5378  $tooltip = Sanitizer::stripAllTags( $tooltip );
5379 
5380  return $tooltip;
5381  }
5382 
5388  public function disableCache() {
5389  wfDebug( "Parser output marked as uncacheable.\n" );
5390  if ( !$this->mOutput ) {
5391  throw new MWException( __METHOD__ .
5392  " can only be called when actually parsing something" );
5393  }
5394  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5395  }
5396 
5405  public function attributeStripCallback( &$text, $frame = false ) {
5406  $text = $this->replaceVariables( $text, $frame );
5407  $text = $this->mStripState->unstripBoth( $text );
5408  return $text;
5409  }
5410 
5416  public function getTags() {
5417  return array_merge(
5418  array_keys( $this->mTransparentTagHooks ),
5419  array_keys( $this->mTagHooks ),
5420  array_keys( $this->mFunctionTagHooks )
5421  );
5422  }
5423 
5434  public function replaceTransparentTags( $text ) {
5435  $matches = [];
5436  $elements = array_keys( $this->mTransparentTagHooks );
5437  $text = self::extractTagsAndParams( $elements, $text, $matches );
5438  $replacements = [];
5439 
5440  foreach ( $matches as $marker => $data ) {
5441  list( $element, $content, $params, $tag ) = $data;
5442  $tagName = strtolower( $element );
5443  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5444  $output = call_user_func_array(
5445  $this->mTransparentTagHooks[$tagName],
5446  [ $content, $params, $this ]
5447  );
5448  } else {
5449  $output = $tag;
5450  }
5451  $replacements[$marker] = $output;
5452  }
5453  return strtr( $text, $replacements );
5454  }
5455 
5485  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5486  global $wgTitle; # not generally used but removes an ugly failure mode
5487 
5488  $magicScopeVariable = $this->lock();
5489  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5490  $outText = '';
5491  $frame = $this->getPreprocessor()->newFrame();
5492 
5493  # Process section extraction flags
5494  $flags = 0;
5495  $sectionParts = explode( '-', $sectionId );
5496  $sectionIndex = array_pop( $sectionParts );
5497  foreach ( $sectionParts as $part ) {
5498  if ( $part === 'T' ) {
5499  $flags |= self::PTD_FOR_INCLUSION;
5500  }
5501  }
5502 
5503  # Check for empty input
5504  if ( strval( $text ) === '' ) {
5505  # Only sections 0 and T-0 exist in an empty document
5506  if ( $sectionIndex == 0 ) {
5507  if ( $mode === 'get' ) {
5508  return '';
5509  } else {
5510  return $newText;
5511  }
5512  } else {
5513  if ( $mode === 'get' ) {
5514  return $newText;
5515  } else {
5516  return $text;
5517  }
5518  }
5519  }
5520 
5521  # Preprocess the text
5522  $root = $this->preprocessToDom( $text, $flags );
5523 
5524  # <h> nodes indicate section breaks
5525  # They can only occur at the top level, so we can find them by iterating the root's children
5526  $node = $root->getFirstChild();
5527 
5528  # Find the target section
5529  if ( $sectionIndex == 0 ) {
5530  # Section zero doesn't nest, level=big
5531  $targetLevel = 1000;
5532  } else {
5533  while ( $node ) {
5534  if ( $node->getName() === 'h' ) {
5535  $bits = $node->splitHeading();
5536  if ( $bits['i'] == $sectionIndex ) {
5537  $targetLevel = $bits['level'];
5538  break;
5539  }
5540  }
5541  if ( $mode === 'replace' ) {
5542  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5543  }
5544  $node = $node->getNextSibling();
5545  }
5546  }
5547 
5548  if ( !$node ) {
5549  # Not found
5550  if ( $mode === 'get' ) {
5551  return $newText;
5552  } else {
5553  return $text;
5554  }
5555  }
5556 
5557  # Find the end of the section, including nested sections
5558  do {
5559  if ( $node->getName() === 'h' ) {
5560  $bits = $node->splitHeading();
5561  $curLevel = $bits['level'];
5562  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5563  break;
5564  }
5565  }
5566  if ( $mode === 'get' ) {
5567  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5568  }
5569  $node = $node->getNextSibling();
5570  } while ( $node );
5571 
5572  # Write out the remainder (in replace mode only)
5573  if ( $mode === 'replace' ) {
5574  # Output the replacement text
5575  # Add two newlines on -- trailing whitespace in $newText is conventionally
5576  # stripped by the editor, so we need both newlines to restore the paragraph gap
5577  # Only add trailing whitespace if there is newText
5578  if ( $newText != "" ) {
5579  $outText .= $newText . "\n\n";
5580  }
5581 
5582  while ( $node ) {
5583  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5584  $node = $node->getNextSibling();
5585  }
5586  }
5587 
5588  if ( is_string( $outText ) ) {
5589  # Re-insert stripped tags
5590  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5591  }
5592 
5593  return $outText;
5594  }
5595 
5610  public function getSection( $text, $sectionId, $defaultText = '' ) {
5611  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5612  }
5613 
5626  public function replaceSection( $oldText, $sectionId, $newText ) {
5627  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5628  }
5629 
5635  public function getRevisionId() {
5636  return $this->mRevisionId;
5637  }
5638 
5645  public function getRevisionObject() {
5646  if ( !is_null( $this->mRevisionObject ) ) {
5647  return $this->mRevisionObject;
5648  }
5649  if ( is_null( $this->mRevisionId ) ) {
5650  return null;
5651  }
5652 
5653  $rev = call_user_func(
5654  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5655  );
5656 
5657  # If the parse is for a new revision, then the callback should have
5658  # already been set to force the object and should match mRevisionId.
5659  # If not, try to fetch by mRevisionId for sanity.
5660  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5661  $rev = Revision::newFromId( $this->mRevisionId );
5662  }
5663 
5664  $this->mRevisionObject = $rev;
5665 
5666  return $this->mRevisionObject;
5667  }
5668 
5674  public function getRevisionTimestamp() {
5675  if ( is_null( $this->mRevisionTimestamp ) ) {
5677 
5678  $revObject = $this->getRevisionObject();
5679  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5680 
5681  # The cryptic '' timezone parameter tells to use the site-default
5682  # timezone offset instead of the user settings.
5683  # Since this value will be saved into the parser cache, served
5684  # to other users, and potentially even used inside links and such,
5685  # it needs to be consistent for all visitors.
5686  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5687 
5688  }
5689  return $this->mRevisionTimestamp;
5690  }
5691 
5697  public function getRevisionUser() {
5698  if ( is_null( $this->mRevisionUser ) ) {
5699  $revObject = $this->getRevisionObject();
5700 
5701  # if this template is subst: the revision id will be blank,
5702  # so just use the current user's name
5703  if ( $revObject ) {
5704  $this->mRevisionUser = $revObject->getUserText();
5705  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5706  $this->mRevisionUser = $this->getUser()->getName();
5707  }
5708  }
5709  return $this->mRevisionUser;
5710  }
5711 
5717  public function getRevisionSize() {
5718  if ( is_null( $this->mRevisionSize ) ) {
5719  $revObject = $this->getRevisionObject();
5720 
5721  # if this variable is subst: the revision id will be blank,
5722  # so just use the parser input size, because the own substituation
5723  # will change the size.
5724  if ( $revObject ) {
5725  $this->mRevisionSize = $revObject->getSize();
5726  } else {
5727  $this->mRevisionSize = $this->mInputSize;
5728  }
5729  }
5730  return $this->mRevisionSize;
5731  }
5732 
5738  public function setDefaultSort( $sort ) {
5739  $this->mDefaultSort = $sort;
5740  $this->mOutput->setProperty( 'defaultsort', $sort );
5741  }
5742 
5753  public function getDefaultSort() {
5754  if ( $this->mDefaultSort !== false ) {
5755  return $this->mDefaultSort;
5756  } else {
5757  return '';
5758  }
5759  }
5760 
5767  public function getCustomDefaultSort() {
5768  return $this->mDefaultSort;
5769  }
5770 
5771  private static function getSectionNameFromStrippedText( $text ) {
5773  $text = Sanitizer::decodeCharReferences( $text );
5774  $text = self::normalizeSectionName( $text );
5775  return $text;
5776  }
5777 
5778  private static function makeAnchor( $sectionName ) {
5779  return '#' . Sanitizer::escapeIdForLink( $sectionName );
5780  }
5781 
5782  private static function makeLegacyAnchor( $sectionName ) {
5784  if ( isset( $wgFragmentMode[1] ) && $wgFragmentMode[1] === 'legacy' ) {
5785  // ForAttribute() and ForLink() are the same for legacy encoding
5787  } else {
5788  $id = Sanitizer::escapeIdForLink( $sectionName );
5789  }
5790 
5791  return "#$id";
5792  }
5793 
5802  public function guessSectionNameFromWikiText( $text ) {
5803  # Strip out wikitext links(they break the anchor)
5804  $text = $this->stripSectionName( $text );
5805  $sectionName = self::getSectionNameFromStrippedText( $text );
5806  return self::makeAnchor( $sectionName );
5807  }
5808 
5818  public function guessLegacySectionNameFromWikiText( $text ) {
5819  # Strip out wikitext links(they break the anchor)
5820  $text = $this->stripSectionName( $text );
5821  $sectionName = self::getSectionNameFromStrippedText( $text );
5822  return self::makeLegacyAnchor( $sectionName );
5823  }
5824 
5830  public static function guessSectionNameFromStrippedText( $text ) {
5831  $sectionName = self::getSectionNameFromStrippedText( $text );
5832  return self::makeAnchor( $sectionName );
5833  }
5834 
5841  private static function normalizeSectionName( $text ) {
5842  # T90902: ensure the same normalization is applied for IDs as to links
5843  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
5844  try {
5845 
5846  $parts = $titleParser->splitTitleString( "#$text" );
5847  } catch ( MalformedTitleException $ex ) {
5848  return $text;
5849  }
5850  return $parts['fragment'];
5851  }
5852 
5867  public function stripSectionName( $text ) {
5868  # Strip internal link markup
5869  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5870  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5871 
5872  # Strip external link markup
5873  # @todo FIXME: Not tolerant to blank link text
5874  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5875  # on how many empty links there are on the page - need to figure that out.
5876  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5877 
5878  # Parse wikitext quotes (italics & bold)
5879  $text = $this->doQuotes( $text );
5880 
5881  # Strip HTML tags
5882  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5883  return $text;
5884  }
5885 
5896  public function testSrvus( $text, Title $title, ParserOptions $options,
5897  $outputType = self::OT_HTML
5898  ) {
5899  $magicScopeVariable = $this->lock();
5900  $this->startParse( $title, $options, $outputType, true );
5901 
5902  $text = $this->replaceVariables( $text );
5903  $text = $this->mStripState->unstripBoth( $text );
5904  $text = Sanitizer::removeHTMLtags( $text );
5905  return $text;
5906  }
5907 
5914  public function testPst( $text, Title $title, ParserOptions $options ) {
5915  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5916  }
5917 
5924  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5925  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5926  }
5927 
5944  public function markerSkipCallback( $s, $callback ) {
5945  $i = 0;
5946  $out = '';
5947  while ( $i < strlen( $s ) ) {
5948  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5949  if ( $markerStart === false ) {
5950  $out .= call_user_func( $callback, substr( $s, $i ) );
5951  break;
5952  } else {
5953  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5954  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5955  if ( $markerEnd === false ) {
5956  $out .= substr( $s, $markerStart );
5957  break;
5958  } else {
5959  $markerEnd += strlen( self::MARKER_SUFFIX );
5960  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5961  $i = $markerEnd;
5962  }
5963  }
5964  }
5965  return $out;
5966  }
5967 
5974  public function killMarkers( $text ) {
5975  return $this->mStripState->killMarkers( $text );
5976  }
5977 
5994  public function serializeHalfParsedText( $text ) {
5995  $data = [
5996  'text' => $text,
5997  'version' => self::HALF_PARSED_VERSION,
5998  'stripState' => $this->mStripState->getSubState( $text ),
5999  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
6000  ];
6001  return $data;
6002  }
6003 
6019  public function unserializeHalfParsedText( $data ) {
6020  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
6021  throw new MWException( __METHOD__ . ': invalid version' );
6022  }
6023 
6024  # First, extract the strip state.
6025  $texts = [ $data['text'] ];
6026  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6027 
6028  # Now renumber links
6029  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6030 
6031  # Should be good to go.
6032  return $texts[0];
6033  }
6034 
6044  public function isValidHalfParsedText( $data ) {
6045  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6046  }
6047 
6057  public static function parseWidthParam( $value, $parseHeight = true ) {
6058  $parsedWidthParam = [];
6059  if ( $value === '' ) {
6060  return $parsedWidthParam;
6061  }
6062  $m = [];
6063  # (T15500) In both cases (width/height and width only),
6064  # permit trailing "px" for backward compatibility.
6065  if ( $parseHeight && preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6066  $width = intval( $m[1] );
6067  $height = intval( $m[2] );
6068  $parsedWidthParam['width'] = $width;
6069  $parsedWidthParam['height'] = $height;
6070  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6071  $width = intval( $value );
6072  $parsedWidthParam['width'] = $width;
6073  }
6074  return $parsedWidthParam;
6075  }
6076 
6086  protected function lock() {
6087  if ( $this->mInParse ) {
6088  throw new MWException( "Parser state cleared while parsing. "
6089  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6090  }
6091 
6092  // Save the backtrace when locking, so that if some code tries locking again,
6093  // we can print the lock owner's backtrace for easier debugging
6094  $e = new Exception;
6095  $this->mInParse = $e->getTraceAsString();
6096 
6097  $recursiveCheck = new ScopedCallback( function () {
6098  $this->mInParse = false;
6099  } );
6100 
6101  return $recursiveCheck;
6102  }
6103 
6114  public static function stripOuterParagraph( $html ) {
6115  $m = [];
6116  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6117  if ( strpos( $m[1], '</p>' ) === false ) {
6118  $html = $m[1];
6119  }
6120  }
6121 
6122  return $html;
6123  }
6124 
6135  public function getFreshParser() {
6136  global $wgParserConf;
6137  if ( $this->mInParse ) {
6138  return new $wgParserConf['class']( $wgParserConf );
6139  } else {
6140  return $this;
6141  }
6142  }
6143 
6150  public function enableOOUI() {
6152  $this->mOutput->setEnableOOUI( true );
6153  }
6154 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5645
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:547
setTitle($t)
Set the context title.
Definition: Parser.php:759
$mAutonumber
Definition: Parser.php:178
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5944
$mPPNodeCount
Definition: Parser.php:192
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2090
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:291
getExternalLinkAttribs($url)
Get an associative array of additional HTML attributes appropriate for a particular external link...
Definition: Parser.php:1929
const MARKER_PREFIX
Definition: Parser.php:135
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
isValidHalfParsedText($data)
Returns true if the given array, presumed to be generated by serializeHalfParsedText(), is compatible with the current version of the parser.
Definition: Parser.php:6044
null means default in associative array form
Definition: hooks.txt:1980
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:1980
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1569
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
getSection($text, $sectionId, $defaultText= '')
This function returns the text of a section, specified by a number ($section).
Definition: Parser.php:5610
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1429
$mTplRedirCache
Definition: Parser.php:194
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5974
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
static tocList($toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
Definition: Linker.php:1581
LinkRenderer $mLinkRenderer
Definition: Parser.php:258
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3529
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:784
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5697
the array() calling protocol came about after MediaWiki 1.4rc1.
stripSectionName($text)
Strips a text string of wikitext for use in a section anchor.
Definition: Parser.php:5867
const OT_PREPROCESS
Definition: Defines.php:187
static parseWidthParam($value, $parseHeight=true)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6057
either a plain
Definition: hooks.txt:2041
$mDoubleUnderscores
Definition: Parser.php:194
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2571
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Group all the pieces relevant to the context of a request into one instance.
getPreloadText($text, Title $title, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:729
setTransparentTagHook($tag, callable $callback)
As setHook(), but letting the contents be parsed.
Definition: Parser.php:4757
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4586
MapCacheLRU null $currentRevisionCache
Definition: Parser.php:244
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4923
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:710
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1833
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
const ID_PRIMARY
Tells escapeUrlForHtml() to encode the ID using the wiki's primary encoding.
Definition: Sanitizer.php:64
nextLinkID()
Definition: Parser.php:848
const SPACE_NOT_NL
Definition: Parser.php:104
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:1980
getImageParams($handler)
Definition: Parser.php:5093
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
doHeadings($text)
Parse headers and return html.
Definition: Parser.php:1612
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
const OT_PLAIN
Definition: Parser.php:115
getTags()
Accessor.
Definition: Parser.php:5416
static isWellFormedXmlFragment($text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:731
const OT_WIKI
Definition: Parser.php:112
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2156
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3673
User $mUser
Definition: Parser.php:201
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for except in special pages derived from QueryPage It s a common pitfall for new developers to submit code containing SQL queries which examine huge numbers of rows Remember that COUNT * is(N), counting rows in atable is like counting beans in a bucket.------------------------------------------------------------------------Replication------------------------------------------------------------------------The largest installation of MediaWiki, Wikimedia, uses a large set ofslave MySQL servers replicating writes made to a master MySQL server.Itis important to understand the issues associated with this setup if youwant to write code destined for Wikipedia.It's often the case that the best algorithm to use for a given taskdepends on whether or not replication is in use.Due to our unabashedWikipedia-centrism, we often just use the replication-friendly version, but if you like, you can use wfGetLB() ->getServerCount() > 1 tocheck to see if replication is in use.===Lag===Lag primarily occurs when large write queries are sent to the master.Writes on the master are executed in parallel, but they are executed inserial when they are replicated to the slaves.The master writes thequery to the binlog when the transaction is committed.The slaves pollthe binlog and start executing the query as soon as it appears.They canservice reads while they are performing a write query, but will not readanything more from the binlog and thus will perform no more writes.Thismeans that if the write query runs for a long time, the slaves will lagbehind the master for the time it takes for the write query to complete.Lag can be exacerbated by high read load.MediaWiki's load balancer willstop sending reads to a slave when it is lagged by more than 30 seconds.If the load ratios are set incorrectly, or if there is too much loadgenerally, this may lead to a slave permanently hovering around 30seconds lag.If all slaves are lagged by more than 30 seconds, MediaWiki will stopwriting to the database.All edits and other write operations will berefused, with an error returned to the user.This gives the slaves achance to catch up.Before we had this mechanism, the slaves wouldregularly lag by several minutes, making review of recent editsdifficult.In addition to this, MediaWiki attempts to ensure that the user seesevents occurring on the wiki in chronological order.A few seconds of lagcan be tolerated, as long as the user sees a consistent picture fromsubsequent requests.This is done by saving the master binlog positionin the session, and then at the start of each request, waiting for theslave to catch up to that position before doing any reads from it.Ifthis wait times out, reads are allowed anyway, but the request isconsidered to be in"lagged slave mode".Lagged slave mode can bechecked by calling wfGetLB() ->getLaggedSlaveMode().The onlypractical consequence at present is a warning displayed in the pagefooter.===Lag avoidance===To avoid excessive lag, queries which write large numbers of rows shouldbe split up, generally to write one row at a time.Multi-row INSERT...SELECT queries are the worst offenders should be avoided altogether.Instead do the select first and then the insert.===Working with lag===Despite our best efforts, it's not practical to guarantee a low-lagenvironment.Lag will usually be less than one second, but mayoccasionally be up to 30 seconds.For scalability, it's very importantto keep load on the master low, so simply sending all your queries tothe master is not the answer.So when you have a genuine need forup-to-date data, the following approach is advised:1) Do a quick query to the master for a sequence number or timestamp 2) Run the full query on the slave and check if it matches the data you gotfrom the master 3) If it doesn't, run the full query on the masterTo avoid swamping the master every time the slaves lag, use of thisapproach should be kept to a minimum.In most cases you should just readfrom the slave and let the user deal with the delay.------------------------------------------------------------------------Lock contention------------------------------------------------------------------------Due to the high write rate on Wikipedia(and some other wikis), MediaWiki developers need to be very careful to structure their writesto avoid long-lasting locks.By default, MediaWiki opens a transactionat the first query, and commits it before the output is sent.Locks willbe held from the time when the query is done until the commit.So youcan reduce lock time by doing as much processing as possible before youdo your write queries.Often this approach is not good enough, and it becomes necessary toenclose small groups of queries in their own transaction.Use thefollowing syntax:$dbw=wfGetDB(DB_MASTER
initialiseVariables()
initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers ...
Definition: Parser.php:2831
static isEnabled()
Definition: MWTidy.php:79
static tidy($text)
Interface with html tidy.
Definition: MWTidy.php:46
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:4858
static fixTagAttributes($text, $element, $sorted=false)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML...
Definition: Sanitizer.php:1109
static makeLegacyAnchor($sectionName)
Definition: Parser.php:5782
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:209
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
database rows
Definition: globals.txt:10
wfHostname()
Fetch server name for use in error reporting etc.
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:863
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2213
setFunctionTagHook($tag, callable $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4872
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3776
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5896
const TOC_START
Definition: Parser.php:138
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:787
SectionProfiler $mProfiler
Definition: Parser.php:253
$sort
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:115
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3698
null for the local wiki Added in
Definition: hooks.txt:1584
There are three types of nodes:
$mHeadings
Definition: Parser.php:194
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4771
static makeSelfLinkObj($nt, $html= '', $query= '', $trail= '', $prefix= '')
Make appropriate markup for a link to the current article.
Definition: Linker.php:186
const NS_SPECIAL
Definition: Defines.php:54
clearState()
Clear Parser state.
Definition: Parser.php:344
__construct($conf=[])
Definition: Parser.php:263
const EXT_LINK_ADDR
Definition: Parser.php:97
$mFirstCall
Definition: Parser.php:153
interwikiTransclude($title, $action)
Transclude an interwiki link.
Definition: Parser.php:3717
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4457
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
Definition: Parser.php:5818
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
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
Options($x=null)
Accessor/mutator for the ParserOptions object.
Definition: Parser.php:841
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5994
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4895
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3518
static activeUsers()
Definition: SiteStats.php:130
$mLinkID
Definition: Parser.php:191
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1645
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2861
limitationWarn($limitationType, $current= '', $max= '')
Warn the user when a parser limitation is reached Will warn at most once the user per limitation type...
Definition: Parser.php:2983
static cleanUrl($url)
Definition: Sanitizer.php:2014
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:273
$mGeneratedPPNodeCount
Definition: Parser.php:192
$mRevisionId
Definition: Parser.php:218
target page
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
doBlockLevels($text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2458
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:813
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:930
const NS_TEMPLATE
Definition: Defines.php:75
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:134
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2473
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:641
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2571
MagicWordArray $mVariables
Definition: Parser.php:160
static validateTagAttributes($attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
Definition: Sanitizer.php:772
const SFH_NO_HASH
Definition: Parser.php:85
const DB_MASTER
Definition: defines.php:26
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 For a description of the see design txt $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
$mForceTocPosition
Definition: Parser.php:196
preprocess($text, Title $title=null, ParserOptions $options, $revid=null, $frame=false)
Expand templates and variables in the text, producing valid, static wikitext.
Definition: Parser.php:684
static getCacheTTL($id)
Allow external reads of TTL array.
Definition: MagicWord.php:314
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5635
const OT_PREPROCESS
Definition: Parser.php:113
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2446
$mFunctionSynonyms
Definition: Parser.php:145
If you want to remove the page from your watchlist later
getPreSaveTransform()
Transform wiki markup when saving the page?
replaceLinkHoldersText($text)
Replace "" link placeholders with plain text of links (not HTML-formatted).
Definition: Parser.php:4906
setLinkID($id)
Definition: Parser.php:855
$mOutputType
Definition: Parser.php:215
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
$mDefaultStripList
Definition: Parser.php:148
static createAssocArgs($args)
Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
Definition: Parser.php:2935
$mExtLinkBracketedRegex
Definition: Parser.php:167
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. '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!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!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:1978
if($line===false) $args
Definition: cdb.php:64
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
const ID_FALLBACK
Tells escapeUrlForHtml() to encode the ID using the fallback encoding, or return false if no fallback...
Definition: Sanitizer.php:72
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:327
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:1665
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3452
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3005
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:52
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1642
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4600
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static factory($mode=false, IContextSource $context=null)
Get a new image gallery.
$wgLanguageCode
Site language code.
Custom PHP profiler for parser/DB type section names that xhprof/xdebug can't handle.
static getPage($name)
Find the object with a given name and return it (or NULL)
static edits()
Definition: SiteStats.php:94
$wgExtraInterlanguageLinkPrefixes
List of additional interwiki prefixes that should be treated as interlanguage links (i...
startExternalParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Set up some variables which are usually set up in parse() so that an external function can call some ...
Definition: Parser.php:4651
wfDebugLog($logGroup, $text, $dest= 'all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
const NO_TEMPLATES
addTrackingCategory($msg)
Definition: Parser.php:4010
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2077
$mVarCache
Definition: Parser.php:149
$wgStylePath
The URL path of the skins directory.
disableCache()
Set a flag in the output object indicating that the content is dynamic and shouldn't be cached...
Definition: Parser.php:5388
$mRevisionObject
Definition: Parser.php:217
static normalizeSectionNameWhitespace($section)
Normalizes whitespace in a section name, such as might be returned by Parser::stripSectionName(), for use in the id's that are used for section links.
Definition: Sanitizer.php:1523
internalParse($text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1258
Title $mTitle
Definition: Parser.php:214
static delimiterReplace($startDelim, $endDelim, $replace, $subject, $flags= '')
Perform an operation equivalent to preg_replace() with flags.
__destruct()
Reduce memory usage to reduce the impact of circular references.
Definition: Parser.php:289
wfEscapeWikiText($text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition: Parser.php:5674
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6114
static register($parser)
$mRevIdForTs
Definition: Parser.php:222
static singleton()
Get an instance of this class.
Definition: LinkCache.php:67
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
static normalizeSubpageLink($contextTitle, $target, &$text)
Definition: Linker.php:1369
$mStripList
Definition: Parser.php:147
$mFunctionTagHooks
Definition: Parser.php:146
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3736
const OT_PLAIN
Definition: Defines.php:189
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3495
$mRevisionTimestamp
Definition: Parser.php:219
$mImageParams
Definition: Parser.php:150
stripAltText($caption, $holders)
Definition: Parser.php:5364
makeLimitReport()
Set the limit report data in the current ParserOutput, and return the limit report HTML comment...
Definition: Parser.php:511
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1628
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 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
static replaceMarkup($search, $replace, $text)
More or less "markup-safe" str_replace() Ignores any instances of the separator inside <...
static normalizeUrlComponent($component, $unsafe)
Definition: Parser.php:1995
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:1262
const VERSION
Update this version number when the ParserOutput format changes in an incompatible way...
Definition: Parser.php:76
const OT_WIKI
Definition: Defines.php:186
Preprocessor $mPreprocessor
Definition: Parser.php:171
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:916
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
static getInstance($ts=false)
Get a timestamp instance in GMT.
Definition: MWTimestamp.php:39
const NS_MEDIA
Definition: Defines.php:53
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
static factory($url, array $options=null, $caller=__METHOD__)
Generate a new request object Deprecated:
replaceVariables($text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2906
const RECOVER_ORIG
wfMatchesDomainList($url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
StripState $mStripState
Definition: Parser.php:183
static setupOOUI($skinName= 'default', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
$mDefaultSort
Definition: Parser.php:193
getUser()
Get a User object either from $this->mUser, if set, or from the ParserOptions object otherwise...
Definition: Parser.php:904
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3930
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1028
const EXT_IMAGE_REGEX
Definition: Parser.php:100
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4663
$params
const NS_CATEGORY
Definition: Defines.php:79
static makeHeadline($level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1642
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:1980
$wgFragmentMode
How should section IDs be encoded? This array can contain 1 or 2 elements, each of them can be one of...
static normalizeSectionName($text)
Apply the same normalization as code making links to this section would.
Definition: Parser.php:5841
setHook($tag, callable $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4726
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
doTableStuff($text)
parse the wiki syntax used to render tables
Definition: Parser.php:1055
static guessSectionNameFromStrippedText($text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
Definition: Parser.php:5830
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:5717
$mImageParamsMagicArray
Definition: Parser.php:151
LinkHolderArray $mLinkHolders
Definition: Parser.php:189
static register($parser)
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:1980
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
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 and or sell copies of the and to permit persons to whom the Software is furnished to do so
Definition: LICENSE.txt:10
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
preSaveTransform($text, Title $title, User $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
Definition: Parser.php:4422
static capturePath(Title $title, IContextSource $context, LinkRenderer $linkRenderer=null)
Just like executePath() but will override global variables and execute the page in "inclusion" mode...
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:876
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:935
static hasSubpages($index)
Does the namespace allow subpages?
formatHeadings($text, $origText, $isMain=true)
This function accomplishes several tasks: 1) Auto-number headings if that option is enabled 2) Add an...
Definition: Parser.php:4030
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:894
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1530
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
static tocLine($anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1545
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$mInputSize
Definition: Parser.php:223
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
Definition: magicword.txt:4
getUserSig(&$user, $nickname=false, $fancySig=null)
Fetch the user's signature text, if any, and normalize to validated, ready-to-insert wikitext...
Definition: Parser.php:4537
const HALF_PARSED_VERSION
Update this version number when the output of serialiseHalfParsedText() changes in an incompatible wa...
Definition: Parser.php:82
const NS_FILE
Definition: Defines.php:71
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:324
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:324
const PTD_FOR_INCLUSION
Definition: Parser.php:107
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:1980
armorLinks($text)
Insert a NOPARSE hacky thing into any inline links in a chunk that's going to go through further pars...
Definition: Parser.php:2424
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:1760
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2873
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
setOutputType($ot)
Set the output type.
Definition: Parser.php:796
$mTagHooks
Definition: Parser.php:142
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:73
static & get($id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:277
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6150
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3557
maybeMakeExternalImage($url)
make an image if it's allowed, either through the global option, through the exception, or through the on-wiki whitelist
Definition: Parser.php:2018
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2433
const OT_HTML
Definition: Defines.php:185
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:304
static images()
Definition: SiteStats.php:139
$mTransparentTagHooks
Definition: Parser.php:143
$mExpensiveFunctionCount
Definition: Parser.php:195
$mUrlProtocols
Definition: Parser.php:167
$mConf
Definition: Parser.php:167
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4682
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:115
wfUrlProtocols($includeProtocolRelative=true)
Returns a regular expression of url protocols.
static makeExternalLink($url, $text, $escape=true, $linktype= '', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:843
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:301
static getExternalLinkRel($url=false, $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:1908
getUser()
Current user.
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
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...
this hook is for auditing only $req
Definition: hooks.txt:991
static escapeIdForAttribute($id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
Definition: Sanitizer.php:1260
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:784
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:1760
array $mLangLinkLanguages
Array with the language name of each language link (i.e.
Definition: Parser.php:236
const OT_MSG
Definition: Parser.php:114
replaceTransparentTags($text)
Replace transparent tags in $text with the values given by the callbacks.
Definition: Parser.php:5434
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
replaceSection($oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
Definition: Parser.php:5626
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3957
$mFunctionHooks
Definition: Parser.php:144
static removeHTMLtags($text, $processCallback=null, $args=[], $extratags=[], $removetags=[], $warnCallback=null)
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
Definition: Sanitizer.php:477
$lines
Definition: router.php:67
testPreprocess($text, Title $title, ParserOptions $options)
Definition: Parser.php:5924
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
MagicWordArray $mSubstWords
Definition: Parser.php:165
const TOC_END
Definition: Parser.php:139
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1542
setFunctionHook($id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4820
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
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3358
$wgScriptPath
The path we should point to.
Variant of the Message class.
Definition: RawMessage.php:34
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6135
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database etc For and for historical it also represents a few features of articles that don t involve their such as access rights See also title txt Article Encapsulates access to the page table of the database The object represents a an and maintains state such as etc Revision Encapsulates individual page revision data and access to the revision text blobs storage system Higher level code should never touch text storage directly
Definition: design.txt:34
static articles()
Definition: SiteStats.php:103
$mRevisionUser
Definition: Parser.php:220
lock()
Lock the current instance of the parser.
Definition: Parser.php:6086
static pages()
Definition: SiteStats.php:112
$line
Definition: cdb.php:59
const SFH_OBJECT_ARGS
Definition: Parser.php:86
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:68
makeKnownLinkHolder($nt, $text= '', $trail= '', $prefix= '')
Render a forced-blue link inline; protect against double expansion of URLs if we're in a mode that pr...
Definition: Parser.php:2400
static escapeIdForLink($id)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid URL...
Definition: Sanitizer.php:1287
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3570
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
Definition: design.txt:79
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php:783
$mIncludeCount
Definition: Parser.php:185
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:3005
$mMarkerIndex
Definition: Parser.php:152
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:2597
getTitle()
Accessor for the Title object.
Definition: Parser.php:777
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition: design.txt:56
extractSections($text, $sectionId, $mode, $newText= '')
Break wikitext input into sections, and either pull or replace some particular section's text...
Definition: Parser.php:5485
ParserOutput $mOutput
Definition: Parser.php:177
getOutput()
Get the ParserOutput object.
Definition: Parser.php:822
doMagicLinks($text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
Definition: Parser.php:1434
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
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
static cleanSigInSig($text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4637
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5738
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3662
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1519
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:623
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. '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). '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:1248
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all.It could be easily changed to send incrementally if that becomes useful
static doBlockLevels($text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
$wgServer
URL of the server.
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:84
incrementExpensiveFunctionCount()
Increment the expensive function count.
Definition: Parser.php:3944
getDisableTitleConversion()
Whether title conversion should be disabled.
$mShowToc
Definition: Parser.php:196
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1959
const DB_REPLICA
Definition: defines.php:25
magicLinkCallback($m)
Definition: Parser.php:1465
const EXT_LINK_URL_CLASS
Definition: Parser.php:94
insertStripItem($text)
Add an item to the strip state Returns the unique tag which must be inserted into the stripped text T...
Definition: Parser.php:1041