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 
593  # Wrap non-interface parser output in a <div> so it can be targeted
594  # with CSS (T37247)
595  $class = $this->mOptions->getWrapOutputClass();
596  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
597  $text = Html::rawElement( 'div', [ 'class' => $class ], $text );
598  }
599 
600  $this->mOutput->setText( $text );
601 
602  $this->mRevisionId = $oldRevisionId;
603  $this->mRevisionObject = $oldRevisionObject;
604  $this->mRevisionTimestamp = $oldRevisionTimestamp;
605  $this->mRevisionUser = $oldRevisionUser;
606  $this->mRevisionSize = $oldRevisionSize;
607  $this->mInputSize = false;
608  $this->currentRevisionCache = null;
609 
610  return $this->mOutput;
611  }
612 
635  public function recursiveTagParse( $text, $frame = false ) {
636  // Avoid PHP 7.1 warning from passing $this by reference
637  $parser = $this;
638  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
639  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
640  $text = $this->internalParse( $text, false, $frame );
641  return $text;
642  }
643 
661  public function recursiveTagParseFully( $text, $frame = false ) {
662  $text = $this->recursiveTagParse( $text, $frame );
663  $text = $this->internalParseHalfParsed( $text, false );
664  return $text;
665  }
666 
678  public function preprocess( $text, Title $title = null,
679  ParserOptions $options, $revid = null, $frame = false
680  ) {
681  $magicScopeVariable = $this->lock();
682  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
683  if ( $revid !== null ) {
684  $this->mRevisionId = $revid;
685  }
686  // Avoid PHP 7.1 warning from passing $this by reference
687  $parser = $this;
688  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
689  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
690  $text = $this->replaceVariables( $text, $frame );
691  $text = $this->mStripState->unstripBoth( $text );
692  return $text;
693  }
694 
704  public function recursivePreprocess( $text, $frame = false ) {
705  $text = $this->replaceVariables( $text, $frame );
706  $text = $this->mStripState->unstripBoth( $text );
707  return $text;
708  }
709 
723  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
724  $msg = new RawMessage( $text );
725  $text = $msg->params( $params )->plain();
726 
727  # Parser (re)initialisation
728  $magicScopeVariable = $this->lock();
729  $this->startParse( $title, $options, self::OT_PLAIN, true );
730 
732  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
733  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
734  $text = $this->mStripState->unstripBoth( $text );
735  return $text;
736  }
737 
744  public static function getRandomString() {
745  wfDeprecated( __METHOD__, '1.26' );
746  return wfRandomString( 16 );
747  }
748 
755  public function setUser( $user ) {
756  $this->mUser = $user;
757  }
758 
765  public function uniqPrefix() {
766  wfDeprecated( __METHOD__, '1.26' );
767  return self::MARKER_PREFIX;
768  }
769 
775  public function setTitle( $t ) {
776  if ( !$t ) {
777  $t = Title::newFromText( 'NO TITLE' );
778  }
779 
780  if ( $t->hasFragment() ) {
781  # Strip the fragment to avoid various odd effects
782  $this->mTitle = $t->createFragmentTarget( '' );
783  } else {
784  $this->mTitle = $t;
785  }
786  }
787 
793  public function getTitle() {
794  return $this->mTitle;
795  }
796 
803  public function Title( $x = null ) {
804  return wfSetVar( $this->mTitle, $x );
805  }
806 
812  public function setOutputType( $ot ) {
813  $this->mOutputType = $ot;
814  # Shortcut alias
815  $this->ot = [
816  'html' => $ot == self::OT_HTML,
817  'wiki' => $ot == self::OT_WIKI,
818  'pre' => $ot == self::OT_PREPROCESS,
819  'plain' => $ot == self::OT_PLAIN,
820  ];
821  }
822 
829  public function OutputType( $x = null ) {
830  return wfSetVar( $this->mOutputType, $x );
831  }
832 
838  public function getOutput() {
839  return $this->mOutput;
840  }
841 
847  public function getOptions() {
848  return $this->mOptions;
849  }
850 
857  public function Options( $x = null ) {
858  return wfSetVar( $this->mOptions, $x );
859  }
860 
864  public function nextLinkID() {
865  return $this->mLinkID++;
866  }
867 
871  public function setLinkID( $id ) {
872  $this->mLinkID = $id;
873  }
874 
879  public function getFunctionLang() {
880  return $this->getTargetLanguage();
881  }
882 
892  public function getTargetLanguage() {
893  $target = $this->mOptions->getTargetLanguage();
894 
895  if ( $target !== null ) {
896  return $target;
897  } elseif ( $this->mOptions->getInterfaceMessage() ) {
898  return $this->mOptions->getUserLangObj();
899  } elseif ( is_null( $this->mTitle ) ) {
900  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
901  }
902 
903  return $this->mTitle->getPageLanguage();
904  }
905 
910  public function getConverterLanguage() {
911  return $this->getTargetLanguage();
912  }
913 
920  public function getUser() {
921  if ( !is_null( $this->mUser ) ) {
922  return $this->mUser;
923  }
924  return $this->mOptions->getUser();
925  }
926 
932  public function getPreprocessor() {
933  if ( !isset( $this->mPreprocessor ) ) {
934  $class = $this->mPreprocessorClass;
935  $this->mPreprocessor = new $class( $this );
936  }
937  return $this->mPreprocessor;
938  }
939 
946  public function getLinkRenderer() {
947  if ( !$this->mLinkRenderer ) {
948  $this->mLinkRenderer = MediaWikiServices::getInstance()
949  ->getLinkRendererFactory()->create();
950  $this->mLinkRenderer->setStubThreshold(
951  $this->getOptions()->getStubThreshold()
952  );
953  }
954 
955  return $this->mLinkRenderer;
956  }
957 
979  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
980  if ( $uniq_prefix !== null ) {
981  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
982  }
983  static $n = 1;
984  $stripped = '';
985  $matches = [];
986 
987  $taglist = implode( '|', $elements );
988  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
989 
990  while ( $text != '' ) {
991  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
992  $stripped .= $p[0];
993  if ( count( $p ) < 5 ) {
994  break;
995  }
996  if ( count( $p ) > 5 ) {
997  # comment
998  $element = $p[4];
999  $attributes = '';
1000  $close = '';
1001  $inside = $p[5];
1002  } else {
1003  # tag
1004  $element = $p[1];
1005  $attributes = $p[2];
1006  $close = $p[3];
1007  $inside = $p[4];
1008  }
1009 
1010  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1011  $stripped .= $marker;
1012 
1013  if ( $close === '/>' ) {
1014  # Empty element tag, <tag />
1015  $content = null;
1016  $text = $inside;
1017  $tail = null;
1018  } else {
1019  if ( $element === '!--' ) {
1020  $end = '/(-->)/';
1021  } else {
1022  $end = "/(<\\/$element\\s*>)/i";
1023  }
1024  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1025  $content = $q[0];
1026  if ( count( $q ) < 3 ) {
1027  # No end tag -- let it run out to the end of the text.
1028  $tail = '';
1029  $text = '';
1030  } else {
1031  $tail = $q[1];
1032  $text = $q[2];
1033  }
1034  }
1035 
1036  $matches[$marker] = [ $element,
1037  $content,
1038  Sanitizer::decodeTagAttributes( $attributes ),
1039  "<$element$attributes$close$content$tail" ];
1040  }
1041  return $stripped;
1042  }
1043 
1049  public function getStripList() {
1050  return $this->mStripList;
1051  }
1052 
1062  public function insertStripItem( $text ) {
1063  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1064  $this->mMarkerIndex++;
1065  $this->mStripState->addGeneral( $marker, $text );
1066  return $marker;
1067  }
1068 
1076  public function doTableStuff( $text ) {
1077 
1078  $lines = StringUtils::explode( "\n", $text );
1079  $out = '';
1080  $td_history = []; # Is currently a td tag open?
1081  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1082  $tr_history = []; # Is currently a tr tag open?
1083  $tr_attributes = []; # history of tr attributes
1084  $has_opened_tr = []; # Did this table open a <tr> element?
1085  $indent_level = 0; # indent level of the table
1086 
1087  foreach ( $lines as $outLine ) {
1088  $line = trim( $outLine );
1089 
1090  if ( $line === '' ) { # empty line, go to next line
1091  $out .= $outLine . "\n";
1092  continue;
1093  }
1094 
1095  $first_character = $line[0];
1096  $first_two = substr( $line, 0, 2 );
1097  $matches = [];
1098 
1099  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1100  # First check if we are starting a new table
1101  $indent_level = strlen( $matches[1] );
1102 
1103  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1104  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1105 
1106  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1107  array_push( $td_history, false );
1108  array_push( $last_tag_history, '' );
1109  array_push( $tr_history, false );
1110  array_push( $tr_attributes, '' );
1111  array_push( $has_opened_tr, false );
1112  } elseif ( count( $td_history ) == 0 ) {
1113  # Don't do any of the following
1114  $out .= $outLine . "\n";
1115  continue;
1116  } elseif ( $first_two === '|}' ) {
1117  # We are ending a table
1118  $line = '</table>' . substr( $line, 2 );
1119  $last_tag = array_pop( $last_tag_history );
1120 
1121  if ( !array_pop( $has_opened_tr ) ) {
1122  $line = "<tr><td></td></tr>{$line}";
1123  }
1124 
1125  if ( array_pop( $tr_history ) ) {
1126  $line = "</tr>{$line}";
1127  }
1128 
1129  if ( array_pop( $td_history ) ) {
1130  $line = "</{$last_tag}>{$line}";
1131  }
1132  array_pop( $tr_attributes );
1133  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1134  } elseif ( $first_two === '|-' ) {
1135  # Now we have a table row
1136  $line = preg_replace( '#^\|-+#', '', $line );
1137 
1138  # Whats after the tag is now only attributes
1139  $attributes = $this->mStripState->unstripBoth( $line );
1140  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1141  array_pop( $tr_attributes );
1142  array_push( $tr_attributes, $attributes );
1143 
1144  $line = '';
1145  $last_tag = array_pop( $last_tag_history );
1146  array_pop( $has_opened_tr );
1147  array_push( $has_opened_tr, true );
1148 
1149  if ( array_pop( $tr_history ) ) {
1150  $line = '</tr>';
1151  }
1152 
1153  if ( array_pop( $td_history ) ) {
1154  $line = "</{$last_tag}>{$line}";
1155  }
1156 
1157  $outLine = $line;
1158  array_push( $tr_history, false );
1159  array_push( $td_history, false );
1160  array_push( $last_tag_history, '' );
1161  } elseif ( $first_character === '|'
1162  || $first_character === '!'
1163  || $first_two === '|+'
1164  ) {
1165  # This might be cell elements, td, th or captions
1166  if ( $first_two === '|+' ) {
1167  $first_character = '+';
1168  $line = substr( $line, 2 );
1169  } else {
1170  $line = substr( $line, 1 );
1171  }
1172 
1173  // Implies both are valid for table headings.
1174  if ( $first_character === '!' ) {
1175  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1176  }
1177 
1178  # Split up multiple cells on the same line.
1179  # FIXME : This can result in improper nesting of tags processed
1180  # by earlier parser steps.
1181  $cells = explode( '||', $line );
1182 
1183  $outLine = '';
1184 
1185  # Loop through each table cell
1186  foreach ( $cells as $cell ) {
1187  $previous = '';
1188  if ( $first_character !== '+' ) {
1189  $tr_after = array_pop( $tr_attributes );
1190  if ( !array_pop( $tr_history ) ) {
1191  $previous = "<tr{$tr_after}>\n";
1192  }
1193  array_push( $tr_history, true );
1194  array_push( $tr_attributes, '' );
1195  array_pop( $has_opened_tr );
1196  array_push( $has_opened_tr, true );
1197  }
1198 
1199  $last_tag = array_pop( $last_tag_history );
1200 
1201  if ( array_pop( $td_history ) ) {
1202  $previous = "</{$last_tag}>\n{$previous}";
1203  }
1204 
1205  if ( $first_character === '|' ) {
1206  $last_tag = 'td';
1207  } elseif ( $first_character === '!' ) {
1208  $last_tag = 'th';
1209  } elseif ( $first_character === '+' ) {
1210  $last_tag = 'caption';
1211  } else {
1212  $last_tag = '';
1213  }
1214 
1215  array_push( $last_tag_history, $last_tag );
1216 
1217  # A cell could contain both parameters and data
1218  $cell_data = explode( '|', $cell, 2 );
1219 
1220  # T2553: Note that a '|' inside an invalid link should not
1221  # be mistaken as delimiting cell parameters
1222  # Bug T153140: Neither should language converter markup.
1223  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1224  $cell = "{$previous}<{$last_tag}>{$cell}";
1225  } elseif ( count( $cell_data ) == 1 ) {
1226  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1227  } else {
1228  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1229  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1230  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1231  }
1232 
1233  $outLine .= $cell;
1234  array_push( $td_history, true );
1235  }
1236  }
1237  $out .= $outLine . "\n";
1238  }
1239 
1240  # Closing open td, tr && table
1241  while ( count( $td_history ) > 0 ) {
1242  if ( array_pop( $td_history ) ) {
1243  $out .= "</td>\n";
1244  }
1245  if ( array_pop( $tr_history ) ) {
1246  $out .= "</tr>\n";
1247  }
1248  if ( !array_pop( $has_opened_tr ) ) {
1249  $out .= "<tr><td></td></tr>\n";
1250  }
1251 
1252  $out .= "</table>\n";
1253  }
1254 
1255  # Remove trailing line-ending (b/c)
1256  if ( substr( $out, -1 ) === "\n" ) {
1257  $out = substr( $out, 0, -1 );
1258  }
1259 
1260  # special case: don't return empty table
1261  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1262  $out = '';
1263  }
1264 
1265  return $out;
1266  }
1267 
1280  public function internalParse( $text, $isMain = true, $frame = false ) {
1281 
1282  $origText = $text;
1283 
1284  // Avoid PHP 7.1 warning from passing $this by reference
1285  $parser = $this;
1286 
1287  # Hook to suspend the parser in this state
1288  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1289  return $text;
1290  }
1291 
1292  # if $frame is provided, then use $frame for replacing any variables
1293  if ( $frame ) {
1294  # use frame depth to infer how include/noinclude tags should be handled
1295  # depth=0 means this is the top-level document; otherwise it's an included document
1296  if ( !$frame->depth ) {
1297  $flag = 0;
1298  } else {
1299  $flag = Parser::PTD_FOR_INCLUSION;
1300  }
1301  $dom = $this->preprocessToDom( $text, $flag );
1302  $text = $frame->expand( $dom );
1303  } else {
1304  # if $frame is not provided, then use old-style replaceVariables
1305  $text = $this->replaceVariables( $text );
1306  }
1307 
1308  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1309  $text = Sanitizer::removeHTMLtags(
1310  $text,
1311  [ $this, 'attributeStripCallback' ],
1312  false,
1313  array_keys( $this->mTransparentTagHooks ),
1314  [],
1315  [ $this, 'addTrackingCategory' ]
1316  );
1317  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1318 
1319  # Tables need to come after variable replacement for things to work
1320  # properly; putting them before other transformations should keep
1321  # exciting things like link expansions from showing up in surprising
1322  # places.
1323  $text = $this->doTableStuff( $text );
1324 
1325  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1326 
1327  $text = $this->doDoubleUnderscore( $text );
1328 
1329  $text = $this->doHeadings( $text );
1330  $text = $this->replaceInternalLinks( $text );
1331  $text = $this->doAllQuotes( $text );
1332  $text = $this->replaceExternalLinks( $text );
1333 
1334  # replaceInternalLinks may sometimes leave behind
1335  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1336  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1337 
1338  $text = $this->doMagicLinks( $text );
1339  $text = $this->formatHeadings( $text, $origText, $isMain );
1340 
1341  return $text;
1342  }
1343 
1353  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1354  $text = $this->mStripState->unstripGeneral( $text );
1355 
1356  // Avoid PHP 7.1 warning from passing $this by reference
1357  $parser = $this;
1358 
1359  if ( $isMain ) {
1360  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1361  }
1362 
1363  # Clean up special characters, only run once, next-to-last before doBlockLevels
1364  $fixtags = [
1365  # French spaces, last one Guillemet-left
1366  # only if there is something before the space
1367  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1368  # french spaces, Guillemet-right
1369  '/(\\302\\253) /' => '\\1&#160;',
1370  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1371  ];
1372  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1373 
1374  $text = $this->doBlockLevels( $text, $linestart );
1375 
1376  $this->replaceLinkHolders( $text );
1377 
1385  if ( !( $this->mOptions->getDisableContentConversion()
1386  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1387  ) {
1388  if ( !$this->mOptions->getInterfaceMessage() ) {
1389  # The position of the convert() call should not be changed. it
1390  # assumes that the links are all replaced and the only thing left
1391  # is the <nowiki> mark.
1392  $text = $this->getConverterLanguage()->convert( $text );
1393  }
1394  }
1395 
1396  $text = $this->mStripState->unstripNoWiki( $text );
1397 
1398  if ( $isMain ) {
1399  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1400  }
1401 
1402  $text = $this->replaceTransparentTags( $text );
1403  $text = $this->mStripState->unstripGeneral( $text );
1404 
1405  $text = Sanitizer::normalizeCharReferences( $text );
1406 
1407  if ( MWTidy::isEnabled() ) {
1408  if ( $this->mOptions->getTidy() ) {
1409  $text = MWTidy::tidy( $text );
1410  }
1411  } else {
1412  # attempt to sanitize at least some nesting problems
1413  # (T4702 and quite a few others)
1414  $tidyregs = [
1415  # ''Something [http://www.cool.com cool''] -->
1416  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1417  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1418  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1419  # fix up an anchor inside another anchor, only
1420  # at least for a single single nested link (T5695)
1421  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1422  '\\1\\2</a>\\3</a>\\1\\4</a>',
1423  # fix div inside inline elements- doBlockLevels won't wrap a line which
1424  # contains a div, so fix it up here; replace
1425  # div with escaped text
1426  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1427  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1428  # remove empty italic or bold tag pairs, some
1429  # introduced by rules above
1430  '/<([bi])><\/\\1>/' => '',
1431  ];
1432 
1433  $text = preg_replace(
1434  array_keys( $tidyregs ),
1435  array_values( $tidyregs ),
1436  $text );
1437  }
1438 
1439  if ( $isMain ) {
1440  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1441  }
1442 
1443  return $text;
1444  }
1445 
1457  public function doMagicLinks( $text ) {
1458  $prots = wfUrlProtocolsWithoutProtRel();
1459  $urlChar = self::EXT_LINK_URL_CLASS;
1460  $addr = self::EXT_LINK_ADDR;
1461  $space = self::SPACE_NOT_NL; # non-newline space
1462  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1463  $spaces = "$space++"; # possessive match of 1 or more spaces
1464  $text = preg_replace_callback(
1465  '!(?: # Start cases
1466  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1467  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1468  (\b # m[3]: Free external links
1469  (?i:$prots)
1470  ($addr$urlChar*) # m[4]: Post-protocol path
1471  ) |
1472  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1473  ([0-9]+)\b |
1474  \bISBN $spaces ( # m[6]: ISBN, capture number
1475  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1476  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1477  [0-9Xx] # check digit
1478  )\b
1479  )!xu", [ $this, 'magicLinkCallback' ], $text );
1480  return $text;
1481  }
1482 
1488  public function magicLinkCallback( $m ) {
1489  if ( isset( $m[1] ) && $m[1] !== '' ) {
1490  # Skip anchor
1491  return $m[0];
1492  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1493  # Skip HTML element
1494  return $m[0];
1495  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1496  # Free external link
1497  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1498  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1499  # RFC or PMID
1500  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1501  if ( !$this->mOptions->getMagicRFCLinks() ) {
1502  return $m[0];
1503  }
1504  $keyword = 'RFC';
1505  $urlmsg = 'rfcurl';
1506  $cssClass = 'mw-magiclink-rfc';
1507  $trackingCat = 'magiclink-tracking-rfc';
1508  $id = $m[5];
1509  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1510  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1511  return $m[0];
1512  }
1513  $keyword = 'PMID';
1514  $urlmsg = 'pubmedurl';
1515  $cssClass = 'mw-magiclink-pmid';
1516  $trackingCat = 'magiclink-tracking-pmid';
1517  $id = $m[5];
1518  } else {
1519  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1520  substr( $m[0], 0, 20 ) . '"' );
1521  }
1522  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1523  $this->addTrackingCategory( $trackingCat );
1524  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1525  } elseif ( isset( $m[6] ) && $m[6] !== ''
1526  && $this->mOptions->getMagicISBNLinks()
1527  ) {
1528  # ISBN
1529  $isbn = $m[6];
1530  $space = self::SPACE_NOT_NL; # non-newline space
1531  $isbn = preg_replace( "/$space/", ' ', $isbn );
1532  $num = strtr( $isbn, [
1533  '-' => '',
1534  ' ' => '',
1535  'x' => 'X',
1536  ] );
1537  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1538  return $this->getLinkRenderer()->makeKnownLink(
1539  SpecialPage::getTitleFor( 'Booksources', $num ),
1540  "ISBN $isbn",
1541  [
1542  'class' => 'internal mw-magiclink-isbn',
1543  'title' => false // suppress title attribute
1544  ]
1545  );
1546  } else {
1547  return $m[0];
1548  }
1549  }
1550 
1560  public function makeFreeExternalLink( $url, $numPostProto ) {
1561  $trail = '';
1562 
1563  # The characters '<' and '>' (which were escaped by
1564  # removeHTMLtags()) should not be included in
1565  # URLs, per RFC 2396.
1566  # Make &nbsp; terminate a URL as well (bug T84937)
1567  $m2 = [];
1568  if ( preg_match(
1569  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1570  $url,
1571  $m2,
1572  PREG_OFFSET_CAPTURE
1573  ) ) {
1574  $trail = substr( $url, $m2[0][1] ) . $trail;
1575  $url = substr( $url, 0, $m2[0][1] );
1576  }
1577 
1578  # Move trailing punctuation to $trail
1579  $sep = ',;\.:!?';
1580  # If there is no left bracket, then consider right brackets fair game too
1581  if ( strpos( $url, '(' ) === false ) {
1582  $sep .= ')';
1583  }
1584 
1585  $urlRev = strrev( $url );
1586  $numSepChars = strspn( $urlRev, $sep );
1587  # Don't break a trailing HTML entity by moving the ; into $trail
1588  # This is in hot code, so use substr_compare to avoid having to
1589  # create a new string object for the comparison
1590  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1591  # more optimization: instead of running preg_match with a $
1592  # anchor, which can be slow, do the match on the reversed
1593  # string starting at the desired offset.
1594  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1595  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1596  $numSepChars--;
1597  }
1598  }
1599  if ( $numSepChars ) {
1600  $trail = substr( $url, -$numSepChars ) . $trail;
1601  $url = substr( $url, 0, -$numSepChars );
1602  }
1603 
1604  # Verify that we still have a real URL after trail removal, and
1605  # not just lone protocol
1606  if ( strlen( $trail ) >= $numPostProto ) {
1607  return $url . $trail;
1608  }
1609 
1610  $url = Sanitizer::cleanUrl( $url );
1611 
1612  # Is this an external image?
1613  $text = $this->maybeMakeExternalImage( $url );
1614  if ( $text === false ) {
1615  # Not an image, make a link
1616  $text = Linker::makeExternalLink( $url,
1617  $this->getConverterLanguage()->markNoConversion( $url, true ),
1618  true, 'free',
1619  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1620  # Register it in the output object...
1621  $this->mOutput->addExternalLink( $url );
1622  }
1623  return $text . $trail;
1624  }
1625 
1635  public function doHeadings( $text ) {
1636  for ( $i = 6; $i >= 1; --$i ) {
1637  $h = str_repeat( '=', $i );
1638  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1639  }
1640  return $text;
1641  }
1642 
1651  public function doAllQuotes( $text ) {
1652  $outtext = '';
1653  $lines = StringUtils::explode( "\n", $text );
1654  foreach ( $lines as $line ) {
1655  $outtext .= $this->doQuotes( $line ) . "\n";
1656  }
1657  $outtext = substr( $outtext, 0, -1 );
1658  return $outtext;
1659  }
1660 
1668  public function doQuotes( $text ) {
1669  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1670  $countarr = count( $arr );
1671  if ( $countarr == 1 ) {
1672  return $text;
1673  }
1674 
1675  // First, do some preliminary work. This may shift some apostrophes from
1676  // being mark-up to being text. It also counts the number of occurrences
1677  // of bold and italics mark-ups.
1678  $numbold = 0;
1679  $numitalics = 0;
1680  for ( $i = 1; $i < $countarr; $i += 2 ) {
1681  $thislen = strlen( $arr[$i] );
1682  // If there are ever four apostrophes, assume the first is supposed to
1683  // be text, and the remaining three constitute mark-up for bold text.
1684  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1685  if ( $thislen == 4 ) {
1686  $arr[$i - 1] .= "'";
1687  $arr[$i] = "'''";
1688  $thislen = 3;
1689  } elseif ( $thislen > 5 ) {
1690  // If there are more than 5 apostrophes in a row, assume they're all
1691  // text except for the last 5.
1692  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1693  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1694  $arr[$i] = "'''''";
1695  $thislen = 5;
1696  }
1697  // Count the number of occurrences of bold and italics mark-ups.
1698  if ( $thislen == 2 ) {
1699  $numitalics++;
1700  } elseif ( $thislen == 3 ) {
1701  $numbold++;
1702  } elseif ( $thislen == 5 ) {
1703  $numitalics++;
1704  $numbold++;
1705  }
1706  }
1707 
1708  // If there is an odd number of both bold and italics, it is likely
1709  // that one of the bold ones was meant to be an apostrophe followed
1710  // by italics. Which one we cannot know for certain, but it is more
1711  // likely to be one that has a single-letter word before it.
1712  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1713  $firstsingleletterword = -1;
1714  $firstmultiletterword = -1;
1715  $firstspace = -1;
1716  for ( $i = 1; $i < $countarr; $i += 2 ) {
1717  if ( strlen( $arr[$i] ) == 3 ) {
1718  $x1 = substr( $arr[$i - 1], -1 );
1719  $x2 = substr( $arr[$i - 1], -2, 1 );
1720  if ( $x1 === ' ' ) {
1721  if ( $firstspace == -1 ) {
1722  $firstspace = $i;
1723  }
1724  } elseif ( $x2 === ' ' ) {
1725  $firstsingleletterword = $i;
1726  // if $firstsingleletterword is set, we don't
1727  // look at the other options, so we can bail early.
1728  break;
1729  } else {
1730  if ( $firstmultiletterword == -1 ) {
1731  $firstmultiletterword = $i;
1732  }
1733  }
1734  }
1735  }
1736 
1737  // If there is a single-letter word, use it!
1738  if ( $firstsingleletterword > -1 ) {
1739  $arr[$firstsingleletterword] = "''";
1740  $arr[$firstsingleletterword - 1] .= "'";
1741  } elseif ( $firstmultiletterword > -1 ) {
1742  // If not, but there's a multi-letter word, use that one.
1743  $arr[$firstmultiletterword] = "''";
1744  $arr[$firstmultiletterword - 1] .= "'";
1745  } elseif ( $firstspace > -1 ) {
1746  // ... otherwise use the first one that has neither.
1747  // (notice that it is possible for all three to be -1 if, for example,
1748  // there is only one pentuple-apostrophe in the line)
1749  $arr[$firstspace] = "''";
1750  $arr[$firstspace - 1] .= "'";
1751  }
1752  }
1753 
1754  // Now let's actually convert our apostrophic mush to HTML!
1755  $output = '';
1756  $buffer = '';
1757  $state = '';
1758  $i = 0;
1759  foreach ( $arr as $r ) {
1760  if ( ( $i % 2 ) == 0 ) {
1761  if ( $state === 'both' ) {
1762  $buffer .= $r;
1763  } else {
1764  $output .= $r;
1765  }
1766  } else {
1767  $thislen = strlen( $r );
1768  if ( $thislen == 2 ) {
1769  if ( $state === 'i' ) {
1770  $output .= '</i>';
1771  $state = '';
1772  } elseif ( $state === 'bi' ) {
1773  $output .= '</i>';
1774  $state = 'b';
1775  } elseif ( $state === 'ib' ) {
1776  $output .= '</b></i><b>';
1777  $state = 'b';
1778  } elseif ( $state === 'both' ) {
1779  $output .= '<b><i>' . $buffer . '</i>';
1780  $state = 'b';
1781  } else { // $state can be 'b' or ''
1782  $output .= '<i>';
1783  $state .= 'i';
1784  }
1785  } elseif ( $thislen == 3 ) {
1786  if ( $state === 'b' ) {
1787  $output .= '</b>';
1788  $state = '';
1789  } elseif ( $state === 'bi' ) {
1790  $output .= '</i></b><i>';
1791  $state = 'i';
1792  } elseif ( $state === 'ib' ) {
1793  $output .= '</b>';
1794  $state = 'i';
1795  } elseif ( $state === 'both' ) {
1796  $output .= '<i><b>' . $buffer . '</b>';
1797  $state = 'i';
1798  } else { // $state can be 'i' or ''
1799  $output .= '<b>';
1800  $state .= 'b';
1801  }
1802  } elseif ( $thislen == 5 ) {
1803  if ( $state === 'b' ) {
1804  $output .= '</b><i>';
1805  $state = 'i';
1806  } elseif ( $state === 'i' ) {
1807  $output .= '</i><b>';
1808  $state = 'b';
1809  } elseif ( $state === 'bi' ) {
1810  $output .= '</i></b>';
1811  $state = '';
1812  } elseif ( $state === 'ib' ) {
1813  $output .= '</b></i>';
1814  $state = '';
1815  } elseif ( $state === 'both' ) {
1816  $output .= '<i><b>' . $buffer . '</b></i>';
1817  $state = '';
1818  } else { // ($state == '')
1819  $buffer = '';
1820  $state = 'both';
1821  }
1822  }
1823  }
1824  $i++;
1825  }
1826  // Now close all remaining tags. Notice that the order is important.
1827  if ( $state === 'b' || $state === 'ib' ) {
1828  $output .= '</b>';
1829  }
1830  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1831  $output .= '</i>';
1832  }
1833  if ( $state === 'bi' ) {
1834  $output .= '</b>';
1835  }
1836  // There might be lonely ''''', so make sure we have a buffer
1837  if ( $state === 'both' && $buffer ) {
1838  $output .= '<b><i>' . $buffer . '</i></b>';
1839  }
1840  return $output;
1841  }
1842 
1856  public function replaceExternalLinks( $text ) {
1857 
1858  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1859  if ( $bits === false ) {
1860  throw new MWException( "PCRE needs to be compiled with "
1861  . "--enable-unicode-properties in order for MediaWiki to function" );
1862  }
1863  $s = array_shift( $bits );
1864 
1865  $i = 0;
1866  while ( $i < count( $bits ) ) {
1867  $url = $bits[$i++];
1868  $i++; // protocol
1869  $text = $bits[$i++];
1870  $trail = $bits[$i++];
1871 
1872  # The characters '<' and '>' (which were escaped by
1873  # removeHTMLtags()) should not be included in
1874  # URLs, per RFC 2396.
1875  $m2 = [];
1876  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1877  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1878  $url = substr( $url, 0, $m2[0][1] );
1879  }
1880 
1881  # If the link text is an image URL, replace it with an <img> tag
1882  # This happened by accident in the original parser, but some people used it extensively
1883  $img = $this->maybeMakeExternalImage( $text );
1884  if ( $img !== false ) {
1885  $text = $img;
1886  }
1887 
1888  $dtrail = '';
1889 
1890  # Set linktype for CSS - if URL==text, link is essentially free
1891  $linktype = ( $text === $url ) ? 'free' : 'text';
1892 
1893  # No link text, e.g. [http://domain.tld/some.link]
1894  if ( $text == '' ) {
1895  # Autonumber
1896  $langObj = $this->getTargetLanguage();
1897  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1898  $linktype = 'autonumber';
1899  } else {
1900  # Have link text, e.g. [http://domain.tld/some.link text]s
1901  # Check for trail
1902  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1903  }
1904 
1905  $text = $this->getConverterLanguage()->markNoConversion( $text );
1906 
1907  $url = Sanitizer::cleanUrl( $url );
1908 
1909  # Use the encoded URL
1910  # This means that users can paste URLs directly into the text
1911  # Funny characters like ö aren't valid in URLs anyway
1912  # This was changed in August 2004
1913  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1914  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1915 
1916  # Register link in the output object.
1917  $this->mOutput->addExternalLink( $url );
1918  }
1919 
1920  return $s;
1921  }
1922 
1932  public static function getExternalLinkRel( $url = false, $title = null ) {
1933  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1934  $ns = $title ? $title->getNamespace() : false;
1935  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1936  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1937  ) {
1938  return 'nofollow';
1939  }
1940  return null;
1941  }
1942 
1953  public function getExternalLinkAttribs( $url ) {
1954  $attribs = [];
1955  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1956 
1957  $target = $this->mOptions->getExternalLinkTarget();
1958  if ( $target ) {
1959  $attribs['target'] = $target;
1960  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1961  // T133507. New windows can navigate parent cross-origin.
1962  // Including noreferrer due to lacking browser
1963  // support of noopener. Eventually noreferrer should be removed.
1964  if ( $rel !== '' ) {
1965  $rel .= ' ';
1966  }
1967  $rel .= 'noreferrer noopener';
1968  }
1969  }
1970  $attribs['rel'] = $rel;
1971  return $attribs;
1972  }
1973 
1983  public static function normalizeLinkUrl( $url ) {
1984  # First, make sure unsafe characters are encoded
1985  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1986  function ( $m ) {
1987  return rawurlencode( $m[0] );
1988  },
1989  $url
1990  );
1991 
1992  $ret = '';
1993  $end = strlen( $url );
1994 
1995  # Fragment part - 'fragment'
1996  $start = strpos( $url, '#' );
1997  if ( $start !== false && $start < $end ) {
1998  $ret = self::normalizeUrlComponent(
1999  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
2000  $end = $start;
2001  }
2002 
2003  # Query part - 'query' minus &=+;
2004  $start = strpos( $url, '?' );
2005  if ( $start !== false && $start < $end ) {
2006  $ret = self::normalizeUrlComponent(
2007  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2008  $end = $start;
2009  }
2010 
2011  # Scheme and path part - 'pchar'
2012  # (we assume no userinfo or encoded colons in the host)
2013  $ret = self::normalizeUrlComponent(
2014  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2015 
2016  return $ret;
2017  }
2018 
2019  private static function normalizeUrlComponent( $component, $unsafe ) {
2020  $callback = function ( $matches ) use ( $unsafe ) {
2021  $char = urldecode( $matches[0] );
2022  $ord = ord( $char );
2023  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2024  # Unescape it
2025  return $char;
2026  } else {
2027  # Leave it escaped, but use uppercase for a-f
2028  return strtoupper( $matches[0] );
2029  }
2030  };
2031  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2032  }
2033 
2042  private function maybeMakeExternalImage( $url ) {
2043  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2044  $imagesexception = !empty( $imagesfrom );
2045  $text = false;
2046  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2047  if ( $imagesexception && is_array( $imagesfrom ) ) {
2048  $imagematch = false;
2049  foreach ( $imagesfrom as $match ) {
2050  if ( strpos( $url, $match ) === 0 ) {
2051  $imagematch = true;
2052  break;
2053  }
2054  }
2055  } elseif ( $imagesexception ) {
2056  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2057  } else {
2058  $imagematch = false;
2059  }
2060 
2061  if ( $this->mOptions->getAllowExternalImages()
2062  || ( $imagesexception && $imagematch )
2063  ) {
2064  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2065  # Image found
2066  $text = Linker::makeExternalImage( $url );
2067  }
2068  }
2069  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2070  && preg_match( self::EXT_IMAGE_REGEX, $url )
2071  ) {
2072  $whitelist = explode(
2073  "\n",
2074  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2075  );
2076 
2077  foreach ( $whitelist as $entry ) {
2078  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2079  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2080  continue;
2081  }
2082  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2083  # Image matches a whitelist entry
2084  $text = Linker::makeExternalImage( $url );
2085  break;
2086  }
2087  }
2088  }
2089  return $text;
2090  }
2091 
2101  public function replaceInternalLinks( $s ) {
2102  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2103  return $s;
2104  }
2105 
2114  public function replaceInternalLinks2( &$s ) {
2116 
2117  static $tc = false, $e1, $e1_img;
2118  # the % is needed to support urlencoded titles as well
2119  if ( !$tc ) {
2120  $tc = Title::legalChars() . '#%';
2121  # Match a link having the form [[namespace:link|alternate]]trail
2122  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2123  # Match cases where there is no "]]", which might still be images
2124  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2125  }
2126 
2127  $holders = new LinkHolderArray( $this );
2128 
2129  # split the entire text string on occurrences of [[
2130  $a = StringUtils::explode( '[[', ' ' . $s );
2131  # get the first element (all text up to first [[), and remove the space we added
2132  $s = $a->current();
2133  $a->next();
2134  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2135  $s = substr( $s, 1 );
2136 
2137  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2138  $e2 = null;
2139  if ( $useLinkPrefixExtension ) {
2140  # Match the end of a line for a word that's not followed by whitespace,
2141  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2143  $charset = $wgContLang->linkPrefixCharset();
2144  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2145  }
2146 
2147  if ( is_null( $this->mTitle ) ) {
2148  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2149  }
2150  $nottalk = !$this->mTitle->isTalkPage();
2151 
2152  if ( $useLinkPrefixExtension ) {
2153  $m = [];
2154  if ( preg_match( $e2, $s, $m ) ) {
2155  $first_prefix = $m[2];
2156  } else {
2157  $first_prefix = false;
2158  }
2159  } else {
2160  $prefix = '';
2161  }
2162 
2163  $useSubpages = $this->areSubpagesAllowed();
2164 
2165  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2166  # Loop for each link
2167  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2168  // @codingStandardsIgnoreEnd
2169 
2170  # Check for excessive memory usage
2171  if ( $holders->isBig() ) {
2172  # Too big
2173  # Do the existence check, replace the link holders and clear the array
2174  $holders->replace( $s );
2175  $holders->clear();
2176  }
2177 
2178  if ( $useLinkPrefixExtension ) {
2179  if ( preg_match( $e2, $s, $m ) ) {
2180  $prefix = $m[2];
2181  $s = $m[1];
2182  } else {
2183  $prefix = '';
2184  }
2185  # first link
2186  if ( $first_prefix ) {
2187  $prefix = $first_prefix;
2188  $first_prefix = false;
2189  }
2190  }
2191 
2192  $might_be_img = false;
2193 
2194  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2195  $text = $m[2];
2196  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2197  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2198  # the real problem is with the $e1 regex
2199  # See T1500.
2200  # Still some problems for cases where the ] is meant to be outside punctuation,
2201  # and no image is in sight. See T4095.
2202  if ( $text !== ''
2203  && substr( $m[3], 0, 1 ) === ']'
2204  && strpos( $text, '[' ) !== false
2205  ) {
2206  $text .= ']'; # so that replaceExternalLinks($text) works later
2207  $m[3] = substr( $m[3], 1 );
2208  }
2209  # fix up urlencoded title texts
2210  if ( strpos( $m[1], '%' ) !== false ) {
2211  # Should anchors '#' also be rejected?
2212  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2213  }
2214  $trail = $m[3];
2215  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2216  # Invalid, but might be an image with a link in its caption
2217  $might_be_img = true;
2218  $text = $m[2];
2219  if ( strpos( $m[1], '%' ) !== false ) {
2220  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2221  }
2222  $trail = "";
2223  } else { # Invalid form; output directly
2224  $s .= $prefix . '[[' . $line;
2225  continue;
2226  }
2227 
2228  $origLink = ltrim( $m[1], ' ' );
2229 
2230  # Don't allow internal links to pages containing
2231  # PROTO: where PROTO is a valid URL protocol; these
2232  # should be external links.
2233  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2234  $s .= $prefix . '[[' . $line;
2235  continue;
2236  }
2237 
2238  # Make subpage if necessary
2239  if ( $useSubpages ) {
2240  $link = $this->maybeDoSubpageLink( $origLink, $text );
2241  } else {
2242  $link = $origLink;
2243  }
2244 
2245  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2246  if ( !$noforce ) {
2247  # Strip off leading ':'
2248  $link = substr( $link, 1 );
2249  }
2250 
2251  $unstrip = $this->mStripState->unstripNoWiki( $link );
2252  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2253  if ( $nt === null ) {
2254  $s .= $prefix . '[[' . $line;
2255  continue;
2256  }
2257 
2258  $ns = $nt->getNamespace();
2259  $iw = $nt->getInterwiki();
2260 
2261  if ( $might_be_img ) { # if this is actually an invalid link
2262  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2263  $found = false;
2264  while ( true ) {
2265  # look at the next 'line' to see if we can close it there
2266  $a->next();
2267  $next_line = $a->current();
2268  if ( $next_line === false || $next_line === null ) {
2269  break;
2270  }
2271  $m = explode( ']]', $next_line, 3 );
2272  if ( count( $m ) == 3 ) {
2273  # the first ]] closes the inner link, the second the image
2274  $found = true;
2275  $text .= "[[{$m[0]}]]{$m[1]}";
2276  $trail = $m[2];
2277  break;
2278  } elseif ( count( $m ) == 2 ) {
2279  # if there's exactly one ]] that's fine, we'll keep looking
2280  $text .= "[[{$m[0]}]]{$m[1]}";
2281  } else {
2282  # if $next_line is invalid too, we need look no further
2283  $text .= '[[' . $next_line;
2284  break;
2285  }
2286  }
2287  if ( !$found ) {
2288  # we couldn't find the end of this imageLink, so output it raw
2289  # but don't ignore what might be perfectly normal links in the text we've examined
2290  $holders->merge( $this->replaceInternalLinks2( $text ) );
2291  $s .= "{$prefix}[[$link|$text";
2292  # note: no $trail, because without an end, there *is* no trail
2293  continue;
2294  }
2295  } else { # it's not an image, so output it raw
2296  $s .= "{$prefix}[[$link|$text";
2297  # note: no $trail, because without an end, there *is* no trail
2298  continue;
2299  }
2300  }
2301 
2302  $wasblank = ( $text == '' );
2303  if ( $wasblank ) {
2304  $text = $link;
2305  } else {
2306  # T6598 madness. Handle the quotes only if they come from the alternate part
2307  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2308  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2309  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2310  $text = $this->doQuotes( $text );
2311  }
2312 
2313  # Link not escaped by : , create the various objects
2314  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2315  # Interwikis
2316  if (
2317  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2318  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2319  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2320  )
2321  ) {
2322  # T26502: filter duplicates
2323  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2324  $this->mLangLinkLanguages[$iw] = true;
2325  $this->mOutput->addLanguageLink( $nt->getFullText() );
2326  }
2327 
2328  $s = rtrim( $s . $prefix );
2329  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2330  continue;
2331  }
2332 
2333  if ( $ns == NS_FILE ) {
2334  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2335  if ( $wasblank ) {
2336  # if no parameters were passed, $text
2337  # becomes something like "File:Foo.png",
2338  # which we don't want to pass on to the
2339  # image generator
2340  $text = '';
2341  } else {
2342  # recursively parse links inside the image caption
2343  # actually, this will parse them in any other parameters, too,
2344  # but it might be hard to fix that, and it doesn't matter ATM
2345  $text = $this->replaceExternalLinks( $text );
2346  $holders->merge( $this->replaceInternalLinks2( $text ) );
2347  }
2348  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2349  $s .= $prefix . $this->armorLinks(
2350  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2351  continue;
2352  }
2353  } elseif ( $ns == NS_CATEGORY ) {
2354  $s = rtrim( $s . "\n" ); # T2087
2355 
2356  if ( $wasblank ) {
2357  $sortkey = $this->getDefaultSort();
2358  } else {
2359  $sortkey = $text;
2360  }
2361  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2362  $sortkey = str_replace( "\n", '', $sortkey );
2363  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2364  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2365 
2369  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2370 
2371  continue;
2372  }
2373  }
2374 
2375  # Self-link checking. For some languages, variants of the title are checked in
2376  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2377  # for linking to a different variant.
2378  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2379  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2380  continue;
2381  }
2382 
2383  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2384  # @todo FIXME: Should do batch file existence checks, see comment below
2385  if ( $ns == NS_MEDIA ) {
2386  # Give extensions a chance to select the file revision for us
2387  $options = [];
2388  $descQuery = false;
2389  Hooks::run( 'BeforeParserFetchFileAndTitle',
2390  [ $this, $nt, &$options, &$descQuery ] );
2391  # Fetch and register the file (file title may be different via hooks)
2392  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2393  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2394  $s .= $prefix . $this->armorLinks(
2395  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2396  continue;
2397  }
2398 
2399  # Some titles, such as valid special pages or files in foreign repos, should
2400  # be shown as bluelinks even though they're not included in the page table
2401  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2402  # batch file existence checks for NS_FILE and NS_MEDIA
2403  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2404  $this->mOutput->addLink( $nt );
2405  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2406  } else {
2407  # Links will be added to the output link list after checking
2408  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2409  }
2410  }
2411  return $holders;
2412  }
2413 
2427  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2428  list( $inside, $trail ) = Linker::splitTrail( $trail );
2429 
2430  if ( $text == '' ) {
2431  $text = htmlspecialchars( $nt->getPrefixedText() );
2432  }
2433 
2434  $link = $this->getLinkRenderer()->makeKnownLink(
2435  $nt, new HtmlArmor( "$prefix$text$inside" )
2436  );
2437 
2438  return $this->armorLinks( $link ) . $trail;
2439  }
2440 
2451  public function armorLinks( $text ) {
2452  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2453  self::MARKER_PREFIX . "NOPARSE$1", $text );
2454  }
2455 
2460  public function areSubpagesAllowed() {
2461  # Some namespaces don't allow subpages
2462  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2463  }
2464 
2473  public function maybeDoSubpageLink( $target, &$text ) {
2474  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2475  }
2476 
2485  public function doBlockLevels( $text, $linestart ) {
2486  return BlockLevelPass::doBlockLevels( $text, $linestart );
2487  }
2488 
2500  public function getVariableValue( $index, $frame = false ) {
2503 
2504  if ( is_null( $this->mTitle ) ) {
2505  // If no title set, bad things are going to happen
2506  // later. Title should always be set since this
2507  // should only be called in the middle of a parse
2508  // operation (but the unit-tests do funky stuff)
2509  throw new MWException( __METHOD__ . ' Should only be '
2510  . ' called while parsing (no title set)' );
2511  }
2512 
2513  // Avoid PHP 7.1 warning from passing $this by reference
2514  $parser = $this;
2515 
2520  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2521  if ( isset( $this->mVarCache[$index] ) ) {
2522  return $this->mVarCache[$index];
2523  }
2524  }
2525 
2526  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2527  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2528 
2529  $pageLang = $this->getFunctionLang();
2530 
2531  switch ( $index ) {
2532  case '!':
2533  $value = '|';
2534  break;
2535  case 'currentmonth':
2536  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2537  break;
2538  case 'currentmonth1':
2539  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2540  break;
2541  case 'currentmonthname':
2542  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2543  break;
2544  case 'currentmonthnamegen':
2545  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2546  break;
2547  case 'currentmonthabbrev':
2548  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2549  break;
2550  case 'currentday':
2551  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2552  break;
2553  case 'currentday2':
2554  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2555  break;
2556  case 'localmonth':
2557  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2558  break;
2559  case 'localmonth1':
2560  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2561  break;
2562  case 'localmonthname':
2563  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2564  break;
2565  case 'localmonthnamegen':
2566  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2567  break;
2568  case 'localmonthabbrev':
2569  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2570  break;
2571  case 'localday':
2572  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2573  break;
2574  case 'localday2':
2575  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2576  break;
2577  case 'pagename':
2578  $value = wfEscapeWikiText( $this->mTitle->getText() );
2579  break;
2580  case 'pagenamee':
2581  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2582  break;
2583  case 'fullpagename':
2584  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2585  break;
2586  case 'fullpagenamee':
2587  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2588  break;
2589  case 'subpagename':
2590  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2591  break;
2592  case 'subpagenamee':
2593  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2594  break;
2595  case 'rootpagename':
2596  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2597  break;
2598  case 'rootpagenamee':
2599  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2600  ' ',
2601  '_',
2602  $this->mTitle->getRootText()
2603  ) ) );
2604  break;
2605  case 'basepagename':
2606  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2607  break;
2608  case 'basepagenamee':
2609  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2610  ' ',
2611  '_',
2612  $this->mTitle->getBaseText()
2613  ) ) );
2614  break;
2615  case 'talkpagename':
2616  if ( $this->mTitle->canTalk() ) {
2617  $talkPage = $this->mTitle->getTalkPage();
2618  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2619  } else {
2620  $value = '';
2621  }
2622  break;
2623  case 'talkpagenamee':
2624  if ( $this->mTitle->canTalk() ) {
2625  $talkPage = $this->mTitle->getTalkPage();
2626  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2627  } else {
2628  $value = '';
2629  }
2630  break;
2631  case 'subjectpagename':
2632  $subjPage = $this->mTitle->getSubjectPage();
2633  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2634  break;
2635  case 'subjectpagenamee':
2636  $subjPage = $this->mTitle->getSubjectPage();
2637  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2638  break;
2639  case 'pageid': // requested in T25427
2640  $pageid = $this->getTitle()->getArticleID();
2641  if ( $pageid == 0 ) {
2642  # 0 means the page doesn't exist in the database,
2643  # which means the user is previewing a new page.
2644  # The vary-revision flag must be set, because the magic word
2645  # will have a different value once the page is saved.
2646  $this->mOutput->setFlag( 'vary-revision' );
2647  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2648  }
2649  $value = $pageid ? $pageid : null;
2650  break;
2651  case 'revisionid':
2652  # Let the edit saving system know we should parse the page
2653  # *after* a revision ID has been assigned.
2654  $this->mOutput->setFlag( 'vary-revision-id' );
2655  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2656  $value = $this->mRevisionId;
2657  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2658  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2659  $this->mOutput->setSpeculativeRevIdUsed( $value );
2660  }
2661  break;
2662  case 'revisionday':
2663  # Let the edit saving system know we should parse the page
2664  # *after* a revision ID has been assigned. This is for null edits.
2665  $this->mOutput->setFlag( 'vary-revision' );
2666  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2667  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2668  break;
2669  case 'revisionday2':
2670  # Let the edit saving system know we should parse the page
2671  # *after* a revision ID has been assigned. This is for null edits.
2672  $this->mOutput->setFlag( 'vary-revision' );
2673  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2674  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2675  break;
2676  case 'revisionmonth':
2677  # Let the edit saving system know we should parse the page
2678  # *after* a revision ID has been assigned. This is for null edits.
2679  $this->mOutput->setFlag( 'vary-revision' );
2680  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2681  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2682  break;
2683  case 'revisionmonth1':
2684  # Let the edit saving system know we should parse the page
2685  # *after* a revision ID has been assigned. This is for null edits.
2686  $this->mOutput->setFlag( 'vary-revision' );
2687  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2688  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2689  break;
2690  case 'revisionyear':
2691  # Let the edit saving system know we should parse the page
2692  # *after* a revision ID has been assigned. This is for null edits.
2693  $this->mOutput->setFlag( 'vary-revision' );
2694  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2695  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2696  break;
2697  case 'revisiontimestamp':
2698  # Let the edit saving system know we should parse the page
2699  # *after* a revision ID has been assigned. This is for null edits.
2700  $this->mOutput->setFlag( 'vary-revision' );
2701  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2702  $value = $this->getRevisionTimestamp();
2703  break;
2704  case 'revisionuser':
2705  # Let the edit saving system know we should parse the page
2706  # *after* a revision ID has been assigned for null edits.
2707  $this->mOutput->setFlag( 'vary-user' );
2708  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2709  $value = $this->getRevisionUser();
2710  break;
2711  case 'revisionsize':
2712  $value = $this->getRevisionSize();
2713  break;
2714  case 'namespace':
2715  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2716  break;
2717  case 'namespacee':
2718  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2719  break;
2720  case 'namespacenumber':
2721  $value = $this->mTitle->getNamespace();
2722  break;
2723  case 'talkspace':
2724  $value = $this->mTitle->canTalk()
2725  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2726  : '';
2727  break;
2728  case 'talkspacee':
2729  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2730  break;
2731  case 'subjectspace':
2732  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2733  break;
2734  case 'subjectspacee':
2735  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2736  break;
2737  case 'currentdayname':
2738  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2739  break;
2740  case 'currentyear':
2741  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2742  break;
2743  case 'currenttime':
2744  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2745  break;
2746  case 'currenthour':
2747  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2748  break;
2749  case 'currentweek':
2750  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2751  # int to remove the padding
2752  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2753  break;
2754  case 'currentdow':
2755  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2756  break;
2757  case 'localdayname':
2758  $value = $pageLang->getWeekdayName(
2759  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2760  );
2761  break;
2762  case 'localyear':
2763  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2764  break;
2765  case 'localtime':
2766  $value = $pageLang->time(
2767  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2768  false,
2769  false
2770  );
2771  break;
2772  case 'localhour':
2773  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2774  break;
2775  case 'localweek':
2776  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2777  # int to remove the padding
2778  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2779  break;
2780  case 'localdow':
2781  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2782  break;
2783  case 'numberofarticles':
2784  $value = $pageLang->formatNum( SiteStats::articles() );
2785  break;
2786  case 'numberoffiles':
2787  $value = $pageLang->formatNum( SiteStats::images() );
2788  break;
2789  case 'numberofusers':
2790  $value = $pageLang->formatNum( SiteStats::users() );
2791  break;
2792  case 'numberofactiveusers':
2793  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2794  break;
2795  case 'numberofpages':
2796  $value = $pageLang->formatNum( SiteStats::pages() );
2797  break;
2798  case 'numberofadmins':
2799  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2800  break;
2801  case 'numberofedits':
2802  $value = $pageLang->formatNum( SiteStats::edits() );
2803  break;
2804  case 'currenttimestamp':
2805  $value = wfTimestamp( TS_MW, $ts );
2806  break;
2807  case 'localtimestamp':
2808  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2809  break;
2810  case 'currentversion':
2812  break;
2813  case 'articlepath':
2814  return $wgArticlePath;
2815  case 'sitename':
2816  return $wgSitename;
2817  case 'server':
2818  return $wgServer;
2819  case 'servername':
2820  return $wgServerName;
2821  case 'scriptpath':
2822  return $wgScriptPath;
2823  case 'stylepath':
2824  return $wgStylePath;
2825  case 'directionmark':
2826  return $pageLang->getDirMark();
2827  case 'contentlanguage':
2829  return $wgLanguageCode;
2830  case 'pagelanguage':
2831  $value = $pageLang->getCode();
2832  break;
2833  case 'cascadingsources':
2835  break;
2836  default:
2837  $ret = null;
2838  Hooks::run(
2839  'ParserGetVariableValueSwitch',
2840  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2841  );
2842 
2843  return $ret;
2844  }
2845 
2846  if ( $index ) {
2847  $this->mVarCache[$index] = $value;
2848  }
2849 
2850  return $value;
2851  }
2852 
2858  public function initialiseVariables() {
2859  $variableIDs = MagicWord::getVariableIDs();
2860  $substIDs = MagicWord::getSubstIDs();
2861 
2862  $this->mVariables = new MagicWordArray( $variableIDs );
2863  $this->mSubstWords = new MagicWordArray( $substIDs );
2864  }
2865 
2888  public function preprocessToDom( $text, $flags = 0 ) {
2889  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2890  return $dom;
2891  }
2892 
2900  public static function splitWhitespace( $s ) {
2901  $ltrimmed = ltrim( $s );
2902  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2903  $trimmed = rtrim( $ltrimmed );
2904  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2905  if ( $diff > 0 ) {
2906  $w2 = substr( $ltrimmed, -$diff );
2907  } else {
2908  $w2 = '';
2909  }
2910  return [ $w1, $trimmed, $w2 ];
2911  }
2912 
2933  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2934  # Is there any text? Also, Prevent too big inclusions!
2935  $textSize = strlen( $text );
2936  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2937  return $text;
2938  }
2939 
2940  if ( $frame === false ) {
2941  $frame = $this->getPreprocessor()->newFrame();
2942  } elseif ( !( $frame instanceof PPFrame ) ) {
2943  wfDebug( __METHOD__ . " called using plain parameters instead of "
2944  . "a PPFrame instance. Creating custom frame.\n" );
2945  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2946  }
2947 
2948  $dom = $this->preprocessToDom( $text );
2949  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2950  $text = $frame->expand( $dom, $flags );
2951 
2952  return $text;
2953  }
2954 
2962  public static function createAssocArgs( $args ) {
2963  $assocArgs = [];
2964  $index = 1;
2965  foreach ( $args as $arg ) {
2966  $eqpos = strpos( $arg, '=' );
2967  if ( $eqpos === false ) {
2968  $assocArgs[$index++] = $arg;
2969  } else {
2970  $name = trim( substr( $arg, 0, $eqpos ) );
2971  $value = trim( substr( $arg, $eqpos + 1 ) );
2972  if ( $value === false ) {
2973  $value = '';
2974  }
2975  if ( $name !== false ) {
2976  $assocArgs[$name] = $value;
2977  }
2978  }
2979  }
2980 
2981  return $assocArgs;
2982  }
2983 
3010  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
3011  # does no harm if $current and $max are present but are unnecessary for the message
3012  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
3013  # only during preview, and that would split the parser cache unnecessarily.
3014  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
3015  ->text();
3016  $this->mOutput->addWarning( $warning );
3017  $this->addTrackingCategory( "$limitationType-category" );
3018  }
3019 
3032  public function braceSubstitution( $piece, $frame ) {
3033 
3034  // Flags
3035 
3036  // $text has been filled
3037  $found = false;
3038  // wiki markup in $text should be escaped
3039  $nowiki = false;
3040  // $text is HTML, armour it against wikitext transformation
3041  $isHTML = false;
3042  // Force interwiki transclusion to be done in raw mode not rendered
3043  $forceRawInterwiki = false;
3044  // $text is a DOM node needing expansion in a child frame
3045  $isChildObj = false;
3046  // $text is a DOM node needing expansion in the current frame
3047  $isLocalObj = false;
3048 
3049  # Title object, where $text came from
3050  $title = false;
3051 
3052  # $part1 is the bit before the first |, and must contain only title characters.
3053  # Various prefixes will be stripped from it later.
3054  $titleWithSpaces = $frame->expand( $piece['title'] );
3055  $part1 = trim( $titleWithSpaces );
3056  $titleText = false;
3057 
3058  # Original title text preserved for various purposes
3059  $originalTitle = $part1;
3060 
3061  # $args is a list of argument nodes, starting from index 0, not including $part1
3062  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3063  # below won't work b/c this $args isn't an object
3064  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3065 
3066  $profileSection = null; // profile templates
3067 
3068  # SUBST
3069  if ( !$found ) {
3070  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3071 
3072  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3073  # Decide whether to expand template or keep wikitext as-is.
3074  if ( $this->ot['wiki'] ) {
3075  if ( $substMatch === false ) {
3076  $literal = true; # literal when in PST with no prefix
3077  } else {
3078  $literal = false; # expand when in PST with subst: or safesubst:
3079  }
3080  } else {
3081  if ( $substMatch == 'subst' ) {
3082  $literal = true; # literal when not in PST with plain subst:
3083  } else {
3084  $literal = false; # expand when not in PST with safesubst: or no prefix
3085  }
3086  }
3087  if ( $literal ) {
3088  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3089  $isLocalObj = true;
3090  $found = true;
3091  }
3092  }
3093 
3094  # Variables
3095  if ( !$found && $args->getLength() == 0 ) {
3096  $id = $this->mVariables->matchStartToEnd( $part1 );
3097  if ( $id !== false ) {
3098  $text = $this->getVariableValue( $id, $frame );
3099  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3100  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3101  }
3102  $found = true;
3103  }
3104  }
3105 
3106  # MSG, MSGNW and RAW
3107  if ( !$found ) {
3108  # Check for MSGNW:
3109  $mwMsgnw = MagicWord::get( 'msgnw' );
3110  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3111  $nowiki = true;
3112  } else {
3113  # Remove obsolete MSG:
3114  $mwMsg = MagicWord::get( 'msg' );
3115  $mwMsg->matchStartAndRemove( $part1 );
3116  }
3117 
3118  # Check for RAW:
3119  $mwRaw = MagicWord::get( 'raw' );
3120  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3121  $forceRawInterwiki = true;
3122  }
3123  }
3124 
3125  # Parser functions
3126  if ( !$found ) {
3127  $colonPos = strpos( $part1, ':' );
3128  if ( $colonPos !== false ) {
3129  $func = substr( $part1, 0, $colonPos );
3130  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3131  $argsLength = $args->getLength();
3132  for ( $i = 0; $i < $argsLength; $i++ ) {
3133  $funcArgs[] = $args->item( $i );
3134  }
3135  try {
3136  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3137  } catch ( Exception $ex ) {
3138  throw $ex;
3139  }
3140 
3141  # The interface for parser functions allows for extracting
3142  # flags into the local scope. Extract any forwarded flags
3143  # here.
3144  extract( $result );
3145  }
3146  }
3147 
3148  # Finish mangling title and then check for loops.
3149  # Set $title to a Title object and $titleText to the PDBK
3150  if ( !$found ) {
3151  $ns = NS_TEMPLATE;
3152  # Split the title into page and subpage
3153  $subpage = '';
3154  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3155  if ( $part1 !== $relative ) {
3156  $part1 = $relative;
3157  $ns = $this->mTitle->getNamespace();
3158  }
3159  $title = Title::newFromText( $part1, $ns );
3160  if ( $title ) {
3161  $titleText = $title->getPrefixedText();
3162  # Check for language variants if the template is not found
3163  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3164  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3165  }
3166  # Do recursion depth check
3167  $limit = $this->mOptions->getMaxTemplateDepth();
3168  if ( $frame->depth >= $limit ) {
3169  $found = true;
3170  $text = '<span class="error">'
3171  . wfMessage( 'parser-template-recursion-depth-warning' )
3172  ->numParams( $limit )->inContentLanguage()->text()
3173  . '</span>';
3174  }
3175  }
3176  }
3177 
3178  # Load from database
3179  if ( !$found && $title ) {
3180  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3181  if ( !$title->isExternal() ) {
3182  if ( $title->isSpecialPage()
3183  && $this->mOptions->getAllowSpecialInclusion()
3184  && $this->ot['html']
3185  ) {
3186  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3187  // Pass the template arguments as URL parameters.
3188  // "uselang" will have no effect since the Language object
3189  // is forced to the one defined in ParserOptions.
3190  $pageArgs = [];
3191  $argsLength = $args->getLength();
3192  for ( $i = 0; $i < $argsLength; $i++ ) {
3193  $bits = $args->item( $i )->splitArg();
3194  if ( strval( $bits['index'] ) === '' ) {
3195  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3196  $value = trim( $frame->expand( $bits['value'] ) );
3197  $pageArgs[$name] = $value;
3198  }
3199  }
3200 
3201  // Create a new context to execute the special page
3202  $context = new RequestContext;
3203  $context->setTitle( $title );
3204  $context->setRequest( new FauxRequest( $pageArgs ) );
3205  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3206  $context->setUser( $this->getUser() );
3207  } else {
3208  // If this page is cached, then we better not be per user.
3209  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3210  }
3211  $context->setLanguage( $this->mOptions->getUserLangObj() );
3213  $title, $context, $this->getLinkRenderer() );
3214  if ( $ret ) {
3215  $text = $context->getOutput()->getHTML();
3216  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3217  $found = true;
3218  $isHTML = true;
3219  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3220  $this->mOutput->updateRuntimeAdaptiveExpiry(
3221  $specialPage->maxIncludeCacheTime()
3222  );
3223  }
3224  }
3225  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3226  $found = false; # access denied
3227  wfDebug( __METHOD__ . ": template inclusion denied for " .
3228  $title->getPrefixedDBkey() . "\n" );
3229  } else {
3230  list( $text, $title ) = $this->getTemplateDom( $title );
3231  if ( $text !== false ) {
3232  $found = true;
3233  $isChildObj = true;
3234  }
3235  }
3236 
3237  # If the title is valid but undisplayable, make a link to it
3238  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3239  $text = "[[:$titleText]]";
3240  $found = true;
3241  }
3242  } elseif ( $title->isTrans() ) {
3243  # Interwiki transclusion
3244  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3245  $text = $this->interwikiTransclude( $title, 'render' );
3246  $isHTML = true;
3247  } else {
3248  $text = $this->interwikiTransclude( $title, 'raw' );
3249  # Preprocess it like a template
3250  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3251  $isChildObj = true;
3252  }
3253  $found = true;
3254  }
3255 
3256  # Do infinite loop check
3257  # This has to be done after redirect resolution to avoid infinite loops via redirects
3258  if ( !$frame->loopCheck( $title ) ) {
3259  $found = true;
3260  $text = '<span class="error">'
3261  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3262  . '</span>';
3263  $this->addTrackingCategory( 'template-loop-category' );
3264  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3265  }
3266  }
3267 
3268  # If we haven't found text to substitute by now, we're done
3269  # Recover the source wikitext and return it
3270  if ( !$found ) {
3271  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3272  if ( $profileSection ) {
3273  $this->mProfiler->scopedProfileOut( $profileSection );
3274  }
3275  return [ 'object' => $text ];
3276  }
3277 
3278  # Expand DOM-style return values in a child frame
3279  if ( $isChildObj ) {
3280  # Clean up argument array
3281  $newFrame = $frame->newChild( $args, $title );
3282 
3283  if ( $nowiki ) {
3284  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3285  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3286  # Expansion is eligible for the empty-frame cache
3287  $text = $newFrame->cachedExpand( $titleText, $text );
3288  } else {
3289  # Uncached expansion
3290  $text = $newFrame->expand( $text );
3291  }
3292  }
3293  if ( $isLocalObj && $nowiki ) {
3294  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3295  $isLocalObj = false;
3296  }
3297 
3298  if ( $profileSection ) {
3299  $this->mProfiler->scopedProfileOut( $profileSection );
3300  }
3301 
3302  # Replace raw HTML by a placeholder
3303  if ( $isHTML ) {
3304  $text = $this->insertStripItem( $text );
3305  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3306  # Escape nowiki-style return values
3307  $text = wfEscapeWikiText( $text );
3308  } elseif ( is_string( $text )
3309  && !$piece['lineStart']
3310  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3311  ) {
3312  # T2529: if the template begins with a table or block-level
3313  # element, it should be treated as beginning a new line.
3314  # This behavior is somewhat controversial.
3315  $text = "\n" . $text;
3316  }
3317 
3318  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3319  # Error, oversize inclusion
3320  if ( $titleText !== false ) {
3321  # Make a working, properly escaped link if possible (T25588)
3322  $text = "[[:$titleText]]";
3323  } else {
3324  # This will probably not be a working link, but at least it may
3325  # provide some hint of where the problem is
3326  preg_replace( '/^:/', '', $originalTitle );
3327  $text = "[[:$originalTitle]]";
3328  }
3329  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3330  . 'post-expand include size too large -->' );
3331  $this->limitationWarn( 'post-expand-template-inclusion' );
3332  }
3333 
3334  if ( $isLocalObj ) {
3335  $ret = [ 'object' => $text ];
3336  } else {
3337  $ret = [ 'text' => $text ];
3338  }
3339 
3340  return $ret;
3341  }
3342 
3362  public function callParserFunction( $frame, $function, array $args = [] ) {
3364 
3365  # Case sensitive functions
3366  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3367  $function = $this->mFunctionSynonyms[1][$function];
3368  } else {
3369  # Case insensitive functions
3370  $function = $wgContLang->lc( $function );
3371  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3372  $function = $this->mFunctionSynonyms[0][$function];
3373  } else {
3374  return [ 'found' => false ];
3375  }
3376  }
3377 
3378  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3379 
3380  # Workaround for PHP bug 35229 and similar
3381  if ( !is_callable( $callback ) ) {
3382  throw new MWException( "Tag hook for $function is not callable\n" );
3383  }
3384 
3385  // Avoid PHP 7.1 warning from passing $this by reference
3386  $parser = $this;
3387 
3388  $allArgs = [ &$parser ];
3389  if ( $flags & self::SFH_OBJECT_ARGS ) {
3390  # Convert arguments to PPNodes and collect for appending to $allArgs
3391  $funcArgs = [];
3392  foreach ( $args as $k => $v ) {
3393  if ( $v instanceof PPNode || $k === 0 ) {
3394  $funcArgs[] = $v;
3395  } else {
3396  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3397  }
3398  }
3399 
3400  # Add a frame parameter, and pass the arguments as an array
3401  $allArgs[] = $frame;
3402  $allArgs[] = $funcArgs;
3403  } else {
3404  # Convert arguments to plain text and append to $allArgs
3405  foreach ( $args as $k => $v ) {
3406  if ( $v instanceof PPNode ) {
3407  $allArgs[] = trim( $frame->expand( $v ) );
3408  } elseif ( is_int( $k ) && $k >= 0 ) {
3409  $allArgs[] = trim( $v );
3410  } else {
3411  $allArgs[] = trim( "$k=$v" );
3412  }
3413  }
3414  }
3415 
3416  $result = call_user_func_array( $callback, $allArgs );
3417 
3418  # The interface for function hooks allows them to return a wikitext
3419  # string or an array containing the string and any flags. This mungs
3420  # things around to match what this method should return.
3421  if ( !is_array( $result ) ) {
3422  $result =[
3423  'found' => true,
3424  'text' => $result,
3425  ];
3426  } else {
3427  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3428  $result['text'] = $result[0];
3429  }
3430  unset( $result[0] );
3431  $result += [
3432  'found' => true,
3433  ];
3434  }
3435 
3436  $noparse = true;
3437  $preprocessFlags = 0;
3438  if ( isset( $result['noparse'] ) ) {
3439  $noparse = $result['noparse'];
3440  }
3441  if ( isset( $result['preprocessFlags'] ) ) {
3442  $preprocessFlags = $result['preprocessFlags'];
3443  }
3444 
3445  if ( !$noparse ) {
3446  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3447  $result['isChildObj'] = true;
3448  }
3449 
3450  return $result;
3451  }
3452 
3461  public function getTemplateDom( $title ) {
3462  $cacheTitle = $title;
3463  $titleText = $title->getPrefixedDBkey();
3464 
3465  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3466  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3467  $title = Title::makeTitle( $ns, $dbk );
3468  $titleText = $title->getPrefixedDBkey();
3469  }
3470  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3471  return [ $this->mTplDomCache[$titleText], $title ];
3472  }
3473 
3474  # Cache miss, go to the database
3475  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3476 
3477  if ( $text === false ) {
3478  $this->mTplDomCache[$titleText] = false;
3479  return [ false, $title ];
3480  }
3481 
3482  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3483  $this->mTplDomCache[$titleText] = $dom;
3484 
3485  if ( !$title->equals( $cacheTitle ) ) {
3486  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3487  [ $title->getNamespace(), $title->getDBkey() ];
3488  }
3489 
3490  return [ $dom, $title ];
3491  }
3492 
3505  $cacheKey = $title->getPrefixedDBkey();
3506  if ( !$this->currentRevisionCache ) {
3507  $this->currentRevisionCache = new MapCacheLRU( 100 );
3508  }
3509  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3510  $this->currentRevisionCache->set( $cacheKey,
3511  // Defaults to Parser::statelessFetchRevision()
3512  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3513  );
3514  }
3515  return $this->currentRevisionCache->get( $cacheKey );
3516  }
3517 
3527  public static function statelessFetchRevision( Title $title, $parser = false ) {
3528  $pageId = $title->getArticleID();
3529  $revId = $title->getLatestRevID();
3530 
3532  if ( $rev ) {
3533  $rev->setTitle( $title );
3534  }
3535 
3536  return $rev;
3537  }
3538 
3544  public function fetchTemplateAndTitle( $title ) {
3545  // Defaults to Parser::statelessFetchTemplate()
3546  $templateCb = $this->mOptions->getTemplateCallback();
3547  $stuff = call_user_func( $templateCb, $title, $this );
3548  // We use U+007F DELETE to distinguish strip markers from regular text.
3549  $text = $stuff['text'];
3550  if ( is_string( $stuff['text'] ) ) {
3551  $text = strtr( $text, "\x7f", "?" );
3552  }
3553  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3554  if ( isset( $stuff['deps'] ) ) {
3555  foreach ( $stuff['deps'] as $dep ) {
3556  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3557  if ( $dep['title']->equals( $this->getTitle() ) ) {
3558  // If we transclude ourselves, the final result
3559  // will change based on the new version of the page
3560  $this->mOutput->setFlag( 'vary-revision' );
3561  }
3562  }
3563  }
3564  return [ $text, $finalTitle ];
3565  }
3566 
3572  public function fetchTemplate( $title ) {
3573  return $this->fetchTemplateAndTitle( $title )[0];
3574  }
3575 
3585  public static function statelessFetchTemplate( $title, $parser = false ) {
3586  $text = $skip = false;
3587  $finalTitle = $title;
3588  $deps = [];
3589 
3590  # Loop to fetch the article, with up to 1 redirect
3591  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3592  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3593  // @codingStandardsIgnoreEnd
3594  # Give extensions a chance to select the revision instead
3595  $id = false; # Assume current
3596  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3597  [ $parser, $title, &$skip, &$id ] );
3598 
3599  if ( $skip ) {
3600  $text = false;
3601  $deps[] = [
3602  'title' => $title,
3603  'page_id' => $title->getArticleID(),
3604  'rev_id' => null
3605  ];
3606  break;
3607  }
3608  # Get the revision
3609  if ( $id ) {
3610  $rev = Revision::newFromId( $id );
3611  } elseif ( $parser ) {
3612  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3613  } else {
3615  }
3616  $rev_id = $rev ? $rev->getId() : 0;
3617  # If there is no current revision, there is no page
3618  if ( $id === false && !$rev ) {
3619  $linkCache = LinkCache::singleton();
3620  $linkCache->addBadLinkObj( $title );
3621  }
3622 
3623  $deps[] = [
3624  'title' => $title,
3625  'page_id' => $title->getArticleID(),
3626  'rev_id' => $rev_id ];
3627  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3628  # We fetched a rev from a different title; register it too...
3629  $deps[] = [
3630  'title' => $rev->getTitle(),
3631  'page_id' => $rev->getPage(),
3632  'rev_id' => $rev_id ];
3633  }
3634 
3635  if ( $rev ) {
3636  $content = $rev->getContent();
3637  $text = $content ? $content->getWikitextForTransclusion() : null;
3638 
3639  Hooks::run( 'ParserFetchTemplate',
3640  [ $parser, $title, $rev, &$text, &$deps ] );
3641 
3642  if ( $text === false || $text === null ) {
3643  $text = false;
3644  break;
3645  }
3646  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3648  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3649  if ( !$message->exists() ) {
3650  $text = false;
3651  break;
3652  }
3653  $content = $message->content();
3654  $text = $message->plain();
3655  } else {
3656  break;
3657  }
3658  if ( !$content ) {
3659  break;
3660  }
3661  # Redirect?
3662  $finalTitle = $title;
3663  $title = $content->getRedirectTarget();
3664  }
3665  return [
3666  'text' => $text,
3667  'finalTitle' => $finalTitle,
3668  'deps' => $deps ];
3669  }
3670 
3678  public function fetchFile( $title, $options = [] ) {
3679  return $this->fetchFileAndTitle( $title, $options )[0];
3680  }
3681 
3689  public function fetchFileAndTitle( $title, $options = [] ) {
3690  $file = $this->fetchFileNoRegister( $title, $options );
3691 
3692  $time = $file ? $file->getTimestamp() : false;
3693  $sha1 = $file ? $file->getSha1() : false;
3694  # Register the file as a dependency...
3695  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3696  if ( $file && !$title->equals( $file->getTitle() ) ) {
3697  # Update fetched file title
3698  $title = $file->getTitle();
3699  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3700  }
3701  return [ $file, $title ];
3702  }
3703 
3714  protected function fetchFileNoRegister( $title, $options = [] ) {
3715  if ( isset( $options['broken'] ) ) {
3716  $file = false; // broken thumbnail forced by hook
3717  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3718  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3719  } else { // get by (name,timestamp)
3720  $file = wfFindFile( $title, $options );
3721  }
3722  return $file;
3723  }
3724 
3733  public function interwikiTransclude( $title, $action ) {
3734  global $wgEnableScaryTranscluding;
3735 
3736  if ( !$wgEnableScaryTranscluding ) {
3737  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3738  }
3739 
3740  $url = $title->getFullURL( [ 'action' => $action ] );
3741 
3742  if ( strlen( $url ) > 255 ) {
3743  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3744  }
3745  return $this->fetchScaryTemplateMaybeFromCache( $url );
3746  }
3747 
3752  public function fetchScaryTemplateMaybeFromCache( $url ) {
3753  global $wgTranscludeCacheExpiry;
3754  $dbr = wfGetDB( DB_REPLICA );
3755  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3756  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3757  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3758  if ( $obj ) {
3759  return $obj->tc_contents;
3760  }
3761 
3762  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3763  $status = $req->execute(); // Status object
3764  if ( $status->isOK() ) {
3765  $text = $req->getContent();
3766  } elseif ( $req->getStatus() != 200 ) {
3767  // Though we failed to fetch the content, this status is useless.
3768  return wfMessage( 'scarytranscludefailed-httpstatus' )
3769  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3770  } else {
3771  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3772  }
3773 
3774  $dbw = wfGetDB( DB_MASTER );
3775  $dbw->replace( 'transcache', [ 'tc_url' ], [
3776  'tc_url' => $url,
3777  'tc_time' => $dbw->timestamp( time() ),
3778  'tc_contents' => $text
3779  ] );
3780  return $text;
3781  }
3782 
3792  public function argSubstitution( $piece, $frame ) {
3793 
3794  $error = false;
3795  $parts = $piece['parts'];
3796  $nameWithSpaces = $frame->expand( $piece['title'] );
3797  $argName = trim( $nameWithSpaces );
3798  $object = false;
3799  $text = $frame->getArgument( $argName );
3800  if ( $text === false && $parts->getLength() > 0
3801  && ( $this->ot['html']
3802  || $this->ot['pre']
3803  || ( $this->ot['wiki'] && $frame->isTemplate() )
3804  )
3805  ) {
3806  # No match in frame, use the supplied default
3807  $object = $parts->item( 0 )->getChildren();
3808  }
3809  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3810  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3811  $this->limitationWarn( 'post-expand-template-argument' );
3812  }
3813 
3814  if ( $text === false && $object === false ) {
3815  # No match anywhere
3816  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3817  }
3818  if ( $error !== false ) {
3819  $text .= $error;
3820  }
3821  if ( $object !== false ) {
3822  $ret = [ 'object' => $object ];
3823  } else {
3824  $ret = [ 'text' => $text ];
3825  }
3826 
3827  return $ret;
3828  }
3829 
3845  public function extensionSubstitution( $params, $frame ) {
3846  static $errorStr = '<span class="error">';
3847  static $errorLen = 20;
3848 
3849  $name = $frame->expand( $params['name'] );
3850  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3851  // Probably expansion depth or node count exceeded. Just punt the
3852  // error up.
3853  return $name;
3854  }
3855 
3856  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3857  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3858  // See above
3859  return $attrText;
3860  }
3861 
3862  // We can't safely check if the expansion for $content resulted in an
3863  // error, because the content could happen to be the error string
3864  // (T149622).
3865  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3866 
3867  $marker = self::MARKER_PREFIX . "-$name-"
3868  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3869 
3870  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3871  ( $this->ot['html'] || $this->ot['pre'] );
3872  if ( $isFunctionTag ) {
3873  $markerType = 'none';
3874  } else {
3875  $markerType = 'general';
3876  }
3877  if ( $this->ot['html'] || $isFunctionTag ) {
3878  $name = strtolower( $name );
3879  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3880  if ( isset( $params['attributes'] ) ) {
3881  $attributes = $attributes + $params['attributes'];
3882  }
3883 
3884  if ( isset( $this->mTagHooks[$name] ) ) {
3885  # Workaround for PHP bug 35229 and similar
3886  if ( !is_callable( $this->mTagHooks[$name] ) ) {
3887  throw new MWException( "Tag hook for $name is not callable\n" );
3888  }
3889  $output = call_user_func_array( $this->mTagHooks[$name],
3890  [ $content, $attributes, $this, $frame ] );
3891  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3892  list( $callback, ) = $this->mFunctionTagHooks[$name];
3893  if ( !is_callable( $callback ) ) {
3894  throw new MWException( "Tag hook for $name is not callable\n" );
3895  }
3896 
3897  // Avoid PHP 7.1 warning from passing $this by reference
3898  $parser = $this;
3899  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3900  } else {
3901  $output = '<span class="error">Invalid tag extension name: ' .
3902  htmlspecialchars( $name ) . '</span>';
3903  }
3904 
3905  if ( is_array( $output ) ) {
3906  # Extract flags to local scope (to override $markerType)
3907  $flags = $output;
3908  $output = $flags[0];
3909  unset( $flags[0] );
3910  extract( $flags );
3911  }
3912  } else {
3913  if ( is_null( $attrText ) ) {
3914  $attrText = '';
3915  }
3916  if ( isset( $params['attributes'] ) ) {
3917  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3918  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3919  htmlspecialchars( $attrValue ) . '"';
3920  }
3921  }
3922  if ( $content === null ) {
3923  $output = "<$name$attrText/>";
3924  } else {
3925  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3926  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3927  // See above
3928  return $close;
3929  }
3930  $output = "<$name$attrText>$content$close";
3931  }
3932  }
3933 
3934  if ( $markerType === 'none' ) {
3935  return $output;
3936  } elseif ( $markerType === 'nowiki' ) {
3937  $this->mStripState->addNoWiki( $marker, $output );
3938  } elseif ( $markerType === 'general' ) {
3939  $this->mStripState->addGeneral( $marker, $output );
3940  } else {
3941  throw new MWException( __METHOD__ . ': invalid marker type' );
3942  }
3943  return $marker;
3944  }
3945 
3953  public function incrementIncludeSize( $type, $size ) {
3954  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3955  return false;
3956  } else {
3957  $this->mIncludeSizes[$type] += $size;
3958  return true;
3959  }
3960  }
3961 
3968  $this->mExpensiveFunctionCount++;
3969  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3970  }
3971 
3980  public function doDoubleUnderscore( $text ) {
3981 
3982  # The position of __TOC__ needs to be recorded
3983  $mw = MagicWord::get( 'toc' );
3984  if ( $mw->match( $text ) ) {
3985  $this->mShowToc = true;
3986  $this->mForceTocPosition = true;
3987 
3988  # Set a placeholder. At the end we'll fill it in with the TOC.
3989  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3990 
3991  # Only keep the first one.
3992  $text = $mw->replace( '', $text );
3993  }
3994 
3995  # Now match and remove the rest of them
3997  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3998 
3999  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
4000  $this->mOutput->mNoGallery = true;
4001  }
4002  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
4003  $this->mShowToc = false;
4004  }
4005  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
4006  && $this->mTitle->getNamespace() == NS_CATEGORY
4007  ) {
4008  $this->addTrackingCategory( 'hidden-category-category' );
4009  }
4010  # (T10068) Allow control over whether robots index a page.
4011  # __INDEX__ always overrides __NOINDEX__, see T16899
4012  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
4013  $this->mOutput->setIndexPolicy( 'noindex' );
4014  $this->addTrackingCategory( 'noindex-category' );
4015  }
4016  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4017  $this->mOutput->setIndexPolicy( 'index' );
4018  $this->addTrackingCategory( 'index-category' );
4019  }
4020 
4021  # Cache all double underscores in the database
4022  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4023  $this->mOutput->setProperty( $key, '' );
4024  }
4025 
4026  return $text;
4027  }
4028 
4034  public function addTrackingCategory( $msg ) {
4035  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4036  }
4037 
4054  public function formatHeadings( $text, $origText, $isMain = true ) {
4055  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4056 
4057  # Inhibit editsection links if requested in the page
4058  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4059  $maybeShowEditLink = $showEditLink = false;
4060  } else {
4061  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4062  $showEditLink = $this->mOptions->getEditSection();
4063  }
4064  if ( $showEditLink ) {
4065  $this->mOutput->setEditSectionTokens( true );
4066  }
4067 
4068  # Get all headlines for numbering them and adding funky stuff like [edit]
4069  # links - this is for later, but we need the number of headlines right now
4070  $matches = [];
4071  $numMatches = preg_match_all(
4072  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4073  $text,
4074  $matches
4075  );
4076 
4077  # if there are fewer than 4 headlines in the article, do not show TOC
4078  # unless it's been explicitly enabled.
4079  $enoughToc = $this->mShowToc &&
4080  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4081 
4082  # Allow user to stipulate that a page should have a "new section"
4083  # link added via __NEWSECTIONLINK__
4084  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4085  $this->mOutput->setNewSection( true );
4086  }
4087 
4088  # Allow user to remove the "new section"
4089  # link via __NONEWSECTIONLINK__
4090  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4091  $this->mOutput->hideNewSection( true );
4092  }
4093 
4094  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4095  # override above conditions and always show TOC above first header
4096  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4097  $this->mShowToc = true;
4098  $enoughToc = true;
4099  }
4100 
4101  # headline counter
4102  $headlineCount = 0;
4103  $numVisible = 0;
4104 
4105  # Ugh .. the TOC should have neat indentation levels which can be
4106  # passed to the skin functions. These are determined here
4107  $toc = '';
4108  $full = '';
4109  $head = [];
4110  $sublevelCount = [];
4111  $levelCount = [];
4112  $level = 0;
4113  $prevlevel = 0;
4114  $toclevel = 0;
4115  $prevtoclevel = 0;
4116  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4117  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4118  $oldType = $this->mOutputType;
4119  $this->setOutputType( self::OT_WIKI );
4120  $frame = $this->getPreprocessor()->newFrame();
4121  $root = $this->preprocessToDom( $origText );
4122  $node = $root->getFirstChild();
4123  $byteOffset = 0;
4124  $tocraw = [];
4125  $refers = [];
4126 
4127  $headlines = $numMatches !== false ? $matches[3] : [];
4128 
4129  foreach ( $headlines as $headline ) {
4130  $isTemplate = false;
4131  $titleText = false;
4132  $sectionIndex = false;
4133  $numbering = '';
4134  $markerMatches = [];
4135  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4136  $serial = $markerMatches[1];
4137  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4138  $isTemplate = ( $titleText != $baseTitleText );
4139  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4140  }
4141 
4142  if ( $toclevel ) {
4143  $prevlevel = $level;
4144  }
4145  $level = $matches[1][$headlineCount];
4146 
4147  if ( $level > $prevlevel ) {
4148  # Increase TOC level
4149  $toclevel++;
4150  $sublevelCount[$toclevel] = 0;
4151  if ( $toclevel < $wgMaxTocLevel ) {
4152  $prevtoclevel = $toclevel;
4153  $toc .= Linker::tocIndent();
4154  $numVisible++;
4155  }
4156  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4157  # Decrease TOC level, find level to jump to
4158 
4159  for ( $i = $toclevel; $i > 0; $i-- ) {
4160  if ( $levelCount[$i] == $level ) {
4161  # Found last matching level
4162  $toclevel = $i;
4163  break;
4164  } elseif ( $levelCount[$i] < $level ) {
4165  # Found first matching level below current level
4166  $toclevel = $i + 1;
4167  break;
4168  }
4169  }
4170  if ( $i == 0 ) {
4171  $toclevel = 1;
4172  }
4173  if ( $toclevel < $wgMaxTocLevel ) {
4174  if ( $prevtoclevel < $wgMaxTocLevel ) {
4175  # Unindent only if the previous toc level was shown :p
4176  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4177  $prevtoclevel = $toclevel;
4178  } else {
4179  $toc .= Linker::tocLineEnd();
4180  }
4181  }
4182  } else {
4183  # No change in level, end TOC line
4184  if ( $toclevel < $wgMaxTocLevel ) {
4185  $toc .= Linker::tocLineEnd();
4186  }
4187  }
4188 
4189  $levelCount[$toclevel] = $level;
4190 
4191  # count number of headlines for each level
4192  $sublevelCount[$toclevel]++;
4193  $dot = 0;
4194  for ( $i = 1; $i <= $toclevel; $i++ ) {
4195  if ( !empty( $sublevelCount[$i] ) ) {
4196  if ( $dot ) {
4197  $numbering .= '.';
4198  }
4199  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4200  $dot = 1;
4201  }
4202  }
4203 
4204  # The safe header is a version of the header text safe to use for links
4205 
4206  # Remove link placeholders by the link text.
4207  # <!--LINK number-->
4208  # turns into
4209  # link text with suffix
4210  # Do this before unstrip since link text can contain strip markers
4211  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4212 
4213  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4214  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4215 
4216  # Strip out HTML (first regex removes any tag not allowed)
4217  # Allowed tags are:
4218  # * <sup> and <sub> (T10393)
4219  # * <i> (T28375)
4220  # * <b> (r105284)
4221  # * <bdi> (T74884)
4222  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4223  # * <s> and <strike> (T35715)
4224  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4225  # to allow setting directionality in toc items.
4226  $tocline = preg_replace(
4227  [
4228  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4229  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4230  ],
4231  [ '', '<$1>' ],
4232  $safeHeadline
4233  );
4234 
4235  # Strip '<span></span>', which is the result from the above if
4236  # <span id="foo"></span> is used to produce an additional anchor
4237  # for a section.
4238  $tocline = str_replace( '<span></span>', '', $tocline );
4239 
4240  $tocline = trim( $tocline );
4241 
4242  # For the anchor, strip out HTML-y stuff period
4243  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4244  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4245 
4246  # Save headline for section edit hint before it's escaped
4247  $headlineHint = $safeHeadline;
4248 
4249  if ( $wgExperimentalHtmlIds ) {
4250  # For reverse compatibility, provide an id that's
4251  # HTML4-compatible, like we used to.
4252  # It may be worth noting, academically, that it's possible for
4253  # the legacy anchor to conflict with a non-legacy headline
4254  # anchor on the page. In this case likely the "correct" thing
4255  # would be to either drop the legacy anchors or make sure
4256  # they're numbered first. However, this would require people
4257  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4258  # manually, so let's not bother worrying about it.
4259  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4260  [ 'noninitial', 'legacy' ] );
4261  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4262 
4263  if ( $legacyHeadline == $safeHeadline ) {
4264  # No reason to have both (in fact, we can't)
4265  $legacyHeadline = false;
4266  }
4267  } else {
4268  $legacyHeadline = false;
4269  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4270  'noninitial' );
4271  }
4272 
4273  # HTML names must be case-insensitively unique (T12721).
4274  # This does not apply to Unicode characters per
4275  # https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4276  # @todo FIXME: We may be changing them depending on the current locale.
4277  $arrayKey = strtolower( $safeHeadline );
4278  if ( $legacyHeadline === false ) {
4279  $legacyArrayKey = false;
4280  } else {
4281  $legacyArrayKey = strtolower( $legacyHeadline );
4282  }
4283 
4284  # Create the anchor for linking from the TOC to the section
4285  $anchor = $safeHeadline;
4286  $legacyAnchor = $legacyHeadline;
4287  if ( isset( $refers[$arrayKey] ) ) {
4288  // @codingStandardsIgnoreStart
4289  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4290  // @codingStandardsIgnoreEnd
4291  $anchor .= "_$i";
4292  $refers["${arrayKey}_$i"] = true;
4293  } else {
4294  $refers[$arrayKey] = true;
4295  }
4296  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4297  // @codingStandardsIgnoreStart
4298  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4299  // @codingStandardsIgnoreEnd
4300  $legacyAnchor .= "_$i";
4301  $refers["${legacyArrayKey}_$i"] = true;
4302  } else {
4303  $refers[$legacyArrayKey] = true;
4304  }
4305 
4306  # Don't number the heading if it is the only one (looks silly)
4307  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4308  # the two are different if the line contains a link
4309  $headline = Html::element(
4310  'span',
4311  [ 'class' => 'mw-headline-number' ],
4312  $numbering
4313  ) . ' ' . $headline;
4314  }
4315 
4316  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4317  $toc .= Linker::tocLine( $anchor, $tocline,
4318  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4319  }
4320 
4321  # Add the section to the section tree
4322  # Find the DOM node for this header
4323  $noOffset = ( $isTemplate || $sectionIndex === false );
4324  while ( $node && !$noOffset ) {
4325  if ( $node->getName() === 'h' ) {
4326  $bits = $node->splitHeading();
4327  if ( $bits['i'] == $sectionIndex ) {
4328  break;
4329  }
4330  }
4331  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4332  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4333  $node = $node->getNextSibling();
4334  }
4335  $tocraw[] = [
4336  'toclevel' => $toclevel,
4337  'level' => $level,
4338  'line' => $tocline,
4339  'number' => $numbering,
4340  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4341  'fromtitle' => $titleText,
4342  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4343  'anchor' => $anchor,
4344  ];
4345 
4346  # give headline the correct <h#> tag
4347  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4348  // Output edit section links as markers with styles that can be customized by skins
4349  if ( $isTemplate ) {
4350  # Put a T flag in the section identifier, to indicate to extractSections()
4351  # that sections inside <includeonly> should be counted.
4352  $editsectionPage = $titleText;
4353  $editsectionSection = "T-$sectionIndex";
4354  $editsectionContent = null;
4355  } else {
4356  $editsectionPage = $this->mTitle->getPrefixedText();
4357  $editsectionSection = $sectionIndex;
4358  $editsectionContent = $headlineHint;
4359  }
4360  // We use a bit of pesudo-xml for editsection markers. The
4361  // language converter is run later on. Using a UNIQ style marker
4362  // leads to the converter screwing up the tokens when it
4363  // converts stuff. And trying to insert strip tags fails too. At
4364  // this point all real inputted tags have already been escaped,
4365  // so we don't have to worry about a user trying to input one of
4366  // these markers directly. We use a page and section attribute
4367  // to stop the language converter from converting these
4368  // important bits of data, but put the headline hint inside a
4369  // content block because the language converter is supposed to
4370  // be able to convert that piece of data.
4371  // Gets replaced with html in ParserOutput::getText
4372  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4373  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4374  if ( $editsectionContent !== null ) {
4375  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4376  } else {
4377  $editlink .= '/>';
4378  }
4379  } else {
4380  $editlink = '';
4381  }
4382  $head[$headlineCount] = Linker::makeHeadline( $level,
4383  $matches['attrib'][$headlineCount], $anchor, $headline,
4384  $editlink, $legacyAnchor );
4385 
4386  $headlineCount++;
4387  }
4388 
4389  $this->setOutputType( $oldType );
4390 
4391  # Never ever show TOC if no headers
4392  if ( $numVisible < 1 ) {
4393  $enoughToc = false;
4394  }
4395 
4396  if ( $enoughToc ) {
4397  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4398  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4399  }
4400  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4401  $this->mOutput->setTOCHTML( $toc );
4402  $toc = self::TOC_START . $toc . self::TOC_END;
4403  }
4404 
4405  if ( $isMain ) {
4406  $this->mOutput->setSections( $tocraw );
4407  }
4408 
4409  # split up and insert constructed headlines
4410  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4411  $i = 0;
4412 
4413  // build an array of document sections
4414  $sections = [];
4415  foreach ( $blocks as $block ) {
4416  // $head is zero-based, sections aren't.
4417  if ( empty( $head[$i - 1] ) ) {
4418  $sections[$i] = $block;
4419  } else {
4420  $sections[$i] = $head[$i - 1] . $block;
4421  }
4422 
4433  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4434 
4435  $i++;
4436  }
4437 
4438  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4439  // append the TOC at the beginning
4440  // Top anchor now in skin
4441  $sections[0] = $sections[0] . $toc . "\n";
4442  }
4443 
4444  $full .= implode( '', $sections );
4445 
4446  if ( $this->mForceTocPosition ) {
4447  return str_replace( '<!--MWTOC-->', $toc, $full );
4448  } else {
4449  return $full;
4450  }
4451  }
4452 
4464  public function preSaveTransform( $text, Title $title, User $user,
4465  ParserOptions $options, $clearState = true
4466  ) {
4467  if ( $clearState ) {
4468  $magicScopeVariable = $this->lock();
4469  }
4470  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4471  $this->setUser( $user );
4472 
4473  // Strip U+0000 NULL (T159174)
4474  $text = str_replace( "\000", '', $text );
4475 
4476  // We still normalize line endings for backwards-compatibility
4477  // with other code that just calls PST, but this should already
4478  // be handled in TextContent subclasses
4479  $text = TextContent::normalizeLineEndings( $text );
4480 
4481  if ( $options->getPreSaveTransform() ) {
4482  $text = $this->pstPass2( $text, $user );
4483  }
4484  $text = $this->mStripState->unstripBoth( $text );
4485 
4486  $this->setUser( null ); # Reset
4487 
4488  return $text;
4489  }
4490 
4499  private function pstPass2( $text, $user ) {
4501 
4502  # Note: This is the timestamp saved as hardcoded wikitext to
4503  # the database, we use $wgContLang here in order to give
4504  # everyone the same signature and use the default one rather
4505  # than the one selected in each user's preferences.
4506  # (see also T14815)
4507  $ts = $this->mOptions->getTimestamp();
4508  $timestamp = MWTimestamp::getLocalInstance( $ts );
4509  $ts = $timestamp->format( 'YmdHis' );
4510  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4511 
4512  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4513 
4514  # Variable replacement
4515  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4516  $text = $this->replaceVariables( $text );
4517 
4518  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4519  # which may corrupt this parser instance via its wfMessage()->text() call-
4520 
4521  # Signatures
4522  $sigText = $this->getUserSig( $user );
4523  $text = strtr( $text, [
4524  '~~~~~' => $d,
4525  '~~~~' => "$sigText $d",
4526  '~~~' => $sigText
4527  ] );
4528 
4529  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4530  $tc = '[' . Title::legalChars() . ']';
4531  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4532 
4533  // [[ns:page (context)|]]
4534  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4535  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4536  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4537  // [[ns:page (context), context|]] (using either single or double-width comma)
4538  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4539  // [[|page]] (reverse pipe trick: add context from page title)
4540  $p2 = "/\[\[\\|($tc+)]]/";
4541 
4542  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4543  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4544  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4545  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4546 
4547  $t = $this->mTitle->getText();
4548  $m = [];
4549  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4550  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4551  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4552  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4553  } else {
4554  # if there's no context, don't bother duplicating the title
4555  $text = preg_replace( $p2, '[[\\1]]', $text );
4556  }
4557 
4558  return $text;
4559  }
4560 
4575  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4576  global $wgMaxSigChars;
4577 
4578  $username = $user->getName();
4579 
4580  # If not given, retrieve from the user object.
4581  if ( $nickname === false ) {
4582  $nickname = $user->getOption( 'nickname' );
4583  }
4584 
4585  if ( is_null( $fancySig ) ) {
4586  $fancySig = $user->getBoolOption( 'fancysig' );
4587  }
4588 
4589  $nickname = $nickname == null ? $username : $nickname;
4590 
4591  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4592  $nickname = $username;
4593  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4594  } elseif ( $fancySig !== false ) {
4595  # Sig. might contain markup; validate this
4596  if ( $this->validateSig( $nickname ) !== false ) {
4597  # Validated; clean up (if needed) and return it
4598  return $this->cleanSig( $nickname, true );
4599  } else {
4600  # Failed to validate; fall back to the default
4601  $nickname = $username;
4602  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4603  }
4604  }
4605 
4606  # Make sure nickname doesnt get a sig in a sig
4607  $nickname = self::cleanSigInSig( $nickname );
4608 
4609  # If we're still here, make it a link to the user page
4610  $userText = wfEscapeWikiText( $username );
4611  $nickText = wfEscapeWikiText( $nickname );
4612  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4613 
4614  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4615  ->title( $this->getTitle() )->text();
4616  }
4617 
4624  public function validateSig( $text ) {
4625  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4626  }
4627 
4638  public function cleanSig( $text, $parsing = false ) {
4639  if ( !$parsing ) {
4640  global $wgTitle;
4641  $magicScopeVariable = $this->lock();
4642  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4643  }
4644 
4645  # Option to disable this feature
4646  if ( !$this->mOptions->getCleanSignatures() ) {
4647  return $text;
4648  }
4649 
4650  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4651  # => Move this logic to braceSubstitution()
4652  $substWord = MagicWord::get( 'subst' );
4653  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4654  $substText = '{{' . $substWord->getSynonym( 0 );
4655 
4656  $text = preg_replace( $substRegex, $substText, $text );
4657  $text = self::cleanSigInSig( $text );
4658  $dom = $this->preprocessToDom( $text );
4659  $frame = $this->getPreprocessor()->newFrame();
4660  $text = $frame->expand( $dom );
4661 
4662  if ( !$parsing ) {
4663  $text = $this->mStripState->unstripBoth( $text );
4664  }
4665 
4666  return $text;
4667  }
4668 
4675  public static function cleanSigInSig( $text ) {
4676  $text = preg_replace( '/~{3,5}/', '', $text );
4677  return $text;
4678  }
4679 
4690  $outputType, $clearState = true
4691  ) {
4692  $this->startParse( $title, $options, $outputType, $clearState );
4693  }
4694 
4701  private function startParse( Title $title = null, ParserOptions $options,
4702  $outputType, $clearState = true
4703  ) {
4704  $this->setTitle( $title );
4705  $this->mOptions = $options;
4706  $this->setOutputType( $outputType );
4707  if ( $clearState ) {
4708  $this->clearState();
4709  }
4710  }
4711 
4720  public function transformMsg( $text, $options, $title = null ) {
4721  static $executing = false;
4722 
4723  # Guard against infinite recursion
4724  if ( $executing ) {
4725  return $text;
4726  }
4727  $executing = true;
4728 
4729  if ( !$title ) {
4730  global $wgTitle;
4731  $title = $wgTitle;
4732  }
4733 
4734  $text = $this->preprocess( $text, $title, $options );
4735 
4736  $executing = false;
4737  return $text;
4738  }
4739 
4764  public function setHook( $tag, $callback ) {
4765  $tag = strtolower( $tag );
4766  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4767  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4768  }
4769  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4770  $this->mTagHooks[$tag] = $callback;
4771  if ( !in_array( $tag, $this->mStripList ) ) {
4772  $this->mStripList[] = $tag;
4773  }
4774 
4775  return $oldVal;
4776  }
4777 
4795  public function setTransparentTagHook( $tag, $callback ) {
4796  $tag = strtolower( $tag );
4797  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4798  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4799  }
4800  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4801  $this->mTransparentTagHooks[$tag] = $callback;
4802 
4803  return $oldVal;
4804  }
4805 
4809  public function clearTagHooks() {
4810  $this->mTagHooks = [];
4811  $this->mFunctionTagHooks = [];
4812  $this->mStripList = $this->mDefaultStripList;
4813  }
4814 
4858  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4860 
4861  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4862  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4863 
4864  # Add to function cache
4865  $mw = MagicWord::get( $id );
4866  if ( !$mw ) {
4867  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4868  }
4869 
4870  $synonyms = $mw->getSynonyms();
4871  $sensitive = intval( $mw->isCaseSensitive() );
4872 
4873  foreach ( $synonyms as $syn ) {
4874  # Case
4875  if ( !$sensitive ) {
4876  $syn = $wgContLang->lc( $syn );
4877  }
4878  # Add leading hash
4879  if ( !( $flags & self::SFH_NO_HASH ) ) {
4880  $syn = '#' . $syn;
4881  }
4882  # Remove trailing colon
4883  if ( substr( $syn, -1, 1 ) === ':' ) {
4884  $syn = substr( $syn, 0, -1 );
4885  }
4886  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4887  }
4888  return $oldVal;
4889  }
4890 
4896  public function getFunctionHooks() {
4897  return array_keys( $this->mFunctionHooks );
4898  }
4899 
4910  public function setFunctionTagHook( $tag, $callback, $flags ) {
4911  $tag = strtolower( $tag );
4912  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4913  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4914  }
4915  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4916  $this->mFunctionTagHooks[$tag] : null;
4917  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4918 
4919  if ( !in_array( $tag, $this->mStripList ) ) {
4920  $this->mStripList[] = $tag;
4921  }
4922 
4923  return $old;
4924  }
4925 
4933  public function replaceLinkHolders( &$text, $options = 0 ) {
4934  $this->mLinkHolders->replace( $text );
4935  }
4936 
4944  public function replaceLinkHoldersText( $text ) {
4945  return $this->mLinkHolders->replaceText( $text );
4946  }
4947 
4961  public function renderImageGallery( $text, $params ) {
4962 
4963  $mode = false;
4964  if ( isset( $params['mode'] ) ) {
4965  $mode = $params['mode'];
4966  }
4967 
4968  try {
4969  $ig = ImageGalleryBase::factory( $mode );
4970  } catch ( Exception $e ) {
4971  // If invalid type set, fallback to default.
4972  $ig = ImageGalleryBase::factory( false );
4973  }
4974 
4975  $ig->setContextTitle( $this->mTitle );
4976  $ig->setShowBytes( false );
4977  $ig->setShowFilename( false );
4978  $ig->setParser( $this );
4979  $ig->setHideBadImages();
4980  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
4981 
4982  if ( isset( $params['showfilename'] ) ) {
4983  $ig->setShowFilename( true );
4984  } else {
4985  $ig->setShowFilename( false );
4986  }
4987  if ( isset( $params['caption'] ) ) {
4988  $caption = $params['caption'];
4989  $caption = htmlspecialchars( $caption );
4990  $caption = $this->replaceInternalLinks( $caption );
4991  $ig->setCaptionHtml( $caption );
4992  }
4993  if ( isset( $params['perrow'] ) ) {
4994  $ig->setPerRow( $params['perrow'] );
4995  }
4996  if ( isset( $params['widths'] ) ) {
4997  $ig->setWidths( $params['widths'] );
4998  }
4999  if ( isset( $params['heights'] ) ) {
5000  $ig->setHeights( $params['heights'] );
5001  }
5002  $ig->setAdditionalOptions( $params );
5003 
5004  // Avoid PHP 7.1 warning from passing $this by reference
5005  $parser = $this;
5006  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
5007 
5008  $lines = StringUtils::explode( "\n", $text );
5009  foreach ( $lines as $line ) {
5010  # match lines like these:
5011  # Image:someimage.jpg|This is some image
5012  $matches = [];
5013  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5014  # Skip empty lines
5015  if ( count( $matches ) == 0 ) {
5016  continue;
5017  }
5018 
5019  if ( strpos( $matches[0], '%' ) !== false ) {
5020  $matches[1] = rawurldecode( $matches[1] );
5021  }
5023  if ( is_null( $title ) ) {
5024  # Bogus title. Ignore these so we don't bomb out later.
5025  continue;
5026  }
5027 
5028  # We need to get what handler the file uses, to figure out parameters.
5029  # Note, a hook can overide the file name, and chose an entirely different
5030  # file (which potentially could be of a different type and have different handler).
5031  $options = [];
5032  $descQuery = false;
5033  Hooks::run( 'BeforeParserFetchFileAndTitle',
5034  [ $this, $title, &$options, &$descQuery ] );
5035  # Don't register it now, as TraditionalImageGallery does that later.
5036  $file = $this->fetchFileNoRegister( $title, $options );
5037  $handler = $file ? $file->getHandler() : false;
5038 
5039  $paramMap = [
5040  'img_alt' => 'gallery-internal-alt',
5041  'img_link' => 'gallery-internal-link',
5042  ];
5043  if ( $handler ) {
5044  $paramMap = $paramMap + $handler->getParamMap();
5045  // We don't want people to specify per-image widths.
5046  // Additionally the width parameter would need special casing anyhow.
5047  unset( $paramMap['img_width'] );
5048  }
5049 
5050  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5051 
5052  $label = '';
5053  $alt = '';
5054  $link = '';
5055  $handlerOptions = [];
5056  if ( isset( $matches[3] ) ) {
5057  // look for an |alt= definition while trying not to break existing
5058  // captions with multiple pipes (|) in it, until a more sensible grammar
5059  // is defined for images in galleries
5060 
5061  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5062  // splitting on '|' is a bit odd, and different from makeImage.
5063  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5064  // Protect LanguageConverter markup
5065  $parameterMatches = StringUtils::delimiterExplode(
5066  '-{', '}-', '|', $matches[3], true /* nested */
5067  );
5068 
5069  foreach ( $parameterMatches as $parameterMatch ) {
5070  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5071  if ( $magicName ) {
5072  $paramName = $paramMap[$magicName];
5073 
5074  switch ( $paramName ) {
5075  case 'gallery-internal-alt':
5076  $alt = $this->stripAltText( $match, false );
5077  break;
5078  case 'gallery-internal-link':
5079  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5080  $chars = self::EXT_LINK_URL_CLASS;
5081  $addr = self::EXT_LINK_ADDR;
5082  $prots = $this->mUrlProtocols;
5083  // check to see if link matches an absolute url, if not then it must be a wiki link.
5084  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5085  // Result of LanguageConverter::markNoConversion
5086  // invoked on an external link.
5087  $linkValue = substr( $linkValue, 4, -2 );
5088  }
5089  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5090  $link = $linkValue;
5091  $this->mOutput->addExternalLink( $link );
5092  } else {
5093  $localLinkTitle = Title::newFromText( $linkValue );
5094  if ( $localLinkTitle !== null ) {
5095  $this->mOutput->addLink( $localLinkTitle );
5096  $link = $localLinkTitle->getLinkURL();
5097  }
5098  }
5099  break;
5100  default:
5101  // Must be a handler specific parameter.
5102  if ( $handler->validateParam( $paramName, $match ) ) {
5103  $handlerOptions[$paramName] = $match;
5104  } else {
5105  // Guess not, consider it as caption.
5106  wfDebug( "$parameterMatch failed parameter validation\n" );
5107  $label = '|' . $parameterMatch;
5108  }
5109  }
5110 
5111  } else {
5112  // Last pipe wins.
5113  $label = '|' . $parameterMatch;
5114  }
5115  }
5116  // Remove the pipe.
5117  $label = substr( $label, 1 );
5118  }
5119 
5120  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5121  }
5122  $html = $ig->toHTML();
5123  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5124  return $html;
5125  }
5126 
5131  public function getImageParams( $handler ) {
5132  if ( $handler ) {
5133  $handlerClass = get_class( $handler );
5134  } else {
5135  $handlerClass = '';
5136  }
5137  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5138  # Initialise static lists
5139  static $internalParamNames = [
5140  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5141  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5142  'bottom', 'text-bottom' ],
5143  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5144  'upright', 'border', 'link', 'alt', 'class' ],
5145  ];
5146  static $internalParamMap;
5147  if ( !$internalParamMap ) {
5148  $internalParamMap = [];
5149  foreach ( $internalParamNames as $type => $names ) {
5150  foreach ( $names as $name ) {
5151  // For grep: img_left, img_right, img_center, img_none,
5152  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5153  // img_bottom, img_text_bottom,
5154  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5155  // img_border, img_link, img_alt, img_class
5156  $magicName = str_replace( '-', '_', "img_$name" );
5157  $internalParamMap[$magicName] = [ $type, $name ];
5158  }
5159  }
5160  }
5161 
5162  # Add handler params
5163  $paramMap = $internalParamMap;
5164  if ( $handler ) {
5165  $handlerParamMap = $handler->getParamMap();
5166  foreach ( $handlerParamMap as $magic => $paramName ) {
5167  $paramMap[$magic] = [ 'handler', $paramName ];
5168  }
5169  }
5170  $this->mImageParams[$handlerClass] = $paramMap;
5171  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5172  }
5173  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5174  }
5175 
5184  public function makeImage( $title, $options, $holders = false ) {
5185  # Check if the options text is of the form "options|alt text"
5186  # Options are:
5187  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5188  # * left no resizing, just left align. label is used for alt= only
5189  # * right same, but right aligned
5190  # * none same, but not aligned
5191  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5192  # * center center the image
5193  # * frame Keep original image size, no magnify-button.
5194  # * framed Same as "frame"
5195  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5196  # * upright reduce width for upright images, rounded to full __0 px
5197  # * border draw a 1px border around the image
5198  # * alt Text for HTML alt attribute (defaults to empty)
5199  # * class Set a class for img node
5200  # * link Set the target of the image link. Can be external, interwiki, or local
5201  # vertical-align values (no % or length right now):
5202  # * baseline
5203  # * sub
5204  # * super
5205  # * top
5206  # * text-top
5207  # * middle
5208  # * bottom
5209  # * text-bottom
5210 
5211  # Protect LanguageConverter markup when splitting into parts
5213  '-{', '}-', '|', $options, true /* allow nesting */
5214  );
5215 
5216  # Give extensions a chance to select the file revision for us
5217  $options = [];
5218  $descQuery = false;
5219  Hooks::run( 'BeforeParserFetchFileAndTitle',
5220  [ $this, $title, &$options, &$descQuery ] );
5221  # Fetch and register the file (file title may be different via hooks)
5222  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5223 
5224  # Get parameter map
5225  $handler = $file ? $file->getHandler() : false;
5226 
5227  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5228 
5229  if ( !$file ) {
5230  $this->addTrackingCategory( 'broken-file-category' );
5231  }
5232 
5233  # Process the input parameters
5234  $caption = '';
5235  $params = [ 'frame' => [], 'handler' => [],
5236  'horizAlign' => [], 'vertAlign' => [] ];
5237  $seenformat = false;
5238  foreach ( $parts as $part ) {
5239  $part = trim( $part );
5240  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5241  $validated = false;
5242  if ( isset( $paramMap[$magicName] ) ) {
5243  list( $type, $paramName ) = $paramMap[$magicName];
5244 
5245  # Special case; width and height come in one variable together
5246  if ( $type === 'handler' && $paramName === 'width' ) {
5247  $parsedWidthParam = $this->parseWidthParam( $value );
5248  if ( isset( $parsedWidthParam['width'] ) ) {
5249  $width = $parsedWidthParam['width'];
5250  if ( $handler->validateParam( 'width', $width ) ) {
5251  $params[$type]['width'] = $width;
5252  $validated = true;
5253  }
5254  }
5255  if ( isset( $parsedWidthParam['height'] ) ) {
5256  $height = $parsedWidthParam['height'];
5257  if ( $handler->validateParam( 'height', $height ) ) {
5258  $params[$type]['height'] = $height;
5259  $validated = true;
5260  }
5261  }
5262  # else no validation -- T15436
5263  } else {
5264  if ( $type === 'handler' ) {
5265  # Validate handler parameter
5266  $validated = $handler->validateParam( $paramName, $value );
5267  } else {
5268  # Validate internal parameters
5269  switch ( $paramName ) {
5270  case 'manualthumb':
5271  case 'alt':
5272  case 'class':
5273  # @todo FIXME: Possibly check validity here for
5274  # manualthumb? downstream behavior seems odd with
5275  # missing manual thumbs.
5276  $validated = true;
5277  $value = $this->stripAltText( $value, $holders );
5278  break;
5279  case 'link':
5280  $chars = self::EXT_LINK_URL_CLASS;
5281  $addr = self::EXT_LINK_ADDR;
5282  $prots = $this->mUrlProtocols;
5283  if ( $value === '' ) {
5284  $paramName = 'no-link';
5285  $value = true;
5286  $validated = true;
5287  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5288  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5289  $paramName = 'link-url';
5290  $this->mOutput->addExternalLink( $value );
5291  if ( $this->mOptions->getExternalLinkTarget() ) {
5292  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5293  }
5294  $validated = true;
5295  }
5296  } else {
5297  $linkTitle = Title::newFromText( $value );
5298  if ( $linkTitle ) {
5299  $paramName = 'link-title';
5300  $value = $linkTitle;
5301  $this->mOutput->addLink( $linkTitle );
5302  $validated = true;
5303  }
5304  }
5305  break;
5306  case 'frameless':
5307  case 'framed':
5308  case 'thumbnail':
5309  // use first appearing option, discard others.
5310  $validated = !$seenformat;
5311  $seenformat = true;
5312  break;
5313  default:
5314  # Most other things appear to be empty or numeric...
5315  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5316  }
5317  }
5318 
5319  if ( $validated ) {
5320  $params[$type][$paramName] = $value;
5321  }
5322  }
5323  }
5324  if ( !$validated ) {
5325  $caption = $part;
5326  }
5327  }
5328 
5329  # Process alignment parameters
5330  if ( $params['horizAlign'] ) {
5331  $params['frame']['align'] = key( $params['horizAlign'] );
5332  }
5333  if ( $params['vertAlign'] ) {
5334  $params['frame']['valign'] = key( $params['vertAlign'] );
5335  }
5336 
5337  $params['frame']['caption'] = $caption;
5338 
5339  # Will the image be presented in a frame, with the caption below?
5340  $imageIsFramed = isset( $params['frame']['frame'] )
5341  || isset( $params['frame']['framed'] )
5342  || isset( $params['frame']['thumbnail'] )
5343  || isset( $params['frame']['manualthumb'] );
5344 
5345  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5346  # came to also set the caption, ordinary text after the image -- which
5347  # makes no sense, because that just repeats the text multiple times in
5348  # screen readers. It *also* came to set the title attribute.
5349  # Now that we have an alt attribute, we should not set the alt text to
5350  # equal the caption: that's worse than useless, it just repeats the
5351  # text. This is the framed/thumbnail case. If there's no caption, we
5352  # use the unnamed parameter for alt text as well, just for the time be-
5353  # ing, if the unnamed param is set and the alt param is not.
5354  # For the future, we need to figure out if we want to tweak this more,
5355  # e.g., introducing a title= parameter for the title; ignoring the un-
5356  # named parameter entirely for images without a caption; adding an ex-
5357  # plicit caption= parameter and preserving the old magic unnamed para-
5358  # meter for BC; ...
5359  if ( $imageIsFramed ) { # Framed image
5360  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5361  # No caption or alt text, add the filename as the alt text so
5362  # that screen readers at least get some description of the image
5363  $params['frame']['alt'] = $title->getText();
5364  }
5365  # Do not set $params['frame']['title'] because tooltips don't make sense
5366  # for framed images
5367  } else { # Inline image
5368  if ( !isset( $params['frame']['alt'] ) ) {
5369  # No alt text, use the "caption" for the alt text
5370  if ( $caption !== '' ) {
5371  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5372  } else {
5373  # No caption, fall back to using the filename for the
5374  # alt text
5375  $params['frame']['alt'] = $title->getText();
5376  }
5377  }
5378  # Use the "caption" for the tooltip text
5379  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5380  }
5381 
5382  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5383 
5384  # Linker does the rest
5385  $time = isset( $options['time'] ) ? $options['time'] : false;
5386  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5387  $time, $descQuery, $this->mOptions->getThumbSize() );
5388 
5389  # Give the handler a chance to modify the parser object
5390  if ( $handler ) {
5391  $handler->parserTransformHook( $this, $file );
5392  }
5393 
5394  return $ret;
5395  }
5396 
5402  protected function stripAltText( $caption, $holders ) {
5403  # Strip bad stuff out of the title (tooltip). We can't just use
5404  # replaceLinkHoldersText() here, because if this function is called
5405  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5406  if ( $holders ) {
5407  $tooltip = $holders->replaceText( $caption );
5408  } else {
5409  $tooltip = $this->replaceLinkHoldersText( $caption );
5410  }
5411 
5412  # make sure there are no placeholders in thumbnail attributes
5413  # that are later expanded to html- so expand them now and
5414  # remove the tags
5415  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5416  $tooltip = Sanitizer::stripAllTags( $tooltip );
5417 
5418  return $tooltip;
5419  }
5420 
5426  public function disableCache() {
5427  wfDebug( "Parser output marked as uncacheable.\n" );
5428  if ( !$this->mOutput ) {
5429  throw new MWException( __METHOD__ .
5430  " can only be called when actually parsing something" );
5431  }
5432  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5433  }
5434 
5443  public function attributeStripCallback( &$text, $frame = false ) {
5444  $text = $this->replaceVariables( $text, $frame );
5445  $text = $this->mStripState->unstripBoth( $text );
5446  return $text;
5447  }
5448 
5454  public function getTags() {
5455  return array_merge(
5456  array_keys( $this->mTransparentTagHooks ),
5457  array_keys( $this->mTagHooks ),
5458  array_keys( $this->mFunctionTagHooks )
5459  );
5460  }
5461 
5472  public function replaceTransparentTags( $text ) {
5473  $matches = [];
5474  $elements = array_keys( $this->mTransparentTagHooks );
5475  $text = self::extractTagsAndParams( $elements, $text, $matches );
5476  $replacements = [];
5477 
5478  foreach ( $matches as $marker => $data ) {
5479  list( $element, $content, $params, $tag ) = $data;
5480  $tagName = strtolower( $element );
5481  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5482  $output = call_user_func_array(
5483  $this->mTransparentTagHooks[$tagName],
5484  [ $content, $params, $this ]
5485  );
5486  } else {
5487  $output = $tag;
5488  }
5489  $replacements[$marker] = $output;
5490  }
5491  return strtr( $text, $replacements );
5492  }
5493 
5523  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5524  global $wgTitle; # not generally used but removes an ugly failure mode
5525 
5526  $magicScopeVariable = $this->lock();
5527  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5528  $outText = '';
5529  $frame = $this->getPreprocessor()->newFrame();
5530 
5531  # Process section extraction flags
5532  $flags = 0;
5533  $sectionParts = explode( '-', $sectionId );
5534  $sectionIndex = array_pop( $sectionParts );
5535  foreach ( $sectionParts as $part ) {
5536  if ( $part === 'T' ) {
5537  $flags |= self::PTD_FOR_INCLUSION;
5538  }
5539  }
5540 
5541  # Check for empty input
5542  if ( strval( $text ) === '' ) {
5543  # Only sections 0 and T-0 exist in an empty document
5544  if ( $sectionIndex == 0 ) {
5545  if ( $mode === 'get' ) {
5546  return '';
5547  } else {
5548  return $newText;
5549  }
5550  } else {
5551  if ( $mode === 'get' ) {
5552  return $newText;
5553  } else {
5554  return $text;
5555  }
5556  }
5557  }
5558 
5559  # Preprocess the text
5560  $root = $this->preprocessToDom( $text, $flags );
5561 
5562  # <h> nodes indicate section breaks
5563  # They can only occur at the top level, so we can find them by iterating the root's children
5564  $node = $root->getFirstChild();
5565 
5566  # Find the target section
5567  if ( $sectionIndex == 0 ) {
5568  # Section zero doesn't nest, level=big
5569  $targetLevel = 1000;
5570  } else {
5571  while ( $node ) {
5572  if ( $node->getName() === 'h' ) {
5573  $bits = $node->splitHeading();
5574  if ( $bits['i'] == $sectionIndex ) {
5575  $targetLevel = $bits['level'];
5576  break;
5577  }
5578  }
5579  if ( $mode === 'replace' ) {
5580  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5581  }
5582  $node = $node->getNextSibling();
5583  }
5584  }
5585 
5586  if ( !$node ) {
5587  # Not found
5588  if ( $mode === 'get' ) {
5589  return $newText;
5590  } else {
5591  return $text;
5592  }
5593  }
5594 
5595  # Find the end of the section, including nested sections
5596  do {
5597  if ( $node->getName() === 'h' ) {
5598  $bits = $node->splitHeading();
5599  $curLevel = $bits['level'];
5600  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5601  break;
5602  }
5603  }
5604  if ( $mode === 'get' ) {
5605  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5606  }
5607  $node = $node->getNextSibling();
5608  } while ( $node );
5609 
5610  # Write out the remainder (in replace mode only)
5611  if ( $mode === 'replace' ) {
5612  # Output the replacement text
5613  # Add two newlines on -- trailing whitespace in $newText is conventionally
5614  # stripped by the editor, so we need both newlines to restore the paragraph gap
5615  # Only add trailing whitespace if there is newText
5616  if ( $newText != "" ) {
5617  $outText .= $newText . "\n\n";
5618  }
5619 
5620  while ( $node ) {
5621  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5622  $node = $node->getNextSibling();
5623  }
5624  }
5625 
5626  if ( is_string( $outText ) ) {
5627  # Re-insert stripped tags
5628  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5629  }
5630 
5631  return $outText;
5632  }
5633 
5648  public function getSection( $text, $sectionId, $defaultText = '' ) {
5649  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5650  }
5651 
5664  public function replaceSection( $oldText, $sectionId, $newText ) {
5665  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5666  }
5667 
5673  public function getRevisionId() {
5674  return $this->mRevisionId;
5675  }
5676 
5683  public function getRevisionObject() {
5684  if ( !is_null( $this->mRevisionObject ) ) {
5685  return $this->mRevisionObject;
5686  }
5687  if ( is_null( $this->mRevisionId ) ) {
5688  return null;
5689  }
5690 
5691  $rev = call_user_func(
5692  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5693  );
5694 
5695  # If the parse is for a new revision, then the callback should have
5696  # already been set to force the object and should match mRevisionId.
5697  # If not, try to fetch by mRevisionId for sanity.
5698  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5699  $rev = Revision::newFromId( $this->mRevisionId );
5700  }
5701 
5702  $this->mRevisionObject = $rev;
5703 
5704  return $this->mRevisionObject;
5705  }
5706 
5712  public function getRevisionTimestamp() {
5713  if ( is_null( $this->mRevisionTimestamp ) ) {
5715 
5716  $revObject = $this->getRevisionObject();
5717  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5718 
5719  # The cryptic '' timezone parameter tells to use the site-default
5720  # timezone offset instead of the user settings.
5721  # Since this value will be saved into the parser cache, served
5722  # to other users, and potentially even used inside links and such,
5723  # it needs to be consistent for all visitors.
5724  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5725 
5726  }
5727  return $this->mRevisionTimestamp;
5728  }
5729 
5735  public function getRevisionUser() {
5736  if ( is_null( $this->mRevisionUser ) ) {
5737  $revObject = $this->getRevisionObject();
5738 
5739  # if this template is subst: the revision id will be blank,
5740  # so just use the current user's name
5741  if ( $revObject ) {
5742  $this->mRevisionUser = $revObject->getUserText();
5743  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5744  $this->mRevisionUser = $this->getUser()->getName();
5745  }
5746  }
5747  return $this->mRevisionUser;
5748  }
5749 
5755  public function getRevisionSize() {
5756  if ( is_null( $this->mRevisionSize ) ) {
5757  $revObject = $this->getRevisionObject();
5758 
5759  # if this variable is subst: the revision id will be blank,
5760  # so just use the parser input size, because the own substituation
5761  # will change the size.
5762  if ( $revObject ) {
5763  $this->mRevisionSize = $revObject->getSize();
5764  } else {
5765  $this->mRevisionSize = $this->mInputSize;
5766  }
5767  }
5768  return $this->mRevisionSize;
5769  }
5770 
5776  public function setDefaultSort( $sort ) {
5777  $this->mDefaultSort = $sort;
5778  $this->mOutput->setProperty( 'defaultsort', $sort );
5779  }
5780 
5791  public function getDefaultSort() {
5792  if ( $this->mDefaultSort !== false ) {
5793  return $this->mDefaultSort;
5794  } else {
5795  return '';
5796  }
5797  }
5798 
5805  public function getCustomDefaultSort() {
5806  return $this->mDefaultSort;
5807  }
5808 
5818  public function guessSectionNameFromWikiText( $text ) {
5819  # Strip out wikitext links(they break the anchor)
5820  $text = $this->stripSectionName( $text );
5822  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5823  }
5824 
5833  public function guessLegacySectionNameFromWikiText( $text ) {
5834  # Strip out wikitext links(they break the anchor)
5835  $text = $this->stripSectionName( $text );
5837  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5838  }
5839 
5854  public function stripSectionName( $text ) {
5855  # Strip internal link markup
5856  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5857  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5858 
5859  # Strip external link markup
5860  # @todo FIXME: Not tolerant to blank link text
5861  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5862  # on how many empty links there are on the page - need to figure that out.
5863  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5864 
5865  # Parse wikitext quotes (italics & bold)
5866  $text = $this->doQuotes( $text );
5867 
5868  # Strip HTML tags
5869  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5870  return $text;
5871  }
5872 
5883  public function testSrvus( $text, Title $title, ParserOptions $options,
5884  $outputType = self::OT_HTML
5885  ) {
5886  $magicScopeVariable = $this->lock();
5887  $this->startParse( $title, $options, $outputType, true );
5888 
5889  $text = $this->replaceVariables( $text );
5890  $text = $this->mStripState->unstripBoth( $text );
5891  $text = Sanitizer::removeHTMLtags( $text );
5892  return $text;
5893  }
5894 
5901  public function testPst( $text, Title $title, ParserOptions $options ) {
5902  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5903  }
5904 
5911  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5912  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5913  }
5914 
5931  public function markerSkipCallback( $s, $callback ) {
5932  $i = 0;
5933  $out = '';
5934  while ( $i < strlen( $s ) ) {
5935  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5936  if ( $markerStart === false ) {
5937  $out .= call_user_func( $callback, substr( $s, $i ) );
5938  break;
5939  } else {
5940  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5941  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5942  if ( $markerEnd === false ) {
5943  $out .= substr( $s, $markerStart );
5944  break;
5945  } else {
5946  $markerEnd += strlen( self::MARKER_SUFFIX );
5947  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5948  $i = $markerEnd;
5949  }
5950  }
5951  }
5952  return $out;
5953  }
5954 
5961  public function killMarkers( $text ) {
5962  return $this->mStripState->killMarkers( $text );
5963  }
5964 
5981  public function serializeHalfParsedText( $text ) {
5982  $data = [
5983  'text' => $text,
5984  'version' => self::HALF_PARSED_VERSION,
5985  'stripState' => $this->mStripState->getSubState( $text ),
5986  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5987  ];
5988  return $data;
5989  }
5990 
6006  public function unserializeHalfParsedText( $data ) {
6007  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
6008  throw new MWException( __METHOD__ . ': invalid version' );
6009  }
6010 
6011  # First, extract the strip state.
6012  $texts = [ $data['text'] ];
6013  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6014 
6015  # Now renumber links
6016  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6017 
6018  # Should be good to go.
6019  return $texts[0];
6020  }
6021 
6031  public function isValidHalfParsedText( $data ) {
6032  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6033  }
6034 
6043  public function parseWidthParam( $value ) {
6044  $parsedWidthParam = [];
6045  if ( $value === '' ) {
6046  return $parsedWidthParam;
6047  }
6048  $m = [];
6049  # (T15500) In both cases (width/height and width only),
6050  # permit trailing "px" for backward compatibility.
6051  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6052  $width = intval( $m[1] );
6053  $height = intval( $m[2] );
6054  $parsedWidthParam['width'] = $width;
6055  $parsedWidthParam['height'] = $height;
6056  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6057  $width = intval( $value );
6058  $parsedWidthParam['width'] = $width;
6059  }
6060  return $parsedWidthParam;
6061  }
6062 
6072  protected function lock() {
6073  if ( $this->mInParse ) {
6074  throw new MWException( "Parser state cleared while parsing. "
6075  . "Did you call Parser::parse recursively?" );
6076  }
6077  $this->mInParse = true;
6078 
6079  $recursiveCheck = new ScopedCallback( function() {
6080  $this->mInParse = false;
6081  } );
6082 
6083  return $recursiveCheck;
6084  }
6085 
6096  public static function stripOuterParagraph( $html ) {
6097  $m = [];
6098  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6099  if ( strpos( $m[1], '</p>' ) === false ) {
6100  $html = $m[1];
6101  }
6102  }
6103 
6104  return $html;
6105  }
6106 
6117  public function getFreshParser() {
6118  global $wgParserConf;
6119  if ( $this->mInParse ) {
6120  return new $wgParserConf['class']( $wgParserConf );
6121  } else {
6122  return $this;
6123  }
6124  }
6125 
6132  public function enableOOUI() {
6134  $this->mOutput->setEnableOOUI( true );
6135  }
6136 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5683
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:556
setTitle($t)
Set the context title.
Definition: Parser.php:775
$mAutonumber
Definition: Parser.php:179
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3309
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5931
$mPPNodeCount
Definition: Parser.php:193
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2114
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:1953
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:6031
null means default in associative array form
Definition: hooks.txt:1957
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:1957
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:5648
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:5961
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:3544
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:784
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5735
setFunctionTagHook($tag, $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4910
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:5854
const OT_PREPROCESS
Definition: Defines.php:184
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 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:1050
either a plain
Definition: hooks.txt:2008
$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:2520
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:723
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4624
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:3220
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 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:1050
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4961
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:704
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1856
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
nextLinkID()
Definition: Parser.php:864
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:1957
getImageParams($handler)
Definition: Parser.php:5131
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:1635
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:5454
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:2123
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3689
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:2858
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:4896
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
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:209
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition: globals.txt:10
wfHostname()
Fetch server name for use in error reporting etc.
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:879
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:3792
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5883
uniqPrefix()
Accessor for mUniqPrefix.
Definition: Parser.php:765
const TOC_START
Definition: Parser.php:139
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:803
SectionProfiler $mProfiler
Definition: Parser.php:254
$sort
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 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:1050
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3714
null for the local wiki Added in
Definition: hooks.txt:1573
There are three types of nodes:
$mHeadings
Definition: Parser.php:195
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4809
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:51
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:3733
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4499
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead.
Definition: Parser.php:5833
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:857
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2750
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5981
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4933
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3527
static activeUsers()
Definition: SiteStats.php:173
$mLinkID
Definition: Parser.php:192
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1668
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2888
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:3010
static cleanUrl($url)
Definition: Sanitizer.php:1862
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:265
$mGeneratedPPNodeCount
Definition: Parser.php:193
static getRandomString()
Get a random string.
Definition: Parser.php:744
$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:1829
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:2485
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:829
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:946
const NS_TEMPLATE
Definition: Defines.php:72
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target...
Definition: Revision.php:134
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2500
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:635
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2520
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:26
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:25
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:678
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:5673
const OT_PREPROCESS
Definition: Parser.php:114
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2473
$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:4944
setLinkID($id)
Definition: Parser.php:871
$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:2962
$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:1955
if($line===false) $args
Definition: cdb.php:63
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
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:3461
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2930
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:4638
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:141
$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:4689
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:4034
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2101
$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:5426
$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:1280
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:5712
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6096
static register($parser)
$mRevIdForTs
Definition: Parser.php:223
static singleton()
Get an instance of this class.
Definition: LinkCache.php:67
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
static normalizeSubpageLink($contextTitle, $target, &$text)
Definition: Linker.php:1353
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6043
$mStripList
Definition: Parser.php:148
$mFunctionTagHooks
Definition: Parser.php:147
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3752
const OT_PLAIN
Definition: Defines.php:186
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3504
$mRevisionTimestamp
Definition: Parser.php:220
$mImageParams
Definition: Parser.php:151
stripAltText($caption, $holders)
Definition: Parser.php:5402
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1651
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:2019
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 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:1050
setHook($tag, $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4764
const OT_WIKI
Definition: Defines.php:183
Preprocessor $mPreprocessor
Definition: Parser.php:172
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:932
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:50
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:2933
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:920
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3953
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1049
const EXT_IMAGE_REGEX
Definition: Parser.php:100
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4701
$params
const NS_CATEGORY
Definition: Defines.php:76
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:979
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:1076
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:5755
$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:1957
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:4464
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:892
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:935
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:1924
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:4054
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:910
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:4575
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:68
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:1957
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:2451
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:1742
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2900
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:812
$mTagHooks
Definition: Parser.php:143
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:70
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 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:1050
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:6132
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:3572
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:2042
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2460
const OT_HTML
Definition: Defines.php:182
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 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:1050
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:285
static images()
Definition: SiteStats.php:181
$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:2537
$mConf
Definition: Parser.php:168
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4720
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
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:1932
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
this hook is for auditing only $req
Definition: hooks.txt:991
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:784
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc next in line in page history
Definition: hooks.txt:1742
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:5472
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:5664
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3980
$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:5911
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:3362
$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:6117
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:149
$mRevisionUser
Definition: Parser.php:221
lock()
Lock the current instance of the parser.
Definition: Parser.php:6072
static pages()
Definition: SiteStats.php:157
$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:2427
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3585
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 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:1029
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:4858
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:2930
$mMarkerIndex
Definition: Parser.php:153
getTitle()
Accessor for the Title object.
Definition: Parser.php:793
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:5523
ParserOutput $mOutput
Definition: Parser.php:178
getOutput()
Get the ParserOutput object.
Definition: Parser.php:838
$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:1457
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:4675
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5776
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3678
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:2613
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:596
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:3967
$mShowToc
Definition: Parser.php:197
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1983
const DB_REPLICA
Definition: defines.php:25
magicLinkCallback($m)
Definition: Parser.php:1488
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:1062
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5901
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.