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  // @codingStandardsIgnoreStart 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  // @codingStandardsIgnoreEnd
103 
104  # Regular expression for a non-newline space
105  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
106 
107  # Flags for preprocessToDom
108  const PTD_FOR_INCLUSION = 1;
109 
110  # Allowed values for $this->mOutputType
111  # Parameter to startExternalParse().
112  const OT_HTML = 1; # like parse()
113  const OT_WIKI = 2; # like preSaveTransform()
115  const OT_MSG = 3;
116  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
117 
135  const MARKER_SUFFIX = "-QINU`\"'\x7f";
136  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
137 
138  # Markers used for wrapping the table of contents
139  const TOC_START = '<mw:toc>';
140  const TOC_END = '</mw:toc>';
141 
142  # Persistent:
143  public $mTagHooks = [];
145  public $mFunctionHooks = [];
146  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
147  public $mFunctionTagHooks = [];
148  public $mStripList = [];
149  public $mDefaultStripList = [];
150  public $mVarCache = [];
151  public $mImageParams = [];
153  public $mMarkerIndex = 0;
154  public $mFirstCall = true;
155 
156  # Initialised by initialiseVariables()
157 
161  public $mVariables;
162 
166  public $mSubstWords;
167  # Initialised in constructor
169 
170  # Initialized in getPreprocessor()
171 
173 
174  # Cleared with clearState():
175 
178  public $mOutput;
179  public $mAutonumber;
180 
184  public $mStripState;
185 
191 
192  public $mLinkID;
196  public $mExpensiveFunctionCount; # number of expensive parser function calls
198 
202  public $mUser; # User object; only used when doing pre-save transform
203 
204  # Temporary
205  # These are variables reset at least once per parse regardless of $clearState
206 
210  public $mOptions;
211 
215  public $mTitle; # Title context, used for self-link rendering and similar things
216  public $mOutputType; # Output type, one of the OT_xxx constants
217  public $ot; # Shortcut alias, see setOutputType()
218  public $mRevisionObject; # The revision object of the specified revision ID
219  public $mRevisionId; # ID to display in {{REVISIONID}} tags
220  public $mRevisionTimestamp; # The timestamp of the specified revision ID
221  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
222  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
223  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
224  public $mInputSize = false; # For {{PAGESIZE}} on current page.
225 
230  public $mUniqPrefix = Parser::MARKER_PREFIX;
231 
238 
246 
251  public $mInParse = false;
252 
254  protected $mProfiler;
255 
259  protected $mLinkRenderer;
260 
264  public function __construct( $conf = [] ) {
265  $this->mConf = $conf;
266  $this->mUrlProtocols = wfUrlProtocols();
267  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
268  self::EXT_LINK_ADDR .
269  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
270  if ( isset( $conf['preprocessorClass'] ) ) {
271  $this->mPreprocessorClass = $conf['preprocessorClass'];
272  } elseif ( defined( 'HPHP_VERSION' ) ) {
273  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
274  $this->mPreprocessorClass = 'Preprocessor_Hash';
275  } elseif ( extension_loaded( 'domxml' ) ) {
276  # PECL extension that conflicts with the core DOM extension (T15770)
277  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
278  $this->mPreprocessorClass = 'Preprocessor_Hash';
279  } elseif ( extension_loaded( 'dom' ) ) {
280  $this->mPreprocessorClass = 'Preprocessor_DOM';
281  } else {
282  $this->mPreprocessorClass = 'Preprocessor_Hash';
283  }
284  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
285  }
286 
290  public function __destruct() {
291  if ( isset( $this->mLinkHolders ) ) {
292  unset( $this->mLinkHolders );
293  }
294  foreach ( $this as $name => $value ) {
295  unset( $this->$name );
296  }
297  }
298 
302  public function __clone() {
303  $this->mInParse = false;
304 
305  // T58226: When you create a reference "to" an object field, that
306  // makes the object field itself be a reference too (until the other
307  // reference goes out of scope). When cloning, any field that's a
308  // reference is copied as a reference in the new object. Both of these
309  // are defined PHP5 behaviors, as inconvenient as it is for us when old
310  // hooks from PHP4 days are passing fields by reference.
311  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
312  // Make a non-reference copy of the field, then rebind the field to
313  // reference the new copy.
314  $tmp = $this->$k;
315  $this->$k =& $tmp;
316  unset( $tmp );
317  }
318 
319  Hooks::run( 'ParserCloned', [ $this ] );
320  }
321 
325  public function firstCallInit() {
326  if ( !$this->mFirstCall ) {
327  return;
328  }
329  $this->mFirstCall = false;
330 
332  CoreTagHooks::register( $this );
333  $this->initialiseVariables();
334 
335  // Avoid PHP 7.1 warning from passing $this by reference
336  $parser = $this;
337  Hooks::run( 'ParserFirstCallInit', [ &$parser ] );
338  }
339 
345  public function clearState() {
346  if ( $this->mFirstCall ) {
347  $this->firstCallInit();
348  }
349  $this->mOutput = new ParserOutput;
350  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
351  $this->mAutonumber = 0;
352  $this->mIncludeCount = [];
353  $this->mLinkHolders = new LinkHolderArray( $this );
354  $this->mLinkID = 0;
355  $this->mRevisionObject = $this->mRevisionTimestamp =
356  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
357  $this->mVarCache = [];
358  $this->mUser = null;
359  $this->mLangLinkLanguages = [];
360  $this->currentRevisionCache = null;
361 
362  $this->mStripState = new StripState;
363 
364  # Clear these on every parse, T6549
365  $this->mTplRedirCache = $this->mTplDomCache = [];
366 
367  $this->mShowToc = true;
368  $this->mForceTocPosition = false;
369  $this->mIncludeSizes = [
370  'post-expand' => 0,
371  'arg' => 0,
372  ];
373  $this->mPPNodeCount = 0;
374  $this->mGeneratedPPNodeCount = 0;
375  $this->mHighestExpansionDepth = 0;
376  $this->mDefaultSort = false;
377  $this->mHeadings = [];
378  $this->mDoubleUnderscores = [];
379  $this->mExpensiveFunctionCount = 0;
380 
381  # Fix cloning
382  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
383  $this->mPreprocessor = null;
384  }
385 
386  $this->mProfiler = new SectionProfiler();
387 
388  // Avoid PHP 7.1 warning from passing $this by reference
389  $parser = $this;
390  Hooks::run( 'ParserClearState', [ &$parser ] );
391  }
392 
405  public function parse(
407  $linestart = true, $clearState = true, $revid = null
408  ) {
414  global $wgShowHostnames;
415 
416  if ( $clearState ) {
417  // We use U+007F DELETE to construct strip markers, so we have to make
418  // sure that this character does not occur in the input text.
419  $text = strtr( $text, "\x7f", "?" );
420  $magicScopeVariable = $this->lock();
421  }
422  // Strip U+0000 NULL (T159174)
423  $text = str_replace( "\000", '', $text );
424 
425  $this->startParse( $title, $options, self::OT_HTML, $clearState );
426 
427  $this->currentRevisionCache = null;
428  $this->mInputSize = strlen( $text );
429  if ( $this->mOptions->getEnableLimitReport() ) {
430  $this->mOutput->resetParseStartTime();
431  }
432 
433  $oldRevisionId = $this->mRevisionId;
434  $oldRevisionObject = $this->mRevisionObject;
435  $oldRevisionTimestamp = $this->mRevisionTimestamp;
436  $oldRevisionUser = $this->mRevisionUser;
437  $oldRevisionSize = $this->mRevisionSize;
438  if ( $revid !== null ) {
439  $this->mRevisionId = $revid;
440  $this->mRevisionObject = null;
441  $this->mRevisionTimestamp = null;
442  $this->mRevisionUser = null;
443  $this->mRevisionSize = null;
444  }
445 
446  // Avoid PHP 7.1 warning from passing $this by reference
447  $parser = $this;
448  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
449  # No more strip!
450  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
451  $text = $this->internalParse( $text );
452  Hooks::run( 'ParserAfterParse', [ &$parser, &$text, &$this->mStripState ] );
453 
454  $text = $this->internalParseHalfParsed( $text, true, $linestart );
455 
463  if ( !( $options->getDisableTitleConversion()
464  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
465  || isset( $this->mDoubleUnderscores['notitleconvert'] )
466  || $this->mOutput->getDisplayTitle() !== false )
467  ) {
468  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
469  if ( $convruletitle ) {
470  $this->mOutput->setTitleText( $convruletitle );
471  } else {
472  $titleText = $this->getConverterLanguage()->convertTitle( $title );
473  $this->mOutput->setTitleText( $titleText );
474  }
475  }
476 
477  # Done parsing! Compute runtime adaptive expiry if set
478  $this->mOutput->finalizeAdaptiveCacheExpiry();
479 
480  # Warn if too many heavyweight parser functions were used
481  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
482  $this->limitationWarn( 'expensive-parserfunction',
483  $this->mExpensiveFunctionCount,
484  $this->mOptions->getExpensiveParserFunctionLimit()
485  );
486  }
487 
488  # Information on include size limits, for the benefit of users who try to skirt them
489  if ( $this->mOptions->getEnableLimitReport() ) {
490  $max = $this->mOptions->getMaxIncludeSize();
491 
492  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
493  if ( $cpuTime !== null ) {
494  $this->mOutput->setLimitReportData( 'limitreport-cputime',
495  sprintf( "%.3f", $cpuTime )
496  );
497  }
498 
499  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
500  $this->mOutput->setLimitReportData( 'limitreport-walltime',
501  sprintf( "%.3f", $wallTime )
502  );
503 
504  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
505  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
506  );
507  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
508  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
509  );
510  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
511  [ $this->mIncludeSizes['post-expand'], $max ]
512  );
513  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
514  [ $this->mIncludeSizes['arg'], $max ]
515  );
516  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
517  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
518  );
519  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
520  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
521  );
522  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
523 
524  $limitReport = "NewPP limit report\n";
525  if ( $wgShowHostnames ) {
526  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
527  }
528  $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
529  $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
530  $limitReport .= 'Dynamic content: ' .
531  ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
532  "\n";
533 
534  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
535  if ( Hooks::run( 'ParserLimitReportFormat',
536  [ $key, &$value, &$limitReport, false, false ]
537  ) ) {
538  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
539  $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
540  ->inLanguage( 'en' )->useDatabase( false );
541  if ( !$valueMsg->exists() ) {
542  $valueMsg = new RawMessage( '$1' );
543  }
544  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
545  $valueMsg->params( $value );
546  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
547  }
548  }
549  }
550  // Since we're not really outputting HTML, decode the entities and
551  // then re-encode the things that need hiding inside HTML comments.
552  $limitReport = htmlspecialchars_decode( $limitReport );
553  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
554 
555  // Sanitize for comment. Note '‐' in the replacement is U+2010,
556  // which looks much like the problematic '-'.
557  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
558  $text .= "\n<!-- \n$limitReport-->\n";
559 
560  // Add on template profiling data in human/machine readable way
561  $dataByFunc = $this->mProfiler->getFunctionStats();
562  uasort( $dataByFunc, function ( $a, $b ) {
563  return $a['real'] < $b['real']; // descending order
564  } );
565  $profileReport = [];
566  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
567  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
568  $item['%real'], $item['real'], $item['calls'],
569  htmlspecialchars( $item['name'] ) );
570  }
571  $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
572  $text .= implode( "\n", $profileReport ) . "\n-->\n";
573 
574  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
575 
576  // Add other cache related metadata
577  if ( $wgShowHostnames ) {
578  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
579  }
580  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
581  $this->mOutput->getCacheTime() );
582  $this->mOutput->setLimitReportData( 'cachereport-ttl',
583  $this->mOutput->getCacheExpiry() );
584  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
585  $this->mOutput->hasDynamicContent() );
586 
587  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
588  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
589  $this->mTitle->getPrefixedDBkey() );
590  }
591  }
592  $this->mOutput->setText( $text );
593 
594  $this->mRevisionId = $oldRevisionId;
595  $this->mRevisionObject = $oldRevisionObject;
596  $this->mRevisionTimestamp = $oldRevisionTimestamp;
597  $this->mRevisionUser = $oldRevisionUser;
598  $this->mRevisionSize = $oldRevisionSize;
599  $this->mInputSize = false;
600  $this->currentRevisionCache = null;
601 
602  return $this->mOutput;
603  }
604 
627  public function recursiveTagParse( $text, $frame = false ) {
628  // Avoid PHP 7.1 warning from passing $this by reference
629  $parser = $this;
630  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
631  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
632  $text = $this->internalParse( $text, false, $frame );
633  return $text;
634  }
635 
653  public function recursiveTagParseFully( $text, $frame = false ) {
654  $text = $this->recursiveTagParse( $text, $frame );
655  $text = $this->internalParseHalfParsed( $text, false );
656  return $text;
657  }
658 
670  public function preprocess( $text, Title $title = null,
671  ParserOptions $options, $revid = null, $frame = false
672  ) {
673  $magicScopeVariable = $this->lock();
674  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
675  if ( $revid !== null ) {
676  $this->mRevisionId = $revid;
677  }
678  // Avoid PHP 7.1 warning from passing $this by reference
679  $parser = $this;
680  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
681  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
682  $text = $this->replaceVariables( $text, $frame );
683  $text = $this->mStripState->unstripBoth( $text );
684  return $text;
685  }
686 
696  public function recursivePreprocess( $text, $frame = false ) {
697  $text = $this->replaceVariables( $text, $frame );
698  $text = $this->mStripState->unstripBoth( $text );
699  return $text;
700  }
701 
715  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
716  $msg = new RawMessage( $text );
717  $text = $msg->params( $params )->plain();
718 
719  # Parser (re)initialisation
720  $magicScopeVariable = $this->lock();
721  $this->startParse( $title, $options, self::OT_PLAIN, true );
722 
724  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
725  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
726  $text = $this->mStripState->unstripBoth( $text );
727  return $text;
728  }
729 
736  public static function getRandomString() {
737  wfDeprecated( __METHOD__, '1.26' );
738  return wfRandomString( 16 );
739  }
740 
747  public function setUser( $user ) {
748  $this->mUser = $user;
749  }
750 
757  public function uniqPrefix() {
758  wfDeprecated( __METHOD__, '1.26' );
759  return self::MARKER_PREFIX;
760  }
761 
767  public function setTitle( $t ) {
768  if ( !$t ) {
769  $t = Title::newFromText( 'NO TITLE' );
770  }
771 
772  if ( $t->hasFragment() ) {
773  # Strip the fragment to avoid various odd effects
774  $this->mTitle = $t->createFragmentTarget( '' );
775  } else {
776  $this->mTitle = $t;
777  }
778  }
779 
785  public function getTitle() {
786  return $this->mTitle;
787  }
788 
795  public function Title( $x = null ) {
796  return wfSetVar( $this->mTitle, $x );
797  }
798 
804  public function setOutputType( $ot ) {
805  $this->mOutputType = $ot;
806  # Shortcut alias
807  $this->ot = [
808  'html' => $ot == self::OT_HTML,
809  'wiki' => $ot == self::OT_WIKI,
810  'pre' => $ot == self::OT_PREPROCESS,
811  'plain' => $ot == self::OT_PLAIN,
812  ];
813  }
814 
821  public function OutputType( $x = null ) {
822  return wfSetVar( $this->mOutputType, $x );
823  }
824 
830  public function getOutput() {
831  return $this->mOutput;
832  }
833 
839  public function getOptions() {
840  return $this->mOptions;
841  }
842 
849  public function Options( $x = null ) {
850  return wfSetVar( $this->mOptions, $x );
851  }
852 
856  public function nextLinkID() {
857  return $this->mLinkID++;
858  }
859 
863  public function setLinkID( $id ) {
864  $this->mLinkID = $id;
865  }
866 
871  public function getFunctionLang() {
872  return $this->getTargetLanguage();
873  }
874 
884  public function getTargetLanguage() {
885  $target = $this->mOptions->getTargetLanguage();
886 
887  if ( $target !== null ) {
888  return $target;
889  } elseif ( $this->mOptions->getInterfaceMessage() ) {
890  return $this->mOptions->getUserLangObj();
891  } elseif ( is_null( $this->mTitle ) ) {
892  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
893  }
894 
895  return $this->mTitle->getPageLanguage();
896  }
897 
902  public function getConverterLanguage() {
903  return $this->getTargetLanguage();
904  }
905 
912  public function getUser() {
913  if ( !is_null( $this->mUser ) ) {
914  return $this->mUser;
915  }
916  return $this->mOptions->getUser();
917  }
918 
924  public function getPreprocessor() {
925  if ( !isset( $this->mPreprocessor ) ) {
926  $class = $this->mPreprocessorClass;
927  $this->mPreprocessor = new $class( $this );
928  }
929  return $this->mPreprocessor;
930  }
931 
938  public function getLinkRenderer() {
939  if ( !$this->mLinkRenderer ) {
940  $this->mLinkRenderer = MediaWikiServices::getInstance()
941  ->getLinkRendererFactory()->create();
942  $this->mLinkRenderer->setStubThreshold(
943  $this->getOptions()->getStubThreshold()
944  );
945  }
946 
947  return $this->mLinkRenderer;
948  }
949 
971  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
972  if ( $uniq_prefix !== null ) {
973  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
974  }
975  static $n = 1;
976  $stripped = '';
977  $matches = [];
978 
979  $taglist = implode( '|', $elements );
980  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
981 
982  while ( $text != '' ) {
983  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
984  $stripped .= $p[0];
985  if ( count( $p ) < 5 ) {
986  break;
987  }
988  if ( count( $p ) > 5 ) {
989  # comment
990  $element = $p[4];
991  $attributes = '';
992  $close = '';
993  $inside = $p[5];
994  } else {
995  # tag
996  $element = $p[1];
997  $attributes = $p[2];
998  $close = $p[3];
999  $inside = $p[4];
1000  }
1001 
1002  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1003  $stripped .= $marker;
1004 
1005  if ( $close === '/>' ) {
1006  # Empty element tag, <tag />
1007  $content = null;
1008  $text = $inside;
1009  $tail = null;
1010  } else {
1011  if ( $element === '!--' ) {
1012  $end = '/(-->)/';
1013  } else {
1014  $end = "/(<\\/$element\\s*>)/i";
1015  }
1016  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1017  $content = $q[0];
1018  if ( count( $q ) < 3 ) {
1019  # No end tag -- let it run out to the end of the text.
1020  $tail = '';
1021  $text = '';
1022  } else {
1023  $tail = $q[1];
1024  $text = $q[2];
1025  }
1026  }
1027 
1028  $matches[$marker] = [ $element,
1029  $content,
1030  Sanitizer::decodeTagAttributes( $attributes ),
1031  "<$element$attributes$close$content$tail" ];
1032  }
1033  return $stripped;
1034  }
1035 
1041  public function getStripList() {
1042  return $this->mStripList;
1043  }
1044 
1054  public function insertStripItem( $text ) {
1055  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1056  $this->mMarkerIndex++;
1057  $this->mStripState->addGeneral( $marker, $text );
1058  return $marker;
1059  }
1060 
1068  public function doTableStuff( $text ) {
1069 
1070  $lines = StringUtils::explode( "\n", $text );
1071  $out = '';
1072  $td_history = []; # Is currently a td tag open?
1073  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1074  $tr_history = []; # Is currently a tr tag open?
1075  $tr_attributes = []; # history of tr attributes
1076  $has_opened_tr = []; # Did this table open a <tr> element?
1077  $indent_level = 0; # indent level of the table
1078 
1079  foreach ( $lines as $outLine ) {
1080  $line = trim( $outLine );
1081 
1082  if ( $line === '' ) { # empty line, go to next line
1083  $out .= $outLine . "\n";
1084  continue;
1085  }
1086 
1087  $first_character = $line[0];
1088  $first_two = substr( $line, 0, 2 );
1089  $matches = [];
1090 
1091  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1092  # First check if we are starting a new table
1093  $indent_level = strlen( $matches[1] );
1094 
1095  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1096  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1097 
1098  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1099  array_push( $td_history, false );
1100  array_push( $last_tag_history, '' );
1101  array_push( $tr_history, false );
1102  array_push( $tr_attributes, '' );
1103  array_push( $has_opened_tr, false );
1104  } elseif ( count( $td_history ) == 0 ) {
1105  # Don't do any of the following
1106  $out .= $outLine . "\n";
1107  continue;
1108  } elseif ( $first_two === '|}' ) {
1109  # We are ending a table
1110  $line = '</table>' . substr( $line, 2 );
1111  $last_tag = array_pop( $last_tag_history );
1112 
1113  if ( !array_pop( $has_opened_tr ) ) {
1114  $line = "<tr><td></td></tr>{$line}";
1115  }
1116 
1117  if ( array_pop( $tr_history ) ) {
1118  $line = "</tr>{$line}";
1119  }
1120 
1121  if ( array_pop( $td_history ) ) {
1122  $line = "</{$last_tag}>{$line}";
1123  }
1124  array_pop( $tr_attributes );
1125  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1126  } elseif ( $first_two === '|-' ) {
1127  # Now we have a table row
1128  $line = preg_replace( '#^\|-+#', '', $line );
1129 
1130  # Whats after the tag is now only attributes
1131  $attributes = $this->mStripState->unstripBoth( $line );
1132  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1133  array_pop( $tr_attributes );
1134  array_push( $tr_attributes, $attributes );
1135 
1136  $line = '';
1137  $last_tag = array_pop( $last_tag_history );
1138  array_pop( $has_opened_tr );
1139  array_push( $has_opened_tr, true );
1140 
1141  if ( array_pop( $tr_history ) ) {
1142  $line = '</tr>';
1143  }
1144 
1145  if ( array_pop( $td_history ) ) {
1146  $line = "</{$last_tag}>{$line}";
1147  }
1148 
1149  $outLine = $line;
1150  array_push( $tr_history, false );
1151  array_push( $td_history, false );
1152  array_push( $last_tag_history, '' );
1153  } elseif ( $first_character === '|'
1154  || $first_character === '!'
1155  || $first_two === '|+'
1156  ) {
1157  # This might be cell elements, td, th or captions
1158  if ( $first_two === '|+' ) {
1159  $first_character = '+';
1160  $line = substr( $line, 2 );
1161  } else {
1162  $line = substr( $line, 1 );
1163  }
1164 
1165  // Implies both are valid for table headings.
1166  if ( $first_character === '!' ) {
1167  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1168  }
1169 
1170  # Split up multiple cells on the same line.
1171  # FIXME : This can result in improper nesting of tags processed
1172  # by earlier parser steps.
1173  $cells = explode( '||', $line );
1174 
1175  $outLine = '';
1176 
1177  # Loop through each table cell
1178  foreach ( $cells as $cell ) {
1179  $previous = '';
1180  if ( $first_character !== '+' ) {
1181  $tr_after = array_pop( $tr_attributes );
1182  if ( !array_pop( $tr_history ) ) {
1183  $previous = "<tr{$tr_after}>\n";
1184  }
1185  array_push( $tr_history, true );
1186  array_push( $tr_attributes, '' );
1187  array_pop( $has_opened_tr );
1188  array_push( $has_opened_tr, true );
1189  }
1190 
1191  $last_tag = array_pop( $last_tag_history );
1192 
1193  if ( array_pop( $td_history ) ) {
1194  $previous = "</{$last_tag}>\n{$previous}";
1195  }
1196 
1197  if ( $first_character === '|' ) {
1198  $last_tag = 'td';
1199  } elseif ( $first_character === '!' ) {
1200  $last_tag = 'th';
1201  } elseif ( $first_character === '+' ) {
1202  $last_tag = 'caption';
1203  } else {
1204  $last_tag = '';
1205  }
1206 
1207  array_push( $last_tag_history, $last_tag );
1208 
1209  # A cell could contain both parameters and data
1210  $cell_data = explode( '|', $cell, 2 );
1211 
1212  # T2553: Note that a '|' inside an invalid link should not
1213  # be mistaken as delimiting cell parameters
1214  # Bug T153140: Neither should language converter markup.
1215  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1216  $cell = "{$previous}<{$last_tag}>{$cell}";
1217  } elseif ( count( $cell_data ) == 1 ) {
1218  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1219  } else {
1220  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1221  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1222  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1223  }
1224 
1225  $outLine .= $cell;
1226  array_push( $td_history, true );
1227  }
1228  }
1229  $out .= $outLine . "\n";
1230  }
1231 
1232  # Closing open td, tr && table
1233  while ( count( $td_history ) > 0 ) {
1234  if ( array_pop( $td_history ) ) {
1235  $out .= "</td>\n";
1236  }
1237  if ( array_pop( $tr_history ) ) {
1238  $out .= "</tr>\n";
1239  }
1240  if ( !array_pop( $has_opened_tr ) ) {
1241  $out .= "<tr><td></td></tr>\n";
1242  }
1243 
1244  $out .= "</table>\n";
1245  }
1246 
1247  # Remove trailing line-ending (b/c)
1248  if ( substr( $out, -1 ) === "\n" ) {
1249  $out = substr( $out, 0, -1 );
1250  }
1251 
1252  # special case: don't return empty table
1253  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1254  $out = '';
1255  }
1256 
1257  return $out;
1258  }
1259 
1272  public function internalParse( $text, $isMain = true, $frame = false ) {
1273 
1274  $origText = $text;
1275 
1276  // Avoid PHP 7.1 warning from passing $this by reference
1277  $parser = $this;
1278 
1279  # Hook to suspend the parser in this state
1280  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1281  return $text;
1282  }
1283 
1284  # if $frame is provided, then use $frame for replacing any variables
1285  if ( $frame ) {
1286  # use frame depth to infer how include/noinclude tags should be handled
1287  # depth=0 means this is the top-level document; otherwise it's an included document
1288  if ( !$frame->depth ) {
1289  $flag = 0;
1290  } else {
1291  $flag = Parser::PTD_FOR_INCLUSION;
1292  }
1293  $dom = $this->preprocessToDom( $text, $flag );
1294  $text = $frame->expand( $dom );
1295  } else {
1296  # if $frame is not provided, then use old-style replaceVariables
1297  $text = $this->replaceVariables( $text );
1298  }
1299 
1300  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1301  $text = Sanitizer::removeHTMLtags(
1302  $text,
1303  [ $this, 'attributeStripCallback' ],
1304  false,
1305  array_keys( $this->mTransparentTagHooks ),
1306  [],
1307  [ $this, 'addTrackingCategory' ]
1308  );
1309  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1310 
1311  # Tables need to come after variable replacement for things to work
1312  # properly; putting them before other transformations should keep
1313  # exciting things like link expansions from showing up in surprising
1314  # places.
1315  $text = $this->doTableStuff( $text );
1316 
1317  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1318 
1319  $text = $this->doDoubleUnderscore( $text );
1320 
1321  $text = $this->doHeadings( $text );
1322  $text = $this->replaceInternalLinks( $text );
1323  $text = $this->doAllQuotes( $text );
1324  $text = $this->replaceExternalLinks( $text );
1325 
1326  # replaceInternalLinks may sometimes leave behind
1327  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1328  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1329 
1330  $text = $this->doMagicLinks( $text );
1331  $text = $this->formatHeadings( $text, $origText, $isMain );
1332 
1333  return $text;
1334  }
1335 
1345  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1346  $text = $this->mStripState->unstripGeneral( $text );
1347 
1348  // Avoid PHP 7.1 warning from passing $this by reference
1349  $parser = $this;
1350 
1351  if ( $isMain ) {
1352  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1353  }
1354 
1355  # Clean up special characters, only run once, next-to-last before doBlockLevels
1356  $fixtags = [
1357  # French spaces, last one Guillemet-left
1358  # only if there is something before the space
1359  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1360  # french spaces, Guillemet-right
1361  '/(\\302\\253) /' => '\\1&#160;',
1362  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1363  ];
1364  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1365 
1366  $text = $this->doBlockLevels( $text, $linestart );
1367 
1368  $this->replaceLinkHolders( $text );
1369 
1377  if ( !( $this->mOptions->getDisableContentConversion()
1378  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1379  ) {
1380  if ( !$this->mOptions->getInterfaceMessage() ) {
1381  # The position of the convert() call should not be changed. it
1382  # assumes that the links are all replaced and the only thing left
1383  # is the <nowiki> mark.
1384  $text = $this->getConverterLanguage()->convert( $text );
1385  }
1386  }
1387 
1388  $text = $this->mStripState->unstripNoWiki( $text );
1389 
1390  if ( $isMain ) {
1391  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1392  }
1393 
1394  $text = $this->replaceTransparentTags( $text );
1395  $text = $this->mStripState->unstripGeneral( $text );
1396 
1397  $text = Sanitizer::normalizeCharReferences( $text );
1398 
1399  if ( MWTidy::isEnabled() ) {
1400  if ( $this->mOptions->getTidy() ) {
1401  $text = MWTidy::tidy( $text );
1402  }
1403  } else {
1404  # attempt to sanitize at least some nesting problems
1405  # (T4702 and quite a few others)
1406  $tidyregs = [
1407  # ''Something [http://www.cool.com cool''] -->
1408  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1409  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1410  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1411  # fix up an anchor inside another anchor, only
1412  # at least for a single single nested link (T5695)
1413  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1414  '\\1\\2</a>\\3</a>\\1\\4</a>',
1415  # fix div inside inline elements- doBlockLevels won't wrap a line which
1416  # contains a div, so fix it up here; replace
1417  # div with escaped text
1418  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1419  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1420  # remove empty italic or bold tag pairs, some
1421  # introduced by rules above
1422  '/<([bi])><\/\\1>/' => '',
1423  ];
1424 
1425  $text = preg_replace(
1426  array_keys( $tidyregs ),
1427  array_values( $tidyregs ),
1428  $text );
1429  }
1430 
1431  if ( $isMain ) {
1432  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1433  }
1434 
1435  return $text;
1436  }
1437 
1449  public function doMagicLinks( $text ) {
1450  $prots = wfUrlProtocolsWithoutProtRel();
1451  $urlChar = self::EXT_LINK_URL_CLASS;
1452  $addr = self::EXT_LINK_ADDR;
1453  $space = self::SPACE_NOT_NL; # non-newline space
1454  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1455  $spaces = "$space++"; # possessive match of 1 or more spaces
1456  $text = preg_replace_callback(
1457  '!(?: # Start cases
1458  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1459  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1460  (\b # m[3]: Free external links
1461  (?i:$prots)
1462  ($addr$urlChar*) # m[4]: Post-protocol path
1463  ) |
1464  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1465  ([0-9]+)\b |
1466  \bISBN $spaces ( # m[6]: ISBN, capture number
1467  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1468  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1469  [0-9Xx] # check digit
1470  )\b
1471  )!xu", [ $this, 'magicLinkCallback' ], $text );
1472  return $text;
1473  }
1474 
1480  public function magicLinkCallback( $m ) {
1481  if ( isset( $m[1] ) && $m[1] !== '' ) {
1482  # Skip anchor
1483  return $m[0];
1484  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1485  # Skip HTML element
1486  return $m[0];
1487  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1488  # Free external link
1489  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1490  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1491  # RFC or PMID
1492  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1493  if ( !$this->mOptions->getMagicRFCLinks() ) {
1494  return $m[0];
1495  }
1496  $keyword = 'RFC';
1497  $urlmsg = 'rfcurl';
1498  $cssClass = 'mw-magiclink-rfc';
1499  $trackingCat = 'magiclink-tracking-rfc';
1500  $id = $m[5];
1501  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1502  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1503  return $m[0];
1504  }
1505  $keyword = 'PMID';
1506  $urlmsg = 'pubmedurl';
1507  $cssClass = 'mw-magiclink-pmid';
1508  $trackingCat = 'magiclink-tracking-pmid';
1509  $id = $m[5];
1510  } else {
1511  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1512  substr( $m[0], 0, 20 ) . '"' );
1513  }
1514  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1515  $this->addTrackingCategory( $trackingCat );
1516  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1517  } elseif ( isset( $m[6] ) && $m[6] !== ''
1518  && $this->mOptions->getMagicISBNLinks()
1519  ) {
1520  # ISBN
1521  $isbn = $m[6];
1522  $space = self::SPACE_NOT_NL; # non-newline space
1523  $isbn = preg_replace( "/$space/", ' ', $isbn );
1524  $num = strtr( $isbn, [
1525  '-' => '',
1526  ' ' => '',
1527  'x' => 'X',
1528  ] );
1529  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1530  return $this->getLinkRenderer()->makeKnownLink(
1531  SpecialPage::getTitleFor( 'Booksources', $num ),
1532  "ISBN $isbn",
1533  [
1534  'class' => 'internal mw-magiclink-isbn',
1535  'title' => false // suppress title attribute
1536  ]
1537  );
1538  } else {
1539  return $m[0];
1540  }
1541  }
1542 
1552  public function makeFreeExternalLink( $url, $numPostProto ) {
1553  $trail = '';
1554 
1555  # The characters '<' and '>' (which were escaped by
1556  # removeHTMLtags()) should not be included in
1557  # URLs, per RFC 2396.
1558  # Make &nbsp; terminate a URL as well (bug T84937)
1559  $m2 = [];
1560  if ( preg_match(
1561  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1562  $url,
1563  $m2,
1564  PREG_OFFSET_CAPTURE
1565  ) ) {
1566  $trail = substr( $url, $m2[0][1] ) . $trail;
1567  $url = substr( $url, 0, $m2[0][1] );
1568  }
1569 
1570  # Move trailing punctuation to $trail
1571  $sep = ',;\.:!?';
1572  # If there is no left bracket, then consider right brackets fair game too
1573  if ( strpos( $url, '(' ) === false ) {
1574  $sep .= ')';
1575  }
1576 
1577  $urlRev = strrev( $url );
1578  $numSepChars = strspn( $urlRev, $sep );
1579  # Don't break a trailing HTML entity by moving the ; into $trail
1580  # This is in hot code, so use substr_compare to avoid having to
1581  # create a new string object for the comparison
1582  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1583  # more optimization: instead of running preg_match with a $
1584  # anchor, which can be slow, do the match on the reversed
1585  # string starting at the desired offset.
1586  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1587  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1588  $numSepChars--;
1589  }
1590  }
1591  if ( $numSepChars ) {
1592  $trail = substr( $url, -$numSepChars ) . $trail;
1593  $url = substr( $url, 0, -$numSepChars );
1594  }
1595 
1596  # Verify that we still have a real URL after trail removal, and
1597  # not just lone protocol
1598  if ( strlen( $trail ) >= $numPostProto ) {
1599  return $url . $trail;
1600  }
1601 
1602  $url = Sanitizer::cleanUrl( $url );
1603 
1604  # Is this an external image?
1605  $text = $this->maybeMakeExternalImage( $url );
1606  if ( $text === false ) {
1607  # Not an image, make a link
1608  $text = Linker::makeExternalLink( $url,
1609  $this->getConverterLanguage()->markNoConversion( $url, true ),
1610  true, 'free',
1611  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1612  # Register it in the output object...
1613  # Replace unnecessary URL escape codes with their equivalent characters
1614  $pasteurized = self::normalizeLinkUrl( $url );
1615  $this->mOutput->addExternalLink( $pasteurized );
1616  }
1617  return $text . $trail;
1618  }
1619 
1629  public function doHeadings( $text ) {
1630  for ( $i = 6; $i >= 1; --$i ) {
1631  $h = str_repeat( '=', $i );
1632  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1633  }
1634  return $text;
1635  }
1636 
1645  public function doAllQuotes( $text ) {
1646  $outtext = '';
1647  $lines = StringUtils::explode( "\n", $text );
1648  foreach ( $lines as $line ) {
1649  $outtext .= $this->doQuotes( $line ) . "\n";
1650  }
1651  $outtext = substr( $outtext, 0, -1 );
1652  return $outtext;
1653  }
1654 
1662  public function doQuotes( $text ) {
1663  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1664  $countarr = count( $arr );
1665  if ( $countarr == 1 ) {
1666  return $text;
1667  }
1668 
1669  // First, do some preliminary work. This may shift some apostrophes from
1670  // being mark-up to being text. It also counts the number of occurrences
1671  // of bold and italics mark-ups.
1672  $numbold = 0;
1673  $numitalics = 0;
1674  for ( $i = 1; $i < $countarr; $i += 2 ) {
1675  $thislen = strlen( $arr[$i] );
1676  // If there are ever four apostrophes, assume the first is supposed to
1677  // be text, and the remaining three constitute mark-up for bold text.
1678  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1679  if ( $thislen == 4 ) {
1680  $arr[$i - 1] .= "'";
1681  $arr[$i] = "'''";
1682  $thislen = 3;
1683  } elseif ( $thislen > 5 ) {
1684  // If there are more than 5 apostrophes in a row, assume they're all
1685  // text except for the last 5.
1686  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1687  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1688  $arr[$i] = "'''''";
1689  $thislen = 5;
1690  }
1691  // Count the number of occurrences of bold and italics mark-ups.
1692  if ( $thislen == 2 ) {
1693  $numitalics++;
1694  } elseif ( $thislen == 3 ) {
1695  $numbold++;
1696  } elseif ( $thislen == 5 ) {
1697  $numitalics++;
1698  $numbold++;
1699  }
1700  }
1701 
1702  // If there is an odd number of both bold and italics, it is likely
1703  // that one of the bold ones was meant to be an apostrophe followed
1704  // by italics. Which one we cannot know for certain, but it is more
1705  // likely to be one that has a single-letter word before it.
1706  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1707  $firstsingleletterword = -1;
1708  $firstmultiletterword = -1;
1709  $firstspace = -1;
1710  for ( $i = 1; $i < $countarr; $i += 2 ) {
1711  if ( strlen( $arr[$i] ) == 3 ) {
1712  $x1 = substr( $arr[$i - 1], -1 );
1713  $x2 = substr( $arr[$i - 1], -2, 1 );
1714  if ( $x1 === ' ' ) {
1715  if ( $firstspace == -1 ) {
1716  $firstspace = $i;
1717  }
1718  } elseif ( $x2 === ' ' ) {
1719  $firstsingleletterword = $i;
1720  // if $firstsingleletterword is set, we don't
1721  // look at the other options, so we can bail early.
1722  break;
1723  } else {
1724  if ( $firstmultiletterword == -1 ) {
1725  $firstmultiletterword = $i;
1726  }
1727  }
1728  }
1729  }
1730 
1731  // If there is a single-letter word, use it!
1732  if ( $firstsingleletterword > -1 ) {
1733  $arr[$firstsingleletterword] = "''";
1734  $arr[$firstsingleletterword - 1] .= "'";
1735  } elseif ( $firstmultiletterword > -1 ) {
1736  // If not, but there's a multi-letter word, use that one.
1737  $arr[$firstmultiletterword] = "''";
1738  $arr[$firstmultiletterword - 1] .= "'";
1739  } elseif ( $firstspace > -1 ) {
1740  // ... otherwise use the first one that has neither.
1741  // (notice that it is possible for all three to be -1 if, for example,
1742  // there is only one pentuple-apostrophe in the line)
1743  $arr[$firstspace] = "''";
1744  $arr[$firstspace - 1] .= "'";
1745  }
1746  }
1747 
1748  // Now let's actually convert our apostrophic mush to HTML!
1749  $output = '';
1750  $buffer = '';
1751  $state = '';
1752  $i = 0;
1753  foreach ( $arr as $r ) {
1754  if ( ( $i % 2 ) == 0 ) {
1755  if ( $state === 'both' ) {
1756  $buffer .= $r;
1757  } else {
1758  $output .= $r;
1759  }
1760  } else {
1761  $thislen = strlen( $r );
1762  if ( $thislen == 2 ) {
1763  if ( $state === 'i' ) {
1764  $output .= '</i>';
1765  $state = '';
1766  } elseif ( $state === 'bi' ) {
1767  $output .= '</i>';
1768  $state = 'b';
1769  } elseif ( $state === 'ib' ) {
1770  $output .= '</b></i><b>';
1771  $state = 'b';
1772  } elseif ( $state === 'both' ) {
1773  $output .= '<b><i>' . $buffer . '</i>';
1774  $state = 'b';
1775  } else { // $state can be 'b' or ''
1776  $output .= '<i>';
1777  $state .= 'i';
1778  }
1779  } elseif ( $thislen == 3 ) {
1780  if ( $state === 'b' ) {
1781  $output .= '</b>';
1782  $state = '';
1783  } elseif ( $state === 'bi' ) {
1784  $output .= '</i></b><i>';
1785  $state = 'i';
1786  } elseif ( $state === 'ib' ) {
1787  $output .= '</b>';
1788  $state = 'i';
1789  } elseif ( $state === 'both' ) {
1790  $output .= '<i><b>' . $buffer . '</b>';
1791  $state = 'i';
1792  } else { // $state can be 'i' or ''
1793  $output .= '<b>';
1794  $state .= 'b';
1795  }
1796  } elseif ( $thislen == 5 ) {
1797  if ( $state === 'b' ) {
1798  $output .= '</b><i>';
1799  $state = 'i';
1800  } elseif ( $state === 'i' ) {
1801  $output .= '</i><b>';
1802  $state = 'b';
1803  } elseif ( $state === 'bi' ) {
1804  $output .= '</i></b>';
1805  $state = '';
1806  } elseif ( $state === 'ib' ) {
1807  $output .= '</b></i>';
1808  $state = '';
1809  } elseif ( $state === 'both' ) {
1810  $output .= '<i><b>' . $buffer . '</b></i>';
1811  $state = '';
1812  } else { // ($state == '')
1813  $buffer = '';
1814  $state = 'both';
1815  }
1816  }
1817  }
1818  $i++;
1819  }
1820  // Now close all remaining tags. Notice that the order is important.
1821  if ( $state === 'b' || $state === 'ib' ) {
1822  $output .= '</b>';
1823  }
1824  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1825  $output .= '</i>';
1826  }
1827  if ( $state === 'bi' ) {
1828  $output .= '</b>';
1829  }
1830  // There might be lonely ''''', so make sure we have a buffer
1831  if ( $state === 'both' && $buffer ) {
1832  $output .= '<b><i>' . $buffer . '</i></b>';
1833  }
1834  return $output;
1835  }
1836 
1850  public function replaceExternalLinks( $text ) {
1851 
1852  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1853  if ( $bits === false ) {
1854  throw new MWException( "PCRE needs to be compiled with "
1855  . "--enable-unicode-properties in order for MediaWiki to function" );
1856  }
1857  $s = array_shift( $bits );
1858 
1859  $i = 0;
1860  while ( $i < count( $bits ) ) {
1861  $url = $bits[$i++];
1862  $i++; // protocol
1863  $text = $bits[$i++];
1864  $trail = $bits[$i++];
1865 
1866  # The characters '<' and '>' (which were escaped by
1867  # removeHTMLtags()) should not be included in
1868  # URLs, per RFC 2396.
1869  $m2 = [];
1870  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1871  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1872  $url = substr( $url, 0, $m2[0][1] );
1873  }
1874 
1875  # If the link text is an image URL, replace it with an <img> tag
1876  # This happened by accident in the original parser, but some people used it extensively
1877  $img = $this->maybeMakeExternalImage( $text );
1878  if ( $img !== false ) {
1879  $text = $img;
1880  }
1881 
1882  $dtrail = '';
1883 
1884  # Set linktype for CSS - if URL==text, link is essentially free
1885  $linktype = ( $text === $url ) ? 'free' : 'text';
1886 
1887  # No link text, e.g. [http://domain.tld/some.link]
1888  if ( $text == '' ) {
1889  # Autonumber
1890  $langObj = $this->getTargetLanguage();
1891  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1892  $linktype = 'autonumber';
1893  } else {
1894  # Have link text, e.g. [http://domain.tld/some.link text]s
1895  # Check for trail
1896  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1897  }
1898 
1899  $text = $this->getConverterLanguage()->markNoConversion( $text );
1900 
1901  $url = Sanitizer::cleanUrl( $url );
1902 
1903  # Use the encoded URL
1904  # This means that users can paste URLs directly into the text
1905  # Funny characters like ö aren't valid in URLs anyway
1906  # This was changed in August 2004
1907  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1908  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1909 
1910  # Register link in the output object.
1911  # Replace unnecessary URL escape codes with the referenced character
1912  # This prevents spammers from hiding links from the filters
1913  $pasteurized = self::normalizeLinkUrl( $url );
1914  $this->mOutput->addExternalLink( $pasteurized );
1915  }
1916 
1917  return $s;
1918  }
1919 
1929  public static function getExternalLinkRel( $url = false, $title = null ) {
1930  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1931  $ns = $title ? $title->getNamespace() : false;
1932  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1933  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1934  ) {
1935  return 'nofollow';
1936  }
1937  return null;
1938  }
1939 
1950  public function getExternalLinkAttribs( $url ) {
1951  $attribs = [];
1952  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1953 
1954  $target = $this->mOptions->getExternalLinkTarget();
1955  if ( $target ) {
1956  $attribs['target'] = $target;
1957  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1958  // T133507. New windows can navigate parent cross-origin.
1959  // Including noreferrer due to lacking browser
1960  // support of noopener. Eventually noreferrer should be removed.
1961  if ( $rel !== '' ) {
1962  $rel .= ' ';
1963  }
1964  $rel .= 'noreferrer noopener';
1965  }
1966  }
1967  $attribs['rel'] = $rel;
1968  return $attribs;
1969  }
1970 
1980  public static function normalizeLinkUrl( $url ) {
1981  # First, make sure unsafe characters are encoded
1982  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1983  function ( $m ) {
1984  return rawurlencode( $m[0] );
1985  },
1986  $url
1987  );
1988 
1989  $ret = '';
1990  $end = strlen( $url );
1991 
1992  # Fragment part - 'fragment'
1993  $start = strpos( $url, '#' );
1994  if ( $start !== false && $start < $end ) {
1995  $ret = self::normalizeUrlComponent(
1996  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1997  $end = $start;
1998  }
1999 
2000  # Query part - 'query' minus &=+;
2001  $start = strpos( $url, '?' );
2002  if ( $start !== false && $start < $end ) {
2003  $ret = self::normalizeUrlComponent(
2004  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2005  $end = $start;
2006  }
2007 
2008  # Scheme and path part - 'pchar'
2009  # (we assume no userinfo or encoded colons in the host)
2010  $ret = self::normalizeUrlComponent(
2011  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2012 
2013  return $ret;
2014  }
2015 
2016  private static function normalizeUrlComponent( $component, $unsafe ) {
2017  $callback = function ( $matches ) use ( $unsafe ) {
2018  $char = urldecode( $matches[0] );
2019  $ord = ord( $char );
2020  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2021  # Unescape it
2022  return $char;
2023  } else {
2024  # Leave it escaped, but use uppercase for a-f
2025  return strtoupper( $matches[0] );
2026  }
2027  };
2028  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2029  }
2030 
2039  private function maybeMakeExternalImage( $url ) {
2040  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2041  $imagesexception = !empty( $imagesfrom );
2042  $text = false;
2043  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2044  if ( $imagesexception && is_array( $imagesfrom ) ) {
2045  $imagematch = false;
2046  foreach ( $imagesfrom as $match ) {
2047  if ( strpos( $url, $match ) === 0 ) {
2048  $imagematch = true;
2049  break;
2050  }
2051  }
2052  } elseif ( $imagesexception ) {
2053  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2054  } else {
2055  $imagematch = false;
2056  }
2057 
2058  if ( $this->mOptions->getAllowExternalImages()
2059  || ( $imagesexception && $imagematch )
2060  ) {
2061  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2062  # Image found
2063  $text = Linker::makeExternalImage( $url );
2064  }
2065  }
2066  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2067  && preg_match( self::EXT_IMAGE_REGEX, $url )
2068  ) {
2069  $whitelist = explode(
2070  "\n",
2071  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2072  );
2073 
2074  foreach ( $whitelist as $entry ) {
2075  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2076  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2077  continue;
2078  }
2079  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2080  # Image matches a whitelist entry
2081  $text = Linker::makeExternalImage( $url );
2082  break;
2083  }
2084  }
2085  }
2086  return $text;
2087  }
2088 
2098  public function replaceInternalLinks( $s ) {
2099  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2100  return $s;
2101  }
2102 
2111  public function replaceInternalLinks2( &$s ) {
2113 
2114  static $tc = false, $e1, $e1_img;
2115  # the % is needed to support urlencoded titles as well
2116  if ( !$tc ) {
2117  $tc = Title::legalChars() . '#%';
2118  # Match a link having the form [[namespace:link|alternate]]trail
2119  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2120  # Match cases where there is no "]]", which might still be images
2121  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2122  }
2123 
2124  $holders = new LinkHolderArray( $this );
2125 
2126  # split the entire text string on occurrences of [[
2127  $a = StringUtils::explode( '[[', ' ' . $s );
2128  # get the first element (all text up to first [[), and remove the space we added
2129  $s = $a->current();
2130  $a->next();
2131  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2132  $s = substr( $s, 1 );
2133 
2134  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2135  $e2 = null;
2136  if ( $useLinkPrefixExtension ) {
2137  # Match the end of a line for a word that's not followed by whitespace,
2138  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2140  $charset = $wgContLang->linkPrefixCharset();
2141  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2142  }
2143 
2144  if ( is_null( $this->mTitle ) ) {
2145  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2146  }
2147  $nottalk = !$this->mTitle->isTalkPage();
2148 
2149  if ( $useLinkPrefixExtension ) {
2150  $m = [];
2151  if ( preg_match( $e2, $s, $m ) ) {
2152  $first_prefix = $m[2];
2153  } else {
2154  $first_prefix = false;
2155  }
2156  } else {
2157  $prefix = '';
2158  }
2159 
2160  $useSubpages = $this->areSubpagesAllowed();
2161 
2162  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2163  # Loop for each link
2164  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2165  // @codingStandardsIgnoreEnd
2166 
2167  # Check for excessive memory usage
2168  if ( $holders->isBig() ) {
2169  # Too big
2170  # Do the existence check, replace the link holders and clear the array
2171  $holders->replace( $s );
2172  $holders->clear();
2173  }
2174 
2175  if ( $useLinkPrefixExtension ) {
2176  if ( preg_match( $e2, $s, $m ) ) {
2177  $prefix = $m[2];
2178  $s = $m[1];
2179  } else {
2180  $prefix = '';
2181  }
2182  # first link
2183  if ( $first_prefix ) {
2184  $prefix = $first_prefix;
2185  $first_prefix = false;
2186  }
2187  }
2188 
2189  $might_be_img = false;
2190 
2191  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2192  $text = $m[2];
2193  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2194  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2195  # the real problem is with the $e1 regex
2196  # See T1500.
2197  # Still some problems for cases where the ] is meant to be outside punctuation,
2198  # and no image is in sight. See T4095.
2199  if ( $text !== ''
2200  && substr( $m[3], 0, 1 ) === ']'
2201  && strpos( $text, '[' ) !== false
2202  ) {
2203  $text .= ']'; # so that replaceExternalLinks($text) works later
2204  $m[3] = substr( $m[3], 1 );
2205  }
2206  # fix up urlencoded title texts
2207  if ( strpos( $m[1], '%' ) !== false ) {
2208  # Should anchors '#' also be rejected?
2209  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2210  }
2211  $trail = $m[3];
2212  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2213  # Invalid, but might be an image with a link in its caption
2214  $might_be_img = true;
2215  $text = $m[2];
2216  if ( strpos( $m[1], '%' ) !== false ) {
2217  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2218  }
2219  $trail = "";
2220  } else { # Invalid form; output directly
2221  $s .= $prefix . '[[' . $line;
2222  continue;
2223  }
2224 
2225  $origLink = ltrim( $m[1], ' ' );
2226 
2227  # Don't allow internal links to pages containing
2228  # PROTO: where PROTO is a valid URL protocol; these
2229  # should be external links.
2230  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2231  $s .= $prefix . '[[' . $line;
2232  continue;
2233  }
2234 
2235  # Make subpage if necessary
2236  if ( $useSubpages ) {
2237  $link = $this->maybeDoSubpageLink( $origLink, $text );
2238  } else {
2239  $link = $origLink;
2240  }
2241 
2242  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2243  if ( !$noforce ) {
2244  # Strip off leading ':'
2245  $link = substr( $link, 1 );
2246  }
2247 
2248  $unstrip = $this->mStripState->unstripNoWiki( $link );
2249  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2250  if ( $nt === null ) {
2251  $s .= $prefix . '[[' . $line;
2252  continue;
2253  }
2254 
2255  $ns = $nt->getNamespace();
2256  $iw = $nt->getInterwiki();
2257 
2258  if ( $might_be_img ) { # if this is actually an invalid link
2259  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2260  $found = false;
2261  while ( true ) {
2262  # look at the next 'line' to see if we can close it there
2263  $a->next();
2264  $next_line = $a->current();
2265  if ( $next_line === false || $next_line === null ) {
2266  break;
2267  }
2268  $m = explode( ']]', $next_line, 3 );
2269  if ( count( $m ) == 3 ) {
2270  # the first ]] closes the inner link, the second the image
2271  $found = true;
2272  $text .= "[[{$m[0]}]]{$m[1]}";
2273  $trail = $m[2];
2274  break;
2275  } elseif ( count( $m ) == 2 ) {
2276  # if there's exactly one ]] that's fine, we'll keep looking
2277  $text .= "[[{$m[0]}]]{$m[1]}";
2278  } else {
2279  # if $next_line is invalid too, we need look no further
2280  $text .= '[[' . $next_line;
2281  break;
2282  }
2283  }
2284  if ( !$found ) {
2285  # we couldn't find the end of this imageLink, so output it raw
2286  # but don't ignore what might be perfectly normal links in the text we've examined
2287  $holders->merge( $this->replaceInternalLinks2( $text ) );
2288  $s .= "{$prefix}[[$link|$text";
2289  # note: no $trail, because without an end, there *is* no trail
2290  continue;
2291  }
2292  } else { # it's not an image, so output it raw
2293  $s .= "{$prefix}[[$link|$text";
2294  # note: no $trail, because without an end, there *is* no trail
2295  continue;
2296  }
2297  }
2298 
2299  $wasblank = ( $text == '' );
2300  if ( $wasblank ) {
2301  $text = $link;
2302  } else {
2303  # T6598 madness. Handle the quotes only if they come from the alternate part
2304  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2305  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2306  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2307  $text = $this->doQuotes( $text );
2308  }
2309 
2310  # Link not escaped by : , create the various objects
2311  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2312  # Interwikis
2313  if (
2314  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2315  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2316  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2317  )
2318  ) {
2319  # T26502: filter duplicates
2320  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2321  $this->mLangLinkLanguages[$iw] = true;
2322  $this->mOutput->addLanguageLink( $nt->getFullText() );
2323  }
2324 
2325  $s = rtrim( $s . $prefix );
2326  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2327  continue;
2328  }
2329 
2330  if ( $ns == NS_FILE ) {
2331  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2332  if ( $wasblank ) {
2333  # if no parameters were passed, $text
2334  # becomes something like "File:Foo.png",
2335  # which we don't want to pass on to the
2336  # image generator
2337  $text = '';
2338  } else {
2339  # recursively parse links inside the image caption
2340  # actually, this will parse them in any other parameters, too,
2341  # but it might be hard to fix that, and it doesn't matter ATM
2342  $text = $this->replaceExternalLinks( $text );
2343  $holders->merge( $this->replaceInternalLinks2( $text ) );
2344  }
2345  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2346  $s .= $prefix . $this->armorLinks(
2347  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2348  continue;
2349  }
2350  } elseif ( $ns == NS_CATEGORY ) {
2351  $s = rtrim( $s . "\n" ); # T2087
2352 
2353  if ( $wasblank ) {
2354  $sortkey = $this->getDefaultSort();
2355  } else {
2356  $sortkey = $text;
2357  }
2358  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2359  $sortkey = str_replace( "\n", '', $sortkey );
2360  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2361  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2362 
2366  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2367 
2368  continue;
2369  }
2370  }
2371 
2372  # Self-link checking. For some languages, variants of the title are checked in
2373  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2374  # for linking to a different variant.
2375  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2376  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2377  continue;
2378  }
2379 
2380  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2381  # @todo FIXME: Should do batch file existence checks, see comment below
2382  if ( $ns == NS_MEDIA ) {
2383  # Give extensions a chance to select the file revision for us
2384  $options = [];
2385  $descQuery = false;
2386  Hooks::run( 'BeforeParserFetchFileAndTitle',
2387  [ $this, $nt, &$options, &$descQuery ] );
2388  # Fetch and register the file (file title may be different via hooks)
2389  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2390  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2391  $s .= $prefix . $this->armorLinks(
2392  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2393  continue;
2394  }
2395 
2396  # Some titles, such as valid special pages or files in foreign repos, should
2397  # be shown as bluelinks even though they're not included in the page table
2398  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2399  # batch file existence checks for NS_FILE and NS_MEDIA
2400  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2401  $this->mOutput->addLink( $nt );
2402  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2403  } else {
2404  # Links will be added to the output link list after checking
2405  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2406  }
2407  }
2408  return $holders;
2409  }
2410 
2424  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2425  list( $inside, $trail ) = Linker::splitTrail( $trail );
2426 
2427  if ( $text == '' ) {
2428  $text = htmlspecialchars( $nt->getPrefixedText() );
2429  }
2430 
2431  $link = $this->getLinkRenderer()->makeKnownLink(
2432  $nt, new HtmlArmor( "$prefix$text$inside" )
2433  );
2434 
2435  return $this->armorLinks( $link ) . $trail;
2436  }
2437 
2448  public function armorLinks( $text ) {
2449  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2450  self::MARKER_PREFIX . "NOPARSE$1", $text );
2451  }
2452 
2457  public function areSubpagesAllowed() {
2458  # Some namespaces don't allow subpages
2459  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2460  }
2461 
2470  public function maybeDoSubpageLink( $target, &$text ) {
2471  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2472  }
2473 
2482  public function doBlockLevels( $text, $linestart ) {
2483  return BlockLevelPass::doBlockLevels( $text, $linestart );
2484  }
2485 
2497  public function getVariableValue( $index, $frame = false ) {
2500 
2501  if ( is_null( $this->mTitle ) ) {
2502  // If no title set, bad things are going to happen
2503  // later. Title should always be set since this
2504  // should only be called in the middle of a parse
2505  // operation (but the unit-tests do funky stuff)
2506  throw new MWException( __METHOD__ . ' Should only be '
2507  . ' called while parsing (no title set)' );
2508  }
2509 
2510  // Avoid PHP 7.1 warning from passing $this by reference
2511  $parser = $this;
2512 
2517  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2518  if ( isset( $this->mVarCache[$index] ) ) {
2519  return $this->mVarCache[$index];
2520  }
2521  }
2522 
2523  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2524  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2525 
2526  $pageLang = $this->getFunctionLang();
2527 
2528  switch ( $index ) {
2529  case '!':
2530  $value = '|';
2531  break;
2532  case 'currentmonth':
2533  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2534  break;
2535  case 'currentmonth1':
2536  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2537  break;
2538  case 'currentmonthname':
2539  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2540  break;
2541  case 'currentmonthnamegen':
2542  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2543  break;
2544  case 'currentmonthabbrev':
2545  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2546  break;
2547  case 'currentday':
2548  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2549  break;
2550  case 'currentday2':
2551  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2552  break;
2553  case 'localmonth':
2554  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2555  break;
2556  case 'localmonth1':
2557  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2558  break;
2559  case 'localmonthname':
2560  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2561  break;
2562  case 'localmonthnamegen':
2563  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2564  break;
2565  case 'localmonthabbrev':
2566  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2567  break;
2568  case 'localday':
2569  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2570  break;
2571  case 'localday2':
2572  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2573  break;
2574  case 'pagename':
2575  $value = wfEscapeWikiText( $this->mTitle->getText() );
2576  break;
2577  case 'pagenamee':
2578  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2579  break;
2580  case 'fullpagename':
2581  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2582  break;
2583  case 'fullpagenamee':
2584  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2585  break;
2586  case 'subpagename':
2587  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2588  break;
2589  case 'subpagenamee':
2590  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2591  break;
2592  case 'rootpagename':
2593  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2594  break;
2595  case 'rootpagenamee':
2596  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2597  ' ',
2598  '_',
2599  $this->mTitle->getRootText()
2600  ) ) );
2601  break;
2602  case 'basepagename':
2603  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2604  break;
2605  case 'basepagenamee':
2606  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2607  ' ',
2608  '_',
2609  $this->mTitle->getBaseText()
2610  ) ) );
2611  break;
2612  case 'talkpagename':
2613  if ( $this->mTitle->canTalk() ) {
2614  $talkPage = $this->mTitle->getTalkPage();
2615  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2616  } else {
2617  $value = '';
2618  }
2619  break;
2620  case 'talkpagenamee':
2621  if ( $this->mTitle->canTalk() ) {
2622  $talkPage = $this->mTitle->getTalkPage();
2623  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2624  } else {
2625  $value = '';
2626  }
2627  break;
2628  case 'subjectpagename':
2629  $subjPage = $this->mTitle->getSubjectPage();
2630  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2631  break;
2632  case 'subjectpagenamee':
2633  $subjPage = $this->mTitle->getSubjectPage();
2634  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2635  break;
2636  case 'pageid': // requested in T25427
2637  $pageid = $this->getTitle()->getArticleID();
2638  if ( $pageid == 0 ) {
2639  # 0 means the page doesn't exist in the database,
2640  # which means the user is previewing a new page.
2641  # The vary-revision flag must be set, because the magic word
2642  # will have a different value once the page is saved.
2643  $this->mOutput->setFlag( 'vary-revision' );
2644  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2645  }
2646  $value = $pageid ? $pageid : null;
2647  break;
2648  case 'revisionid':
2649  # Let the edit saving system know we should parse the page
2650  # *after* a revision ID has been assigned.
2651  $this->mOutput->setFlag( 'vary-revision-id' );
2652  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2653  $value = $this->mRevisionId;
2654  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2655  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2656  $this->mOutput->setSpeculativeRevIdUsed( $value );
2657  }
2658  break;
2659  case 'revisionday':
2660  # Let the edit saving system know we should parse the page
2661  # *after* a revision ID has been assigned. This is for null edits.
2662  $this->mOutput->setFlag( 'vary-revision' );
2663  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2664  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2665  break;
2666  case 'revisionday2':
2667  # Let the edit saving system know we should parse the page
2668  # *after* a revision ID has been assigned. This is for null edits.
2669  $this->mOutput->setFlag( 'vary-revision' );
2670  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2671  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2672  break;
2673  case 'revisionmonth':
2674  # Let the edit saving system know we should parse the page
2675  # *after* a revision ID has been assigned. This is for null edits.
2676  $this->mOutput->setFlag( 'vary-revision' );
2677  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2678  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2679  break;
2680  case 'revisionmonth1':
2681  # Let the edit saving system know we should parse the page
2682  # *after* a revision ID has been assigned. This is for null edits.
2683  $this->mOutput->setFlag( 'vary-revision' );
2684  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2685  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2686  break;
2687  case 'revisionyear':
2688  # Let the edit saving system know we should parse the page
2689  # *after* a revision ID has been assigned. This is for null edits.
2690  $this->mOutput->setFlag( 'vary-revision' );
2691  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2692  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2693  break;
2694  case 'revisiontimestamp':
2695  # Let the edit saving system know we should parse the page
2696  # *after* a revision ID has been assigned. This is for null edits.
2697  $this->mOutput->setFlag( 'vary-revision' );
2698  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2699  $value = $this->getRevisionTimestamp();
2700  break;
2701  case 'revisionuser':
2702  # Let the edit saving system know we should parse the page
2703  # *after* a revision ID has been assigned for null edits.
2704  $this->mOutput->setFlag( 'vary-user' );
2705  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2706  $value = $this->getRevisionUser();
2707  break;
2708  case 'revisionsize':
2709  $value = $this->getRevisionSize();
2710  break;
2711  case 'namespace':
2712  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2713  break;
2714  case 'namespacee':
2715  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2716  break;
2717  case 'namespacenumber':
2718  $value = $this->mTitle->getNamespace();
2719  break;
2720  case 'talkspace':
2721  $value = $this->mTitle->canTalk()
2722  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2723  : '';
2724  break;
2725  case 'talkspacee':
2726  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2727  break;
2728  case 'subjectspace':
2729  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2730  break;
2731  case 'subjectspacee':
2732  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2733  break;
2734  case 'currentdayname':
2735  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2736  break;
2737  case 'currentyear':
2738  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2739  break;
2740  case 'currenttime':
2741  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2742  break;
2743  case 'currenthour':
2744  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2745  break;
2746  case 'currentweek':
2747  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2748  # int to remove the padding
2749  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2750  break;
2751  case 'currentdow':
2752  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2753  break;
2754  case 'localdayname':
2755  $value = $pageLang->getWeekdayName(
2756  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2757  );
2758  break;
2759  case 'localyear':
2760  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2761  break;
2762  case 'localtime':
2763  $value = $pageLang->time(
2764  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2765  false,
2766  false
2767  );
2768  break;
2769  case 'localhour':
2770  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2771  break;
2772  case 'localweek':
2773  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2774  # int to remove the padding
2775  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2776  break;
2777  case 'localdow':
2778  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2779  break;
2780  case 'numberofarticles':
2781  $value = $pageLang->formatNum( SiteStats::articles() );
2782  break;
2783  case 'numberoffiles':
2784  $value = $pageLang->formatNum( SiteStats::images() );
2785  break;
2786  case 'numberofusers':
2787  $value = $pageLang->formatNum( SiteStats::users() );
2788  break;
2789  case 'numberofactiveusers':
2790  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2791  break;
2792  case 'numberofpages':
2793  $value = $pageLang->formatNum( SiteStats::pages() );
2794  break;
2795  case 'numberofadmins':
2796  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2797  break;
2798  case 'numberofedits':
2799  $value = $pageLang->formatNum( SiteStats::edits() );
2800  break;
2801  case 'currenttimestamp':
2802  $value = wfTimestamp( TS_MW, $ts );
2803  break;
2804  case 'localtimestamp':
2805  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2806  break;
2807  case 'currentversion':
2809  break;
2810  case 'articlepath':
2811  return $wgArticlePath;
2812  case 'sitename':
2813  return $wgSitename;
2814  case 'server':
2815  return $wgServer;
2816  case 'servername':
2817  return $wgServerName;
2818  case 'scriptpath':
2819  return $wgScriptPath;
2820  case 'stylepath':
2821  return $wgStylePath;
2822  case 'directionmark':
2823  return $pageLang->getDirMark();
2824  case 'contentlanguage':
2826  return $wgLanguageCode;
2827  case 'pagelanguage':
2828  $value = $pageLang->getCode();
2829  break;
2830  case 'cascadingsources':
2832  break;
2833  default:
2834  $ret = null;
2835  Hooks::run(
2836  'ParserGetVariableValueSwitch',
2837  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2838  );
2839 
2840  return $ret;
2841  }
2842 
2843  if ( $index ) {
2844  $this->mVarCache[$index] = $value;
2845  }
2846 
2847  return $value;
2848  }
2849 
2855  public function initialiseVariables() {
2856  $variableIDs = MagicWord::getVariableIDs();
2857  $substIDs = MagicWord::getSubstIDs();
2858 
2859  $this->mVariables = new MagicWordArray( $variableIDs );
2860  $this->mSubstWords = new MagicWordArray( $substIDs );
2861  }
2862 
2885  public function preprocessToDom( $text, $flags = 0 ) {
2886  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2887  return $dom;
2888  }
2889 
2897  public static function splitWhitespace( $s ) {
2898  $ltrimmed = ltrim( $s );
2899  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2900  $trimmed = rtrim( $ltrimmed );
2901  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2902  if ( $diff > 0 ) {
2903  $w2 = substr( $ltrimmed, -$diff );
2904  } else {
2905  $w2 = '';
2906  }
2907  return [ $w1, $trimmed, $w2 ];
2908  }
2909 
2930  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2931  # Is there any text? Also, Prevent too big inclusions!
2932  $textSize = strlen( $text );
2933  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2934  return $text;
2935  }
2936 
2937  if ( $frame === false ) {
2938  $frame = $this->getPreprocessor()->newFrame();
2939  } elseif ( !( $frame instanceof PPFrame ) ) {
2940  wfDebug( __METHOD__ . " called using plain parameters instead of "
2941  . "a PPFrame instance. Creating custom frame.\n" );
2942  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2943  }
2944 
2945  $dom = $this->preprocessToDom( $text );
2946  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2947  $text = $frame->expand( $dom, $flags );
2948 
2949  return $text;
2950  }
2951 
2959  public static function createAssocArgs( $args ) {
2960  $assocArgs = [];
2961  $index = 1;
2962  foreach ( $args as $arg ) {
2963  $eqpos = strpos( $arg, '=' );
2964  if ( $eqpos === false ) {
2965  $assocArgs[$index++] = $arg;
2966  } else {
2967  $name = trim( substr( $arg, 0, $eqpos ) );
2968  $value = trim( substr( $arg, $eqpos + 1 ) );
2969  if ( $value === false ) {
2970  $value = '';
2971  }
2972  if ( $name !== false ) {
2973  $assocArgs[$name] = $value;
2974  }
2975  }
2976  }
2977 
2978  return $assocArgs;
2979  }
2980 
3007  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
3008  # does no harm if $current and $max are present but are unnecessary for the message
3009  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
3010  # only during preview, and that would split the parser cache unnecessarily.
3011  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
3012  ->text();
3013  $this->mOutput->addWarning( $warning );
3014  $this->addTrackingCategory( "$limitationType-category" );
3015  }
3016 
3029  public function braceSubstitution( $piece, $frame ) {
3030 
3031  // Flags
3032 
3033  // $text has been filled
3034  $found = false;
3035  // wiki markup in $text should be escaped
3036  $nowiki = false;
3037  // $text is HTML, armour it against wikitext transformation
3038  $isHTML = false;
3039  // Force interwiki transclusion to be done in raw mode not rendered
3040  $forceRawInterwiki = false;
3041  // $text is a DOM node needing expansion in a child frame
3042  $isChildObj = false;
3043  // $text is a DOM node needing expansion in the current frame
3044  $isLocalObj = false;
3045 
3046  # Title object, where $text came from
3047  $title = false;
3048 
3049  # $part1 is the bit before the first |, and must contain only title characters.
3050  # Various prefixes will be stripped from it later.
3051  $titleWithSpaces = $frame->expand( $piece['title'] );
3052  $part1 = trim( $titleWithSpaces );
3053  $titleText = false;
3054 
3055  # Original title text preserved for various purposes
3056  $originalTitle = $part1;
3057 
3058  # $args is a list of argument nodes, starting from index 0, not including $part1
3059  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3060  # below won't work b/c this $args isn't an object
3061  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3062 
3063  $profileSection = null; // profile templates
3064 
3065  # SUBST
3066  if ( !$found ) {
3067  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3068 
3069  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3070  # Decide whether to expand template or keep wikitext as-is.
3071  if ( $this->ot['wiki'] ) {
3072  if ( $substMatch === false ) {
3073  $literal = true; # literal when in PST with no prefix
3074  } else {
3075  $literal = false; # expand when in PST with subst: or safesubst:
3076  }
3077  } else {
3078  if ( $substMatch == 'subst' ) {
3079  $literal = true; # literal when not in PST with plain subst:
3080  } else {
3081  $literal = false; # expand when not in PST with safesubst: or no prefix
3082  }
3083  }
3084  if ( $literal ) {
3085  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3086  $isLocalObj = true;
3087  $found = true;
3088  }
3089  }
3090 
3091  # Variables
3092  if ( !$found && $args->getLength() == 0 ) {
3093  $id = $this->mVariables->matchStartToEnd( $part1 );
3094  if ( $id !== false ) {
3095  $text = $this->getVariableValue( $id, $frame );
3096  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3097  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3098  }
3099  $found = true;
3100  }
3101  }
3102 
3103  # MSG, MSGNW and RAW
3104  if ( !$found ) {
3105  # Check for MSGNW:
3106  $mwMsgnw = MagicWord::get( 'msgnw' );
3107  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3108  $nowiki = true;
3109  } else {
3110  # Remove obsolete MSG:
3111  $mwMsg = MagicWord::get( 'msg' );
3112  $mwMsg->matchStartAndRemove( $part1 );
3113  }
3114 
3115  # Check for RAW:
3116  $mwRaw = MagicWord::get( 'raw' );
3117  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3118  $forceRawInterwiki = true;
3119  }
3120  }
3121 
3122  # Parser functions
3123  if ( !$found ) {
3124  $colonPos = strpos( $part1, ':' );
3125  if ( $colonPos !== false ) {
3126  $func = substr( $part1, 0, $colonPos );
3127  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3128  $argsLength = $args->getLength();
3129  for ( $i = 0; $i < $argsLength; $i++ ) {
3130  $funcArgs[] = $args->item( $i );
3131  }
3132  try {
3133  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3134  } catch ( Exception $ex ) {
3135  throw $ex;
3136  }
3137 
3138  # The interface for parser functions allows for extracting
3139  # flags into the local scope. Extract any forwarded flags
3140  # here.
3141  extract( $result );
3142  }
3143  }
3144 
3145  # Finish mangling title and then check for loops.
3146  # Set $title to a Title object and $titleText to the PDBK
3147  if ( !$found ) {
3148  $ns = NS_TEMPLATE;
3149  # Split the title into page and subpage
3150  $subpage = '';
3151  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3152  if ( $part1 !== $relative ) {
3153  $part1 = $relative;
3154  $ns = $this->mTitle->getNamespace();
3155  }
3156  $title = Title::newFromText( $part1, $ns );
3157  if ( $title ) {
3158  $titleText = $title->getPrefixedText();
3159  # Check for language variants if the template is not found
3160  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3161  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3162  }
3163  # Do recursion depth check
3164  $limit = $this->mOptions->getMaxTemplateDepth();
3165  if ( $frame->depth >= $limit ) {
3166  $found = true;
3167  $text = '<span class="error">'
3168  . wfMessage( 'parser-template-recursion-depth-warning' )
3169  ->numParams( $limit )->inContentLanguage()->text()
3170  . '</span>';
3171  }
3172  }
3173  }
3174 
3175  # Load from database
3176  if ( !$found && $title ) {
3177  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3178  if ( !$title->isExternal() ) {
3179  if ( $title->isSpecialPage()
3180  && $this->mOptions->getAllowSpecialInclusion()
3181  && $this->ot['html']
3182  ) {
3183  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3184  // Pass the template arguments as URL parameters.
3185  // "uselang" will have no effect since the Language object
3186  // is forced to the one defined in ParserOptions.
3187  $pageArgs = [];
3188  $argsLength = $args->getLength();
3189  for ( $i = 0; $i < $argsLength; $i++ ) {
3190  $bits = $args->item( $i )->splitArg();
3191  if ( strval( $bits['index'] ) === '' ) {
3192  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3193  $value = trim( $frame->expand( $bits['value'] ) );
3194  $pageArgs[$name] = $value;
3195  }
3196  }
3197 
3198  // Create a new context to execute the special page
3199  $context = new RequestContext;
3200  $context->setTitle( $title );
3201  $context->setRequest( new FauxRequest( $pageArgs ) );
3202  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3203  $context->setUser( $this->getUser() );
3204  } else {
3205  // If this page is cached, then we better not be per user.
3206  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3207  }
3208  $context->setLanguage( $this->mOptions->getUserLangObj() );
3210  $title, $context, $this->getLinkRenderer() );
3211  if ( $ret ) {
3212  $text = $context->getOutput()->getHTML();
3213  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3214  $found = true;
3215  $isHTML = true;
3216  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3217  $this->mOutput->updateRuntimeAdaptiveExpiry(
3218  $specialPage->maxIncludeCacheTime()
3219  );
3220  }
3221  }
3222  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3223  $found = false; # access denied
3224  wfDebug( __METHOD__ . ": template inclusion denied for " .
3225  $title->getPrefixedDBkey() . "\n" );
3226  } else {
3227  list( $text, $title ) = $this->getTemplateDom( $title );
3228  if ( $text !== false ) {
3229  $found = true;
3230  $isChildObj = true;
3231  }
3232  }
3233 
3234  # If the title is valid but undisplayable, make a link to it
3235  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3236  $text = "[[:$titleText]]";
3237  $found = true;
3238  }
3239  } elseif ( $title->isTrans() ) {
3240  # Interwiki transclusion
3241  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3242  $text = $this->interwikiTransclude( $title, 'render' );
3243  $isHTML = true;
3244  } else {
3245  $text = $this->interwikiTransclude( $title, 'raw' );
3246  # Preprocess it like a template
3247  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3248  $isChildObj = true;
3249  }
3250  $found = true;
3251  }
3252 
3253  # Do infinite loop check
3254  # This has to be done after redirect resolution to avoid infinite loops via redirects
3255  if ( !$frame->loopCheck( $title ) ) {
3256  $found = true;
3257  $text = '<span class="error">'
3258  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3259  . '</span>';
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  # Workaround for PHP bug 35229 and similar
3377  if ( !is_callable( $callback ) ) {
3378  throw new MWException( "Tag hook for $function is not callable\n" );
3379  }
3380 
3381  // Avoid PHP 7.1 warning from passing $this by reference
3382  $parser = $this;
3383 
3384  $allArgs = [ &$parser ];
3385  if ( $flags & self::SFH_OBJECT_ARGS ) {
3386  # Convert arguments to PPNodes and collect for appending to $allArgs
3387  $funcArgs = [];
3388  foreach ( $args as $k => $v ) {
3389  if ( $v instanceof PPNode || $k === 0 ) {
3390  $funcArgs[] = $v;
3391  } else {
3392  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3393  }
3394  }
3395 
3396  # Add a frame parameter, and pass the arguments as an array
3397  $allArgs[] = $frame;
3398  $allArgs[] = $funcArgs;
3399  } else {
3400  # Convert arguments to plain text and append to $allArgs
3401  foreach ( $args as $k => $v ) {
3402  if ( $v instanceof PPNode ) {
3403  $allArgs[] = trim( $frame->expand( $v ) );
3404  } elseif ( is_int( $k ) && $k >= 0 ) {
3405  $allArgs[] = trim( $v );
3406  } else {
3407  $allArgs[] = trim( "$k=$v" );
3408  }
3409  }
3410  }
3411 
3412  $result = call_user_func_array( $callback, $allArgs );
3413 
3414  # The interface for function hooks allows them to return a wikitext
3415  # string or an array containing the string and any flags. This mungs
3416  # things around to match what this method should return.
3417  if ( !is_array( $result ) ) {
3418  $result =[
3419  'found' => true,
3420  'text' => $result,
3421  ];
3422  } else {
3423  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3424  $result['text'] = $result[0];
3425  }
3426  unset( $result[0] );
3427  $result += [
3428  'found' => true,
3429  ];
3430  }
3431 
3432  $noparse = true;
3433  $preprocessFlags = 0;
3434  if ( isset( $result['noparse'] ) ) {
3435  $noparse = $result['noparse'];
3436  }
3437  if ( isset( $result['preprocessFlags'] ) ) {
3438  $preprocessFlags = $result['preprocessFlags'];
3439  }
3440 
3441  if ( !$noparse ) {
3442  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3443  $result['isChildObj'] = true;
3444  }
3445 
3446  return $result;
3447  }
3448 
3457  public function getTemplateDom( $title ) {
3458  $cacheTitle = $title;
3459  $titleText = $title->getPrefixedDBkey();
3460 
3461  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3462  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3463  $title = Title::makeTitle( $ns, $dbk );
3464  $titleText = $title->getPrefixedDBkey();
3465  }
3466  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3467  return [ $this->mTplDomCache[$titleText], $title ];
3468  }
3469 
3470  # Cache miss, go to the database
3471  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3472 
3473  if ( $text === false ) {
3474  $this->mTplDomCache[$titleText] = false;
3475  return [ false, $title ];
3476  }
3477 
3478  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3479  $this->mTplDomCache[$titleText] = $dom;
3480 
3481  if ( !$title->equals( $cacheTitle ) ) {
3482  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3483  [ $title->getNamespace(), $cdb = $title->getDBkey() ];
3484  }
3485 
3486  return [ $dom, $title ];
3487  }
3488 
3501  $cacheKey = $title->getPrefixedDBkey();
3502  if ( !$this->currentRevisionCache ) {
3503  $this->currentRevisionCache = new MapCacheLRU( 100 );
3504  }
3505  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3506  $this->currentRevisionCache->set( $cacheKey,
3507  // Defaults to Parser::statelessFetchRevision()
3508  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3509  );
3510  }
3511  return $this->currentRevisionCache->get( $cacheKey );
3512  }
3513 
3523  public static function statelessFetchRevision( Title $title, $parser = false ) {
3524  $pageId = $title->getArticleID();
3525  $revId = $title->getLatestRevID();
3526 
3528  if ( $rev ) {
3529  $rev->setTitle( $title );
3530  }
3531 
3532  return $rev;
3533  }
3534 
3540  public function fetchTemplateAndTitle( $title ) {
3541  // Defaults to Parser::statelessFetchTemplate()
3542  $templateCb = $this->mOptions->getTemplateCallback();
3543  $stuff = call_user_func( $templateCb, $title, $this );
3544  // We use U+007F DELETE to distinguish strip markers from regular text.
3545  $text = $stuff['text'];
3546  if ( is_string( $stuff['text'] ) ) {
3547  $text = strtr( $text, "\x7f", "?" );
3548  }
3549  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3550  if ( isset( $stuff['deps'] ) ) {
3551  foreach ( $stuff['deps'] as $dep ) {
3552  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3553  if ( $dep['title']->equals( $this->getTitle() ) ) {
3554  // If we transclude ourselves, the final result
3555  // will change based on the new version of the page
3556  $this->mOutput->setFlag( 'vary-revision' );
3557  }
3558  }
3559  }
3560  return [ $text, $finalTitle ];
3561  }
3562 
3568  public function fetchTemplate( $title ) {
3569  return $this->fetchTemplateAndTitle( $title )[0];
3570  }
3571 
3581  public static function statelessFetchTemplate( $title, $parser = false ) {
3582  $text = $skip = false;
3583  $finalTitle = $title;
3584  $deps = [];
3585 
3586  # Loop to fetch the article, with up to 1 redirect
3587  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3588  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3589  // @codingStandardsIgnoreEnd
3590  # Give extensions a chance to select the revision instead
3591  $id = false; # Assume current
3592  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3593  [ $parser, $title, &$skip, &$id ] );
3594 
3595  if ( $skip ) {
3596  $text = false;
3597  $deps[] = [
3598  'title' => $title,
3599  'page_id' => $title->getArticleID(),
3600  'rev_id' => null
3601  ];
3602  break;
3603  }
3604  # Get the revision
3605  if ( $id ) {
3606  $rev = Revision::newFromId( $id );
3607  } elseif ( $parser ) {
3608  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3609  } else {
3611  }
3612  $rev_id = $rev ? $rev->getId() : 0;
3613  # If there is no current revision, there is no page
3614  if ( $id === false && !$rev ) {
3615  $linkCache = LinkCache::singleton();
3616  $linkCache->addBadLinkObj( $title );
3617  }
3618 
3619  $deps[] = [
3620  'title' => $title,
3621  'page_id' => $title->getArticleID(),
3622  'rev_id' => $rev_id ];
3623  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3624  # We fetched a rev from a different title; register it too...
3625  $deps[] = [
3626  'title' => $rev->getTitle(),
3627  'page_id' => $rev->getPage(),
3628  'rev_id' => $rev_id ];
3629  }
3630 
3631  if ( $rev ) {
3632  $content = $rev->getContent();
3633  $text = $content ? $content->getWikitextForTransclusion() : null;
3634 
3635  Hooks::run( 'ParserFetchTemplate',
3636  [ $parser, $title, $rev, &$text, &$deps ] );
3637 
3638  if ( $text === false || $text === null ) {
3639  $text = false;
3640  break;
3641  }
3642  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3644  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3645  if ( !$message->exists() ) {
3646  $text = false;
3647  break;
3648  }
3649  $content = $message->content();
3650  $text = $message->plain();
3651  } else {
3652  break;
3653  }
3654  if ( !$content ) {
3655  break;
3656  }
3657  # Redirect?
3658  $finalTitle = $title;
3659  $title = $content->getRedirectTarget();
3660  }
3661  return [
3662  'text' => $text,
3663  'finalTitle' => $finalTitle,
3664  'deps' => $deps ];
3665  }
3666 
3674  public function fetchFile( $title, $options = [] ) {
3675  return $this->fetchFileAndTitle( $title, $options )[0];
3676  }
3677 
3685  public function fetchFileAndTitle( $title, $options = [] ) {
3686  $file = $this->fetchFileNoRegister( $title, $options );
3687 
3688  $time = $file ? $file->getTimestamp() : false;
3689  $sha1 = $file ? $file->getSha1() : false;
3690  # Register the file as a dependency...
3691  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3692  if ( $file && !$title->equals( $file->getTitle() ) ) {
3693  # Update fetched file title
3694  $title = $file->getTitle();
3695  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3696  }
3697  return [ $file, $title ];
3698  }
3699 
3710  protected function fetchFileNoRegister( $title, $options = [] ) {
3711  if ( isset( $options['broken'] ) ) {
3712  $file = false; // broken thumbnail forced by hook
3713  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3714  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3715  } else { // get by (name,timestamp)
3716  $file = wfFindFile( $title, $options );
3717  }
3718  return $file;
3719  }
3720 
3729  public function interwikiTransclude( $title, $action ) {
3730  global $wgEnableScaryTranscluding;
3731 
3732  if ( !$wgEnableScaryTranscluding ) {
3733  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3734  }
3735 
3736  $url = $title->getFullURL( [ 'action' => $action ] );
3737 
3738  if ( strlen( $url ) > 255 ) {
3739  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3740  }
3741  return $this->fetchScaryTemplateMaybeFromCache( $url );
3742  }
3743 
3748  public function fetchScaryTemplateMaybeFromCache( $url ) {
3749  global $wgTranscludeCacheExpiry;
3750  $dbr = wfGetDB( DB_REPLICA );
3751  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3752  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3753  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3754  if ( $obj ) {
3755  return $obj->tc_contents;
3756  }
3757 
3758  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3759  $status = $req->execute(); // Status object
3760  if ( $status->isOK() ) {
3761  $text = $req->getContent();
3762  } elseif ( $req->getStatus() != 200 ) {
3763  // Though we failed to fetch the content, this status is useless.
3764  return wfMessage( 'scarytranscludefailed-httpstatus' )
3765  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3766  } else {
3767  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3768  }
3769 
3770  $dbw = wfGetDB( DB_MASTER );
3771  $dbw->replace( 'transcache', [ 'tc_url' ], [
3772  'tc_url' => $url,
3773  'tc_time' => $dbw->timestamp( time() ),
3774  'tc_contents' => $text
3775  ] );
3776  return $text;
3777  }
3778 
3788  public function argSubstitution( $piece, $frame ) {
3789 
3790  $error = false;
3791  $parts = $piece['parts'];
3792  $nameWithSpaces = $frame->expand( $piece['title'] );
3793  $argName = trim( $nameWithSpaces );
3794  $object = false;
3795  $text = $frame->getArgument( $argName );
3796  if ( $text === false && $parts->getLength() > 0
3797  && ( $this->ot['html']
3798  || $this->ot['pre']
3799  || ( $this->ot['wiki'] && $frame->isTemplate() )
3800  )
3801  ) {
3802  # No match in frame, use the supplied default
3803  $object = $parts->item( 0 )->getChildren();
3804  }
3805  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3806  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3807  $this->limitationWarn( 'post-expand-template-argument' );
3808  }
3809 
3810  if ( $text === false && $object === false ) {
3811  # No match anywhere
3812  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3813  }
3814  if ( $error !== false ) {
3815  $text .= $error;
3816  }
3817  if ( $object !== false ) {
3818  $ret = [ 'object' => $object ];
3819  } else {
3820  $ret = [ 'text' => $text ];
3821  }
3822 
3823  return $ret;
3824  }
3825 
3841  public function extensionSubstitution( $params, $frame ) {
3842  static $errorStr = '<span class="error">';
3843  static $errorLen = 20;
3844 
3845  $name = $frame->expand( $params['name'] );
3846  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3847  // Probably expansion depth or node count exceeded. Just punt the
3848  // error up.
3849  return $name;
3850  }
3851 
3852  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3853  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3854  // See above
3855  return $attrText;
3856  }
3857 
3858  // We can't safely check if the expansion for $content resulted in an
3859  // error, because the content could happen to be the error string
3860  // (T149622).
3861  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3862 
3863  $marker = self::MARKER_PREFIX . "-$name-"
3864  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3865 
3866  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3867  ( $this->ot['html'] || $this->ot['pre'] );
3868  if ( $isFunctionTag ) {
3869  $markerType = 'none';
3870  } else {
3871  $markerType = 'general';
3872  }
3873  if ( $this->ot['html'] || $isFunctionTag ) {
3874  $name = strtolower( $name );
3875  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3876  if ( isset( $params['attributes'] ) ) {
3877  $attributes = $attributes + $params['attributes'];
3878  }
3879 
3880  if ( isset( $this->mTagHooks[$name] ) ) {
3881  # Workaround for PHP bug 35229 and similar
3882  if ( !is_callable( $this->mTagHooks[$name] ) ) {
3883  throw new MWException( "Tag hook for $name is not callable\n" );
3884  }
3885  $output = call_user_func_array( $this->mTagHooks[$name],
3886  [ $content, $attributes, $this, $frame ] );
3887  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3888  list( $callback, ) = $this->mFunctionTagHooks[$name];
3889  if ( !is_callable( $callback ) ) {
3890  throw new MWException( "Tag hook for $name is not callable\n" );
3891  }
3892 
3893  // Avoid PHP 7.1 warning from passing $this by reference
3894  $parser = $this;
3895  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3896  } else {
3897  $output = '<span class="error">Invalid tag extension name: ' .
3898  htmlspecialchars( $name ) . '</span>';
3899  }
3900 
3901  if ( is_array( $output ) ) {
3902  # Extract flags to local scope (to override $markerType)
3903  $flags = $output;
3904  $output = $flags[0];
3905  unset( $flags[0] );
3906  extract( $flags );
3907  }
3908  } else {
3909  if ( is_null( $attrText ) ) {
3910  $attrText = '';
3911  }
3912  if ( isset( $params['attributes'] ) ) {
3913  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3914  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3915  htmlspecialchars( $attrValue ) . '"';
3916  }
3917  }
3918  if ( $content === null ) {
3919  $output = "<$name$attrText/>";
3920  } else {
3921  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3922  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3923  // See above
3924  return $close;
3925  }
3926  $output = "<$name$attrText>$content$close";
3927  }
3928  }
3929 
3930  if ( $markerType === 'none' ) {
3931  return $output;
3932  } elseif ( $markerType === 'nowiki' ) {
3933  $this->mStripState->addNoWiki( $marker, $output );
3934  } elseif ( $markerType === 'general' ) {
3935  $this->mStripState->addGeneral( $marker, $output );
3936  } else {
3937  throw new MWException( __METHOD__ . ': invalid marker type' );
3938  }
3939  return $marker;
3940  }
3941 
3949  public function incrementIncludeSize( $type, $size ) {
3950  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3951  return false;
3952  } else {
3953  $this->mIncludeSizes[$type] += $size;
3954  return true;
3955  }
3956  }
3957 
3964  $this->mExpensiveFunctionCount++;
3965  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3966  }
3967 
3976  public function doDoubleUnderscore( $text ) {
3977 
3978  # The position of __TOC__ needs to be recorded
3979  $mw = MagicWord::get( 'toc' );
3980  if ( $mw->match( $text ) ) {
3981  $this->mShowToc = true;
3982  $this->mForceTocPosition = true;
3983 
3984  # Set a placeholder. At the end we'll fill it in with the TOC.
3985  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3986 
3987  # Only keep the first one.
3988  $text = $mw->replace( '', $text );
3989  }
3990 
3991  # Now match and remove the rest of them
3993  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3994 
3995  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3996  $this->mOutput->mNoGallery = true;
3997  }
3998  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3999  $this->mShowToc = false;
4000  }
4001  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
4002  && $this->mTitle->getNamespace() == NS_CATEGORY
4003  ) {
4004  $this->addTrackingCategory( 'hidden-category-category' );
4005  }
4006  # (T10068) Allow control over whether robots index a page.
4007  # __INDEX__ always overrides __NOINDEX__, see T16899
4008  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
4009  $this->mOutput->setIndexPolicy( 'noindex' );
4010  $this->addTrackingCategory( 'noindex-category' );
4011  }
4012  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4013  $this->mOutput->setIndexPolicy( 'index' );
4014  $this->addTrackingCategory( 'index-category' );
4015  }
4016 
4017  # Cache all double underscores in the database
4018  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4019  $this->mOutput->setProperty( $key, '' );
4020  }
4021 
4022  return $text;
4023  }
4024 
4030  public function addTrackingCategory( $msg ) {
4031  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4032  }
4033 
4050  public function formatHeadings( $text, $origText, $isMain = true ) {
4051  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4052 
4053  # Inhibit editsection links if requested in the page
4054  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4055  $maybeShowEditLink = $showEditLink = false;
4056  } else {
4057  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4058  $showEditLink = $this->mOptions->getEditSection();
4059  }
4060  if ( $showEditLink ) {
4061  $this->mOutput->setEditSectionTokens( true );
4062  }
4063 
4064  # Get all headlines for numbering them and adding funky stuff like [edit]
4065  # links - this is for later, but we need the number of headlines right now
4066  $matches = [];
4067  $numMatches = preg_match_all(
4068  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4069  $text,
4070  $matches
4071  );
4072 
4073  # if there are fewer than 4 headlines in the article, do not show TOC
4074  # unless it's been explicitly enabled.
4075  $enoughToc = $this->mShowToc &&
4076  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4077 
4078  # Allow user to stipulate that a page should have a "new section"
4079  # link added via __NEWSECTIONLINK__
4080  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4081  $this->mOutput->setNewSection( true );
4082  }
4083 
4084  # Allow user to remove the "new section"
4085  # link via __NONEWSECTIONLINK__
4086  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4087  $this->mOutput->hideNewSection( true );
4088  }
4089 
4090  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4091  # override above conditions and always show TOC above first header
4092  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4093  $this->mShowToc = true;
4094  $enoughToc = true;
4095  }
4096 
4097  # headline counter
4098  $headlineCount = 0;
4099  $numVisible = 0;
4100 
4101  # Ugh .. the TOC should have neat indentation levels which can be
4102  # passed to the skin functions. These are determined here
4103  $toc = '';
4104  $full = '';
4105  $head = [];
4106  $sublevelCount = [];
4107  $levelCount = [];
4108  $level = 0;
4109  $prevlevel = 0;
4110  $toclevel = 0;
4111  $prevtoclevel = 0;
4112  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4113  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4114  $oldType = $this->mOutputType;
4115  $this->setOutputType( self::OT_WIKI );
4116  $frame = $this->getPreprocessor()->newFrame();
4117  $root = $this->preprocessToDom( $origText );
4118  $node = $root->getFirstChild();
4119  $byteOffset = 0;
4120  $tocraw = [];
4121  $refers = [];
4122 
4123  $headlines = $numMatches !== false ? $matches[3] : [];
4124 
4125  foreach ( $headlines as $headline ) {
4126  $isTemplate = false;
4127  $titleText = false;
4128  $sectionIndex = false;
4129  $numbering = '';
4130  $markerMatches = [];
4131  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4132  $serial = $markerMatches[1];
4133  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4134  $isTemplate = ( $titleText != $baseTitleText );
4135  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4136  }
4137 
4138  if ( $toclevel ) {
4139  $prevlevel = $level;
4140  }
4141  $level = $matches[1][$headlineCount];
4142 
4143  if ( $level > $prevlevel ) {
4144  # Increase TOC level
4145  $toclevel++;
4146  $sublevelCount[$toclevel] = 0;
4147  if ( $toclevel < $wgMaxTocLevel ) {
4148  $prevtoclevel = $toclevel;
4149  $toc .= Linker::tocIndent();
4150  $numVisible++;
4151  }
4152  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4153  # Decrease TOC level, find level to jump to
4154 
4155  for ( $i = $toclevel; $i > 0; $i-- ) {
4156  if ( $levelCount[$i] == $level ) {
4157  # Found last matching level
4158  $toclevel = $i;
4159  break;
4160  } elseif ( $levelCount[$i] < $level ) {
4161  # Found first matching level below current level
4162  $toclevel = $i + 1;
4163  break;
4164  }
4165  }
4166  if ( $i == 0 ) {
4167  $toclevel = 1;
4168  }
4169  if ( $toclevel < $wgMaxTocLevel ) {
4170  if ( $prevtoclevel < $wgMaxTocLevel ) {
4171  # Unindent only if the previous toc level was shown :p
4172  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4173  $prevtoclevel = $toclevel;
4174  } else {
4175  $toc .= Linker::tocLineEnd();
4176  }
4177  }
4178  } else {
4179  # No change in level, end TOC line
4180  if ( $toclevel < $wgMaxTocLevel ) {
4181  $toc .= Linker::tocLineEnd();
4182  }
4183  }
4184 
4185  $levelCount[$toclevel] = $level;
4186 
4187  # count number of headlines for each level
4188  $sublevelCount[$toclevel]++;
4189  $dot = 0;
4190  for ( $i = 1; $i <= $toclevel; $i++ ) {
4191  if ( !empty( $sublevelCount[$i] ) ) {
4192  if ( $dot ) {
4193  $numbering .= '.';
4194  }
4195  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4196  $dot = 1;
4197  }
4198  }
4199 
4200  # The safe header is a version of the header text safe to use for links
4201 
4202  # Remove link placeholders by the link text.
4203  # <!--LINK number-->
4204  # turns into
4205  # link text with suffix
4206  # Do this before unstrip since link text can contain strip markers
4207  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4208 
4209  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4210  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4211 
4212  # Strip out HTML (first regex removes any tag not allowed)
4213  # Allowed tags are:
4214  # * <sup> and <sub> (T10393)
4215  # * <i> (T28375)
4216  # * <b> (r105284)
4217  # * <bdi> (T74884)
4218  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4219  # * <s> and <strike> (T35715)
4220  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4221  # to allow setting directionality in toc items.
4222  $tocline = preg_replace(
4223  [
4224  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4225  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4226  ],
4227  [ '', '<$1>' ],
4228  $safeHeadline
4229  );
4230 
4231  # Strip '<span></span>', which is the result from the above if
4232  # <span id="foo"></span> is used to produce an additional anchor
4233  # for a section.
4234  $tocline = str_replace( '<span></span>', '', $tocline );
4235 
4236  $tocline = trim( $tocline );
4237 
4238  # For the anchor, strip out HTML-y stuff period
4239  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4240  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4241 
4242  # Save headline for section edit hint before it's escaped
4243  $headlineHint = $safeHeadline;
4244 
4245  if ( $wgExperimentalHtmlIds ) {
4246  # For reverse compatibility, provide an id that's
4247  # HTML4-compatible, like we used to.
4248  # It may be worth noting, academically, that it's possible for
4249  # the legacy anchor to conflict with a non-legacy headline
4250  # anchor on the page. In this case likely the "correct" thing
4251  # would be to either drop the legacy anchors or make sure
4252  # they're numbered first. However, this would require people
4253  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4254  # manually, so let's not bother worrying about it.
4255  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4256  [ 'noninitial', 'legacy' ] );
4257  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4258 
4259  if ( $legacyHeadline == $safeHeadline ) {
4260  # No reason to have both (in fact, we can't)
4261  $legacyHeadline = false;
4262  }
4263  } else {
4264  $legacyHeadline = false;
4265  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4266  'noninitial' );
4267  }
4268 
4269  # HTML names must be case-insensitively unique (T12721).
4270  # This does not apply to Unicode characters per
4271  # https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4272  # @todo FIXME: We may be changing them depending on the current locale.
4273  $arrayKey = strtolower( $safeHeadline );
4274  if ( $legacyHeadline === false ) {
4275  $legacyArrayKey = false;
4276  } else {
4277  $legacyArrayKey = strtolower( $legacyHeadline );
4278  }
4279 
4280  # Create the anchor for linking from the TOC to the section
4281  $anchor = $safeHeadline;
4282  $legacyAnchor = $legacyHeadline;
4283  if ( isset( $refers[$arrayKey] ) ) {
4284  // @codingStandardsIgnoreStart
4285  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4286  // @codingStandardsIgnoreEnd
4287  $anchor .= "_$i";
4288  $refers["${arrayKey}_$i"] = true;
4289  } else {
4290  $refers[$arrayKey] = true;
4291  }
4292  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4293  // @codingStandardsIgnoreStart
4294  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4295  // @codingStandardsIgnoreEnd
4296  $legacyAnchor .= "_$i";
4297  $refers["${legacyArrayKey}_$i"] = true;
4298  } else {
4299  $refers[$legacyArrayKey] = true;
4300  }
4301 
4302  # Don't number the heading if it is the only one (looks silly)
4303  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4304  # the two are different if the line contains a link
4305  $headline = Html::element(
4306  'span',
4307  [ 'class' => 'mw-headline-number' ],
4308  $numbering
4309  ) . ' ' . $headline;
4310  }
4311 
4312  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4313  $toc .= Linker::tocLine( $anchor, $tocline,
4314  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4315  }
4316 
4317  # Add the section to the section tree
4318  # Find the DOM node for this header
4319  $noOffset = ( $isTemplate || $sectionIndex === false );
4320  while ( $node && !$noOffset ) {
4321  if ( $node->getName() === 'h' ) {
4322  $bits = $node->splitHeading();
4323  if ( $bits['i'] == $sectionIndex ) {
4324  break;
4325  }
4326  }
4327  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4328  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4329  $node = $node->getNextSibling();
4330  }
4331  $tocraw[] = [
4332  'toclevel' => $toclevel,
4333  'level' => $level,
4334  'line' => $tocline,
4335  'number' => $numbering,
4336  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4337  'fromtitle' => $titleText,
4338  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4339  'anchor' => $anchor,
4340  ];
4341 
4342  # give headline the correct <h#> tag
4343  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4344  // Output edit section links as markers with styles that can be customized by skins
4345  if ( $isTemplate ) {
4346  # Put a T flag in the section identifier, to indicate to extractSections()
4347  # that sections inside <includeonly> should be counted.
4348  $editsectionPage = $titleText;
4349  $editsectionSection = "T-$sectionIndex";
4350  $editsectionContent = null;
4351  } else {
4352  $editsectionPage = $this->mTitle->getPrefixedText();
4353  $editsectionSection = $sectionIndex;
4354  $editsectionContent = $headlineHint;
4355  }
4356  // We use a bit of pesudo-xml for editsection markers. The
4357  // language converter is run later on. Using a UNIQ style marker
4358  // leads to the converter screwing up the tokens when it
4359  // converts stuff. And trying to insert strip tags fails too. At
4360  // this point all real inputted tags have already been escaped,
4361  // so we don't have to worry about a user trying to input one of
4362  // these markers directly. We use a page and section attribute
4363  // to stop the language converter from converting these
4364  // important bits of data, but put the headline hint inside a
4365  // content block because the language converter is supposed to
4366  // be able to convert that piece of data.
4367  // Gets replaced with html in ParserOutput::getText
4368  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4369  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4370  if ( $editsectionContent !== null ) {
4371  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4372  } else {
4373  $editlink .= '/>';
4374  }
4375  } else {
4376  $editlink = '';
4377  }
4378  $head[$headlineCount] = Linker::makeHeadline( $level,
4379  $matches['attrib'][$headlineCount], $anchor, $headline,
4380  $editlink, $legacyAnchor );
4381 
4382  $headlineCount++;
4383  }
4384 
4385  $this->setOutputType( $oldType );
4386 
4387  # Never ever show TOC if no headers
4388  if ( $numVisible < 1 ) {
4389  $enoughToc = false;
4390  }
4391 
4392  if ( $enoughToc ) {
4393  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4394  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4395  }
4396  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4397  $this->mOutput->setTOCHTML( $toc );
4398  $toc = self::TOC_START . $toc . self::TOC_END;
4399  $this->mOutput->addModules( 'mediawiki.toc' );
4400  }
4401 
4402  if ( $isMain ) {
4403  $this->mOutput->setSections( $tocraw );
4404  }
4405 
4406  # split up and insert constructed headlines
4407  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4408  $i = 0;
4409 
4410  // build an array of document sections
4411  $sections = [];
4412  foreach ( $blocks as $block ) {
4413  // $head is zero-based, sections aren't.
4414  if ( empty( $head[$i - 1] ) ) {
4415  $sections[$i] = $block;
4416  } else {
4417  $sections[$i] = $head[$i - 1] . $block;
4418  }
4419 
4430  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4431 
4432  $i++;
4433  }
4434 
4435  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4436  // append the TOC at the beginning
4437  // Top anchor now in skin
4438  $sections[0] = $sections[0] . $toc . "\n";
4439  }
4440 
4441  $full .= implode( '', $sections );
4442 
4443  if ( $this->mForceTocPosition ) {
4444  return str_replace( '<!--MWTOC-->', $toc, $full );
4445  } else {
4446  return $full;
4447  }
4448  }
4449 
4461  public function preSaveTransform( $text, Title $title, User $user,
4462  ParserOptions $options, $clearState = true
4463  ) {
4464  if ( $clearState ) {
4465  $magicScopeVariable = $this->lock();
4466  }
4467  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4468  $this->setUser( $user );
4469 
4470  // Strip U+0000 NULL (T159174)
4471  $text = str_replace( "\000", '', $text );
4472 
4473  // We still normalize line endings for backwards-compatibility
4474  // with other code that just calls PST, but this should already
4475  // be handled in TextContent subclasses
4476  $text = TextContent::normalizeLineEndings( $text );
4477 
4478  if ( $options->getPreSaveTransform() ) {
4479  $text = $this->pstPass2( $text, $user );
4480  }
4481  $text = $this->mStripState->unstripBoth( $text );
4482 
4483  $this->setUser( null ); # Reset
4484 
4485  return $text;
4486  }
4487 
4496  private function pstPass2( $text, $user ) {
4498 
4499  # Note: This is the timestamp saved as hardcoded wikitext to
4500  # the database, we use $wgContLang here in order to give
4501  # everyone the same signature and use the default one rather
4502  # than the one selected in each user's preferences.
4503  # (see also T14815)
4504  $ts = $this->mOptions->getTimestamp();
4505  $timestamp = MWTimestamp::getLocalInstance( $ts );
4506  $ts = $timestamp->format( 'YmdHis' );
4507  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4508 
4509  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4510 
4511  # Variable replacement
4512  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4513  $text = $this->replaceVariables( $text );
4514 
4515  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4516  # which may corrupt this parser instance via its wfMessage()->text() call-
4517 
4518  # Signatures
4519  $sigText = $this->getUserSig( $user );
4520  $text = strtr( $text, [
4521  '~~~~~' => $d,
4522  '~~~~' => "$sigText $d",
4523  '~~~' => $sigText
4524  ] );
4525 
4526  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4527  $tc = '[' . Title::legalChars() . ']';
4528  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4529 
4530  // [[ns:page (context)|]]
4531  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4532  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4533  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4534  // [[ns:page (context), context|]] (using either single or double-width comma)
4535  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4536  // [[|page]] (reverse pipe trick: add context from page title)
4537  $p2 = "/\[\[\\|($tc+)]]/";
4538 
4539  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4540  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4541  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4542  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4543 
4544  $t = $this->mTitle->getText();
4545  $m = [];
4546  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4547  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4548  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4549  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4550  } else {
4551  # if there's no context, don't bother duplicating the title
4552  $text = preg_replace( $p2, '[[\\1]]', $text );
4553  }
4554 
4555  return $text;
4556  }
4557 
4572  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4573  global $wgMaxSigChars;
4574 
4575  $username = $user->getName();
4576 
4577  # If not given, retrieve from the user object.
4578  if ( $nickname === false ) {
4579  $nickname = $user->getOption( 'nickname' );
4580  }
4581 
4582  if ( is_null( $fancySig ) ) {
4583  $fancySig = $user->getBoolOption( 'fancysig' );
4584  }
4585 
4586  $nickname = $nickname == null ? $username : $nickname;
4587 
4588  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4589  $nickname = $username;
4590  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4591  } elseif ( $fancySig !== false ) {
4592  # Sig. might contain markup; validate this
4593  if ( $this->validateSig( $nickname ) !== false ) {
4594  # Validated; clean up (if needed) and return it
4595  return $this->cleanSig( $nickname, true );
4596  } else {
4597  # Failed to validate; fall back to the default
4598  $nickname = $username;
4599  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4600  }
4601  }
4602 
4603  # Make sure nickname doesnt get a sig in a sig
4604  $nickname = self::cleanSigInSig( $nickname );
4605 
4606  # If we're still here, make it a link to the user page
4607  $userText = wfEscapeWikiText( $username );
4608  $nickText = wfEscapeWikiText( $nickname );
4609  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4610 
4611  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4612  ->title( $this->getTitle() )->text();
4613  }
4614 
4621  public function validateSig( $text ) {
4622  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4623  }
4624 
4635  public function cleanSig( $text, $parsing = false ) {
4636  if ( !$parsing ) {
4637  global $wgTitle;
4638  $magicScopeVariable = $this->lock();
4639  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4640  }
4641 
4642  # Option to disable this feature
4643  if ( !$this->mOptions->getCleanSignatures() ) {
4644  return $text;
4645  }
4646 
4647  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4648  # => Move this logic to braceSubstitution()
4649  $substWord = MagicWord::get( 'subst' );
4650  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4651  $substText = '{{' . $substWord->getSynonym( 0 );
4652 
4653  $text = preg_replace( $substRegex, $substText, $text );
4654  $text = self::cleanSigInSig( $text );
4655  $dom = $this->preprocessToDom( $text );
4656  $frame = $this->getPreprocessor()->newFrame();
4657  $text = $frame->expand( $dom );
4658 
4659  if ( !$parsing ) {
4660  $text = $this->mStripState->unstripBoth( $text );
4661  }
4662 
4663  return $text;
4664  }
4665 
4672  public static function cleanSigInSig( $text ) {
4673  $text = preg_replace( '/~{3,5}/', '', $text );
4674  return $text;
4675  }
4676 
4687  $outputType, $clearState = true
4688  ) {
4689  $this->startParse( $title, $options, $outputType, $clearState );
4690  }
4691 
4698  private function startParse( Title $title = null, ParserOptions $options,
4699  $outputType, $clearState = true
4700  ) {
4701  $this->setTitle( $title );
4702  $this->mOptions = $options;
4703  $this->setOutputType( $outputType );
4704  if ( $clearState ) {
4705  $this->clearState();
4706  }
4707  }
4708 
4717  public function transformMsg( $text, $options, $title = null ) {
4718  static $executing = false;
4719 
4720  # Guard against infinite recursion
4721  if ( $executing ) {
4722  return $text;
4723  }
4724  $executing = true;
4725 
4726  if ( !$title ) {
4727  global $wgTitle;
4728  $title = $wgTitle;
4729  }
4730 
4731  $text = $this->preprocess( $text, $title, $options );
4732 
4733  $executing = false;
4734  return $text;
4735  }
4736 
4761  public function setHook( $tag, $callback ) {
4762  $tag = strtolower( $tag );
4763  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4764  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4765  }
4766  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4767  $this->mTagHooks[$tag] = $callback;
4768  if ( !in_array( $tag, $this->mStripList ) ) {
4769  $this->mStripList[] = $tag;
4770  }
4771 
4772  return $oldVal;
4773  }
4774 
4792  public function setTransparentTagHook( $tag, $callback ) {
4793  $tag = strtolower( $tag );
4794  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4795  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4796  }
4797  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4798  $this->mTransparentTagHooks[$tag] = $callback;
4799 
4800  return $oldVal;
4801  }
4802 
4806  public function clearTagHooks() {
4807  $this->mTagHooks = [];
4808  $this->mFunctionTagHooks = [];
4809  $this->mStripList = $this->mDefaultStripList;
4810  }
4811 
4855  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4857 
4858  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4859  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4860 
4861  # Add to function cache
4862  $mw = MagicWord::get( $id );
4863  if ( !$mw ) {
4864  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4865  }
4866 
4867  $synonyms = $mw->getSynonyms();
4868  $sensitive = intval( $mw->isCaseSensitive() );
4869 
4870  foreach ( $synonyms as $syn ) {
4871  # Case
4872  if ( !$sensitive ) {
4873  $syn = $wgContLang->lc( $syn );
4874  }
4875  # Add leading hash
4876  if ( !( $flags & self::SFH_NO_HASH ) ) {
4877  $syn = '#' . $syn;
4878  }
4879  # Remove trailing colon
4880  if ( substr( $syn, -1, 1 ) === ':' ) {
4881  $syn = substr( $syn, 0, -1 );
4882  }
4883  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4884  }
4885  return $oldVal;
4886  }
4887 
4893  public function getFunctionHooks() {
4894  return array_keys( $this->mFunctionHooks );
4895  }
4896 
4907  public function setFunctionTagHook( $tag, $callback, $flags ) {
4908  $tag = strtolower( $tag );
4909  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4910  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4911  }
4912  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4913  $this->mFunctionTagHooks[$tag] : null;
4914  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4915 
4916  if ( !in_array( $tag, $this->mStripList ) ) {
4917  $this->mStripList[] = $tag;
4918  }
4919 
4920  return $old;
4921  }
4922 
4930  public function replaceLinkHolders( &$text, $options = 0 ) {
4931  $this->mLinkHolders->replace( $text );
4932  }
4933 
4941  public function replaceLinkHoldersText( $text ) {
4942  return $this->mLinkHolders->replaceText( $text );
4943  }
4944 
4958  public function renderImageGallery( $text, $params ) {
4959 
4960  $mode = false;
4961  if ( isset( $params['mode'] ) ) {
4962  $mode = $params['mode'];
4963  }
4964 
4965  try {
4966  $ig = ImageGalleryBase::factory( $mode );
4967  } catch ( Exception $e ) {
4968  // If invalid type set, fallback to default.
4969  $ig = ImageGalleryBase::factory( false );
4970  }
4971 
4972  $ig->setContextTitle( $this->mTitle );
4973  $ig->setShowBytes( false );
4974  $ig->setShowFilename( false );
4975  $ig->setParser( $this );
4976  $ig->setHideBadImages();
4977  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
4978 
4979  if ( isset( $params['showfilename'] ) ) {
4980  $ig->setShowFilename( true );
4981  } else {
4982  $ig->setShowFilename( false );
4983  }
4984  if ( isset( $params['caption'] ) ) {
4985  $caption = $params['caption'];
4986  $caption = htmlspecialchars( $caption );
4987  $caption = $this->replaceInternalLinks( $caption );
4988  $ig->setCaptionHtml( $caption );
4989  }
4990  if ( isset( $params['perrow'] ) ) {
4991  $ig->setPerRow( $params['perrow'] );
4992  }
4993  if ( isset( $params['widths'] ) ) {
4994  $ig->setWidths( $params['widths'] );
4995  }
4996  if ( isset( $params['heights'] ) ) {
4997  $ig->setHeights( $params['heights'] );
4998  }
4999  $ig->setAdditionalOptions( $params );
5000 
5001  // Avoid PHP 7.1 warning from passing $this by reference
5002  $parser = $this;
5003  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
5004 
5005  $lines = StringUtils::explode( "\n", $text );
5006  foreach ( $lines as $line ) {
5007  # match lines like these:
5008  # Image:someimage.jpg|This is some image
5009  $matches = [];
5010  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5011  # Skip empty lines
5012  if ( count( $matches ) == 0 ) {
5013  continue;
5014  }
5015 
5016  if ( strpos( $matches[0], '%' ) !== false ) {
5017  $matches[1] = rawurldecode( $matches[1] );
5018  }
5020  if ( is_null( $title ) ) {
5021  # Bogus title. Ignore these so we don't bomb out later.
5022  continue;
5023  }
5024 
5025  # We need to get what handler the file uses, to figure out parameters.
5026  # Note, a hook can overide the file name, and chose an entirely different
5027  # file (which potentially could be of a different type and have different handler).
5028  $options = [];
5029  $descQuery = false;
5030  Hooks::run( 'BeforeParserFetchFileAndTitle',
5031  [ $this, $title, &$options, &$descQuery ] );
5032  # Don't register it now, as TraditionalImageGallery does that later.
5033  $file = $this->fetchFileNoRegister( $title, $options );
5034  $handler = $file ? $file->getHandler() : false;
5035 
5036  $paramMap = [
5037  'img_alt' => 'gallery-internal-alt',
5038  'img_link' => 'gallery-internal-link',
5039  ];
5040  if ( $handler ) {
5041  $paramMap = $paramMap + $handler->getParamMap();
5042  // We don't want people to specify per-image widths.
5043  // Additionally the width parameter would need special casing anyhow.
5044  unset( $paramMap['img_width'] );
5045  }
5046 
5047  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5048 
5049  $label = '';
5050  $alt = '';
5051  $link = '';
5052  $handlerOptions = [];
5053  if ( isset( $matches[3] ) ) {
5054  // look for an |alt= definition while trying not to break existing
5055  // captions with multiple pipes (|) in it, until a more sensible grammar
5056  // is defined for images in galleries
5057 
5058  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5059  // splitting on '|' is a bit odd, and different from makeImage.
5060  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5061  // Protect LanguageConverter markup
5062  $parameterMatches = StringUtils::delimiterExplode(
5063  '-{', '}-', '|', $matches[3], true /* nested */
5064  );
5065 
5066  foreach ( $parameterMatches as $parameterMatch ) {
5067  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5068  if ( $magicName ) {
5069  $paramName = $paramMap[$magicName];
5070 
5071  switch ( $paramName ) {
5072  case 'gallery-internal-alt':
5073  $alt = $this->stripAltText( $match, false );
5074  break;
5075  case 'gallery-internal-link':
5076  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5077  $chars = self::EXT_LINK_URL_CLASS;
5078  $addr = self::EXT_LINK_ADDR;
5079  $prots = $this->mUrlProtocols;
5080  // check to see if link matches an absolute url, if not then it must be a wiki link.
5081  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5082  // Result of LanguageConverter::markNoConversion
5083  // invoked on an external link.
5084  $linkValue = substr( $linkValue, 4, -2 );
5085  }
5086  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5087  $link = $linkValue;
5088  } else {
5089  $localLinkTitle = Title::newFromText( $linkValue );
5090  if ( $localLinkTitle !== null ) {
5091  $link = $localLinkTitle->getLinkURL();
5092  }
5093  }
5094  break;
5095  default:
5096  // Must be a handler specific parameter.
5097  if ( $handler->validateParam( $paramName, $match ) ) {
5098  $handlerOptions[$paramName] = $match;
5099  } else {
5100  // Guess not, consider it as caption.
5101  wfDebug( "$parameterMatch failed parameter validation\n" );
5102  $label = '|' . $parameterMatch;
5103  }
5104  }
5105 
5106  } else {
5107  // Last pipe wins.
5108  $label = '|' . $parameterMatch;
5109  }
5110  }
5111  // Remove the pipe.
5112  $label = substr( $label, 1 );
5113  }
5114 
5115  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5116  }
5117  $html = $ig->toHTML();
5118  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5119  return $html;
5120  }
5121 
5126  public function getImageParams( $handler ) {
5127  if ( $handler ) {
5128  $handlerClass = get_class( $handler );
5129  } else {
5130  $handlerClass = '';
5131  }
5132  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5133  # Initialise static lists
5134  static $internalParamNames = [
5135  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5136  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5137  'bottom', 'text-bottom' ],
5138  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5139  'upright', 'border', 'link', 'alt', 'class' ],
5140  ];
5141  static $internalParamMap;
5142  if ( !$internalParamMap ) {
5143  $internalParamMap = [];
5144  foreach ( $internalParamNames as $type => $names ) {
5145  foreach ( $names as $name ) {
5146  $magicName = str_replace( '-', '_', "img_$name" );
5147  $internalParamMap[$magicName] = [ $type, $name ];
5148  }
5149  }
5150  }
5151 
5152  # Add handler params
5153  $paramMap = $internalParamMap;
5154  if ( $handler ) {
5155  $handlerParamMap = $handler->getParamMap();
5156  foreach ( $handlerParamMap as $magic => $paramName ) {
5157  $paramMap[$magic] = [ 'handler', $paramName ];
5158  }
5159  }
5160  $this->mImageParams[$handlerClass] = $paramMap;
5161  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5162  }
5163  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5164  }
5165 
5174  public function makeImage( $title, $options, $holders = false ) {
5175  # Check if the options text is of the form "options|alt text"
5176  # Options are:
5177  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5178  # * left no resizing, just left align. label is used for alt= only
5179  # * right same, but right aligned
5180  # * none same, but not aligned
5181  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5182  # * center center the image
5183  # * frame Keep original image size, no magnify-button.
5184  # * framed Same as "frame"
5185  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5186  # * upright reduce width for upright images, rounded to full __0 px
5187  # * border draw a 1px border around the image
5188  # * alt Text for HTML alt attribute (defaults to empty)
5189  # * class Set a class for img node
5190  # * link Set the target of the image link. Can be external, interwiki, or local
5191  # vertical-align values (no % or length right now):
5192  # * baseline
5193  # * sub
5194  # * super
5195  # * top
5196  # * text-top
5197  # * middle
5198  # * bottom
5199  # * text-bottom
5200 
5201  # Protect LanguageConverter markup when splitting into parts
5203  '-{', '}-', '|', $options, true /* allow nesting */
5204  );
5205 
5206  # Give extensions a chance to select the file revision for us
5207  $options = [];
5208  $descQuery = false;
5209  Hooks::run( 'BeforeParserFetchFileAndTitle',
5210  [ $this, $title, &$options, &$descQuery ] );
5211  # Fetch and register the file (file title may be different via hooks)
5212  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5213 
5214  # Get parameter map
5215  $handler = $file ? $file->getHandler() : false;
5216 
5217  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5218 
5219  if ( !$file ) {
5220  $this->addTrackingCategory( 'broken-file-category' );
5221  }
5222 
5223  # Process the input parameters
5224  $caption = '';
5225  $params = [ 'frame' => [], 'handler' => [],
5226  'horizAlign' => [], 'vertAlign' => [] ];
5227  $seenformat = false;
5228  foreach ( $parts as $part ) {
5229  $part = trim( $part );
5230  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5231  $validated = false;
5232  if ( isset( $paramMap[$magicName] ) ) {
5233  list( $type, $paramName ) = $paramMap[$magicName];
5234 
5235  # Special case; width and height come in one variable together
5236  if ( $type === 'handler' && $paramName === 'width' ) {
5237  $parsedWidthParam = $this->parseWidthParam( $value );
5238  if ( isset( $parsedWidthParam['width'] ) ) {
5239  $width = $parsedWidthParam['width'];
5240  if ( $handler->validateParam( 'width', $width ) ) {
5241  $params[$type]['width'] = $width;
5242  $validated = true;
5243  }
5244  }
5245  if ( isset( $parsedWidthParam['height'] ) ) {
5246  $height = $parsedWidthParam['height'];
5247  if ( $handler->validateParam( 'height', $height ) ) {
5248  $params[$type]['height'] = $height;
5249  $validated = true;
5250  }
5251  }
5252  # else no validation -- T15436
5253  } else {
5254  if ( $type === 'handler' ) {
5255  # Validate handler parameter
5256  $validated = $handler->validateParam( $paramName, $value );
5257  } else {
5258  # Validate internal parameters
5259  switch ( $paramName ) {
5260  case 'manualthumb':
5261  case 'alt':
5262  case 'class':
5263  # @todo FIXME: Possibly check validity here for
5264  # manualthumb? downstream behavior seems odd with
5265  # missing manual thumbs.
5266  $validated = true;
5267  $value = $this->stripAltText( $value, $holders );
5268  break;
5269  case 'link':
5270  $chars = self::EXT_LINK_URL_CLASS;
5271  $addr = self::EXT_LINK_ADDR;
5272  $prots = $this->mUrlProtocols;
5273  if ( $value === '' ) {
5274  $paramName = 'no-link';
5275  $value = true;
5276  $validated = true;
5277  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5278  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5279  $paramName = 'link-url';
5280  $this->mOutput->addExternalLink( $value );
5281  if ( $this->mOptions->getExternalLinkTarget() ) {
5282  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5283  }
5284  $validated = true;
5285  }
5286  } else {
5287  $linkTitle = Title::newFromText( $value );
5288  if ( $linkTitle ) {
5289  $paramName = 'link-title';
5290  $value = $linkTitle;
5291  $this->mOutput->addLink( $linkTitle );
5292  $validated = true;
5293  }
5294  }
5295  break;
5296  case 'frameless':
5297  case 'framed':
5298  case 'thumbnail':
5299  // use first appearing option, discard others.
5300  $validated = !$seenformat;
5301  $seenformat = true;
5302  break;
5303  default:
5304  # Most other things appear to be empty or numeric...
5305  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5306  }
5307  }
5308 
5309  if ( $validated ) {
5310  $params[$type][$paramName] = $value;
5311  }
5312  }
5313  }
5314  if ( !$validated ) {
5315  $caption = $part;
5316  }
5317  }
5318 
5319  # Process alignment parameters
5320  if ( $params['horizAlign'] ) {
5321  $params['frame']['align'] = key( $params['horizAlign'] );
5322  }
5323  if ( $params['vertAlign'] ) {
5324  $params['frame']['valign'] = key( $params['vertAlign'] );
5325  }
5326 
5327  $params['frame']['caption'] = $caption;
5328 
5329  # Will the image be presented in a frame, with the caption below?
5330  $imageIsFramed = isset( $params['frame']['frame'] )
5331  || isset( $params['frame']['framed'] )
5332  || isset( $params['frame']['thumbnail'] )
5333  || isset( $params['frame']['manualthumb'] );
5334 
5335  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5336  # came to also set the caption, ordinary text after the image -- which
5337  # makes no sense, because that just repeats the text multiple times in
5338  # screen readers. It *also* came to set the title attribute.
5339  # Now that we have an alt attribute, we should not set the alt text to
5340  # equal the caption: that's worse than useless, it just repeats the
5341  # text. This is the framed/thumbnail case. If there's no caption, we
5342  # use the unnamed parameter for alt text as well, just for the time be-
5343  # ing, if the unnamed param is set and the alt param is not.
5344  # For the future, we need to figure out if we want to tweak this more,
5345  # e.g., introducing a title= parameter for the title; ignoring the un-
5346  # named parameter entirely for images without a caption; adding an ex-
5347  # plicit caption= parameter and preserving the old magic unnamed para-
5348  # meter for BC; ...
5349  if ( $imageIsFramed ) { # Framed image
5350  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5351  # No caption or alt text, add the filename as the alt text so
5352  # that screen readers at least get some description of the image
5353  $params['frame']['alt'] = $title->getText();
5354  }
5355  # Do not set $params['frame']['title'] because tooltips don't make sense
5356  # for framed images
5357  } else { # Inline image
5358  if ( !isset( $params['frame']['alt'] ) ) {
5359  # No alt text, use the "caption" for the alt text
5360  if ( $caption !== '' ) {
5361  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5362  } else {
5363  # No caption, fall back to using the filename for the
5364  # alt text
5365  $params['frame']['alt'] = $title->getText();
5366  }
5367  }
5368  # Use the "caption" for the tooltip text
5369  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5370  }
5371 
5372  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5373 
5374  # Linker does the rest
5375  $time = isset( $options['time'] ) ? $options['time'] : false;
5376  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5377  $time, $descQuery, $this->mOptions->getThumbSize() );
5378 
5379  # Give the handler a chance to modify the parser object
5380  if ( $handler ) {
5381  $handler->parserTransformHook( $this, $file );
5382  }
5383 
5384  return $ret;
5385  }
5386 
5392  protected function stripAltText( $caption, $holders ) {
5393  # Strip bad stuff out of the title (tooltip). We can't just use
5394  # replaceLinkHoldersText() here, because if this function is called
5395  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5396  if ( $holders ) {
5397  $tooltip = $holders->replaceText( $caption );
5398  } else {
5399  $tooltip = $this->replaceLinkHoldersText( $caption );
5400  }
5401 
5402  # make sure there are no placeholders in thumbnail attributes
5403  # that are later expanded to html- so expand them now and
5404  # remove the tags
5405  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5406  $tooltip = Sanitizer::stripAllTags( $tooltip );
5407 
5408  return $tooltip;
5409  }
5410 
5416  public function disableCache() {
5417  wfDebug( "Parser output marked as uncacheable.\n" );
5418  if ( !$this->mOutput ) {
5419  throw new MWException( __METHOD__ .
5420  " can only be called when actually parsing something" );
5421  }
5422  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5423  }
5424 
5433  public function attributeStripCallback( &$text, $frame = false ) {
5434  $text = $this->replaceVariables( $text, $frame );
5435  $text = $this->mStripState->unstripBoth( $text );
5436  return $text;
5437  }
5438 
5444  public function getTags() {
5445  return array_merge(
5446  array_keys( $this->mTransparentTagHooks ),
5447  array_keys( $this->mTagHooks ),
5448  array_keys( $this->mFunctionTagHooks )
5449  );
5450  }
5451 
5462  public function replaceTransparentTags( $text ) {
5463  $matches = [];
5464  $elements = array_keys( $this->mTransparentTagHooks );
5465  $text = self::extractTagsAndParams( $elements, $text, $matches );
5466  $replacements = [];
5467 
5468  foreach ( $matches as $marker => $data ) {
5469  list( $element, $content, $params, $tag ) = $data;
5470  $tagName = strtolower( $element );
5471  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5472  $output = call_user_func_array(
5473  $this->mTransparentTagHooks[$tagName],
5474  [ $content, $params, $this ]
5475  );
5476  } else {
5477  $output = $tag;
5478  }
5479  $replacements[$marker] = $output;
5480  }
5481  return strtr( $text, $replacements );
5482  }
5483 
5513  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5514  global $wgTitle; # not generally used but removes an ugly failure mode
5515 
5516  $magicScopeVariable = $this->lock();
5517  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5518  $outText = '';
5519  $frame = $this->getPreprocessor()->newFrame();
5520 
5521  # Process section extraction flags
5522  $flags = 0;
5523  $sectionParts = explode( '-', $sectionId );
5524  $sectionIndex = array_pop( $sectionParts );
5525  foreach ( $sectionParts as $part ) {
5526  if ( $part === 'T' ) {
5527  $flags |= self::PTD_FOR_INCLUSION;
5528  }
5529  }
5530 
5531  # Check for empty input
5532  if ( strval( $text ) === '' ) {
5533  # Only sections 0 and T-0 exist in an empty document
5534  if ( $sectionIndex == 0 ) {
5535  if ( $mode === 'get' ) {
5536  return '';
5537  } else {
5538  return $newText;
5539  }
5540  } else {
5541  if ( $mode === 'get' ) {
5542  return $newText;
5543  } else {
5544  return $text;
5545  }
5546  }
5547  }
5548 
5549  # Preprocess the text
5550  $root = $this->preprocessToDom( $text, $flags );
5551 
5552  # <h> nodes indicate section breaks
5553  # They can only occur at the top level, so we can find them by iterating the root's children
5554  $node = $root->getFirstChild();
5555 
5556  # Find the target section
5557  if ( $sectionIndex == 0 ) {
5558  # Section zero doesn't nest, level=big
5559  $targetLevel = 1000;
5560  } else {
5561  while ( $node ) {
5562  if ( $node->getName() === 'h' ) {
5563  $bits = $node->splitHeading();
5564  if ( $bits['i'] == $sectionIndex ) {
5565  $targetLevel = $bits['level'];
5566  break;
5567  }
5568  }
5569  if ( $mode === 'replace' ) {
5570  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5571  }
5572  $node = $node->getNextSibling();
5573  }
5574  }
5575 
5576  if ( !$node ) {
5577  # Not found
5578  if ( $mode === 'get' ) {
5579  return $newText;
5580  } else {
5581  return $text;
5582  }
5583  }
5584 
5585  # Find the end of the section, including nested sections
5586  do {
5587  if ( $node->getName() === 'h' ) {
5588  $bits = $node->splitHeading();
5589  $curLevel = $bits['level'];
5590  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5591  break;
5592  }
5593  }
5594  if ( $mode === 'get' ) {
5595  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5596  }
5597  $node = $node->getNextSibling();
5598  } while ( $node );
5599 
5600  # Write out the remainder (in replace mode only)
5601  if ( $mode === 'replace' ) {
5602  # Output the replacement text
5603  # Add two newlines on -- trailing whitespace in $newText is conventionally
5604  # stripped by the editor, so we need both newlines to restore the paragraph gap
5605  # Only add trailing whitespace if there is newText
5606  if ( $newText != "" ) {
5607  $outText .= $newText . "\n\n";
5608  }
5609 
5610  while ( $node ) {
5611  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5612  $node = $node->getNextSibling();
5613  }
5614  }
5615 
5616  if ( is_string( $outText ) ) {
5617  # Re-insert stripped tags
5618  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5619  }
5620 
5621  return $outText;
5622  }
5623 
5638  public function getSection( $text, $sectionId, $defaultText = '' ) {
5639  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5640  }
5641 
5654  public function replaceSection( $oldText, $sectionId, $newText ) {
5655  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5656  }
5657 
5663  public function getRevisionId() {
5664  return $this->mRevisionId;
5665  }
5666 
5673  public function getRevisionObject() {
5674  if ( !is_null( $this->mRevisionObject ) ) {
5675  return $this->mRevisionObject;
5676  }
5677  if ( is_null( $this->mRevisionId ) ) {
5678  return null;
5679  }
5680 
5681  $rev = call_user_func(
5682  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5683  );
5684 
5685  # If the parse is for a new revision, then the callback should have
5686  # already been set to force the object and should match mRevisionId.
5687  # If not, try to fetch by mRevisionId for sanity.
5688  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5689  $rev = Revision::newFromId( $this->mRevisionId );
5690  }
5691 
5692  $this->mRevisionObject = $rev;
5693 
5694  return $this->mRevisionObject;
5695  }
5696 
5702  public function getRevisionTimestamp() {
5703  if ( is_null( $this->mRevisionTimestamp ) ) {
5705 
5706  $revObject = $this->getRevisionObject();
5707  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5708 
5709  # The cryptic '' timezone parameter tells to use the site-default
5710  # timezone offset instead of the user settings.
5711  # Since this value will be saved into the parser cache, served
5712  # to other users, and potentially even used inside links and such,
5713  # it needs to be consistent for all visitors.
5714  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5715 
5716  }
5717  return $this->mRevisionTimestamp;
5718  }
5719 
5725  public function getRevisionUser() {
5726  if ( is_null( $this->mRevisionUser ) ) {
5727  $revObject = $this->getRevisionObject();
5728 
5729  # if this template is subst: the revision id will be blank,
5730  # so just use the current user's name
5731  if ( $revObject ) {
5732  $this->mRevisionUser = $revObject->getUserText();
5733  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5734  $this->mRevisionUser = $this->getUser()->getName();
5735  }
5736  }
5737  return $this->mRevisionUser;
5738  }
5739 
5745  public function getRevisionSize() {
5746  if ( is_null( $this->mRevisionSize ) ) {
5747  $revObject = $this->getRevisionObject();
5748 
5749  # if this variable is subst: the revision id will be blank,
5750  # so just use the parser input size, because the own substituation
5751  # will change the size.
5752  if ( $revObject ) {
5753  $this->mRevisionSize = $revObject->getSize();
5754  } else {
5755  $this->mRevisionSize = $this->mInputSize;
5756  }
5757  }
5758  return $this->mRevisionSize;
5759  }
5760 
5766  public function setDefaultSort( $sort ) {
5767  $this->mDefaultSort = $sort;
5768  $this->mOutput->setProperty( 'defaultsort', $sort );
5769  }
5770 
5781  public function getDefaultSort() {
5782  if ( $this->mDefaultSort !== false ) {
5783  return $this->mDefaultSort;
5784  } else {
5785  return '';
5786  }
5787  }
5788 
5795  public function getCustomDefaultSort() {
5796  return $this->mDefaultSort;
5797  }
5798 
5808  public function guessSectionNameFromWikiText( $text ) {
5809  # Strip out wikitext links(they break the anchor)
5810  $text = $this->stripSectionName( $text );
5812  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5813  }
5814 
5823  public function guessLegacySectionNameFromWikiText( $text ) {
5824  # Strip out wikitext links(they break the anchor)
5825  $text = $this->stripSectionName( $text );
5827  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5828  }
5829 
5844  public function stripSectionName( $text ) {
5845  # Strip internal link markup
5846  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5847  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5848 
5849  # Strip external link markup
5850  # @todo FIXME: Not tolerant to blank link text
5851  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5852  # on how many empty links there are on the page - need to figure that out.
5853  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5854 
5855  # Parse wikitext quotes (italics & bold)
5856  $text = $this->doQuotes( $text );
5857 
5858  # Strip HTML tags
5859  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5860  return $text;
5861  }
5862 
5873  public function testSrvus( $text, Title $title, ParserOptions $options,
5874  $outputType = self::OT_HTML
5875  ) {
5876  $magicScopeVariable = $this->lock();
5877  $this->startParse( $title, $options, $outputType, true );
5878 
5879  $text = $this->replaceVariables( $text );
5880  $text = $this->mStripState->unstripBoth( $text );
5881  $text = Sanitizer::removeHTMLtags( $text );
5882  return $text;
5883  }
5884 
5891  public function testPst( $text, Title $title, ParserOptions $options ) {
5892  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5893  }
5894 
5901  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5902  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5903  }
5904 
5921  public function markerSkipCallback( $s, $callback ) {
5922  $i = 0;
5923  $out = '';
5924  while ( $i < strlen( $s ) ) {
5925  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5926  if ( $markerStart === false ) {
5927  $out .= call_user_func( $callback, substr( $s, $i ) );
5928  break;
5929  } else {
5930  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5931  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5932  if ( $markerEnd === false ) {
5933  $out .= substr( $s, $markerStart );
5934  break;
5935  } else {
5936  $markerEnd += strlen( self::MARKER_SUFFIX );
5937  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5938  $i = $markerEnd;
5939  }
5940  }
5941  }
5942  return $out;
5943  }
5944 
5951  public function killMarkers( $text ) {
5952  return $this->mStripState->killMarkers( $text );
5953  }
5954 
5971  public function serializeHalfParsedText( $text ) {
5972  $data = [
5973  'text' => $text,
5974  'version' => self::HALF_PARSED_VERSION,
5975  'stripState' => $this->mStripState->getSubState( $text ),
5976  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5977  ];
5978  return $data;
5979  }
5980 
5996  public function unserializeHalfParsedText( $data ) {
5997  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5998  throw new MWException( __METHOD__ . ': invalid version' );
5999  }
6000 
6001  # First, extract the strip state.
6002  $texts = [ $data['text'] ];
6003  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6004 
6005  # Now renumber links
6006  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6007 
6008  # Should be good to go.
6009  return $texts[0];
6010  }
6011 
6021  public function isValidHalfParsedText( $data ) {
6022  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6023  }
6024 
6033  public function parseWidthParam( $value ) {
6034  $parsedWidthParam = [];
6035  if ( $value === '' ) {
6036  return $parsedWidthParam;
6037  }
6038  $m = [];
6039  # (T15500) In both cases (width/height and width only),
6040  # permit trailing "px" for backward compatibility.
6041  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6042  $width = intval( $m[1] );
6043  $height = intval( $m[2] );
6044  $parsedWidthParam['width'] = $width;
6045  $parsedWidthParam['height'] = $height;
6046  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6047  $width = intval( $value );
6048  $parsedWidthParam['width'] = $width;
6049  }
6050  return $parsedWidthParam;
6051  }
6052 
6062  protected function lock() {
6063  if ( $this->mInParse ) {
6064  throw new MWException( "Parser state cleared while parsing. "
6065  . "Did you call Parser::parse recursively?" );
6066  }
6067  $this->mInParse = true;
6068 
6069  $recursiveCheck = new ScopedCallback( function() {
6070  $this->mInParse = false;
6071  } );
6072 
6073  return $recursiveCheck;
6074  }
6075 
6086  public static function stripOuterParagraph( $html ) {
6087  $m = [];
6088  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6089  if ( strpos( $m[1], '</p>' ) === false ) {
6090  $html = $m[1];
6091  }
6092  }
6093 
6094  return $html;
6095  }
6096 
6107  public function getFreshParser() {
6108  global $wgParserConf;
6109  if ( $this->mInParse ) {
6110  return new $wgParserConf['class']( $wgParserConf );
6111  } else {
6112  return $this;
6113  }
6114  }
6115 
6122  public function enableOOUI() {
6124  $this->mOutput->setEnableOOUI( true );
6125  }
6126 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5673
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:544
setTitle($t)
Set the context title.
Definition: Parser.php:767
$mAutonumber
Definition: Parser.php:179
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3269
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5921
$mPPNodeCount
Definition: Parser.php:193
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2111
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:272
getExternalLinkAttribs($url)
Get an associative array of additional HTML attributes appropriate for a particular external link...
Definition: Parser.php:1950
const MARKER_PREFIX
Definition: Parser.php:136
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:6021
null means default in associative array form
Definition: hooks.txt:1944
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:1944
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1547
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:5638
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1289
$mTplRedirCache
Definition: Parser.php:195
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5951
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:1559
LinkRenderer $mLinkRenderer
Definition: Parser.php:259
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3540
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:781
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5725
setFunctionTagHook($tag, $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4907
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:5844
const OT_PREPROCESS
Definition: Defines.php:182
either a plain
Definition: hooks.txt:1995
$mDoubleUnderscores
Definition: Parser.php:195
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:2503
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:715
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4621
MapCacheLRU null $currentRevisionCache
Definition: Parser.php:245
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3180
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4958
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:696
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1850
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
nextLinkID()
Definition: Parser.php:856
const SPACE_NOT_NL
Definition: Parser.php:105
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:1944
getImageParams($handler)
Definition: Parser.php:5126
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:1629
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:116
getTags()
Accessor.
Definition: Parser.php:5444
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your own If you create new you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1046
static isWellFormedXmlFragment($text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:698
const OT_WIKI
Definition: Parser.php:113
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2110
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3685
User $mUser
Definition: Parser.php:202
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:2855
static isEnabled()
Definition: MWTidy.php:79
Set options of the Parser.
static tidy($text)
Interface with html tidy.
Definition: MWTidy.php:46
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:4893
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:1071
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:871
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3788
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5873
uniqPrefix()
Accessor for mUniqPrefix.
Definition: Parser.php:757
const TOC_START
Definition: Parser.php:139
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:795
SectionProfiler $mProfiler
Definition: Parser.php:254
$sort
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3710
null for the local wiki Added in
Definition: hooks.txt:1560
There are three types of nodes:
$mHeadings
Definition: Parser.php:195
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4806
static makeSelfLinkObj($nt, $html= '', $query= '', $trail= '', $prefix= '')
Make appropriate markup for a link to the current article.
Definition: Linker.php:181
const NS_SPECIAL
Definition: Defines.php:49
clearState()
Clear Parser state.
Definition: Parser.php:345
__construct($conf=[])
Definition: Parser.php:264
const EXT_LINK_ADDR
Definition: Parser.php:97
$mFirstCall
Definition: Parser.php:154
interwikiTransclude($title, $action)
Transclude an interwiki link.
Definition: Parser.php:3729
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4496
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead.
Definition: Parser.php:5823
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:849
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2733
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5971
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4930
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3523
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your own If you create new you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1046
static activeUsers()
Definition: SiteStats.php:165
$mLinkID
Definition: Parser.php:192
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1662
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2885
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:3007
static cleanUrl($url)
Definition: Sanitizer.php:1858
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:262
$mGeneratedPPNodeCount
Definition: Parser.php:193
static getRandomString()
Get a random string.
Definition: Parser.php:736
$mRevisionId
Definition: Parser.php:219
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
Definition: Sanitizer.php:1825
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:2482
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:821
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:938
const NS_TEMPLATE
Definition: Defines.php:70
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:131
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2497
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:627
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2503
MagicWordArray $mVariables
Definition: Parser.php:161
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:748
const SFH_NO_HASH
Definition: Parser.php:85
const DB_MASTER
Definition: defines.php:25
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
wfRandomString($length=32)
Get a random string containing a number of pseudo-random hex characters.
$mForceTocPosition
Definition: Parser.php:197
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:670
static getCacheTTL($id)
Allow external reads of TTL array.
Definition: MagicWord.php:295
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5663
const OT_PREPROCESS
Definition: Parser.php:114
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2470
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your own If you create new you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:1022
$mFunctionSynonyms
Definition: Parser.php:146
If you want to remove the page from your watchlist later
replaceLinkHoldersText($text)
Replace "" link placeholders with plain text of links (not HTML-formatted).
Definition: Parser.php:4941
setLinkID($id)
Definition: Parser.php:863
$mOutputType
Definition: Parser.php:216
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:149
static createAssocArgs($args)
Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
Definition: Parser.php:2959
$mExtLinkBracketedRegex
Definition: Parser.php:168
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 '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:1942
if($line===false) $args
Definition: cdb.php:63
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your own If you create new you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition: hooks.txt:1046
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:308
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:1634
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3457
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2913
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1502
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4635
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:133
$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:4686
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:4030
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2098
$mVarCache
Definition: Parser.php:150
$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:5416
$mRevisionObject
Definition: Parser.php:218
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:1383
internalParse($text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1272
Title $mTitle
Definition: Parser.php:215
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:290
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:5702
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6086
static register($parser)
$mRevIdForTs
Definition: Parser.php:223
static singleton()
Get an instance of this class.
Definition: LinkCache.php:64
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:1353
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6033
$mStripList
Definition: Parser.php:148
$mFunctionTagHooks
Definition: Parser.php:147
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3748
const OT_PLAIN
Definition: Defines.php:184
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3500
$mRevisionTimestamp
Definition: Parser.php:220
$mImageParams
Definition: Parser.php:151
stripAltText($caption, $holders)
Definition: Parser.php:5392
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1645
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:2016
const VERSION
Update this version number when the ParserOutput format changes in an incompatible way...
Definition: Parser.php:76
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your own If you create new you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
setHook($tag, $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4761
const OT_WIKI
Definition: Defines.php:181
Preprocessor $mPreprocessor
Definition: Parser.php:172
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:924
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:48
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
replaceVariables($text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2930
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:184
$mDefaultSort
Definition: Parser.php:194
getUser()
Get a User object either from $this->mUser, if set, or from the ParserOptions object otherwise...
Definition: Parser.php:912
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3949
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1041
const EXT_IMAGE_REGEX
Definition: Parser.php:100
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4698
$params
const NS_CATEGORY
Definition: Defines.php:74
static makeHeadline($level, $attribs, $anchor, $html, $link, $legacyAnchor=false)
Create a headline for content.
Definition: Linker.php:1615
static extractTagsAndParams($elements, $text, &$matches, $uniq_prefix=null)
Replaces all occurrences of HTML-style comments and the given tags in the text with a random marker a...
Definition: Parser.php:971
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:1068
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:5745
$mImageParamsMagicArray
Definition: Parser.php:152
LinkHolderArray $mLinkHolders
Definition: Parser.php:190
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:1944
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
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:117
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:4461
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:884
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:932
static newKnownCurrent(IDatabase $db, $pageId, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1918
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:4050
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:902
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1514
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static tocLine($anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1529
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:224
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:4572
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:66
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:325
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:319
const PTD_FOR_INCLUSION
Definition: Parser.php:108
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:1944
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:2448
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:1729
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2897
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:804
$mTagHooks
Definition: Parser.php:143
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:68
static & get($id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:258
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6122
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:246
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3568
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:2039
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2457
const OT_HTML
Definition: Defines.php:180
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1171
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your own If you create new you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1046
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:285
static images()
Definition: SiteStats.php:173
$mTransparentTagHooks
Definition: Parser.php:144
$mExpensiveFunctionCount
Definition: Parser.php:196
$mUrlProtocols
Definition: Parser.php:168
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2520
$mConf
Definition: Parser.php:168
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4717
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:113
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:838
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:302
static getExternalLinkRel($url=false, $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:1929
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:988
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:781
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:1729
array $mLangLinkLanguages
Array with the language name of each language link (i.e.
Definition: Parser.php:237
const OT_MSG
Definition: Parser.php:115
replaceTransparentTags($text)
Replace transparent tags in $text with the values given by the callbacks.
Definition: Parser.php:5462
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:5654
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3976
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your own If you create new you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired 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 inclusive $limit
Definition: hooks.txt:1046
$mFunctionHooks
Definition: Parser.php:145
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:462
$lines
Definition: router.php:67
testPreprocess($text, Title $title, ParserOptions $options)
Definition: Parser.php:5901
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:166
const TOC_END
Definition: Parser.php:140
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1402
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: Message.php:1361
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6107
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:141
$mRevisionUser
Definition: Parser.php:221
lock()
Lock the current instance of the parser.
Definition: Parser.php:6062
static pages()
Definition: SiteStats.php:149
$line
Definition: cdb.php:58
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:2424
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3581
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
setFunctionHook($id, $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4855
static setupOOUI($skinName= '', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php:778
$mIncludeCount
Definition: Parser.php:186
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:2913
$mMarkerIndex
Definition: Parser.php:153
getTitle()
Accessor for the Title object.
Definition: Parser.php:785
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:5513
ParserOutput $mOutput
Definition: Parser.php:178
getOutput()
Get the ParserOutput object.
Definition: Parser.php:830
$wgExperimentalHtmlIds
Should we allow a broader set of characters in id attributes, per HTML5? If not, use only HTML 4-comp...
doMagicLinks($text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
Definition: Parser.php:1449
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:304
static cleanSigInSig($text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4672
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5766
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3674
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1503
error also a ContextSource you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2596
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:593
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:86
incrementExpensiveFunctionCount()
Increment the expensive function count.
Definition: Parser.php:3963
$mShowToc
Definition: Parser.php:197
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1980
const DB_REPLICA
Definition: defines.php:24
magicLinkCallback($m)
Definition: Parser.php:1480
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:1054
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5891
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.
if(!$wgRequest->checkUrlExtension()) if(!$wgEnableAPI) $wgTitle
Definition: api.php:57
static explode($separator, $subject)
Workalike for explode() with limited memory usage.
ParserOptions $mOptions
Definition: Parser.php:210
parse($text, Title $title, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
Definition: Parser.php:405
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 h