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 );
2303  // Special case: strip newlines when only thing between
2304  // this link and next are newlines
2305  $s .= trim( $trail, "\n" ) === '' ? '' : $trail;
2306  continue;
2307  }
2308 
2309  if ( $ns == NS_FILE ) {
2310  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2311  if ( $wasblank ) {
2312  # if no parameters were passed, $text
2313  # becomes something like "File:Foo.png",
2314  # which we don't want to pass on to the
2315  # image generator
2316  $text = '';
2317  } else {
2318  # recursively parse links inside the image caption
2319  # actually, this will parse them in any other parameters, too,
2320  # but it might be hard to fix that, and it doesn't matter ATM
2321  $text = $this->replaceExternalLinks( $text );
2322  $holders->merge( $this->replaceInternalLinks2( $text ) );
2323  }
2324  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2325  $s .= $prefix . $this->armorLinks(
2326  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2327  continue;
2328  }
2329  } elseif ( $ns == NS_CATEGORY ) {
2333  $s = rtrim( $s . $prefix ); # T2087, T87753
2334  // Special case: strip newlines when only thing between
2335  // this link and next are newlines
2336  $s .= trim( $trail, "\n" ) === '' ? '' : $trail;
2337 
2338  if ( $wasblank ) {
2339  $sortkey = $this->getDefaultSort();
2340  } else {
2341  $sortkey = $text;
2342  }
2343  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2344  $sortkey = str_replace( "\n", '', $sortkey );
2345  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2346  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2347 
2348  continue;
2349  }
2350  }
2351 
2352  # Self-link checking. For some languages, variants of the title are checked in
2353  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2354  # for linking to a different variant.
2355  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2356  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2357  continue;
2358  }
2359 
2360  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2361  # @todo FIXME: Should do batch file existence checks, see comment below
2362  if ( $ns == NS_MEDIA ) {
2363  # Give extensions a chance to select the file revision for us
2364  $options = [];
2365  $descQuery = false;
2366  Hooks::run( 'BeforeParserFetchFileAndTitle',
2367  [ $this, $nt, &$options, &$descQuery ] );
2368  # Fetch and register the file (file title may be different via hooks)
2369  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2370  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2371  $s .= $prefix . $this->armorLinks(
2372  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2373  continue;
2374  }
2375 
2376  # Some titles, such as valid special pages or files in foreign repos, should
2377  # be shown as bluelinks even though they're not included in the page table
2378  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2379  # batch file existence checks for NS_FILE and NS_MEDIA
2380  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2381  $this->mOutput->addLink( $nt );
2382  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2383  } else {
2384  # Links will be added to the output link list after checking
2385  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2386  }
2387  }
2388  return $holders;
2389  }
2390 
2404  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2405  list( $inside, $trail ) = Linker::splitTrail( $trail );
2406 
2407  if ( $text == '' ) {
2408  $text = htmlspecialchars( $nt->getPrefixedText() );
2409  }
2410 
2411  $link = $this->getLinkRenderer()->makeKnownLink(
2412  $nt, new HtmlArmor( "$prefix$text$inside" )
2413  );
2414 
2415  return $this->armorLinks( $link ) . $trail;
2416  }
2417 
2428  public function armorLinks( $text ) {
2429  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2430  self::MARKER_PREFIX . "NOPARSE$1", $text );
2431  }
2432 
2437  public function areSubpagesAllowed() {
2438  # Some namespaces don't allow subpages
2439  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2440  }
2441 
2450  public function maybeDoSubpageLink( $target, &$text ) {
2451  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2452  }
2453 
2462  public function doBlockLevels( $text, $linestart ) {
2463  return BlockLevelPass::doBlockLevels( $text, $linestart );
2464  }
2465 
2477  public function getVariableValue( $index, $frame = false ) {
2480 
2481  if ( is_null( $this->mTitle ) ) {
2482  // If no title set, bad things are going to happen
2483  // later. Title should always be set since this
2484  // should only be called in the middle of a parse
2485  // operation (but the unit-tests do funky stuff)
2486  throw new MWException( __METHOD__ . ' Should only be '
2487  . ' called while parsing (no title set)' );
2488  }
2489 
2490  // Avoid PHP 7.1 warning from passing $this by reference
2491  $parser = $this;
2492 
2497  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2498  if ( isset( $this->mVarCache[$index] ) ) {
2499  return $this->mVarCache[$index];
2500  }
2501  }
2502 
2503  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2504  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2505 
2506  $pageLang = $this->getFunctionLang();
2507 
2508  switch ( $index ) {
2509  case '!':
2510  $value = '|';
2511  break;
2512  case 'currentmonth':
2513  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2514  break;
2515  case 'currentmonth1':
2516  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2517  break;
2518  case 'currentmonthname':
2519  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2520  break;
2521  case 'currentmonthnamegen':
2522  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2523  break;
2524  case 'currentmonthabbrev':
2525  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2526  break;
2527  case 'currentday':
2528  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2529  break;
2530  case 'currentday2':
2531  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2532  break;
2533  case 'localmonth':
2534  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2535  break;
2536  case 'localmonth1':
2537  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2538  break;
2539  case 'localmonthname':
2540  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2541  break;
2542  case 'localmonthnamegen':
2543  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2544  break;
2545  case 'localmonthabbrev':
2546  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2547  break;
2548  case 'localday':
2549  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2550  break;
2551  case 'localday2':
2552  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2553  break;
2554  case 'pagename':
2555  $value = wfEscapeWikiText( $this->mTitle->getText() );
2556  break;
2557  case 'pagenamee':
2558  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2559  break;
2560  case 'fullpagename':
2561  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2562  break;
2563  case 'fullpagenamee':
2564  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2565  break;
2566  case 'subpagename':
2567  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2568  break;
2569  case 'subpagenamee':
2570  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2571  break;
2572  case 'rootpagename':
2573  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2574  break;
2575  case 'rootpagenamee':
2576  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2577  ' ',
2578  '_',
2579  $this->mTitle->getRootText()
2580  ) ) );
2581  break;
2582  case 'basepagename':
2583  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2584  break;
2585  case 'basepagenamee':
2586  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2587  ' ',
2588  '_',
2589  $this->mTitle->getBaseText()
2590  ) ) );
2591  break;
2592  case 'talkpagename':
2593  if ( $this->mTitle->canHaveTalkPage() ) {
2594  $talkPage = $this->mTitle->getTalkPage();
2595  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2596  } else {
2597  $value = '';
2598  }
2599  break;
2600  case 'talkpagenamee':
2601  if ( $this->mTitle->canHaveTalkPage() ) {
2602  $talkPage = $this->mTitle->getTalkPage();
2603  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2604  } else {
2605  $value = '';
2606  }
2607  break;
2608  case 'subjectpagename':
2609  $subjPage = $this->mTitle->getSubjectPage();
2610  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2611  break;
2612  case 'subjectpagenamee':
2613  $subjPage = $this->mTitle->getSubjectPage();
2614  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2615  break;
2616  case 'pageid': // requested in T25427
2617  $pageid = $this->getTitle()->getArticleID();
2618  if ( $pageid == 0 ) {
2619  # 0 means the page doesn't exist in the database,
2620  # which means the user is previewing a new page.
2621  # The vary-revision flag must be set, because the magic word
2622  # will have a different value once the page is saved.
2623  $this->mOutput->setFlag( 'vary-revision' );
2624  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2625  }
2626  $value = $pageid ? $pageid : null;
2627  break;
2628  case 'revisionid':
2629  # Let the edit saving system know we should parse the page
2630  # *after* a revision ID has been assigned.
2631  $this->mOutput->setFlag( 'vary-revision-id' );
2632  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2633  $value = $this->mRevisionId;
2634  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2635  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2636  $this->mOutput->setSpeculativeRevIdUsed( $value );
2637  }
2638  break;
2639  case 'revisionday':
2640  # Let the edit saving system know we should parse the page
2641  # *after* a revision ID has been assigned. This is for null edits.
2642  $this->mOutput->setFlag( 'vary-revision' );
2643  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2644  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2645  break;
2646  case 'revisionday2':
2647  # Let the edit saving system know we should parse the page
2648  # *after* a revision ID has been assigned. This is for null edits.
2649  $this->mOutput->setFlag( 'vary-revision' );
2650  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2651  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2652  break;
2653  case 'revisionmonth':
2654  # Let the edit saving system know we should parse the page
2655  # *after* a revision ID has been assigned. This is for null edits.
2656  $this->mOutput->setFlag( 'vary-revision' );
2657  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2658  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2659  break;
2660  case 'revisionmonth1':
2661  # Let the edit saving system know we should parse the page
2662  # *after* a revision ID has been assigned. This is for null edits.
2663  $this->mOutput->setFlag( 'vary-revision' );
2664  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2665  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2666  break;
2667  case 'revisionyear':
2668  # Let the edit saving system know we should parse the page
2669  # *after* a revision ID has been assigned. This is for null edits.
2670  $this->mOutput->setFlag( 'vary-revision' );
2671  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2672  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2673  break;
2674  case 'revisiontimestamp':
2675  # Let the edit saving system know we should parse the page
2676  # *after* a revision ID has been assigned. This is for null edits.
2677  $this->mOutput->setFlag( 'vary-revision' );
2678  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2679  $value = $this->getRevisionTimestamp();
2680  break;
2681  case 'revisionuser':
2682  # Let the edit saving system know we should parse the page
2683  # *after* a revision ID has been assigned for null edits.
2684  $this->mOutput->setFlag( 'vary-user' );
2685  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2686  $value = $this->getRevisionUser();
2687  break;
2688  case 'revisionsize':
2689  $value = $this->getRevisionSize();
2690  break;
2691  case 'namespace':
2692  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2693  break;
2694  case 'namespacee':
2695  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2696  break;
2697  case 'namespacenumber':
2698  $value = $this->mTitle->getNamespace();
2699  break;
2700  case 'talkspace':
2701  $value = $this->mTitle->canHaveTalkPage()
2702  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2703  : '';
2704  break;
2705  case 'talkspacee':
2706  $value = $this->mTitle->canHaveTalkPage() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2707  break;
2708  case 'subjectspace':
2709  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2710  break;
2711  case 'subjectspacee':
2712  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2713  break;
2714  case 'currentdayname':
2715  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2716  break;
2717  case 'currentyear':
2718  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2719  break;
2720  case 'currenttime':
2721  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2722  break;
2723  case 'currenthour':
2724  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2725  break;
2726  case 'currentweek':
2727  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2728  # int to remove the padding
2729  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2730  break;
2731  case 'currentdow':
2732  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2733  break;
2734  case 'localdayname':
2735  $value = $pageLang->getWeekdayName(
2736  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2737  );
2738  break;
2739  case 'localyear':
2740  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2741  break;
2742  case 'localtime':
2743  $value = $pageLang->time(
2744  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2745  false,
2746  false
2747  );
2748  break;
2749  case 'localhour':
2750  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2751  break;
2752  case 'localweek':
2753  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2754  # int to remove the padding
2755  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2756  break;
2757  case 'localdow':
2758  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2759  break;
2760  case 'numberofarticles':
2761  $value = $pageLang->formatNum( SiteStats::articles() );
2762  break;
2763  case 'numberoffiles':
2764  $value = $pageLang->formatNum( SiteStats::images() );
2765  break;
2766  case 'numberofusers':
2767  $value = $pageLang->formatNum( SiteStats::users() );
2768  break;
2769  case 'numberofactiveusers':
2770  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2771  break;
2772  case 'numberofpages':
2773  $value = $pageLang->formatNum( SiteStats::pages() );
2774  break;
2775  case 'numberofadmins':
2776  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2777  break;
2778  case 'numberofedits':
2779  $value = $pageLang->formatNum( SiteStats::edits() );
2780  break;
2781  case 'currenttimestamp':
2782  $value = wfTimestamp( TS_MW, $ts );
2783  break;
2784  case 'localtimestamp':
2785  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2786  break;
2787  case 'currentversion':
2789  break;
2790  case 'articlepath':
2791  return $wgArticlePath;
2792  case 'sitename':
2793  return $wgSitename;
2794  case 'server':
2795  return $wgServer;
2796  case 'servername':
2797  return $wgServerName;
2798  case 'scriptpath':
2799  return $wgScriptPath;
2800  case 'stylepath':
2801  return $wgStylePath;
2802  case 'directionmark':
2803  return $pageLang->getDirMark();
2804  case 'contentlanguage':
2806  return $wgLanguageCode;
2807  case 'pagelanguage':
2808  $value = $pageLang->getCode();
2809  break;
2810  case 'cascadingsources':
2812  break;
2813  default:
2814  $ret = null;
2815  Hooks::run(
2816  'ParserGetVariableValueSwitch',
2817  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2818  );
2819 
2820  return $ret;
2821  }
2822 
2823  if ( $index ) {
2824  $this->mVarCache[$index] = $value;
2825  }
2826 
2827  return $value;
2828  }
2829 
2835  public function initialiseVariables() {
2836  $variableIDs = MagicWord::getVariableIDs();
2837  $substIDs = MagicWord::getSubstIDs();
2838 
2839  $this->mVariables = new MagicWordArray( $variableIDs );
2840  $this->mSubstWords = new MagicWordArray( $substIDs );
2841  }
2842 
2865  public function preprocessToDom( $text, $flags = 0 ) {
2866  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2867  return $dom;
2868  }
2869 
2877  public static function splitWhitespace( $s ) {
2878  $ltrimmed = ltrim( $s );
2879  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2880  $trimmed = rtrim( $ltrimmed );
2881  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2882  if ( $diff > 0 ) {
2883  $w2 = substr( $ltrimmed, -$diff );
2884  } else {
2885  $w2 = '';
2886  }
2887  return [ $w1, $trimmed, $w2 ];
2888  }
2889 
2910  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2911  # Is there any text? Also, Prevent too big inclusions!
2912  $textSize = strlen( $text );
2913  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2914  return $text;
2915  }
2916 
2917  if ( $frame === false ) {
2918  $frame = $this->getPreprocessor()->newFrame();
2919  } elseif ( !( $frame instanceof PPFrame ) ) {
2920  wfDebug( __METHOD__ . " called using plain parameters instead of "
2921  . "a PPFrame instance. Creating custom frame.\n" );
2922  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2923  }
2924 
2925  $dom = $this->preprocessToDom( $text );
2926  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2927  $text = $frame->expand( $dom, $flags );
2928 
2929  return $text;
2930  }
2931 
2939  public static function createAssocArgs( $args ) {
2940  $assocArgs = [];
2941  $index = 1;
2942  foreach ( $args as $arg ) {
2943  $eqpos = strpos( $arg, '=' );
2944  if ( $eqpos === false ) {
2945  $assocArgs[$index++] = $arg;
2946  } else {
2947  $name = trim( substr( $arg, 0, $eqpos ) );
2948  $value = trim( substr( $arg, $eqpos + 1 ) );
2949  if ( $value === false ) {
2950  $value = '';
2951  }
2952  if ( $name !== false ) {
2953  $assocArgs[$name] = $value;
2954  }
2955  }
2956  }
2957 
2958  return $assocArgs;
2959  }
2960 
2987  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2988  # does no harm if $current and $max are present but are unnecessary for the message
2989  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2990  # only during preview, and that would split the parser cache unnecessarily.
2991  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
2992  ->text();
2993  $this->mOutput->addWarning( $warning );
2994  $this->addTrackingCategory( "$limitationType-category" );
2995  }
2996 
3009  public function braceSubstitution( $piece, $frame ) {
3010  // Flags
3011 
3012  // $text has been filled
3013  $found = false;
3014  // wiki markup in $text should be escaped
3015  $nowiki = false;
3016  // $text is HTML, armour it against wikitext transformation
3017  $isHTML = false;
3018  // Force interwiki transclusion to be done in raw mode not rendered
3019  $forceRawInterwiki = false;
3020  // $text is a DOM node needing expansion in a child frame
3021  $isChildObj = false;
3022  // $text is a DOM node needing expansion in the current frame
3023  $isLocalObj = false;
3024 
3025  # Title object, where $text came from
3026  $title = false;
3027 
3028  # $part1 is the bit before the first |, and must contain only title characters.
3029  # Various prefixes will be stripped from it later.
3030  $titleWithSpaces = $frame->expand( $piece['title'] );
3031  $part1 = trim( $titleWithSpaces );
3032  $titleText = false;
3033 
3034  # Original title text preserved for various purposes
3035  $originalTitle = $part1;
3036 
3037  # $args is a list of argument nodes, starting from index 0, not including $part1
3038  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3039  # below won't work b/c this $args isn't an object
3040  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3041 
3042  $profileSection = null; // profile templates
3043 
3044  # SUBST
3045  if ( !$found ) {
3046  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3047 
3048  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3049  # Decide whether to expand template or keep wikitext as-is.
3050  if ( $this->ot['wiki'] ) {
3051  if ( $substMatch === false ) {
3052  $literal = true; # literal when in PST with no prefix
3053  } else {
3054  $literal = false; # expand when in PST with subst: or safesubst:
3055  }
3056  } else {
3057  if ( $substMatch == 'subst' ) {
3058  $literal = true; # literal when not in PST with plain subst:
3059  } else {
3060  $literal = false; # expand when not in PST with safesubst: or no prefix
3061  }
3062  }
3063  if ( $literal ) {
3064  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3065  $isLocalObj = true;
3066  $found = true;
3067  }
3068  }
3069 
3070  # Variables
3071  if ( !$found && $args->getLength() == 0 ) {
3072  $id = $this->mVariables->matchStartToEnd( $part1 );
3073  if ( $id !== false ) {
3074  $text = $this->getVariableValue( $id, $frame );
3075  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3076  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3077  }
3078  $found = true;
3079  }
3080  }
3081 
3082  # MSG, MSGNW and RAW
3083  if ( !$found ) {
3084  # Check for MSGNW:
3085  $mwMsgnw = MagicWord::get( 'msgnw' );
3086  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3087  $nowiki = true;
3088  } else {
3089  # Remove obsolete MSG:
3090  $mwMsg = MagicWord::get( 'msg' );
3091  $mwMsg->matchStartAndRemove( $part1 );
3092  }
3093 
3094  # Check for RAW:
3095  $mwRaw = MagicWord::get( 'raw' );
3096  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3097  $forceRawInterwiki = true;
3098  }
3099  }
3100 
3101  # Parser functions
3102  if ( !$found ) {
3103  $colonPos = strpos( $part1, ':' );
3104  if ( $colonPos !== false ) {
3105  $func = substr( $part1, 0, $colonPos );
3106  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3107  $argsLength = $args->getLength();
3108  for ( $i = 0; $i < $argsLength; $i++ ) {
3109  $funcArgs[] = $args->item( $i );
3110  }
3111  try {
3112  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3113  } catch ( Exception $ex ) {
3114  throw $ex;
3115  }
3116 
3117  # The interface for parser functions allows for extracting
3118  # flags into the local scope. Extract any forwarded flags
3119  # here.
3120  extract( $result );
3121  }
3122  }
3123 
3124  # Finish mangling title and then check for loops.
3125  # Set $title to a Title object and $titleText to the PDBK
3126  if ( !$found ) {
3127  $ns = NS_TEMPLATE;
3128  # Split the title into page and subpage
3129  $subpage = '';
3130  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3131  if ( $part1 !== $relative ) {
3132  $part1 = $relative;
3133  $ns = $this->mTitle->getNamespace();
3134  }
3135  $title = Title::newFromText( $part1, $ns );
3136  if ( $title ) {
3137  $titleText = $title->getPrefixedText();
3138  # Check for language variants if the template is not found
3139  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3140  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3141  }
3142  # Do recursion depth check
3143  $limit = $this->mOptions->getMaxTemplateDepth();
3144  if ( $frame->depth >= $limit ) {
3145  $found = true;
3146  $text = '<span class="error">'
3147  . wfMessage( 'parser-template-recursion-depth-warning' )
3148  ->numParams( $limit )->inContentLanguage()->text()
3149  . '</span>';
3150  }
3151  }
3152  }
3153 
3154  # Load from database
3155  if ( !$found && $title ) {
3156  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3157  if ( !$title->isExternal() ) {
3158  if ( $title->isSpecialPage()
3159  && $this->mOptions->getAllowSpecialInclusion()
3160  && $this->ot['html']
3161  ) {
3162  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3163  // Pass the template arguments as URL parameters.
3164  // "uselang" will have no effect since the Language object
3165  // is forced to the one defined in ParserOptions.
3166  $pageArgs = [];
3167  $argsLength = $args->getLength();
3168  for ( $i = 0; $i < $argsLength; $i++ ) {
3169  $bits = $args->item( $i )->splitArg();
3170  if ( strval( $bits['index'] ) === '' ) {
3171  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3172  $value = trim( $frame->expand( $bits['value'] ) );
3173  $pageArgs[$name] = $value;
3174  }
3175  }
3176 
3177  // Create a new context to execute the special page
3178  $context = new RequestContext;
3179  $context->setTitle( $title );
3180  $context->setRequest( new FauxRequest( $pageArgs ) );
3181  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3182  $context->setUser( $this->getUser() );
3183  } else {
3184  // If this page is cached, then we better not be per user.
3185  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3186  }
3187  $context->setLanguage( $this->mOptions->getUserLangObj() );
3189  $title, $context, $this->getLinkRenderer() );
3190  if ( $ret ) {
3191  $text = $context->getOutput()->getHTML();
3192  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3193  $found = true;
3194  $isHTML = true;
3195  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3196  $this->mOutput->updateRuntimeAdaptiveExpiry(
3197  $specialPage->maxIncludeCacheTime()
3198  );
3199  }
3200  }
3201  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3202  $found = false; # access denied
3203  wfDebug( __METHOD__ . ": template inclusion denied for " .
3204  $title->getPrefixedDBkey() . "\n" );
3205  } else {
3206  list( $text, $title ) = $this->getTemplateDom( $title );
3207  if ( $text !== false ) {
3208  $found = true;
3209  $isChildObj = true;
3210  }
3211  }
3212 
3213  # If the title is valid but undisplayable, make a link to it
3214  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3215  $text = "[[:$titleText]]";
3216  $found = true;
3217  }
3218  } elseif ( $title->isTrans() ) {
3219  # Interwiki transclusion
3220  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3221  $text = $this->interwikiTransclude( $title, 'render' );
3222  $isHTML = true;
3223  } else {
3224  $text = $this->interwikiTransclude( $title, 'raw' );
3225  # Preprocess it like a template
3226  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3227  $isChildObj = true;
3228  }
3229  $found = true;
3230  }
3231 
3232  # Do infinite loop check
3233  # This has to be done after redirect resolution to avoid infinite loops via redirects
3234  if ( !$frame->loopCheck( $title ) ) {
3235  $found = true;
3236  $text = '<span class="error">'
3237  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3238  . '</span>';
3239  $this->addTrackingCategory( 'template-loop-category' );
3240  $this->mOutput->addWarning( wfMessage( 'template-loop-warning',
3241  wfEscapeWikiText( $titleText ) )->text() );
3242  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3243  }
3244  }
3245 
3246  # If we haven't found text to substitute by now, we're done
3247  # Recover the source wikitext and return it
3248  if ( !$found ) {
3249  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3250  if ( $profileSection ) {
3251  $this->mProfiler->scopedProfileOut( $profileSection );
3252  }
3253  return [ 'object' => $text ];
3254  }
3255 
3256  # Expand DOM-style return values in a child frame
3257  if ( $isChildObj ) {
3258  # Clean up argument array
3259  $newFrame = $frame->newChild( $args, $title );
3260 
3261  if ( $nowiki ) {
3262  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3263  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3264  # Expansion is eligible for the empty-frame cache
3265  $text = $newFrame->cachedExpand( $titleText, $text );
3266  } else {
3267  # Uncached expansion
3268  $text = $newFrame->expand( $text );
3269  }
3270  }
3271  if ( $isLocalObj && $nowiki ) {
3272  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3273  $isLocalObj = false;
3274  }
3275 
3276  if ( $profileSection ) {
3277  $this->mProfiler->scopedProfileOut( $profileSection );
3278  }
3279 
3280  # Replace raw HTML by a placeholder
3281  if ( $isHTML ) {
3282  $text = $this->insertStripItem( $text );
3283  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3284  # Escape nowiki-style return values
3285  $text = wfEscapeWikiText( $text );
3286  } elseif ( is_string( $text )
3287  && !$piece['lineStart']
3288  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3289  ) {
3290  # T2529: if the template begins with a table or block-level
3291  # element, it should be treated as beginning a new line.
3292  # This behavior is somewhat controversial.
3293  $text = "\n" . $text;
3294  }
3295 
3296  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3297  # Error, oversize inclusion
3298  if ( $titleText !== false ) {
3299  # Make a working, properly escaped link if possible (T25588)
3300  $text = "[[:$titleText]]";
3301  } else {
3302  # This will probably not be a working link, but at least it may
3303  # provide some hint of where the problem is
3304  preg_replace( '/^:/', '', $originalTitle );
3305  $text = "[[:$originalTitle]]";
3306  }
3307  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3308  . 'post-expand include size too large -->' );
3309  $this->limitationWarn( 'post-expand-template-inclusion' );
3310  }
3311 
3312  if ( $isLocalObj ) {
3313  $ret = [ 'object' => $text ];
3314  } else {
3315  $ret = [ 'text' => $text ];
3316  }
3317 
3318  return $ret;
3319  }
3320 
3340  public function callParserFunction( $frame, $function, array $args = [] ) {
3342 
3343  # Case sensitive functions
3344  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3345  $function = $this->mFunctionSynonyms[1][$function];
3346  } else {
3347  # Case insensitive functions
3348  $function = $wgContLang->lc( $function );
3349  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3350  $function = $this->mFunctionSynonyms[0][$function];
3351  } else {
3352  return [ 'found' => false ];
3353  }
3354  }
3355 
3356  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3357 
3358  // Avoid PHP 7.1 warning from passing $this by reference
3359  $parser = $this;
3360 
3361  $allArgs = [ &$parser ];
3362  if ( $flags & self::SFH_OBJECT_ARGS ) {
3363  # Convert arguments to PPNodes and collect for appending to $allArgs
3364  $funcArgs = [];
3365  foreach ( $args as $k => $v ) {
3366  if ( $v instanceof PPNode || $k === 0 ) {
3367  $funcArgs[] = $v;
3368  } else {
3369  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3370  }
3371  }
3372 
3373  # Add a frame parameter, and pass the arguments as an array
3374  $allArgs[] = $frame;
3375  $allArgs[] = $funcArgs;
3376  } else {
3377  # Convert arguments to plain text and append to $allArgs
3378  foreach ( $args as $k => $v ) {
3379  if ( $v instanceof PPNode ) {
3380  $allArgs[] = trim( $frame->expand( $v ) );
3381  } elseif ( is_int( $k ) && $k >= 0 ) {
3382  $allArgs[] = trim( $v );
3383  } else {
3384  $allArgs[] = trim( "$k=$v" );
3385  }
3386  }
3387  }
3388 
3389  $result = call_user_func_array( $callback, $allArgs );
3390 
3391  # The interface for function hooks allows them to return a wikitext
3392  # string or an array containing the string and any flags. This mungs
3393  # things around to match what this method should return.
3394  if ( !is_array( $result ) ) {
3395  $result = [
3396  'found' => true,
3397  'text' => $result,
3398  ];
3399  } else {
3400  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3401  $result['text'] = $result[0];
3402  }
3403  unset( $result[0] );
3404  $result += [
3405  'found' => true,
3406  ];
3407  }
3408 
3409  $noparse = true;
3410  $preprocessFlags = 0;
3411  if ( isset( $result['noparse'] ) ) {
3412  $noparse = $result['noparse'];
3413  }
3414  if ( isset( $result['preprocessFlags'] ) ) {
3415  $preprocessFlags = $result['preprocessFlags'];
3416  }
3417 
3418  if ( !$noparse ) {
3419  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3420  $result['isChildObj'] = true;
3421  }
3422 
3423  return $result;
3424  }
3425 
3434  public function getTemplateDom( $title ) {
3435  $cacheTitle = $title;
3436  $titleText = $title->getPrefixedDBkey();
3437 
3438  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3439  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3440  $title = Title::makeTitle( $ns, $dbk );
3441  $titleText = $title->getPrefixedDBkey();
3442  }
3443  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3444  return [ $this->mTplDomCache[$titleText], $title ];
3445  }
3446 
3447  # Cache miss, go to the database
3448  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3449 
3450  if ( $text === false ) {
3451  $this->mTplDomCache[$titleText] = false;
3452  return [ false, $title ];
3453  }
3454 
3455  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3456  $this->mTplDomCache[$titleText] = $dom;
3457 
3458  if ( !$title->equals( $cacheTitle ) ) {
3459  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3460  [ $title->getNamespace(), $title->getDBkey() ];
3461  }
3462 
3463  return [ $dom, $title ];
3464  }
3465 
3478  $cacheKey = $title->getPrefixedDBkey();
3479  if ( !$this->currentRevisionCache ) {
3480  $this->currentRevisionCache = new MapCacheLRU( 100 );
3481  }
3482  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3483  $this->currentRevisionCache->set( $cacheKey,
3484  // Defaults to Parser::statelessFetchRevision()
3485  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3486  );
3487  }
3488  return $this->currentRevisionCache->get( $cacheKey );
3489  }
3490 
3500  public static function statelessFetchRevision( Title $title, $parser = false ) {
3501  $pageId = $title->getArticleID();
3502  $revId = $title->getLatestRevID();
3503 
3504  $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $pageId, $revId );
3505  if ( $rev ) {
3506  $rev->setTitle( $title );
3507  }
3508 
3509  return $rev;
3510  }
3511 
3517  public function fetchTemplateAndTitle( $title ) {
3518  // Defaults to Parser::statelessFetchTemplate()
3519  $templateCb = $this->mOptions->getTemplateCallback();
3520  $stuff = call_user_func( $templateCb, $title, $this );
3521  // We use U+007F DELETE to distinguish strip markers from regular text.
3522  $text = $stuff['text'];
3523  if ( is_string( $stuff['text'] ) ) {
3524  $text = strtr( $text, "\x7f", "?" );
3525  }
3526  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3527  if ( isset( $stuff['deps'] ) ) {
3528  foreach ( $stuff['deps'] as $dep ) {
3529  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3530  if ( $dep['title']->equals( $this->getTitle() ) ) {
3531  // If we transclude ourselves, the final result
3532  // will change based on the new version of the page
3533  $this->mOutput->setFlag( 'vary-revision' );
3534  }
3535  }
3536  }
3537  return [ $text, $finalTitle ];
3538  }
3539 
3545  public function fetchTemplate( $title ) {
3546  return $this->fetchTemplateAndTitle( $title )[0];
3547  }
3548 
3558  public static function statelessFetchTemplate( $title, $parser = false ) {
3559  $text = $skip = false;
3560  $finalTitle = $title;
3561  $deps = [];
3562 
3563  # Loop to fetch the article, with up to 1 redirect
3564  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3565  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3566  // @codingStandardsIgnoreEnd
3567  # Give extensions a chance to select the revision instead
3568  $id = false; # Assume current
3569  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3570  [ $parser, $title, &$skip, &$id ] );
3571 
3572  if ( $skip ) {
3573  $text = false;
3574  $deps[] = [
3575  'title' => $title,
3576  'page_id' => $title->getArticleID(),
3577  'rev_id' => null
3578  ];
3579  break;
3580  }
3581  # Get the revision
3582  if ( $id ) {
3583  $rev = Revision::newFromId( $id );
3584  } elseif ( $parser ) {
3585  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3586  } else {
3588  }
3589  $rev_id = $rev ? $rev->getId() : 0;
3590  # If there is no current revision, there is no page
3591  if ( $id === false && !$rev ) {
3592  $linkCache = LinkCache::singleton();
3593  $linkCache->addBadLinkObj( $title );
3594  }
3595 
3596  $deps[] = [
3597  'title' => $title,
3598  'page_id' => $title->getArticleID(),
3599  'rev_id' => $rev_id ];
3600  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3601  # We fetched a rev from a different title; register it too...
3602  $deps[] = [
3603  'title' => $rev->getTitle(),
3604  'page_id' => $rev->getPage(),
3605  'rev_id' => $rev_id ];
3606  }
3607 
3608  if ( $rev ) {
3609  $content = $rev->getContent();
3610  $text = $content ? $content->getWikitextForTransclusion() : null;
3611 
3612  Hooks::run( 'ParserFetchTemplate',
3613  [ $parser, $title, $rev, &$text, &$deps ] );
3614 
3615  if ( $text === false || $text === null ) {
3616  $text = false;
3617  break;
3618  }
3619  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3621  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3622  if ( !$message->exists() ) {
3623  $text = false;
3624  break;
3625  }
3626  $content = $message->content();
3627  $text = $message->plain();
3628  } else {
3629  break;
3630  }
3631  if ( !$content ) {
3632  break;
3633  }
3634  # Redirect?
3635  $finalTitle = $title;
3636  $title = $content->getRedirectTarget();
3637  }
3638  return [
3639  'text' => $text,
3640  'finalTitle' => $finalTitle,
3641  'deps' => $deps ];
3642  }
3643 
3651  public function fetchFile( $title, $options = [] ) {
3652  return $this->fetchFileAndTitle( $title, $options )[0];
3653  }
3654 
3662  public function fetchFileAndTitle( $title, $options = [] ) {
3663  $file = $this->fetchFileNoRegister( $title, $options );
3664 
3665  $time = $file ? $file->getTimestamp() : false;
3666  $sha1 = $file ? $file->getSha1() : false;
3667  # Register the file as a dependency...
3668  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3669  if ( $file && !$title->equals( $file->getTitle() ) ) {
3670  # Update fetched file title
3671  $title = $file->getTitle();
3672  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3673  }
3674  return [ $file, $title ];
3675  }
3676 
3687  protected function fetchFileNoRegister( $title, $options = [] ) {
3688  if ( isset( $options['broken'] ) ) {
3689  $file = false; // broken thumbnail forced by hook
3690  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3691  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3692  } else { // get by (name,timestamp)
3693  $file = wfFindFile( $title, $options );
3694  }
3695  return $file;
3696  }
3697 
3706  public function interwikiTransclude( $title, $action ) {
3707  global $wgEnableScaryTranscluding;
3708 
3709  if ( !$wgEnableScaryTranscluding ) {
3710  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3711  }
3712 
3713  $url = $title->getFullURL( [ 'action' => $action ] );
3714 
3715  if ( strlen( $url ) > 255 ) {
3716  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3717  }
3718  return $this->fetchScaryTemplateMaybeFromCache( $url );
3719  }
3720 
3725  public function fetchScaryTemplateMaybeFromCache( $url ) {
3726  global $wgTranscludeCacheExpiry;
3727  $dbr = wfGetDB( DB_REPLICA );
3728  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3729  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3730  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3731  if ( $obj ) {
3732  return $obj->tc_contents;
3733  }
3734 
3735  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3736  $status = $req->execute(); // Status object
3737  if ( $status->isOK() ) {
3738  $text = $req->getContent();
3739  } elseif ( $req->getStatus() != 200 ) {
3740  // Though we failed to fetch the content, this status is useless.
3741  return wfMessage( 'scarytranscludefailed-httpstatus' )
3742  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3743  } else {
3744  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3745  }
3746 
3747  $dbw = wfGetDB( DB_MASTER );
3748  $dbw->replace( 'transcache', [ 'tc_url' ], [
3749  'tc_url' => $url,
3750  'tc_time' => $dbw->timestamp( time() ),
3751  'tc_contents' => $text
3752  ] );
3753  return $text;
3754  }
3755 
3765  public function argSubstitution( $piece, $frame ) {
3766  $error = false;
3767  $parts = $piece['parts'];
3768  $nameWithSpaces = $frame->expand( $piece['title'] );
3769  $argName = trim( $nameWithSpaces );
3770  $object = false;
3771  $text = $frame->getArgument( $argName );
3772  if ( $text === false && $parts->getLength() > 0
3773  && ( $this->ot['html']
3774  || $this->ot['pre']
3775  || ( $this->ot['wiki'] && $frame->isTemplate() )
3776  )
3777  ) {
3778  # No match in frame, use the supplied default
3779  $object = $parts->item( 0 )->getChildren();
3780  }
3781  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3782  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3783  $this->limitationWarn( 'post-expand-template-argument' );
3784  }
3785 
3786  if ( $text === false && $object === false ) {
3787  # No match anywhere
3788  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3789  }
3790  if ( $error !== false ) {
3791  $text .= $error;
3792  }
3793  if ( $object !== false ) {
3794  $ret = [ 'object' => $object ];
3795  } else {
3796  $ret = [ 'text' => $text ];
3797  }
3798 
3799  return $ret;
3800  }
3801 
3817  public function extensionSubstitution( $params, $frame ) {
3818  static $errorStr = '<span class="error">';
3819  static $errorLen = 20;
3820 
3821  $name = $frame->expand( $params['name'] );
3822  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3823  // Probably expansion depth or node count exceeded. Just punt the
3824  // error up.
3825  return $name;
3826  }
3827 
3828  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3829  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3830  // See above
3831  return $attrText;
3832  }
3833 
3834  // We can't safely check if the expansion for $content resulted in an
3835  // error, because the content could happen to be the error string
3836  // (T149622).
3837  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3838 
3839  $marker = self::MARKER_PREFIX . "-$name-"
3840  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3841 
3842  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3843  ( $this->ot['html'] || $this->ot['pre'] );
3844  if ( $isFunctionTag ) {
3845  $markerType = 'none';
3846  } else {
3847  $markerType = 'general';
3848  }
3849  if ( $this->ot['html'] || $isFunctionTag ) {
3850  $name = strtolower( $name );
3851  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3852  if ( isset( $params['attributes'] ) ) {
3853  $attributes = $attributes + $params['attributes'];
3854  }
3855 
3856  if ( isset( $this->mTagHooks[$name] ) ) {
3857  $output = call_user_func_array( $this->mTagHooks[$name],
3858  [ $content, $attributes, $this, $frame ] );
3859  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3860  list( $callback, ) = $this->mFunctionTagHooks[$name];
3861 
3862  // Avoid PHP 7.1 warning from passing $this by reference
3863  $parser = $this;
3864  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3865  } else {
3866  $output = '<span class="error">Invalid tag extension name: ' .
3867  htmlspecialchars( $name ) . '</span>';
3868  }
3869 
3870  if ( is_array( $output ) ) {
3871  # Extract flags to local scope (to override $markerType)
3872  $flags = $output;
3873  $output = $flags[0];
3874  unset( $flags[0] );
3875  extract( $flags );
3876  }
3877  } else {
3878  if ( is_null( $attrText ) ) {
3879  $attrText = '';
3880  }
3881  if ( isset( $params['attributes'] ) ) {
3882  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3883  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3884  htmlspecialchars( $attrValue ) . '"';
3885  }
3886  }
3887  if ( $content === null ) {
3888  $output = "<$name$attrText/>";
3889  } else {
3890  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3891  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3892  // See above
3893  return $close;
3894  }
3895  $output = "<$name$attrText>$content$close";
3896  }
3897  }
3898 
3899  if ( $markerType === 'none' ) {
3900  return $output;
3901  } elseif ( $markerType === 'nowiki' ) {
3902  $this->mStripState->addNoWiki( $marker, $output );
3903  } elseif ( $markerType === 'general' ) {
3904  $this->mStripState->addGeneral( $marker, $output );
3905  } else {
3906  throw new MWException( __METHOD__ . ': invalid marker type' );
3907  }
3908  return $marker;
3909  }
3910 
3918  public function incrementIncludeSize( $type, $size ) {
3919  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3920  return false;
3921  } else {
3922  $this->mIncludeSizes[$type] += $size;
3923  return true;
3924  }
3925  }
3926 
3933  $this->mExpensiveFunctionCount++;
3934  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3935  }
3936 
3945  public function doDoubleUnderscore( $text ) {
3946  # The position of __TOC__ needs to be recorded
3947  $mw = MagicWord::get( 'toc' );
3948  if ( $mw->match( $text ) ) {
3949  $this->mShowToc = true;
3950  $this->mForceTocPosition = true;
3951 
3952  # Set a placeholder. At the end we'll fill it in with the TOC.
3953  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3954 
3955  # Only keep the first one.
3956  $text = $mw->replace( '', $text );
3957  }
3958 
3959  # Now match and remove the rest of them
3961  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3962 
3963  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3964  $this->mOutput->mNoGallery = true;
3965  }
3966  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3967  $this->mShowToc = false;
3968  }
3969  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3970  && $this->mTitle->getNamespace() == NS_CATEGORY
3971  ) {
3972  $this->addTrackingCategory( 'hidden-category-category' );
3973  }
3974  # (T10068) Allow control over whether robots index a page.
3975  # __INDEX__ always overrides __NOINDEX__, see T16899
3976  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3977  $this->mOutput->setIndexPolicy( 'noindex' );
3978  $this->addTrackingCategory( 'noindex-category' );
3979  }
3980  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
3981  $this->mOutput->setIndexPolicy( 'index' );
3982  $this->addTrackingCategory( 'index-category' );
3983  }
3984 
3985  # Cache all double underscores in the database
3986  foreach ( $this->mDoubleUnderscores as $key => $val ) {
3987  $this->mOutput->setProperty( $key, '' );
3988  }
3989 
3990  return $text;
3991  }
3992 
3998  public function addTrackingCategory( $msg ) {
3999  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4000  }
4001 
4018  public function formatHeadings( $text, $origText, $isMain = true ) {
4019  global $wgMaxTocLevel;
4020 
4021  # Inhibit editsection links if requested in the page
4022  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4023  $maybeShowEditLink = $showEditLink = false;
4024  } else {
4025  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4026  $showEditLink = $this->mOptions->getEditSection();
4027  }
4028  if ( $showEditLink ) {
4029  $this->mOutput->setEditSectionTokens( true );
4030  }
4031 
4032  # Get all headlines for numbering them and adding funky stuff like [edit]
4033  # links - this is for later, but we need the number of headlines right now
4034  $matches = [];
4035  $numMatches = preg_match_all(
4036  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4037  $text,
4038  $matches
4039  );
4040 
4041  # if there are fewer than 4 headlines in the article, do not show TOC
4042  # unless it's been explicitly enabled.
4043  $enoughToc = $this->mShowToc &&
4044  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4045 
4046  # Allow user to stipulate that a page should have a "new section"
4047  # link added via __NEWSECTIONLINK__
4048  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4049  $this->mOutput->setNewSection( true );
4050  }
4051 
4052  # Allow user to remove the "new section"
4053  # link via __NONEWSECTIONLINK__
4054  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4055  $this->mOutput->hideNewSection( true );
4056  }
4057 
4058  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4059  # override above conditions and always show TOC above first header
4060  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4061  $this->mShowToc = true;
4062  $enoughToc = true;
4063  }
4064 
4065  # headline counter
4066  $headlineCount = 0;
4067  $numVisible = 0;
4068 
4069  # Ugh .. the TOC should have neat indentation levels which can be
4070  # passed to the skin functions. These are determined here
4071  $toc = '';
4072  $full = '';
4073  $head = [];
4074  $sublevelCount = [];
4075  $levelCount = [];
4076  $level = 0;
4077  $prevlevel = 0;
4078  $toclevel = 0;
4079  $prevtoclevel = 0;
4080  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4081  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4082  $oldType = $this->mOutputType;
4083  $this->setOutputType( self::OT_WIKI );
4084  $frame = $this->getPreprocessor()->newFrame();
4085  $root = $this->preprocessToDom( $origText );
4086  $node = $root->getFirstChild();
4087  $byteOffset = 0;
4088  $tocraw = [];
4089  $refers = [];
4090 
4091  $headlines = $numMatches !== false ? $matches[3] : [];
4092 
4093  foreach ( $headlines as $headline ) {
4094  $isTemplate = false;
4095  $titleText = false;
4096  $sectionIndex = false;
4097  $numbering = '';
4098  $markerMatches = [];
4099  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4100  $serial = $markerMatches[1];
4101  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4102  $isTemplate = ( $titleText != $baseTitleText );
4103  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4104  }
4105 
4106  if ( $toclevel ) {
4107  $prevlevel = $level;
4108  }
4109  $level = $matches[1][$headlineCount];
4110 
4111  if ( $level > $prevlevel ) {
4112  # Increase TOC level
4113  $toclevel++;
4114  $sublevelCount[$toclevel] = 0;
4115  if ( $toclevel < $wgMaxTocLevel ) {
4116  $prevtoclevel = $toclevel;
4117  $toc .= Linker::tocIndent();
4118  $numVisible++;
4119  }
4120  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4121  # Decrease TOC level, find level to jump to
4122 
4123  for ( $i = $toclevel; $i > 0; $i-- ) {
4124  if ( $levelCount[$i] == $level ) {
4125  # Found last matching level
4126  $toclevel = $i;
4127  break;
4128  } elseif ( $levelCount[$i] < $level ) {
4129  # Found first matching level below current level
4130  $toclevel = $i + 1;
4131  break;
4132  }
4133  }
4134  if ( $i == 0 ) {
4135  $toclevel = 1;
4136  }
4137  if ( $toclevel < $wgMaxTocLevel ) {
4138  if ( $prevtoclevel < $wgMaxTocLevel ) {
4139  # Unindent only if the previous toc level was shown :p
4140  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4141  $prevtoclevel = $toclevel;
4142  } else {
4143  $toc .= Linker::tocLineEnd();
4144  }
4145  }
4146  } else {
4147  # No change in level, end TOC line
4148  if ( $toclevel < $wgMaxTocLevel ) {
4149  $toc .= Linker::tocLineEnd();
4150  }
4151  }
4152 
4153  $levelCount[$toclevel] = $level;
4154 
4155  # count number of headlines for each level
4156  $sublevelCount[$toclevel]++;
4157  $dot = 0;
4158  for ( $i = 1; $i <= $toclevel; $i++ ) {
4159  if ( !empty( $sublevelCount[$i] ) ) {
4160  if ( $dot ) {
4161  $numbering .= '.';
4162  }
4163  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4164  $dot = 1;
4165  }
4166  }
4167 
4168  # The safe header is a version of the header text safe to use for links
4169 
4170  # Remove link placeholders by the link text.
4171  # <!--LINK number-->
4172  # turns into
4173  # link text with suffix
4174  # Do this before unstrip since link text can contain strip markers
4175  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4176 
4177  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4178  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4179 
4180  # Strip out HTML (first regex removes any tag not allowed)
4181  # Allowed tags are:
4182  # * <sup> and <sub> (T10393)
4183  # * <i> (T28375)
4184  # * <b> (r105284)
4185  # * <bdi> (T74884)
4186  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4187  # * <s> and <strike> (T35715)
4188  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4189  # to allow setting directionality in toc items.
4190  $tocline = preg_replace(
4191  [
4192  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4193  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4194  ],
4195  [ '', '<$1>' ],
4196  $safeHeadline
4197  );
4198 
4199  # Strip '<span></span>', which is the result from the above if
4200  # <span id="foo"></span> is used to produce an additional anchor
4201  # for a section.
4202  $tocline = str_replace( '<span></span>', '', $tocline );
4203 
4204  $tocline = trim( $tocline );
4205 
4206  # For the anchor, strip out HTML-y stuff period
4207  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4208  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4209 
4210  # Save headline for section edit hint before it's escaped
4211  $headlineHint = $safeHeadline;
4212 
4213  # Decode HTML entities
4214  $safeHeadline = Sanitizer::decodeCharReferences( $safeHeadline );
4215  $fallbackHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_FALLBACK );
4216  $linkAnchor = Sanitizer::escapeIdForLink( $safeHeadline );
4217  $safeHeadline = Sanitizer::escapeIdForAttribute( $safeHeadline, Sanitizer::ID_PRIMARY );
4218  if ( $fallbackHeadline === $safeHeadline ) {
4219  # No reason to have both (in fact, we can't)
4220  $fallbackHeadline = false;
4221  }
4222 
4223  # HTML IDs must be case-insensitively unique for IE compatibility (T12721).
4224  # @todo FIXME: We may be changing them depending on the current locale.
4225  $arrayKey = strtolower( $safeHeadline );
4226  if ( $fallbackHeadline === false ) {
4227  $fallbackArrayKey = false;
4228  } else {
4229  $fallbackArrayKey = strtolower( $fallbackHeadline );
4230  }
4231 
4232  # Create the anchor for linking from the TOC to the section
4233  $anchor = $safeHeadline;
4234  $fallbackAnchor = $fallbackHeadline;
4235  if ( isset( $refers[$arrayKey] ) ) {
4236  // @codingStandardsIgnoreStart
4237  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4238  // @codingStandardsIgnoreEnd
4239  $anchor .= "_$i";
4240  $linkAnchor .= "_$i";
4241  $refers["${arrayKey}_$i"] = true;
4242  } else {
4243  $refers[$arrayKey] = true;
4244  }
4245  if ( $fallbackHeadline !== false && isset( $refers[$fallbackArrayKey] ) ) {
4246  // @codingStandardsIgnoreStart
4247  for ( $i = 2; isset( $refers["${fallbackArrayKey}_$i"] ); ++$i );
4248  // @codingStandardsIgnoreEnd
4249  $fallbackAnchor .= "_$i";
4250  $refers["${fallbackArrayKey}_$i"] = true;
4251  } else {
4252  $refers[$fallbackArrayKey] = true;
4253  }
4254 
4255  # Don't number the heading if it is the only one (looks silly)
4256  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4257  # the two are different if the line contains a link
4258  $headline = Html::element(
4259  'span',
4260  [ 'class' => 'mw-headline-number' ],
4261  $numbering
4262  ) . ' ' . $headline;
4263  }
4264 
4265  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4266  $toc .= Linker::tocLine( $linkAnchor, $tocline,
4267  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4268  }
4269 
4270  # Add the section to the section tree
4271  # Find the DOM node for this header
4272  $noOffset = ( $isTemplate || $sectionIndex === false );
4273  while ( $node && !$noOffset ) {
4274  if ( $node->getName() === 'h' ) {
4275  $bits = $node->splitHeading();
4276  if ( $bits['i'] == $sectionIndex ) {
4277  break;
4278  }
4279  }
4280  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4281  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4282  $node = $node->getNextSibling();
4283  }
4284  $tocraw[] = [
4285  'toclevel' => $toclevel,
4286  'level' => $level,
4287  'line' => $tocline,
4288  'number' => $numbering,
4289  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4290  'fromtitle' => $titleText,
4291  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4292  'anchor' => $anchor,
4293  ];
4294 
4295  # give headline the correct <h#> tag
4296  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4297  // Output edit section links as markers with styles that can be customized by skins
4298  if ( $isTemplate ) {
4299  # Put a T flag in the section identifier, to indicate to extractSections()
4300  # that sections inside <includeonly> should be counted.
4301  $editsectionPage = $titleText;
4302  $editsectionSection = "T-$sectionIndex";
4303  $editsectionContent = null;
4304  } else {
4305  $editsectionPage = $this->mTitle->getPrefixedText();
4306  $editsectionSection = $sectionIndex;
4307  $editsectionContent = $headlineHint;
4308  }
4309  // We use a bit of pesudo-xml for editsection markers. The
4310  // language converter is run later on. Using a UNIQ style marker
4311  // leads to the converter screwing up the tokens when it
4312  // converts stuff. And trying to insert strip tags fails too. At
4313  // this point all real inputted tags have already been escaped,
4314  // so we don't have to worry about a user trying to input one of
4315  // these markers directly. We use a page and section attribute
4316  // to stop the language converter from converting these
4317  // important bits of data, but put the headline hint inside a
4318  // content block because the language converter is supposed to
4319  // be able to convert that piece of data.
4320  // Gets replaced with html in ParserOutput::getText
4321  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4322  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4323  if ( $editsectionContent !== null ) {
4324  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4325  } else {
4326  $editlink .= '/>';
4327  }
4328  } else {
4329  $editlink = '';
4330  }
4331  $head[$headlineCount] = Linker::makeHeadline( $level,
4332  $matches['attrib'][$headlineCount], $anchor, $headline,
4333  $editlink, $fallbackAnchor );
4334 
4335  $headlineCount++;
4336  }
4337 
4338  $this->setOutputType( $oldType );
4339 
4340  # Never ever show TOC if no headers
4341  if ( $numVisible < 1 ) {
4342  $enoughToc = false;
4343  }
4344 
4345  if ( $enoughToc ) {
4346  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4347  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4348  }
4349  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4350  $this->mOutput->setTOCHTML( $toc );
4351  $toc = self::TOC_START . $toc . self::TOC_END;
4352  }
4353 
4354  if ( $isMain ) {
4355  $this->mOutput->setSections( $tocraw );
4356  }
4357 
4358  # split up and insert constructed headlines
4359  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4360  $i = 0;
4361 
4362  // build an array of document sections
4363  $sections = [];
4364  foreach ( $blocks as $block ) {
4365  // $head is zero-based, sections aren't.
4366  if ( empty( $head[$i - 1] ) ) {
4367  $sections[$i] = $block;
4368  } else {
4369  $sections[$i] = $head[$i - 1] . $block;
4370  }
4371 
4382  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4383 
4384  $i++;
4385  }
4386 
4387  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4388  // append the TOC at the beginning
4389  // Top anchor now in skin
4390  $sections[0] = $sections[0] . $toc . "\n";
4391  }
4392 
4393  $full .= implode( '', $sections );
4394 
4395  if ( $this->mForceTocPosition ) {
4396  return str_replace( '<!--MWTOC-->', $toc, $full );
4397  } else {
4398  return $full;
4399  }
4400  }
4401 
4413  public function preSaveTransform( $text, Title $title, User $user,
4414  ParserOptions $options, $clearState = true
4415  ) {
4416  if ( $clearState ) {
4417  $magicScopeVariable = $this->lock();
4418  }
4419  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4420  $this->setUser( $user );
4421 
4422  // Strip U+0000 NULL (T159174)
4423  $text = str_replace( "\000", '', $text );
4424 
4425  // We still normalize line endings for backwards-compatibility
4426  // with other code that just calls PST, but this should already
4427  // be handled in TextContent subclasses
4428  $text = TextContent::normalizeLineEndings( $text );
4429 
4430  if ( $options->getPreSaveTransform() ) {
4431  $text = $this->pstPass2( $text, $user );
4432  }
4433  $text = $this->mStripState->unstripBoth( $text );
4434 
4435  $this->setUser( null ); # Reset
4436 
4437  return $text;
4438  }
4439 
4448  private function pstPass2( $text, $user ) {
4450 
4451  # Note: This is the timestamp saved as hardcoded wikitext to
4452  # the database, we use $wgContLang here in order to give
4453  # everyone the same signature and use the default one rather
4454  # than the one selected in each user's preferences.
4455  # (see also T14815)
4456  $ts = $this->mOptions->getTimestamp();
4457  $timestamp = MWTimestamp::getLocalInstance( $ts );
4458  $ts = $timestamp->format( 'YmdHis' );
4459  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4460 
4461  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4462 
4463  # Variable replacement
4464  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4465  $text = $this->replaceVariables( $text );
4466 
4467  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4468  # which may corrupt this parser instance via its wfMessage()->text() call-
4469 
4470  # Signatures
4471  if ( strpos( $text, '~~~' ) !== false ) {
4472  $sigText = $this->getUserSig( $user );
4473  $text = strtr( $text, [
4474  '~~~~~' => $d,
4475  '~~~~' => "$sigText $d",
4476  '~~~' => $sigText
4477  ] );
4478  # The main two signature forms used above are time-sensitive
4479  $this->mOutput->setFlag( 'user-signature' );
4480  }
4481 
4482  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4483  $tc = '[' . Title::legalChars() . ']';
4484  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4485 
4486  // [[ns:page (context)|]]
4487  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4488  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4489  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4490  // [[ns:page (context), context|]] (using either single or double-width comma)
4491  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4492  // [[|page]] (reverse pipe trick: add context from page title)
4493  $p2 = "/\[\[\\|($tc+)]]/";
4494 
4495  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4496  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4497  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4498  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4499 
4500  $t = $this->mTitle->getText();
4501  $m = [];
4502  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4503  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4504  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4505  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4506  } else {
4507  # if there's no context, don't bother duplicating the title
4508  $text = preg_replace( $p2, '[[\\1]]', $text );
4509  }
4510 
4511  return $text;
4512  }
4513 
4528  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4529  global $wgMaxSigChars;
4530 
4531  $username = $user->getName();
4532 
4533  # If not given, retrieve from the user object.
4534  if ( $nickname === false ) {
4535  $nickname = $user->getOption( 'nickname' );
4536  }
4537 
4538  if ( is_null( $fancySig ) ) {
4539  $fancySig = $user->getBoolOption( 'fancysig' );
4540  }
4541 
4542  $nickname = $nickname == null ? $username : $nickname;
4543 
4544  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4545  $nickname = $username;
4546  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4547  } elseif ( $fancySig !== false ) {
4548  # Sig. might contain markup; validate this
4549  if ( $this->validateSig( $nickname ) !== false ) {
4550  # Validated; clean up (if needed) and return it
4551  return $this->cleanSig( $nickname, true );
4552  } else {
4553  # Failed to validate; fall back to the default
4554  $nickname = $username;
4555  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4556  }
4557  }
4558 
4559  # Make sure nickname doesnt get a sig in a sig
4560  $nickname = self::cleanSigInSig( $nickname );
4561 
4562  # If we're still here, make it a link to the user page
4563  $userText = wfEscapeWikiText( $username );
4564  $nickText = wfEscapeWikiText( $nickname );
4565  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4566 
4567  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4568  ->title( $this->getTitle() )->text();
4569  }
4570 
4577  public function validateSig( $text ) {
4578  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4579  }
4580 
4591  public function cleanSig( $text, $parsing = false ) {
4592  if ( !$parsing ) {
4593  global $wgTitle;
4594  $magicScopeVariable = $this->lock();
4595  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4596  }
4597 
4598  # Option to disable this feature
4599  if ( !$this->mOptions->getCleanSignatures() ) {
4600  return $text;
4601  }
4602 
4603  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4604  # => Move this logic to braceSubstitution()
4605  $substWord = MagicWord::get( 'subst' );
4606  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4607  $substText = '{{' . $substWord->getSynonym( 0 );
4608 
4609  $text = preg_replace( $substRegex, $substText, $text );
4610  $text = self::cleanSigInSig( $text );
4611  $dom = $this->preprocessToDom( $text );
4612  $frame = $this->getPreprocessor()->newFrame();
4613  $text = $frame->expand( $dom );
4614 
4615  if ( !$parsing ) {
4616  $text = $this->mStripState->unstripBoth( $text );
4617  }
4618 
4619  return $text;
4620  }
4621 
4628  public static function cleanSigInSig( $text ) {
4629  $text = preg_replace( '/~{3,5}/', '', $text );
4630  return $text;
4631  }
4632 
4643  $outputType, $clearState = true
4644  ) {
4645  $this->startParse( $title, $options, $outputType, $clearState );
4646  }
4647 
4654  private function startParse( Title $title = null, ParserOptions $options,
4655  $outputType, $clearState = true
4656  ) {
4657  $this->setTitle( $title );
4658  $this->mOptions = $options;
4659  $this->setOutputType( $outputType );
4660  if ( $clearState ) {
4661  $this->clearState();
4662  }
4663  }
4664 
4673  public function transformMsg( $text, $options, $title = null ) {
4674  static $executing = false;
4675 
4676  # Guard against infinite recursion
4677  if ( $executing ) {
4678  return $text;
4679  }
4680  $executing = true;
4681 
4682  if ( !$title ) {
4683  global $wgTitle;
4684  $title = $wgTitle;
4685  }
4686 
4687  $text = $this->preprocess( $text, $title, $options );
4688 
4689  $executing = false;
4690  return $text;
4691  }
4692 
4717  public function setHook( $tag, callable $callback ) {
4718  $tag = strtolower( $tag );
4719  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4720  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4721  }
4722  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4723  $this->mTagHooks[$tag] = $callback;
4724  if ( !in_array( $tag, $this->mStripList ) ) {
4725  $this->mStripList[] = $tag;
4726  }
4727 
4728  return $oldVal;
4729  }
4730 
4748  public function setTransparentTagHook( $tag, callable $callback ) {
4749  $tag = strtolower( $tag );
4750  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4751  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4752  }
4753  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4754  $this->mTransparentTagHooks[$tag] = $callback;
4755 
4756  return $oldVal;
4757  }
4758 
4762  public function clearTagHooks() {
4763  $this->mTagHooks = [];
4764  $this->mFunctionTagHooks = [];
4765  $this->mStripList = $this->mDefaultStripList;
4766  }
4767 
4811  public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
4813 
4814  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4815  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4816 
4817  # Add to function cache
4818  $mw = MagicWord::get( $id );
4819  if ( !$mw ) {
4820  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4821  }
4822 
4823  $synonyms = $mw->getSynonyms();
4824  $sensitive = intval( $mw->isCaseSensitive() );
4825 
4826  foreach ( $synonyms as $syn ) {
4827  # Case
4828  if ( !$sensitive ) {
4829  $syn = $wgContLang->lc( $syn );
4830  }
4831  # Add leading hash
4832  if ( !( $flags & self::SFH_NO_HASH ) ) {
4833  $syn = '#' . $syn;
4834  }
4835  # Remove trailing colon
4836  if ( substr( $syn, -1, 1 ) === ':' ) {
4837  $syn = substr( $syn, 0, -1 );
4838  }
4839  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4840  }
4841  return $oldVal;
4842  }
4843 
4849  public function getFunctionHooks() {
4850  return array_keys( $this->mFunctionHooks );
4851  }
4852 
4863  public function setFunctionTagHook( $tag, callable $callback, $flags ) {
4864  $tag = strtolower( $tag );
4865  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4866  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4867  }
4868  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4869  $this->mFunctionTagHooks[$tag] : null;
4870  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4871 
4872  if ( !in_array( $tag, $this->mStripList ) ) {
4873  $this->mStripList[] = $tag;
4874  }
4875 
4876  return $old;
4877  }
4878 
4886  public function replaceLinkHolders( &$text, $options = 0 ) {
4887  $this->mLinkHolders->replace( $text );
4888  }
4889 
4897  public function replaceLinkHoldersText( $text ) {
4898  return $this->mLinkHolders->replaceText( $text );
4899  }
4900 
4914  public function renderImageGallery( $text, $params ) {
4915  $mode = false;
4916  if ( isset( $params['mode'] ) ) {
4917  $mode = $params['mode'];
4918  }
4919 
4920  try {
4921  $ig = ImageGalleryBase::factory( $mode );
4922  } catch ( Exception $e ) {
4923  // If invalid type set, fallback to default.
4924  $ig = ImageGalleryBase::factory( false );
4925  }
4926 
4927  $ig->setContextTitle( $this->mTitle );
4928  $ig->setShowBytes( false );
4929  $ig->setShowDimensions( false );
4930  $ig->setShowFilename( false );
4931  $ig->setParser( $this );
4932  $ig->setHideBadImages();
4933  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
4934 
4935  if ( isset( $params['showfilename'] ) ) {
4936  $ig->setShowFilename( true );
4937  } else {
4938  $ig->setShowFilename( false );
4939  }
4940  if ( isset( $params['caption'] ) ) {
4941  $caption = $params['caption'];
4942  $caption = htmlspecialchars( $caption );
4943  $caption = $this->replaceInternalLinks( $caption );
4944  $ig->setCaptionHtml( $caption );
4945  }
4946  if ( isset( $params['perrow'] ) ) {
4947  $ig->setPerRow( $params['perrow'] );
4948  }
4949  if ( isset( $params['widths'] ) ) {
4950  $ig->setWidths( $params['widths'] );
4951  }
4952  if ( isset( $params['heights'] ) ) {
4953  $ig->setHeights( $params['heights'] );
4954  }
4955  $ig->setAdditionalOptions( $params );
4956 
4957  // Avoid PHP 7.1 warning from passing $this by reference
4958  $parser = $this;
4959  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
4960 
4961  $lines = StringUtils::explode( "\n", $text );
4962  foreach ( $lines as $line ) {
4963  # match lines like these:
4964  # Image:someimage.jpg|This is some image
4965  $matches = [];
4966  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4967  # Skip empty lines
4968  if ( count( $matches ) == 0 ) {
4969  continue;
4970  }
4971 
4972  if ( strpos( $matches[0], '%' ) !== false ) {
4973  $matches[1] = rawurldecode( $matches[1] );
4974  }
4976  if ( is_null( $title ) ) {
4977  # Bogus title. Ignore these so we don't bomb out later.
4978  continue;
4979  }
4980 
4981  # We need to get what handler the file uses, to figure out parameters.
4982  # Note, a hook can overide the file name, and chose an entirely different
4983  # file (which potentially could be of a different type and have different handler).
4984  $options = [];
4985  $descQuery = false;
4986  Hooks::run( 'BeforeParserFetchFileAndTitle',
4987  [ $this, $title, &$options, &$descQuery ] );
4988  # Don't register it now, as TraditionalImageGallery does that later.
4989  $file = $this->fetchFileNoRegister( $title, $options );
4990  $handler = $file ? $file->getHandler() : false;
4991 
4992  $paramMap = [
4993  'img_alt' => 'gallery-internal-alt',
4994  'img_link' => 'gallery-internal-link',
4995  ];
4996  if ( $handler ) {
4997  $paramMap = $paramMap + $handler->getParamMap();
4998  // We don't want people to specify per-image widths.
4999  // Additionally the width parameter would need special casing anyhow.
5000  unset( $paramMap['img_width'] );
5001  }
5002 
5003  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5004 
5005  $label = '';
5006  $alt = '';
5007  $link = '';
5008  $handlerOptions = [];
5009  if ( isset( $matches[3] ) ) {
5010  // look for an |alt= definition while trying not to break existing
5011  // captions with multiple pipes (|) in it, until a more sensible grammar
5012  // is defined for images in galleries
5013 
5014  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5015  // splitting on '|' is a bit odd, and different from makeImage.
5016  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5017  // Protect LanguageConverter markup
5018  $parameterMatches = StringUtils::delimiterExplode(
5019  '-{', '}-', '|', $matches[3], true /* nested */
5020  );
5021 
5022  foreach ( $parameterMatches as $parameterMatch ) {
5023  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5024  if ( $magicName ) {
5025  $paramName = $paramMap[$magicName];
5026 
5027  switch ( $paramName ) {
5028  case 'gallery-internal-alt':
5029  $alt = $this->stripAltText( $match, false );
5030  break;
5031  case 'gallery-internal-link':
5032  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5033  $chars = self::EXT_LINK_URL_CLASS;
5034  $addr = self::EXT_LINK_ADDR;
5035  $prots = $this->mUrlProtocols;
5036  // check to see if link matches an absolute url, if not then it must be a wiki link.
5037  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5038  // Result of LanguageConverter::markNoConversion
5039  // invoked on an external link.
5040  $linkValue = substr( $linkValue, 4, -2 );
5041  }
5042  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5043  $link = $linkValue;
5044  $this->mOutput->addExternalLink( $link );
5045  } else {
5046  $localLinkTitle = Title::newFromText( $linkValue );
5047  if ( $localLinkTitle !== null ) {
5048  $this->mOutput->addLink( $localLinkTitle );
5049  $link = $localLinkTitle->getLinkURL();
5050  }
5051  }
5052  break;
5053  default:
5054  // Must be a handler specific parameter.
5055  if ( $handler->validateParam( $paramName, $match ) ) {
5056  $handlerOptions[$paramName] = $match;
5057  } else {
5058  // Guess not, consider it as caption.
5059  wfDebug( "$parameterMatch failed parameter validation\n" );
5060  $label = '|' . $parameterMatch;
5061  }
5062  }
5063 
5064  } else {
5065  // Last pipe wins.
5066  $label = '|' . $parameterMatch;
5067  }
5068  }
5069  // Remove the pipe.
5070  $label = substr( $label, 1 );
5071  }
5072 
5073  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5074  }
5075  $html = $ig->toHTML();
5076  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5077  return $html;
5078  }
5079 
5084  public function getImageParams( $handler ) {
5085  if ( $handler ) {
5086  $handlerClass = get_class( $handler );
5087  } else {
5088  $handlerClass = '';
5089  }
5090  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5091  # Initialise static lists
5092  static $internalParamNames = [
5093  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5094  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5095  'bottom', 'text-bottom' ],
5096  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5097  'upright', 'border', 'link', 'alt', 'class' ],
5098  ];
5099  static $internalParamMap;
5100  if ( !$internalParamMap ) {
5101  $internalParamMap = [];
5102  foreach ( $internalParamNames as $type => $names ) {
5103  foreach ( $names as $name ) {
5104  // For grep: img_left, img_right, img_center, img_none,
5105  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5106  // img_bottom, img_text_bottom,
5107  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5108  // img_border, img_link, img_alt, img_class
5109  $magicName = str_replace( '-', '_', "img_$name" );
5110  $internalParamMap[$magicName] = [ $type, $name ];
5111  }
5112  }
5113  }
5114 
5115  # Add handler params
5116  $paramMap = $internalParamMap;
5117  if ( $handler ) {
5118  $handlerParamMap = $handler->getParamMap();
5119  foreach ( $handlerParamMap as $magic => $paramName ) {
5120  $paramMap[$magic] = [ 'handler', $paramName ];
5121  }
5122  }
5123  $this->mImageParams[$handlerClass] = $paramMap;
5124  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5125  }
5126  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5127  }
5128 
5137  public function makeImage( $title, $options, $holders = false ) {
5138  # Check if the options text is of the form "options|alt text"
5139  # Options are:
5140  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5141  # * left no resizing, just left align. label is used for alt= only
5142  # * right same, but right aligned
5143  # * none same, but not aligned
5144  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5145  # * center center the image
5146  # * frame Keep original image size, no magnify-button.
5147  # * framed Same as "frame"
5148  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5149  # * upright reduce width for upright images, rounded to full __0 px
5150  # * border draw a 1px border around the image
5151  # * alt Text for HTML alt attribute (defaults to empty)
5152  # * class Set a class for img node
5153  # * link Set the target of the image link. Can be external, interwiki, or local
5154  # vertical-align values (no % or length right now):
5155  # * baseline
5156  # * sub
5157  # * super
5158  # * top
5159  # * text-top
5160  # * middle
5161  # * bottom
5162  # * text-bottom
5163 
5164  # Protect LanguageConverter markup when splitting into parts
5166  '-{', '}-', '|', $options, true /* allow nesting */
5167  );
5168 
5169  # Give extensions a chance to select the file revision for us
5170  $options = [];
5171  $descQuery = false;
5172  Hooks::run( 'BeforeParserFetchFileAndTitle',
5173  [ $this, $title, &$options, &$descQuery ] );
5174  # Fetch and register the file (file title may be different via hooks)
5175  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5176 
5177  # Get parameter map
5178  $handler = $file ? $file->getHandler() : false;
5179 
5180  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5181 
5182  if ( !$file ) {
5183  $this->addTrackingCategory( 'broken-file-category' );
5184  }
5185 
5186  # Process the input parameters
5187  $caption = '';
5188  $params = [ 'frame' => [], 'handler' => [],
5189  'horizAlign' => [], 'vertAlign' => [] ];
5190  $seenformat = false;
5191  foreach ( $parts as $part ) {
5192  $part = trim( $part );
5193  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5194  $validated = false;
5195  if ( isset( $paramMap[$magicName] ) ) {
5196  list( $type, $paramName ) = $paramMap[$magicName];
5197 
5198  # Special case; width and height come in one variable together
5199  if ( $type === 'handler' && $paramName === 'width' ) {
5200  $parsedWidthParam = $this->parseWidthParam( $value );
5201  if ( isset( $parsedWidthParam['width'] ) ) {
5202  $width = $parsedWidthParam['width'];
5203  if ( $handler->validateParam( 'width', $width ) ) {
5204  $params[$type]['width'] = $width;
5205  $validated = true;
5206  }
5207  }
5208  if ( isset( $parsedWidthParam['height'] ) ) {
5209  $height = $parsedWidthParam['height'];
5210  if ( $handler->validateParam( 'height', $height ) ) {
5211  $params[$type]['height'] = $height;
5212  $validated = true;
5213  }
5214  }
5215  # else no validation -- T15436
5216  } else {
5217  if ( $type === 'handler' ) {
5218  # Validate handler parameter
5219  $validated = $handler->validateParam( $paramName, $value );
5220  } else {
5221  # Validate internal parameters
5222  switch ( $paramName ) {
5223  case 'manualthumb':
5224  case 'alt':
5225  case 'class':
5226  # @todo FIXME: Possibly check validity here for
5227  # manualthumb? downstream behavior seems odd with
5228  # missing manual thumbs.
5229  $validated = true;
5230  $value = $this->stripAltText( $value, $holders );
5231  break;
5232  case 'link':
5233  $chars = self::EXT_LINK_URL_CLASS;
5234  $addr = self::EXT_LINK_ADDR;
5235  $prots = $this->mUrlProtocols;
5236  if ( $value === '' ) {
5237  $paramName = 'no-link';
5238  $value = true;
5239  $validated = true;
5240  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5241  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5242  $paramName = 'link-url';
5243  $this->mOutput->addExternalLink( $value );
5244  if ( $this->mOptions->getExternalLinkTarget() ) {
5245  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5246  }
5247  $validated = true;
5248  }
5249  } else {
5250  $linkTitle = Title::newFromText( $value );
5251  if ( $linkTitle ) {
5252  $paramName = 'link-title';
5253  $value = $linkTitle;
5254  $this->mOutput->addLink( $linkTitle );
5255  $validated = true;
5256  }
5257  }
5258  break;
5259  case 'frameless':
5260  case 'framed':
5261  case 'thumbnail':
5262  // use first appearing option, discard others.
5263  $validated = !$seenformat;
5264  $seenformat = true;
5265  break;
5266  default:
5267  # Most other things appear to be empty or numeric...
5268  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5269  }
5270  }
5271 
5272  if ( $validated ) {
5273  $params[$type][$paramName] = $value;
5274  }
5275  }
5276  }
5277  if ( !$validated ) {
5278  $caption = $part;
5279  }
5280  }
5281 
5282  # Process alignment parameters
5283  if ( $params['horizAlign'] ) {
5284  $params['frame']['align'] = key( $params['horizAlign'] );
5285  }
5286  if ( $params['vertAlign'] ) {
5287  $params['frame']['valign'] = key( $params['vertAlign'] );
5288  }
5289 
5290  $params['frame']['caption'] = $caption;
5291 
5292  # Will the image be presented in a frame, with the caption below?
5293  $imageIsFramed = isset( $params['frame']['frame'] )
5294  || isset( $params['frame']['framed'] )
5295  || isset( $params['frame']['thumbnail'] )
5296  || isset( $params['frame']['manualthumb'] );
5297 
5298  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5299  # came to also set the caption, ordinary text after the image -- which
5300  # makes no sense, because that just repeats the text multiple times in
5301  # screen readers. It *also* came to set the title attribute.
5302  # Now that we have an alt attribute, we should not set the alt text to
5303  # equal the caption: that's worse than useless, it just repeats the
5304  # text. This is the framed/thumbnail case. If there's no caption, we
5305  # use the unnamed parameter for alt text as well, just for the time be-
5306  # ing, if the unnamed param is set and the alt param is not.
5307  # For the future, we need to figure out if we want to tweak this more,
5308  # e.g., introducing a title= parameter for the title; ignoring the un-
5309  # named parameter entirely for images without a caption; adding an ex-
5310  # plicit caption= parameter and preserving the old magic unnamed para-
5311  # meter for BC; ...
5312  if ( $imageIsFramed ) { # Framed image
5313  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5314  # No caption or alt text, add the filename as the alt text so
5315  # that screen readers at least get some description of the image
5316  $params['frame']['alt'] = $title->getText();
5317  }
5318  # Do not set $params['frame']['title'] because tooltips don't make sense
5319  # for framed images
5320  } else { # Inline image
5321  if ( !isset( $params['frame']['alt'] ) ) {
5322  # No alt text, use the "caption" for the alt text
5323  if ( $caption !== '' ) {
5324  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5325  } else {
5326  # No caption, fall back to using the filename for the
5327  # alt text
5328  $params['frame']['alt'] = $title->getText();
5329  }
5330  }
5331  # Use the "caption" for the tooltip text
5332  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5333  }
5334 
5335  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5336 
5337  # Linker does the rest
5338  $time = isset( $options['time'] ) ? $options['time'] : false;
5339  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5340  $time, $descQuery, $this->mOptions->getThumbSize() );
5341 
5342  # Give the handler a chance to modify the parser object
5343  if ( $handler ) {
5344  $handler->parserTransformHook( $this, $file );
5345  }
5346 
5347  return $ret;
5348  }
5349 
5355  protected function stripAltText( $caption, $holders ) {
5356  # Strip bad stuff out of the title (tooltip). We can't just use
5357  # replaceLinkHoldersText() here, because if this function is called
5358  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5359  if ( $holders ) {
5360  $tooltip = $holders->replaceText( $caption );
5361  } else {
5362  $tooltip = $this->replaceLinkHoldersText( $caption );
5363  }
5364 
5365  # make sure there are no placeholders in thumbnail attributes
5366  # that are later expanded to html- so expand them now and
5367  # remove the tags
5368  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5369  $tooltip = Sanitizer::stripAllTags( $tooltip );
5370 
5371  return $tooltip;
5372  }
5373 
5379  public function disableCache() {
5380  wfDebug( "Parser output marked as uncacheable.\n" );
5381  if ( !$this->mOutput ) {
5382  throw new MWException( __METHOD__ .
5383  " can only be called when actually parsing something" );
5384  }
5385  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5386  }
5387 
5396  public function attributeStripCallback( &$text, $frame = false ) {
5397  $text = $this->replaceVariables( $text, $frame );
5398  $text = $this->mStripState->unstripBoth( $text );
5399  return $text;
5400  }
5401 
5407  public function getTags() {
5408  return array_merge(
5409  array_keys( $this->mTransparentTagHooks ),
5410  array_keys( $this->mTagHooks ),
5411  array_keys( $this->mFunctionTagHooks )
5412  );
5413  }
5414 
5425  public function replaceTransparentTags( $text ) {
5426  $matches = [];
5427  $elements = array_keys( $this->mTransparentTagHooks );
5428  $text = self::extractTagsAndParams( $elements, $text, $matches );
5429  $replacements = [];
5430 
5431  foreach ( $matches as $marker => $data ) {
5432  list( $element, $content, $params, $tag ) = $data;
5433  $tagName = strtolower( $element );
5434  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5435  $output = call_user_func_array(
5436  $this->mTransparentTagHooks[$tagName],
5437  [ $content, $params, $this ]
5438  );
5439  } else {
5440  $output = $tag;
5441  }
5442  $replacements[$marker] = $output;
5443  }
5444  return strtr( $text, $replacements );
5445  }
5446 
5476  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5477  global $wgTitle; # not generally used but removes an ugly failure mode
5478 
5479  $magicScopeVariable = $this->lock();
5480  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5481  $outText = '';
5482  $frame = $this->getPreprocessor()->newFrame();
5483 
5484  # Process section extraction flags
5485  $flags = 0;
5486  $sectionParts = explode( '-', $sectionId );
5487  $sectionIndex = array_pop( $sectionParts );
5488  foreach ( $sectionParts as $part ) {
5489  if ( $part === 'T' ) {
5490  $flags |= self::PTD_FOR_INCLUSION;
5491  }
5492  }
5493 
5494  # Check for empty input
5495  if ( strval( $text ) === '' ) {
5496  # Only sections 0 and T-0 exist in an empty document
5497  if ( $sectionIndex == 0 ) {
5498  if ( $mode === 'get' ) {
5499  return '';
5500  } else {
5501  return $newText;
5502  }
5503  } else {
5504  if ( $mode === 'get' ) {
5505  return $newText;
5506  } else {
5507  return $text;
5508  }
5509  }
5510  }
5511 
5512  # Preprocess the text
5513  $root = $this->preprocessToDom( $text, $flags );
5514 
5515  # <h> nodes indicate section breaks
5516  # They can only occur at the top level, so we can find them by iterating the root's children
5517  $node = $root->getFirstChild();
5518 
5519  # Find the target section
5520  if ( $sectionIndex == 0 ) {
5521  # Section zero doesn't nest, level=big
5522  $targetLevel = 1000;
5523  } else {
5524  while ( $node ) {
5525  if ( $node->getName() === 'h' ) {
5526  $bits = $node->splitHeading();
5527  if ( $bits['i'] == $sectionIndex ) {
5528  $targetLevel = $bits['level'];
5529  break;
5530  }
5531  }
5532  if ( $mode === 'replace' ) {
5533  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5534  }
5535  $node = $node->getNextSibling();
5536  }
5537  }
5538 
5539  if ( !$node ) {
5540  # Not found
5541  if ( $mode === 'get' ) {
5542  return $newText;
5543  } else {
5544  return $text;
5545  }
5546  }
5547 
5548  # Find the end of the section, including nested sections
5549  do {
5550  if ( $node->getName() === 'h' ) {
5551  $bits = $node->splitHeading();
5552  $curLevel = $bits['level'];
5553  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5554  break;
5555  }
5556  }
5557  if ( $mode === 'get' ) {
5558  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5559  }
5560  $node = $node->getNextSibling();
5561  } while ( $node );
5562 
5563  # Write out the remainder (in replace mode only)
5564  if ( $mode === 'replace' ) {
5565  # Output the replacement text
5566  # Add two newlines on -- trailing whitespace in $newText is conventionally
5567  # stripped by the editor, so we need both newlines to restore the paragraph gap
5568  # Only add trailing whitespace if there is newText
5569  if ( $newText != "" ) {
5570  $outText .= $newText . "\n\n";
5571  }
5572 
5573  while ( $node ) {
5574  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5575  $node = $node->getNextSibling();
5576  }
5577  }
5578 
5579  if ( is_string( $outText ) ) {
5580  # Re-insert stripped tags
5581  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5582  }
5583 
5584  return $outText;
5585  }
5586 
5601  public function getSection( $text, $sectionId, $defaultText = '' ) {
5602  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5603  }
5604 
5617  public function replaceSection( $oldText, $sectionId, $newText ) {
5618  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5619  }
5620 
5626  public function getRevisionId() {
5627  return $this->mRevisionId;
5628  }
5629 
5636  public function getRevisionObject() {
5637  if ( !is_null( $this->mRevisionObject ) ) {
5638  return $this->mRevisionObject;
5639  }
5640  if ( is_null( $this->mRevisionId ) ) {
5641  return null;
5642  }
5643 
5644  $rev = call_user_func(
5645  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5646  );
5647 
5648  # If the parse is for a new revision, then the callback should have
5649  # already been set to force the object and should match mRevisionId.
5650  # If not, try to fetch by mRevisionId for sanity.
5651  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5652  $rev = Revision::newFromId( $this->mRevisionId );
5653  }
5654 
5655  $this->mRevisionObject = $rev;
5656 
5657  return $this->mRevisionObject;
5658  }
5659 
5665  public function getRevisionTimestamp() {
5666  if ( is_null( $this->mRevisionTimestamp ) ) {
5668 
5669  $revObject = $this->getRevisionObject();
5670  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5671 
5672  # The cryptic '' timezone parameter tells to use the site-default
5673  # timezone offset instead of the user settings.
5674  # Since this value will be saved into the parser cache, served
5675  # to other users, and potentially even used inside links and such,
5676  # it needs to be consistent for all visitors.
5677  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5678 
5679  }
5680  return $this->mRevisionTimestamp;
5681  }
5682 
5688  public function getRevisionUser() {
5689  if ( is_null( $this->mRevisionUser ) ) {
5690  $revObject = $this->getRevisionObject();
5691 
5692  # if this template is subst: the revision id will be blank,
5693  # so just use the current user's name
5694  if ( $revObject ) {
5695  $this->mRevisionUser = $revObject->getUserText();
5696  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5697  $this->mRevisionUser = $this->getUser()->getName();
5698  }
5699  }
5700  return $this->mRevisionUser;
5701  }
5702 
5708  public function getRevisionSize() {
5709  if ( is_null( $this->mRevisionSize ) ) {
5710  $revObject = $this->getRevisionObject();
5711 
5712  # if this variable is subst: the revision id will be blank,
5713  # so just use the parser input size, because the own substituation
5714  # will change the size.
5715  if ( $revObject ) {
5716  $this->mRevisionSize = $revObject->getSize();
5717  } else {
5718  $this->mRevisionSize = $this->mInputSize;
5719  }
5720  }
5721  return $this->mRevisionSize;
5722  }
5723 
5729  public function setDefaultSort( $sort ) {
5730  $this->mDefaultSort = $sort;
5731  $this->mOutput->setProperty( 'defaultsort', $sort );
5732  }
5733 
5744  public function getDefaultSort() {
5745  if ( $this->mDefaultSort !== false ) {
5746  return $this->mDefaultSort;
5747  } else {
5748  return '';
5749  }
5750  }
5751 
5758  public function getCustomDefaultSort() {
5759  return $this->mDefaultSort;
5760  }
5761 
5771  public function guessSectionNameFromWikiText( $text ) {
5772  # Strip out wikitext links(they break the anchor)
5773  $text = $this->stripSectionName( $text );
5775  $text = Sanitizer::decodeCharReferences( $text );
5776  return '#' . Sanitizer::escapeIdForLink( $text );
5777  }
5778 
5788  public function guessLegacySectionNameFromWikiText( $text ) {
5790 
5791  # Strip out wikitext links(they break the anchor)
5792  $text = $this->stripSectionName( $text );
5794  $text = Sanitizer::decodeCharReferences( $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 
5820  public function stripSectionName( $text ) {
5821  # Strip internal link markup
5822  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5823  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5824 
5825  # Strip external link markup
5826  # @todo FIXME: Not tolerant to blank link text
5827  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5828  # on how many empty links there are on the page - need to figure that out.
5829  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5830 
5831  # Parse wikitext quotes (italics & bold)
5832  $text = $this->doQuotes( $text );
5833 
5834  # Strip HTML tags
5835  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5836  return $text;
5837  }
5838 
5849  public function testSrvus( $text, Title $title, ParserOptions $options,
5850  $outputType = self::OT_HTML
5851  ) {
5852  $magicScopeVariable = $this->lock();
5853  $this->startParse( $title, $options, $outputType, true );
5854 
5855  $text = $this->replaceVariables( $text );
5856  $text = $this->mStripState->unstripBoth( $text );
5857  $text = Sanitizer::removeHTMLtags( $text );
5858  return $text;
5859  }
5860 
5867  public function testPst( $text, Title $title, ParserOptions $options ) {
5868  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5869  }
5870 
5877  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5878  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5879  }
5880 
5897  public function markerSkipCallback( $s, $callback ) {
5898  $i = 0;
5899  $out = '';
5900  while ( $i < strlen( $s ) ) {
5901  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5902  if ( $markerStart === false ) {
5903  $out .= call_user_func( $callback, substr( $s, $i ) );
5904  break;
5905  } else {
5906  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5907  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5908  if ( $markerEnd === false ) {
5909  $out .= substr( $s, $markerStart );
5910  break;
5911  } else {
5912  $markerEnd += strlen( self::MARKER_SUFFIX );
5913  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5914  $i = $markerEnd;
5915  }
5916  }
5917  }
5918  return $out;
5919  }
5920 
5927  public function killMarkers( $text ) {
5928  return $this->mStripState->killMarkers( $text );
5929  }
5930 
5947  public function serializeHalfParsedText( $text ) {
5948  $data = [
5949  'text' => $text,
5950  'version' => self::HALF_PARSED_VERSION,
5951  'stripState' => $this->mStripState->getSubState( $text ),
5952  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5953  ];
5954  return $data;
5955  }
5956 
5972  public function unserializeHalfParsedText( $data ) {
5973  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5974  throw new MWException( __METHOD__ . ': invalid version' );
5975  }
5976 
5977  # First, extract the strip state.
5978  $texts = [ $data['text'] ];
5979  $texts = $this->mStripState->merge( $data['stripState'], $texts );
5980 
5981  # Now renumber links
5982  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
5983 
5984  # Should be good to go.
5985  return $texts[0];
5986  }
5987 
5997  public function isValidHalfParsedText( $data ) {
5998  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
5999  }
6000 
6009  public function parseWidthParam( $value ) {
6010  $parsedWidthParam = [];
6011  if ( $value === '' ) {
6012  return $parsedWidthParam;
6013  }
6014  $m = [];
6015  # (T15500) In both cases (width/height and width only),
6016  # permit trailing "px" for backward compatibility.
6017  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6018  $width = intval( $m[1] );
6019  $height = intval( $m[2] );
6020  $parsedWidthParam['width'] = $width;
6021  $parsedWidthParam['height'] = $height;
6022  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6023  $width = intval( $value );
6024  $parsedWidthParam['width'] = $width;
6025  }
6026  return $parsedWidthParam;
6027  }
6028 
6038  protected function lock() {
6039  if ( $this->mInParse ) {
6040  throw new MWException( "Parser state cleared while parsing. "
6041  . "Did you call Parser::parse recursively? Lock is held by: " . $this->mInParse );
6042  }
6043 
6044  // Save the backtrace when locking, so that if some code tries locking again,
6045  // we can print the lock owner's backtrace for easier debugging
6046  $e = new Exception;
6047  $this->mInParse = $e->getTraceAsString();
6048 
6049  $recursiveCheck = new ScopedCallback( function () {
6050  $this->mInParse = false;
6051  } );
6052 
6053  return $recursiveCheck;
6054  }
6055 
6066  public static function stripOuterParagraph( $html ) {
6067  $m = [];
6068  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6069  if ( strpos( $m[1], '</p>' ) === false ) {
6070  $html = $m[1];
6071  }
6072  }
6073 
6074  return $html;
6075  }
6076 
6087  public function getFreshParser() {
6088  global $wgParserConf;
6089  if ( $this->mInParse ) {
6090  return new $wgParserConf['class']( $wgParserConf );
6091  } else {
6092  return $this;
6093  }
6094  }
6095 
6102  public function enableOOUI() {
6104  $this->mOutput->setEnableOOUI( true );
6105  }
6106 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5636
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:550
setTitle($t)
Set the context title.
Definition: Parser.php:754
$mAutonumber
Definition: Parser.php:179
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3420
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5897
$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:272
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:5997
null means default in associative array form
Definition: hooks.txt:1966
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:1966
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:5601
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1423
$mTplRedirCache
Definition: Parser.php:195
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5927
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:3517
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:782
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5688
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:5820
const OT_PREPROCESS
Definition: Defines.php:187
either a plain
Definition: hooks.txt:2027
$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:2556
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:4748
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4577
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:3331
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4914
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:1966
getImageParams($handler)
Definition: Parser.php:5084
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:5407
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:2142
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3662
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:2835
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:4849
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:2199
setFunctionTagHook($tag, callable $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4863
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3765
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5849
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:3687
null for the local wiki Added in
Definition: hooks.txt:1582
There are three types of nodes:
$mHeadings
Definition: Parser.php:195
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4762
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:3706
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4448
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead, if possible.
Definition: Parser.php:5788
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:2803
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5947
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4886
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3500
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:2865
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:2987
static cleanUrl($url)
Definition: Sanitizer.php:2003
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
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
Definition: Sanitizer.php:1970
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:2462
$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:2477
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:636
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2556
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:295
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5626
const OT_PREPROCESS
Definition: Parser.php:114
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2450
$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:4897
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:2939
$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:1964
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:308
static splitTrail($trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition: Linker.php:1650
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3434
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2983
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1636
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4591
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:4642
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:3998
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:5379
$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:1517
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:5665
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6066
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:6009
$mStripList
Definition: Parser.php:148
$mFunctionTagHooks
Definition: Parser.php:147
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3725
const OT_PLAIN
Definition: Defines.php:189
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3477
$mRevisionTimestamp
Definition: Parser.php:220
$mImageParams
Definition: Parser.php:151
stripAltText($caption, $holders)
Definition: Parser.php:5355
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:2910
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:3918
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:4654
$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:1966
$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:4717
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:5708
$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:1966
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:4413
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:933
static newKnownCurrent(IDatabase $db, $pageId, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1940
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:4018
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:4528
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:1966
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:2428
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:1751
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2877
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:258
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6102
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3545
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:2437
const OT_HTML
Definition: Defines.php:185
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:285
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:4673
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:989
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:1248
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:782
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc next in line in page history
Definition: hooks.txt:1751
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:5425
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:5617
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3945
$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:5877
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:1536
setFunctionHook($id, callable $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4811
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3340
$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:6087
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:6038
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:2404
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:1275
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3558
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:2983
$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:2582
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:5476
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:4628
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5729
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3651
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:1246
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:3932
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
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5867
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.
if(!$wgRequest->checkUrlExtension()) if(!$wgEnableAPI) $wgTitle
Definition: api.php:57
static explode($separator, $subject)
Workalike for explode() with limited memory usage.
ParserOptions $mOptions
Definition: Parser.php:210
parse($text, Title $title, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
Definition: Parser.php:405
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to 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:782
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 ContentHandler::unserializeContent() can be used to create a Content object of the appropriate type.However, it is recommended to instead use WikiPage::getContent() resp.Revision::getContent() to get a page's content as a Content object.These two methods should be the ONLY way in which page content is accessed.Another important function of ContentHandler objects is to define custom action handlers for a content model, see ContentHandler::getActionOverrides().This is similar to what WikiPage::getActionOverrides() was already doing.==Serialization==With the ContentHandler facility, page content no longer has to be text based.Objects implementing the Content interface are used to represent and handle the content internally.For storage and data exchange, each content model supports at least one serialization format via ContentHandler::serializeContent($content).The list of supported formats for a given content model can be accessed using ContentHandler::getSupportedFormats().Content serialization formats are identified using MIME type like strings.The following formats are built in:*text/x-wiki-wikitext *text/javascript-for js pages *text/css-for css pages *text/plain-for future use, e.g.with plain text messages.*text/html-for future use, e.g.with plain html messages.*application/vnd.php.serialized-for future use with the api and for extensions *application/json-for future use with the api, and for use by extensions *application/xml-for future use with the api, and for use by extensions In PHP, use the corresponding CONTENT_FORMAT_XXX constant.Note that when using the API to access page content, especially action=edit, action=parse and action=query &prop=revisions, the model and format of the content should always be handled explicitly.Without that information, interpretation of the provided content is not reliable.The same applies to XML dumps generated via maintenance/dumpBackup.php or Special:Export.Also note that the API will provide encapsulated, serialized content-so if the API was called with format=json, and contentformat is also json(or rather, application/json), the page content is represented as a string containing an escaped json structure.Extensions that use JSON to serialize some types of page content may provide specialized API modules that allow access to that content in a more natural form.==Compatibility==The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least for pages that contain wikitext or other text based content.However, a number of functions and hooks have been deprecated in favor of new versions that are aware of the page's content model, and will now generate warnings when used.Most importantly, the following functions have been deprecated:*Revisions::getText() is deprecated in favor Revisions::getContent()*WikiPage::getText() is deprecated in favor WikiPage::getContent() Also, the old Article::getContent()(w