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 = self::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  // Run deprecated hook
554  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ], '1.22' );
555 
556  // Sanitize for comment. Note '‐' in the replacement is U+2010,
557  // which looks much like the problematic '-'.
558  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
559  $text .= "\n<!-- \n$limitReport-->\n";
560 
561  // Add on template profiling data in human/machine readable way
562  $dataByFunc = $this->mProfiler->getFunctionStats();
563  uasort( $dataByFunc, function ( $a, $b ) {
564  return $a['real'] < $b['real']; // descending order
565  } );
566  $profileReport = [];
567  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
568  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
569  $item['%real'], $item['real'], $item['calls'],
570  htmlspecialchars( $item['name'] ) );
571  }
572  $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
573  $text .= implode( "\n", $profileReport ) . "\n-->\n";
574 
575  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
576 
577  // Add other cache related metadata
578  if ( $wgShowHostnames ) {
579  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
580  }
581  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
582  $this->mOutput->getCacheTime() );
583  $this->mOutput->setLimitReportData( 'cachereport-ttl',
584  $this->mOutput->getCacheExpiry() );
585  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
586  $this->mOutput->hasDynamicContent() );
587 
588  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
589  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
590  $this->mTitle->getPrefixedDBkey() );
591  }
592  }
593 
594  # Wrap non-interface parser output in a <div> so it can be targeted
595  # with CSS (T37247)
596  $class = $this->mOptions->getWrapOutputClass();
597  if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
598  $text = Html::rawElement( 'div', [ 'class' => $class ], $text );
599  }
600 
601  $this->mOutput->setText( $text );
602 
603  $this->mRevisionId = $oldRevisionId;
604  $this->mRevisionObject = $oldRevisionObject;
605  $this->mRevisionTimestamp = $oldRevisionTimestamp;
606  $this->mRevisionUser = $oldRevisionUser;
607  $this->mRevisionSize = $oldRevisionSize;
608  $this->mInputSize = false;
609  $this->currentRevisionCache = null;
610 
611  return $this->mOutput;
612  }
613 
636  public function recursiveTagParse( $text, $frame = false ) {
637  // Avoid PHP 7.1 warning from passing $this by reference
638  $parser = $this;
639  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
640  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
641  $text = $this->internalParse( $text, false, $frame );
642  return $text;
643  }
644 
662  public function recursiveTagParseFully( $text, $frame = false ) {
663  $text = $this->recursiveTagParse( $text, $frame );
664  $text = $this->internalParseHalfParsed( $text, false );
665  return $text;
666  }
667 
679  public function preprocess( $text, Title $title = null,
680  ParserOptions $options, $revid = null, $frame = false
681  ) {
682  $magicScopeVariable = $this->lock();
683  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
684  if ( $revid !== null ) {
685  $this->mRevisionId = $revid;
686  }
687  // Avoid PHP 7.1 warning from passing $this by reference
688  $parser = $this;
689  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
690  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
691  $text = $this->replaceVariables( $text, $frame );
692  $text = $this->mStripState->unstripBoth( $text );
693  return $text;
694  }
695 
705  public function recursivePreprocess( $text, $frame = false ) {
706  $text = $this->replaceVariables( $text, $frame );
707  $text = $this->mStripState->unstripBoth( $text );
708  return $text;
709  }
710 
724  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
725  $msg = new RawMessage( $text );
726  $text = $msg->params( $params )->plain();
727 
728  # Parser (re)initialisation
729  $magicScopeVariable = $this->lock();
730  $this->startParse( $title, $options, self::OT_PLAIN, true );
731 
733  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
734  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
735  $text = $this->mStripState->unstripBoth( $text );
736  return $text;
737  }
738 
745  public static function getRandomString() {
746  wfDeprecated( __METHOD__, '1.26' );
747  return wfRandomString( 16 );
748  }
749 
756  public function setUser( $user ) {
757  $this->mUser = $user;
758  }
759 
766  public function uniqPrefix() {
767  wfDeprecated( __METHOD__, '1.26' );
768  return self::MARKER_PREFIX;
769  }
770 
776  public function setTitle( $t ) {
777  if ( !$t ) {
778  $t = Title::newFromText( 'NO TITLE' );
779  }
780 
781  if ( $t->hasFragment() ) {
782  # Strip the fragment to avoid various odd effects
783  $this->mTitle = $t->createFragmentTarget( '' );
784  } else {
785  $this->mTitle = $t;
786  }
787  }
788 
794  public function getTitle() {
795  return $this->mTitle;
796  }
797 
804  public function Title( $x = null ) {
805  return wfSetVar( $this->mTitle, $x );
806  }
807 
813  public function setOutputType( $ot ) {
814  $this->mOutputType = $ot;
815  # Shortcut alias
816  $this->ot = [
817  'html' => $ot == self::OT_HTML,
818  'wiki' => $ot == self::OT_WIKI,
819  'pre' => $ot == self::OT_PREPROCESS,
820  'plain' => $ot == self::OT_PLAIN,
821  ];
822  }
823 
830  public function OutputType( $x = null ) {
831  return wfSetVar( $this->mOutputType, $x );
832  }
833 
839  public function getOutput() {
840  return $this->mOutput;
841  }
842 
848  public function getOptions() {
849  return $this->mOptions;
850  }
851 
858  public function Options( $x = null ) {
859  return wfSetVar( $this->mOptions, $x );
860  }
861 
865  public function nextLinkID() {
866  return $this->mLinkID++;
867  }
868 
872  public function setLinkID( $id ) {
873  $this->mLinkID = $id;
874  }
875 
880  public function getFunctionLang() {
881  return $this->getTargetLanguage();
882  }
883 
893  public function getTargetLanguage() {
894  $target = $this->mOptions->getTargetLanguage();
895 
896  if ( $target !== null ) {
897  return $target;
898  } elseif ( $this->mOptions->getInterfaceMessage() ) {
899  return $this->mOptions->getUserLangObj();
900  } elseif ( is_null( $this->mTitle ) ) {
901  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
902  }
903 
904  return $this->mTitle->getPageLanguage();
905  }
906 
911  public function getConverterLanguage() {
912  return $this->getTargetLanguage();
913  }
914 
921  public function getUser() {
922  if ( !is_null( $this->mUser ) ) {
923  return $this->mUser;
924  }
925  return $this->mOptions->getUser();
926  }
927 
933  public function getPreprocessor() {
934  if ( !isset( $this->mPreprocessor ) ) {
935  $class = $this->mPreprocessorClass;
936  $this->mPreprocessor = new $class( $this );
937  }
938  return $this->mPreprocessor;
939  }
940 
947  public function getLinkRenderer() {
948  if ( !$this->mLinkRenderer ) {
949  $this->mLinkRenderer = MediaWikiServices::getInstance()
950  ->getLinkRendererFactory()->create();
951  $this->mLinkRenderer->setStubThreshold(
952  $this->getOptions()->getStubThreshold()
953  );
954  }
955 
956  return $this->mLinkRenderer;
957  }
958 
980  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
981  if ( $uniq_prefix !== null ) {
982  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
983  }
984  static $n = 1;
985  $stripped = '';
986  $matches = [];
987 
988  $taglist = implode( '|', $elements );
989  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
990 
991  while ( $text != '' ) {
992  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
993  $stripped .= $p[0];
994  if ( count( $p ) < 5 ) {
995  break;
996  }
997  if ( count( $p ) > 5 ) {
998  # comment
999  $element = $p[4];
1000  $attributes = '';
1001  $close = '';
1002  $inside = $p[5];
1003  } else {
1004  # tag
1005  $element = $p[1];
1006  $attributes = $p[2];
1007  $close = $p[3];
1008  $inside = $p[4];
1009  }
1010 
1011  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1012  $stripped .= $marker;
1013 
1014  if ( $close === '/>' ) {
1015  # Empty element tag, <tag />
1016  $content = null;
1017  $text = $inside;
1018  $tail = null;
1019  } else {
1020  if ( $element === '!--' ) {
1021  $end = '/(-->)/';
1022  } else {
1023  $end = "/(<\\/$element\\s*>)/i";
1024  }
1025  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1026  $content = $q[0];
1027  if ( count( $q ) < 3 ) {
1028  # No end tag -- let it run out to the end of the text.
1029  $tail = '';
1030  $text = '';
1031  } else {
1032  $tail = $q[1];
1033  $text = $q[2];
1034  }
1035  }
1036 
1037  $matches[$marker] = [ $element,
1038  $content,
1039  Sanitizer::decodeTagAttributes( $attributes ),
1040  "<$element$attributes$close$content$tail" ];
1041  }
1042  return $stripped;
1043  }
1044 
1050  public function getStripList() {
1051  return $this->mStripList;
1052  }
1053 
1063  public function insertStripItem( $text ) {
1064  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1065  $this->mMarkerIndex++;
1066  $this->mStripState->addGeneral( $marker, $text );
1067  return $marker;
1068  }
1069 
1077  public function doTableStuff( $text ) {
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  $origText = $text;
1282 
1283  // Avoid PHP 7.1 warning from passing $this by reference
1284  $parser = $this;
1285 
1286  # Hook to suspend the parser in this state
1287  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1288  return $text;
1289  }
1290 
1291  # if $frame is provided, then use $frame for replacing any variables
1292  if ( $frame ) {
1293  # use frame depth to infer how include/noinclude tags should be handled
1294  # depth=0 means this is the top-level document; otherwise it's an included document
1295  if ( !$frame->depth ) {
1296  $flag = 0;
1297  } else {
1298  $flag = self::PTD_FOR_INCLUSION;
1299  }
1300  $dom = $this->preprocessToDom( $text, $flag );
1301  $text = $frame->expand( $dom );
1302  } else {
1303  # if $frame is not provided, then use old-style replaceVariables
1304  $text = $this->replaceVariables( $text );
1305  }
1306 
1307  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1308  $text = Sanitizer::removeHTMLtags(
1309  $text,
1310  [ $this, 'attributeStripCallback' ],
1311  false,
1312  array_keys( $this->mTransparentTagHooks ),
1313  [],
1314  [ $this, 'addTrackingCategory' ]
1315  );
1316  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1317 
1318  # Tables need to come after variable replacement for things to work
1319  # properly; putting them before other transformations should keep
1320  # exciting things like link expansions from showing up in surprising
1321  # places.
1322  $text = $this->doTableStuff( $text );
1323 
1324  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1325 
1326  $text = $this->doDoubleUnderscore( $text );
1327 
1328  $text = $this->doHeadings( $text );
1329  $text = $this->replaceInternalLinks( $text );
1330  $text = $this->doAllQuotes( $text );
1331  $text = $this->replaceExternalLinks( $text );
1332 
1333  # replaceInternalLinks may sometimes leave behind
1334  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1335  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1336 
1337  $text = $this->doMagicLinks( $text );
1338  $text = $this->formatHeadings( $text, $origText, $isMain );
1339 
1340  return $text;
1341  }
1342 
1352  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1353  $text = $this->mStripState->unstripGeneral( $text );
1354 
1355  // Avoid PHP 7.1 warning from passing $this by reference
1356  $parser = $this;
1357 
1358  if ( $isMain ) {
1359  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1360  }
1361 
1362  # Clean up special characters, only run once, next-to-last before doBlockLevels
1363  $fixtags = [
1364  # French spaces, last one Guillemet-left
1365  # only if there is something before the space
1366  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1367  # french spaces, Guillemet-right
1368  '/(\\302\\253) /' => '\\1&#160;',
1369  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1370  ];
1371  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1372 
1373  $text = $this->doBlockLevels( $text, $linestart );
1374 
1375  $this->replaceLinkHolders( $text );
1376 
1384  if ( !( $this->mOptions->getDisableContentConversion()
1385  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1386  ) {
1387  if ( !$this->mOptions->getInterfaceMessage() ) {
1388  # The position of the convert() call should not be changed. it
1389  # assumes that the links are all replaced and the only thing left
1390  # is the <nowiki> mark.
1391  $text = $this->getConverterLanguage()->convert( $text );
1392  }
1393  }
1394 
1395  $text = $this->mStripState->unstripNoWiki( $text );
1396 
1397  if ( $isMain ) {
1398  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1399  }
1400 
1401  $text = $this->replaceTransparentTags( $text );
1402  $text = $this->mStripState->unstripGeneral( $text );
1403 
1404  $text = Sanitizer::normalizeCharReferences( $text );
1405 
1406  if ( MWTidy::isEnabled() ) {
1407  if ( $this->mOptions->getTidy() ) {
1408  $text = MWTidy::tidy( $text );
1409  }
1410  } else {
1411  # attempt to sanitize at least some nesting problems
1412  # (T4702 and quite a few others)
1413  $tidyregs = [
1414  # ''Something [http://www.cool.com cool''] -->
1415  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1416  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1417  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1418  # fix up an anchor inside another anchor, only
1419  # at least for a single single nested link (T5695)
1420  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1421  '\\1\\2</a>\\3</a>\\1\\4</a>',
1422  # fix div inside inline elements- doBlockLevels won't wrap a line which
1423  # contains a div, so fix it up here; replace
1424  # div with escaped text
1425  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1426  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1427  # remove empty italic or bold tag pairs, some
1428  # introduced by rules above
1429  '/<([bi])><\/\\1>/' => '',
1430  ];
1431 
1432  $text = preg_replace(
1433  array_keys( $tidyregs ),
1434  array_values( $tidyregs ),
1435  $text );
1436  }
1437 
1438  if ( $isMain ) {
1439  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1440  }
1441 
1442  return $text;
1443  }
1444 
1456  public function doMagicLinks( $text ) {
1457  $prots = wfUrlProtocolsWithoutProtRel();
1458  $urlChar = self::EXT_LINK_URL_CLASS;
1459  $addr = self::EXT_LINK_ADDR;
1460  $space = self::SPACE_NOT_NL; # non-newline space
1461  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1462  $spaces = "$space++"; # possessive match of 1 or more spaces
1463  $text = preg_replace_callback(
1464  '!(?: # Start cases
1465  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1466  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1467  (\b # m[3]: Free external links
1468  (?i:$prots)
1469  ($addr$urlChar*) # m[4]: Post-protocol path
1470  ) |
1471  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1472  ([0-9]+)\b |
1473  \bISBN $spaces ( # m[6]: ISBN, capture number
1474  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1475  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1476  [0-9Xx] # check digit
1477  )\b
1478  )!xu", [ $this, 'magicLinkCallback' ], $text );
1479  return $text;
1480  }
1481 
1487  public function magicLinkCallback( $m ) {
1488  if ( isset( $m[1] ) && $m[1] !== '' ) {
1489  # Skip anchor
1490  return $m[0];
1491  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1492  # Skip HTML element
1493  return $m[0];
1494  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1495  # Free external link
1496  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1497  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1498  # RFC or PMID
1499  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1500  if ( !$this->mOptions->getMagicRFCLinks() ) {
1501  return $m[0];
1502  }
1503  $keyword = 'RFC';
1504  $urlmsg = 'rfcurl';
1505  $cssClass = 'mw-magiclink-rfc';
1506  $trackingCat = 'magiclink-tracking-rfc';
1507  $id = $m[5];
1508  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1509  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1510  return $m[0];
1511  }
1512  $keyword = 'PMID';
1513  $urlmsg = 'pubmedurl';
1514  $cssClass = 'mw-magiclink-pmid';
1515  $trackingCat = 'magiclink-tracking-pmid';
1516  $id = $m[5];
1517  } else {
1518  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1519  substr( $m[0], 0, 20 ) . '"' );
1520  }
1521  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1522  $this->addTrackingCategory( $trackingCat );
1523  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1524  } elseif ( isset( $m[6] ) && $m[6] !== ''
1525  && $this->mOptions->getMagicISBNLinks()
1526  ) {
1527  # ISBN
1528  $isbn = $m[6];
1529  $space = self::SPACE_NOT_NL; # non-newline space
1530  $isbn = preg_replace( "/$space/", ' ', $isbn );
1531  $num = strtr( $isbn, [
1532  '-' => '',
1533  ' ' => '',
1534  'x' => 'X',
1535  ] );
1536  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1537  return $this->getLinkRenderer()->makeKnownLink(
1538  SpecialPage::getTitleFor( 'Booksources', $num ),
1539  "ISBN $isbn",
1540  [
1541  'class' => 'internal mw-magiclink-isbn',
1542  'title' => false // suppress title attribute
1543  ]
1544  );
1545  } else {
1546  return $m[0];
1547  }
1548  }
1549 
1559  public function makeFreeExternalLink( $url, $numPostProto ) {
1560  $trail = '';
1561 
1562  # The characters '<' and '>' (which were escaped by
1563  # removeHTMLtags()) should not be included in
1564  # URLs, per RFC 2396.
1565  # Make &nbsp; terminate a URL as well (bug T84937)
1566  $m2 = [];
1567  if ( preg_match(
1568  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1569  $url,
1570  $m2,
1571  PREG_OFFSET_CAPTURE
1572  ) ) {
1573  $trail = substr( $url, $m2[0][1] ) . $trail;
1574  $url = substr( $url, 0, $m2[0][1] );
1575  }
1576 
1577  # Move trailing punctuation to $trail
1578  $sep = ',;\.:!?';
1579  # If there is no left bracket, then consider right brackets fair game too
1580  if ( strpos( $url, '(' ) === false ) {
1581  $sep .= ')';
1582  }
1583 
1584  $urlRev = strrev( $url );
1585  $numSepChars = strspn( $urlRev, $sep );
1586  # Don't break a trailing HTML entity by moving the ; into $trail
1587  # This is in hot code, so use substr_compare to avoid having to
1588  # create a new string object for the comparison
1589  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1590  # more optimization: instead of running preg_match with a $
1591  # anchor, which can be slow, do the match on the reversed
1592  # string starting at the desired offset.
1593  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1594  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1595  $numSepChars--;
1596  }
1597  }
1598  if ( $numSepChars ) {
1599  $trail = substr( $url, -$numSepChars ) . $trail;
1600  $url = substr( $url, 0, -$numSepChars );
1601  }
1602 
1603  # Verify that we still have a real URL after trail removal, and
1604  # not just lone protocol
1605  if ( strlen( $trail ) >= $numPostProto ) {
1606  return $url . $trail;
1607  }
1608 
1609  $url = Sanitizer::cleanUrl( $url );
1610 
1611  # Is this an external image?
1612  $text = $this->maybeMakeExternalImage( $url );
1613  if ( $text === false ) {
1614  # Not an image, make a link
1615  $text = Linker::makeExternalLink( $url,
1616  $this->getConverterLanguage()->markNoConversion( $url, true ),
1617  true, 'free',
1618  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1619  # Register it in the output object...
1620  $this->mOutput->addExternalLink( $url );
1621  }
1622  return $text . $trail;
1623  }
1624 
1634  public function doHeadings( $text ) {
1635  for ( $i = 6; $i >= 1; --$i ) {
1636  $h = str_repeat( '=', $i );
1637  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1638  }
1639  return $text;
1640  }
1641 
1650  public function doAllQuotes( $text ) {
1651  $outtext = '';
1652  $lines = StringUtils::explode( "\n", $text );
1653  foreach ( $lines as $line ) {
1654  $outtext .= $this->doQuotes( $line ) . "\n";
1655  }
1656  $outtext = substr( $outtext, 0, -1 );
1657  return $outtext;
1658  }
1659 
1667  public function doQuotes( $text ) {
1668  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1669  $countarr = count( $arr );
1670  if ( $countarr == 1 ) {
1671  return $text;
1672  }
1673 
1674  // First, do some preliminary work. This may shift some apostrophes from
1675  // being mark-up to being text. It also counts the number of occurrences
1676  // of bold and italics mark-ups.
1677  $numbold = 0;
1678  $numitalics = 0;
1679  for ( $i = 1; $i < $countarr; $i += 2 ) {
1680  $thislen = strlen( $arr[$i] );
1681  // If there are ever four apostrophes, assume the first is supposed to
1682  // be text, and the remaining three constitute mark-up for bold text.
1683  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1684  if ( $thislen == 4 ) {
1685  $arr[$i - 1] .= "'";
1686  $arr[$i] = "'''";
1687  $thislen = 3;
1688  } elseif ( $thislen > 5 ) {
1689  // If there are more than 5 apostrophes in a row, assume they're all
1690  // text except for the last 5.
1691  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1692  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1693  $arr[$i] = "'''''";
1694  $thislen = 5;
1695  }
1696  // Count the number of occurrences of bold and italics mark-ups.
1697  if ( $thislen == 2 ) {
1698  $numitalics++;
1699  } elseif ( $thislen == 3 ) {
1700  $numbold++;
1701  } elseif ( $thislen == 5 ) {
1702  $numitalics++;
1703  $numbold++;
1704  }
1705  }
1706 
1707  // If there is an odd number of both bold and italics, it is likely
1708  // that one of the bold ones was meant to be an apostrophe followed
1709  // by italics. Which one we cannot know for certain, but it is more
1710  // likely to be one that has a single-letter word before it.
1711  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1712  $firstsingleletterword = -1;
1713  $firstmultiletterword = -1;
1714  $firstspace = -1;
1715  for ( $i = 1; $i < $countarr; $i += 2 ) {
1716  if ( strlen( $arr[$i] ) == 3 ) {
1717  $x1 = substr( $arr[$i - 1], -1 );
1718  $x2 = substr( $arr[$i - 1], -2, 1 );
1719  if ( $x1 === ' ' ) {
1720  if ( $firstspace == -1 ) {
1721  $firstspace = $i;
1722  }
1723  } elseif ( $x2 === ' ' ) {
1724  $firstsingleletterword = $i;
1725  // if $firstsingleletterword is set, we don't
1726  // look at the other options, so we can bail early.
1727  break;
1728  } else {
1729  if ( $firstmultiletterword == -1 ) {
1730  $firstmultiletterword = $i;
1731  }
1732  }
1733  }
1734  }
1735 
1736  // If there is a single-letter word, use it!
1737  if ( $firstsingleletterword > -1 ) {
1738  $arr[$firstsingleletterword] = "''";
1739  $arr[$firstsingleletterword - 1] .= "'";
1740  } elseif ( $firstmultiletterword > -1 ) {
1741  // If not, but there's a multi-letter word, use that one.
1742  $arr[$firstmultiletterword] = "''";
1743  $arr[$firstmultiletterword - 1] .= "'";
1744  } elseif ( $firstspace > -1 ) {
1745  // ... otherwise use the first one that has neither.
1746  // (notice that it is possible for all three to be -1 if, for example,
1747  // there is only one pentuple-apostrophe in the line)
1748  $arr[$firstspace] = "''";
1749  $arr[$firstspace - 1] .= "'";
1750  }
1751  }
1752 
1753  // Now let's actually convert our apostrophic mush to HTML!
1754  $output = '';
1755  $buffer = '';
1756  $state = '';
1757  $i = 0;
1758  foreach ( $arr as $r ) {
1759  if ( ( $i % 2 ) == 0 ) {
1760  if ( $state === 'both' ) {
1761  $buffer .= $r;
1762  } else {
1763  $output .= $r;
1764  }
1765  } else {
1766  $thislen = strlen( $r );
1767  if ( $thislen == 2 ) {
1768  if ( $state === 'i' ) {
1769  $output .= '</i>';
1770  $state = '';
1771  } elseif ( $state === 'bi' ) {
1772  $output .= '</i>';
1773  $state = 'b';
1774  } elseif ( $state === 'ib' ) {
1775  $output .= '</b></i><b>';
1776  $state = 'b';
1777  } elseif ( $state === 'both' ) {
1778  $output .= '<b><i>' . $buffer . '</i>';
1779  $state = 'b';
1780  } else { // $state can be 'b' or ''
1781  $output .= '<i>';
1782  $state .= 'i';
1783  }
1784  } elseif ( $thislen == 3 ) {
1785  if ( $state === 'b' ) {
1786  $output .= '</b>';
1787  $state = '';
1788  } elseif ( $state === 'bi' ) {
1789  $output .= '</i></b><i>';
1790  $state = 'i';
1791  } elseif ( $state === 'ib' ) {
1792  $output .= '</b>';
1793  $state = 'i';
1794  } elseif ( $state === 'both' ) {
1795  $output .= '<i><b>' . $buffer . '</b>';
1796  $state = 'i';
1797  } else { // $state can be 'i' or ''
1798  $output .= '<b>';
1799  $state .= 'b';
1800  }
1801  } elseif ( $thislen == 5 ) {
1802  if ( $state === 'b' ) {
1803  $output .= '</b><i>';
1804  $state = 'i';
1805  } elseif ( $state === 'i' ) {
1806  $output .= '</i><b>';
1807  $state = 'b';
1808  } elseif ( $state === 'bi' ) {
1809  $output .= '</i></b>';
1810  $state = '';
1811  } elseif ( $state === 'ib' ) {
1812  $output .= '</b></i>';
1813  $state = '';
1814  } elseif ( $state === 'both' ) {
1815  $output .= '<i><b>' . $buffer . '</b></i>';
1816  $state = '';
1817  } else { // ($state == '')
1818  $buffer = '';
1819  $state = 'both';
1820  }
1821  }
1822  }
1823  $i++;
1824  }
1825  // Now close all remaining tags. Notice that the order is important.
1826  if ( $state === 'b' || $state === 'ib' ) {
1827  $output .= '</b>';
1828  }
1829  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1830  $output .= '</i>';
1831  }
1832  if ( $state === 'bi' ) {
1833  $output .= '</b>';
1834  }
1835  // There might be lonely ''''', so make sure we have a buffer
1836  if ( $state === 'both' && $buffer ) {
1837  $output .= '<b><i>' . $buffer . '</i></b>';
1838  }
1839  return $output;
1840  }
1841 
1855  public function replaceExternalLinks( $text ) {
1856  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1857  if ( $bits === false ) {
1858  throw new MWException( "PCRE needs to be compiled with "
1859  . "--enable-unicode-properties in order for MediaWiki to function" );
1860  }
1861  $s = array_shift( $bits );
1862 
1863  $i = 0;
1864  while ( $i < count( $bits ) ) {
1865  $url = $bits[$i++];
1866  $i++; // protocol
1867  $text = $bits[$i++];
1868  $trail = $bits[$i++];
1869 
1870  # The characters '<' and '>' (which were escaped by
1871  # removeHTMLtags()) should not be included in
1872  # URLs, per RFC 2396.
1873  $m2 = [];
1874  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1875  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1876  $url = substr( $url, 0, $m2[0][1] );
1877  }
1878 
1879  # If the link text is an image URL, replace it with an <img> tag
1880  # This happened by accident in the original parser, but some people used it extensively
1881  $img = $this->maybeMakeExternalImage( $text );
1882  if ( $img !== false ) {
1883  $text = $img;
1884  }
1885 
1886  $dtrail = '';
1887 
1888  # Set linktype for CSS - if URL==text, link is essentially free
1889  $linktype = ( $text === $url ) ? 'free' : 'text';
1890 
1891  # No link text, e.g. [http://domain.tld/some.link]
1892  if ( $text == '' ) {
1893  # Autonumber
1894  $langObj = $this->getTargetLanguage();
1895  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1896  $linktype = 'autonumber';
1897  } else {
1898  # Have link text, e.g. [http://domain.tld/some.link text]s
1899  # Check for trail
1900  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1901  }
1902 
1903  $text = $this->getConverterLanguage()->markNoConversion( $text );
1904 
1905  $url = Sanitizer::cleanUrl( $url );
1906 
1907  # Use the encoded URL
1908  # This means that users can paste URLs directly into the text
1909  # Funny characters like ö aren't valid in URLs anyway
1910  # This was changed in August 2004
1911  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1912  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1913 
1914  # Register link in the output object.
1915  $this->mOutput->addExternalLink( $url );
1916  }
1917 
1918  return $s;
1919  }
1920 
1930  public static function getExternalLinkRel( $url = false, $title = null ) {
1931  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1932  $ns = $title ? $title->getNamespace() : false;
1933  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1934  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1935  ) {
1936  return 'nofollow';
1937  }
1938  return null;
1939  }
1940 
1951  public function getExternalLinkAttribs( $url ) {
1952  $attribs = [];
1953  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1954 
1955  $target = $this->mOptions->getExternalLinkTarget();
1956  if ( $target ) {
1957  $attribs['target'] = $target;
1958  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1959  // T133507. New windows can navigate parent cross-origin.
1960  // Including noreferrer due to lacking browser
1961  // support of noopener. Eventually noreferrer should be removed.
1962  if ( $rel !== '' ) {
1963  $rel .= ' ';
1964  }
1965  $rel .= 'noreferrer noopener';
1966  }
1967  }
1968  $attribs['rel'] = $rel;
1969  return $attribs;
1970  }
1971 
1981  public static function normalizeLinkUrl( $url ) {
1982  # First, make sure unsafe characters are encoded
1983  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1984  function ( $m ) {
1985  return rawurlencode( $m[0] );
1986  },
1987  $url
1988  );
1989 
1990  $ret = '';
1991  $end = strlen( $url );
1992 
1993  # Fragment part - 'fragment'
1994  $start = strpos( $url, '#' );
1995  if ( $start !== false && $start < $end ) {
1996  $ret = self::normalizeUrlComponent(
1997  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1998  $end = $start;
1999  }
2000 
2001  # Query part - 'query' minus &=+;
2002  $start = strpos( $url, '?' );
2003  if ( $start !== false && $start < $end ) {
2004  $ret = self::normalizeUrlComponent(
2005  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2006  $end = $start;
2007  }
2008 
2009  # Scheme and path part - 'pchar'
2010  # (we assume no userinfo or encoded colons in the host)
2011  $ret = self::normalizeUrlComponent(
2012  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2013 
2014  return $ret;
2015  }
2016 
2017  private static function normalizeUrlComponent( $component, $unsafe ) {
2018  $callback = function ( $matches ) use ( $unsafe ) {
2019  $char = urldecode( $matches[0] );
2020  $ord = ord( $char );
2021  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2022  # Unescape it
2023  return $char;
2024  } else {
2025  # Leave it escaped, but use uppercase for a-f
2026  return strtoupper( $matches[0] );
2027  }
2028  };
2029  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2030  }
2031 
2040  private function maybeMakeExternalImage( $url ) {
2041  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2042  $imagesexception = !empty( $imagesfrom );
2043  $text = false;
2044  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2045  if ( $imagesexception && is_array( $imagesfrom ) ) {
2046  $imagematch = false;
2047  foreach ( $imagesfrom as $match ) {
2048  if ( strpos( $url, $match ) === 0 ) {
2049  $imagematch = true;
2050  break;
2051  }
2052  }
2053  } elseif ( $imagesexception ) {
2054  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2055  } else {
2056  $imagematch = false;
2057  }
2058 
2059  if ( $this->mOptions->getAllowExternalImages()
2060  || ( $imagesexception && $imagematch )
2061  ) {
2062  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2063  # Image found
2064  $text = Linker::makeExternalImage( $url );
2065  }
2066  }
2067  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2068  && preg_match( self::EXT_IMAGE_REGEX, $url )
2069  ) {
2070  $whitelist = explode(
2071  "\n",
2072  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2073  );
2074 
2075  foreach ( $whitelist as $entry ) {
2076  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2077  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2078  continue;
2079  }
2080  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2081  # Image matches a whitelist entry
2082  $text = Linker::makeExternalImage( $url );
2083  break;
2084  }
2085  }
2086  }
2087  return $text;
2088  }
2089 
2099  public function replaceInternalLinks( $s ) {
2100  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2101  return $s;
2102  }
2103 
2112  public function replaceInternalLinks2( &$s ) {
2114 
2115  static $tc = false, $e1, $e1_img;
2116  # the % is needed to support urlencoded titles as well
2117  if ( !$tc ) {
2118  $tc = Title::legalChars() . '#%';
2119  # Match a link having the form [[namespace:link|alternate]]trail
2120  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2121  # Match cases where there is no "]]", which might still be images
2122  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2123  }
2124 
2125  $holders = new LinkHolderArray( $this );
2126 
2127  # split the entire text string on occurrences of [[
2128  $a = StringUtils::explode( '[[', ' ' . $s );
2129  # get the first element (all text up to first [[), and remove the space we added
2130  $s = $a->current();
2131  $a->next();
2132  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2133  $s = substr( $s, 1 );
2134 
2135  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2136  $e2 = null;
2137  if ( $useLinkPrefixExtension ) {
2138  # Match the end of a line for a word that's not followed by whitespace,
2139  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2141  $charset = $wgContLang->linkPrefixCharset();
2142  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2143  }
2144 
2145  if ( is_null( $this->mTitle ) ) {
2146  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2147  }
2148  $nottalk = !$this->mTitle->isTalkPage();
2149 
2150  if ( $useLinkPrefixExtension ) {
2151  $m = [];
2152  if ( preg_match( $e2, $s, $m ) ) {
2153  $first_prefix = $m[2];
2154  } else {
2155  $first_prefix = false;
2156  }
2157  } else {
2158  $prefix = '';
2159  }
2160 
2161  $useSubpages = $this->areSubpagesAllowed();
2162 
2163  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2164  # Loop for each link
2165  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2166  // @codingStandardsIgnoreEnd
2167 
2168  # Check for excessive memory usage
2169  if ( $holders->isBig() ) {
2170  # Too big
2171  # Do the existence check, replace the link holders and clear the array
2172  $holders->replace( $s );
2173  $holders->clear();
2174  }
2175 
2176  if ( $useLinkPrefixExtension ) {
2177  if ( preg_match( $e2, $s, $m ) ) {
2178  $prefix = $m[2];
2179  $s = $m[1];
2180  } else {
2181  $prefix = '';
2182  }
2183  # first link
2184  if ( $first_prefix ) {
2185  $prefix = $first_prefix;
2186  $first_prefix = false;
2187  }
2188  }
2189 
2190  $might_be_img = false;
2191 
2192  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2193  $text = $m[2];
2194  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2195  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2196  # the real problem is with the $e1 regex
2197  # See T1500.
2198  # Still some problems for cases where the ] is meant to be outside punctuation,
2199  # and no image is in sight. See T4095.
2200  if ( $text !== ''
2201  && substr( $m[3], 0, 1 ) === ']'
2202  && strpos( $text, '[' ) !== false
2203  ) {
2204  $text .= ']'; # so that replaceExternalLinks($text) works later
2205  $m[3] = substr( $m[3], 1 );
2206  }
2207  # fix up urlencoded title texts
2208  if ( strpos( $m[1], '%' ) !== false ) {
2209  # Should anchors '#' also be rejected?
2210  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2211  }
2212  $trail = $m[3];
2213  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2214  # Invalid, but might be an image with a link in its caption
2215  $might_be_img = true;
2216  $text = $m[2];
2217  if ( strpos( $m[1], '%' ) !== false ) {
2218  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2219  }
2220  $trail = "";
2221  } else { # Invalid form; output directly
2222  $s .= $prefix . '[[' . $line;
2223  continue;
2224  }
2225 
2226  $origLink = ltrim( $m[1], ' ' );
2227 
2228  # Don't allow internal links to pages containing
2229  # PROTO: where PROTO is a valid URL protocol; these
2230  # should be external links.
2231  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2232  $s .= $prefix . '[[' . $line;
2233  continue;
2234  }
2235 
2236  # Make subpage if necessary
2237  if ( $useSubpages ) {
2238  $link = $this->maybeDoSubpageLink( $origLink, $text );
2239  } else {
2240  $link = $origLink;
2241  }
2242 
2243  $unstrip = $this->mStripState->unstripNoWiki( $link );
2244  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2245  if ( $nt === null ) {
2246  $s .= $prefix . '[[' . $line;
2247  continue;
2248  }
2249 
2250  $ns = $nt->getNamespace();
2251  $iw = $nt->getInterwiki();
2252 
2253  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2254 
2255  if ( $might_be_img ) { # if this is actually an invalid link
2256  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2257  $found = false;
2258  while ( true ) {
2259  # look at the next 'line' to see if we can close it there
2260  $a->next();
2261  $next_line = $a->current();
2262  if ( $next_line === false || $next_line === null ) {
2263  break;
2264  }
2265  $m = explode( ']]', $next_line, 3 );
2266  if ( count( $m ) == 3 ) {
2267  # the first ]] closes the inner link, the second the image
2268  $found = true;
2269  $text .= "[[{$m[0]}]]{$m[1]}";
2270  $trail = $m[2];
2271  break;
2272  } elseif ( count( $m ) == 2 ) {
2273  # if there's exactly one ]] that's fine, we'll keep looking
2274  $text .= "[[{$m[0]}]]{$m[1]}";
2275  } else {
2276  # if $next_line is invalid too, we need look no further
2277  $text .= '[[' . $next_line;
2278  break;
2279  }
2280  }
2281  if ( !$found ) {
2282  # we couldn't find the end of this imageLink, so output it raw
2283  # but don't ignore what might be perfectly normal links in the text we've examined
2284  $holders->merge( $this->replaceInternalLinks2( $text ) );
2285  $s .= "{$prefix}[[$link|$text";
2286  # note: no $trail, because without an end, there *is* no trail
2287  continue;
2288  }
2289  } else { # it's not an image, so output it raw
2290  $s .= "{$prefix}[[$link|$text";
2291  # note: no $trail, because without an end, there *is* no trail
2292  continue;
2293  }
2294  }
2295 
2296  $wasblank = ( $text == '' );
2297  if ( $wasblank ) {
2298  $text = $link;
2299  if ( !$noforce ) {
2300  # Strip off leading ':'
2301  $text = substr( $text, 1 );
2302  }
2303  } else {
2304  # T6598 madness. Handle the quotes only if they come from the alternate part
2305  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2306  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2307  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2308  $text = $this->doQuotes( $text );
2309  }
2310 
2311  # Link not escaped by : , create the various objects
2312  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2313  # Interwikis
2314  if (
2315  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2316  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2317  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2318  )
2319  ) {
2320  # T26502: filter duplicates
2321  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2322  $this->mLangLinkLanguages[$iw] = true;
2323  $this->mOutput->addLanguageLink( $nt->getFullText() );
2324  }
2325 
2326  $s = rtrim( $s . $prefix );
2327  $s .= trim( $trail, "\n" ) == '' ? '' : $prefix . $trail;
2328  continue;
2329  }
2330 
2331  if ( $ns == NS_FILE ) {
2332  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2333  if ( $wasblank ) {
2334  # if no parameters were passed, $text
2335  # becomes something like "File:Foo.png",
2336  # which we don't want to pass on to the
2337  # image generator
2338  $text = '';
2339  } else {
2340  # recursively parse links inside the image caption
2341  # actually, this will parse them in any other parameters, too,
2342  # but it might be hard to fix that, and it doesn't matter ATM
2343  $text = $this->replaceExternalLinks( $text );
2344  $holders->merge( $this->replaceInternalLinks2( $text ) );
2345  }
2346  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2347  $s .= $prefix . $this->armorLinks(
2348  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2349  continue;
2350  }
2351  } elseif ( $ns == NS_CATEGORY ) {
2352  $s = rtrim( $s . "\n" ); # T2087
2353 
2354  if ( $wasblank ) {
2355  $sortkey = $this->getDefaultSort();
2356  } else {
2357  $sortkey = $text;
2358  }
2359  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2360  $sortkey = str_replace( "\n", '', $sortkey );
2361  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2362  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2363 
2367  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2368 
2369  continue;
2370  }
2371  }
2372 
2373  # Self-link checking. For some languages, variants of the title are checked in
2374  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2375  # for linking to a different variant.
2376  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2377  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2378  continue;
2379  }
2380 
2381  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2382  # @todo FIXME: Should do batch file existence checks, see comment below
2383  if ( $ns == NS_MEDIA ) {
2384  # Give extensions a chance to select the file revision for us
2385  $options = [];
2386  $descQuery = false;
2387  Hooks::run( 'BeforeParserFetchFileAndTitle',
2388  [ $this, $nt, &$options, &$descQuery ] );
2389  # Fetch and register the file (file title may be different via hooks)
2390  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2391  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2392  $s .= $prefix . $this->armorLinks(
2393  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2394  continue;
2395  }
2396 
2397  # Some titles, such as valid special pages or files in foreign repos, should
2398  # be shown as bluelinks even though they're not included in the page table
2399  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2400  # batch file existence checks for NS_FILE and NS_MEDIA
2401  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2402  $this->mOutput->addLink( $nt );
2403  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2404  } else {
2405  # Links will be added to the output link list after checking
2406  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2407  }
2408  }
2409  return $holders;
2410  }
2411 
2425  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2426  list( $inside, $trail ) = Linker::splitTrail( $trail );
2427 
2428  if ( $text == '' ) {
2429  $text = htmlspecialchars( $nt->getPrefixedText() );
2430  }
2431 
2432  $link = $this->getLinkRenderer()->makeKnownLink(
2433  $nt, new HtmlArmor( "$prefix$text$inside" )
2434  );
2435 
2436  return $this->armorLinks( $link ) . $trail;
2437  }
2438 
2449  public function armorLinks( $text ) {
2450  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2451  self::MARKER_PREFIX . "NOPARSE$1", $text );
2452  }
2453 
2458  public function areSubpagesAllowed() {
2459  # Some namespaces don't allow subpages
2460  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2461  }
2462 
2471  public function maybeDoSubpageLink( $target, &$text ) {
2472  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2473  }
2474 
2483  public function doBlockLevels( $text, $linestart ) {
2484  return BlockLevelPass::doBlockLevels( $text, $linestart );
2485  }
2486 
2498  public function getVariableValue( $index, $frame = false ) {
2501 
2502  if ( is_null( $this->mTitle ) ) {
2503  // If no title set, bad things are going to happen
2504  // later. Title should always be set since this
2505  // should only be called in the middle of a parse
2506  // operation (but the unit-tests do funky stuff)
2507  throw new MWException( __METHOD__ . ' Should only be '
2508  . ' called while parsing (no title set)' );
2509  }
2510 
2511  // Avoid PHP 7.1 warning from passing $this by reference
2512  $parser = $this;
2513 
2518  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2519  if ( isset( $this->mVarCache[$index] ) ) {
2520  return $this->mVarCache[$index];
2521  }
2522  }
2523 
2524  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2525  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2526 
2527  $pageLang = $this->getFunctionLang();
2528 
2529  switch ( $index ) {
2530  case '!':
2531  $value = '|';
2532  break;
2533  case 'currentmonth':
2534  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2535  break;
2536  case 'currentmonth1':
2537  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2538  break;
2539  case 'currentmonthname':
2540  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2541  break;
2542  case 'currentmonthnamegen':
2543  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2544  break;
2545  case 'currentmonthabbrev':
2546  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2547  break;
2548  case 'currentday':
2549  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2550  break;
2551  case 'currentday2':
2552  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2553  break;
2554  case 'localmonth':
2555  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2556  break;
2557  case 'localmonth1':
2558  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2559  break;
2560  case 'localmonthname':
2561  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2562  break;
2563  case 'localmonthnamegen':
2564  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2565  break;
2566  case 'localmonthabbrev':
2567  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2568  break;
2569  case 'localday':
2570  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2571  break;
2572  case 'localday2':
2573  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2574  break;
2575  case 'pagename':
2576  $value = wfEscapeWikiText( $this->mTitle->getText() );
2577  break;
2578  case 'pagenamee':
2579  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2580  break;
2581  case 'fullpagename':
2582  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2583  break;
2584  case 'fullpagenamee':
2585  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2586  break;
2587  case 'subpagename':
2588  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2589  break;
2590  case 'subpagenamee':
2591  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2592  break;
2593  case 'rootpagename':
2594  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2595  break;
2596  case 'rootpagenamee':
2597  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2598  ' ',
2599  '_',
2600  $this->mTitle->getRootText()
2601  ) ) );
2602  break;
2603  case 'basepagename':
2604  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2605  break;
2606  case 'basepagenamee':
2607  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2608  ' ',
2609  '_',
2610  $this->mTitle->getBaseText()
2611  ) ) );
2612  break;
2613  case 'talkpagename':
2614  if ( $this->mTitle->canTalk() ) {
2615  $talkPage = $this->mTitle->getTalkPage();
2616  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2617  } else {
2618  $value = '';
2619  }
2620  break;
2621  case 'talkpagenamee':
2622  if ( $this->mTitle->canTalk() ) {
2623  $talkPage = $this->mTitle->getTalkPage();
2624  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2625  } else {
2626  $value = '';
2627  }
2628  break;
2629  case 'subjectpagename':
2630  $subjPage = $this->mTitle->getSubjectPage();
2631  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2632  break;
2633  case 'subjectpagenamee':
2634  $subjPage = $this->mTitle->getSubjectPage();
2635  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2636  break;
2637  case 'pageid': // requested in T25427
2638  $pageid = $this->getTitle()->getArticleID();
2639  if ( $pageid == 0 ) {
2640  # 0 means the page doesn't exist in the database,
2641  # which means the user is previewing a new page.
2642  # The vary-revision flag must be set, because the magic word
2643  # will have a different value once the page is saved.
2644  $this->mOutput->setFlag( 'vary-revision' );
2645  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2646  }
2647  $value = $pageid ? $pageid : null;
2648  break;
2649  case 'revisionid':
2650  # Let the edit saving system know we should parse the page
2651  # *after* a revision ID has been assigned.
2652  $this->mOutput->setFlag( 'vary-revision-id' );
2653  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2654  $value = $this->mRevisionId;
2655  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2656  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2657  $this->mOutput->setSpeculativeRevIdUsed( $value );
2658  }
2659  break;
2660  case 'revisionday':
2661  # Let the edit saving system know we should parse the page
2662  # *after* a revision ID has been assigned. This is for null edits.
2663  $this->mOutput->setFlag( 'vary-revision' );
2664  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2665  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2666  break;
2667  case 'revisionday2':
2668  # Let the edit saving system know we should parse the page
2669  # *after* a revision ID has been assigned. This is for null edits.
2670  $this->mOutput->setFlag( 'vary-revision' );
2671  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2672  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2673  break;
2674  case 'revisionmonth':
2675  # Let the edit saving system know we should parse the page
2676  # *after* a revision ID has been assigned. This is for null edits.
2677  $this->mOutput->setFlag( 'vary-revision' );
2678  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2679  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2680  break;
2681  case 'revisionmonth1':
2682  # Let the edit saving system know we should parse the page
2683  # *after* a revision ID has been assigned. This is for null edits.
2684  $this->mOutput->setFlag( 'vary-revision' );
2685  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2686  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2687  break;
2688  case 'revisionyear':
2689  # Let the edit saving system know we should parse the page
2690  # *after* a revision ID has been assigned. This is for null edits.
2691  $this->mOutput->setFlag( 'vary-revision' );
2692  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2693  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2694  break;
2695  case 'revisiontimestamp':
2696  # Let the edit saving system know we should parse the page
2697  # *after* a revision ID has been assigned. This is for null edits.
2698  $this->mOutput->setFlag( 'vary-revision' );
2699  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2700  $value = $this->getRevisionTimestamp();
2701  break;
2702  case 'revisionuser':
2703  # Let the edit saving system know we should parse the page
2704  # *after* a revision ID has been assigned for null edits.
2705  $this->mOutput->setFlag( 'vary-user' );
2706  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2707  $value = $this->getRevisionUser();
2708  break;
2709  case 'revisionsize':
2710  $value = $this->getRevisionSize();
2711  break;
2712  case 'namespace':
2713  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2714  break;
2715  case 'namespacee':
2716  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2717  break;
2718  case 'namespacenumber':
2719  $value = $this->mTitle->getNamespace();
2720  break;
2721  case 'talkspace':
2722  $value = $this->mTitle->canTalk()
2723  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2724  : '';
2725  break;
2726  case 'talkspacee':
2727  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2728  break;
2729  case 'subjectspace':
2730  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2731  break;
2732  case 'subjectspacee':
2733  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2734  break;
2735  case 'currentdayname':
2736  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2737  break;
2738  case 'currentyear':
2739  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2740  break;
2741  case 'currenttime':
2742  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2743  break;
2744  case 'currenthour':
2745  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2746  break;
2747  case 'currentweek':
2748  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2749  # int to remove the padding
2750  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2751  break;
2752  case 'currentdow':
2753  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2754  break;
2755  case 'localdayname':
2756  $value = $pageLang->getWeekdayName(
2757  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2758  );
2759  break;
2760  case 'localyear':
2761  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2762  break;
2763  case 'localtime':
2764  $value = $pageLang->time(
2765  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2766  false,
2767  false
2768  );
2769  break;
2770  case 'localhour':
2771  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2772  break;
2773  case 'localweek':
2774  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2775  # int to remove the padding
2776  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2777  break;
2778  case 'localdow':
2779  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2780  break;
2781  case 'numberofarticles':
2782  $value = $pageLang->formatNum( SiteStats::articles() );
2783  break;
2784  case 'numberoffiles':
2785  $value = $pageLang->formatNum( SiteStats::images() );
2786  break;
2787  case 'numberofusers':
2788  $value = $pageLang->formatNum( SiteStats::users() );
2789  break;
2790  case 'numberofactiveusers':
2791  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2792  break;
2793  case 'numberofpages':
2794  $value = $pageLang->formatNum( SiteStats::pages() );
2795  break;
2796  case 'numberofadmins':
2797  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2798  break;
2799  case 'numberofedits':
2800  $value = $pageLang->formatNum( SiteStats::edits() );
2801  break;
2802  case 'currenttimestamp':
2803  $value = wfTimestamp( TS_MW, $ts );
2804  break;
2805  case 'localtimestamp':
2806  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2807  break;
2808  case 'currentversion':
2810  break;
2811  case 'articlepath':
2812  return $wgArticlePath;
2813  case 'sitename':
2814  return $wgSitename;
2815  case 'server':
2816  return $wgServer;
2817  case 'servername':
2818  return $wgServerName;
2819  case 'scriptpath':
2820  return $wgScriptPath;
2821  case 'stylepath':
2822  return $wgStylePath;
2823  case 'directionmark':
2824  return $pageLang->getDirMark();
2825  case 'contentlanguage':
2827  return $wgLanguageCode;
2828  case 'pagelanguage':
2829  $value = $pageLang->getCode();
2830  break;
2831  case 'cascadingsources':
2833  break;
2834  default:
2835  $ret = null;
2836  Hooks::run(
2837  'ParserGetVariableValueSwitch',
2838  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2839  );
2840 
2841  return $ret;
2842  }
2843 
2844  if ( $index ) {
2845  $this->mVarCache[$index] = $value;
2846  }
2847 
2848  return $value;
2849  }
2850 
2856  public function initialiseVariables() {
2857  $variableIDs = MagicWord::getVariableIDs();
2858  $substIDs = MagicWord::getSubstIDs();
2859 
2860  $this->mVariables = new MagicWordArray( $variableIDs );
2861  $this->mSubstWords = new MagicWordArray( $substIDs );
2862  }
2863 
2886  public function preprocessToDom( $text, $flags = 0 ) {
2887  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2888  return $dom;
2889  }
2890 
2898  public static function splitWhitespace( $s ) {
2899  $ltrimmed = ltrim( $s );
2900  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2901  $trimmed = rtrim( $ltrimmed );
2902  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2903  if ( $diff > 0 ) {
2904  $w2 = substr( $ltrimmed, -$diff );
2905  } else {
2906  $w2 = '';
2907  }
2908  return [ $w1, $trimmed, $w2 ];
2909  }
2910 
2931  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2932  # Is there any text? Also, Prevent too big inclusions!
2933  $textSize = strlen( $text );
2934  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2935  return $text;
2936  }
2937 
2938  if ( $frame === false ) {
2939  $frame = $this->getPreprocessor()->newFrame();
2940  } elseif ( !( $frame instanceof PPFrame ) ) {
2941  wfDebug( __METHOD__ . " called using plain parameters instead of "
2942  . "a PPFrame instance. Creating custom frame.\n" );
2943  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2944  }
2945 
2946  $dom = $this->preprocessToDom( $text );
2947  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2948  $text = $frame->expand( $dom, $flags );
2949 
2950  return $text;
2951  }
2952 
2960  public static function createAssocArgs( $args ) {
2961  $assocArgs = [];
2962  $index = 1;
2963  foreach ( $args as $arg ) {
2964  $eqpos = strpos( $arg, '=' );
2965  if ( $eqpos === false ) {
2966  $assocArgs[$index++] = $arg;
2967  } else {
2968  $name = trim( substr( $arg, 0, $eqpos ) );
2969  $value = trim( substr( $arg, $eqpos + 1 ) );
2970  if ( $value === false ) {
2971  $value = '';
2972  }
2973  if ( $name !== false ) {
2974  $assocArgs[$name] = $value;
2975  }
2976  }
2977  }
2978 
2979  return $assocArgs;
2980  }
2981 
3008  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
3009  # does no harm if $current and $max are present but are unnecessary for the message
3010  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
3011  # only during preview, and that would split the parser cache unnecessarily.
3012  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
3013  ->text();
3014  $this->mOutput->addWarning( $warning );
3015  $this->addTrackingCategory( "$limitationType-category" );
3016  }
3017 
3030  public function braceSubstitution( $piece, $frame ) {
3031  // Flags
3032 
3033  // $text has been filled
3034  $found = false;
3035  // wiki markup in $text should be escaped
3036  $nowiki = false;
3037  // $text is HTML, armour it against wikitext transformation
3038  $isHTML = false;
3039  // Force interwiki transclusion to be done in raw mode not rendered
3040  $forceRawInterwiki = false;
3041  // $text is a DOM node needing expansion in a child frame
3042  $isChildObj = false;
3043  // $text is a DOM node needing expansion in the current frame
3044  $isLocalObj = false;
3045 
3046  # Title object, where $text came from
3047  $title = false;
3048 
3049  # $part1 is the bit before the first |, and must contain only title characters.
3050  # Various prefixes will be stripped from it later.
3051  $titleWithSpaces = $frame->expand( $piece['title'] );
3052  $part1 = trim( $titleWithSpaces );
3053  $titleText = false;
3054 
3055  # Original title text preserved for various purposes
3056  $originalTitle = $part1;
3057 
3058  # $args is a list of argument nodes, starting from index 0, not including $part1
3059  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3060  # below won't work b/c this $args isn't an object
3061  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3062 
3063  $profileSection = null; // profile templates
3064 
3065  # SUBST
3066  if ( !$found ) {
3067  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3068 
3069  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3070  # Decide whether to expand template or keep wikitext as-is.
3071  if ( $this->ot['wiki'] ) {
3072  if ( $substMatch === false ) {
3073  $literal = true; # literal when in PST with no prefix
3074  } else {
3075  $literal = false; # expand when in PST with subst: or safesubst:
3076  }
3077  } else {
3078  if ( $substMatch == 'subst' ) {
3079  $literal = true; # literal when not in PST with plain subst:
3080  } else {
3081  $literal = false; # expand when not in PST with safesubst: or no prefix
3082  }
3083  }
3084  if ( $literal ) {
3085  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3086  $isLocalObj = true;
3087  $found = true;
3088  }
3089  }
3090 
3091  # Variables
3092  if ( !$found && $args->getLength() == 0 ) {
3093  $id = $this->mVariables->matchStartToEnd( $part1 );
3094  if ( $id !== false ) {
3095  $text = $this->getVariableValue( $id, $frame );
3096  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3097  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3098  }
3099  $found = true;
3100  }
3101  }
3102 
3103  # MSG, MSGNW and RAW
3104  if ( !$found ) {
3105  # Check for MSGNW:
3106  $mwMsgnw = MagicWord::get( 'msgnw' );
3107  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3108  $nowiki = true;
3109  } else {
3110  # Remove obsolete MSG:
3111  $mwMsg = MagicWord::get( 'msg' );
3112  $mwMsg->matchStartAndRemove( $part1 );
3113  }
3114 
3115  # Check for RAW:
3116  $mwRaw = MagicWord::get( 'raw' );
3117  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3118  $forceRawInterwiki = true;
3119  }
3120  }
3121 
3122  # Parser functions
3123  if ( !$found ) {
3124  $colonPos = strpos( $part1, ':' );
3125  if ( $colonPos !== false ) {
3126  $func = substr( $part1, 0, $colonPos );
3127  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3128  $argsLength = $args->getLength();
3129  for ( $i = 0; $i < $argsLength; $i++ ) {
3130  $funcArgs[] = $args->item( $i );
3131  }
3132  try {
3133  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3134  } catch ( Exception $ex ) {
3135  throw $ex;
3136  }
3137 
3138  # The interface for parser functions allows for extracting
3139  # flags into the local scope. Extract any forwarded flags
3140  # here.
3141  extract( $result );
3142  }
3143  }
3144 
3145  # Finish mangling title and then check for loops.
3146  # Set $title to a Title object and $titleText to the PDBK
3147  if ( !$found ) {
3148  $ns = NS_TEMPLATE;
3149  # Split the title into page and subpage
3150  $subpage = '';
3151  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3152  if ( $part1 !== $relative ) {
3153  $part1 = $relative;
3154  $ns = $this->mTitle->getNamespace();
3155  }
3156  $title = Title::newFromText( $part1, $ns );
3157  if ( $title ) {
3158  $titleText = $title->getPrefixedText();
3159  # Check for language variants if the template is not found
3160  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3161  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3162  }
3163  # Do recursion depth check
3164  $limit = $this->mOptions->getMaxTemplateDepth();
3165  if ( $frame->depth >= $limit ) {
3166  $found = true;
3167  $text = '<span class="error">'
3168  . wfMessage( 'parser-template-recursion-depth-warning' )
3169  ->numParams( $limit )->inContentLanguage()->text()
3170  . '</span>';
3171  }
3172  }
3173  }
3174 
3175  # Load from database
3176  if ( !$found && $title ) {
3177  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3178  if ( !$title->isExternal() ) {
3179  if ( $title->isSpecialPage()
3180  && $this->mOptions->getAllowSpecialInclusion()
3181  && $this->ot['html']
3182  ) {
3183  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3184  // Pass the template arguments as URL parameters.
3185  // "uselang" will have no effect since the Language object
3186  // is forced to the one defined in ParserOptions.
3187  $pageArgs = [];
3188  $argsLength = $args->getLength();
3189  for ( $i = 0; $i < $argsLength; $i++ ) {
3190  $bits = $args->item( $i )->splitArg();
3191  if ( strval( $bits['index'] ) === '' ) {
3192  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3193  $value = trim( $frame->expand( $bits['value'] ) );
3194  $pageArgs[$name] = $value;
3195  }
3196  }
3197 
3198  // Create a new context to execute the special page
3199  $context = new RequestContext;
3200  $context->setTitle( $title );
3201  $context->setRequest( new FauxRequest( $pageArgs ) );
3202  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3203  $context->setUser( $this->getUser() );
3204  } else {
3205  // If this page is cached, then we better not be per user.
3206  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3207  }
3208  $context->setLanguage( $this->mOptions->getUserLangObj() );
3210  $title, $context, $this->getLinkRenderer() );
3211  if ( $ret ) {
3212  $text = $context->getOutput()->getHTML();
3213  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3214  $found = true;
3215  $isHTML = true;
3216  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3217  $this->mOutput->updateRuntimeAdaptiveExpiry(
3218  $specialPage->maxIncludeCacheTime()
3219  );
3220  }
3221  }
3222  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3223  $found = false; # access denied
3224  wfDebug( __METHOD__ . ": template inclusion denied for " .
3225  $title->getPrefixedDBkey() . "\n" );
3226  } else {
3227  list( $text, $title ) = $this->getTemplateDom( $title );
3228  if ( $text !== false ) {
3229  $found = true;
3230  $isChildObj = true;
3231  }
3232  }
3233 
3234  # If the title is valid but undisplayable, make a link to it
3235  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3236  $text = "[[:$titleText]]";
3237  $found = true;
3238  }
3239  } elseif ( $title->isTrans() ) {
3240  # Interwiki transclusion
3241  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3242  $text = $this->interwikiTransclude( $title, 'render' );
3243  $isHTML = true;
3244  } else {
3245  $text = $this->interwikiTransclude( $title, 'raw' );
3246  # Preprocess it like a template
3247  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3248  $isChildObj = true;
3249  }
3250  $found = true;
3251  }
3252 
3253  # Do infinite loop check
3254  # This has to be done after redirect resolution to avoid infinite loops via redirects
3255  if ( !$frame->loopCheck( $title ) ) {
3256  $found = true;
3257  $text = '<span class="error">'
3258  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3259  . '</span>';
3260  $this->addTrackingCategory( 'template-loop-category' );
3261  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3262  }
3263  }
3264 
3265  # If we haven't found text to substitute by now, we're done
3266  # Recover the source wikitext and return it
3267  if ( !$found ) {
3268  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3269  if ( $profileSection ) {
3270  $this->mProfiler->scopedProfileOut( $profileSection );
3271  }
3272  return [ 'object' => $text ];
3273  }
3274 
3275  # Expand DOM-style return values in a child frame
3276  if ( $isChildObj ) {
3277  # Clean up argument array
3278  $newFrame = $frame->newChild( $args, $title );
3279 
3280  if ( $nowiki ) {
3281  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3282  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3283  # Expansion is eligible for the empty-frame cache
3284  $text = $newFrame->cachedExpand( $titleText, $text );
3285  } else {
3286  # Uncached expansion
3287  $text = $newFrame->expand( $text );
3288  }
3289  }
3290  if ( $isLocalObj && $nowiki ) {
3291  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3292  $isLocalObj = false;
3293  }
3294 
3295  if ( $profileSection ) {
3296  $this->mProfiler->scopedProfileOut( $profileSection );
3297  }
3298 
3299  # Replace raw HTML by a placeholder
3300  if ( $isHTML ) {
3301  $text = $this->insertStripItem( $text );
3302  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3303  # Escape nowiki-style return values
3304  $text = wfEscapeWikiText( $text );
3305  } elseif ( is_string( $text )
3306  && !$piece['lineStart']
3307  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3308  ) {
3309  # T2529: if the template begins with a table or block-level
3310  # element, it should be treated as beginning a new line.
3311  # This behavior is somewhat controversial.
3312  $text = "\n" . $text;
3313  }
3314 
3315  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3316  # Error, oversize inclusion
3317  if ( $titleText !== false ) {
3318  # Make a working, properly escaped link if possible (T25588)
3319  $text = "[[:$titleText]]";
3320  } else {
3321  # This will probably not be a working link, but at least it may
3322  # provide some hint of where the problem is
3323  preg_replace( '/^:/', '', $originalTitle );
3324  $text = "[[:$originalTitle]]";
3325  }
3326  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3327  . 'post-expand include size too large -->' );
3328  $this->limitationWarn( 'post-expand-template-inclusion' );
3329  }
3330 
3331  if ( $isLocalObj ) {
3332  $ret = [ 'object' => $text ];
3333  } else {
3334  $ret = [ 'text' => $text ];
3335  }
3336 
3337  return $ret;
3338  }
3339 
3359  public function callParserFunction( $frame, $function, array $args = [] ) {
3361 
3362  # Case sensitive functions
3363  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3364  $function = $this->mFunctionSynonyms[1][$function];
3365  } else {
3366  # Case insensitive functions
3367  $function = $wgContLang->lc( $function );
3368  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3369  $function = $this->mFunctionSynonyms[0][$function];
3370  } else {
3371  return [ 'found' => false ];
3372  }
3373  }
3374 
3375  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3376 
3377  // Avoid PHP 7.1 warning from passing $this by reference
3378  $parser = $this;
3379 
3380  $allArgs = [ &$parser ];
3381  if ( $flags & self::SFH_OBJECT_ARGS ) {
3382  # Convert arguments to PPNodes and collect for appending to $allArgs
3383  $funcArgs = [];
3384  foreach ( $args as $k => $v ) {
3385  if ( $v instanceof PPNode || $k === 0 ) {
3386  $funcArgs[] = $v;
3387  } else {
3388  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3389  }
3390  }
3391 
3392  # Add a frame parameter, and pass the arguments as an array
3393  $allArgs[] = $frame;
3394  $allArgs[] = $funcArgs;
3395  } else {
3396  # Convert arguments to plain text and append to $allArgs
3397  foreach ( $args as $k => $v ) {
3398  if ( $v instanceof PPNode ) {
3399  $allArgs[] = trim( $frame->expand( $v ) );
3400  } elseif ( is_int( $k ) && $k >= 0 ) {
3401  $allArgs[] = trim( $v );
3402  } else {
3403  $allArgs[] = trim( "$k=$v" );
3404  }
3405  }
3406  }
3407 
3408  $result = call_user_func_array( $callback, $allArgs );
3409 
3410  # The interface for function hooks allows them to return a wikitext
3411  # string or an array containing the string and any flags. This mungs
3412  # things around to match what this method should return.
3413  if ( !is_array( $result ) ) {
3414  $result =[
3415  'found' => true,
3416  'text' => $result,
3417  ];
3418  } else {
3419  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3420  $result['text'] = $result[0];
3421  }
3422  unset( $result[0] );
3423  $result += [
3424  'found' => true,
3425  ];
3426  }
3427 
3428  $noparse = true;
3429  $preprocessFlags = 0;
3430  if ( isset( $result['noparse'] ) ) {
3431  $noparse = $result['noparse'];
3432  }
3433  if ( isset( $result['preprocessFlags'] ) ) {
3434  $preprocessFlags = $result['preprocessFlags'];
3435  }
3436 
3437  if ( !$noparse ) {
3438  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3439  $result['isChildObj'] = true;
3440  }
3441 
3442  return $result;
3443  }
3444 
3453  public function getTemplateDom( $title ) {
3454  $cacheTitle = $title;
3455  $titleText = $title->getPrefixedDBkey();
3456 
3457  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3458  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3459  $title = Title::makeTitle( $ns, $dbk );
3460  $titleText = $title->getPrefixedDBkey();
3461  }
3462  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3463  return [ $this->mTplDomCache[$titleText], $title ];
3464  }
3465 
3466  # Cache miss, go to the database
3467  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3468 
3469  if ( $text === false ) {
3470  $this->mTplDomCache[$titleText] = false;
3471  return [ false, $title ];
3472  }
3473 
3474  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3475  $this->mTplDomCache[$titleText] = $dom;
3476 
3477  if ( !$title->equals( $cacheTitle ) ) {
3478  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3479  [ $title->getNamespace(), $title->getDBkey() ];
3480  }
3481 
3482  return [ $dom, $title ];
3483  }
3484 
3497  $cacheKey = $title->getPrefixedDBkey();
3498  if ( !$this->currentRevisionCache ) {
3499  $this->currentRevisionCache = new MapCacheLRU( 100 );
3500  }
3501  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3502  $this->currentRevisionCache->set( $cacheKey,
3503  // Defaults to Parser::statelessFetchRevision()
3504  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3505  );
3506  }
3507  return $this->currentRevisionCache->get( $cacheKey );
3508  }
3509 
3519  public static function statelessFetchRevision( Title $title, $parser = false ) {
3520  $pageId = $title->getArticleID();
3521  $revId = $title->getLatestRevID();
3522 
3524  if ( $rev ) {
3525  $rev->setTitle( $title );
3526  }
3527 
3528  return $rev;
3529  }
3530 
3536  public function fetchTemplateAndTitle( $title ) {
3537  // Defaults to Parser::statelessFetchTemplate()
3538  $templateCb = $this->mOptions->getTemplateCallback();
3539  $stuff = call_user_func( $templateCb, $title, $this );
3540  // We use U+007F DELETE to distinguish strip markers from regular text.
3541  $text = $stuff['text'];
3542  if ( is_string( $stuff['text'] ) ) {
3543  $text = strtr( $text, "\x7f", "?" );
3544  }
3545  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3546  if ( isset( $stuff['deps'] ) ) {
3547  foreach ( $stuff['deps'] as $dep ) {
3548  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3549  if ( $dep['title']->equals( $this->getTitle() ) ) {
3550  // If we transclude ourselves, the final result
3551  // will change based on the new version of the page
3552  $this->mOutput->setFlag( 'vary-revision' );
3553  }
3554  }
3555  }
3556  return [ $text, $finalTitle ];
3557  }
3558 
3564  public function fetchTemplate( $title ) {
3565  return $this->fetchTemplateAndTitle( $title )[0];
3566  }
3567 
3577  public static function statelessFetchTemplate( $title, $parser = false ) {
3578  $text = $skip = false;
3579  $finalTitle = $title;
3580  $deps = [];
3581 
3582  # Loop to fetch the article, with up to 1 redirect
3583  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3584  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3585  // @codingStandardsIgnoreEnd
3586  # Give extensions a chance to select the revision instead
3587  $id = false; # Assume current
3588  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3589  [ $parser, $title, &$skip, &$id ] );
3590 
3591  if ( $skip ) {
3592  $text = false;
3593  $deps[] = [
3594  'title' => $title,
3595  'page_id' => $title->getArticleID(),
3596  'rev_id' => null
3597  ];
3598  break;
3599  }
3600  # Get the revision
3601  if ( $id ) {
3602  $rev = Revision::newFromId( $id );
3603  } elseif ( $parser ) {
3604  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3605  } else {
3607  }
3608  $rev_id = $rev ? $rev->getId() : 0;
3609  # If there is no current revision, there is no page
3610  if ( $id === false && !$rev ) {
3611  $linkCache = LinkCache::singleton();
3612  $linkCache->addBadLinkObj( $title );
3613  }
3614 
3615  $deps[] = [
3616  'title' => $title,
3617  'page_id' => $title->getArticleID(),
3618  'rev_id' => $rev_id ];
3619  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3620  # We fetched a rev from a different title; register it too...
3621  $deps[] = [
3622  'title' => $rev->getTitle(),
3623  'page_id' => $rev->getPage(),
3624  'rev_id' => $rev_id ];
3625  }
3626 
3627  if ( $rev ) {
3628  $content = $rev->getContent();
3629  $text = $content ? $content->getWikitextForTransclusion() : null;
3630 
3631  Hooks::run( 'ParserFetchTemplate',
3632  [ $parser, $title, $rev, &$text, &$deps ] );
3633 
3634  if ( $text === false || $text === null ) {
3635  $text = false;
3636  break;
3637  }
3638  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3640  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3641  if ( !$message->exists() ) {
3642  $text = false;
3643  break;
3644  }
3645  $content = $message->content();
3646  $text = $message->plain();
3647  } else {
3648  break;
3649  }
3650  if ( !$content ) {
3651  break;
3652  }
3653  # Redirect?
3654  $finalTitle = $title;
3655  $title = $content->getRedirectTarget();
3656  }
3657  return [
3658  'text' => $text,
3659  'finalTitle' => $finalTitle,
3660  'deps' => $deps ];
3661  }
3662 
3670  public function fetchFile( $title, $options = [] ) {
3671  return $this->fetchFileAndTitle( $title, $options )[0];
3672  }
3673 
3681  public function fetchFileAndTitle( $title, $options = [] ) {
3682  $file = $this->fetchFileNoRegister( $title, $options );
3683 
3684  $time = $file ? $file->getTimestamp() : false;
3685  $sha1 = $file ? $file->getSha1() : false;
3686  # Register the file as a dependency...
3687  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3688  if ( $file && !$title->equals( $file->getTitle() ) ) {
3689  # Update fetched file title
3690  $title = $file->getTitle();
3691  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3692  }
3693  return [ $file, $title ];
3694  }
3695 
3706  protected function fetchFileNoRegister( $title, $options = [] ) {
3707  if ( isset( $options['broken'] ) ) {
3708  $file = false; // broken thumbnail forced by hook
3709  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3710  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3711  } else { // get by (name,timestamp)
3712  $file = wfFindFile( $title, $options );
3713  }
3714  return $file;
3715  }
3716 
3725  public function interwikiTransclude( $title, $action ) {
3726  global $wgEnableScaryTranscluding;
3727 
3728  if ( !$wgEnableScaryTranscluding ) {
3729  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3730  }
3731 
3732  $url = $title->getFullURL( [ 'action' => $action ] );
3733 
3734  if ( strlen( $url ) > 255 ) {
3735  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3736  }
3737  return $this->fetchScaryTemplateMaybeFromCache( $url );
3738  }
3739 
3744  public function fetchScaryTemplateMaybeFromCache( $url ) {
3745  global $wgTranscludeCacheExpiry;
3746  $dbr = wfGetDB( DB_REPLICA );
3747  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3748  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3749  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3750  if ( $obj ) {
3751  return $obj->tc_contents;
3752  }
3753 
3754  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3755  $status = $req->execute(); // Status object
3756  if ( $status->isOK() ) {
3757  $text = $req->getContent();
3758  } elseif ( $req->getStatus() != 200 ) {
3759  // Though we failed to fetch the content, this status is useless.
3760  return wfMessage( 'scarytranscludefailed-httpstatus' )
3761  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3762  } else {
3763  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3764  }
3765 
3766  $dbw = wfGetDB( DB_MASTER );
3767  $dbw->replace( 'transcache', [ 'tc_url' ], [
3768  'tc_url' => $url,
3769  'tc_time' => $dbw->timestamp( time() ),
3770  'tc_contents' => $text
3771  ] );
3772  return $text;
3773  }
3774 
3784  public function argSubstitution( $piece, $frame ) {
3785  $error = false;
3786  $parts = $piece['parts'];
3787  $nameWithSpaces = $frame->expand( $piece['title'] );
3788  $argName = trim( $nameWithSpaces );
3789  $object = false;
3790  $text = $frame->getArgument( $argName );
3791  if ( $text === false && $parts->getLength() > 0
3792  && ( $this->ot['html']
3793  || $this->ot['pre']
3794  || ( $this->ot['wiki'] && $frame->isTemplate() )
3795  )
3796  ) {
3797  # No match in frame, use the supplied default
3798  $object = $parts->item( 0 )->getChildren();
3799  }
3800  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3801  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3802  $this->limitationWarn( 'post-expand-template-argument' );
3803  }
3804 
3805  if ( $text === false && $object === false ) {
3806  # No match anywhere
3807  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3808  }
3809  if ( $error !== false ) {
3810  $text .= $error;
3811  }
3812  if ( $object !== false ) {
3813  $ret = [ 'object' => $object ];
3814  } else {
3815  $ret = [ 'text' => $text ];
3816  }
3817 
3818  return $ret;
3819  }
3820 
3836  public function extensionSubstitution( $params, $frame ) {
3837  static $errorStr = '<span class="error">';
3838  static $errorLen = 20;
3839 
3840  $name = $frame->expand( $params['name'] );
3841  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3842  // Probably expansion depth or node count exceeded. Just punt the
3843  // error up.
3844  return $name;
3845  }
3846 
3847  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3848  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3849  // See above
3850  return $attrText;
3851  }
3852 
3853  // We can't safely check if the expansion for $content resulted in an
3854  // error, because the content could happen to be the error string
3855  // (T149622).
3856  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3857 
3858  $marker = self::MARKER_PREFIX . "-$name-"
3859  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3860 
3861  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3862  ( $this->ot['html'] || $this->ot['pre'] );
3863  if ( $isFunctionTag ) {
3864  $markerType = 'none';
3865  } else {
3866  $markerType = 'general';
3867  }
3868  if ( $this->ot['html'] || $isFunctionTag ) {
3869  $name = strtolower( $name );
3870  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3871  if ( isset( $params['attributes'] ) ) {
3872  $attributes = $attributes + $params['attributes'];
3873  }
3874 
3875  if ( isset( $this->mTagHooks[$name] ) ) {
3876  $output = call_user_func_array( $this->mTagHooks[$name],
3877  [ $content, $attributes, $this, $frame ] );
3878  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3879  list( $callback, ) = $this->mFunctionTagHooks[$name];
3880 
3881  // Avoid PHP 7.1 warning from passing $this by reference
3882  $parser = $this;
3883  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3884  } else {
3885  $output = '<span class="error">Invalid tag extension name: ' .
3886  htmlspecialchars( $name ) . '</span>';
3887  }
3888 
3889  if ( is_array( $output ) ) {
3890  # Extract flags to local scope (to override $markerType)
3891  $flags = $output;
3892  $output = $flags[0];
3893  unset( $flags[0] );
3894  extract( $flags );
3895  }
3896  } else {
3897  if ( is_null( $attrText ) ) {
3898  $attrText = '';
3899  }
3900  if ( isset( $params['attributes'] ) ) {
3901  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3902  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3903  htmlspecialchars( $attrValue ) . '"';
3904  }
3905  }
3906  if ( $content === null ) {
3907  $output = "<$name$attrText/>";
3908  } else {
3909  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3910  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3911  // See above
3912  return $close;
3913  }
3914  $output = "<$name$attrText>$content$close";
3915  }
3916  }
3917 
3918  if ( $markerType === 'none' ) {
3919  return $output;
3920  } elseif ( $markerType === 'nowiki' ) {
3921  $this->mStripState->addNoWiki( $marker, $output );
3922  } elseif ( $markerType === 'general' ) {
3923  $this->mStripState->addGeneral( $marker, $output );
3924  } else {
3925  throw new MWException( __METHOD__ . ': invalid marker type' );
3926  }
3927  return $marker;
3928  }
3929 
3937  public function incrementIncludeSize( $type, $size ) {
3938  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3939  return false;
3940  } else {
3941  $this->mIncludeSizes[$type] += $size;
3942  return true;
3943  }
3944  }
3945 
3952  $this->mExpensiveFunctionCount++;
3953  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3954  }
3955 
3964  public function doDoubleUnderscore( $text ) {
3965  # The position of __TOC__ needs to be recorded
3966  $mw = MagicWord::get( 'toc' );
3967  if ( $mw->match( $text ) ) {
3968  $this->mShowToc = true;
3969  $this->mForceTocPosition = true;
3970 
3971  # Set a placeholder. At the end we'll fill it in with the TOC.
3972  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3973 
3974  # Only keep the first one.
3975  $text = $mw->replace( '', $text );
3976  }
3977 
3978  # Now match and remove the rest of them
3980  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3981 
3982  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3983  $this->mOutput->mNoGallery = true;
3984  }
3985  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3986  $this->mShowToc = false;
3987  }
3988  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3989  && $this->mTitle->getNamespace() == NS_CATEGORY
3990  ) {
3991  $this->addTrackingCategory( 'hidden-category-category' );
3992  }
3993  # (T10068) Allow control over whether robots index a page.
3994  # __INDEX__ always overrides __NOINDEX__, see T16899
3995  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3996  $this->mOutput->setIndexPolicy( 'noindex' );
3997  $this->addTrackingCategory( 'noindex-category' );
3998  }
3999  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4000  $this->mOutput->setIndexPolicy( 'index' );
4001  $this->addTrackingCategory( 'index-category' );
4002  }
4003 
4004  # Cache all double underscores in the database
4005  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4006  $this->mOutput->setProperty( $key, '' );
4007  }
4008 
4009  return $text;
4010  }
4011 
4017  public function addTrackingCategory( $msg ) {
4018  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4019  }
4020 
4037  public function formatHeadings( $text, $origText, $isMain = true ) {
4038  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4039 
4040  # Inhibit editsection links if requested in the page
4041  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4042  $maybeShowEditLink = $showEditLink = false;
4043  } else {
4044  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4045  $showEditLink = $this->mOptions->getEditSection();
4046  }
4047  if ( $showEditLink ) {
4048  $this->mOutput->setEditSectionTokens( true );
4049  }
4050 
4051  # Get all headlines for numbering them and adding funky stuff like [edit]
4052  # links - this is for later, but we need the number of headlines right now
4053  $matches = [];
4054  $numMatches = preg_match_all(
4055  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4056  $text,
4057  $matches
4058  );
4059 
4060  # if there are fewer than 4 headlines in the article, do not show TOC
4061  # unless it's been explicitly enabled.
4062  $enoughToc = $this->mShowToc &&
4063  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4064 
4065  # Allow user to stipulate that a page should have a "new section"
4066  # link added via __NEWSECTIONLINK__
4067  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4068  $this->mOutput->setNewSection( true );
4069  }
4070 
4071  # Allow user to remove the "new section"
4072  # link via __NONEWSECTIONLINK__
4073  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4074  $this->mOutput->hideNewSection( true );
4075  }
4076 
4077  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4078  # override above conditions and always show TOC above first header
4079  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4080  $this->mShowToc = true;
4081  $enoughToc = true;
4082  }
4083 
4084  # headline counter
4085  $headlineCount = 0;
4086  $numVisible = 0;
4087 
4088  # Ugh .. the TOC should have neat indentation levels which can be
4089  # passed to the skin functions. These are determined here
4090  $toc = '';
4091  $full = '';
4092  $head = [];
4093  $sublevelCount = [];
4094  $levelCount = [];
4095  $level = 0;
4096  $prevlevel = 0;
4097  $toclevel = 0;
4098  $prevtoclevel = 0;
4099  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4100  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4101  $oldType = $this->mOutputType;
4102  $this->setOutputType( self::OT_WIKI );
4103  $frame = $this->getPreprocessor()->newFrame();
4104  $root = $this->preprocessToDom( $origText );
4105  $node = $root->getFirstChild();
4106  $byteOffset = 0;
4107  $tocraw = [];
4108  $refers = [];
4109 
4110  $headlines = $numMatches !== false ? $matches[3] : [];
4111 
4112  foreach ( $headlines as $headline ) {
4113  $isTemplate = false;
4114  $titleText = false;
4115  $sectionIndex = false;
4116  $numbering = '';
4117  $markerMatches = [];
4118  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4119  $serial = $markerMatches[1];
4120  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4121  $isTemplate = ( $titleText != $baseTitleText );
4122  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4123  }
4124 
4125  if ( $toclevel ) {
4126  $prevlevel = $level;
4127  }
4128  $level = $matches[1][$headlineCount];
4129 
4130  if ( $level > $prevlevel ) {
4131  # Increase TOC level
4132  $toclevel++;
4133  $sublevelCount[$toclevel] = 0;
4134  if ( $toclevel < $wgMaxTocLevel ) {
4135  $prevtoclevel = $toclevel;
4136  $toc .= Linker::tocIndent();
4137  $numVisible++;
4138  }
4139  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4140  # Decrease TOC level, find level to jump to
4141 
4142  for ( $i = $toclevel; $i > 0; $i-- ) {
4143  if ( $levelCount[$i] == $level ) {
4144  # Found last matching level
4145  $toclevel = $i;
4146  break;
4147  } elseif ( $levelCount[$i] < $level ) {
4148  # Found first matching level below current level
4149  $toclevel = $i + 1;
4150  break;
4151  }
4152  }
4153  if ( $i == 0 ) {
4154  $toclevel = 1;
4155  }
4156  if ( $toclevel < $wgMaxTocLevel ) {
4157  if ( $prevtoclevel < $wgMaxTocLevel ) {
4158  # Unindent only if the previous toc level was shown :p
4159  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4160  $prevtoclevel = $toclevel;
4161  } else {
4162  $toc .= Linker::tocLineEnd();
4163  }
4164  }
4165  } else {
4166  # No change in level, end TOC line
4167  if ( $toclevel < $wgMaxTocLevel ) {
4168  $toc .= Linker::tocLineEnd();
4169  }
4170  }
4171 
4172  $levelCount[$toclevel] = $level;
4173 
4174  # count number of headlines for each level
4175  $sublevelCount[$toclevel]++;
4176  $dot = 0;
4177  for ( $i = 1; $i <= $toclevel; $i++ ) {
4178  if ( !empty( $sublevelCount[$i] ) ) {
4179  if ( $dot ) {
4180  $numbering .= '.';
4181  }
4182  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4183  $dot = 1;
4184  }
4185  }
4186 
4187  # The safe header is a version of the header text safe to use for links
4188 
4189  # Remove link placeholders by the link text.
4190  # <!--LINK number-->
4191  # turns into
4192  # link text with suffix
4193  # Do this before unstrip since link text can contain strip markers
4194  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4195 
4196  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4197  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4198 
4199  # Strip out HTML (first regex removes any tag not allowed)
4200  # Allowed tags are:
4201  # * <sup> and <sub> (T10393)
4202  # * <i> (T28375)
4203  # * <b> (r105284)
4204  # * <bdi> (T74884)
4205  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4206  # * <s> and <strike> (T35715)
4207  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4208  # to allow setting directionality in toc items.
4209  $tocline = preg_replace(
4210  [
4211  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4212  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4213  ],
4214  [ '', '<$1>' ],
4215  $safeHeadline
4216  );
4217 
4218  # Strip '<span></span>', which is the result from the above if
4219  # <span id="foo"></span> is used to produce an additional anchor
4220  # for a section.
4221  $tocline = str_replace( '<span></span>', '', $tocline );
4222 
4223  $tocline = trim( $tocline );
4224 
4225  # For the anchor, strip out HTML-y stuff period
4226  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4227  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4228 
4229  # Save headline for section edit hint before it's escaped
4230  $headlineHint = $safeHeadline;
4231 
4232  if ( $wgExperimentalHtmlIds ) {
4233  # For reverse compatibility, provide an id that's
4234  # HTML4-compatible, like we used to.
4235  # It may be worth noting, academically, that it's possible for
4236  # the legacy anchor to conflict with a non-legacy headline
4237  # anchor on the page. In this case likely the "correct" thing
4238  # would be to either drop the legacy anchors or make sure
4239  # they're numbered first. However, this would require people
4240  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4241  # manually, so let's not bother worrying about it.
4242  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4243  [ 'noninitial', 'legacy' ] );
4244  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4245 
4246  if ( $legacyHeadline == $safeHeadline ) {
4247  # No reason to have both (in fact, we can't)
4248  $legacyHeadline = false;
4249  }
4250  } else {
4251  $legacyHeadline = false;
4252  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4253  'noninitial' );
4254  }
4255 
4256  # HTML names must be case-insensitively unique (T12721).
4257  # This does not apply to Unicode characters per
4258  # https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4259  # @todo FIXME: We may be changing them depending on the current locale.
4260  $arrayKey = strtolower( $safeHeadline );
4261  if ( $legacyHeadline === false ) {
4262  $legacyArrayKey = false;
4263  } else {
4264  $legacyArrayKey = strtolower( $legacyHeadline );
4265  }
4266 
4267  # Create the anchor for linking from the TOC to the section
4268  $anchor = $safeHeadline;
4269  $legacyAnchor = $legacyHeadline;
4270  if ( isset( $refers[$arrayKey] ) ) {
4271  // @codingStandardsIgnoreStart
4272  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4273  // @codingStandardsIgnoreEnd
4274  $anchor .= "_$i";
4275  $refers["${arrayKey}_$i"] = true;
4276  } else {
4277  $refers[$arrayKey] = true;
4278  }
4279  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4280  // @codingStandardsIgnoreStart
4281  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4282  // @codingStandardsIgnoreEnd
4283  $legacyAnchor .= "_$i";
4284  $refers["${legacyArrayKey}_$i"] = true;
4285  } else {
4286  $refers[$legacyArrayKey] = true;
4287  }
4288 
4289  # Don't number the heading if it is the only one (looks silly)
4290  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4291  # the two are different if the line contains a link
4292  $headline = Html::element(
4293  'span',
4294  [ 'class' => 'mw-headline-number' ],
4295  $numbering
4296  ) . ' ' . $headline;
4297  }
4298 
4299  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4300  $toc .= Linker::tocLine( $anchor, $tocline,
4301  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4302  }
4303 
4304  # Add the section to the section tree
4305  # Find the DOM node for this header
4306  $noOffset = ( $isTemplate || $sectionIndex === false );
4307  while ( $node && !$noOffset ) {
4308  if ( $node->getName() === 'h' ) {
4309  $bits = $node->splitHeading();
4310  if ( $bits['i'] == $sectionIndex ) {
4311  break;
4312  }
4313  }
4314  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4315  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4316  $node = $node->getNextSibling();
4317  }
4318  $tocraw[] = [
4319  'toclevel' => $toclevel,
4320  'level' => $level,
4321  'line' => $tocline,
4322  'number' => $numbering,
4323  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4324  'fromtitle' => $titleText,
4325  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4326  'anchor' => $anchor,
4327  ];
4328 
4329  # give headline the correct <h#> tag
4330  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4331  // Output edit section links as markers with styles that can be customized by skins
4332  if ( $isTemplate ) {
4333  # Put a T flag in the section identifier, to indicate to extractSections()
4334  # that sections inside <includeonly> should be counted.
4335  $editsectionPage = $titleText;
4336  $editsectionSection = "T-$sectionIndex";
4337  $editsectionContent = null;
4338  } else {
4339  $editsectionPage = $this->mTitle->getPrefixedText();
4340  $editsectionSection = $sectionIndex;
4341  $editsectionContent = $headlineHint;
4342  }
4343  // We use a bit of pesudo-xml for editsection markers. The
4344  // language converter is run later on. Using a UNIQ style marker
4345  // leads to the converter screwing up the tokens when it
4346  // converts stuff. And trying to insert strip tags fails too. At
4347  // this point all real inputted tags have already been escaped,
4348  // so we don't have to worry about a user trying to input one of
4349  // these markers directly. We use a page and section attribute
4350  // to stop the language converter from converting these
4351  // important bits of data, but put the headline hint inside a
4352  // content block because the language converter is supposed to
4353  // be able to convert that piece of data.
4354  // Gets replaced with html in ParserOutput::getText
4355  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4356  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4357  if ( $editsectionContent !== null ) {
4358  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4359  } else {
4360  $editlink .= '/>';
4361  }
4362  } else {
4363  $editlink = '';
4364  }
4365  $head[$headlineCount] = Linker::makeHeadline( $level,
4366  $matches['attrib'][$headlineCount], $anchor, $headline,
4367  $editlink, $legacyAnchor );
4368 
4369  $headlineCount++;
4370  }
4371 
4372  $this->setOutputType( $oldType );
4373 
4374  # Never ever show TOC if no headers
4375  if ( $numVisible < 1 ) {
4376  $enoughToc = false;
4377  }
4378 
4379  if ( $enoughToc ) {
4380  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4381  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4382  }
4383  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4384  $this->mOutput->setTOCHTML( $toc );
4385  $toc = self::TOC_START . $toc . self::TOC_END;
4386  }
4387 
4388  if ( $isMain ) {
4389  $this->mOutput->setSections( $tocraw );
4390  }
4391 
4392  # split up and insert constructed headlines
4393  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4394  $i = 0;
4395 
4396  // build an array of document sections
4397  $sections = [];
4398  foreach ( $blocks as $block ) {
4399  // $head is zero-based, sections aren't.
4400  if ( empty( $head[$i - 1] ) ) {
4401  $sections[$i] = $block;
4402  } else {
4403  $sections[$i] = $head[$i - 1] . $block;
4404  }
4405 
4416  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4417 
4418  $i++;
4419  }
4420 
4421  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4422  // append the TOC at the beginning
4423  // Top anchor now in skin
4424  $sections[0] = $sections[0] . $toc . "\n";
4425  }
4426 
4427  $full .= implode( '', $sections );
4428 
4429  if ( $this->mForceTocPosition ) {
4430  return str_replace( '<!--MWTOC-->', $toc, $full );
4431  } else {
4432  return $full;
4433  }
4434  }
4435 
4447  public function preSaveTransform( $text, Title $title, User $user,
4448  ParserOptions $options, $clearState = true
4449  ) {
4450  if ( $clearState ) {
4451  $magicScopeVariable = $this->lock();
4452  }
4453  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4454  $this->setUser( $user );
4455 
4456  // Strip U+0000 NULL (T159174)
4457  $text = str_replace( "\000", '', $text );
4458 
4459  // We still normalize line endings for backwards-compatibility
4460  // with other code that just calls PST, but this should already
4461  // be handled in TextContent subclasses
4462  $text = TextContent::normalizeLineEndings( $text );
4463 
4464  if ( $options->getPreSaveTransform() ) {
4465  $text = $this->pstPass2( $text, $user );
4466  }
4467  $text = $this->mStripState->unstripBoth( $text );
4468 
4469  $this->setUser( null ); # Reset
4470 
4471  return $text;
4472  }
4473 
4482  private function pstPass2( $text, $user ) {
4484 
4485  # Note: This is the timestamp saved as hardcoded wikitext to
4486  # the database, we use $wgContLang here in order to give
4487  # everyone the same signature and use the default one rather
4488  # than the one selected in each user's preferences.
4489  # (see also T14815)
4490  $ts = $this->mOptions->getTimestamp();
4491  $timestamp = MWTimestamp::getLocalInstance( $ts );
4492  $ts = $timestamp->format( 'YmdHis' );
4493  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4494 
4495  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4496 
4497  # Variable replacement
4498  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4499  $text = $this->replaceVariables( $text );
4500 
4501  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4502  # which may corrupt this parser instance via its wfMessage()->text() call-
4503 
4504  # Signatures
4505  if ( strpos( $text, '~~~' ) !== false ) {
4506  $sigText = $this->getUserSig( $user );
4507  $text = strtr( $text, [
4508  '~~~~~' => $d,
4509  '~~~~' => "$sigText $d",
4510  '~~~' => $sigText
4511  ] );
4512  # The main two signature forms used above are time-sensitive
4513  $this->mOutput->setFlag( 'user-signature' );
4514  }
4515 
4516  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4517  $tc = '[' . Title::legalChars() . ']';
4518  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4519 
4520  // [[ns:page (context)|]]
4521  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4522  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4523  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4524  // [[ns:page (context), context|]] (using either single or double-width comma)
4525  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4526  // [[|page]] (reverse pipe trick: add context from page title)
4527  $p2 = "/\[\[\\|($tc+)]]/";
4528 
4529  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4530  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4531  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4532  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4533 
4534  $t = $this->mTitle->getText();
4535  $m = [];
4536  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4537  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4538  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4539  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4540  } else {
4541  # if there's no context, don't bother duplicating the title
4542  $text = preg_replace( $p2, '[[\\1]]', $text );
4543  }
4544 
4545  return $text;
4546  }
4547 
4562  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4563  global $wgMaxSigChars;
4564 
4565  $username = $user->getName();
4566 
4567  # If not given, retrieve from the user object.
4568  if ( $nickname === false ) {
4569  $nickname = $user->getOption( 'nickname' );
4570  }
4571 
4572  if ( is_null( $fancySig ) ) {
4573  $fancySig = $user->getBoolOption( 'fancysig' );
4574  }
4575 
4576  $nickname = $nickname == null ? $username : $nickname;
4577 
4578  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4579  $nickname = $username;
4580  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4581  } elseif ( $fancySig !== false ) {
4582  # Sig. might contain markup; validate this
4583  if ( $this->validateSig( $nickname ) !== false ) {
4584  # Validated; clean up (if needed) and return it
4585  return $this->cleanSig( $nickname, true );
4586  } else {
4587  # Failed to validate; fall back to the default
4588  $nickname = $username;
4589  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4590  }
4591  }
4592 
4593  # Make sure nickname doesnt get a sig in a sig
4594  $nickname = self::cleanSigInSig( $nickname );
4595 
4596  # If we're still here, make it a link to the user page
4597  $userText = wfEscapeWikiText( $username );
4598  $nickText = wfEscapeWikiText( $nickname );
4599  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4600 
4601  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4602  ->title( $this->getTitle() )->text();
4603  }
4604 
4611  public function validateSig( $text ) {
4612  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4613  }
4614 
4625  public function cleanSig( $text, $parsing = false ) {
4626  if ( !$parsing ) {
4627  global $wgTitle;
4628  $magicScopeVariable = $this->lock();
4629  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4630  }
4631 
4632  # Option to disable this feature
4633  if ( !$this->mOptions->getCleanSignatures() ) {
4634  return $text;
4635  }
4636 
4637  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4638  # => Move this logic to braceSubstitution()
4639  $substWord = MagicWord::get( 'subst' );
4640  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4641  $substText = '{{' . $substWord->getSynonym( 0 );
4642 
4643  $text = preg_replace( $substRegex, $substText, $text );
4644  $text = self::cleanSigInSig( $text );
4645  $dom = $this->preprocessToDom( $text );
4646  $frame = $this->getPreprocessor()->newFrame();
4647  $text = $frame->expand( $dom );
4648 
4649  if ( !$parsing ) {
4650  $text = $this->mStripState->unstripBoth( $text );
4651  }
4652 
4653  return $text;
4654  }
4655 
4662  public static function cleanSigInSig( $text ) {
4663  $text = preg_replace( '/~{3,5}/', '', $text );
4664  return $text;
4665  }
4666 
4677  $outputType, $clearState = true
4678  ) {
4679  $this->startParse( $title, $options, $outputType, $clearState );
4680  }
4681 
4688  private function startParse( Title $title = null, ParserOptions $options,
4689  $outputType, $clearState = true
4690  ) {
4691  $this->setTitle( $title );
4692  $this->mOptions = $options;
4693  $this->setOutputType( $outputType );
4694  if ( $clearState ) {
4695  $this->clearState();
4696  }
4697  }
4698 
4707  public function transformMsg( $text, $options, $title = null ) {
4708  static $executing = false;
4709 
4710  # Guard against infinite recursion
4711  if ( $executing ) {
4712  return $text;
4713  }
4714  $executing = true;
4715 
4716  if ( !$title ) {
4717  global $wgTitle;
4718  $title = $wgTitle;
4719  }
4720 
4721  $text = $this->preprocess( $text, $title, $options );
4722 
4723  $executing = false;
4724  return $text;
4725  }
4726 
4751  public function setHook( $tag, callable $callback ) {
4752  $tag = strtolower( $tag );
4753  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4754  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4755  }
4756  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4757  $this->mTagHooks[$tag] = $callback;
4758  if ( !in_array( $tag, $this->mStripList ) ) {
4759  $this->mStripList[] = $tag;
4760  }
4761 
4762  return $oldVal;
4763  }
4764 
4782  public function setTransparentTagHook( $tag, callable $callback ) {
4783  $tag = strtolower( $tag );
4784  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4785  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4786  }
4787  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4788  $this->mTransparentTagHooks[$tag] = $callback;
4789 
4790  return $oldVal;
4791  }
4792 
4796  public function clearTagHooks() {
4797  $this->mTagHooks = [];
4798  $this->mFunctionTagHooks = [];
4799  $this->mStripList = $this->mDefaultStripList;
4800  }
4801 
4845  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4847 
4848  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4849  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4850 
4851  # Add to function cache
4852  $mw = MagicWord::get( $id );
4853  if ( !$mw ) {
4854  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4855  }
4856 
4857  $synonyms = $mw->getSynonyms();
4858  $sensitive = intval( $mw->isCaseSensitive() );
4859 
4860  foreach ( $synonyms as $syn ) {
4861  # Case
4862  if ( !$sensitive ) {
4863  $syn = $wgContLang->lc( $syn );
4864  }
4865  # Add leading hash
4866  if ( !( $flags & self::SFH_NO_HASH ) ) {
4867  $syn = '#' . $syn;
4868  }
4869  # Remove trailing colon
4870  if ( substr( $syn, -1, 1 ) === ':' ) {
4871  $syn = substr( $syn, 0, -1 );
4872  }
4873  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4874  }
4875  return $oldVal;
4876  }
4877 
4883  public function getFunctionHooks() {
4884  return array_keys( $this->mFunctionHooks );
4885  }
4886 
4897  public function setFunctionTagHook( $tag, callable $callback, $flags ) {
4898  $tag = strtolower( $tag );
4899  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4900  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4901  }
4902  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4903  $this->mFunctionTagHooks[$tag] : null;
4904  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4905 
4906  if ( !in_array( $tag, $this->mStripList ) ) {
4907  $this->mStripList[] = $tag;
4908  }
4909 
4910  return $old;
4911  }
4912 
4920  public function replaceLinkHolders( &$text, $options = 0 ) {
4921  $this->mLinkHolders->replace( $text );
4922  }
4923 
4931  public function replaceLinkHoldersText( $text ) {
4932  return $this->mLinkHolders->replaceText( $text );
4933  }
4934 
4948  public function renderImageGallery( $text, $params ) {
4949  $mode = false;
4950  if ( isset( $params['mode'] ) ) {
4951  $mode = $params['mode'];
4952  }
4953 
4954  try {
4955  $ig = ImageGalleryBase::factory( $mode );
4956  } catch ( Exception $e ) {
4957  // If invalid type set, fallback to default.
4958  $ig = ImageGalleryBase::factory( false );
4959  }
4960 
4961  $ig->setContextTitle( $this->mTitle );
4962  $ig->setShowBytes( false );
4963  $ig->setShowDimensions( false );
4964  $ig->setShowFilename( false );
4965  $ig->setParser( $this );
4966  $ig->setHideBadImages();
4967  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
4968 
4969  if ( isset( $params['showfilename'] ) ) {
4970  $ig->setShowFilename( true );
4971  } else {
4972  $ig->setShowFilename( false );
4973  }
4974  if ( isset( $params['caption'] ) ) {
4975  $caption = $params['caption'];
4976  $caption = htmlspecialchars( $caption );
4977  $caption = $this->replaceInternalLinks( $caption );
4978  $ig->setCaptionHtml( $caption );
4979  }
4980  if ( isset( $params['perrow'] ) ) {
4981  $ig->setPerRow( $params['perrow'] );
4982  }
4983  if ( isset( $params['widths'] ) ) {
4984  $ig->setWidths( $params['widths'] );
4985  }
4986  if ( isset( $params['heights'] ) ) {
4987  $ig->setHeights( $params['heights'] );
4988  }
4989  $ig->setAdditionalOptions( $params );
4990 
4991  // Avoid PHP 7.1 warning from passing $this by reference
4992  $parser = $this;
4993  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
4994 
4995  $lines = StringUtils::explode( "\n", $text );
4996  foreach ( $lines as $line ) {
4997  # match lines like these:
4998  # Image:someimage.jpg|This is some image
4999  $matches = [];
5000  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5001  # Skip empty lines
5002  if ( count( $matches ) == 0 ) {
5003  continue;
5004  }
5005 
5006  if ( strpos( $matches[0], '%' ) !== false ) {
5007  $matches[1] = rawurldecode( $matches[1] );
5008  }
5010  if ( is_null( $title ) ) {
5011  # Bogus title. Ignore these so we don't bomb out later.
5012  continue;
5013  }
5014 
5015  # We need to get what handler the file uses, to figure out parameters.
5016  # Note, a hook can overide the file name, and chose an entirely different
5017  # file (which potentially could be of a different type and have different handler).
5018  $options = [];
5019  $descQuery = false;
5020  Hooks::run( 'BeforeParserFetchFileAndTitle',
5021  [ $this, $title, &$options, &$descQuery ] );
5022  # Don't register it now, as TraditionalImageGallery does that later.
5023  $file = $this->fetchFileNoRegister( $title, $options );
5024  $handler = $file ? $file->getHandler() : false;
5025 
5026  $paramMap = [
5027  'img_alt' => 'gallery-internal-alt',
5028  'img_link' => 'gallery-internal-link',
5029  ];
5030  if ( $handler ) {
5031  $paramMap = $paramMap + $handler->getParamMap();
5032  // We don't want people to specify per-image widths.
5033  // Additionally the width parameter would need special casing anyhow.
5034  unset( $paramMap['img_width'] );
5035  }
5036 
5037  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5038 
5039  $label = '';
5040  $alt = '';
5041  $link = '';
5042  $handlerOptions = [];
5043  if ( isset( $matches[3] ) ) {
5044  // look for an |alt= definition while trying not to break existing
5045  // captions with multiple pipes (|) in it, until a more sensible grammar
5046  // is defined for images in galleries
5047 
5048  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5049  // splitting on '|' is a bit odd, and different from makeImage.
5050  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5051  // Protect LanguageConverter markup
5052  $parameterMatches = StringUtils::delimiterExplode(
5053  '-{', '}-', '|', $matches[3], true /* nested */
5054  );
5055 
5056  foreach ( $parameterMatches as $parameterMatch ) {
5057  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5058  if ( $magicName ) {
5059  $paramName = $paramMap[$magicName];
5060 
5061  switch ( $paramName ) {
5062  case 'gallery-internal-alt':
5063  $alt = $this->stripAltText( $match, false );
5064  break;
5065  case 'gallery-internal-link':
5066  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5067  $chars = self::EXT_LINK_URL_CLASS;
5068  $addr = self::EXT_LINK_ADDR;
5069  $prots = $this->mUrlProtocols;
5070  // check to see if link matches an absolute url, if not then it must be a wiki link.
5071  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5072  // Result of LanguageConverter::markNoConversion
5073  // invoked on an external link.
5074  $linkValue = substr( $linkValue, 4, -2 );
5075  }
5076  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5077  $link = $linkValue;
5078  $this->mOutput->addExternalLink( $link );
5079  } else {
5080  $localLinkTitle = Title::newFromText( $linkValue );
5081  if ( $localLinkTitle !== null ) {
5082  $this->mOutput->addLink( $localLinkTitle );
5083  $link = $localLinkTitle->getLinkURL();
5084  }
5085  }
5086  break;
5087  default:
5088  // Must be a handler specific parameter.
5089  if ( $handler->validateParam( $paramName, $match ) ) {
5090  $handlerOptions[$paramName] = $match;
5091  } else {
5092  // Guess not, consider it as caption.
5093  wfDebug( "$parameterMatch failed parameter validation\n" );
5094  $label = '|' . $parameterMatch;
5095  }
5096  }
5097 
5098  } else {
5099  // Last pipe wins.
5100  $label = '|' . $parameterMatch;
5101  }
5102  }
5103  // Remove the pipe.
5104  $label = substr( $label, 1 );
5105  }
5106 
5107  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5108  }
5109  $html = $ig->toHTML();
5110  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5111  return $html;
5112  }
5113 
5118  public function getImageParams( $handler ) {
5119  if ( $handler ) {
5120  $handlerClass = get_class( $handler );
5121  } else {
5122  $handlerClass = '';
5123  }
5124  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5125  # Initialise static lists
5126  static $internalParamNames = [
5127  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5128  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5129  'bottom', 'text-bottom' ],
5130  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5131  'upright', 'border', 'link', 'alt', 'class' ],
5132  ];
5133  static $internalParamMap;
5134  if ( !$internalParamMap ) {
5135  $internalParamMap = [];
5136  foreach ( $internalParamNames as $type => $names ) {
5137  foreach ( $names as $name ) {
5138  // For grep: img_left, img_right, img_center, img_none,
5139  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5140  // img_bottom, img_text_bottom,
5141  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5142  // img_border, img_link, img_alt, img_class
5143  $magicName = str_replace( '-', '_', "img_$name" );
5144  $internalParamMap[$magicName] = [ $type, $name ];
5145  }
5146  }
5147  }
5148 
5149  # Add handler params
5150  $paramMap = $internalParamMap;
5151  if ( $handler ) {
5152  $handlerParamMap = $handler->getParamMap();
5153  foreach ( $handlerParamMap as $magic => $paramName ) {
5154  $paramMap[$magic] = [ 'handler', $paramName ];
5155  }
5156  }
5157  $this->mImageParams[$handlerClass] = $paramMap;
5158  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5159  }
5160  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5161  }
5162 
5171  public function makeImage( $title, $options, $holders = false ) {
5172  # Check if the options text is of the form "options|alt text"
5173  # Options are:
5174  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5175  # * left no resizing, just left align. label is used for alt= only
5176  # * right same, but right aligned
5177  # * none same, but not aligned
5178  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5179  # * center center the image
5180  # * frame Keep original image size, no magnify-button.
5181  # * framed Same as "frame"
5182  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5183  # * upright reduce width for upright images, rounded to full __0 px
5184  # * border draw a 1px border around the image
5185  # * alt Text for HTML alt attribute (defaults to empty)
5186  # * class Set a class for img node
5187  # * link Set the target of the image link. Can be external, interwiki, or local
5188  # vertical-align values (no % or length right now):
5189  # * baseline
5190  # * sub
5191  # * super
5192  # * top
5193  # * text-top
5194  # * middle
5195  # * bottom
5196  # * text-bottom
5197 
5198  # Protect LanguageConverter markup when splitting into parts
5200  '-{', '}-', '|', $options, true /* allow nesting */
5201  );
5202 
5203  # Give extensions a chance to select the file revision for us
5204  $options = [];
5205  $descQuery = false;
5206  Hooks::run( 'BeforeParserFetchFileAndTitle',
5207  [ $this, $title, &$options, &$descQuery ] );
5208  # Fetch and register the file (file title may be different via hooks)
5209  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5210 
5211  # Get parameter map
5212  $handler = $file ? $file->getHandler() : false;
5213 
5214  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5215 
5216  if ( !$file ) {
5217  $this->addTrackingCategory( 'broken-file-category' );
5218  }
5219 
5220  # Process the input parameters
5221  $caption = '';
5222  $params = [ 'frame' => [], 'handler' => [],
5223  'horizAlign' => [], 'vertAlign' => [] ];
5224  $seenformat = false;
5225  foreach ( $parts as $part ) {
5226  $part = trim( $part );
5227  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5228  $validated = false;
5229  if ( isset( $paramMap[$magicName] ) ) {
5230  list( $type, $paramName ) = $paramMap[$magicName];
5231 
5232  # Special case; width and height come in one variable together
5233  if ( $type === 'handler' && $paramName === 'width' ) {
5234  $parsedWidthParam = $this->parseWidthParam( $value );
5235  if ( isset( $parsedWidthParam['width'] ) ) {
5236  $width = $parsedWidthParam['width'];
5237  if ( $handler->validateParam( 'width', $width ) ) {
5238  $params[$type]['width'] = $width;
5239  $validated = true;
5240  }
5241  }
5242  if ( isset( $parsedWidthParam['height'] ) ) {
5243  $height = $parsedWidthParam['height'];
5244  if ( $handler->validateParam( 'height', $height ) ) {
5245  $params[$type]['height'] = $height;
5246  $validated = true;
5247  }
5248  }
5249  # else no validation -- T15436
5250  } else {
5251  if ( $type === 'handler' ) {
5252  # Validate handler parameter
5253  $validated = $handler->validateParam( $paramName, $value );
5254  } else {
5255  # Validate internal parameters
5256  switch ( $paramName ) {
5257  case 'manualthumb':
5258  case 'alt':
5259  case 'class':
5260  # @todo FIXME: Possibly check validity here for
5261  # manualthumb? downstream behavior seems odd with
5262  # missing manual thumbs.
5263  $validated = true;
5264  $value = $this->stripAltText( $value, $holders );
5265  break;
5266  case 'link':
5267  $chars = self::EXT_LINK_URL_CLASS;
5268  $addr = self::EXT_LINK_ADDR;
5269  $prots = $this->mUrlProtocols;
5270  if ( $value === '' ) {
5271  $paramName = 'no-link';
5272  $value = true;
5273  $validated = true;
5274  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5275  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5276  $paramName = 'link-url';
5277  $this->mOutput->addExternalLink( $value );
5278  if ( $this->mOptions->getExternalLinkTarget() ) {
5279  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5280  }
5281  $validated = true;
5282  }
5283  } else {
5284  $linkTitle = Title::newFromText( $value );
5285  if ( $linkTitle ) {
5286  $paramName = 'link-title';
5287  $value = $linkTitle;
5288  $this->mOutput->addLink( $linkTitle );
5289  $validated = true;
5290  }
5291  }
5292  break;
5293  case 'frameless':
5294  case 'framed':
5295  case 'thumbnail':
5296  // use first appearing option, discard others.
5297  $validated = !$seenformat;
5298  $seenformat = true;
5299  break;
5300  default:
5301  # Most other things appear to be empty or numeric...
5302  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5303  }
5304  }
5305 
5306  if ( $validated ) {
5307  $params[$type][$paramName] = $value;
5308  }
5309  }
5310  }
5311  if ( !$validated ) {
5312  $caption = $part;
5313  }
5314  }
5315 
5316  # Process alignment parameters
5317  if ( $params['horizAlign'] ) {
5318  $params['frame']['align'] = key( $params['horizAlign'] );
5319  }
5320  if ( $params['vertAlign'] ) {
5321  $params['frame']['valign'] = key( $params['vertAlign'] );
5322  }
5323 
5324  $params['frame']['caption'] = $caption;
5325 
5326  # Will the image be presented in a frame, with the caption below?
5327  $imageIsFramed = isset( $params['frame']['frame'] )
5328  || isset( $params['frame']['framed'] )
5329  || isset( $params['frame']['thumbnail'] )
5330  || isset( $params['frame']['manualthumb'] );
5331 
5332  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5333  # came to also set the caption, ordinary text after the image -- which
5334  # makes no sense, because that just repeats the text multiple times in
5335  # screen readers. It *also* came to set the title attribute.
5336  # Now that we have an alt attribute, we should not set the alt text to
5337  # equal the caption: that's worse than useless, it just repeats the
5338  # text. This is the framed/thumbnail case. If there's no caption, we
5339  # use the unnamed parameter for alt text as well, just for the time be-
5340  # ing, if the unnamed param is set and the alt param is not.
5341  # For the future, we need to figure out if we want to tweak this more,
5342  # e.g., introducing a title= parameter for the title; ignoring the un-
5343  # named parameter entirely for images without a caption; adding an ex-
5344  # plicit caption= parameter and preserving the old magic unnamed para-
5345  # meter for BC; ...
5346  if ( $imageIsFramed ) { # Framed image
5347  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5348  # No caption or alt text, add the filename as the alt text so
5349  # that screen readers at least get some description of the image
5350  $params['frame']['alt'] = $title->getText();
5351  }
5352  # Do not set $params['frame']['title'] because tooltips don't make sense
5353  # for framed images
5354  } else { # Inline image
5355  if ( !isset( $params['frame']['alt'] ) ) {
5356  # No alt text, use the "caption" for the alt text
5357  if ( $caption !== '' ) {
5358  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5359  } else {
5360  # No caption, fall back to using the filename for the
5361  # alt text
5362  $params['frame']['alt'] = $title->getText();
5363  }
5364  }
5365  # Use the "caption" for the tooltip text
5366  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5367  }
5368 
5369  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5370 
5371  # Linker does the rest
5372  $time = isset( $options['time'] ) ? $options['time'] : false;
5373  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5374  $time, $descQuery, $this->mOptions->getThumbSize() );
5375 
5376  # Give the handler a chance to modify the parser object
5377  if ( $handler ) {
5378  $handler->parserTransformHook( $this, $file );
5379  }
5380 
5381  return $ret;
5382  }
5383 
5389  protected function stripAltText( $caption, $holders ) {
5390  # Strip bad stuff out of the title (tooltip). We can't just use
5391  # replaceLinkHoldersText() here, because if this function is called
5392  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5393  if ( $holders ) {
5394  $tooltip = $holders->replaceText( $caption );
5395  } else {
5396  $tooltip = $this->replaceLinkHoldersText( $caption );
5397  }
5398 
5399  # make sure there are no placeholders in thumbnail attributes
5400  # that are later expanded to html- so expand them now and
5401  # remove the tags
5402  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5403  $tooltip = Sanitizer::stripAllTags( $tooltip );
5404 
5405  return $tooltip;
5406  }
5407 
5413  public function disableCache() {
5414  wfDebug( "Parser output marked as uncacheable.\n" );
5415  if ( !$this->mOutput ) {
5416  throw new MWException( __METHOD__ .
5417  " can only be called when actually parsing something" );
5418  }
5419  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5420  }
5421 
5430  public function attributeStripCallback( &$text, $frame = false ) {
5431  $text = $this->replaceVariables( $text, $frame );
5432  $text = $this->mStripState->unstripBoth( $text );
5433  return $text;
5434  }
5435 
5441  public function getTags() {
5442  return array_merge(
5443  array_keys( $this->mTransparentTagHooks ),
5444  array_keys( $this->mTagHooks ),
5445  array_keys( $this->mFunctionTagHooks )
5446  );
5447  }
5448 
5459  public function replaceTransparentTags( $text ) {
5460  $matches = [];
5461  $elements = array_keys( $this->mTransparentTagHooks );
5462  $text = self::extractTagsAndParams( $elements, $text, $matches );
5463  $replacements = [];
5464 
5465  foreach ( $matches as $marker => $data ) {
5466  list( $element, $content, $params, $tag ) = $data;
5467  $tagName = strtolower( $element );
5468  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5469  $output = call_user_func_array(
5470  $this->mTransparentTagHooks[$tagName],
5471  [ $content, $params, $this ]
5472  );
5473  } else {
5474  $output = $tag;
5475  }
5476  $replacements[$marker] = $output;
5477  }
5478  return strtr( $text, $replacements );
5479  }
5480 
5510  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5511  global $wgTitle; # not generally used but removes an ugly failure mode
5512 
5513  $magicScopeVariable = $this->lock();
5514  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5515  $outText = '';
5516  $frame = $this->getPreprocessor()->newFrame();
5517 
5518  # Process section extraction flags
5519  $flags = 0;
5520  $sectionParts = explode( '-', $sectionId );
5521  $sectionIndex = array_pop( $sectionParts );
5522  foreach ( $sectionParts as $part ) {
5523  if ( $part === 'T' ) {
5524  $flags |= self::PTD_FOR_INCLUSION;
5525  }
5526  }
5527 
5528  # Check for empty input
5529  if ( strval( $text ) === '' ) {
5530  # Only sections 0 and T-0 exist in an empty document
5531  if ( $sectionIndex == 0 ) {
5532  if ( $mode === 'get' ) {
5533  return '';
5534  } else {
5535  return $newText;
5536  }
5537  } else {
5538  if ( $mode === 'get' ) {
5539  return $newText;
5540  } else {
5541  return $text;
5542  }
5543  }
5544  }
5545 
5546  # Preprocess the text
5547  $root = $this->preprocessToDom( $text, $flags );
5548 
5549  # <h> nodes indicate section breaks
5550  # They can only occur at the top level, so we can find them by iterating the root's children
5551  $node = $root->getFirstChild();
5552 
5553  # Find the target section
5554  if ( $sectionIndex == 0 ) {
5555  # Section zero doesn't nest, level=big
5556  $targetLevel = 1000;
5557  } else {
5558  while ( $node ) {
5559  if ( $node->getName() === 'h' ) {
5560  $bits = $node->splitHeading();
5561  if ( $bits['i'] == $sectionIndex ) {
5562  $targetLevel = $bits['level'];
5563  break;
5564  }
5565  }
5566  if ( $mode === 'replace' ) {
5567  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5568  }
5569  $node = $node->getNextSibling();
5570  }
5571  }
5572 
5573  if ( !$node ) {
5574  # Not found
5575  if ( $mode === 'get' ) {
5576  return $newText;
5577  } else {
5578  return $text;
5579  }
5580  }
5581 
5582  # Find the end of the section, including nested sections
5583  do {
5584  if ( $node->getName() === 'h' ) {
5585  $bits = $node->splitHeading();
5586  $curLevel = $bits['level'];
5587  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5588  break;
5589  }
5590  }
5591  if ( $mode === 'get' ) {
5592  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5593  }
5594  $node = $node->getNextSibling();
5595  } while ( $node );
5596 
5597  # Write out the remainder (in replace mode only)
5598  if ( $mode === 'replace' ) {
5599  # Output the replacement text
5600  # Add two newlines on -- trailing whitespace in $newText is conventionally
5601  # stripped by the editor, so we need both newlines to restore the paragraph gap
5602  # Only add trailing whitespace if there is newText
5603  if ( $newText != "" ) {
5604  $outText .= $newText . "\n\n";
5605  }
5606 
5607  while ( $node ) {
5608  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5609  $node = $node->getNextSibling();
5610  }
5611  }
5612 
5613  if ( is_string( $outText ) ) {
5614  # Re-insert stripped tags
5615  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5616  }
5617 
5618  return $outText;
5619  }
5620 
5635  public function getSection( $text, $sectionId, $defaultText = '' ) {
5636  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5637  }
5638 
5651  public function replaceSection( $oldText, $sectionId, $newText ) {
5652  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5653  }
5654 
5660  public function getRevisionId() {
5661  return $this->mRevisionId;
5662  }
5663 
5670  public function getRevisionObject() {
5671  if ( !is_null( $this->mRevisionObject ) ) {
5672  return $this->mRevisionObject;
5673  }
5674  if ( is_null( $this->mRevisionId ) ) {
5675  return null;
5676  }
5677 
5678  $rev = call_user_func(
5679  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5680  );
5681 
5682  # If the parse is for a new revision, then the callback should have
5683  # already been set to force the object and should match mRevisionId.
5684  # If not, try to fetch by mRevisionId for sanity.
5685  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5686  $rev = Revision::newFromId( $this->mRevisionId );
5687  }
5688 
5689  $this->mRevisionObject = $rev;
5690 
5691  return $this->mRevisionObject;
5692  }
5693 
5699  public function getRevisionTimestamp() {
5700  if ( is_null( $this->mRevisionTimestamp ) ) {
5702 
5703  $revObject = $this->getRevisionObject();
5704  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5705 
5706  # The cryptic '' timezone parameter tells to use the site-default
5707  # timezone offset instead of the user settings.
5708  # Since this value will be saved into the parser cache, served
5709  # to other users, and potentially even used inside links and such,
5710  # it needs to be consistent for all visitors.
5711  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5712 
5713  }
5714  return $this->mRevisionTimestamp;
5715  }
5716 
5722  public function getRevisionUser() {
5723  if ( is_null( $this->mRevisionUser ) ) {
5724  $revObject = $this->getRevisionObject();
5725 
5726  # if this template is subst: the revision id will be blank,
5727  # so just use the current user's name
5728  if ( $revObject ) {
5729  $this->mRevisionUser = $revObject->getUserText();
5730  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5731  $this->mRevisionUser = $this->getUser()->getName();
5732  }
5733  }
5734  return $this->mRevisionUser;
5735  }
5736 
5742  public function getRevisionSize() {
5743  if ( is_null( $this->mRevisionSize ) ) {
5744  $revObject = $this->getRevisionObject();
5745 
5746  # if this variable is subst: the revision id will be blank,
5747  # so just use the parser input size, because the own substituation
5748  # will change the size.
5749  if ( $revObject ) {
5750  $this->mRevisionSize = $revObject->getSize();
5751  } else {
5752  $this->mRevisionSize = $this->mInputSize;
5753  }
5754  }
5755  return $this->mRevisionSize;
5756  }
5757 
5763  public function setDefaultSort( $sort ) {
5764  $this->mDefaultSort = $sort;
5765  $this->mOutput->setProperty( 'defaultsort', $sort );
5766  }
5767 
5778  public function getDefaultSort() {
5779  if ( $this->mDefaultSort !== false ) {
5780  return $this->mDefaultSort;
5781  } else {
5782  return '';
5783  }
5784  }
5785 
5792  public function getCustomDefaultSort() {
5793  return $this->mDefaultSort;
5794  }
5795 
5805  public function guessSectionNameFromWikiText( $text ) {
5806  # Strip out wikitext links(they break the anchor)
5807  $text = $this->stripSectionName( $text );
5809  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5810  }
5811 
5820  public function guessLegacySectionNameFromWikiText( $text ) {
5821  # Strip out wikitext links(they break the anchor)
5822  $text = $this->stripSectionName( $text );
5824  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5825  }
5826 
5841  public function stripSectionName( $text ) {
5842  # Strip internal link markup
5843  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5844  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5845 
5846  # Strip external link markup
5847  # @todo FIXME: Not tolerant to blank link text
5848  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5849  # on how many empty links there are on the page - need to figure that out.
5850  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5851 
5852  # Parse wikitext quotes (italics & bold)
5853  $text = $this->doQuotes( $text );
5854 
5855  # Strip HTML tags
5856  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5857  return $text;
5858  }
5859 
5870  public function testSrvus( $text, Title $title, ParserOptions $options,
5871  $outputType = self::OT_HTML
5872  ) {
5873  $magicScopeVariable = $this->lock();
5874  $this->startParse( $title, $options, $outputType, true );
5875 
5876  $text = $this->replaceVariables( $text );
5877  $text = $this->mStripState->unstripBoth( $text );
5878  $text = Sanitizer::removeHTMLtags( $text );
5879  return $text;
5880  }
5881 
5888  public function testPst( $text, Title $title, ParserOptions $options ) {
5889  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5890  }
5891 
5898  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5899  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5900  }
5901 
5918  public function markerSkipCallback( $s, $callback ) {
5919  $i = 0;
5920  $out = '';
5921  while ( $i < strlen( $s ) ) {
5922  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5923  if ( $markerStart === false ) {
5924  $out .= call_user_func( $callback, substr( $s, $i ) );
5925  break;
5926  } else {
5927  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5928  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5929  if ( $markerEnd === false ) {
5930  $out .= substr( $s, $markerStart );
5931  break;
5932  } else {
5933  $markerEnd += strlen( self::MARKER_SUFFIX );
5934  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5935  $i = $markerEnd;
5936  }
5937  }
5938  }
5939  return $out;
5940  }
5941 
5948  public function killMarkers( $text ) {
5949  return $this->mStripState->killMarkers( $text );
5950  }
5951 
5968  public function serializeHalfParsedText( $text ) {
5969  $data = [
5970  'text' => $text,
5971  'version' => self::HALF_PARSED_VERSION,
5972  'stripState' => $this->mStripState->getSubState( $text ),
5973  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5974  ];
5975  return $data;
5976  }
5977 
5993  public function unserializeHalfParsedText( $data ) {
5994  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5995  throw new MWException( __METHOD__ . ': invalid version' );
5996  }
5997 
5998  # First, extract the strip state.
5999  $texts = [ $data['text'] ];
6000  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6001 
6002  # Now renumber links
6003  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6004 
6005  # Should be good to go.
6006  return $texts[0];
6007  }
6008 
6018  public function isValidHalfParsedText( $data ) {
6019  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6020  }
6021 
6030  public function parseWidthParam( $value ) {
6031  $parsedWidthParam = [];
6032  if ( $value === '' ) {
6033  return $parsedWidthParam;
6034  }
6035  $m = [];
6036  # (T15500) In both cases (width/height and width only),
6037  # permit trailing "px" for backward compatibility.
6038  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6039  $width = intval( $m[1] );
6040  $height = intval( $m[2] );
6041  $parsedWidthParam['width'] = $width;
6042  $parsedWidthParam['height'] = $height;
6043  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6044  $width = intval( $value );
6045  $parsedWidthParam['width'] = $width;
6046  }
6047  return $parsedWidthParam;
6048  }
6049 
6059  protected function lock() {
6060  if ( $this->mInParse ) {
6061  throw new MWException( "Parser state cleared while parsing. "
6062  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6063  }
6064 
6065  // Save the backtrace when locking, so that if some code tries locking again,
6066  // we can print the lock owner's backtrace for easier debugging
6067  $e = new Exception;
6068  $this->mInParse = $e->getTraceAsString();
6069 
6070  $recursiveCheck = new ScopedCallback( function () {
6071  $this->mInParse = false;
6072  } );
6073 
6074  return $recursiveCheck;
6075  }
6076 
6087  public static function stripOuterParagraph( $html ) {
6088  $m = [];
6089  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6090  if ( strpos( $m[1], '</p>' ) === false ) {
6091  $html = $m[1];
6092  }
6093  }
6094 
6095  return $html;
6096  }
6097 
6108  public function getFreshParser() {
6109  global $wgParserConf;
6110  if ( $this->mInParse ) {
6111  return new $wgParserConf['class']( $wgParserConf );
6112  } else {
6113  return $this;
6114  }
6115  }
6116 
6123  public function enableOOUI() {
6125  $this->mOutput->setEnableOOUI( true );
6126  }
6127 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5670
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:550
setTitle($t)
Set the context title.
Definition: Parser.php:776
$mAutonumber
Definition: Parser.php:179
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3331
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5918
$mPPNodeCount
Definition: Parser.php:193
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2112
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:1951
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:6018
null means default in associative array form
Definition: hooks.txt:1964
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:1964
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1550
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:5635
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1303
$mTplRedirCache
Definition: Parser.php:195
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5948
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:1562
LinkRenderer $mLinkRenderer
Definition: Parser.php:259
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3536
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:782
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5722
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:5841
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:1048
either a plain
Definition: hooks.txt:2025
$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:2554
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:724
setTransparentTagHook($tag, callable $callback)
As setHook(), but letting the contents be parsed.
Definition: Parser.php:4782
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4611
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:3242
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:1048
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4948
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:705
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1855
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
nextLinkID()
Definition: Parser.php:865
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:1964
getImageParams($handler)
Definition: Parser.php:5118
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:1634
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:5441
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:2140
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3681
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:2856
static isEnabled()
Definition: MWTidy.php:79
static tidy($text)
Interface with html tidy.
Definition: MWTidy.php:46
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:4883
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:1085
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:880
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
setFunctionTagHook($tag, callable $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4897
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3784
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5870
uniqPrefix()
Accessor for mUniqPrefix.
Definition: Parser.php:766
const TOC_START
Definition: Parser.php:139
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:804
SectionProfiler $mProfiler
Definition: Parser.php:254
$sort
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:115
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:1048
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3706
null for the local wiki Added in
Definition: hooks.txt:1580
There are three types of nodes:
$mHeadings
Definition: Parser.php:195
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4796
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:3725
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4482
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead.
Definition: Parser.php:5820
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:858
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2800
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5968
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4920
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3519
static activeUsers()
Definition: SiteStats.php:173
$mLinkID
Definition: Parser.php:192
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1667
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2886
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:3008
static cleanUrl($url)
Definition: Sanitizer.php:1876
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:745
$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:1843
target page
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
doBlockLevels($text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2483
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:830
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:947
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:2498
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:636
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2554
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:679
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:5660
const OT_PREPROCESS
Definition: Parser.php:114
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2471
$mFunctionSynonyms
Definition: Parser.php:146
If you want to remove the page from your watchlist later
getPreSaveTransform()
Transform wiki markup when saving the page?
replaceLinkHoldersText($text)
Replace "" link placeholders with plain text of links (not HTML-formatted).
Definition: Parser.php:4931
setLinkID($id)
Definition: Parser.php:872
$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:2960
$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:1962
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:1637
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3453
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2980
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1516
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4625
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:4676
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:4017
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2099
$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:5413
$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:1397
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:5699
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6087
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:1356
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6030
$mStripList
Definition: Parser.php:148
$mFunctionTagHooks
Definition: Parser.php:147
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3744
const OT_PLAIN
Definition: Defines.php:186
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3496
$mRevisionTimestamp
Definition: Parser.php:220
$mImageParams
Definition: Parser.php:151
stripAltText($caption, $holders)
Definition: Parser.php:5389
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1650
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:2017
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:1048
const OT_WIKI
Definition: Defines.php:183
Preprocessor $mPreprocessor
Definition: Parser.php:172
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:933
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:2931
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
static setupOOUI($skinName= 'default', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
$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:921
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3937
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1050
const EXT_IMAGE_REGEX
Definition: Parser.php:100
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4688
$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:1618
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:980
setHook($tag, callable $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4751
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:1077
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:5742
$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:1964
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to and or sell copies of the and to permit persons to whom the Software is furnished to do so
Definition: LICENSE.txt:10
Some information about database access in MediaWiki By Tim January Database layout For information about the MediaWiki database such as a description of the tables and their please see
Definition: database.txt:2
preSaveTransform($text, Title $title, User $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
Definition: Parser.php:4447
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:893
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:933
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:1922
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:4037
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:911
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1517
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:1532
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:4562
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:1964
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:2449
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:1749
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2898
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:813
$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:1048
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:6123
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3564
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:2040
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2458
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:1185
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:1048
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
$mConf
Definition: Parser.php:168
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4707
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:1930
getUser()
Current user.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
this hook is for auditing only $req
Definition: hooks.txt:989
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:782
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:1749
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:5459
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:5651
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3964
$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:5898
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:1416
setFunctionHook($id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4845
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3359
$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:6108
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:6059
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:2425
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3577
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:1027
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
Definition: design.txt:79
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php: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:2980
$mMarkerIndex
Definition: Parser.php:153
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g.with the RejectParserCacheValue hook) because MediaWiki won't do it for you.&$defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2580
getTitle()
Accessor for the Title object.
Definition: Parser.php:794
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:5510
ParserOutput $mOutput
Definition: Parser.php:178
getOutput()
Get the ParserOutput object.
Definition: Parser.php:839
$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:1456
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this submitted means any form of or written communication sent to the Licensor or its including but not limited to communication on electronic mailing source code control and issue tracking systems that are managed or on behalf the Licensor for the purpose of discussing and improving the but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work Grant of Copyright License Subject to the terms and conditions of this each Contributor hereby grants to You a non no royalty irrevocable copyright license to prepare Derivative Works publicly display
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
static cleanSigInSig($text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4662
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5763
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3670
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1506
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:84
incrementExpensiveFunctionCount()
Increment the expensive function count.
Definition: Parser.php:3951
getDisableTitleConversion()
Whether title conversion should be disabled.
$mShowToc
Definition: Parser.php:197
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1981
const DB_REPLICA
Definition: defines.php:25
magicLinkCallback($m)
Definition: Parser.php:1487
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:1063
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5888
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.
if(!$wgRequest->checkUrlExtension()) if(!$wgEnableAPI) $wgTitle
Definition: api.php:57
static explode($separator, $subject)
Workalike for explode() with limited memory usage.