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 function setUser( $user ) {
746  $this->mUser = $user;
747  }
748 
754  public function setTitle( $t ) {
755  if ( !$t ) {
756  $t = Title::newFromText( 'NO TITLE' );
757  }
758 
759  if ( $t->hasFragment() ) {
760  # Strip the fragment to avoid various odd effects
761  $this->mTitle = $t->createFragmentTarget( '' );
762  } else {
763  $this->mTitle = $t;
764  }
765  }
766 
772  public function getTitle() {
773  return $this->mTitle;
774  }
775 
782  public function Title( $x = null ) {
783  return wfSetVar( $this->mTitle, $x );
784  }
785 
791  public function setOutputType( $ot ) {
792  $this->mOutputType = $ot;
793  # Shortcut alias
794  $this->ot = [
795  'html' => $ot == self::OT_HTML,
796  'wiki' => $ot == self::OT_WIKI,
797  'pre' => $ot == self::OT_PREPROCESS,
798  'plain' => $ot == self::OT_PLAIN,
799  ];
800  }
801 
808  public function OutputType( $x = null ) {
809  return wfSetVar( $this->mOutputType, $x );
810  }
811 
817  public function getOutput() {
818  return $this->mOutput;
819  }
820 
826  public function getOptions() {
827  return $this->mOptions;
828  }
829 
836  public function Options( $x = null ) {
837  return wfSetVar( $this->mOptions, $x );
838  }
839 
843  public function nextLinkID() {
844  return $this->mLinkID++;
845  }
846 
850  public function setLinkID( $id ) {
851  $this->mLinkID = $id;
852  }
853 
858  public function getFunctionLang() {
859  return $this->getTargetLanguage();
860  }
861 
871  public function getTargetLanguage() {
872  $target = $this->mOptions->getTargetLanguage();
873 
874  if ( $target !== null ) {
875  return $target;
876  } elseif ( $this->mOptions->getInterfaceMessage() ) {
877  return $this->mOptions->getUserLangObj();
878  } elseif ( is_null( $this->mTitle ) ) {
879  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
880  }
881 
882  return $this->mTitle->getPageLanguage();
883  }
884 
889  public function getConverterLanguage() {
890  return $this->getTargetLanguage();
891  }
892 
899  public function getUser() {
900  if ( !is_null( $this->mUser ) ) {
901  return $this->mUser;
902  }
903  return $this->mOptions->getUser();
904  }
905 
911  public function getPreprocessor() {
912  if ( !isset( $this->mPreprocessor ) ) {
913  $class = $this->mPreprocessorClass;
914  $this->mPreprocessor = new $class( $this );
915  }
916  return $this->mPreprocessor;
917  }
918 
925  public function getLinkRenderer() {
926  if ( !$this->mLinkRenderer ) {
927  $this->mLinkRenderer = MediaWikiServices::getInstance()
928  ->getLinkRendererFactory()->create();
929  $this->mLinkRenderer->setStubThreshold(
930  $this->getOptions()->getStubThreshold()
931  );
932  }
933 
934  return $this->mLinkRenderer;
935  }
936 
956  public static function extractTagsAndParams( $elements, $text, &$matches ) {
957  static $n = 1;
958  $stripped = '';
959  $matches = [];
960 
961  $taglist = implode( '|', $elements );
962  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
963 
964  while ( $text != '' ) {
965  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
966  $stripped .= $p[0];
967  if ( count( $p ) < 5 ) {
968  break;
969  }
970  if ( count( $p ) > 5 ) {
971  # comment
972  $element = $p[4];
973  $attributes = '';
974  $close = '';
975  $inside = $p[5];
976  } else {
977  # tag
978  $element = $p[1];
979  $attributes = $p[2];
980  $close = $p[3];
981  $inside = $p[4];
982  }
983 
984  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
985  $stripped .= $marker;
986 
987  if ( $close === '/>' ) {
988  # Empty element tag, <tag />
989  $content = null;
990  $text = $inside;
991  $tail = null;
992  } else {
993  if ( $element === '!--' ) {
994  $end = '/(-->)/';
995  } else {
996  $end = "/(<\\/$element\\s*>)/i";
997  }
998  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
999  $content = $q[0];
1000  if ( count( $q ) < 3 ) {
1001  # No end tag -- let it run out to the end of the text.
1002  $tail = '';
1003  $text = '';
1004  } else {
1005  $tail = $q[1];
1006  $text = $q[2];
1007  }
1008  }
1009 
1010  $matches[$marker] = [ $element,
1011  $content,
1012  Sanitizer::decodeTagAttributes( $attributes ),
1013  "<$element$attributes$close$content$tail" ];
1014  }
1015  return $stripped;
1016  }
1017 
1023  public function getStripList() {
1024  return $this->mStripList;
1025  }
1026 
1036  public function insertStripItem( $text ) {
1037  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1038  $this->mMarkerIndex++;
1039  $this->mStripState->addGeneral( $marker, $text );
1040  return $marker;
1041  }
1042 
1050  public function doTableStuff( $text ) {
1051  $lines = StringUtils::explode( "\n", $text );
1052  $out = '';
1053  $td_history = []; # Is currently a td tag open?
1054  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1055  $tr_history = []; # Is currently a tr tag open?
1056  $tr_attributes = []; # history of tr attributes
1057  $has_opened_tr = []; # Did this table open a <tr> element?
1058  $indent_level = 0; # indent level of the table
1059 
1060  foreach ( $lines as $outLine ) {
1061  $line = trim( $outLine );
1062 
1063  if ( $line === '' ) { # empty line, go to next line
1064  $out .= $outLine . "\n";
1065  continue;
1066  }
1067 
1068  $first_character = $line[0];
1069  $first_two = substr( $line, 0, 2 );
1070  $matches = [];
1071 
1072  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1073  # First check if we are starting a new table
1074  $indent_level = strlen( $matches[1] );
1075 
1076  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1077  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1078 
1079  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1080  array_push( $td_history, false );
1081  array_push( $last_tag_history, '' );
1082  array_push( $tr_history, false );
1083  array_push( $tr_attributes, '' );
1084  array_push( $has_opened_tr, false );
1085  } elseif ( count( $td_history ) == 0 ) {
1086  # Don't do any of the following
1087  $out .= $outLine . "\n";
1088  continue;
1089  } elseif ( $first_two === '|}' ) {
1090  # We are ending a table
1091  $line = '</table>' . substr( $line, 2 );
1092  $last_tag = array_pop( $last_tag_history );
1093 
1094  if ( !array_pop( $has_opened_tr ) ) {
1095  $line = "<tr><td></td></tr>{$line}";
1096  }
1097 
1098  if ( array_pop( $tr_history ) ) {
1099  $line = "</tr>{$line}";
1100  }
1101 
1102  if ( array_pop( $td_history ) ) {
1103  $line = "</{$last_tag}>{$line}";
1104  }
1105  array_pop( $tr_attributes );
1106  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1107  } elseif ( $first_two === '|-' ) {
1108  # Now we have a table row
1109  $line = preg_replace( '#^\|-+#', '', $line );
1110 
1111  # Whats after the tag is now only attributes
1112  $attributes = $this->mStripState->unstripBoth( $line );
1113  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1114  array_pop( $tr_attributes );
1115  array_push( $tr_attributes, $attributes );
1116 
1117  $line = '';
1118  $last_tag = array_pop( $last_tag_history );
1119  array_pop( $has_opened_tr );
1120  array_push( $has_opened_tr, true );
1121 
1122  if ( array_pop( $tr_history ) ) {
1123  $line = '</tr>';
1124  }
1125 
1126  if ( array_pop( $td_history ) ) {
1127  $line = "</{$last_tag}>{$line}";
1128  }
1129 
1130  $outLine = $line;
1131  array_push( $tr_history, false );
1132  array_push( $td_history, false );
1133  array_push( $last_tag_history, '' );
1134  } elseif ( $first_character === '|'
1135  || $first_character === '!'
1136  || $first_two === '|+'
1137  ) {
1138  # This might be cell elements, td, th or captions
1139  if ( $first_two === '|+' ) {
1140  $first_character = '+';
1141  $line = substr( $line, 2 );
1142  } else {
1143  $line = substr( $line, 1 );
1144  }
1145 
1146  // Implies both are valid for table headings.
1147  if ( $first_character === '!' ) {
1148  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1149  }
1150 
1151  # Split up multiple cells on the same line.
1152  # FIXME : This can result in improper nesting of tags processed
1153  # by earlier parser steps.
1154  $cells = explode( '||', $line );
1155 
1156  $outLine = '';
1157 
1158  # Loop through each table cell
1159  foreach ( $cells as $cell ) {
1160  $previous = '';
1161  if ( $first_character !== '+' ) {
1162  $tr_after = array_pop( $tr_attributes );
1163  if ( !array_pop( $tr_history ) ) {
1164  $previous = "<tr{$tr_after}>\n";
1165  }
1166  array_push( $tr_history, true );
1167  array_push( $tr_attributes, '' );
1168  array_pop( $has_opened_tr );
1169  array_push( $has_opened_tr, true );
1170  }
1171 
1172  $last_tag = array_pop( $last_tag_history );
1173 
1174  if ( array_pop( $td_history ) ) {
1175  $previous = "</{$last_tag}>\n{$previous}";
1176  }
1177 
1178  if ( $first_character === '|' ) {
1179  $last_tag = 'td';
1180  } elseif ( $first_character === '!' ) {
1181  $last_tag = 'th';
1182  } elseif ( $first_character === '+' ) {
1183  $last_tag = 'caption';
1184  } else {
1185  $last_tag = '';
1186  }
1187 
1188  array_push( $last_tag_history, $last_tag );
1189 
1190  # A cell could contain both parameters and data
1191  $cell_data = explode( '|', $cell, 2 );
1192 
1193  # T2553: Note that a '|' inside an invalid link should not
1194  # be mistaken as delimiting cell parameters
1195  # Bug T153140: Neither should language converter markup.
1196  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1197  $cell = "{$previous}<{$last_tag}>{$cell}";
1198  } elseif ( count( $cell_data ) == 1 ) {
1199  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1200  } else {
1201  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1202  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1203  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1204  }
1205 
1206  $outLine .= $cell;
1207  array_push( $td_history, true );
1208  }
1209  }
1210  $out .= $outLine . "\n";
1211  }
1212 
1213  # Closing open td, tr && table
1214  while ( count( $td_history ) > 0 ) {
1215  if ( array_pop( $td_history ) ) {
1216  $out .= "</td>\n";
1217  }
1218  if ( array_pop( $tr_history ) ) {
1219  $out .= "</tr>\n";
1220  }
1221  if ( !array_pop( $has_opened_tr ) ) {
1222  $out .= "<tr><td></td></tr>\n";
1223  }
1224 
1225  $out .= "</table>\n";
1226  }
1227 
1228  # Remove trailing line-ending (b/c)
1229  if ( substr( $out, -1 ) === "\n" ) {
1230  $out = substr( $out, 0, -1 );
1231  }
1232 
1233  # special case: don't return empty table
1234  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1235  $out = '';
1236  }
1237 
1238  return $out;
1239  }
1240 
1253  public function internalParse( $text, $isMain = true, $frame = false ) {
1254  $origText = $text;
1255 
1256  // Avoid PHP 7.1 warning from passing $this by reference
1257  $parser = $this;
1258 
1259  # Hook to suspend the parser in this state
1260  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1261  return $text;
1262  }
1263 
1264  # if $frame is provided, then use $frame for replacing any variables
1265  if ( $frame ) {
1266  # use frame depth to infer how include/noinclude tags should be handled
1267  # depth=0 means this is the top-level document; otherwise it's an included document
1268  if ( !$frame->depth ) {
1269  $flag = 0;
1270  } else {
1271  $flag = self::PTD_FOR_INCLUSION;
1272  }
1273  $dom = $this->preprocessToDom( $text, $flag );
1274  $text = $frame->expand( $dom );
1275  } else {
1276  # if $frame is not provided, then use old-style replaceVariables
1277  $text = $this->replaceVariables( $text );
1278  }
1279 
1280  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1281  $text = Sanitizer::removeHTMLtags(
1282  $text,
1283  [ $this, 'attributeStripCallback' ],
1284  false,
1285  array_keys( $this->mTransparentTagHooks ),
1286  [],
1287  [ $this, 'addTrackingCategory' ]
1288  );
1289  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1290 
1291  # Tables need to come after variable replacement for things to work
1292  # properly; putting them before other transformations should keep
1293  # exciting things like link expansions from showing up in surprising
1294  # places.
1295  $text = $this->doTableStuff( $text );
1296 
1297  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1298 
1299  $text = $this->doDoubleUnderscore( $text );
1300 
1301  $text = $this->doHeadings( $text );
1302  $text = $this->replaceInternalLinks( $text );
1303  $text = $this->doAllQuotes( $text );
1304  $text = $this->replaceExternalLinks( $text );
1305 
1306  # replaceInternalLinks may sometimes leave behind
1307  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1308  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1309 
1310  $text = $this->doMagicLinks( $text );
1311  $text = $this->formatHeadings( $text, $origText, $isMain );
1312 
1313  return $text;
1314  }
1315 
1325  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1326  $text = $this->mStripState->unstripGeneral( $text );
1327 
1328  // Avoid PHP 7.1 warning from passing $this by reference
1329  $parser = $this;
1330 
1331  if ( $isMain ) {
1332  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1333  }
1334 
1335  # Clean up special characters, only run once, next-to-last before doBlockLevels
1336  $fixtags = [
1337  # French spaces, last one Guillemet-left
1338  # only if there is something before the space
1339  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1340  # french spaces, Guillemet-right
1341  '/(\\302\\253) /' => '\\1&#160;',
1342  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1343  ];
1344  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1345 
1346  $text = $this->doBlockLevels( $text, $linestart );
1347 
1348  $this->replaceLinkHolders( $text );
1349 
1357  if ( !( $this->mOptions->getDisableContentConversion()
1358  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1359  ) {
1360  if ( !$this->mOptions->getInterfaceMessage() ) {
1361  # The position of the convert() call should not be changed. it
1362  # assumes that the links are all replaced and the only thing left
1363  # is the <nowiki> mark.
1364  $text = $this->getConverterLanguage()->convert( $text );
1365  }
1366  }
1367 
1368  $text = $this->mStripState->unstripNoWiki( $text );
1369 
1370  if ( $isMain ) {
1371  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1372  }
1373 
1374  $text = $this->replaceTransparentTags( $text );
1375  $text = $this->mStripState->unstripGeneral( $text );
1376 
1377  $text = Sanitizer::normalizeCharReferences( $text );
1378 
1379  if ( MWTidy::isEnabled() ) {
1380  if ( $this->mOptions->getTidy() ) {
1381  $text = MWTidy::tidy( $text );
1382  }
1383  } else {
1384  # attempt to sanitize at least some nesting problems
1385  # (T4702 and quite a few others)
1386  $tidyregs = [
1387  # ''Something [http://www.cool.com cool''] -->
1388  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1389  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1390  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1391  # fix up an anchor inside another anchor, only
1392  # at least for a single single nested link (T5695)
1393  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1394  '\\1\\2</a>\\3</a>\\1\\4</a>',
1395  # fix div inside inline elements- doBlockLevels won't wrap a line which
1396  # contains a div, so fix it up here; replace
1397  # div with escaped text
1398  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1399  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1400  # remove empty italic or bold tag pairs, some
1401  # introduced by rules above
1402  '/<([bi])><\/\\1>/' => '',
1403  ];
1404 
1405  $text = preg_replace(
1406  array_keys( $tidyregs ),
1407  array_values( $tidyregs ),
1408  $text );
1409  }
1410 
1411  if ( $isMain ) {
1412  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1413  }
1414 
1415  return $text;
1416  }
1417 
1429  public function doMagicLinks( $text ) {
1430  $prots = wfUrlProtocolsWithoutProtRel();
1431  $urlChar = self::EXT_LINK_URL_CLASS;
1432  $addr = self::EXT_LINK_ADDR;
1433  $space = self::SPACE_NOT_NL; # non-newline space
1434  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1435  $spaces = "$space++"; # possessive match of 1 or more spaces
1436  $text = preg_replace_callback(
1437  '!(?: # Start cases
1438  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1439  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1440  (\b # m[3]: Free external links
1441  (?i:$prots)
1442  ($addr$urlChar*) # m[4]: Post-protocol path
1443  ) |
1444  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1445  ([0-9]+)\b |
1446  \bISBN $spaces ( # m[6]: ISBN, capture number
1447  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1448  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1449  [0-9Xx] # check digit
1450  )\b
1451  )!xu", [ $this, 'magicLinkCallback' ], $text );
1452  return $text;
1453  }
1454 
1460  public function magicLinkCallback( $m ) {
1461  if ( isset( $m[1] ) && $m[1] !== '' ) {
1462  # Skip anchor
1463  return $m[0];
1464  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1465  # Skip HTML element
1466  return $m[0];
1467  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1468  # Free external link
1469  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1470  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1471  # RFC or PMID
1472  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1473  if ( !$this->mOptions->getMagicRFCLinks() ) {
1474  return $m[0];
1475  }
1476  $keyword = 'RFC';
1477  $urlmsg = 'rfcurl';
1478  $cssClass = 'mw-magiclink-rfc';
1479  $trackingCat = 'magiclink-tracking-rfc';
1480  $id = $m[5];
1481  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1482  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1483  return $m[0];
1484  }
1485  $keyword = 'PMID';
1486  $urlmsg = 'pubmedurl';
1487  $cssClass = 'mw-magiclink-pmid';
1488  $trackingCat = 'magiclink-tracking-pmid';
1489  $id = $m[5];
1490  } else {
1491  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1492  substr( $m[0], 0, 20 ) . '"' );
1493  }
1494  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1495  $this->addTrackingCategory( $trackingCat );
1496  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1497  } elseif ( isset( $m[6] ) && $m[6] !== ''
1498  && $this->mOptions->getMagicISBNLinks()
1499  ) {
1500  # ISBN
1501  $isbn = $m[6];
1502  $space = self::SPACE_NOT_NL; # non-newline space
1503  $isbn = preg_replace( "/$space/", ' ', $isbn );
1504  $num = strtr( $isbn, [
1505  '-' => '',
1506  ' ' => '',
1507  'x' => 'X',
1508  ] );
1509  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1510  return $this->getLinkRenderer()->makeKnownLink(
1511  SpecialPage::getTitleFor( 'Booksources', $num ),
1512  "ISBN $isbn",
1513  [
1514  'class' => 'internal mw-magiclink-isbn',
1515  'title' => false // suppress title attribute
1516  ]
1517  );
1518  } else {
1519  return $m[0];
1520  }
1521  }
1522 
1532  public function makeFreeExternalLink( $url, $numPostProto ) {
1533  $trail = '';
1534 
1535  # The characters '<' and '>' (which were escaped by
1536  # removeHTMLtags()) should not be included in
1537  # URLs, per RFC 2396.
1538  # Make &nbsp; terminate a URL as well (bug T84937)
1539  $m2 = [];
1540  if ( preg_match(
1541  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1542  $url,
1543  $m2,
1544  PREG_OFFSET_CAPTURE
1545  ) ) {
1546  $trail = substr( $url, $m2[0][1] ) . $trail;
1547  $url = substr( $url, 0, $m2[0][1] );
1548  }
1549 
1550  # Move trailing punctuation to $trail
1551  $sep = ',;\.:!?';
1552  # If there is no left bracket, then consider right brackets fair game too
1553  if ( strpos( $url, '(' ) === false ) {
1554  $sep .= ')';
1555  }
1556 
1557  $urlRev = strrev( $url );
1558  $numSepChars = strspn( $urlRev, $sep );
1559  # Don't break a trailing HTML entity by moving the ; into $trail
1560  # This is in hot code, so use substr_compare to avoid having to
1561  # create a new string object for the comparison
1562  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1563  # more optimization: instead of running preg_match with a $
1564  # anchor, which can be slow, do the match on the reversed
1565  # string starting at the desired offset.
1566  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1567  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1568  $numSepChars--;
1569  }
1570  }
1571  if ( $numSepChars ) {
1572  $trail = substr( $url, -$numSepChars ) . $trail;
1573  $url = substr( $url, 0, -$numSepChars );
1574  }
1575 
1576  # Verify that we still have a real URL after trail removal, and
1577  # not just lone protocol
1578  if ( strlen( $trail ) >= $numPostProto ) {
1579  return $url . $trail;
1580  }
1581 
1582  $url = Sanitizer::cleanUrl( $url );
1583 
1584  # Is this an external image?
1585  $text = $this->maybeMakeExternalImage( $url );
1586  if ( $text === false ) {
1587  # Not an image, make a link
1588  $text = Linker::makeExternalLink( $url,
1589  $this->getConverterLanguage()->markNoConversion( $url, true ),
1590  true, 'free',
1591  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1592  # Register it in the output object...
1593  $this->mOutput->addExternalLink( $url );
1594  }
1595  return $text . $trail;
1596  }
1597 
1607  public function doHeadings( $text ) {
1608  for ( $i = 6; $i >= 1; --$i ) {
1609  $h = str_repeat( '=', $i );
1610  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1611  }
1612  return $text;
1613  }
1614 
1623  public function doAllQuotes( $text ) {
1624  $outtext = '';
1625  $lines = StringUtils::explode( "\n", $text );
1626  foreach ( $lines as $line ) {
1627  $outtext .= $this->doQuotes( $line ) . "\n";
1628  }
1629  $outtext = substr( $outtext, 0, -1 );
1630  return $outtext;
1631  }
1632 
1640  public function doQuotes( $text ) {
1641  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1642  $countarr = count( $arr );
1643  if ( $countarr == 1 ) {
1644  return $text;
1645  }
1646 
1647  // First, do some preliminary work. This may shift some apostrophes from
1648  // being mark-up to being text. It also counts the number of occurrences
1649  // of bold and italics mark-ups.
1650  $numbold = 0;
1651  $numitalics = 0;
1652  for ( $i = 1; $i < $countarr; $i += 2 ) {
1653  $thislen = strlen( $arr[$i] );
1654  // If there are ever four apostrophes, assume the first is supposed to
1655  // be text, and the remaining three constitute mark-up for bold text.
1656  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1657  if ( $thislen == 4 ) {
1658  $arr[$i - 1] .= "'";
1659  $arr[$i] = "'''";
1660  $thislen = 3;
1661  } elseif ( $thislen > 5 ) {
1662  // If there are more than 5 apostrophes in a row, assume they're all
1663  // text except for the last 5.
1664  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1665  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1666  $arr[$i] = "'''''";
1667  $thislen = 5;
1668  }
1669  // Count the number of occurrences of bold and italics mark-ups.
1670  if ( $thislen == 2 ) {
1671  $numitalics++;
1672  } elseif ( $thislen == 3 ) {
1673  $numbold++;
1674  } elseif ( $thislen == 5 ) {
1675  $numitalics++;
1676  $numbold++;
1677  }
1678  }
1679 
1680  // If there is an odd number of both bold and italics, it is likely
1681  // that one of the bold ones was meant to be an apostrophe followed
1682  // by italics. Which one we cannot know for certain, but it is more
1683  // likely to be one that has a single-letter word before it.
1684  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1685  $firstsingleletterword = -1;
1686  $firstmultiletterword = -1;
1687  $firstspace = -1;
1688  for ( $i = 1; $i < $countarr; $i += 2 ) {
1689  if ( strlen( $arr[$i] ) == 3 ) {
1690  $x1 = substr( $arr[$i - 1], -1 );
1691  $x2 = substr( $arr[$i - 1], -2, 1 );
1692  if ( $x1 === ' ' ) {
1693  if ( $firstspace == -1 ) {
1694  $firstspace = $i;
1695  }
1696  } elseif ( $x2 === ' ' ) {
1697  $firstsingleletterword = $i;
1698  // if $firstsingleletterword is set, we don't
1699  // look at the other options, so we can bail early.
1700  break;
1701  } else {
1702  if ( $firstmultiletterword == -1 ) {
1703  $firstmultiletterword = $i;
1704  }
1705  }
1706  }
1707  }
1708 
1709  // If there is a single-letter word, use it!
1710  if ( $firstsingleletterword > -1 ) {
1711  $arr[$firstsingleletterword] = "''";
1712  $arr[$firstsingleletterword - 1] .= "'";
1713  } elseif ( $firstmultiletterword > -1 ) {
1714  // If not, but there's a multi-letter word, use that one.
1715  $arr[$firstmultiletterword] = "''";
1716  $arr[$firstmultiletterword - 1] .= "'";
1717  } elseif ( $firstspace > -1 ) {
1718  // ... otherwise use the first one that has neither.
1719  // (notice that it is possible for all three to be -1 if, for example,
1720  // there is only one pentuple-apostrophe in the line)
1721  $arr[$firstspace] = "''";
1722  $arr[$firstspace - 1] .= "'";
1723  }
1724  }
1725 
1726  // Now let's actually convert our apostrophic mush to HTML!
1727  $output = '';
1728  $buffer = '';
1729  $state = '';
1730  $i = 0;
1731  foreach ( $arr as $r ) {
1732  if ( ( $i % 2 ) == 0 ) {
1733  if ( $state === 'both' ) {
1734  $buffer .= $r;
1735  } else {
1736  $output .= $r;
1737  }
1738  } else {
1739  $thislen = strlen( $r );
1740  if ( $thislen == 2 ) {
1741  if ( $state === 'i' ) {
1742  $output .= '</i>';
1743  $state = '';
1744  } elseif ( $state === 'bi' ) {
1745  $output .= '</i>';
1746  $state = 'b';
1747  } elseif ( $state === 'ib' ) {
1748  $output .= '</b></i><b>';
1749  $state = 'b';
1750  } elseif ( $state === 'both' ) {
1751  $output .= '<b><i>' . $buffer . '</i>';
1752  $state = 'b';
1753  } else { // $state can be 'b' or ''
1754  $output .= '<i>';
1755  $state .= 'i';
1756  }
1757  } elseif ( $thislen == 3 ) {
1758  if ( $state === 'b' ) {
1759  $output .= '</b>';
1760  $state = '';
1761  } elseif ( $state === 'bi' ) {
1762  $output .= '</i></b><i>';
1763  $state = 'i';
1764  } elseif ( $state === 'ib' ) {
1765  $output .= '</b>';
1766  $state = 'i';
1767  } elseif ( $state === 'both' ) {
1768  $output .= '<i><b>' . $buffer . '</b>';
1769  $state = 'i';
1770  } else { // $state can be 'i' or ''
1771  $output .= '<b>';
1772  $state .= 'b';
1773  }
1774  } elseif ( $thislen == 5 ) {
1775  if ( $state === 'b' ) {
1776  $output .= '</b><i>';
1777  $state = 'i';
1778  } elseif ( $state === 'i' ) {
1779  $output .= '</i><b>';
1780  $state = 'b';
1781  } elseif ( $state === 'bi' ) {
1782  $output .= '</i></b>';
1783  $state = '';
1784  } elseif ( $state === 'ib' ) {
1785  $output .= '</b></i>';
1786  $state = '';
1787  } elseif ( $state === 'both' ) {
1788  $output .= '<i><b>' . $buffer . '</b></i>';
1789  $state = '';
1790  } else { // ($state == '')
1791  $buffer = '';
1792  $state = 'both';
1793  }
1794  }
1795  }
1796  $i++;
1797  }
1798  // Now close all remaining tags. Notice that the order is important.
1799  if ( $state === 'b' || $state === 'ib' ) {
1800  $output .= '</b>';
1801  }
1802  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1803  $output .= '</i>';
1804  }
1805  if ( $state === 'bi' ) {
1806  $output .= '</b>';
1807  }
1808  // There might be lonely ''''', so make sure we have a buffer
1809  if ( $state === 'both' && $buffer ) {
1810  $output .= '<b><i>' . $buffer . '</i></b>';
1811  }
1812  return $output;
1813  }
1814 
1828  public function replaceExternalLinks( $text ) {
1829  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1830  if ( $bits === false ) {
1831  throw new MWException( "PCRE needs to be compiled with "
1832  . "--enable-unicode-properties in order for MediaWiki to function" );
1833  }
1834  $s = array_shift( $bits );
1835 
1836  $i = 0;
1837  while ( $i < count( $bits ) ) {
1838  $url = $bits[$i++];
1839  $i++; // protocol
1840  $text = $bits[$i++];
1841  $trail = $bits[$i++];
1842 
1843  # The characters '<' and '>' (which were escaped by
1844  # removeHTMLtags()) should not be included in
1845  # URLs, per RFC 2396.
1846  $m2 = [];
1847  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1848  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1849  $url = substr( $url, 0, $m2[0][1] );
1850  }
1851 
1852  # If the link text is an image URL, replace it with an <img> tag
1853  # This happened by accident in the original parser, but some people used it extensively
1854  $img = $this->maybeMakeExternalImage( $text );
1855  if ( $img !== false ) {
1856  $text = $img;
1857  }
1858 
1859  $dtrail = '';
1860 
1861  # Set linktype for CSS - if URL==text, link is essentially free
1862  $linktype = ( $text === $url ) ? 'free' : 'text';
1863 
1864  # No link text, e.g. [http://domain.tld/some.link]
1865  if ( $text == '' ) {
1866  # Autonumber
1867  $langObj = $this->getTargetLanguage();
1868  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1869  $linktype = 'autonumber';
1870  } else {
1871  # Have link text, e.g. [http://domain.tld/some.link text]s
1872  # Check for trail
1873  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1874  }
1875 
1876  $text = $this->getConverterLanguage()->markNoConversion( $text );
1877 
1878  $url = Sanitizer::cleanUrl( $url );
1879 
1880  # Use the encoded URL
1881  # This means that users can paste URLs directly into the text
1882  # Funny characters like ö aren't valid in URLs anyway
1883  # This was changed in August 2004
1884  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1885  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1886 
1887  # Register link in the output object.
1888  $this->mOutput->addExternalLink( $url );
1889  }
1890 
1891  return $s;
1892  }
1893 
1903  public static function getExternalLinkRel( $url = false, $title = null ) {
1904  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1905  $ns = $title ? $title->getNamespace() : false;
1906  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1907  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1908  ) {
1909  return 'nofollow';
1910  }
1911  return null;
1912  }
1913 
1924  public function getExternalLinkAttribs( $url ) {
1925  $attribs = [];
1926  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1927 
1928  $target = $this->mOptions->getExternalLinkTarget();
1929  if ( $target ) {
1930  $attribs['target'] = $target;
1931  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1932  // T133507. New windows can navigate parent cross-origin.
1933  // Including noreferrer due to lacking browser
1934  // support of noopener. Eventually noreferrer should be removed.
1935  if ( $rel !== '' ) {
1936  $rel .= ' ';
1937  }
1938  $rel .= 'noreferrer noopener';
1939  }
1940  }
1941  $attribs['rel'] = $rel;
1942  return $attribs;
1943  }
1944 
1954  public static function normalizeLinkUrl( $url ) {
1955  # First, make sure unsafe characters are encoded
1956  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1957  function ( $m ) {
1958  return rawurlencode( $m[0] );
1959  },
1960  $url
1961  );
1962 
1963  $ret = '';
1964  $end = strlen( $url );
1965 
1966  # Fragment part - 'fragment'
1967  $start = strpos( $url, '#' );
1968  if ( $start !== false && $start < $end ) {
1969  $ret = self::normalizeUrlComponent(
1970  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1971  $end = $start;
1972  }
1973 
1974  # Query part - 'query' minus &=+;
1975  $start = strpos( $url, '?' );
1976  if ( $start !== false && $start < $end ) {
1977  $ret = self::normalizeUrlComponent(
1978  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
1979  $end = $start;
1980  }
1981 
1982  # Scheme and path part - 'pchar'
1983  # (we assume no userinfo or encoded colons in the host)
1984  $ret = self::normalizeUrlComponent(
1985  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
1986 
1987  return $ret;
1988  }
1989 
1990  private static function normalizeUrlComponent( $component, $unsafe ) {
1991  $callback = function ( $matches ) use ( $unsafe ) {
1992  $char = urldecode( $matches[0] );
1993  $ord = ord( $char );
1994  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
1995  # Unescape it
1996  return $char;
1997  } else {
1998  # Leave it escaped, but use uppercase for a-f
1999  return strtoupper( $matches[0] );
2000  }
2001  };
2002  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2003  }
2004 
2013  private function maybeMakeExternalImage( $url ) {
2014  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2015  $imagesexception = !empty( $imagesfrom );
2016  $text = false;
2017  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2018  if ( $imagesexception && is_array( $imagesfrom ) ) {
2019  $imagematch = false;
2020  foreach ( $imagesfrom as $match ) {
2021  if ( strpos( $url, $match ) === 0 ) {
2022  $imagematch = true;
2023  break;
2024  }
2025  }
2026  } elseif ( $imagesexception ) {
2027  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2028  } else {
2029  $imagematch = false;
2030  }
2031 
2032  if ( $this->mOptions->getAllowExternalImages()
2033  || ( $imagesexception && $imagematch )
2034  ) {
2035  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2036  # Image found
2037  $text = Linker::makeExternalImage( $url );
2038  }
2039  }
2040  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2041  && preg_match( self::EXT_IMAGE_REGEX, $url )
2042  ) {
2043  $whitelist = explode(
2044  "\n",
2045  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2046  );
2047 
2048  foreach ( $whitelist as $entry ) {
2049  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2050  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2051  continue;
2052  }
2053  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2054  # Image matches a whitelist entry
2055  $text = Linker::makeExternalImage( $url );
2056  break;
2057  }
2058  }
2059  }
2060  return $text;
2061  }
2062 
2072  public function replaceInternalLinks( $s ) {
2073  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2074  return $s;
2075  }
2076 
2085  public function replaceInternalLinks2( &$s ) {
2087 
2088  static $tc = false, $e1, $e1_img;
2089  # the % is needed to support urlencoded titles as well
2090  if ( !$tc ) {
2091  $tc = Title::legalChars() . '#%';
2092  # Match a link having the form [[namespace:link|alternate]]trail
2093  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2094  # Match cases where there is no "]]", which might still be images
2095  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2096  }
2097 
2098  $holders = new LinkHolderArray( $this );
2099 
2100  # split the entire text string on occurrences of [[
2101  $a = StringUtils::explode( '[[', ' ' . $s );
2102  # get the first element (all text up to first [[), and remove the space we added
2103  $s = $a->current();
2104  $a->next();
2105  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2106  $s = substr( $s, 1 );
2107 
2108  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2109  $e2 = null;
2110  if ( $useLinkPrefixExtension ) {
2111  # Match the end of a line for a word that's not followed by whitespace,
2112  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2114  $charset = $wgContLang->linkPrefixCharset();
2115  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2116  }
2117 
2118  if ( is_null( $this->mTitle ) ) {
2119  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2120  }
2121  $nottalk = !$this->mTitle->isTalkPage();
2122 
2123  if ( $useLinkPrefixExtension ) {
2124  $m = [];
2125  if ( preg_match( $e2, $s, $m ) ) {
2126  $first_prefix = $m[2];
2127  } else {
2128  $first_prefix = false;
2129  }
2130  } else {
2131  $prefix = '';
2132  }
2133 
2134  $useSubpages = $this->areSubpagesAllowed();
2135 
2136  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2137  # Loop for each link
2138  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2139  // @codingStandardsIgnoreEnd
2140 
2141  # Check for excessive memory usage
2142  if ( $holders->isBig() ) {
2143  # Too big
2144  # Do the existence check, replace the link holders and clear the array
2145  $holders->replace( $s );
2146  $holders->clear();
2147  }
2148 
2149  if ( $useLinkPrefixExtension ) {
2150  if ( preg_match( $e2, $s, $m ) ) {
2151  $prefix = $m[2];
2152  $s = $m[1];
2153  } else {
2154  $prefix = '';
2155  }
2156  # first link
2157  if ( $first_prefix ) {
2158  $prefix = $first_prefix;
2159  $first_prefix = false;
2160  }
2161  }
2162 
2163  $might_be_img = false;
2164 
2165  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2166  $text = $m[2];
2167  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2168  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2169  # the real problem is with the $e1 regex
2170  # See T1500.
2171  # Still some problems for cases where the ] is meant to be outside punctuation,
2172  # and no image is in sight. See T4095.
2173  if ( $text !== ''
2174  && substr( $m[3], 0, 1 ) === ']'
2175  && strpos( $text, '[' ) !== false
2176  ) {
2177  $text .= ']'; # so that replaceExternalLinks($text) works later
2178  $m[3] = substr( $m[3], 1 );
2179  }
2180  # fix up urlencoded title texts
2181  if ( strpos( $m[1], '%' ) !== false ) {
2182  # Should anchors '#' also be rejected?
2183  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2184  }
2185  $trail = $m[3];
2186  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2187  # Invalid, but might be an image with a link in its caption
2188  $might_be_img = true;
2189  $text = $m[2];
2190  if ( strpos( $m[1], '%' ) !== false ) {
2191  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2192  }
2193  $trail = "";
2194  } else { # Invalid form; output directly
2195  $s .= $prefix . '[[' . $line;
2196  continue;
2197  }
2198 
2199  $origLink = ltrim( $m[1], ' ' );
2200 
2201  # Don't allow internal links to pages containing
2202  # PROTO: where PROTO is a valid URL protocol; these
2203  # should be external links.
2204  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2205  $s .= $prefix . '[[' . $line;
2206  continue;
2207  }
2208 
2209  # Make subpage if necessary
2210  if ( $useSubpages ) {
2211  $link = $this->maybeDoSubpageLink( $origLink, $text );
2212  } else {
2213  $link = $origLink;
2214  }
2215 
2216  $unstrip = $this->mStripState->unstripNoWiki( $link );
2217  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2218  if ( $nt === null ) {
2219  $s .= $prefix . '[[' . $line;
2220  continue;
2221  }
2222 
2223  $ns = $nt->getNamespace();
2224  $iw = $nt->getInterwiki();
2225 
2226  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2227 
2228  if ( $might_be_img ) { # if this is actually an invalid link
2229  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2230  $found = false;
2231  while ( true ) {
2232  # look at the next 'line' to see if we can close it there
2233  $a->next();
2234  $next_line = $a->current();
2235  if ( $next_line === false || $next_line === null ) {
2236  break;
2237  }
2238  $m = explode( ']]', $next_line, 3 );
2239  if ( count( $m ) == 3 ) {
2240  # the first ]] closes the inner link, the second the image
2241  $found = true;
2242  $text .= "[[{$m[0]}]]{$m[1]}";
2243  $trail = $m[2];
2244  break;
2245  } elseif ( count( $m ) == 2 ) {
2246  # if there's exactly one ]] that's fine, we'll keep looking
2247  $text .= "[[{$m[0]}]]{$m[1]}";
2248  } else {
2249  # if $next_line is invalid too, we need look no further
2250  $text .= '[[' . $next_line;
2251  break;
2252  }
2253  }
2254  if ( !$found ) {
2255  # we couldn't find the end of this imageLink, so output it raw
2256  # but don't ignore what might be perfectly normal links in the text we've examined
2257  $holders->merge( $this->replaceInternalLinks2( $text ) );
2258  $s .= "{$prefix}[[$link|$text";
2259  # note: no $trail, because without an end, there *is* no trail
2260  continue;
2261  }
2262  } else { # it's not an image, so output it raw
2263  $s .= "{$prefix}[[$link|$text";
2264  # note: no $trail, because without an end, there *is* no trail
2265  continue;
2266  }
2267  }
2268 
2269  $wasblank = ( $text == '' );
2270  if ( $wasblank ) {
2271  $text = $link;
2272  if ( !$noforce ) {
2273  # Strip off leading ':'
2274  $text = substr( $text, 1 );
2275  }
2276  } else {
2277  # T6598 madness. Handle the quotes only if they come from the alternate part
2278  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2279  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2280  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2281  $text = $this->doQuotes( $text );
2282  }
2283 
2284  # Link not escaped by : , create the various objects
2285  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2286  # Interwikis
2287  if (
2288  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2289  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2290  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2291  )
2292  ) {
2293  # T26502: filter duplicates
2294  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2295  $this->mLangLinkLanguages[$iw] = true;
2296  $this->mOutput->addLanguageLink( $nt->getFullText() );
2297  }
2298 
2302  $s = rtrim( $s . $prefix ) . $trail; # T175416
2303  continue;
2304  }
2305 
2306  if ( $ns == NS_FILE ) {
2307  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2308  if ( $wasblank ) {
2309  # if no parameters were passed, $text
2310  # becomes something like "File:Foo.png",
2311  # which we don't want to pass on to the
2312  # image generator
2313  $text = '';
2314  } else {
2315  # recursively parse links inside the image caption
2316  # actually, this will parse them in any other parameters, too,
2317  # but it might be hard to fix that, and it doesn't matter ATM
2318  $text = $this->replaceExternalLinks( $text );
2319  $holders->merge( $this->replaceInternalLinks2( $text ) );
2320  }
2321  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2322  $s .= $prefix . $this->armorLinks(
2323  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2324  continue;
2325  }
2326  } elseif ( $ns == NS_CATEGORY ) {
2330  $s = rtrim( $s . $prefix ) . $trail; # T2087, T87753
2331 
2332  if ( $wasblank ) {
2333  $sortkey = $this->getDefaultSort();
2334  } else {
2335  $sortkey = $text;
2336  }
2337  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2338  $sortkey = str_replace( "\n", '', $sortkey );
2339  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2340  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2341 
2342  continue;
2343  }
2344  }
2345 
2346  # Self-link checking. For some languages, variants of the title are checked in
2347  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2348  # for linking to a different variant.
2349  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2350  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2351  continue;
2352  }
2353 
2354  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2355  # @todo FIXME: Should do batch file existence checks, see comment below
2356  if ( $ns == NS_MEDIA ) {
2357  # Give extensions a chance to select the file revision for us
2358  $options = [];
2359  $descQuery = false;
2360  Hooks::run( 'BeforeParserFetchFileAndTitle',
2361  [ $this, $nt, &$options, &$descQuery ] );
2362  # Fetch and register the file (file title may be different via hooks)
2363  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2364  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2365  $s .= $prefix . $this->armorLinks(
2366  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2367  continue;
2368  }
2369 
2370  # Some titles, such as valid special pages or files in foreign repos, should
2371  # be shown as bluelinks even though they're not included in the page table
2372  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2373  # batch file existence checks for NS_FILE and NS_MEDIA
2374  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2375  $this->mOutput->addLink( $nt );
2376  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2377  } else {
2378  # Links will be added to the output link list after checking
2379  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2380  }
2381  }
2382  return $holders;
2383  }
2384 
2398  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2399  list( $inside, $trail ) = Linker::splitTrail( $trail );
2400 
2401  if ( $text == '' ) {
2402  $text = htmlspecialchars( $nt->getPrefixedText() );
2403  }
2404 
2405  $link = $this->getLinkRenderer()->makeKnownLink(
2406  $nt, new HtmlArmor( "$prefix$text$inside" )
2407  );
2408 
2409  return $this->armorLinks( $link ) . $trail;
2410  }
2411 
2422  public function armorLinks( $text ) {
2423  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2424  self::MARKER_PREFIX . "NOPARSE$1", $text );
2425  }
2426 
2431  public function areSubpagesAllowed() {
2432  # Some namespaces don't allow subpages
2433  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2434  }
2435 
2444  public function maybeDoSubpageLink( $target, &$text ) {
2445  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2446  }
2447 
2456  public function doBlockLevels( $text, $linestart ) {
2457  return BlockLevelPass::doBlockLevels( $text, $linestart );
2458  }
2459 
2471  public function getVariableValue( $index, $frame = false ) {
2474 
2475  if ( is_null( $this->mTitle ) ) {
2476  // If no title set, bad things are going to happen
2477  // later. Title should always be set since this
2478  // should only be called in the middle of a parse
2479  // operation (but the unit-tests do funky stuff)
2480  throw new MWException( __METHOD__ . ' Should only be '
2481  . ' called while parsing (no title set)' );
2482  }
2483 
2484  // Avoid PHP 7.1 warning from passing $this by reference
2485  $parser = $this;
2486 
2491  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2492  if ( isset( $this->mVarCache[$index] ) ) {
2493  return $this->mVarCache[$index];
2494  }
2495  }
2496 
2497  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2498  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2499 
2500  $pageLang = $this->getFunctionLang();
2501 
2502  switch ( $index ) {
2503  case '!':
2504  $value = '|';
2505  break;
2506  case 'currentmonth':
2507  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ), true );
2508  break;
2509  case 'currentmonth1':
2510  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ), true );
2511  break;
2512  case 'currentmonthname':
2513  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2514  break;
2515  case 'currentmonthnamegen':
2516  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2517  break;
2518  case 'currentmonthabbrev':
2519  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2520  break;
2521  case 'currentday':
2522  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ), true );
2523  break;
2524  case 'currentday2':
2525  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ), true );
2526  break;
2527  case 'localmonth':
2528  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ), true );
2529  break;
2530  case 'localmonth1':
2531  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ), true );
2532  break;
2533  case 'localmonthname':
2534  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2535  break;
2536  case 'localmonthnamegen':
2537  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2538  break;
2539  case 'localmonthabbrev':
2540  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2541  break;
2542  case 'localday':
2543  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ), true );
2544  break;
2545  case 'localday2':
2546  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ), true );
2547  break;
2548  case 'pagename':
2549  $value = wfEscapeWikiText( $this->mTitle->getText() );
2550  break;
2551  case 'pagenamee':
2552  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2553  break;
2554  case 'fullpagename':
2555  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2556  break;
2557  case 'fullpagenamee':
2558  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2559  break;
2560  case 'subpagename':
2561  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2562  break;
2563  case 'subpagenamee':
2564  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2565  break;
2566  case 'rootpagename':
2567  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2568  break;
2569  case 'rootpagenamee':
2570  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2571  ' ',
2572  '_',
2573  $this->mTitle->getRootText()
2574  ) ) );
2575  break;
2576  case 'basepagename':
2577  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2578  break;
2579  case 'basepagenamee':
2580  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2581  ' ',
2582  '_',
2583  $this->mTitle->getBaseText()
2584  ) ) );
2585  break;
2586  case 'talkpagename':
2587  if ( $this->mTitle->canHaveTalkPage() ) {
2588  $talkPage = $this->mTitle->getTalkPage();
2589  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2590  } else {
2591  $value = '';
2592  }
2593  break;
2594  case 'talkpagenamee':
2595  if ( $this->mTitle->canHaveTalkPage() ) {
2596  $talkPage = $this->mTitle->getTalkPage();
2597  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2598  } else {
2599  $value = '';
2600  }
2601  break;
2602  case 'subjectpagename':
2603  $subjPage = $this->mTitle->getSubjectPage();
2604  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2605  break;
2606  case 'subjectpagenamee':
2607  $subjPage = $this->mTitle->getSubjectPage();
2608  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2609  break;
2610  case 'pageid': // requested in T25427
2611  $pageid = $this->getTitle()->getArticleID();
2612  if ( $pageid == 0 ) {
2613  # 0 means the page doesn't exist in the database,
2614  # which means the user is previewing a new page.
2615  # The vary-revision flag must be set, because the magic word
2616  # will have a different value once the page is saved.
2617  $this->mOutput->setFlag( 'vary-revision' );
2618  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2619  }
2620  $value = $pageid ? $pageid : null;
2621  break;
2622  case 'revisionid':
2623  # Let the edit saving system know we should parse the page
2624  # *after* a revision ID has been assigned.
2625  $this->mOutput->setFlag( 'vary-revision-id' );
2626  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2627  $value = $this->mRevisionId;
2628  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2629  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2630  $this->mOutput->setSpeculativeRevIdUsed( $value );
2631  }
2632  break;
2633  case 'revisionday':
2634  # Let the edit saving system know we should parse the page
2635  # *after* a revision ID has been assigned. This is for null edits.
2636  $this->mOutput->setFlag( 'vary-revision' );
2637  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2638  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2639  break;
2640  case 'revisionday2':
2641  # Let the edit saving system know we should parse the page
2642  # *after* a revision ID has been assigned. This is for null edits.
2643  $this->mOutput->setFlag( 'vary-revision' );
2644  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2645  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2646  break;
2647  case 'revisionmonth':
2648  # Let the edit saving system know we should parse the page
2649  # *after* a revision ID has been assigned. This is for null edits.
2650  $this->mOutput->setFlag( 'vary-revision' );
2651  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2652  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2653  break;
2654  case 'revisionmonth1':
2655  # Let the edit saving system know we should parse the page
2656  # *after* a revision ID has been assigned. This is for null edits.
2657  $this->mOutput->setFlag( 'vary-revision' );
2658  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2659  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2660  break;
2661  case 'revisionyear':
2662  # Let the edit saving system know we should parse the page
2663  # *after* a revision ID has been assigned. This is for null edits.
2664  $this->mOutput->setFlag( 'vary-revision' );
2665  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2666  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2667  break;
2668  case 'revisiontimestamp':
2669  # Let the edit saving system know we should parse the page
2670  # *after* a revision ID has been assigned. This is for null edits.
2671  $this->mOutput->setFlag( 'vary-revision' );
2672  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2673  $value = $this->getRevisionTimestamp();
2674  break;
2675  case 'revisionuser':
2676  # Let the edit saving system know we should parse the page
2677  # *after* a revision ID has been assigned for null edits.
2678  $this->mOutput->setFlag( 'vary-user' );
2679  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2680  $value = $this->getRevisionUser();
2681  break;
2682  case 'revisionsize':
2683  $value = $this->getRevisionSize();
2684  break;
2685  case 'namespace':
2686  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2687  break;
2688  case 'namespacee':
2689  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2690  break;
2691  case 'namespacenumber':
2692  $value = $this->mTitle->getNamespace();
2693  break;
2694  case 'talkspace':
2695  $value = $this->mTitle->canHaveTalkPage()
2696  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2697  : '';
2698  break;
2699  case 'talkspacee':
2700  $value = $this->mTitle->canHaveTalkPage() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2701  break;
2702  case 'subjectspace':
2703  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2704  break;
2705  case 'subjectspacee':
2706  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2707  break;
2708  case 'currentdayname':
2709  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2710  break;
2711  case 'currentyear':
2712  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2713  break;
2714  case 'currenttime':
2715  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2716  break;
2717  case 'currenthour':
2718  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2719  break;
2720  case 'currentweek':
2721  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2722  # int to remove the padding
2723  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2724  break;
2725  case 'currentdow':
2726  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2727  break;
2728  case 'localdayname':
2729  $value = $pageLang->getWeekdayName(
2730  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2731  );
2732  break;
2733  case 'localyear':
2734  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2735  break;
2736  case 'localtime':
2737  $value = $pageLang->time(
2738  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2739  false,
2740  false
2741  );
2742  break;
2743  case 'localhour':
2744  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2745  break;
2746  case 'localweek':
2747  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2748  # int to remove the padding
2749  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2750  break;
2751  case 'localdow':
2752  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2753  break;
2754  case 'numberofarticles':
2755  $value = $pageLang->formatNum( SiteStats::articles() );
2756  break;
2757  case 'numberoffiles':
2758  $value = $pageLang->formatNum( SiteStats::images() );
2759  break;
2760  case 'numberofusers':
2761  $value = $pageLang->formatNum( SiteStats::users() );
2762  break;
2763  case 'numberofactiveusers':
2764  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2765  break;
2766  case 'numberofpages':
2767  $value = $pageLang->formatNum( SiteStats::pages() );
2768  break;
2769  case 'numberofadmins':
2770  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2771  break;
2772  case 'numberofedits':
2773  $value = $pageLang->formatNum( SiteStats::edits() );
2774  break;
2775  case 'currenttimestamp':
2776  $value = wfTimestamp( TS_MW, $ts );
2777  break;
2778  case 'localtimestamp':
2779  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2780  break;
2781  case 'currentversion':
2783  break;
2784  case 'articlepath':
2785  return $wgArticlePath;
2786  case 'sitename':
2787  return $wgSitename;
2788  case 'server':
2789  return $wgServer;
2790  case 'servername':
2791  return $wgServerName;
2792  case 'scriptpath':
2793  return $wgScriptPath;
2794  case 'stylepath':
2795  return $wgStylePath;
2796  case 'directionmark':
2797  return $pageLang->getDirMark();
2798  case 'contentlanguage':
2800  return $wgLanguageCode;
2801  case 'pagelanguage':
2802  $value = $pageLang->getCode();
2803  break;
2804  case 'cascadingsources':
2806  break;
2807  default:
2808  $ret = null;
2809  Hooks::run(
2810  'ParserGetVariableValueSwitch',
2811  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2812  );
2813 
2814  return $ret;
2815  }
2816 
2817  if ( $index ) {
2818  $this->mVarCache[$index] = $value;
2819  }
2820 
2821  return $value;
2822  }
2823 
2829  public function initialiseVariables() {
2830  $variableIDs = MagicWord::getVariableIDs();
2831  $substIDs = MagicWord::getSubstIDs();
2832 
2833  $this->mVariables = new MagicWordArray( $variableIDs );
2834  $this->mSubstWords = new MagicWordArray( $substIDs );
2835  }
2836 
2859  public function preprocessToDom( $text, $flags = 0 ) {
2860  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2861  return $dom;
2862  }
2863 
2871  public static function splitWhitespace( $s ) {
2872  $ltrimmed = ltrim( $s );
2873  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2874  $trimmed = rtrim( $ltrimmed );
2875  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2876  if ( $diff > 0 ) {
2877  $w2 = substr( $ltrimmed, -$diff );
2878  } else {
2879  $w2 = '';
2880  }
2881  return [ $w1, $trimmed, $w2 ];
2882  }
2883 
2904  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2905  # Is there any text? Also, Prevent too big inclusions!
2906  $textSize = strlen( $text );
2907  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2908  return $text;
2909  }
2910 
2911  if ( $frame === false ) {
2912  $frame = $this->getPreprocessor()->newFrame();
2913  } elseif ( !( $frame instanceof PPFrame ) ) {
2914  wfDebug( __METHOD__ . " called using plain parameters instead of "
2915  . "a PPFrame instance. Creating custom frame.\n" );
2916  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2917  }
2918 
2919  $dom = $this->preprocessToDom( $text );
2920  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2921  $text = $frame->expand( $dom, $flags );
2922 
2923  return $text;
2924  }
2925 
2933  public static function createAssocArgs( $args ) {
2934  $assocArgs = [];
2935  $index = 1;
2936  foreach ( $args as $arg ) {
2937  $eqpos = strpos( $arg, '=' );
2938  if ( $eqpos === false ) {
2939  $assocArgs[$index++] = $arg;
2940  } else {
2941  $name = trim( substr( $arg, 0, $eqpos ) );
2942  $value = trim( substr( $arg, $eqpos + 1 ) );
2943  if ( $value === false ) {
2944  $value = '';
2945  }
2946  if ( $name !== false ) {
2947  $assocArgs[$name] = $value;
2948  }
2949  }
2950  }
2951 
2952  return $assocArgs;
2953  }
2954 
2981  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2982  # does no harm if $current and $max are present but are unnecessary for the message
2983  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2984  # only during preview, and that would split the parser cache unnecessarily.
2985  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
2986  ->text();
2987  $this->mOutput->addWarning( $warning );
2988  $this->addTrackingCategory( "$limitationType-category" );
2989  }
2990 
3003  public function braceSubstitution( $piece, $frame ) {
3004  // Flags
3005 
3006  // $text has been filled
3007  $found = false;
3008  // wiki markup in $text should be escaped
3009  $nowiki = false;
3010  // $text is HTML, armour it against wikitext transformation
3011  $isHTML = false;
3012  // Force interwiki transclusion to be done in raw mode not rendered
3013  $forceRawInterwiki = false;
3014  // $text is a DOM node needing expansion in a child frame
3015  $isChildObj = false;
3016  // $text is a DOM node needing expansion in the current frame
3017  $isLocalObj = false;
3018 
3019  # Title object, where $text came from
3020  $title = false;
3021 
3022  # $part1 is the bit before the first |, and must contain only title characters.
3023  # Various prefixes will be stripped from it later.
3024  $titleWithSpaces = $frame->expand( $piece['title'] );
3025  $part1 = trim( $titleWithSpaces );
3026  $titleText = false;
3027 
3028  # Original title text preserved for various purposes
3029  $originalTitle = $part1;
3030 
3031  # $args is a list of argument nodes, starting from index 0, not including $part1
3032  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3033  # below won't work b/c this $args isn't an object
3034  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3035 
3036  $profileSection = null; // profile templates
3037 
3038  # SUBST
3039  if ( !$found ) {
3040  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3041 
3042  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3043  # Decide whether to expand template or keep wikitext as-is.
3044  if ( $this->ot['wiki'] ) {
3045  if ( $substMatch === false ) {
3046  $literal = true; # literal when in PST with no prefix
3047  } else {
3048  $literal = false; # expand when in PST with subst: or safesubst:
3049  }
3050  } else {
3051  if ( $substMatch == 'subst' ) {
3052  $literal = true; # literal when not in PST with plain subst:
3053  } else {
3054  $literal = false; # expand when not in PST with safesubst: or no prefix
3055  }
3056  }
3057  if ( $literal ) {
3058  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3059  $isLocalObj = true;
3060  $found = true;
3061  }
3062  }
3063 
3064  # Variables
3065  if ( !$found && $args->getLength() == 0 ) {
3066  $id = $this->mVariables->matchStartToEnd( $part1 );
3067  if ( $id !== false ) {
3068  $text = $this->getVariableValue( $id, $frame );
3069  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3070  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3071  }
3072  $found = true;
3073  }
3074  }
3075 
3076  # MSG, MSGNW and RAW
3077  if ( !$found ) {
3078  # Check for MSGNW:
3079  $mwMsgnw = MagicWord::get( 'msgnw' );
3080  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3081  $nowiki = true;
3082  } else {
3083  # Remove obsolete MSG:
3084  $mwMsg = MagicWord::get( 'msg' );
3085  $mwMsg->matchStartAndRemove( $part1 );
3086  }
3087 
3088  # Check for RAW:
3089  $mwRaw = MagicWord::get( 'raw' );
3090  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3091  $forceRawInterwiki = true;
3092  }
3093  }
3094 
3095  # Parser functions
3096  if ( !$found ) {
3097  $colonPos = strpos( $part1, ':' );
3098  if ( $colonPos !== false ) {
3099  $func = substr( $part1, 0, $colonPos );
3100  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3101  $argsLength = $args->getLength();
3102  for ( $i = 0; $i < $argsLength; $i++ ) {
3103  $funcArgs[] = $args->item( $i );
3104  }
3105  try {
3106  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3107  } catch ( Exception $ex ) {
3108  throw $ex;
3109  }
3110 
3111  # The interface for parser functions allows for extracting
3112  # flags into the local scope. Extract any forwarded flags
3113  # here.
3114  extract( $result );
3115  }
3116  }
3117 
3118  # Finish mangling title and then check for loops.
3119  # Set $title to a Title object and $titleText to the PDBK
3120  if ( !$found ) {
3121  $ns = NS_TEMPLATE;
3122  # Split the title into page and subpage
3123  $subpage = '';
3124  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3125  if ( $part1 !== $relative ) {
3126  $part1 = $relative;
3127  $ns = $this->mTitle->getNamespace();
3128  }
3129  $title = Title::newFromText( $part1, $ns );
3130  if ( $title ) {
3131  $titleText = $title->getPrefixedText();
3132  # Check for language variants if the template is not found
3133  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3134  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3135  }
3136  # Do recursion depth check
3137  $limit = $this->mOptions->getMaxTemplateDepth();
3138  if ( $frame->depth >= $limit ) {
3139  $found = true;
3140  $text = '<span class="error">'
3141  . wfMessage( 'parser-template-recursion-depth-warning' )
3142  ->numParams( $limit )->inContentLanguage()->text()
3143  . '</span>';
3144  }
3145  }
3146  }
3147 
3148  # Load from database
3149  if ( !$found && $title ) {
3150  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3151  if ( !$title->isExternal() ) {
3152  if ( $title->isSpecialPage()
3153  && $this->mOptions->getAllowSpecialInclusion()
3154  && $this->ot['html']
3155  ) {
3156  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3157  // Pass the template arguments as URL parameters.
3158  // "uselang" will have no effect since the Language object
3159  // is forced to the one defined in ParserOptions.
3160  $pageArgs = [];
3161  $argsLength = $args->getLength();
3162  for ( $i = 0; $i < $argsLength; $i++ ) {
3163  $bits = $args->item( $i )->splitArg();
3164  if ( strval( $bits['index'] ) === '' ) {
3165  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3166  $value = trim( $frame->expand( $bits['value'] ) );
3167  $pageArgs[$name] = $value;
3168  }
3169  }
3170 
3171  // Create a new context to execute the special page
3172  $context = new RequestContext;
3173  $context->setTitle( $title );
3174  $context->setRequest( new FauxRequest( $pageArgs ) );
3175  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3176  $context->setUser( $this->getUser() );
3177  } else {
3178  // If this page is cached, then we better not be per user.
3179  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3180  }
3181  $context->setLanguage( $this->mOptions->getUserLangObj() );
3183  $title, $context, $this->getLinkRenderer() );
3184  if ( $ret ) {
3185  $text = $context->getOutput()->getHTML();
3186  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3187  $found = true;
3188  $isHTML = true;
3189  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3190  $this->mOutput->updateRuntimeAdaptiveExpiry(
3191  $specialPage->maxIncludeCacheTime()
3192  );
3193  }
3194  }
3195  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3196  $found = false; # access denied
3197  wfDebug( __METHOD__ . ": template inclusion denied for " .
3198  $title->getPrefixedDBkey() . "\n" );
3199  } else {
3200  list( $text, $title ) = $this->getTemplateDom( $title );
3201  if ( $text !== false ) {
3202  $found = true;
3203  $isChildObj = true;
3204  }
3205  }
3206 
3207  # If the title is valid but undisplayable, make a link to it
3208  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3209  $text = "[[:$titleText]]";
3210  $found = true;
3211  }
3212  } elseif ( $title->isTrans() ) {
3213  # Interwiki transclusion
3214  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3215  $text = $this->interwikiTransclude( $title, 'render' );
3216  $isHTML = true;
3217  } else {
3218  $text = $this->interwikiTransclude( $title, 'raw' );
3219  # Preprocess it like a template
3220  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3221  $isChildObj = true;
3222  }
3223  $found = true;
3224  }
3225 
3226  # Do infinite loop check
3227  # This has to be done after redirect resolution to avoid infinite loops via redirects
3228  if ( !$frame->loopCheck( $title ) ) {
3229  $found = true;
3230  $text = '<span class="error">'
3231  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3232  . '</span>';
3233  $this->addTrackingCategory( 'template-loop-category' );
3234  $this->mOutput->addWarning( wfMessage( 'template-loop-warning',
3235  wfEscapeWikiText( $titleText ) )->text() );
3236  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3237  }
3238  }
3239 
3240  # If we haven't found text to substitute by now, we're done
3241  # Recover the source wikitext and return it
3242  if ( !$found ) {
3243  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3244  if ( $profileSection ) {
3245  $this->mProfiler->scopedProfileOut( $profileSection );
3246  }
3247  return [ 'object' => $text ];
3248  }
3249 
3250  # Expand DOM-style return values in a child frame
3251  if ( $isChildObj ) {
3252  # Clean up argument array
3253  $newFrame = $frame->newChild( $args, $title );
3254 
3255  if ( $nowiki ) {
3256  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3257  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3258  # Expansion is eligible for the empty-frame cache
3259  $text = $newFrame->cachedExpand( $titleText, $text );
3260  } else {
3261  # Uncached expansion
3262  $text = $newFrame->expand( $text );
3263  }
3264  }
3265  if ( $isLocalObj && $nowiki ) {
3266  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3267  $isLocalObj = false;
3268  }
3269 
3270  if ( $profileSection ) {
3271  $this->mProfiler->scopedProfileOut( $profileSection );
3272  }
3273 
3274  # Replace raw HTML by a placeholder
3275  if ( $isHTML ) {
3276  $text = $this->insertStripItem( $text );
3277  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3278  # Escape nowiki-style return values
3279  $text = wfEscapeWikiText( $text );
3280  } elseif ( is_string( $text )
3281  && !$piece['lineStart']
3282  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3283  ) {
3284  # T2529: if the template begins with a table or block-level
3285  # element, it should be treated as beginning a new line.
3286  # This behavior is somewhat controversial.
3287  $text = "\n" . $text;
3288  }
3289 
3290  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3291  # Error, oversize inclusion
3292  if ( $titleText !== false ) {
3293  # Make a working, properly escaped link if possible (T25588)
3294  $text = "[[:$titleText]]";
3295  } else {
3296  # This will probably not be a working link, but at least it may
3297  # provide some hint of where the problem is
3298  preg_replace( '/^:/', '', $originalTitle );
3299  $text = "[[:$originalTitle]]";
3300  }
3301  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3302  . 'post-expand include size too large -->' );
3303  $this->limitationWarn( 'post-expand-template-inclusion' );
3304  }
3305 
3306  if ( $isLocalObj ) {
3307  $ret = [ 'object' => $text ];
3308  } else {
3309  $ret = [ 'text' => $text ];
3310  }
3311 
3312  return $ret;
3313  }
3314 
3334  public function callParserFunction( $frame, $function, array $args = [] ) {
3336 
3337  # Case sensitive functions
3338  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3339  $function = $this->mFunctionSynonyms[1][$function];
3340  } else {
3341  # Case insensitive functions
3342  $function = $wgContLang->lc( $function );
3343  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3344  $function = $this->mFunctionSynonyms[0][$function];
3345  } else {
3346  return [ 'found' => false ];
3347  }
3348  }
3349 
3350  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3351 
3352  // Avoid PHP 7.1 warning from passing $this by reference
3353  $parser = $this;
3354 
3355  $allArgs = [ &$parser ];
3356  if ( $flags & self::SFH_OBJECT_ARGS ) {
3357  # Convert arguments to PPNodes and collect for appending to $allArgs
3358  $funcArgs = [];
3359  foreach ( $args as $k => $v ) {
3360  if ( $v instanceof PPNode || $k === 0 ) {
3361  $funcArgs[] = $v;
3362  } else {
3363  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3364  }
3365  }
3366 
3367  # Add a frame parameter, and pass the arguments as an array
3368  $allArgs[] = $frame;
3369  $allArgs[] = $funcArgs;
3370  } else {
3371  # Convert arguments to plain text and append to $allArgs
3372  foreach ( $args as $k => $v ) {
3373  if ( $v instanceof PPNode ) {
3374  $allArgs[] = trim( $frame->expand( $v ) );
3375  } elseif ( is_int( $k ) && $k >= 0 ) {
3376  $allArgs[] = trim( $v );
3377  } else {
3378  $allArgs[] = trim( "$k=$v" );
3379  }
3380  }
3381  }
3382 
3383  $result = call_user_func_array( $callback, $allArgs );
3384 
3385  # The interface for function hooks allows them to return a wikitext
3386  # string or an array containing the string and any flags. This mungs
3387  # things around to match what this method should return.
3388  if ( !is_array( $result ) ) {
3389  $result = [
3390  'found' => true,
3391  'text' => $result,
3392  ];
3393  } else {
3394  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3395  $result['text'] = $result[0];
3396  }
3397  unset( $result[0] );
3398  $result += [
3399  'found' => true,
3400  ];
3401  }
3402 
3403  $noparse = true;
3404  $preprocessFlags = 0;
3405  if ( isset( $result['noparse'] ) ) {
3406  $noparse = $result['noparse'];
3407  }
3408  if ( isset( $result['preprocessFlags'] ) ) {
3409  $preprocessFlags = $result['preprocessFlags'];
3410  }
3411 
3412  if ( !$noparse ) {
3413  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3414  $result['isChildObj'] = true;
3415  }
3416 
3417  return $result;
3418  }
3419 
3428  public function getTemplateDom( $title ) {
3429  $cacheTitle = $title;
3430  $titleText = $title->getPrefixedDBkey();
3431 
3432  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3433  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3434  $title = Title::makeTitle( $ns, $dbk );
3435  $titleText = $title->getPrefixedDBkey();
3436  }
3437  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3438  return [ $this->mTplDomCache[$titleText], $title ];
3439  }
3440 
3441  # Cache miss, go to the database
3442  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3443 
3444  if ( $text === false ) {
3445  $this->mTplDomCache[$titleText] = false;
3446  return [ false, $title ];
3447  }
3448 
3449  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3450  $this->mTplDomCache[$titleText] = $dom;
3451 
3452  if ( !$title->equals( $cacheTitle ) ) {
3453  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3454  [ $title->getNamespace(), $title->getDBkey() ];
3455  }
3456 
3457  return [ $dom, $title ];
3458  }
3459 
3472  $cacheKey = $title->getPrefixedDBkey();
3473  if ( !$this->currentRevisionCache ) {
3474  $this->currentRevisionCache = new MapCacheLRU( 100 );
3475  }
3476  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3477  $this->currentRevisionCache->set( $cacheKey,
3478  // Defaults to Parser::statelessFetchRevision()
3479  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3480  );
3481  }
3482  return $this->currentRevisionCache->get( $cacheKey );
3483  }
3484 
3494  public static function statelessFetchRevision( Title $title, $parser = false ) {
3495  $pageId = $title->getArticleID();
3496  $revId = $title->getLatestRevID();
3497 
3498  $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $pageId, $revId );
3499  if ( $rev ) {
3500  $rev->setTitle( $title );
3501  }
3502 
3503  return $rev;
3504  }
3505 
3511  public function fetchTemplateAndTitle( $title ) {
3512  // Defaults to Parser::statelessFetchTemplate()
3513  $templateCb = $this->mOptions->getTemplateCallback();
3514  $stuff = call_user_func( $templateCb, $title, $this );
3515  // We use U+007F DELETE to distinguish strip markers from regular text.
3516  $text = $stuff['text'];
3517  if ( is_string( $stuff['text'] ) ) {
3518  $text = strtr( $text, "\x7f", "?" );
3519  }
3520  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3521  if ( isset( $stuff['deps'] ) ) {
3522  foreach ( $stuff['deps'] as $dep ) {
3523  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3524  if ( $dep['title']->equals( $this->getTitle() ) ) {
3525  // If we transclude ourselves, the final result
3526  // will change based on the new version of the page
3527  $this->mOutput->setFlag( 'vary-revision' );
3528  }
3529  }
3530  }
3531  return [ $text, $finalTitle ];
3532  }
3533 
3539  public function fetchTemplate( $title ) {
3540  return $this->fetchTemplateAndTitle( $title )[0];
3541  }
3542 
3552  public static function statelessFetchTemplate( $title, $parser = false ) {
3553  $text = $skip = false;
3554  $finalTitle = $title;
3555  $deps = [];
3556 
3557  # Loop to fetch the article, with up to 1 redirect
3558  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3559  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3560  // @codingStandardsIgnoreEnd
3561  # Give extensions a chance to select the revision instead
3562  $id = false; # Assume current
3563  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3564  [ $parser, $title, &$skip, &$id ] );
3565 
3566  if ( $skip ) {
3567  $text = false;
3568  $deps[] = [
3569  'title' => $title,
3570  'page_id' => $title->getArticleID(),
3571  'rev_id' => null
3572  ];
3573  break;
3574  }
3575  # Get the revision
3576  if ( $id ) {
3577  $rev = Revision::newFromId( $id );
3578  } elseif ( $parser ) {
3579  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3580  } else {
3582  }
3583  $rev_id = $rev ? $rev->getId() : 0;
3584  # If there is no current revision, there is no page
3585  if ( $id === false && !$rev ) {
3586  $linkCache = LinkCache::singleton();
3587  $linkCache->addBadLinkObj( $title );
3588  }
3589 
3590  $deps[] = [
3591  'title' => $title,
3592  'page_id' => $title->getArticleID(),
3593  'rev_id' => $rev_id ];
3594  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3595  # We fetched a rev from a different title; register it too...
3596  $deps[] = [
3597  'title' => $rev->getTitle(),
3598  'page_id' => $rev->getPage(),
3599  'rev_id' => $rev_id ];
3600  }
3601 
3602  if ( $rev ) {
3603  $content = $rev->getContent();
3604  $text = $content ? $content->getWikitextForTransclusion() : null;
3605 
3606  Hooks::run( 'ParserFetchTemplate',
3607  [ $parser, $title, $rev, &$text, &$deps ] );
3608 
3609  if ( $text === false || $text === null ) {
3610  $text = false;
3611  break;
3612  }
3613  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3615  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3616  if ( !$message->exists() ) {
3617  $text = false;
3618  break;
3619  }
3620  $content = $message->content();
3621  $text = $message->plain();
3622  } else {
3623  break;
3624  }
3625  if ( !$content ) {
3626  break;
3627  }
3628  # Redirect?
3629  $finalTitle = $title;
3630  $title = $content->getRedirectTarget();
3631  }
3632  return [
3633  'text' => $text,
3634  'finalTitle' => $finalTitle,
3635  'deps' => $deps ];
3636  }
3637 
3645  public function fetchFile( $title, $options = [] ) {
3646  return $this->fetchFileAndTitle( $title, $options )[0];
3647  }
3648 
3656  public function fetchFileAndTitle( $title, $options = [] ) {
3657  $file = $this->fetchFileNoRegister( $title, $options );
3658 
3659  $time = $file ? $file->getTimestamp() : false;
3660  $sha1 = $file ? $file->getSha1() : false;
3661  # Register the file as a dependency...
3662  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3663  if ( $file && !$title->equals( $file->getTitle() ) ) {
3664  # Update fetched file title
3665  $title = $file->getTitle();
3666  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3667  }
3668  return [ $file, $title ];
3669  }
3670 
3681  protected function fetchFileNoRegister( $title, $options = [] ) {
3682  if ( isset( $options['broken'] ) ) {
3683  $file = false; // broken thumbnail forced by hook
3684  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3685  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3686  } else { // get by (name,timestamp)
3687  $file = wfFindFile( $title, $options );
3688  }
3689  return $file;
3690  }
3691 
3700  public function interwikiTransclude( $title, $action ) {
3701  global $wgEnableScaryTranscluding;
3702 
3703  if ( !$wgEnableScaryTranscluding ) {
3704  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3705  }
3706 
3707  $url = $title->getFullURL( [ 'action' => $action ] );
3708 
3709  if ( strlen( $url ) > 255 ) {
3710  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3711  }
3712  return $this->fetchScaryTemplateMaybeFromCache( $url );
3713  }
3714 
3719  public function fetchScaryTemplateMaybeFromCache( $url ) {
3720  global $wgTranscludeCacheExpiry;
3721  $dbr = wfGetDB( DB_REPLICA );
3722  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3723  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3724  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3725  if ( $obj ) {
3726  return $obj->tc_contents;
3727  }
3728 
3729  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3730  $status = $req->execute(); // Status object
3731  if ( $status->isOK() ) {
3732  $text = $req->getContent();
3733  } elseif ( $req->getStatus() != 200 ) {
3734  // Though we failed to fetch the content, this status is useless.
3735  return wfMessage( 'scarytranscludefailed-httpstatus' )
3736  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3737  } else {
3738  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3739  }
3740 
3741  $dbw = wfGetDB( DB_MASTER );
3742  $dbw->replace( 'transcache', [ 'tc_url' ], [
3743  'tc_url' => $url,
3744  'tc_time' => $dbw->timestamp( time() ),
3745  'tc_contents' => $text
3746  ] );
3747  return $text;
3748  }
3749 
3759  public function argSubstitution( $piece, $frame ) {
3760  $error = false;
3761  $parts = $piece['parts'];
3762  $nameWithSpaces = $frame->expand( $piece['title'] );
3763  $argName = trim( $nameWithSpaces );
3764  $object = false;
3765  $text = $frame->getArgument( $argName );
3766  if ( $text === false && $parts->getLength() > 0
3767  && ( $this->ot['html']
3768  || $this->ot['pre']
3769  || ( $this->ot['wiki'] && $frame->isTemplate() )
3770  )
3771  ) {
3772  # No match in frame, use the supplied default
3773  $object = $parts->item( 0 )->getChildren();
3774  }
3775  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3776  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3777  $this->limitationWarn( 'post-expand-template-argument' );
3778  }
3779 
3780  if ( $text === false && $object === false ) {
3781  # No match anywhere
3782  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3783  }
3784  if ( $error !== false ) {
3785  $text .= $error;
3786  }
3787  if ( $object !== false ) {
3788  $ret = [ 'object' => $object ];
3789  } else {
3790  $ret = [ 'text' => $text ];
3791  }
3792 
3793  return $ret;
3794  }
3795 
3811  public function extensionSubstitution( $params, $frame ) {
3812  static $errorStr = '<span class="error">';
3813  static $errorLen = 20;
3814 
3815  $name = $frame->expand( $params['name'] );
3816  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3817  // Probably expansion depth or node count exceeded. Just punt the
3818  // error up.
3819  return $name;
3820  }
3821 
3822  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3823  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3824  // See above
3825  return $attrText;
3826  }
3827 
3828  // We can't safely check if the expansion for $content resulted in an
3829  // error, because the content could happen to be the error string
3830  // (T149622).
3831  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3832 
3833  $marker = self::MARKER_PREFIX . "-$name-"
3834  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3835 
3836  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3837  ( $this->ot['html'] || $this->ot['pre'] );
3838  if ( $isFunctionTag ) {
3839  $markerType = 'none';
3840  } else {
3841  $markerType = 'general';
3842  }
3843  if ( $this->ot['html'] || $isFunctionTag ) {
3844  $name = strtolower( $name );
3845  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3846  if ( isset( $params['attributes'] ) ) {
3847  $attributes = $attributes + $params['attributes'];
3848  }
3849 
3850  if ( isset( $this->mTagHooks[$name] ) ) {
3851  $output = call_user_func_array( $this->mTagHooks[$name],
3852  [ $content, $attributes, $this, $frame ] );
3853  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3854  list( $callback, ) = $this->mFunctionTagHooks[$name];
3855 
3856  // Avoid PHP 7.1 warning from passing $this by reference
3857  $parser = $this;
3858  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3859  } else {
3860  $output = '<span class="error">Invalid tag extension name: ' .
3861  htmlspecialchars( $name ) . '</span>';
3862  }
3863 
3864  if ( is_array( $output ) ) {
3865  # Extract flags to local scope (to override $markerType)
3866  $flags = $output;
3867  $output = $flags[0];
3868  unset( $flags[0] );
3869  extract( $flags );
3870  }
3871  } else {
3872  if ( is_null( $attrText ) ) {
3873  $attrText = '';
3874  }
3875  if ( isset( $params['attributes'] ) ) {
3876  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3877  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3878  htmlspecialchars( $attrValue ) . '"';
3879  }
3880  }
3881  if ( $content === null ) {
3882  $output = "<$name$attrText/>";
3883  } else {
3884  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3885  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3886  // See above
3887  return $close;
3888  }
3889  $output = "<$name$attrText>$content$close";
3890  }
3891  }
3892 
3893  if ( $markerType === 'none' ) {
3894  return $output;
3895  } elseif ( $markerType === 'nowiki' ) {
3896  $this->mStripState->addNoWiki( $marker, $output );
3897  } elseif ( $markerType === 'general' ) {
3898  $this->mStripState->addGeneral( $marker, $output );
3899  } else {
3900  throw new MWException( __METHOD__ . ': invalid marker type' );
3901  }
3902  return $marker;
3903  }
3904 
3912  public function incrementIncludeSize( $type, $size ) {
3913  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3914  return false;
3915  } else {
3916  $this->mIncludeSizes[$type] += $size;
3917  return true;
3918  }
3919  }
3920 
3927  $this->mExpensiveFunctionCount++;
3928  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3929  }
3930 
3939  public function doDoubleUnderscore( $text ) {
3940  # The position of __TOC__ needs to be recorded
3941  $mw = MagicWord::get( 'toc' );
3942  if ( $mw->match( $text ) ) {
3943  $this->mShowToc = true;
3944  $this->mForceTocPosition = true;
3945 
3946  # Set a placeholder. At the end we'll fill it in with the TOC.
3947  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3948 
3949  # Only keep the first one.
3950  $text = $mw->replace( '', $text );
3951  }
3952 
3953  # Now match and remove the rest of them
3955  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3956 
3957  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3958  $this->mOutput->mNoGallery = true;
3959  }
3960  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3961  $this->mShowToc = false;
3962  }
3963  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3964  && $this->mTitle->getNamespace() == NS_CATEGORY
3965  ) {
3966  $this->addTrackingCategory( 'hidden-category-category' );
3967  }
3968  # (T10068) Allow control over whether robots index a page.
3969  # __INDEX__ always overrides __NOINDEX__, see T16899
3970  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3971  $this->mOutput->setIndexPolicy( 'noindex' );
3972  $this->addTrackingCategory( 'noindex-category' );
3973  }
3974  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
3975  $this->mOutput->setIndexPolicy( 'index' );
3976  $this->addTrackingCategory( 'index-category' );
3977  }
3978 
3979  # Cache all double underscores in the database
3980  foreach ( $this->mDoubleUnderscores as $key => $val ) {
3981  $this->mOutput->setProperty( $key, '' );
3982  }
3983 
3984  return $text;
3985  }
3986 
3992  public function addTrackingCategory( $msg ) {
3993  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
3994  }
3995 
4012  public function formatHeadings( $text, $origText, $isMain = true ) {
4013  global $wgMaxTocLevel;
4014 
4015  # Inhibit editsection links if requested in the page
4016  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4017  $maybeShowEditLink = $showEditLink = false;
4018  } else {
4019  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4020  $showEditLink = $this->mOptions->getEditSection();
4021  }
4022  if ( $showEditLink ) {
4023  $this->mOutput->setEditSectionTokens( true );
4024  }
4025 
4026  # Get all headlines for numbering them and adding funky stuff like [edit]
4027  # links - this is for later, but we need the number of headlines right now
4028  $matches = [];
4029  $numMatches = preg_match_all(
4030  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4031  $text,
4032  $matches
4033  );
4034 
4035  # if there are fewer than 4 headlines in the article, do not show TOC
4036  # unless it's been explicitly enabled.
4037  $enoughToc = $this->mShowToc &&
4038  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4039 
4040  # Allow user to stipulate that a page should have a "new section"
4041  # link added via __NEWSECTIONLINK__
4042  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4043  $this->mOutput->setNewSection( true );
4044  }
4045 
4046  # Allow user to remove the "new section"
4047  # link via __NONEWSECTIONLINK__
4048  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4049  $this->mOutput->hideNewSection( true );
4050  }
4051 
4052  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4053  # override above conditions and always show TOC above first header
4054  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4055  $this->mShowToc = true;
4056  $enoughToc = true;
4057  }
4058 
4059  # headline counter
4060  $headlineCount = 0;
4061  $numVisible = 0;
4062 
4063  # Ugh .. the TOC should have neat indentation levels which can be
4064  # passed to the skin functions. These are determined here
4065  $toc = '';
4066  $full = '';
4067  $head = [];
4068  $sublevelCount = [];
4069  $levelCount = [];
4070  $level = 0;
4071  $prevlevel = 0;
4072  $toclevel = 0;
4073  $prevtoclevel = 0;
4074  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4075  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4076  $oldType = $this->mOutputType;
4077  $this->setOutputType( self::OT_WIKI );
4078  $frame = $this->getPreprocessor()->newFrame();
4079  $root = $this->preprocessToDom( $origText );
4080  $node = $root->getFirstChild();
4081  $byteOffset = 0;
4082  $tocraw = [];
4083  $refers = [];
4084 
4085  $headlines = $numMatches !== false ? $matches[3] : [];
4086 
4087  foreach ( $headlines as $headline ) {
4088  $isTemplate = false;
4089  $titleText = false;
4090  $sectionIndex = false;
4091  $numbering = '';
4092  $markerMatches = [];
4093  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4094  $serial = $markerMatches[1];
4095  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4096  $isTemplate = ( $titleText != $baseTitleText );
4097  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4098  }
4099 
4100  if ( $toclevel ) {
4101  $prevlevel = $level;
4102  }
4103  $level = $matches[1][$headlineCount];
4104 
4105  if ( $level > $prevlevel ) {
4106  # Increase TOC level
4107  $toclevel++;
4108  $sublevelCount[$toclevel] = 0;
4109  if ( $toclevel < $wgMaxTocLevel ) {
4110  $prevtoclevel = $toclevel;
4111  $toc .= Linker::tocIndent();
4112  $numVisible++;
4113  }
4114  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4115  # Decrease TOC level, find level to jump to
4116 
4117  for ( $i = $toclevel; $i > 0; $i-- ) {
4118  if ( $levelCount[$i] == $level ) {
4119  # Found last matching level
4120  $toclevel = $i;
4121  break;
4122  } elseif ( $levelCount[$i] < $level ) {
4123  # Found first matching level below current level
4124  $toclevel = $i + 1;
4125  break;
4126  }
4127  }
4128  if ( $i == 0 ) {
4129  $toclevel = 1;
4130  }
4131  if ( $toclevel < $wgMaxTocLevel ) {
4132  if ( $prevtoclevel < $wgMaxTocLevel ) {
4133  # Unindent only if the previous toc level was shown :p
4134  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4135  $prevtoclevel = $toclevel;
4136  } else {
4137  $toc .= Linker::tocLineEnd();
4138  }
4139  }
4140  } else {
4141  # No change in level, end TOC line
4142  if ( $toclevel < $wgMaxTocLevel ) {
4143  $toc .= Linker::tocLineEnd();
4144  }
4145  }
4146 
4147  $levelCount[$toclevel] = $level;
4148 
4149  # count number of headlines for each level
4150  $sublevelCount[$toclevel]++;
4151  $dot = 0;
4152  for ( $i = 1; $i <= $toclevel; $i++ ) {
4153  if ( !empty( $sublevelCount[$i] ) ) {
4154  if ( $dot ) {
4155  $numbering .= '.';
4156  }
4157  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4158  $dot = 1;
4159  }
4160  }
4161 
4162  # The safe header is a version of the header text safe to use for links
4163 
4164  # Remove link placeholders by the link text.
4165  # <!--LINK number-->
4166  # turns into
4167  # link text with suffix
4168  # Do this before unstrip since link text can contain strip markers
4169  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4170 
4171  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4172  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4173 
4174  # Strip out HTML (first regex removes any tag not allowed)
4175  # Allowed tags are:
4176  # * <sup> and <sub> (T10393)
4177  # * <i> (T28375)
4178  # * <b> (r105284)
4179  # * <bdi> (T74884)
4180  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4181  # * <s> and <strike> (T35715)
4182  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4183  # to allow setting directionality in toc items.
4184  $tocline = preg_replace(
4185  [
4186  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4187  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4188  ],
4189  [ '', '<$1>' ],
4190  $safeHeadline
4191  );
4192 
4193  # Strip '<span></span>', which is the result from the above if
4194  # <span id="foo"></span> is used to produce an additional anchor
4195  # for a section.
4196  $tocline = str_replace( '<span></span>', '', $tocline );
4197 
4198  $tocline = trim( $tocline );
4199 
4200  # For the anchor, strip out HTML-y stuff period
4201  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4202  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4203 
4204  # Save headline for section edit hint before it's escaped
4205  $headlineHint = $safeHeadline;
4206 
4207  # Decode HTML entities
4208  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4209 
4210  $safeHeadline = $this->normalizeSectionName( $safeHeadline );
4211 
4212  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4213  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4214  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4215  if ( $fallbackHeadline === $safeHeadline ) {
4216  # No reason to have both (in fact, we can't)
4217  $fallbackHeadline = false;
4218  }
4219 
4220  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4221  # @todo FIXME: We may be changing them depending on the current locale.
4222  $arrayKey = strtolower( $safeHeadline );
4223  if ( $fallbackHeadline === false ) {
4224  $fallbackArrayKey = false;
4225  } else {
4226  $fallbackArrayKey = strtolower( $fallbackHeadline );
4227  }
4228 
4229  # Create the anchor for linking from the TOC to the section
4230  $anchor = $safeHeadline;
4231  $fallbackAnchor = $fallbackHeadline;
4232  if ( isset( $refers[$arrayKey] ) ) {
4233  // @codingStandardsIgnoreStart
4234  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4235  // @codingStandardsIgnoreEnd
4236  $anchor .= "_$i";
4237  $linkAnchor .= "_$i";
4238  $refers["${arrayKey}_$i"] = true;
4239  } else {
4240  $refers[$arrayKey] = true;
4241  }
4242  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4243  // @codingStandardsIgnoreStart
4244  for ( $i = 2; isset( $refers["${fallbackArrayKey}_$i"] ); ++$i );
4245  // @codingStandardsIgnoreEnd
4246  $fallbackAnchor .= "_$i";
4247  $refers["${fallbackArrayKey}_$i"] = true;
4248  } else {
4249  $refers[$fallbackArrayKey] = true;
4250  }
4251 
4252  # Don't number the heading if it is the only one (looks silly)
4253  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4254  # the two are different if the line contains a link
4255  $headline = Html::element(
4256  'span',
4257  [ 'class' => 'mw-headline-number' ],
4258  $numbering
4259  ) . ' ' . $headline;
4260  }
4261 
4262  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4263  $toc .= Linker::tocLine( $linkAnchor, $tocline,
4264  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4265  }
4266 
4267  # Add the section to the section tree
4268  # Find the DOM node for this header
4269  $noOffset = ( $isTemplate || $sectionIndex === false );
4270  while ( $node && !$noOffset ) {
4271  if ( $node->getName() === 'h' ) {
4272  $bits = $node->splitHeading();
4273  if ( $bits['i'] == $sectionIndex ) {
4274  break;
4275  }
4276  }
4277  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4278  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4279  $node = $node->getNextSibling();
4280  }
4281  $tocraw[] = [
4282  'toclevel' => $toclevel,
4283  'level' => $level,
4284  'line' => $tocline,
4285  'number' => $numbering,
4286  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4287  'fromtitle' => $titleText,
4288  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4289  'anchor' => $anchor,
4290  ];
4291 
4292  # give headline the correct <h#> tag
4293  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4294  // Output edit section links as markers with styles that can be customized by skins
4295  if ( $isTemplate ) {
4296  # Put a T flag in the section identifier, to indicate to extractSections()
4297  # that sections inside <includeonly> should be counted.
4298  $editsectionPage = $titleText;
4299  $editsectionSection = "T-$sectionIndex";
4300  $editsectionContent = null;
4301  } else {
4302  $editsectionPage = $this->mTitle->getPrefixedText();
4303  $editsectionSection = $sectionIndex;
4304  $editsectionContent = $headlineHint;
4305  }
4306  // We use a bit of pesudo-xml for editsection markers. The
4307  // language converter is run later on. Using a UNIQ style marker
4308  // leads to the converter screwing up the tokens when it
4309  // converts stuff. And trying to insert strip tags fails too. At
4310  // this point all real inputted tags have already been escaped,
4311  // so we don't have to worry about a user trying to input one of
4312  // these markers directly. We use a page and section attribute
4313  // to stop the language converter from converting these
4314  // important bits of data, but put the headline hint inside a
4315  // content block because the language converter is supposed to
4316  // be able to convert that piece of data.
4317  // Gets replaced with html in ParserOutput::getText
4318  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4319  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4320  if ( $editsectionContent !== null ) {
4321  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4322  } else {
4323  $editlink .= '/>';
4324  }
4325  } else {
4326  $editlink = '';
4327  }
4328  $head[$headlineCount] = Linker::makeHeadline( $level,
4329  $matches['attrib'][$headlineCount], $anchor, $headline,
4330  $editlink, $fallbackAnchor );
4331 
4332  $headlineCount++;
4333  }
4334 
4335  $this->setOutputType( $oldType );
4336 
4337  # Never ever show TOC if no headers
4338  if ( $numVisible < 1 ) {
4339  $enoughToc = false;
4340  }
4341 
4342  if ( $enoughToc ) {
4343  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4344  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4345  }
4346  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4347  $this->mOutput->setTOCHTML( $toc );
4348  $toc = self::TOC_START . $toc . self::TOC_END;
4349  }
4350 
4351  if ( $isMain ) {
4352  $this->mOutput->setSections( $tocraw );
4353  }
4354 
4355  # split up and insert constructed headlines
4356  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4357  $i = 0;
4358 
4359  // build an array of document sections
4360  $sections = [];
4361  foreach ( $blocks as $block ) {
4362  // $head is zero-based, sections aren't.
4363  if ( empty( $head[$i - 1] ) ) {
4364  $sections[$i] = $block;
4365  } else {
4366  $sections[$i] = $head[$i - 1] . $block;
4367  }
4368 
4379  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4380 
4381  $i++;
4382  }
4383 
4384  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4385  // append the TOC at the beginning
4386  // Top anchor now in skin
4387  $sections[0] = $sections[0] . $toc . "\n";
4388  }
4389 
4390  $full .= implode( '', $sections );
4391 
4392  if ( $this->mForceTocPosition ) {
4393  return str_replace( '<!--MWTOC-->', $toc, $full );
4394  } else {
4395  return $full;
4396  }
4397  }
4398 
4410  public function preSaveTransform( $text, Title $title, User $user,
4411  ParserOptions $options, $clearState = true
4412  ) {
4413  if ( $clearState ) {
4414  $magicScopeVariable = $this->lock();
4415  }
4416  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4417  $this->setUser( $user );
4418 
4419  // Strip U+0000 NULL (T159174)
4420  $text = str_replace( "\000", '', $text );
4421 
4422  // We still normalize line endings for backwards-compatibility
4423  // with other code that just calls PST, but this should already
4424  // be handled in TextContent subclasses
4425  $text = TextContent::normalizeLineEndings( $text );
4426 
4427  if ( $options->getPreSaveTransform() ) {
4428  $text = $this->pstPass2( $text, $user );
4429  }
4430  $text = $this->mStripState->unstripBoth( $text );
4431 
4432  $this->setUser( null ); # Reset
4433 
4434  return $text;
4435  }
4436 
4445  private function pstPass2( $text, $user ) {
4447 
4448  # Note: This is the timestamp saved as hardcoded wikitext to
4449  # the database, we use $wgContLang here in order to give
4450  # everyone the same signature and use the default one rather
4451  # than the one selected in each user's preferences.
4452  # (see also T14815)
4453  $ts = $this->mOptions->getTimestamp();
4454  $timestamp = MWTimestamp::getLocalInstance( $ts );
4455  $ts = $timestamp->format( 'YmdHis' );
4456  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4457 
4458  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4459 
4460  # Variable replacement
4461  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4462  $text = $this->replaceVariables( $text );
4463 
4464  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4465  # which may corrupt this parser instance via its wfMessage()->text() call-
4466 
4467  # Signatures
4468  if ( strpos( $text, '~~~' ) !== false ) {
4469  $sigText = $this->getUserSig( $user );
4470  $text = strtr( $text, [
4471  '~~~~~' => $d,
4472  '~~~~' => "$sigText $d",
4473  '~~~' => $sigText
4474  ] );
4475  # The main two signature forms used above are time-sensitive
4476  $this->mOutput->setFlag( 'user-signature' );
4477  }
4478 
4479  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4480  $tc = '[' . Title::legalChars() . ']';
4481  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4482 
4483  // [[ns:page (context)|]]
4484  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4485  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4486  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4487  // [[ns:page (context), context|]] (using either single or double-width comma)
4488  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4489  // [[|page]] (reverse pipe trick: add context from page title)
4490  $p2 = "/\[\[\\|($tc+)]]/";
4491 
4492  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4493  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4494  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4495  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4496 
4497  $t = $this->mTitle->getText();
4498  $m = [];
4499  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4500  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4501  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4502  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4503  } else {
4504  # if there's no context, don't bother duplicating the title
4505  $text = preg_replace( $p2, '[[\\1]]', $text );
4506  }
4507 
4508  return $text;
4509  }
4510 
4525  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4526  global $wgMaxSigChars;
4527 
4528  $username = $user->getName();
4529 
4530  # If not given, retrieve from the user object.
4531  if ( $nickname === false ) {
4532  $nickname = $user->getOption( 'nickname' );
4533  }
4534 
4535  if ( is_null( $fancySig ) ) {
4536  $fancySig = $user->getBoolOption( 'fancysig' );
4537  }
4538 
4539  $nickname = $nickname == null ? $username : $nickname;
4540 
4541  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4542  $nickname = $username;
4543  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4544  } elseif ( $fancySig !== false ) {
4545  # Sig. might contain markup; validate this
4546  if ( $this->validateSig( $nickname ) !== false ) {
4547  # Validated; clean up (if needed) and return it
4548  return $this->cleanSig( $nickname, true );
4549  } else {
4550  # Failed to validate; fall back to the default
4551  $nickname = $username;
4552  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4553  }
4554  }
4555 
4556  # Make sure nickname doesnt get a sig in a sig
4557  $nickname = self::cleanSigInSig( $nickname );
4558 
4559  # If we're still here, make it a link to the user page
4560  $userText = wfEscapeWikiText( $username );
4561  $nickText = wfEscapeWikiText( $nickname );
4562  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4563 
4564  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4565  ->title( $this->getTitle() )->text();
4566  }
4567 
4574  public function validateSig( $text ) {
4575  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4576  }
4577 
4588  public function cleanSig( $text, $parsing = false ) {
4589  if ( !$parsing ) {
4590  global $wgTitle;
4591  $magicScopeVariable = $this->lock();
4592  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4593  }
4594 
4595  # Option to disable this feature
4596  if ( !$this->mOptions->getCleanSignatures() ) {
4597  return $text;
4598  }
4599 
4600  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4601  # => Move this logic to braceSubstitution()
4602  $substWord = MagicWord::get( 'subst' );
4603  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4604  $substText = '{{' . $substWord->getSynonym( 0 );
4605 
4606  $text = preg_replace( $substRegex, $substText, $text );
4607  $text = self::cleanSigInSig( $text );
4608  $dom = $this->preprocessToDom( $text );
4609  $frame = $this->getPreprocessor()->newFrame();
4610  $text = $frame->expand( $dom );
4611 
4612  if ( !$parsing ) {
4613  $text = $this->mStripState->unstripBoth( $text );
4614  }
4615 
4616  return $text;
4617  }
4618 
4625  public static function cleanSigInSig( $text ) {
4626  $text = preg_replace( '/~{3,5}/', '', $text );
4627  return $text;
4628  }
4629 
4640  $outputType, $clearState = true
4641  ) {
4642  $this->startParse( $title, $options, $outputType, $clearState );
4643  }
4644 
4651  private function startParse( Title $title = null, ParserOptions $options,
4652  $outputType, $clearState = true
4653  ) {
4654  $this->setTitle( $title );
4655  $this->mOptions = $options;
4656  $this->setOutputType( $outputType );
4657  if ( $clearState ) {
4658  $this->clearState();
4659  }
4660  }
4661 
4670  public function transformMsg( $text, $options, $title = null ) {
4671  static $executing = false;
4672 
4673  # Guard against infinite recursion
4674  if ( $executing ) {
4675  return $text;
4676  }
4677  $executing = true;
4678 
4679  if ( !$title ) {
4680  global $wgTitle;
4681  $title = $wgTitle;
4682  }
4683 
4684  $text = $this->preprocess( $text, $title, $options );
4685 
4686  $executing = false;
4687  return $text;
4688  }
4689 
4714  public function setHook( $tag, callable $callback ) {
4715  $tag = strtolower( $tag );
4716  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4717  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4718  }
4719  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4720  $this->mTagHooks[$tag] = $callback;
4721  if ( !in_array( $tag, $this->mStripList ) ) {
4722  $this->mStripList[] = $tag;
4723  }
4724 
4725  return $oldVal;
4726  }
4727 
4745  public function setTransparentTagHook( $tag, callable $callback ) {
4746  $tag = strtolower( $tag );
4747  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4748  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4749  }
4750  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4751  $this->mTransparentTagHooks[$tag] = $callback;
4752 
4753  return $oldVal;
4754  }
4755 
4759  public function clearTagHooks() {
4760  $this->mTagHooks = [];
4761  $this->mFunctionTagHooks = [];
4762  $this->mStripList = $this->mDefaultStripList;
4763  }
4764 
4808  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4810 
4811  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4812  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4813 
4814  # Add to function cache
4815  $mw = MagicWord::get( $id );
4816  if ( !$mw ) {
4817  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4818  }
4819 
4820  $synonyms = $mw->getSynonyms();
4821  $sensitive = intval( $mw->isCaseSensitive() );
4822 
4823  foreach ( $synonyms as $syn ) {
4824  # Case
4825  if ( !$sensitive ) {
4826  $syn = $wgContLang->lc( $syn );
4827  }
4828  # Add leading hash
4829  if ( !( $flags & self::SFH_NO_HASH ) ) {
4830  $syn = '#' . $syn;
4831  }
4832  # Remove trailing colon
4833  if ( substr( $syn, -1, 1 ) === ':' ) {
4834  $syn = substr( $syn, 0, -1 );
4835  }
4836  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4837  }
4838  return $oldVal;
4839  }
4840 
4846  public function getFunctionHooks() {
4847  return array_keys( $this->mFunctionHooks );
4848  }
4849 
4860  public function setFunctionTagHook( $tag, callable $callback, $flags ) {
4861  $tag = strtolower( $tag );
4862  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4863  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4864  }
4865  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4866  $this->mFunctionTagHooks[$tag] : null;
4867  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4868 
4869  if ( !in_array( $tag, $this->mStripList ) ) {
4870  $this->mStripList[] = $tag;
4871  }
4872 
4873  return $old;
4874  }
4875 
4883  public function replaceLinkHolders( &$text, $options = 0 ) {
4884  $this->mLinkHolders->replace( $text );
4885  }
4886 
4894  public function replaceLinkHoldersText( $text ) {
4895  return $this->mLinkHolders->replaceText( $text );
4896  }
4897 
4911  public function renderImageGallery( $text, $params ) {
4912  $mode = false;
4913  if ( isset( $params['mode'] ) ) {
4914  $mode = $params['mode'];
4915  }
4916 
4917  try {
4918  $ig = ImageGalleryBase::factory( $mode );
4919  } catch ( Exception $e ) {
4920  // If invalid type set, fallback to default.
4921  $ig = ImageGalleryBase::factory( false );
4922  }
4923 
4924  $ig->setContextTitle( $this->mTitle );
4925  $ig->setShowBytes( false );
4926  $ig->setShowDimensions( false );
4927  $ig->setShowFilename( false );
4928  $ig->setParser( $this );
4929  $ig->setHideBadImages();
4930  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
4931 
4932  if ( isset( $params['showfilename'] ) ) {
4933  $ig->setShowFilename( true );
4934  } else {
4935  $ig->setShowFilename( false );
4936  }
4937  if ( isset( $params['caption'] ) ) {
4938  $caption = $params['caption'];
4939  $caption = htmlspecialchars( $caption );
4940  $caption = $this->replaceInternalLinks( $caption );
4941  $ig->setCaptionHtml( $caption );
4942  }
4943  if ( isset( $params['perrow'] ) ) {
4944  $ig->setPerRow( $params['perrow'] );
4945  }
4946  if ( isset( $params['widths'] ) ) {
4947  $ig->setWidths( $params['widths'] );
4948  }
4949  if ( isset( $params['heights'] ) ) {
4950  $ig->setHeights( $params['heights'] );
4951  }
4952  $ig->setAdditionalOptions( $params );
4953 
4954  // Avoid PHP 7.1 warning from passing $this by reference
4955  $parser = $this;
4956  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
4957 
4958  $lines = StringUtils::explode( "\n", $text );
4959  foreach ( $lines as $line ) {
4960  # match lines like these:
4961  # Image:someimage.jpg|This is some image
4962  $matches = [];
4963  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4964  # Skip empty lines
4965  if ( count( $matches ) == 0 ) {
4966  continue;
4967  }
4968 
4969  if ( strpos( $matches[0], '%' ) !== false ) {
4970  $matches[1] = rawurldecode( $matches[1] );
4971  }
4973  if ( is_null( $title ) ) {
4974  # Bogus title. Ignore these so we don't bomb out later.
4975  continue;
4976  }
4977 
4978  # We need to get what handler the file uses, to figure out parameters.
4979  # Note, a hook can overide the file name, and chose an entirely different
4980  # file (which potentially could be of a different type and have different handler).
4981  $options = [];
4982  $descQuery = false;
4983  Hooks::run( 'BeforeParserFetchFileAndTitle',
4984  [ $this, $title, &$options, &$descQuery ] );
4985  # Don't register it now, as TraditionalImageGallery does that later.
4986  $file = $this->fetchFileNoRegister( $title, $options );
4987  $handler = $file ? $file->getHandler() : false;
4988 
4989  $paramMap = [
4990  'img_alt' => 'gallery-internal-alt',
4991  'img_link' => 'gallery-internal-link',
4992  ];
4993  if ( $handler ) {
4994  $paramMap = $paramMap + $handler->getParamMap();
4995  // We don't want people to specify per-image widths.
4996  // Additionally the width parameter would need special casing anyhow.
4997  unset( $paramMap['img_width'] );
4998  }
4999 
5000  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5001 
5002  $label = '';
5003  $alt = '';
5004  $link = '';
5005  $handlerOptions = [];
5006  if ( isset( $matches[3] ) ) {
5007  // look for an |alt= definition while trying not to break existing
5008  // captions with multiple pipes (|) in it, until a more sensible grammar
5009  // is defined for images in galleries
5010 
5011  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5012  // splitting on '|' is a bit odd, and different from makeImage.
5013  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5014  // Protect LanguageConverter markup
5015  $parameterMatches = StringUtils::delimiterExplode(
5016  '-{', '}-', '|', $matches[3], true /* nested */
5017  );
5018 
5019  foreach ( $parameterMatches as $parameterMatch ) {
5020  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5021  if ( $magicName ) {
5022  $paramName = $paramMap[$magicName];
5023 
5024  switch ( $paramName ) {
5025  case 'gallery-internal-alt':
5026  $alt = $this->stripAltText( $match, false );
5027  break;
5028  case 'gallery-internal-link':
5029  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5030  $chars = self::EXT_LINK_URL_CLASS;
5031  $addr = self::EXT_LINK_ADDR;
5032  $prots = $this->mUrlProtocols;
5033  // check to see if link matches an absolute url, if not then it must be a wiki link.
5034  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5035  // Result of LanguageConverter::markNoConversion
5036  // invoked on an external link.
5037  $linkValue = substr( $linkValue, 4, -2 );
5038  }
5039  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5040  $link = $linkValue;
5041  $this->mOutput->addExternalLink( $link );
5042  } else {
5043  $localLinkTitle = Title::newFromText( $linkValue );
5044  if ( $localLinkTitle !== null ) {
5045  $this->mOutput->addLink( $localLinkTitle );
5046  $link = $localLinkTitle->getLinkURL();
5047  }
5048  }
5049  break;
5050  default:
5051  // Must be a handler specific parameter.
5052  if ( $handler->validateParam( $paramName, $match ) ) {
5053  $handlerOptions[$paramName] = $match;
5054  } else {
5055  // Guess not, consider it as caption.
5056  wfDebug( "$parameterMatch failed parameter validation\n" );
5057  $label = '|' . $parameterMatch;
5058  }
5059  }
5060 
5061  } else {
5062  // Last pipe wins.
5063  $label = '|' . $parameterMatch;
5064  }
5065  }
5066  // Remove the pipe.
5067  $label = substr( $label, 1 );
5068  }
5069 
5070  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5071  }
5072  $html = $ig->toHTML();
5073  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5074  return $html;
5075  }
5076 
5081  public function getImageParams( $handler ) {
5082  if ( $handler ) {
5083  $handlerClass = get_class( $handler );
5084  } else {
5085  $handlerClass = '';
5086  }
5087  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5088  # Initialise static lists
5089  static $internalParamNames = [
5090  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5091  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5092  'bottom', 'text-bottom' ],
5093  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5094  'upright', 'border', 'link', 'alt', 'class' ],
5095  ];
5096  static $internalParamMap;
5097  if ( !$internalParamMap ) {
5098  $internalParamMap = [];
5099  foreach ( $internalParamNames as $type => $names ) {
5100  foreach ( $names as $name ) {
5101  // For grep: img_left, img_right, img_center, img_none,
5102  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5103  // img_bottom, img_text_bottom,
5104  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5105  // img_border, img_link, img_alt, img_class
5106  $magicName = str_replace( '-', '_', "img_$name" );
5107  $internalParamMap[$magicName] = [ $type, $name ];
5108  }
5109  }
5110  }
5111 
5112  # Add handler params
5113  $paramMap = $internalParamMap;
5114  if ( $handler ) {
5115  $handlerParamMap = $handler->getParamMap();
5116  foreach ( $handlerParamMap as $magic => $paramName ) {
5117  $paramMap[$magic] = [ 'handler', $paramName ];
5118  }
5119  }
5120  $this->mImageParams[$handlerClass] = $paramMap;
5121  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5122  }
5123  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5124  }
5125 
5134  public function makeImage( $title, $options, $holders = false ) {
5135  # Check if the options text is of the form "options|alt text"
5136  # Options are:
5137  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5138  # * left no resizing, just left align. label is used for alt= only
5139  # * right same, but right aligned
5140  # * none same, but not aligned
5141  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5142  # * center center the image
5143  # * frame Keep original image size, no magnify-button.
5144  # * framed Same as "frame"
5145  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5146  # * upright reduce width for upright images, rounded to full __0 px
5147  # * border draw a 1px border around the image
5148  # * alt Text for HTML alt attribute (defaults to empty)
5149  # * class Set a class for img node
5150  # * link Set the target of the image link. Can be external, interwiki, or local
5151  # vertical-align values (no % or length right now):
5152  # * baseline
5153  # * sub
5154  # * super
5155  # * top
5156  # * text-top
5157  # * middle
5158  # * bottom
5159  # * text-bottom
5160 
5161  # Protect LanguageConverter markup when splitting into parts
5163  '-{', '}-', '|', $options, true /* allow nesting */
5164  );
5165 
5166  # Give extensions a chance to select the file revision for us
5167  $options = [];
5168  $descQuery = false;
5169  Hooks::run( 'BeforeParserFetchFileAndTitle',
5170  [ $this, $title, &$options, &$descQuery ] );
5171  # Fetch and register the file (file title may be different via hooks)
5172  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5173 
5174  # Get parameter map
5175  $handler = $file ? $file->getHandler() : false;
5176 
5177  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5178 
5179  if ( !$file ) {
5180  $this->addTrackingCategory( 'broken-file-category' );
5181  }
5182 
5183  # Process the input parameters
5184  $caption = '';
5185  $params = [ 'frame' => [], 'handler' => [],
5186  'horizAlign' => [], 'vertAlign' => [] ];
5187  $seenformat = false;
5188  foreach ( $parts as $part ) {
5189  $part = trim( $part );
5190  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5191  $validated = false;
5192  if ( isset( $paramMap[$magicName] ) ) {
5193  list( $type, $paramName ) = $paramMap[$magicName];
5194 
5195  # Special case; width and height come in one variable together
5196  if ( $type === 'handler' && $paramName === 'width' ) {
5197  $parsedWidthParam = $this->parseWidthParam( $value );
5198  if ( isset( $parsedWidthParam['width'] ) ) {
5199  $width = $parsedWidthParam['width'];
5200  if ( $handler->validateParam( 'width', $width ) ) {
5201  $params[$type]['width'] = $width;
5202  $validated = true;
5203  }
5204  }
5205  if ( isset( $parsedWidthParam['height'] ) ) {
5206  $height = $parsedWidthParam['height'];
5207  if ( $handler->validateParam( 'height', $height ) ) {
5208  $params[$type]['height'] = $height;
5209  $validated = true;
5210  }
5211  }
5212  # else no validation -- T15436
5213  } else {
5214  if ( $type === 'handler' ) {
5215  # Validate handler parameter
5216  $validated = $handler->validateParam( $paramName, $value );
5217  } else {
5218  # Validate internal parameters
5219  switch ( $paramName ) {
5220  case 'manualthumb':
5221  case 'alt':
5222  case 'class':
5223  # @todo FIXME: Possibly check validity here for
5224  # manualthumb? downstream behavior seems odd with
5225  # missing manual thumbs.
5226  $validated = true;
5227  $value = $this->stripAltText( $value, $holders );
5228  break;
5229  case 'link':
5230  $chars = self::EXT_LINK_URL_CLASS;
5231  $addr = self::EXT_LINK_ADDR;
5232  $prots = $this->mUrlProtocols;
5233  if ( $value === '' ) {
5234  $paramName = 'no-link';
5235  $value = true;
5236  $validated = true;
5237  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5238  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5239  $paramName = 'link-url';
5240  $this->mOutput->addExternalLink( $value );
5241  if ( $this->mOptions->getExternalLinkTarget() ) {
5242  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5243  }
5244  $validated = true;
5245  }
5246  } else {
5247  $linkTitle = Title::newFromText( $value );
5248  if ( $linkTitle ) {
5249  $paramName = 'link-title';
5250  $value = $linkTitle;
5251  $this->mOutput->addLink( $linkTitle );
5252  $validated = true;
5253  }
5254  }
5255  break;
5256  case 'frameless':
5257  case 'framed':
5258  case 'thumbnail':
5259  // use first appearing option, discard others.
5260  $validated = !$seenformat;
5261  $seenformat = true;
5262  break;
5263  default:
5264  # Most other things appear to be empty or numeric...
5265  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5266  }
5267  }
5268 
5269  if ( $validated ) {
5270  $params[$type][$paramName] = $value;
5271  }
5272  }
5273  }
5274  if ( !$validated ) {
5275  $caption = $part;
5276  }
5277  }
5278 
5279  # Process alignment parameters
5280  if ( $params['horizAlign'] ) {
5281  $params['frame']['align'] = key( $params['horizAlign'] );
5282  }
5283  if ( $params['vertAlign'] ) {
5284  $params['frame']['valign'] = key( $params['vertAlign'] );
5285  }
5286 
5287  $params['frame']['caption'] = $caption;
5288 
5289  # Will the image be presented in a frame, with the caption below?
5290  $imageIsFramed = isset( $params['frame']['frame'] )
5291  || isset( $params['frame']['framed'] )
5292  || isset( $params['frame']['thumbnail'] )
5293  || isset( $params['frame']['manualthumb'] );
5294 
5295  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5296  # came to also set the caption, ordinary text after the image -- which
5297  # makes no sense, because that just repeats the text multiple times in
5298  # screen readers. It *also* came to set the title attribute.
5299  # Now that we have an alt attribute, we should not set the alt text to
5300  # equal the caption: that's worse than useless, it just repeats the
5301  # text. This is the framed/thumbnail case. If there's no caption, we
5302  # use the unnamed parameter for alt text as well, just for the time be-
5303  # ing, if the unnamed param is set and the alt param is not.
5304  # For the future, we need to figure out if we want to tweak this more,
5305  # e.g., introducing a title= parameter for the title; ignoring the un-
5306  # named parameter entirely for images without a caption; adding an ex-
5307  # plicit caption= parameter and preserving the old magic unnamed para-
5308  # meter for BC; ...
5309  if ( $imageIsFramed ) { # Framed image
5310  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5311  # No caption or alt text, add the filename as the alt text so
5312  # that screen readers at least get some description of the image
5313  $params['frame']['alt'] = $title->getText();
5314  }
5315  # Do not set $params['frame']['title'] because tooltips don't make sense
5316  # for framed images
5317  } else { # Inline image
5318  if ( !isset( $params['frame']['alt'] ) ) {
5319  # No alt text, use the "caption" for the alt text
5320  if ( $caption !== '' ) {
5321  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5322  } else {
5323  # No caption, fall back to using the filename for the
5324  # alt text
5325  $params['frame']['alt'] = $title->getText();
5326  }
5327  }
5328  # Use the "caption" for the tooltip text
5329  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5330  }
5331 
5332  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5333 
5334  # Linker does the rest
5335  $time = isset( $options['time'] ) ? $options['time'] : false;
5336  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5337  $time, $descQuery, $this->mOptions->getThumbSize() );
5338 
5339  # Give the handler a chance to modify the parser object
5340  if ( $handler ) {
5341  $handler->parserTransformHook( $this, $file );
5342  }
5343 
5344  return $ret;
5345  }
5346 
5352  protected function stripAltText( $caption, $holders ) {
5353  # Strip bad stuff out of the title (tooltip). We can't just use
5354  # replaceLinkHoldersText() here, because if this function is called
5355  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5356  if ( $holders ) {
5357  $tooltip = $holders->replaceText( $caption );
5358  } else {
5359  $tooltip = $this->replaceLinkHoldersText( $caption );
5360  }
5361 
5362  # make sure there are no placeholders in thumbnail attributes
5363  # that are later expanded to html- so expand them now and
5364  # remove the tags
5365  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5366  $tooltip = Sanitizer::stripAllTags( $tooltip );
5367 
5368  return $tooltip;
5369  }
5370 
5376  public function disableCache() {
5377  wfDebug( "Parser output marked as uncacheable.\n" );
5378  if ( !$this->mOutput ) {
5379  throw new MWException( __METHOD__ .
5380  " can only be called when actually parsing something" );
5381  }
5382  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5383  }
5384 
5393  public function attributeStripCallback( &$text, $frame = false ) {
5394  $text = $this->replaceVariables( $text, $frame );
5395  $text = $this->mStripState->unstripBoth( $text );
5396  return $text;
5397  }
5398 
5404  public function getTags() {
5405  return array_merge(
5406  array_keys( $this->mTransparentTagHooks ),
5407  array_keys( $this->mTagHooks ),
5408  array_keys( $this->mFunctionTagHooks )
5409  );
5410  }
5411 
5422  public function replaceTransparentTags( $text ) {
5423  $matches = [];
5424  $elements = array_keys( $this->mTransparentTagHooks );
5425  $text = self::extractTagsAndParams( $elements, $text, $matches );
5426  $replacements = [];
5427 
5428  foreach ( $matches as $marker => $data ) {
5429  list( $element, $content, $params, $tag ) = $data;
5430  $tagName = strtolower( $element );
5431  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5432  $output = call_user_func_array(
5433  $this->mTransparentTagHooks[$tagName],
5434  [ $content, $params, $this ]
5435  );
5436  } else {
5437  $output = $tag;
5438  }
5439  $replacements[$marker] = $output;
5440  }
5441  return strtr( $text, $replacements );
5442  }
5443 
5473  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5474  global $wgTitle; # not generally used but removes an ugly failure mode
5475 
5476  $magicScopeVariable = $this->lock();
5477  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5478  $outText = '';
5479  $frame = $this->getPreprocessor()->newFrame();
5480 
5481  # Process section extraction flags
5482  $flags = 0;
5483  $sectionParts = explode( '-', $sectionId );
5484  $sectionIndex = array_pop( $sectionParts );
5485  foreach ( $sectionParts as $part ) {
5486  if ( $part === 'T' ) {
5487  $flags |= self::PTD_FOR_INCLUSION;
5488  }
5489  }
5490 
5491  # Check for empty input
5492  if ( strval( $text ) === '' ) {
5493  # Only sections 0 and T-0 exist in an empty document
5494  if ( $sectionIndex == 0 ) {
5495  if ( $mode === 'get' ) {
5496  return '';
5497  } else {
5498  return $newText;
5499  }
5500  } else {
5501  if ( $mode === 'get' ) {
5502  return $newText;
5503  } else {
5504  return $text;
5505  }
5506  }
5507  }
5508 
5509  # Preprocess the text
5510  $root = $this->preprocessToDom( $text, $flags );
5511 
5512  # <h> nodes indicate section breaks
5513  # They can only occur at the top level, so we can find them by iterating the root's children
5514  $node = $root->getFirstChild();
5515 
5516  # Find the target section
5517  if ( $sectionIndex == 0 ) {
5518  # Section zero doesn't nest, level=big
5519  $targetLevel = 1000;
5520  } else {
5521  while ( $node ) {
5522  if ( $node->getName() === 'h' ) {
5523  $bits = $node->splitHeading();
5524  if ( $bits['i'] == $sectionIndex ) {
5525  $targetLevel = $bits['level'];
5526  break;
5527  }
5528  }
5529  if ( $mode === 'replace' ) {
5530  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5531  }
5532  $node = $node->getNextSibling();
5533  }
5534  }
5535 
5536  if ( !$node ) {
5537  # Not found
5538  if ( $mode === 'get' ) {
5539  return $newText;
5540  } else {
5541  return $text;
5542  }
5543  }
5544 
5545  # Find the end of the section, including nested sections
5546  do {
5547  if ( $node->getName() === 'h' ) {
5548  $bits = $node->splitHeading();
5549  $curLevel = $bits['level'];
5550  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5551  break;
5552  }
5553  }
5554  if ( $mode === 'get' ) {
5555  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5556  }
5557  $node = $node->getNextSibling();
5558  } while ( $node );
5559 
5560  # Write out the remainder (in replace mode only)
5561  if ( $mode === 'replace' ) {
5562  # Output the replacement text
5563  # Add two newlines on -- trailing whitespace in $newText is conventionally
5564  # stripped by the editor, so we need both newlines to restore the paragraph gap
5565  # Only add trailing whitespace if there is newText
5566  if ( $newText != "" ) {
5567  $outText .= $newText . "\n\n";
5568  }
5569 
5570  while ( $node ) {
5571  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5572  $node = $node->getNextSibling();
5573  }
5574  }
5575 
5576  if ( is_string( $outText ) ) {
5577  # Re-insert stripped tags
5578  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5579  }
5580 
5581  return $outText;
5582  }
5583 
5598  public function getSection( $text, $sectionId, $defaultText = '' ) {
5599  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5600  }
5601 
5614  public function replaceSection( $oldText, $sectionId, $newText ) {
5615  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5616  }
5617 
5623  public function getRevisionId() {
5624  return $this->mRevisionId;
5625  }
5626 
5633  public function getRevisionObject() {
5634  if ( !is_null( $this->mRevisionObject ) ) {
5635  return $this->mRevisionObject;
5636  }
5637  if ( is_null( $this->mRevisionId ) ) {
5638  return null;
5639  }
5640 
5641  $rev = call_user_func(
5642  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5643  );
5644 
5645  # If the parse is for a new revision, then the callback should have
5646  # already been set to force the object and should match mRevisionId.
5647  # If not, try to fetch by mRevisionId for sanity.
5648  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5649  $rev = Revision::newFromId( $this->mRevisionId );
5650  }
5651 
5652  $this->mRevisionObject = $rev;
5653 
5654  return $this->mRevisionObject;
5655  }
5656 
5662  public function getRevisionTimestamp() {
5663  if ( is_null( $this->mRevisionTimestamp ) ) {
5665 
5666  $revObject = $this->getRevisionObject();
5667  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5668 
5669  # The cryptic '' timezone parameter tells to use the site-default
5670  # timezone offset instead of the user settings.
5671  # Since this value will be saved into the parser cache, served
5672  # to other users, and potentially even used inside links and such,
5673  # it needs to be consistent for all visitors.
5674  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5675 
5676  }
5677  return $this->mRevisionTimestamp;
5678  }
5679 
5685  public function getRevisionUser() {
5686  if ( is_null( $this->mRevisionUser ) ) {
5687  $revObject = $this->getRevisionObject();
5688 
5689  # if this template is subst: the revision id will be blank,
5690  # so just use the current user's name
5691  if ( $revObject ) {
5692  $this->mRevisionUser = $revObject->getUserText();
5693  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5694  $this->mRevisionUser = $this->getUser()->getName();
5695  }
5696  }
5697  return $this->mRevisionUser;
5698  }
5699 
5705  public function getRevisionSize() {
5706  if ( is_null( $this->mRevisionSize ) ) {
5707  $revObject = $this->getRevisionObject();
5708 
5709  # if this variable is subst: the revision id will be blank,
5710  # so just use the parser input size, because the own substituation
5711  # will change the size.
5712  if ( $revObject ) {
5713  $this->mRevisionSize = $revObject->getSize();
5714  } else {
5715  $this->mRevisionSize = $this->mInputSize;
5716  }
5717  }
5718  return $this->mRevisionSize;
5719  }
5720 
5726  public function setDefaultSort( $sort ) {
5727  $this->mDefaultSort = $sort;
5728  $this->mOutput->setProperty( 'defaultsort', $sort );
5729  }
5730 
5741  public function getDefaultSort() {
5742  if ( $this->mDefaultSort !== false ) {
5743  return $this->mDefaultSort;
5744  } else {
5745  return '';
5746  }
5747  }
5748 
5755  public function getCustomDefaultSort() {
5756  return $this->mDefaultSort;
5757  }
5758 
5768  public function guessSectionNameFromWikiText( $text ) {
5769  # Strip out wikitext links(they break the anchor)
5770  $text = $this->stripSectionName( $text );
5772  $text = Sanitizer::decodeCharReferences( $text );
5773  $text = $this->normalizeSectionName( $text );
5774 
5775  return '#' . Sanitizer::escapeIdForLink( $text );
5776  }
5777 
5787  public function guessLegacySectionNameFromWikiText( $text ) {
5789 
5790  # Strip out wikitext links(they break the anchor)
5791  $text = $this->stripSectionName( $text );
5793  $text = Sanitizer::decodeCharReferences( $text );
5794  $text = $this->normalizeSectionName( $text );
5795 
5796  if ( isset( $wgFragmentMode[1] ) && $wgFragmentMode[1] === 'legacy' ) {
5797  // ForAttribute() and ForLink() are the same for legacy encoding
5799  } else {
5800  $id = Sanitizer::escapeIdForLink( $text );
5801  }
5802 
5803  return "#$id";
5804  }
5805 
5812  private function normalizeSectionName( $text ) {
5813  # T90902: ensure the same normalization is applied for IDs as to links
5814  $titleParser = MediaWikiServices::getInstance()->getTitleParser();
5815  try {
5816 
5817  $parts = $titleParser->splitTitleString( "#$text" );
5818  } catch ( MalformedTitleException $ex ) {
5819  return $text;
5820  }
5821  return $parts['fragment'];
5822  }
5823 
5838  public function stripSectionName( $text ) {
5839  # Strip internal link markup
5840  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5841  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5842 
5843  # Strip external link markup
5844  # @todo FIXME: Not tolerant to blank link text
5845  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5846  # on how many empty links there are on the page - need to figure that out.
5847  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5848 
5849  # Parse wikitext quotes (italics & bold)
5850  $text = $this->doQuotes( $text );
5851 
5852  # Strip HTML tags
5853  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5854  return $text;
5855  }
5856 
5867  public function testSrvus( $text, Title $title, ParserOptions $options,
5868  $outputType = self::OT_HTML
5869  ) {
5870  $magicScopeVariable = $this->lock();
5871  $this->startParse( $title, $options, $outputType, true );
5872 
5873  $text = $this->replaceVariables( $text );
5874  $text = $this->mStripState->unstripBoth( $text );
5875  $text = Sanitizer::removeHTMLtags( $text );
5876  return $text;
5877  }
5878 
5885  public function testPst( $text, Title $title, ParserOptions $options ) {
5886  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5887  }
5888 
5895  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5896  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5897  }
5898 
5915  public function markerSkipCallback( $s, $callback ) {
5916  $i = 0;
5917  $out = '';
5918  while ( $i < strlen( $s ) ) {
5919  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5920  if ( $markerStart === false ) {
5921  $out .= call_user_func( $callback, substr( $s, $i ) );
5922  break;
5923  } else {
5924  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5925  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5926  if ( $markerEnd === false ) {
5927  $out .= substr( $s, $markerStart );
5928  break;
5929  } else {
5930  $markerEnd += strlen( self::MARKER_SUFFIX );
5931  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5932  $i = $markerEnd;
5933  }
5934  }
5935  }
5936  return $out;
5937  }
5938 
5945  public function killMarkers( $text ) {
5946  return $this->mStripState->killMarkers( $text );
5947  }
5948 
5965  public function serializeHalfParsedText( $text ) {
5966  $data = [
5967  'text' => $text,
5968  'version' => self::HALF_PARSED_VERSION,
5969  'stripState' => $this->mStripState->getSubState( $text ),
5970  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5971  ];
5972  return $data;
5973  }
5974 
5990  public function unserializeHalfParsedText( $data ) {
5991  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5992  throw new MWException( __METHOD__ . ': invalid version' );
5993  }
5994 
5995  # First, extract the strip state.
5996  $texts = [ $data['text'] ];
5997  $texts = $this->mStripState->merge( $data['stripState'], $texts );
5998 
5999  # Now renumber links
6000  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6001 
6002  # Should be good to go.
6003  return $texts[0];
6004  }
6005 
6015  public function isValidHalfParsedText( $data ) {
6016  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6017  }
6018 
6027  public function parseWidthParam( $value ) {
6028  $parsedWidthParam = [];
6029  if ( $value === '' ) {
6030  return $parsedWidthParam;
6031  }
6032  $m = [];
6033  # (T15500) In both cases (width/height and width only),
6034  # permit trailing "px" for backward compatibility.
6035  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6036  $width = intval( $m[1] );
6037  $height = intval( $m[2] );
6038  $parsedWidthParam['width'] = $width;
6039  $parsedWidthParam['height'] = $height;
6040  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6041  $width = intval( $value );
6042  $parsedWidthParam['width'] = $width;
6043  }
6044  return $parsedWidthParam;
6045  }
6046 
6056  protected function lock() {
6057  if ( $this->mInParse ) {
6058  throw new MWException( "Parser state cleared while parsing. "
6059  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6060  }
6061 
6062  // Save the backtrace when locking, so that if some code tries locking again,
6063  // we can print the lock owner's backtrace for easier debugging
6064  $e = new Exception;
6065  $this->mInParse = $e->getTraceAsString();
6066 
6067  $recursiveCheck = new ScopedCallback( function () {
6068  $this->mInParse = false;
6069  } );
6070 
6071  return $recursiveCheck;
6072  }
6073 
6084  public static function stripOuterParagraph( $html ) {
6085  $m = [];
6086  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6087  if ( strpos( $m[1], '</p>' ) === false ) {
6088  $html = $m[1];
6089  }
6090  }
6091 
6092  return $html;
6093  }
6094 
6105  public function getFreshParser() {
6106  global $wgParserConf;
6107  if ( $this->mInParse ) {
6108  return new $wgParserConf['class']( $wgParserConf );
6109  } else {
6110  return $this;
6111  }
6112  }
6113 
6120  public function enableOOUI() {
6122  $this->mOutput->setEnableOOUI( true );
6123  }
6124 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5633
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:551
setTitle($t)
Set the context title.
Definition: Parser.php:754
$mAutonumber
Definition: Parser.php:179
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3421
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5915
$mPPNodeCount
Definition: Parser.php:193
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2085
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:291
getExternalLinkAttribs($url)
Get an associative array of additional HTML attributes appropriate for a particular external link...
Definition: Parser.php:1924
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:6015
null means default in associative array form
Definition: hooks.txt:1968
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:1968
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1559
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:5598
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1427
$mTplRedirCache
Definition: Parser.php:195
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5945
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:1571
LinkRenderer $mLinkRenderer
Definition: Parser.php:259
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3511
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:784
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5685
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:5838
const OT_PREPROCESS
Definition: Defines.php:187
either a plain
Definition: hooks.txt:2029
$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:2558
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Group all the pieces relevant to the context of a request into one instance.
getPreloadText($text, Title $title, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:724
setTransparentTagHook($tag, callable $callback)
As setHook(), but letting the contents be parsed.
Definition: Parser.php:4745
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4574
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:3332
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4911
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:1828
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
const ID_PRIMARY
Tells escapeUrlForHtml() to encode the ID using the wiki's primary encoding.
Definition: Sanitizer.php:64
nextLinkID()
Definition: Parser.php:843
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:1968
getImageParams($handler)
Definition: Parser.php:5081
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:1607
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:5404
static isWellFormedXmlFragment($text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:730
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:2144
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3656
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:2829
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:4846
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:1100
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:858
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2201
setFunctionTagHook($tag, callable $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4860
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3759
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5867
const TOC_START
Definition: Parser.php:139
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:782
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
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3681
null for the local wiki Added in
Definition: hooks.txt:1584
There are three types of nodes:
$mHeadings
Definition: Parser.php:195
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4759
static makeSelfLinkObj($nt, $html= '', $query= '', $trail= '', $prefix= '')
Make appropriate markup for a link to the current article.
Definition: Linker.php:186
const NS_SPECIAL
Definition: Defines.php:54
clearState()
Clear Parser state.
Definition: Parser.php: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:3700
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4445
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
Definition: Parser.php:5787
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:836
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2805
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5965
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4883
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3494
static activeUsers()
Definition: SiteStats.php:170
$mLinkID
Definition: Parser.php:192
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1640
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2859
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:2981
static cleanUrl($url)
Definition: Sanitizer.php:2012
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:268
$mGeneratedPPNodeCount
Definition: Parser.php:193
$mRevisionId
Definition: Parser.php:219
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:2456
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:808
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:925
const NS_TEMPLATE
Definition: Defines.php:75
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target...
Definition: Revision.php:134
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2471
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:636
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2558
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:763
const SFH_NO_HASH
Definition: Parser.php:85
const DB_MASTER
Definition: defines.php:26
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:25
$mForceTocPosition
Definition: Parser.php: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:314
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5623
const OT_PREPROCESS
Definition: Parser.php:114
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2444
$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:4894
setLinkID($id)
Definition: Parser.php:850
$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:2933
$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:1966
if($line===false) $args
Definition: cdb.php:63
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
const ID_FALLBACK
Tells escapeUrlForHtml() to encode the ID using the fallback encoding, or return false if no fallback...
Definition: Sanitizer.php:72
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:327
static splitTrail($trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition: Linker.php:1650
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3428
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2986
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:52
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1640
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4588
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:138
$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:4639
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:3992
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2072
$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:5376
$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:1521
internalParse($text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1253
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:5662
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6084
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:1359
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6027
$mStripList
Definition: Parser.php:148
$mFunctionTagHooks
Definition: Parser.php:147
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3719
normalizeSectionName($text)
Apply the same normalization as code making links to this section would.
Definition: Parser.php:5812
const OT_PLAIN
Definition: Defines.php:189
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3471
$mRevisionTimestamp
Definition: Parser.php:220
$mImageParams
Definition: Parser.php:151
stripAltText($caption, $holders)
Definition: Parser.php:5352
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1623
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:1990
const VERSION
Update this version number when the ParserOutput format changes in an incompatible way...
Definition: Parser.php:76
const OT_WIKI
Definition: Defines.php:186
Preprocessor $mPreprocessor
Definition: Parser.php:172
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:911
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of normal(non-web) applications--they might conflict with distributors'policies
static getInstance($ts=false)
Get a timestamp instance in GMT.
Definition: MWTimestamp.php:39
const NS_MEDIA
Definition: Defines.php:53
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
replaceVariables($text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2904
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:899
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3912
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1023
const EXT_IMAGE_REGEX
Definition: Parser.php:100
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4651
$params
const NS_CATEGORY
Definition: Defines.php:79
static makeHeadline($level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1627
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1968
$wgFragmentMode
How should section IDs be encoded? This array can contain 1 or 2 elements, each of them can be one of...
setHook($tag, callable $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4714
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:1050
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:5705
$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:1968
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:4410
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:871
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:935
static newKnownCurrent(IDatabase $db, $pageId, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:2090
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:4012
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:889
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1520
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
static tocLine($anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1535
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:4525
const HALF_PARSED_VERSION
Update this version number when the output of serialiseHalfParsedText() changes in an incompatible wa...
Definition: Parser.php:82
const NS_FILE
Definition: Defines.php:71
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
Definition: Parser.php: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:324
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:1968
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:2422
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:1753
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2871
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:791
$mTagHooks
Definition: Parser.php:143
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:73
static & get($id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:277
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6120
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3539
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:2013
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2431
const OT_HTML
Definition: Defines.php:185
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:304
static images()
Definition: SiteStats.php:178
$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:4670
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:843
__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:1903
getUser()
Current user.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
this hook is for auditing only $req
Definition: hooks.txt:991
static escapeIdForAttribute($id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
Definition: Sanitizer.php:1249
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:784
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc next in line in page history
Definition: hooks.txt:1753
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:5422
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:5614
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3939
$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:477
$lines
Definition: router.php:67
testPreprocess($text, Title $title, ParserOptions $options)
Definition: Parser.php:5895
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:1540
setFunctionHook($id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4808
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3334
$wgScriptPath
The path we should point to.
Variant of the Message class.
Definition: RawMessage.php:34
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6105
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:146
$mRevisionUser
Definition: Parser.php:221
lock()
Lock the current instance of the parser.
Definition: Parser.php:6056
static pages()
Definition: SiteStats.php:154
$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:2398
static escapeIdForLink($id)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid URL...
Definition: Sanitizer.php:1276
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3552
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
Definition: design.txt:79
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php:783
$mIncludeCount
Definition: Parser.php: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:2986
$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:2584
getTitle()
Accessor for the Title object.
Definition: Parser.php:772
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:5473
ParserOutput $mOutput
Definition: Parser.php:178
getOutput()
Get the ParserOutput object.
Definition: Parser.php:817
doMagicLinks($text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
Definition: Parser.php:1429
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:4625
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5726
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3645
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1509
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:618
Status::newGood()`to allow deletion, and then`return false`from the hook function.Ensure you consume the 'ChangeTagAfterDelete'hook to carry out custom deletion actions.$tag:name of the tag $user:user initiating the action &$status:Status object.See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use.&$tags:list of all active tags.Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function.Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change.&$allowedTags:List of all the tags the user is allowed to add.Any tags the user wants to add($addTags) that are not in this array will cause it to fail.You may add or remove tags to this array as required.$addTags:List of tags user intends to add.$user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed.$performer:The User who will perform the change $user:The User whose groups will be changed &$add:The groups that will be added &$remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation.$collationName:Name of the collation in question &$collationObject:Null.Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user's email has been confirmed successfully.$user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object.Called by Content::getParserOutput after parsing has finished.Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML).$content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput.May be used to override the normal model-specific rendering of page content.$content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering.To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state.$generateHtml:boolean, indicating whether full HTML should be generated.If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object.&$output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title.May be used to assign a different model for that title.$title:the Title in question &$model:the model name.Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers.Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core.$modeName:the requested content model name &$handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page.This is especially useful to prevent some content models to be used in some special location.$contentModel:ID of the content model in question $title:the Title in question.&$ok:Output parameter, whether it is OK to use $contentModel on $title.Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run &$pager:Pager object for contributions &$queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions &$data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions &$ret:the HTML line $row:the DB row for this line &$classes:the classes to add to the surrounding< li > &$attribs:associative array of other HTML attributes for the< li > element.Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title &$tools:Array of tool links $specialPage:SpecialPage instance for context and services.Can be either SpecialContributions or DeletedContributionsPage.Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested.Handler functions that modify $result should generally return false to disable further attempts at conversion.$content:The Content object to be converted.$toModel:The ID of the content model to convert to.$lossy:boolean indicating whether lossy conversion is allowed.&$result:Output parameter, in case the handler function wants to provide a converted Content object.Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g.for a special namespace, etc.$article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery &$data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished.Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions &$ret:the HTML line $row:the DB row for this line &$classes:the classes to add to the surrounding< li > &$attribs:associative array of other HTML attributes for the< li > element.Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function.$differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable's value is null.This hook can be used to inject content into said class member variable.$differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the"mark as patrolled"link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink".$differenceEngine:DifferenceEngine object &$markAsPatrolledLink:The"mark as patrolled"link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter.For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions.&$rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision's author, whether the revision was marked as a minor edit or not, etc.$differenceEngine:DifferenceEngine object &$newHeader:The string containing the various#mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1248
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all.It could be easily changed to send incrementally if that becomes useful
static doBlockLevels($text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
$wgServer
URL of the server.
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:84
incrementExpensiveFunctionCount()
Increment the expensive function count.
Definition: Parser.php:3926
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:1954
const DB_REPLICA
Definition: defines.php:25
magicLinkCallback($m)
Definition: Parser.php:1460
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:1036
static stripAllTags($html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
Definition: Sanitizer.php:1974
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5885
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.
if(!$wgRequest->checkUrlExtension()) if(isset($_SERVER['PATH_INFO'])&&$_SERVER['PATH_INFO']!= '') if(!$wgEnableAPI) $wgTitle
Definition: api.php:68
static explode($separator, $subject)
Workalike for explode() with limited memory usage.
ParserOptions $mOptions
Definition: Parser.php:210
parse($text, Title $title, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
Definition: Parser.php:405
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:784
static numberingroup($group)
Find the number of users in a given user group.
Definition: SiteStats.php:188
=Architecture==Two class hierarchies are used to provide the functionality associated with the different content models:*Content interface(and AbstractContent base class) define functionality that acts on the concrete content of a page, and *ContentHandler base class provides functionality specific to a content model, but not acting on concrete content.The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content.These Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text.All manipulation and analysis of page content must be done via the appropriate methods of the Content object.For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers.The ContentHandler object for a given content model can be obtained using ContentHandler::getForModelID($id).Also Title, WikiPage and Revision now have getContentHandler() methods for convenience.ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting on the content of some page.ContentHandler::makeEmptyContent() and Cont