MediaWiki  1.28.3
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 bug 19052
92  const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}]';
93  # Simplified expression to match an IPv4 or IPv6 address, or
94  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
95  const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}])';
96  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
97  // @codingStandardsIgnoreStart Generic.Files.LineLength
98  const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}]+)
99  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
100  // @codingStandardsIgnoreEnd
101 
102  # Regular expression for a non-newline space
103  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
104 
105  # Flags for preprocessToDom
106  const PTD_FOR_INCLUSION = 1;
107 
108  # Allowed values for $this->mOutputType
109  # Parameter to startExternalParse().
110  const OT_HTML = 1; # like parse()
111  const OT_WIKI = 2; # like preSaveTransform()
113  const OT_MSG = 3;
114  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
115 
133  const MARKER_SUFFIX = "-QINU`\"'\x7f";
134  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
135 
136  # Markers used for wrapping the table of contents
137  const TOC_START = '<mw:toc>';
138  const TOC_END = '</mw:toc>';
139 
140  # Persistent:
141  public $mTagHooks = [];
143  public $mFunctionHooks = [];
144  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
145  public $mFunctionTagHooks = [];
146  public $mStripList = [];
147  public $mDefaultStripList = [];
148  public $mVarCache = [];
149  public $mImageParams = [];
151  public $mMarkerIndex = 0;
152  public $mFirstCall = true;
153 
154  # Initialised by initialiseVariables()
155 
159  public $mVariables;
160 
164  public $mSubstWords;
165  # Initialised in constructor
167 
168  # Initialized in getPreprocessor()
169 
171 
172  # Cleared with clearState():
173 
176  public $mOutput;
177  public $mAutonumber;
178 
182  public $mStripState;
183 
189 
190  public $mLinkID;
194  public $mExpensiveFunctionCount; # number of expensive parser function calls
196 
200  public $mUser; # User object; only used when doing pre-save transform
201 
202  # Temporary
203  # These are variables reset at least once per parse regardless of $clearState
204 
208  public $mOptions;
209 
213  public $mTitle; # Title context, used for self-link rendering and similar things
214  public $mOutputType; # Output type, one of the OT_xxx constants
215  public $ot; # Shortcut alias, see setOutputType()
216  public $mRevisionObject; # The revision object of the specified revision ID
217  public $mRevisionId; # ID to display in {{REVISIONID}} tags
218  public $mRevisionTimestamp; # The timestamp of the specified revision ID
219  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
220  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
221  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
222  public $mInputSize = false; # For {{PAGESIZE}} on current page.
223 
228  public $mUniqPrefix = Parser::MARKER_PREFIX;
229 
236 
244 
249  public $mInParse = false;
250 
252  protected $mProfiler;
253 
257  protected $mLinkRenderer;
258 
262  public function __construct( $conf = [] ) {
263  $this->mConf = $conf;
264  $this->mUrlProtocols = wfUrlProtocols();
265  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
266  self::EXT_LINK_ADDR .
267  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
268  if ( isset( $conf['preprocessorClass'] ) ) {
269  $this->mPreprocessorClass = $conf['preprocessorClass'];
270  } elseif ( defined( 'HPHP_VERSION' ) ) {
271  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
272  $this->mPreprocessorClass = 'Preprocessor_Hash';
273  } elseif ( extension_loaded( 'domxml' ) ) {
274  # PECL extension that conflicts with the core DOM extension (bug 13770)
275  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
276  $this->mPreprocessorClass = 'Preprocessor_Hash';
277  } elseif ( extension_loaded( 'dom' ) ) {
278  $this->mPreprocessorClass = 'Preprocessor_DOM';
279  } else {
280  $this->mPreprocessorClass = 'Preprocessor_Hash';
281  }
282  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
283  }
284 
288  public function __destruct() {
289  if ( isset( $this->mLinkHolders ) ) {
290  unset( $this->mLinkHolders );
291  }
292  foreach ( $this as $name => $value ) {
293  unset( $this->$name );
294  }
295  }
296 
300  public function __clone() {
301  $this->mInParse = false;
302 
303  // Bug 56226: When you create a reference "to" an object field, that
304  // makes the object field itself be a reference too (until the other
305  // reference goes out of scope). When cloning, any field that's a
306  // reference is copied as a reference in the new object. Both of these
307  // are defined PHP5 behaviors, as inconvenient as it is for us when old
308  // hooks from PHP4 days are passing fields by reference.
309  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
310  // Make a non-reference copy of the field, then rebind the field to
311  // reference the new copy.
312  $tmp = $this->$k;
313  $this->$k =& $tmp;
314  unset( $tmp );
315  }
316 
317  Hooks::run( 'ParserCloned', [ $this ] );
318  }
319 
323  public function firstCallInit() {
324  if ( !$this->mFirstCall ) {
325  return;
326  }
327  $this->mFirstCall = false;
328 
330  CoreTagHooks::register( $this );
331  $this->initialiseVariables();
332 
333  // Avoid PHP 7.1 warning from passing $this by reference
334  $parser = $this;
335  Hooks::run( 'ParserFirstCallInit', [ &$parser ] );
336  }
337 
343  public function clearState() {
344  if ( $this->mFirstCall ) {
345  $this->firstCallInit();
346  }
347  $this->mOutput = new ParserOutput;
348  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
349  $this->mAutonumber = 0;
350  $this->mIncludeCount = [];
351  $this->mLinkHolders = new LinkHolderArray( $this );
352  $this->mLinkID = 0;
353  $this->mRevisionObject = $this->mRevisionTimestamp =
354  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
355  $this->mVarCache = [];
356  $this->mUser = null;
357  $this->mLangLinkLanguages = [];
358  $this->currentRevisionCache = null;
359 
360  $this->mStripState = new StripState;
361 
362  # Clear these on every parse, bug 4549
363  $this->mTplRedirCache = $this->mTplDomCache = [];
364 
365  $this->mShowToc = true;
366  $this->mForceTocPosition = false;
367  $this->mIncludeSizes = [
368  'post-expand' => 0,
369  'arg' => 0,
370  ];
371  $this->mPPNodeCount = 0;
372  $this->mGeneratedPPNodeCount = 0;
373  $this->mHighestExpansionDepth = 0;
374  $this->mDefaultSort = false;
375  $this->mHeadings = [];
376  $this->mDoubleUnderscores = [];
377  $this->mExpensiveFunctionCount = 0;
378 
379  # Fix cloning
380  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
381  $this->mPreprocessor = null;
382  }
383 
384  $this->mProfiler = new SectionProfiler();
385 
386  // Avoid PHP 7.1 warning from passing $this by reference
387  $parser = $this;
388  Hooks::run( 'ParserClearState', [ &$parser ] );
389  }
390 
403  public function parse(
405  $linestart = true, $clearState = true, $revid = null
406  ) {
412  global $wgShowHostnames;
413 
414  if ( $clearState ) {
415  // We use U+007F DELETE to construct strip markers, so we have to make
416  // sure that this character does not occur in the input text.
417  $text = strtr( $text, "\x7f", "?" );
418  $magicScopeVariable = $this->lock();
419  }
420 
421  $this->startParse( $title, $options, self::OT_HTML, $clearState );
422 
423  $this->currentRevisionCache = null;
424  $this->mInputSize = strlen( $text );
425  if ( $this->mOptions->getEnableLimitReport() ) {
426  $this->mOutput->resetParseStartTime();
427  }
428 
429  $oldRevisionId = $this->mRevisionId;
430  $oldRevisionObject = $this->mRevisionObject;
431  $oldRevisionTimestamp = $this->mRevisionTimestamp;
432  $oldRevisionUser = $this->mRevisionUser;
433  $oldRevisionSize = $this->mRevisionSize;
434  if ( $revid !== null ) {
435  $this->mRevisionId = $revid;
436  $this->mRevisionObject = null;
437  $this->mRevisionTimestamp = null;
438  $this->mRevisionUser = null;
439  $this->mRevisionSize = null;
440  }
441 
442  // Avoid PHP 7.1 warning from passing $this by reference
443  $parser = $this;
444  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
445  # No more strip!
446  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
447  $text = $this->internalParse( $text );
448  Hooks::run( 'ParserAfterParse', [ &$parser, &$text, &$this->mStripState ] );
449 
450  $text = $this->internalParseHalfParsed( $text, true, $linestart );
451 
459  if ( !( $options->getDisableTitleConversion()
460  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
461  || isset( $this->mDoubleUnderscores['notitleconvert'] )
462  || $this->mOutput->getDisplayTitle() !== false )
463  ) {
464  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
465  if ( $convruletitle ) {
466  $this->mOutput->setTitleText( $convruletitle );
467  } else {
468  $titleText = $this->getConverterLanguage()->convertTitle( $title );
469  $this->mOutput->setTitleText( $titleText );
470  }
471  }
472 
473  # Done parsing! Compute runtime adaptive expiry if set
474  $this->mOutput->finalizeAdaptiveCacheExpiry();
475 
476  # Warn if too many heavyweight parser functions were used
477  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
478  $this->limitationWarn( 'expensive-parserfunction',
479  $this->mExpensiveFunctionCount,
480  $this->mOptions->getExpensiveParserFunctionLimit()
481  );
482  }
483 
484  # Information on include size limits, for the benefit of users who try to skirt them
485  if ( $this->mOptions->getEnableLimitReport() ) {
486  $max = $this->mOptions->getMaxIncludeSize();
487 
488  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
489  if ( $cpuTime !== null ) {
490  $this->mOutput->setLimitReportData( 'limitreport-cputime',
491  sprintf( "%.3f", $cpuTime )
492  );
493  }
494 
495  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
496  $this->mOutput->setLimitReportData( 'limitreport-walltime',
497  sprintf( "%.3f", $wallTime )
498  );
499 
500  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
501  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
502  );
503  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
504  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
505  );
506  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
507  [ $this->mIncludeSizes['post-expand'], $max ]
508  );
509  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
510  [ $this->mIncludeSizes['arg'], $max ]
511  );
512  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
513  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
514  );
515  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
516  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
517  );
518  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
519 
520  $limitReport = "NewPP limit report\n";
521  if ( $wgShowHostnames ) {
522  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
523  }
524  $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
525  $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
526  $limitReport .= 'Dynamic content: ' .
527  ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
528  "\n";
529 
530  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
531  if ( Hooks::run( 'ParserLimitReportFormat',
532  [ $key, &$value, &$limitReport, false, false ]
533  ) ) {
534  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
535  $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
536  ->inLanguage( 'en' )->useDatabase( false );
537  if ( !$valueMsg->exists() ) {
538  $valueMsg = new RawMessage( '$1' );
539  }
540  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
541  $valueMsg->params( $value );
542  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
543  }
544  }
545  }
546  // Since we're not really outputting HTML, decode the entities and
547  // then re-encode the things that need hiding inside HTML comments.
548  $limitReport = htmlspecialchars_decode( $limitReport );
549  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
550 
551  // Sanitize for comment. Note '‐' in the replacement is U+2010,
552  // which looks much like the problematic '-'.
553  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
554  $text .= "\n<!-- \n$limitReport-->\n";
555 
556  // Add on template profiling data
557  $dataByFunc = $this->mProfiler->getFunctionStats();
558  uasort( $dataByFunc, function ( $a, $b ) {
559  return $a['real'] < $b['real']; // descending order
560  } );
561  $profileReport = "Transclusion expansion time report (%,ms,calls,template)\n";
562  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
563  $profileReport .= sprintf( "%6.2f%% %8.3f %6d - %s\n",
564  $item['%real'], $item['real'], $item['calls'],
565  htmlspecialchars( $item['name'] ) );
566  }
567  $text .= "\n<!-- \n$profileReport-->\n";
568 
569  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
570  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
571  $this->mTitle->getPrefixedDBkey() );
572  }
573  }
574  $this->mOutput->setText( $text );
575 
576  $this->mRevisionId = $oldRevisionId;
577  $this->mRevisionObject = $oldRevisionObject;
578  $this->mRevisionTimestamp = $oldRevisionTimestamp;
579  $this->mRevisionUser = $oldRevisionUser;
580  $this->mRevisionSize = $oldRevisionSize;
581  $this->mInputSize = false;
582  $this->currentRevisionCache = null;
583 
584  return $this->mOutput;
585  }
586 
609  public function recursiveTagParse( $text, $frame = false ) {
610  // Avoid PHP 7.1 warning from passing $this by reference
611  $parser = $this;
612  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
613  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
614  $text = $this->internalParse( $text, false, $frame );
615  return $text;
616  }
617 
635  public function recursiveTagParseFully( $text, $frame = false ) {
636  $text = $this->recursiveTagParse( $text, $frame );
637  $text = $this->internalParseHalfParsed( $text, false );
638  return $text;
639  }
640 
652  public function preprocess( $text, Title $title = null,
653  ParserOptions $options, $revid = null, $frame = false
654  ) {
655  $magicScopeVariable = $this->lock();
656  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
657  if ( $revid !== null ) {
658  $this->mRevisionId = $revid;
659  }
660  // Avoid PHP 7.1 warning from passing $this by reference
661  $parser = $this;
662  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
663  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
664  $text = $this->replaceVariables( $text, $frame );
665  $text = $this->mStripState->unstripBoth( $text );
666  return $text;
667  }
668 
678  public function recursivePreprocess( $text, $frame = false ) {
679  $text = $this->replaceVariables( $text, $frame );
680  $text = $this->mStripState->unstripBoth( $text );
681  return $text;
682  }
683 
697  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
698  $msg = new RawMessage( $text );
699  $text = $msg->params( $params )->plain();
700 
701  # Parser (re)initialisation
702  $magicScopeVariable = $this->lock();
703  $this->startParse( $title, $options, self::OT_PLAIN, true );
704 
706  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
707  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
708  $text = $this->mStripState->unstripBoth( $text );
709  return $text;
710  }
711 
718  public static function getRandomString() {
719  wfDeprecated( __METHOD__, '1.26' );
720  return wfRandomString( 16 );
721  }
722 
729  public function setUser( $user ) {
730  $this->mUser = $user;
731  }
732 
739  public function uniqPrefix() {
740  wfDeprecated( __METHOD__, '1.26' );
741  return self::MARKER_PREFIX;
742  }
743 
749  public function setTitle( $t ) {
750  if ( !$t ) {
751  $t = Title::newFromText( 'NO TITLE' );
752  }
753 
754  if ( $t->hasFragment() ) {
755  # Strip the fragment to avoid various odd effects
756  $this->mTitle = $t->createFragmentTarget( '' );
757  } else {
758  $this->mTitle = $t;
759  }
760  }
761 
767  public function getTitle() {
768  return $this->mTitle;
769  }
770 
777  public function Title( $x = null ) {
778  return wfSetVar( $this->mTitle, $x );
779  }
780 
786  public function setOutputType( $ot ) {
787  $this->mOutputType = $ot;
788  # Shortcut alias
789  $this->ot = [
790  'html' => $ot == self::OT_HTML,
791  'wiki' => $ot == self::OT_WIKI,
792  'pre' => $ot == self::OT_PREPROCESS,
793  'plain' => $ot == self::OT_PLAIN,
794  ];
795  }
796 
803  public function OutputType( $x = null ) {
804  return wfSetVar( $this->mOutputType, $x );
805  }
806 
812  public function getOutput() {
813  return $this->mOutput;
814  }
815 
821  public function getOptions() {
822  return $this->mOptions;
823  }
824 
831  public function Options( $x = null ) {
832  return wfSetVar( $this->mOptions, $x );
833  }
834 
838  public function nextLinkID() {
839  return $this->mLinkID++;
840  }
841 
845  public function setLinkID( $id ) {
846  $this->mLinkID = $id;
847  }
848 
853  public function getFunctionLang() {
854  return $this->getTargetLanguage();
855  }
856 
866  public function getTargetLanguage() {
867  $target = $this->mOptions->getTargetLanguage();
868 
869  if ( $target !== null ) {
870  return $target;
871  } elseif ( $this->mOptions->getInterfaceMessage() ) {
872  return $this->mOptions->getUserLangObj();
873  } elseif ( is_null( $this->mTitle ) ) {
874  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
875  }
876 
877  return $this->mTitle->getPageLanguage();
878  }
879 
884  public function getConverterLanguage() {
885  return $this->getTargetLanguage();
886  }
887 
894  public function getUser() {
895  if ( !is_null( $this->mUser ) ) {
896  return $this->mUser;
897  }
898  return $this->mOptions->getUser();
899  }
900 
906  public function getPreprocessor() {
907  if ( !isset( $this->mPreprocessor ) ) {
908  $class = $this->mPreprocessorClass;
909  $this->mPreprocessor = new $class( $this );
910  }
911  return $this->mPreprocessor;
912  }
913 
920  public function getLinkRenderer() {
921  if ( !$this->mLinkRenderer ) {
922  $this->mLinkRenderer = MediaWikiServices::getInstance()
923  ->getLinkRendererFactory()->create();
924  $this->mLinkRenderer->setStubThreshold(
925  $this->getOptions()->getStubThreshold()
926  );
927  }
928 
929  return $this->mLinkRenderer;
930  }
931 
953  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
954  if ( $uniq_prefix !== null ) {
955  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
956  }
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 
1052  $lines = StringUtils::explode( "\n", $text );
1053  $out = '';
1054  $td_history = []; # Is currently a td tag open?
1055  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1056  $tr_history = []; # Is currently a tr tag open?
1057  $tr_attributes = []; # history of tr attributes
1058  $has_opened_tr = []; # Did this table open a <tr> element?
1059  $indent_level = 0; # indent level of the table
1060 
1061  foreach ( $lines as $outLine ) {
1062  $line = trim( $outLine );
1063 
1064  if ( $line === '' ) { # empty line, go to next line
1065  $out .= $outLine . "\n";
1066  continue;
1067  }
1068 
1069  $first_character = $line[0];
1070  $first_two = substr( $line, 0, 2 );
1071  $matches = [];
1072 
1073  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1074  # First check if we are starting a new table
1075  $indent_level = strlen( $matches[1] );
1076 
1077  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1078  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1079 
1080  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1081  array_push( $td_history, false );
1082  array_push( $last_tag_history, '' );
1083  array_push( $tr_history, false );
1084  array_push( $tr_attributes, '' );
1085  array_push( $has_opened_tr, false );
1086  } elseif ( count( $td_history ) == 0 ) {
1087  # Don't do any of the following
1088  $out .= $outLine . "\n";
1089  continue;
1090  } elseif ( $first_two === '|}' ) {
1091  # We are ending a table
1092  $line = '</table>' . substr( $line, 2 );
1093  $last_tag = array_pop( $last_tag_history );
1094 
1095  if ( !array_pop( $has_opened_tr ) ) {
1096  $line = "<tr><td></td></tr>{$line}";
1097  }
1098 
1099  if ( array_pop( $tr_history ) ) {
1100  $line = "</tr>{$line}";
1101  }
1102 
1103  if ( array_pop( $td_history ) ) {
1104  $line = "</{$last_tag}>{$line}";
1105  }
1106  array_pop( $tr_attributes );
1107  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1108  } elseif ( $first_two === '|-' ) {
1109  # Now we have a table row
1110  $line = preg_replace( '#^\|-+#', '', $line );
1111 
1112  # Whats after the tag is now only attributes
1113  $attributes = $this->mStripState->unstripBoth( $line );
1114  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1115  array_pop( $tr_attributes );
1116  array_push( $tr_attributes, $attributes );
1117 
1118  $line = '';
1119  $last_tag = array_pop( $last_tag_history );
1120  array_pop( $has_opened_tr );
1121  array_push( $has_opened_tr, true );
1122 
1123  if ( array_pop( $tr_history ) ) {
1124  $line = '</tr>';
1125  }
1126 
1127  if ( array_pop( $td_history ) ) {
1128  $line = "</{$last_tag}>{$line}";
1129  }
1130 
1131  $outLine = $line;
1132  array_push( $tr_history, false );
1133  array_push( $td_history, false );
1134  array_push( $last_tag_history, '' );
1135  } elseif ( $first_character === '|'
1136  || $first_character === '!'
1137  || $first_two === '|+'
1138  ) {
1139  # This might be cell elements, td, th or captions
1140  if ( $first_two === '|+' ) {
1141  $first_character = '+';
1142  $line = substr( $line, 2 );
1143  } else {
1144  $line = substr( $line, 1 );
1145  }
1146 
1147  // Implies both are valid for table headings.
1148  if ( $first_character === '!' ) {
1149  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1150  }
1151 
1152  # Split up multiple cells on the same line.
1153  # FIXME : This can result in improper nesting of tags processed
1154  # by earlier parser steps.
1155  $cells = explode( '||', $line );
1156 
1157  $outLine = '';
1158 
1159  # Loop through each table cell
1160  foreach ( $cells as $cell ) {
1161  $previous = '';
1162  if ( $first_character !== '+' ) {
1163  $tr_after = array_pop( $tr_attributes );
1164  if ( !array_pop( $tr_history ) ) {
1165  $previous = "<tr{$tr_after}>\n";
1166  }
1167  array_push( $tr_history, true );
1168  array_push( $tr_attributes, '' );
1169  array_pop( $has_opened_tr );
1170  array_push( $has_opened_tr, true );
1171  }
1172 
1173  $last_tag = array_pop( $last_tag_history );
1174 
1175  if ( array_pop( $td_history ) ) {
1176  $previous = "</{$last_tag}>\n{$previous}";
1177  }
1178 
1179  if ( $first_character === '|' ) {
1180  $last_tag = 'td';
1181  } elseif ( $first_character === '!' ) {
1182  $last_tag = 'th';
1183  } elseif ( $first_character === '+' ) {
1184  $last_tag = 'caption';
1185  } else {
1186  $last_tag = '';
1187  }
1188 
1189  array_push( $last_tag_history, $last_tag );
1190 
1191  # A cell could contain both parameters and data
1192  $cell_data = explode( '|', $cell, 2 );
1193 
1194  # Bug 553: Note that a '|' inside an invalid link should not
1195  # be mistaken as delimiting cell parameters
1196  if ( strpos( $cell_data[0], '[[' ) !== false ) {
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 
1255  $origText = $text;
1256 
1257  // Avoid PHP 7.1 warning from passing $this by reference
1258  $parser = $this;
1259 
1260  # Hook to suspend the parser in this state
1261  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1262  return $text;
1263  }
1264 
1265  # if $frame is provided, then use $frame for replacing any variables
1266  if ( $frame ) {
1267  # use frame depth to infer how include/noinclude tags should be handled
1268  # depth=0 means this is the top-level document; otherwise it's an included document
1269  if ( !$frame->depth ) {
1270  $flag = 0;
1271  } else {
1272  $flag = Parser::PTD_FOR_INCLUSION;
1273  }
1274  $dom = $this->preprocessToDom( $text, $flag );
1275  $text = $frame->expand( $dom );
1276  } else {
1277  # if $frame is not provided, then use old-style replaceVariables
1278  $text = $this->replaceVariables( $text );
1279  }
1280 
1281  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1282  $text = Sanitizer::removeHTMLtags(
1283  $text,
1284  [ $this, 'attributeStripCallback' ],
1285  false,
1286  array_keys( $this->mTransparentTagHooks ),
1287  [],
1288  [ $this, 'addTrackingCategory' ]
1289  );
1290  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1291 
1292  # Tables need to come after variable replacement for things to work
1293  # properly; putting them before other transformations should keep
1294  # exciting things like link expansions from showing up in surprising
1295  # places.
1296  $text = $this->doTableStuff( $text );
1297 
1298  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1299 
1300  $text = $this->doDoubleUnderscore( $text );
1301 
1302  $text = $this->doHeadings( $text );
1303  $text = $this->replaceInternalLinks( $text );
1304  $text = $this->doAllQuotes( $text );
1305  $text = $this->replaceExternalLinks( $text );
1306 
1307  # replaceInternalLinks may sometimes leave behind
1308  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1309  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1310 
1311  $text = $this->doMagicLinks( $text );
1312  $text = $this->formatHeadings( $text, $origText, $isMain );
1313 
1314  return $text;
1315  }
1316 
1326  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1327  $text = $this->mStripState->unstripGeneral( $text );
1328 
1329  // Avoid PHP 7.1 warning from passing $this by reference
1330  $parser = $this;
1331 
1332  if ( $isMain ) {
1333  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1334  }
1335 
1336  # Clean up special characters, only run once, next-to-last before doBlockLevels
1337  $fixtags = [
1338  # french spaces, last one Guillemet-left
1339  # only if there is something before the space
1340  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1341  # french spaces, Guillemet-right
1342  '/(\\302\\253) /' => '\\1&#160;',
1343  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
1344  ];
1345  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1346 
1347  $text = $this->doBlockLevels( $text, $linestart );
1348 
1349  $this->replaceLinkHolders( $text );
1350 
1358  if ( !( $this->mOptions->getDisableContentConversion()
1359  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1360  ) {
1361  if ( !$this->mOptions->getInterfaceMessage() ) {
1362  # The position of the convert() call should not be changed. it
1363  # assumes that the links are all replaced and the only thing left
1364  # is the <nowiki> mark.
1365  $text = $this->getConverterLanguage()->convert( $text );
1366  }
1367  }
1368 
1369  $text = $this->mStripState->unstripNoWiki( $text );
1370 
1371  if ( $isMain ) {
1372  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1373  }
1374 
1375  $text = $this->replaceTransparentTags( $text );
1376  $text = $this->mStripState->unstripGeneral( $text );
1377 
1378  $text = Sanitizer::normalizeCharReferences( $text );
1379 
1380  if ( MWTidy::isEnabled() ) {
1381  if ( $this->mOptions->getTidy() ) {
1382  $text = MWTidy::tidy( $text );
1383  }
1384  } else {
1385  # attempt to sanitize at least some nesting problems
1386  # (bug #2702 and quite a few others)
1387  $tidyregs = [
1388  # ''Something [http://www.cool.com cool''] -->
1389  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1390  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1391  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1392  # fix up an anchor inside another anchor, only
1393  # at least for a single single nested link (bug 3695)
1394  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1395  '\\1\\2</a>\\3</a>\\1\\4</a>',
1396  # fix div inside inline elements- doBlockLevels won't wrap a line which
1397  # contains a div, so fix it up here; replace
1398  # div with escaped text
1399  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1400  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1401  # remove empty italic or bold tag pairs, some
1402  # introduced by rules above
1403  '/<([bi])><\/\\1>/' => '',
1404  ];
1405 
1406  $text = preg_replace(
1407  array_keys( $tidyregs ),
1408  array_values( $tidyregs ),
1409  $text );
1410  }
1411 
1412  if ( $isMain ) {
1413  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1414  }
1415 
1416  return $text;
1417  }
1418 
1430  public function doMagicLinks( $text ) {
1431  $prots = wfUrlProtocolsWithoutProtRel();
1432  $urlChar = self::EXT_LINK_URL_CLASS;
1433  $addr = self::EXT_LINK_ADDR;
1434  $space = self::SPACE_NOT_NL; # non-newline space
1435  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1436  $spaces = "$space++"; # possessive match of 1 or more spaces
1437  $text = preg_replace_callback(
1438  '!(?: # Start cases
1439  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1440  (<.*?>) | # m[2]: Skip stuff inside
1441  # HTML elements' . "
1442  (\b(?i:$prots)($addr$urlChar*)) | # m[3]: Free external links
1443  # m[4]: Post-protocol path
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  // (bug 13227: ''''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  // (bug 13227: ''''''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 
1830  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1831  if ( $bits === false ) {
1832  throw new MWException( "PCRE needs to be compiled with "
1833  . "--enable-unicode-properties in order for MediaWiki to function" );
1834  }
1835  $s = array_shift( $bits );
1836 
1837  $i = 0;
1838  while ( $i < count( $bits ) ) {
1839  $url = $bits[$i++];
1840  $i++; // protocol
1841  $text = $bits[$i++];
1842  $trail = $bits[$i++];
1843 
1844  # The characters '<' and '>' (which were escaped by
1845  # removeHTMLtags()) should not be included in
1846  # URLs, per RFC 2396.
1847  $m2 = [];
1848  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1849  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1850  $url = substr( $url, 0, $m2[0][1] );
1851  }
1852 
1853  # If the link text is an image URL, replace it with an <img> tag
1854  # This happened by accident in the original parser, but some people used it extensively
1855  $img = $this->maybeMakeExternalImage( $text );
1856  if ( $img !== false ) {
1857  $text = $img;
1858  }
1859 
1860  $dtrail = '';
1861 
1862  # Set linktype for CSS - if URL==text, link is essentially free
1863  $linktype = ( $text === $url ) ? 'free' : 'text';
1864 
1865  # No link text, e.g. [http://domain.tld/some.link]
1866  if ( $text == '' ) {
1867  # Autonumber
1868  $langObj = $this->getTargetLanguage();
1869  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1870  $linktype = 'autonumber';
1871  } else {
1872  # Have link text, e.g. [http://domain.tld/some.link text]s
1873  # Check for trail
1874  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1875  }
1876 
1877  $text = $this->getConverterLanguage()->markNoConversion( $text );
1878 
1879  $url = Sanitizer::cleanUrl( $url );
1880 
1881  # Use the encoded URL
1882  # This means that users can paste URLs directly into the text
1883  # Funny characters like ö aren't valid in URLs anyway
1884  # This was changed in August 2004
1885  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1886  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1887 
1888  # Register link in the output object.
1889  $this->mOutput->addExternalLink( $url );
1890  }
1891 
1892  return $s;
1893  }
1894 
1904  public static function getExternalLinkRel( $url = false, $title = null ) {
1905  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1906  $ns = $title ? $title->getNamespace() : false;
1907  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1908  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1909  ) {
1910  return 'nofollow';
1911  }
1912  return null;
1913  }
1914 
1925  public function getExternalLinkAttribs( $url ) {
1926  $attribs = [];
1927  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1928 
1929  $target = $this->mOptions->getExternalLinkTarget();
1930  if ( $target ) {
1931  $attribs['target'] = $target;
1932  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1933  // T133507. New windows can navigate parent cross-origin.
1934  // Including noreferrer due to lacking browser
1935  // support of noopener. Eventually noreferrer should be removed.
1936  if ( $rel !== '' ) {
1937  $rel .= ' ';
1938  }
1939  $rel .= 'noreferrer noopener';
1940  }
1941  }
1942  $attribs['rel'] = $rel;
1943  return $attribs;
1944  }
1945 
1953  public static function replaceUnusualEscapes( $url ) {
1954  wfDeprecated( __METHOD__, '1.24' );
1955  return self::normalizeLinkUrl( $url );
1956  }
1957 
1967  public static function normalizeLinkUrl( $url ) {
1968  # First, make sure unsafe characters are encoded
1969  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1970  function ( $m ) {
1971  return rawurlencode( $m[0] );
1972  },
1973  $url
1974  );
1975 
1976  $ret = '';
1977  $end = strlen( $url );
1978 
1979  # Fragment part - 'fragment'
1980  $start = strpos( $url, '#' );
1981  if ( $start !== false && $start < $end ) {
1982  $ret = self::normalizeUrlComponent(
1983  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1984  $end = $start;
1985  }
1986 
1987  # Query part - 'query' minus &=+;
1988  $start = strpos( $url, '?' );
1989  if ( $start !== false && $start < $end ) {
1990  $ret = self::normalizeUrlComponent(
1991  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
1992  $end = $start;
1993  }
1994 
1995  # Scheme and path part - 'pchar'
1996  # (we assume no userinfo or encoded colons in the host)
1997  $ret = self::normalizeUrlComponent(
1998  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
1999 
2000  return $ret;
2001  }
2002 
2003  private static function normalizeUrlComponent( $component, $unsafe ) {
2004  $callback = function ( $matches ) use ( $unsafe ) {
2005  $char = urldecode( $matches[0] );
2006  $ord = ord( $char );
2007  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2008  # Unescape it
2009  return $char;
2010  } else {
2011  # Leave it escaped, but use uppercase for a-f
2012  return strtoupper( $matches[0] );
2013  }
2014  };
2015  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2016  }
2017 
2026  private function maybeMakeExternalImage( $url ) {
2027  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2028  $imagesexception = !empty( $imagesfrom );
2029  $text = false;
2030  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2031  if ( $imagesexception && is_array( $imagesfrom ) ) {
2032  $imagematch = false;
2033  foreach ( $imagesfrom as $match ) {
2034  if ( strpos( $url, $match ) === 0 ) {
2035  $imagematch = true;
2036  break;
2037  }
2038  }
2039  } elseif ( $imagesexception ) {
2040  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2041  } else {
2042  $imagematch = false;
2043  }
2044 
2045  if ( $this->mOptions->getAllowExternalImages()
2046  || ( $imagesexception && $imagematch )
2047  ) {
2048  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2049  # Image found
2050  $text = Linker::makeExternalImage( $url );
2051  }
2052  }
2053  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2054  && preg_match( self::EXT_IMAGE_REGEX, $url )
2055  ) {
2056  $whitelist = explode(
2057  "\n",
2058  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2059  );
2060 
2061  foreach ( $whitelist as $entry ) {
2062  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2063  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2064  continue;
2065  }
2066  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2067  # Image matches a whitelist entry
2068  $text = Linker::makeExternalImage( $url );
2069  break;
2070  }
2071  }
2072  }
2073  return $text;
2074  }
2075 
2085  public function replaceInternalLinks( $s ) {
2086  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2087  return $s;
2088  }
2089 
2098  public function replaceInternalLinks2( &$s ) {
2100 
2101  static $tc = false, $e1, $e1_img;
2102  # the % is needed to support urlencoded titles as well
2103  if ( !$tc ) {
2104  $tc = Title::legalChars() . '#%';
2105  # Match a link having the form [[namespace:link|alternate]]trail
2106  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2107  # Match cases where there is no "]]", which might still be images
2108  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2109  }
2110 
2111  $holders = new LinkHolderArray( $this );
2112 
2113  # split the entire text string on occurrences of [[
2114  $a = StringUtils::explode( '[[', ' ' . $s );
2115  # get the first element (all text up to first [[), and remove the space we added
2116  $s = $a->current();
2117  $a->next();
2118  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2119  $s = substr( $s, 1 );
2120 
2121  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2122  $e2 = null;
2123  if ( $useLinkPrefixExtension ) {
2124  # Match the end of a line for a word that's not followed by whitespace,
2125  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2127  $charset = $wgContLang->linkPrefixCharset();
2128  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2129  }
2130 
2131  if ( is_null( $this->mTitle ) ) {
2132  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2133  }
2134  $nottalk = !$this->mTitle->isTalkPage();
2135 
2136  if ( $useLinkPrefixExtension ) {
2137  $m = [];
2138  if ( preg_match( $e2, $s, $m ) ) {
2139  $first_prefix = $m[2];
2140  } else {
2141  $first_prefix = false;
2142  }
2143  } else {
2144  $prefix = '';
2145  }
2146 
2147  $useSubpages = $this->areSubpagesAllowed();
2148 
2149  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2150  # Loop for each link
2151  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2152  // @codingStandardsIgnoreEnd
2153 
2154  # Check for excessive memory usage
2155  if ( $holders->isBig() ) {
2156  # Too big
2157  # Do the existence check, replace the link holders and clear the array
2158  $holders->replace( $s );
2159  $holders->clear();
2160  }
2161 
2162  if ( $useLinkPrefixExtension ) {
2163  if ( preg_match( $e2, $s, $m ) ) {
2164  $prefix = $m[2];
2165  $s = $m[1];
2166  } else {
2167  $prefix = '';
2168  }
2169  # first link
2170  if ( $first_prefix ) {
2171  $prefix = $first_prefix;
2172  $first_prefix = false;
2173  }
2174  }
2175 
2176  $might_be_img = false;
2177 
2178  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2179  $text = $m[2];
2180  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2181  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2182  # the real problem is with the $e1 regex
2183  # See bug 1300.
2184  # Still some problems for cases where the ] is meant to be outside punctuation,
2185  # and no image is in sight. See bug 2095.
2186  if ( $text !== ''
2187  && substr( $m[3], 0, 1 ) === ']'
2188  && strpos( $text, '[' ) !== false
2189  ) {
2190  $text .= ']'; # so that replaceExternalLinks($text) works later
2191  $m[3] = substr( $m[3], 1 );
2192  }
2193  # fix up urlencoded title texts
2194  if ( strpos( $m[1], '%' ) !== false ) {
2195  # Should anchors '#' also be rejected?
2196  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2197  }
2198  $trail = $m[3];
2199  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2200  # Invalid, but might be an image with a link in its caption
2201  $might_be_img = true;
2202  $text = $m[2];
2203  if ( strpos( $m[1], '%' ) !== false ) {
2204  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2205  }
2206  $trail = "";
2207  } else { # Invalid form; output directly
2208  $s .= $prefix . '[[' . $line;
2209  continue;
2210  }
2211 
2212  $origLink = $m[1];
2213 
2214  # Don't allow internal links to pages containing
2215  # PROTO: where PROTO is a valid URL protocol; these
2216  # should be external links.
2217  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2218  $s .= $prefix . '[[' . $line;
2219  continue;
2220  }
2221 
2222  # Make subpage if necessary
2223  if ( $useSubpages ) {
2224  $link = $this->maybeDoSubpageLink( $origLink, $text );
2225  } else {
2226  $link = $origLink;
2227  }
2228 
2229  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2230  if ( !$noforce ) {
2231  # Strip off leading ':'
2232  $link = substr( $link, 1 );
2233  }
2234 
2235  $unstrip = $this->mStripState->unstripNoWiki( $link );
2236  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2237  if ( $nt === null ) {
2238  $s .= $prefix . '[[' . $line;
2239  continue;
2240  }
2241 
2242  $ns = $nt->getNamespace();
2243  $iw = $nt->getInterwiki();
2244 
2245  if ( $might_be_img ) { # if this is actually an invalid link
2246  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2247  $found = false;
2248  while ( true ) {
2249  # look at the next 'line' to see if we can close it there
2250  $a->next();
2251  $next_line = $a->current();
2252  if ( $next_line === false || $next_line === null ) {
2253  break;
2254  }
2255  $m = explode( ']]', $next_line, 3 );
2256  if ( count( $m ) == 3 ) {
2257  # the first ]] closes the inner link, the second the image
2258  $found = true;
2259  $text .= "[[{$m[0]}]]{$m[1]}";
2260  $trail = $m[2];
2261  break;
2262  } elseif ( count( $m ) == 2 ) {
2263  # if there's exactly one ]] that's fine, we'll keep looking
2264  $text .= "[[{$m[0]}]]{$m[1]}";
2265  } else {
2266  # if $next_line is invalid too, we need look no further
2267  $text .= '[[' . $next_line;
2268  break;
2269  }
2270  }
2271  if ( !$found ) {
2272  # we couldn't find the end of this imageLink, so output it raw
2273  # but don't ignore what might be perfectly normal links in the text we've examined
2274  $holders->merge( $this->replaceInternalLinks2( $text ) );
2275  $s .= "{$prefix}[[$link|$text";
2276  # note: no $trail, because without an end, there *is* no trail
2277  continue;
2278  }
2279  } else { # it's not an image, so output it raw
2280  $s .= "{$prefix}[[$link|$text";
2281  # note: no $trail, because without an end, there *is* no trail
2282  continue;
2283  }
2284  }
2285 
2286  $wasblank = ( $text == '' );
2287  if ( $wasblank ) {
2288  $text = $link;
2289  } else {
2290  # Bug 4598 madness. Handle the quotes only if they come from the alternate part
2291  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2292  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2293  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2294  $text = $this->doQuotes( $text );
2295  }
2296 
2297  # Link not escaped by : , create the various objects
2298  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2299  # Interwikis
2300  if (
2301  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2302  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2303  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2304  )
2305  ) {
2306  # Bug 24502: filter duplicates
2307  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2308  $this->mLangLinkLanguages[$iw] = true;
2309  $this->mOutput->addLanguageLink( $nt->getFullText() );
2310  }
2311 
2312  $s = rtrim( $s . $prefix );
2313  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2314  continue;
2315  }
2316 
2317  if ( $ns == NS_FILE ) {
2318  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2319  if ( $wasblank ) {
2320  # if no parameters were passed, $text
2321  # becomes something like "File:Foo.png",
2322  # which we don't want to pass on to the
2323  # image generator
2324  $text = '';
2325  } else {
2326  # recursively parse links inside the image caption
2327  # actually, this will parse them in any other parameters, too,
2328  # but it might be hard to fix that, and it doesn't matter ATM
2329  $text = $this->replaceExternalLinks( $text );
2330  $holders->merge( $this->replaceInternalLinks2( $text ) );
2331  }
2332  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2333  $s .= $prefix . $this->armorLinks(
2334  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2335  continue;
2336  }
2337  } elseif ( $ns == NS_CATEGORY ) {
2338  $s = rtrim( $s . "\n" ); # bug 87
2339 
2340  if ( $wasblank ) {
2341  $sortkey = $this->getDefaultSort();
2342  } else {
2343  $sortkey = $text;
2344  }
2345  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2346  $sortkey = str_replace( "\n", '', $sortkey );
2347  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2348  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2349 
2353  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2354 
2355  continue;
2356  }
2357  }
2358 
2359  # Self-link checking. For some languages, variants of the title are checked in
2360  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2361  # for linking to a different variant.
2362  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2363  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2364  continue;
2365  }
2366 
2367  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2368  # @todo FIXME: Should do batch file existence checks, see comment below
2369  if ( $ns == NS_MEDIA ) {
2370  # Give extensions a chance to select the file revision for us
2371  $options = [];
2372  $descQuery = false;
2373  Hooks::run( 'BeforeParserFetchFileAndTitle',
2374  [ $this, $nt, &$options, &$descQuery ] );
2375  # Fetch and register the file (file title may be different via hooks)
2376  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2377  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2378  $s .= $prefix . $this->armorLinks(
2379  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2380  continue;
2381  }
2382 
2383  # Some titles, such as valid special pages or files in foreign repos, should
2384  # be shown as bluelinks even though they're not included in the page table
2385  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2386  # batch file existence checks for NS_FILE and NS_MEDIA
2387  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2388  $this->mOutput->addLink( $nt );
2389  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2390  } else {
2391  # Links will be added to the output link list after checking
2392  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2393  }
2394  }
2395  return $holders;
2396  }
2397 
2411  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2412  list( $inside, $trail ) = Linker::splitTrail( $trail );
2413 
2414  if ( $text == '' ) {
2415  $text = htmlspecialchars( $nt->getPrefixedText() );
2416  }
2417 
2418  $link = $this->getLinkRenderer()->makeKnownLink(
2419  $nt, new HtmlArmor( "$prefix$text$inside" )
2420  );
2421 
2422  return $this->armorLinks( $link ) . $trail;
2423  }
2424 
2435  public function armorLinks( $text ) {
2436  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2437  self::MARKER_PREFIX . "NOPARSE$1", $text );
2438  }
2439 
2444  public function areSubpagesAllowed() {
2445  # Some namespaces don't allow subpages
2446  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2447  }
2448 
2457  public function maybeDoSubpageLink( $target, &$text ) {
2458  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2459  }
2460 
2469  public function doBlockLevels( $text, $linestart ) {
2470  return BlockLevelPass::doBlockLevels( $text, $linestart );
2471  }
2472 
2484  public function getVariableValue( $index, $frame = false ) {
2487 
2488  if ( is_null( $this->mTitle ) ) {
2489  // If no title set, bad things are going to happen
2490  // later. Title should always be set since this
2491  // should only be called in the middle of a parse
2492  // operation (but the unit-tests do funky stuff)
2493  throw new MWException( __METHOD__ . ' Should only be '
2494  . ' called while parsing (no title set)' );
2495  }
2496 
2497  // Avoid PHP 7.1 warning from passing $this by reference
2498  $parser = $this;
2499 
2504  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2505  if ( isset( $this->mVarCache[$index] ) ) {
2506  return $this->mVarCache[$index];
2507  }
2508  }
2509 
2510  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2511  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2512 
2513  $pageLang = $this->getFunctionLang();
2514 
2515  switch ( $index ) {
2516  case '!':
2517  $value = '|';
2518  break;
2519  case 'currentmonth':
2520  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2521  break;
2522  case 'currentmonth1':
2523  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2524  break;
2525  case 'currentmonthname':
2526  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2527  break;
2528  case 'currentmonthnamegen':
2529  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2530  break;
2531  case 'currentmonthabbrev':
2532  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2533  break;
2534  case 'currentday':
2535  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2536  break;
2537  case 'currentday2':
2538  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2539  break;
2540  case 'localmonth':
2541  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2542  break;
2543  case 'localmonth1':
2544  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2545  break;
2546  case 'localmonthname':
2547  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2548  break;
2549  case 'localmonthnamegen':
2550  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2551  break;
2552  case 'localmonthabbrev':
2553  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2554  break;
2555  case 'localday':
2556  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2557  break;
2558  case 'localday2':
2559  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2560  break;
2561  case 'pagename':
2562  $value = wfEscapeWikiText( $this->mTitle->getText() );
2563  break;
2564  case 'pagenamee':
2565  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2566  break;
2567  case 'fullpagename':
2568  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2569  break;
2570  case 'fullpagenamee':
2571  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2572  break;
2573  case 'subpagename':
2574  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2575  break;
2576  case 'subpagenamee':
2577  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2578  break;
2579  case 'rootpagename':
2580  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2581  break;
2582  case 'rootpagenamee':
2583  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2584  ' ',
2585  '_',
2586  $this->mTitle->getRootText()
2587  ) ) );
2588  break;
2589  case 'basepagename':
2590  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2591  break;
2592  case 'basepagenamee':
2593  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2594  ' ',
2595  '_',
2596  $this->mTitle->getBaseText()
2597  ) ) );
2598  break;
2599  case 'talkpagename':
2600  if ( $this->mTitle->canTalk() ) {
2601  $talkPage = $this->mTitle->getTalkPage();
2602  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2603  } else {
2604  $value = '';
2605  }
2606  break;
2607  case 'talkpagenamee':
2608  if ( $this->mTitle->canTalk() ) {
2609  $talkPage = $this->mTitle->getTalkPage();
2610  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2611  } else {
2612  $value = '';
2613  }
2614  break;
2615  case 'subjectpagename':
2616  $subjPage = $this->mTitle->getSubjectPage();
2617  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2618  break;
2619  case 'subjectpagenamee':
2620  $subjPage = $this->mTitle->getSubjectPage();
2621  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2622  break;
2623  case 'pageid': // requested in bug 23427
2624  $pageid = $this->getTitle()->getArticleID();
2625  if ( $pageid == 0 ) {
2626  # 0 means the page doesn't exist in the database,
2627  # which means the user is previewing a new page.
2628  # The vary-revision flag must be set, because the magic word
2629  # will have a different value once the page is saved.
2630  $this->mOutput->setFlag( 'vary-revision' );
2631  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2632  }
2633  $value = $pageid ? $pageid : null;
2634  break;
2635  case 'revisionid':
2636  # Let the edit saving system know we should parse the page
2637  # *after* a revision ID has been assigned.
2638  $this->mOutput->setFlag( 'vary-revision-id' );
2639  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2640  $value = $this->mRevisionId;
2641  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2642  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2643  $this->mOutput->setSpeculativeRevIdUsed( $value );
2644  }
2645  break;
2646  case 'revisionday':
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__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2651  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2652  break;
2653  case 'revisionday2':
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__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2658  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2659  break;
2660  case 'revisionmonth':
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__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2665  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2666  break;
2667  case 'revisionmonth1':
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__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2672  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2673  break;
2674  case 'revisionyear':
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__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2679  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2680  break;
2681  case 'revisiontimestamp':
2682  # Let the edit saving system know we should parse the page
2683  # *after* a revision ID has been assigned. This is for null edits.
2684  $this->mOutput->setFlag( 'vary-revision' );
2685  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2686  $value = $this->getRevisionTimestamp();
2687  break;
2688  case 'revisionuser':
2689  # Let the edit saving system know we should parse the page
2690  # *after* a revision ID has been assigned for null edits.
2691  $this->mOutput->setFlag( 'vary-user' );
2692  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2693  $value = $this->getRevisionUser();
2694  break;
2695  case 'revisionsize':
2696  $value = $this->getRevisionSize();
2697  break;
2698  case 'namespace':
2699  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2700  break;
2701  case 'namespacee':
2702  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2703  break;
2704  case 'namespacenumber':
2705  $value = $this->mTitle->getNamespace();
2706  break;
2707  case 'talkspace':
2708  $value = $this->mTitle->canTalk()
2709  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2710  : '';
2711  break;
2712  case 'talkspacee':
2713  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2714  break;
2715  case 'subjectspace':
2716  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2717  break;
2718  case 'subjectspacee':
2719  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2720  break;
2721  case 'currentdayname':
2722  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2723  break;
2724  case 'currentyear':
2725  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2726  break;
2727  case 'currenttime':
2728  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2729  break;
2730  case 'currenthour':
2731  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2732  break;
2733  case 'currentweek':
2734  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2735  # int to remove the padding
2736  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2737  break;
2738  case 'currentdow':
2739  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2740  break;
2741  case 'localdayname':
2742  $value = $pageLang->getWeekdayName(
2743  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2744  );
2745  break;
2746  case 'localyear':
2747  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2748  break;
2749  case 'localtime':
2750  $value = $pageLang->time(
2751  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2752  false,
2753  false
2754  );
2755  break;
2756  case 'localhour':
2757  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2758  break;
2759  case 'localweek':
2760  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2761  # int to remove the padding
2762  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2763  break;
2764  case 'localdow':
2765  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2766  break;
2767  case 'numberofarticles':
2768  $value = $pageLang->formatNum( SiteStats::articles() );
2769  break;
2770  case 'numberoffiles':
2771  $value = $pageLang->formatNum( SiteStats::images() );
2772  break;
2773  case 'numberofusers':
2774  $value = $pageLang->formatNum( SiteStats::users() );
2775  break;
2776  case 'numberofactiveusers':
2777  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2778  break;
2779  case 'numberofpages':
2780  $value = $pageLang->formatNum( SiteStats::pages() );
2781  break;
2782  case 'numberofadmins':
2783  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2784  break;
2785  case 'numberofedits':
2786  $value = $pageLang->formatNum( SiteStats::edits() );
2787  break;
2788  case 'currenttimestamp':
2789  $value = wfTimestamp( TS_MW, $ts );
2790  break;
2791  case 'localtimestamp':
2792  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2793  break;
2794  case 'currentversion':
2796  break;
2797  case 'articlepath':
2798  return $wgArticlePath;
2799  case 'sitename':
2800  return $wgSitename;
2801  case 'server':
2802  return $wgServer;
2803  case 'servername':
2804  return $wgServerName;
2805  case 'scriptpath':
2806  return $wgScriptPath;
2807  case 'stylepath':
2808  return $wgStylePath;
2809  case 'directionmark':
2810  return $pageLang->getDirMark();
2811  case 'contentlanguage':
2813  return $wgLanguageCode;
2814  case 'cascadingsources':
2816  break;
2817  default:
2818  $ret = null;
2819  Hooks::run(
2820  'ParserGetVariableValueSwitch',
2821  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2822  );
2823 
2824  return $ret;
2825  }
2826 
2827  if ( $index ) {
2828  $this->mVarCache[$index] = $value;
2829  }
2830 
2831  return $value;
2832  }
2833 
2839  public function initialiseVariables() {
2840  $variableIDs = MagicWord::getVariableIDs();
2841  $substIDs = MagicWord::getSubstIDs();
2842 
2843  $this->mVariables = new MagicWordArray( $variableIDs );
2844  $this->mSubstWords = new MagicWordArray( $substIDs );
2845  }
2846 
2869  public function preprocessToDom( $text, $flags = 0 ) {
2870  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2871  return $dom;
2872  }
2873 
2881  public static function splitWhitespace( $s ) {
2882  $ltrimmed = ltrim( $s );
2883  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2884  $trimmed = rtrim( $ltrimmed );
2885  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2886  if ( $diff > 0 ) {
2887  $w2 = substr( $ltrimmed, -$diff );
2888  } else {
2889  $w2 = '';
2890  }
2891  return [ $w1, $trimmed, $w2 ];
2892  }
2893 
2914  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2915  # Is there any text? Also, Prevent too big inclusions!
2916  $textSize = strlen( $text );
2917  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2918  return $text;
2919  }
2920 
2921  if ( $frame === false ) {
2922  $frame = $this->getPreprocessor()->newFrame();
2923  } elseif ( !( $frame instanceof PPFrame ) ) {
2924  wfDebug( __METHOD__ . " called using plain parameters instead of "
2925  . "a PPFrame instance. Creating custom frame.\n" );
2926  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2927  }
2928 
2929  $dom = $this->preprocessToDom( $text );
2930  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2931  $text = $frame->expand( $dom, $flags );
2932 
2933  return $text;
2934  }
2935 
2943  public static function createAssocArgs( $args ) {
2944  $assocArgs = [];
2945  $index = 1;
2946  foreach ( $args as $arg ) {
2947  $eqpos = strpos( $arg, '=' );
2948  if ( $eqpos === false ) {
2949  $assocArgs[$index++] = $arg;
2950  } else {
2951  $name = trim( substr( $arg, 0, $eqpos ) );
2952  $value = trim( substr( $arg, $eqpos + 1 ) );
2953  if ( $value === false ) {
2954  $value = '';
2955  }
2956  if ( $name !== false ) {
2957  $assocArgs[$name] = $value;
2958  }
2959  }
2960  }
2961 
2962  return $assocArgs;
2963  }
2964 
2991  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2992  # does no harm if $current and $max are present but are unnecessary for the message
2993  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2994  # only during preview, and that would split the parser cache unnecessarily.
2995  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
2996  ->text();
2997  $this->mOutput->addWarning( $warning );
2998  $this->addTrackingCategory( "$limitationType-category" );
2999  }
3000 
3013  public function braceSubstitution( $piece, $frame ) {
3014 
3015  // Flags
3016 
3017  // $text has been filled
3018  $found = false;
3019  // wiki markup in $text should be escaped
3020  $nowiki = false;
3021  // $text is HTML, armour it against wikitext transformation
3022  $isHTML = false;
3023  // Force interwiki transclusion to be done in raw mode not rendered
3024  $forceRawInterwiki = false;
3025  // $text is a DOM node needing expansion in a child frame
3026  $isChildObj = false;
3027  // $text is a DOM node needing expansion in the current frame
3028  $isLocalObj = false;
3029 
3030  # Title object, where $text came from
3031  $title = false;
3032 
3033  # $part1 is the bit before the first |, and must contain only title characters.
3034  # Various prefixes will be stripped from it later.
3035  $titleWithSpaces = $frame->expand( $piece['title'] );
3036  $part1 = trim( $titleWithSpaces );
3037  $titleText = false;
3038 
3039  # Original title text preserved for various purposes
3040  $originalTitle = $part1;
3041 
3042  # $args is a list of argument nodes, starting from index 0, not including $part1
3043  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3044  # below won't work b/c this $args isn't an object
3045  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3046 
3047  $profileSection = null; // profile templates
3048 
3049  # SUBST
3050  if ( !$found ) {
3051  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3052 
3053  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3054  # Decide whether to expand template or keep wikitext as-is.
3055  if ( $this->ot['wiki'] ) {
3056  if ( $substMatch === false ) {
3057  $literal = true; # literal when in PST with no prefix
3058  } else {
3059  $literal = false; # expand when in PST with subst: or safesubst:
3060  }
3061  } else {
3062  if ( $substMatch == 'subst' ) {
3063  $literal = true; # literal when not in PST with plain subst:
3064  } else {
3065  $literal = false; # expand when not in PST with safesubst: or no prefix
3066  }
3067  }
3068  if ( $literal ) {
3069  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3070  $isLocalObj = true;
3071  $found = true;
3072  }
3073  }
3074 
3075  # Variables
3076  if ( !$found && $args->getLength() == 0 ) {
3077  $id = $this->mVariables->matchStartToEnd( $part1 );
3078  if ( $id !== false ) {
3079  $text = $this->getVariableValue( $id, $frame );
3080  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3081  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3082  }
3083  $found = true;
3084  }
3085  }
3086 
3087  # MSG, MSGNW and RAW
3088  if ( !$found ) {
3089  # Check for MSGNW:
3090  $mwMsgnw = MagicWord::get( 'msgnw' );
3091  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3092  $nowiki = true;
3093  } else {
3094  # Remove obsolete MSG:
3095  $mwMsg = MagicWord::get( 'msg' );
3096  $mwMsg->matchStartAndRemove( $part1 );
3097  }
3098 
3099  # Check for RAW:
3100  $mwRaw = MagicWord::get( 'raw' );
3101  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3102  $forceRawInterwiki = true;
3103  }
3104  }
3105 
3106  # Parser functions
3107  if ( !$found ) {
3108  $colonPos = strpos( $part1, ':' );
3109  if ( $colonPos !== false ) {
3110  $func = substr( $part1, 0, $colonPos );
3111  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3112  $argsLength = $args->getLength();
3113  for ( $i = 0; $i < $argsLength; $i++ ) {
3114  $funcArgs[] = $args->item( $i );
3115  }
3116  try {
3117  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3118  } catch ( Exception $ex ) {
3119  throw $ex;
3120  }
3121 
3122  # The interface for parser functions allows for extracting
3123  # flags into the local scope. Extract any forwarded flags
3124  # here.
3125  extract( $result );
3126  }
3127  }
3128 
3129  # Finish mangling title and then check for loops.
3130  # Set $title to a Title object and $titleText to the PDBK
3131  if ( !$found ) {
3132  $ns = NS_TEMPLATE;
3133  # Split the title into page and subpage
3134  $subpage = '';
3135  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3136  if ( $part1 !== $relative ) {
3137  $part1 = $relative;
3138  $ns = $this->mTitle->getNamespace();
3139  }
3140  $title = Title::newFromText( $part1, $ns );
3141  if ( $title ) {
3142  $titleText = $title->getPrefixedText();
3143  # Check for language variants if the template is not found
3144  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3145  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3146  }
3147  # Do recursion depth check
3148  $limit = $this->mOptions->getMaxTemplateDepth();
3149  if ( $frame->depth >= $limit ) {
3150  $found = true;
3151  $text = '<span class="error">'
3152  . wfMessage( 'parser-template-recursion-depth-warning' )
3153  ->numParams( $limit )->inContentLanguage()->text()
3154  . '</span>';
3155  }
3156  }
3157  }
3158 
3159  # Load from database
3160  if ( !$found && $title ) {
3161  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3162  if ( !$title->isExternal() ) {
3163  if ( $title->isSpecialPage()
3164  && $this->mOptions->getAllowSpecialInclusion()
3165  && $this->ot['html']
3166  ) {
3167  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3168  // Pass the template arguments as URL parameters.
3169  // "uselang" will have no effect since the Language object
3170  // is forced to the one defined in ParserOptions.
3171  $pageArgs = [];
3172  $argsLength = $args->getLength();
3173  for ( $i = 0; $i < $argsLength; $i++ ) {
3174  $bits = $args->item( $i )->splitArg();
3175  if ( strval( $bits['index'] ) === '' ) {
3176  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3177  $value = trim( $frame->expand( $bits['value'] ) );
3178  $pageArgs[$name] = $value;
3179  }
3180  }
3181 
3182  // Create a new context to execute the special page
3183  $context = new RequestContext;
3184  $context->setTitle( $title );
3185  $context->setRequest( new FauxRequest( $pageArgs ) );
3186  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3187  $context->setUser( $this->getUser() );
3188  } else {
3189  // If this page is cached, then we better not be per user.
3190  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3191  }
3192  $context->setLanguage( $this->mOptions->getUserLangObj() );
3194  $title, $context, $this->getLinkRenderer() );
3195  if ( $ret ) {
3196  $text = $context->getOutput()->getHTML();
3197  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3198  $found = true;
3199  $isHTML = true;
3200  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3201  $this->mOutput->updateRuntimeAdaptiveExpiry(
3202  $specialPage->maxIncludeCacheTime()
3203  );
3204  }
3205  }
3206  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3207  $found = false; # access denied
3208  wfDebug( __METHOD__ . ": template inclusion denied for " .
3209  $title->getPrefixedDBkey() . "\n" );
3210  } else {
3211  list( $text, $title ) = $this->getTemplateDom( $title );
3212  if ( $text !== false ) {
3213  $found = true;
3214  $isChildObj = true;
3215  }
3216  }
3217 
3218  # If the title is valid but undisplayable, make a link to it
3219  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3220  $text = "[[:$titleText]]";
3221  $found = true;
3222  }
3223  } elseif ( $title->isTrans() ) {
3224  # Interwiki transclusion
3225  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3226  $text = $this->interwikiTransclude( $title, 'render' );
3227  $isHTML = true;
3228  } else {
3229  $text = $this->interwikiTransclude( $title, 'raw' );
3230  # Preprocess it like a template
3231  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3232  $isChildObj = true;
3233  }
3234  $found = true;
3235  }
3236 
3237  # Do infinite loop check
3238  # This has to be done after redirect resolution to avoid infinite loops via redirects
3239  if ( !$frame->loopCheck( $title ) ) {
3240  $found = true;
3241  $text = '<span class="error">'
3242  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3243  . '</span>';
3244  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3245  }
3246  }
3247 
3248  # If we haven't found text to substitute by now, we're done
3249  # Recover the source wikitext and return it
3250  if ( !$found ) {
3251  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3252  if ( $profileSection ) {
3253  $this->mProfiler->scopedProfileOut( $profileSection );
3254  }
3255  return [ 'object' => $text ];
3256  }
3257 
3258  # Expand DOM-style return values in a child frame
3259  if ( $isChildObj ) {
3260  # Clean up argument array
3261  $newFrame = $frame->newChild( $args, $title );
3262 
3263  if ( $nowiki ) {
3264  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3265  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3266  # Expansion is eligible for the empty-frame cache
3267  $text = $newFrame->cachedExpand( $titleText, $text );
3268  } else {
3269  # Uncached expansion
3270  $text = $newFrame->expand( $text );
3271  }
3272  }
3273  if ( $isLocalObj && $nowiki ) {
3274  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3275  $isLocalObj = false;
3276  }
3277 
3278  if ( $profileSection ) {
3279  $this->mProfiler->scopedProfileOut( $profileSection );
3280  }
3281 
3282  # Replace raw HTML by a placeholder
3283  if ( $isHTML ) {
3284  $text = $this->insertStripItem( $text );
3285  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3286  # Escape nowiki-style return values
3287  $text = wfEscapeWikiText( $text );
3288  } elseif ( is_string( $text )
3289  && !$piece['lineStart']
3290  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3291  ) {
3292  # Bug 529: if the template begins with a table or block-level
3293  # element, it should be treated as beginning a new line.
3294  # This behavior is somewhat controversial.
3295  $text = "\n" . $text;
3296  }
3297 
3298  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3299  # Error, oversize inclusion
3300  if ( $titleText !== false ) {
3301  # Make a working, properly escaped link if possible (bug 23588)
3302  $text = "[[:$titleText]]";
3303  } else {
3304  # This will probably not be a working link, but at least it may
3305  # provide some hint of where the problem is
3306  preg_replace( '/^:/', '', $originalTitle );
3307  $text = "[[:$originalTitle]]";
3308  }
3309  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3310  . 'post-expand include size too large -->' );
3311  $this->limitationWarn( 'post-expand-template-inclusion' );
3312  }
3313 
3314  if ( $isLocalObj ) {
3315  $ret = [ 'object' => $text ];
3316  } else {
3317  $ret = [ 'text' => $text ];
3318  }
3319 
3320  return $ret;
3321  }
3322 
3342  public function callParserFunction( $frame, $function, array $args = [] ) {
3344 
3345  # Case sensitive functions
3346  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3347  $function = $this->mFunctionSynonyms[1][$function];
3348  } else {
3349  # Case insensitive functions
3350  $function = $wgContLang->lc( $function );
3351  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3352  $function = $this->mFunctionSynonyms[0][$function];
3353  } else {
3354  return [ 'found' => false ];
3355  }
3356  }
3357 
3358  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3359 
3360  # Workaround for PHP bug 35229 and similar
3361  if ( !is_callable( $callback ) ) {
3362  throw new MWException( "Tag hook for $function is not callable\n" );
3363  }
3364 
3365  // Avoid PHP 7.1 warning from passing $this by reference
3366  $parser = $this;
3367 
3368  $allArgs = [ &$parser ];
3369  if ( $flags & self::SFH_OBJECT_ARGS ) {
3370  # Convert arguments to PPNodes and collect for appending to $allArgs
3371  $funcArgs = [];
3372  foreach ( $args as $k => $v ) {
3373  if ( $v instanceof PPNode || $k === 0 ) {
3374  $funcArgs[] = $v;
3375  } else {
3376  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3377  }
3378  }
3379 
3380  # Add a frame parameter, and pass the arguments as an array
3381  $allArgs[] = $frame;
3382  $allArgs[] = $funcArgs;
3383  } else {
3384  # Convert arguments to plain text and append to $allArgs
3385  foreach ( $args as $k => $v ) {
3386  if ( $v instanceof PPNode ) {
3387  $allArgs[] = trim( $frame->expand( $v ) );
3388  } elseif ( is_int( $k ) && $k >= 0 ) {
3389  $allArgs[] = trim( $v );
3390  } else {
3391  $allArgs[] = trim( "$k=$v" );
3392  }
3393  }
3394  }
3395 
3396  $result = call_user_func_array( $callback, $allArgs );
3397 
3398  # The interface for function hooks allows them to return a wikitext
3399  # string or an array containing the string and any flags. This mungs
3400  # things around to match what this method should return.
3401  if ( !is_array( $result ) ) {
3402  $result =[
3403  'found' => true,
3404  'text' => $result,
3405  ];
3406  } else {
3407  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3408  $result['text'] = $result[0];
3409  }
3410  unset( $result[0] );
3411  $result += [
3412  'found' => true,
3413  ];
3414  }
3415 
3416  $noparse = true;
3417  $preprocessFlags = 0;
3418  if ( isset( $result['noparse'] ) ) {
3419  $noparse = $result['noparse'];
3420  }
3421  if ( isset( $result['preprocessFlags'] ) ) {
3422  $preprocessFlags = $result['preprocessFlags'];
3423  }
3424 
3425  if ( !$noparse ) {
3426  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3427  $result['isChildObj'] = true;
3428  }
3429 
3430  return $result;
3431  }
3432 
3441  public function getTemplateDom( $title ) {
3442  $cacheTitle = $title;
3443  $titleText = $title->getPrefixedDBkey();
3444 
3445  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3446  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3447  $title = Title::makeTitle( $ns, $dbk );
3448  $titleText = $title->getPrefixedDBkey();
3449  }
3450  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3451  return [ $this->mTplDomCache[$titleText], $title ];
3452  }
3453 
3454  # Cache miss, go to the database
3455  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3456 
3457  if ( $text === false ) {
3458  $this->mTplDomCache[$titleText] = false;
3459  return [ false, $title ];
3460  }
3461 
3462  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3463  $this->mTplDomCache[$titleText] = $dom;
3464 
3465  if ( !$title->equals( $cacheTitle ) ) {
3466  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3467  [ $title->getNamespace(), $cdb = $title->getDBkey() ];
3468  }
3469 
3470  return [ $dom, $title ];
3471  }
3472 
3485  $cacheKey = $title->getPrefixedDBkey();
3486  if ( !$this->currentRevisionCache ) {
3487  $this->currentRevisionCache = new MapCacheLRU( 100 );
3488  }
3489  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3490  $this->currentRevisionCache->set( $cacheKey,
3491  // Defaults to Parser::statelessFetchRevision()
3492  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3493  );
3494  }
3495  return $this->currentRevisionCache->get( $cacheKey );
3496  }
3497 
3507  public static function statelessFetchRevision( Title $title, $parser = false ) {
3508  $pageId = $title->getArticleID();
3509  $revId = $title->getLatestRevID();
3510 
3512  if ( $rev ) {
3513  $rev->setTitle( $title );
3514  }
3515 
3516  return $rev;
3517  }
3518 
3524  public function fetchTemplateAndTitle( $title ) {
3525  // Defaults to Parser::statelessFetchTemplate()
3526  $templateCb = $this->mOptions->getTemplateCallback();
3527  $stuff = call_user_func( $templateCb, $title, $this );
3528  // We use U+007F DELETE to distinguish strip markers from regular text.
3529  $text = $stuff['text'];
3530  if ( is_string( $stuff['text'] ) ) {
3531  $text = strtr( $text, "\x7f", "?" );
3532  }
3533  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3534  if ( isset( $stuff['deps'] ) ) {
3535  foreach ( $stuff['deps'] as $dep ) {
3536  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3537  if ( $dep['title']->equals( $this->getTitle() ) ) {
3538  // If we transclude ourselves, the final result
3539  // will change based on the new version of the page
3540  $this->mOutput->setFlag( 'vary-revision' );
3541  }
3542  }
3543  }
3544  return [ $text, $finalTitle ];
3545  }
3546 
3552  public function fetchTemplate( $title ) {
3553  return $this->fetchTemplateAndTitle( $title )[0];
3554  }
3555 
3565  public static function statelessFetchTemplate( $title, $parser = false ) {
3566  $text = $skip = false;
3567  $finalTitle = $title;
3568  $deps = [];
3569 
3570  # Loop to fetch the article, with up to 1 redirect
3571  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3572  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3573  // @codingStandardsIgnoreEnd
3574  # Give extensions a chance to select the revision instead
3575  $id = false; # Assume current
3576  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3577  [ $parser, $title, &$skip, &$id ] );
3578 
3579  if ( $skip ) {
3580  $text = false;
3581  $deps[] = [
3582  'title' => $title,
3583  'page_id' => $title->getArticleID(),
3584  'rev_id' => null
3585  ];
3586  break;
3587  }
3588  # Get the revision
3589  if ( $id ) {
3590  $rev = Revision::newFromId( $id );
3591  } elseif ( $parser ) {
3592  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3593  } else {
3595  }
3596  $rev_id = $rev ? $rev->getId() : 0;
3597  # If there is no current revision, there is no page
3598  if ( $id === false && !$rev ) {
3599  $linkCache = LinkCache::singleton();
3600  $linkCache->addBadLinkObj( $title );
3601  }
3602 
3603  $deps[] = [
3604  'title' => $title,
3605  'page_id' => $title->getArticleID(),
3606  'rev_id' => $rev_id ];
3607  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3608  # We fetched a rev from a different title; register it too...
3609  $deps[] = [
3610  'title' => $rev->getTitle(),
3611  'page_id' => $rev->getPage(),
3612  'rev_id' => $rev_id ];
3613  }
3614 
3615  if ( $rev ) {
3616  $content = $rev->getContent();
3617  $text = $content ? $content->getWikitextForTransclusion() : null;
3618 
3619  if ( $text === false || $text === null ) {
3620  $text = false;
3621  break;
3622  }
3623  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3625  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3626  if ( !$message->exists() ) {
3627  $text = false;
3628  break;
3629  }
3630  $content = $message->content();
3631  $text = $message->plain();
3632  } else {
3633  break;
3634  }
3635  if ( !$content ) {
3636  break;
3637  }
3638  # Redirect?
3639  $finalTitle = $title;
3640  $title = $content->getRedirectTarget();
3641  }
3642  return [
3643  'text' => $text,
3644  'finalTitle' => $finalTitle,
3645  'deps' => $deps ];
3646  }
3647 
3655  public function fetchFile( $title, $options = [] ) {
3656  return $this->fetchFileAndTitle( $title, $options )[0];
3657  }
3658 
3666  public function fetchFileAndTitle( $title, $options = [] ) {
3667  $file = $this->fetchFileNoRegister( $title, $options );
3668 
3669  $time = $file ? $file->getTimestamp() : false;
3670  $sha1 = $file ? $file->getSha1() : false;
3671  # Register the file as a dependency...
3672  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3673  if ( $file && !$title->equals( $file->getTitle() ) ) {
3674  # Update fetched file title
3675  $title = $file->getTitle();
3676  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3677  }
3678  return [ $file, $title ];
3679  }
3680 
3691  protected function fetchFileNoRegister( $title, $options = [] ) {
3692  if ( isset( $options['broken'] ) ) {
3693  $file = false; // broken thumbnail forced by hook
3694  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3695  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3696  } else { // get by (name,timestamp)
3697  $file = wfFindFile( $title, $options );
3698  }
3699  return $file;
3700  }
3701 
3710  public function interwikiTransclude( $title, $action ) {
3711  global $wgEnableScaryTranscluding;
3712 
3713  if ( !$wgEnableScaryTranscluding ) {
3714  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3715  }
3716 
3717  $url = $title->getFullURL( [ 'action' => $action ] );
3718 
3719  if ( strlen( $url ) > 255 ) {
3720  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3721  }
3722  return $this->fetchScaryTemplateMaybeFromCache( $url );
3723  }
3724 
3729  public function fetchScaryTemplateMaybeFromCache( $url ) {
3730  global $wgTranscludeCacheExpiry;
3731  $dbr = wfGetDB( DB_REPLICA );
3732  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3733  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3734  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3735  if ( $obj ) {
3736  return $obj->tc_contents;
3737  }
3738 
3739  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3740  $status = $req->execute(); // Status object
3741  if ( $status->isOK() ) {
3742  $text = $req->getContent();
3743  } elseif ( $req->getStatus() != 200 ) {
3744  // Though we failed to fetch the content, this status is useless.
3745  return wfMessage( 'scarytranscludefailed-httpstatus' )
3746  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3747  } else {
3748  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3749  }
3750 
3751  $dbw = wfGetDB( DB_MASTER );
3752  $dbw->replace( 'transcache', [ 'tc_url' ], [
3753  'tc_url' => $url,
3754  'tc_time' => $dbw->timestamp( time() ),
3755  'tc_contents' => $text
3756  ] );
3757  return $text;
3758  }
3759 
3769  public function argSubstitution( $piece, $frame ) {
3770 
3771  $error = false;
3772  $parts = $piece['parts'];
3773  $nameWithSpaces = $frame->expand( $piece['title'] );
3774  $argName = trim( $nameWithSpaces );
3775  $object = false;
3776  $text = $frame->getArgument( $argName );
3777  if ( $text === false && $parts->getLength() > 0
3778  && ( $this->ot['html']
3779  || $this->ot['pre']
3780  || ( $this->ot['wiki'] && $frame->isTemplate() )
3781  )
3782  ) {
3783  # No match in frame, use the supplied default
3784  $object = $parts->item( 0 )->getChildren();
3785  }
3786  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3787  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3788  $this->limitationWarn( 'post-expand-template-argument' );
3789  }
3790 
3791  if ( $text === false && $object === false ) {
3792  # No match anywhere
3793  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3794  }
3795  if ( $error !== false ) {
3796  $text .= $error;
3797  }
3798  if ( $object !== false ) {
3799  $ret = [ 'object' => $object ];
3800  } else {
3801  $ret = [ 'text' => $text ];
3802  }
3803 
3804  return $ret;
3805  }
3806 
3822  public function extensionSubstitution( $params, $frame ) {
3823  static $errorStr = '<span class="error">';
3824  static $errorLen = 20;
3825 
3826  $name = $frame->expand( $params['name'] );
3827  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3828  // Probably expansion depth or node count exceeded. Just punt the
3829  // error up.
3830  return $name;
3831  }
3832 
3833  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3834  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3835  // See above
3836  return $attrText;
3837  }
3838 
3839  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3840  if ( substr( $content, 0, $errorLen ) === $errorStr ) {
3841  // See above
3842  return $content;
3843  }
3844 
3845  $marker = self::MARKER_PREFIX . "-$name-"
3846  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3847 
3848  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3849  ( $this->ot['html'] || $this->ot['pre'] );
3850  if ( $isFunctionTag ) {
3851  $markerType = 'none';
3852  } else {
3853  $markerType = 'general';
3854  }
3855  if ( $this->ot['html'] || $isFunctionTag ) {
3856  $name = strtolower( $name );
3857  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3858  if ( isset( $params['attributes'] ) ) {
3859  $attributes = $attributes + $params['attributes'];
3860  }
3861 
3862  if ( isset( $this->mTagHooks[$name] ) ) {
3863  # Workaround for PHP bug 35229 and similar
3864  if ( !is_callable( $this->mTagHooks[$name] ) ) {
3865  throw new MWException( "Tag hook for $name is not callable\n" );
3866  }
3867  $output = call_user_func_array( $this->mTagHooks[$name],
3868  [ $content, $attributes, $this, $frame ] );
3869  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3870  list( $callback, ) = $this->mFunctionTagHooks[$name];
3871  if ( !is_callable( $callback ) ) {
3872  throw new MWException( "Tag hook for $name is not callable\n" );
3873  }
3874 
3875  // Avoid PHP 7.1 warning from passing $this by reference
3876  $parser = $this;
3877  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3878  } else {
3879  $output = '<span class="error">Invalid tag extension name: ' .
3880  htmlspecialchars( $name ) . '</span>';
3881  }
3882 
3883  if ( is_array( $output ) ) {
3884  # Extract flags to local scope (to override $markerType)
3885  $flags = $output;
3886  $output = $flags[0];
3887  unset( $flags[0] );
3888  extract( $flags );
3889  }
3890  } else {
3891  if ( is_null( $attrText ) ) {
3892  $attrText = '';
3893  }
3894  if ( isset( $params['attributes'] ) ) {
3895  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3896  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3897  htmlspecialchars( $attrValue ) . '"';
3898  }
3899  }
3900  if ( $content === null ) {
3901  $output = "<$name$attrText/>";
3902  } else {
3903  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3904  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3905  // See above
3906  return $close;
3907  }
3908  $output = "<$name$attrText>$content$close";
3909  }
3910  }
3911 
3912  if ( $markerType === 'none' ) {
3913  return $output;
3914  } elseif ( $markerType === 'nowiki' ) {
3915  $this->mStripState->addNoWiki( $marker, $output );
3916  } elseif ( $markerType === 'general' ) {
3917  $this->mStripState->addGeneral( $marker, $output );
3918  } else {
3919  throw new MWException( __METHOD__ . ': invalid marker type' );
3920  }
3921  return $marker;
3922  }
3923 
3931  public function incrementIncludeSize( $type, $size ) {
3932  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3933  return false;
3934  } else {
3935  $this->mIncludeSizes[$type] += $size;
3936  return true;
3937  }
3938  }
3939 
3946  $this->mExpensiveFunctionCount++;
3947  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3948  }
3949 
3958  public function doDoubleUnderscore( $text ) {
3959 
3960  # The position of __TOC__ needs to be recorded
3961  $mw = MagicWord::get( 'toc' );
3962  if ( $mw->match( $text ) ) {
3963  $this->mShowToc = true;
3964  $this->mForceTocPosition = true;
3965 
3966  # Set a placeholder. At the end we'll fill it in with the TOC.
3967  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3968 
3969  # Only keep the first one.
3970  $text = $mw->replace( '', $text );
3971  }
3972 
3973  # Now match and remove the rest of them
3975  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3976 
3977  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3978  $this->mOutput->mNoGallery = true;
3979  }
3980  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3981  $this->mShowToc = false;
3982  }
3983  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3984  && $this->mTitle->getNamespace() == NS_CATEGORY
3985  ) {
3986  $this->addTrackingCategory( 'hidden-category-category' );
3987  }
3988  # (bug 8068) Allow control over whether robots index a page.
3989  # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here! This
3990  # is not desirable, the last one on the page should win.
3991  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3992  $this->mOutput->setIndexPolicy( 'noindex' );
3993  $this->addTrackingCategory( 'noindex-category' );
3994  }
3995  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
3996  $this->mOutput->setIndexPolicy( 'index' );
3997  $this->addTrackingCategory( 'index-category' );
3998  }
3999 
4000  # Cache all double underscores in the database
4001  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4002  $this->mOutput->setProperty( $key, '' );
4003  }
4004 
4005  return $text;
4006  }
4007 
4013  public function addTrackingCategory( $msg ) {
4014  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4015  }
4016 
4033  public function formatHeadings( $text, $origText, $isMain = true ) {
4034  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4035 
4036  # Inhibit editsection links if requested in the page
4037  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4038  $maybeShowEditLink = $showEditLink = false;
4039  } else {
4040  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4041  $showEditLink = $this->mOptions->getEditSection();
4042  }
4043  if ( $showEditLink ) {
4044  $this->mOutput->setEditSectionTokens( true );
4045  }
4046 
4047  # Get all headlines for numbering them and adding funky stuff like [edit]
4048  # links - this is for later, but we need the number of headlines right now
4049  $matches = [];
4050  $numMatches = preg_match_all(
4051  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4052  $text,
4053  $matches
4054  );
4055 
4056  # if there are fewer than 4 headlines in the article, do not show TOC
4057  # unless it's been explicitly enabled.
4058  $enoughToc = $this->mShowToc &&
4059  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4060 
4061  # Allow user to stipulate that a page should have a "new section"
4062  # link added via __NEWSECTIONLINK__
4063  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4064  $this->mOutput->setNewSection( true );
4065  }
4066 
4067  # Allow user to remove the "new section"
4068  # link via __NONEWSECTIONLINK__
4069  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4070  $this->mOutput->hideNewSection( true );
4071  }
4072 
4073  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4074  # override above conditions and always show TOC above first header
4075  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4076  $this->mShowToc = true;
4077  $enoughToc = true;
4078  }
4079 
4080  # headline counter
4081  $headlineCount = 0;
4082  $numVisible = 0;
4083 
4084  # Ugh .. the TOC should have neat indentation levels which can be
4085  # passed to the skin functions. These are determined here
4086  $toc = '';
4087  $full = '';
4088  $head = [];
4089  $sublevelCount = [];
4090  $levelCount = [];
4091  $level = 0;
4092  $prevlevel = 0;
4093  $toclevel = 0;
4094  $prevtoclevel = 0;
4095  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4096  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4097  $oldType = $this->mOutputType;
4098  $this->setOutputType( self::OT_WIKI );
4099  $frame = $this->getPreprocessor()->newFrame();
4100  $root = $this->preprocessToDom( $origText );
4101  $node = $root->getFirstChild();
4102  $byteOffset = 0;
4103  $tocraw = [];
4104  $refers = [];
4105 
4106  $headlines = $numMatches !== false ? $matches[3] : [];
4107 
4108  foreach ( $headlines as $headline ) {
4109  $isTemplate = false;
4110  $titleText = false;
4111  $sectionIndex = false;
4112  $numbering = '';
4113  $markerMatches = [];
4114  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4115  $serial = $markerMatches[1];
4116  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4117  $isTemplate = ( $titleText != $baseTitleText );
4118  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4119  }
4120 
4121  if ( $toclevel ) {
4122  $prevlevel = $level;
4123  }
4124  $level = $matches[1][$headlineCount];
4125 
4126  if ( $level > $prevlevel ) {
4127  # Increase TOC level
4128  $toclevel++;
4129  $sublevelCount[$toclevel] = 0;
4130  if ( $toclevel < $wgMaxTocLevel ) {
4131  $prevtoclevel = $toclevel;
4132  $toc .= Linker::tocIndent();
4133  $numVisible++;
4134  }
4135  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4136  # Decrease TOC level, find level to jump to
4137 
4138  for ( $i = $toclevel; $i > 0; $i-- ) {
4139  if ( $levelCount[$i] == $level ) {
4140  # Found last matching level
4141  $toclevel = $i;
4142  break;
4143  } elseif ( $levelCount[$i] < $level ) {
4144  # Found first matching level below current level
4145  $toclevel = $i + 1;
4146  break;
4147  }
4148  }
4149  if ( $i == 0 ) {
4150  $toclevel = 1;
4151  }
4152  if ( $toclevel < $wgMaxTocLevel ) {
4153  if ( $prevtoclevel < $wgMaxTocLevel ) {
4154  # Unindent only if the previous toc level was shown :p
4155  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4156  $prevtoclevel = $toclevel;
4157  } else {
4158  $toc .= Linker::tocLineEnd();
4159  }
4160  }
4161  } else {
4162  # No change in level, end TOC line
4163  if ( $toclevel < $wgMaxTocLevel ) {
4164  $toc .= Linker::tocLineEnd();
4165  }
4166  }
4167 
4168  $levelCount[$toclevel] = $level;
4169 
4170  # count number of headlines for each level
4171  $sublevelCount[$toclevel]++;
4172  $dot = 0;
4173  for ( $i = 1; $i <= $toclevel; $i++ ) {
4174  if ( !empty( $sublevelCount[$i] ) ) {
4175  if ( $dot ) {
4176  $numbering .= '.';
4177  }
4178  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4179  $dot = 1;
4180  }
4181  }
4182 
4183  # The safe header is a version of the header text safe to use for links
4184 
4185  # Remove link placeholders by the link text.
4186  # <!--LINK number-->
4187  # turns into
4188  # link text with suffix
4189  # Do this before unstrip since link text can contain strip markers
4190  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4191 
4192  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4193  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4194 
4195  # Strip out HTML (first regex removes any tag not allowed)
4196  # Allowed tags are:
4197  # * <sup> and <sub> (bug 8393)
4198  # * <i> (bug 26375)
4199  # * <b> (r105284)
4200  # * <bdi> (bug 72884)
4201  # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
4202  # * <s> and <strike> (T35715)
4203  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4204  # to allow setting directionality in toc items.
4205  $tocline = preg_replace(
4206  [
4207  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4208  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4209  ],
4210  [ '', '<$1>' ],
4211  $safeHeadline
4212  );
4213 
4214  # Strip '<span></span>', which is the result from the above if
4215  # <span id="foo"></span> is used to produce an additional anchor
4216  # for a section.
4217  $tocline = str_replace( '<span></span>', '', $tocline );
4218 
4219  $tocline = trim( $tocline );
4220 
4221  # For the anchor, strip out HTML-y stuff period
4222  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4223  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4224 
4225  # Save headline for section edit hint before it's escaped
4226  $headlineHint = $safeHeadline;
4227 
4228  if ( $wgExperimentalHtmlIds ) {
4229  # For reverse compatibility, provide an id that's
4230  # HTML4-compatible, like we used to.
4231  # It may be worth noting, academically, that it's possible for
4232  # the legacy anchor to conflict with a non-legacy headline
4233  # anchor on the page. In this case likely the "correct" thing
4234  # would be to either drop the legacy anchors or make sure
4235  # they're numbered first. However, this would require people
4236  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4237  # manually, so let's not bother worrying about it.
4238  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4239  [ 'noninitial', 'legacy' ] );
4240  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4241 
4242  if ( $legacyHeadline == $safeHeadline ) {
4243  # No reason to have both (in fact, we can't)
4244  $legacyHeadline = false;
4245  }
4246  } else {
4247  $legacyHeadline = false;
4248  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4249  'noninitial' );
4250  }
4251 
4252  # HTML names must be case-insensitively unique (bug 10721).
4253  # This does not apply to Unicode characters per
4254  # http://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4255  # @todo FIXME: We may be changing them depending on the current locale.
4256  $arrayKey = strtolower( $safeHeadline );
4257  if ( $legacyHeadline === false ) {
4258  $legacyArrayKey = false;
4259  } else {
4260  $legacyArrayKey = strtolower( $legacyHeadline );
4261  }
4262 
4263  # Create the anchor for linking from the TOC to the section
4264  $anchor = $safeHeadline;
4265  $legacyAnchor = $legacyHeadline;
4266  if ( isset( $refers[$arrayKey] ) ) {
4267  // @codingStandardsIgnoreStart
4268  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4269  // @codingStandardsIgnoreEnd
4270  $anchor .= "_$i";
4271  $refers["${arrayKey}_$i"] = true;
4272  } else {
4273  $refers[$arrayKey] = true;
4274  }
4275  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4276  // @codingStandardsIgnoreStart
4277  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4278  // @codingStandardsIgnoreEnd
4279  $legacyAnchor .= "_$i";
4280  $refers["${legacyArrayKey}_$i"] = true;
4281  } else {
4282  $refers[$legacyArrayKey] = true;
4283  }
4284 
4285  # Don't number the heading if it is the only one (looks silly)
4286  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4287  # the two are different if the line contains a link
4288  $headline = Html::element(
4289  'span',
4290  [ 'class' => 'mw-headline-number' ],
4291  $numbering
4292  ) . ' ' . $headline;
4293  }
4294 
4295  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4296  $toc .= Linker::tocLine( $anchor, $tocline,
4297  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4298  }
4299 
4300  # Add the section to the section tree
4301  # Find the DOM node for this header
4302  $noOffset = ( $isTemplate || $sectionIndex === false );
4303  while ( $node && !$noOffset ) {
4304  if ( $node->getName() === 'h' ) {
4305  $bits = $node->splitHeading();
4306  if ( $bits['i'] == $sectionIndex ) {
4307  break;
4308  }
4309  }
4310  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4311  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4312  $node = $node->getNextSibling();
4313  }
4314  $tocraw[] = [
4315  'toclevel' => $toclevel,
4316  'level' => $level,
4317  'line' => $tocline,
4318  'number' => $numbering,
4319  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4320  'fromtitle' => $titleText,
4321  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4322  'anchor' => $anchor,
4323  ];
4324 
4325  # give headline the correct <h#> tag
4326  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4327  // Output edit section links as markers with styles that can be customized by skins
4328  if ( $isTemplate ) {
4329  # Put a T flag in the section identifier, to indicate to extractSections()
4330  # that sections inside <includeonly> should be counted.
4331  $editsectionPage = $titleText;
4332  $editsectionSection = "T-$sectionIndex";
4333  $editsectionContent = null;
4334  } else {
4335  $editsectionPage = $this->mTitle->getPrefixedText();
4336  $editsectionSection = $sectionIndex;
4337  $editsectionContent = $headlineHint;
4338  }
4339  // We use a bit of pesudo-xml for editsection markers. The
4340  // language converter is run later on. Using a UNIQ style marker
4341  // leads to the converter screwing up the tokens when it
4342  // converts stuff. And trying to insert strip tags fails too. At
4343  // this point all real inputted tags have already been escaped,
4344  // so we don't have to worry about a user trying to input one of
4345  // these markers directly. We use a page and section attribute
4346  // to stop the language converter from converting these
4347  // important bits of data, but put the headline hint inside a
4348  // content block because the language converter is supposed to
4349  // be able to convert that piece of data.
4350  // Gets replaced with html in ParserOutput::getText
4351  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4352  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4353  if ( $editsectionContent !== null ) {
4354  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4355  } else {
4356  $editlink .= '/>';
4357  }
4358  } else {
4359  $editlink = '';
4360  }
4361  $head[$headlineCount] = Linker::makeHeadline( $level,
4362  $matches['attrib'][$headlineCount], $anchor, $headline,
4363  $editlink, $legacyAnchor );
4364 
4365  $headlineCount++;
4366  }
4367 
4368  $this->setOutputType( $oldType );
4369 
4370  # Never ever show TOC if no headers
4371  if ( $numVisible < 1 ) {
4372  $enoughToc = false;
4373  }
4374 
4375  if ( $enoughToc ) {
4376  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4377  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4378  }
4379  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4380  $this->mOutput->setTOCHTML( $toc );
4381  $toc = self::TOC_START . $toc . self::TOC_END;
4382  $this->mOutput->addModules( 'mediawiki.toc' );
4383  }
4384 
4385  if ( $isMain ) {
4386  $this->mOutput->setSections( $tocraw );
4387  }
4388 
4389  # split up and insert constructed headlines
4390  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4391  $i = 0;
4392 
4393  // build an array of document sections
4394  $sections = [];
4395  foreach ( $blocks as $block ) {
4396  // $head is zero-based, sections aren't.
4397  if ( empty( $head[$i - 1] ) ) {
4398  $sections[$i] = $block;
4399  } else {
4400  $sections[$i] = $head[$i - 1] . $block;
4401  }
4402 
4413  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4414 
4415  $i++;
4416  }
4417 
4418  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4419  // append the TOC at the beginning
4420  // Top anchor now in skin
4421  $sections[0] = $sections[0] . $toc . "\n";
4422  }
4423 
4424  $full .= implode( '', $sections );
4425 
4426  if ( $this->mForceTocPosition ) {
4427  return str_replace( '<!--MWTOC-->', $toc, $full );
4428  } else {
4429  return $full;
4430  }
4431  }
4432 
4444  public function preSaveTransform( $text, Title $title, User $user,
4445  ParserOptions $options, $clearState = true
4446  ) {
4447  if ( $clearState ) {
4448  $magicScopeVariable = $this->lock();
4449  }
4450  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4451  $this->setUser( $user );
4452 
4453  // We still normalize line endings for backwards-compatibility
4454  // with other code that just calls PST, but this should already
4455  // be handled in TextContent subclasses
4456  $text = TextContent::normalizeLineEndings( $text );
4457 
4458  if ( $options->getPreSaveTransform() ) {
4459  $text = $this->pstPass2( $text, $user );
4460  }
4461  $text = $this->mStripState->unstripBoth( $text );
4462 
4463  $this->setUser( null ); # Reset
4464 
4465  return $text;
4466  }
4467 
4476  private function pstPass2( $text, $user ) {
4478 
4479  # Note: This is the timestamp saved as hardcoded wikitext to
4480  # the database, we use $wgContLang here in order to give
4481  # everyone the same signature and use the default one rather
4482  # than the one selected in each user's preferences.
4483  # (see also bug 12815)
4484  $ts = $this->mOptions->getTimestamp();
4486  $ts = $timestamp->format( 'YmdHis' );
4487  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4488 
4489  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4490 
4491  # Variable replacement
4492  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4493  $text = $this->replaceVariables( $text );
4494 
4495  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4496  # which may corrupt this parser instance via its wfMessage()->text() call-
4497 
4498  # Signatures
4499  $sigText = $this->getUserSig( $user );
4500  $text = strtr( $text, [
4501  '~~~~~' => $d,
4502  '~~~~' => "$sigText $d",
4503  '~~~' => $sigText
4504  ] );
4505 
4506  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4507  $tc = '[' . Title::legalChars() . ']';
4508  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4509 
4510  // [[ns:page (context)|]]
4511  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4512  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4513  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4514  // [[ns:page (context), context|]] (using either single or double-width comma)
4515  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4516  // [[|page]] (reverse pipe trick: add context from page title)
4517  $p2 = "/\[\[\\|($tc+)]]/";
4518 
4519  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4520  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4521  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4522  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4523 
4524  $t = $this->mTitle->getText();
4525  $m = [];
4526  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4527  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4528  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4529  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4530  } else {
4531  # if there's no context, don't bother duplicating the title
4532  $text = preg_replace( $p2, '[[\\1]]', $text );
4533  }
4534 
4535  return $text;
4536  }
4537 
4552  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4553  global $wgMaxSigChars;
4554 
4555  $username = $user->getName();
4556 
4557  # If not given, retrieve from the user object.
4558  if ( $nickname === false ) {
4559  $nickname = $user->getOption( 'nickname' );
4560  }
4561 
4562  if ( is_null( $fancySig ) ) {
4563  $fancySig = $user->getBoolOption( 'fancysig' );
4564  }
4565 
4566  $nickname = $nickname == null ? $username : $nickname;
4567 
4568  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4569  $nickname = $username;
4570  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4571  } elseif ( $fancySig !== false ) {
4572  # Sig. might contain markup; validate this
4573  if ( $this->validateSig( $nickname ) !== false ) {
4574  # Validated; clean up (if needed) and return it
4575  return $this->cleanSig( $nickname, true );
4576  } else {
4577  # Failed to validate; fall back to the default
4578  $nickname = $username;
4579  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4580  }
4581  }
4582 
4583  # Make sure nickname doesnt get a sig in a sig
4584  $nickname = self::cleanSigInSig( $nickname );
4585 
4586  # If we're still here, make it a link to the user page
4587  $userText = wfEscapeWikiText( $username );
4588  $nickText = wfEscapeWikiText( $nickname );
4589  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4590 
4591  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4592  ->title( $this->getTitle() )->text();
4593  }
4594 
4601  public function validateSig( $text ) {
4602  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4603  }
4604 
4615  public function cleanSig( $text, $parsing = false ) {
4616  if ( !$parsing ) {
4617  global $wgTitle;
4618  $magicScopeVariable = $this->lock();
4619  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4620  }
4621 
4622  # Option to disable this feature
4623  if ( !$this->mOptions->getCleanSignatures() ) {
4624  return $text;
4625  }
4626 
4627  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4628  # => Move this logic to braceSubstitution()
4629  $substWord = MagicWord::get( 'subst' );
4630  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4631  $substText = '{{' . $substWord->getSynonym( 0 );
4632 
4633  $text = preg_replace( $substRegex, $substText, $text );
4634  $text = self::cleanSigInSig( $text );
4635  $dom = $this->preprocessToDom( $text );
4636  $frame = $this->getPreprocessor()->newFrame();
4637  $text = $frame->expand( $dom );
4638 
4639  if ( !$parsing ) {
4640  $text = $this->mStripState->unstripBoth( $text );
4641  }
4642 
4643  return $text;
4644  }
4645 
4652  public static function cleanSigInSig( $text ) {
4653  $text = preg_replace( '/~{3,5}/', '', $text );
4654  return $text;
4655  }
4656 
4667  $outputType, $clearState = true
4668  ) {
4669  $this->startParse( $title, $options, $outputType, $clearState );
4670  }
4671 
4678  private function startParse( Title $title = null, ParserOptions $options,
4679  $outputType, $clearState = true
4680  ) {
4681  $this->setTitle( $title );
4682  $this->mOptions = $options;
4683  $this->setOutputType( $outputType );
4684  if ( $clearState ) {
4685  $this->clearState();
4686  }
4687  }
4688 
4697  public function transformMsg( $text, $options, $title = null ) {
4698  static $executing = false;
4699 
4700  # Guard against infinite recursion
4701  if ( $executing ) {
4702  return $text;
4703  }
4704  $executing = true;
4705 
4706  if ( !$title ) {
4707  global $wgTitle;
4708  $title = $wgTitle;
4709  }
4710 
4711  $text = $this->preprocess( $text, $title, $options );
4712 
4713  $executing = false;
4714  return $text;
4715  }
4716 
4741  public function setHook( $tag, $callback ) {
4742  $tag = strtolower( $tag );
4743  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4744  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4745  }
4746  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4747  $this->mTagHooks[$tag] = $callback;
4748  if ( !in_array( $tag, $this->mStripList ) ) {
4749  $this->mStripList[] = $tag;
4750  }
4751 
4752  return $oldVal;
4753  }
4754 
4772  public function setTransparentTagHook( $tag, $callback ) {
4773  $tag = strtolower( $tag );
4774  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4775  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4776  }
4777  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4778  $this->mTransparentTagHooks[$tag] = $callback;
4779 
4780  return $oldVal;
4781  }
4782 
4786  public function clearTagHooks() {
4787  $this->mTagHooks = [];
4788  $this->mFunctionTagHooks = [];
4789  $this->mStripList = $this->mDefaultStripList;
4790  }
4791 
4835  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4837 
4838  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4839  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4840 
4841  # Add to function cache
4842  $mw = MagicWord::get( $id );
4843  if ( !$mw ) {
4844  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4845  }
4846 
4847  $synonyms = $mw->getSynonyms();
4848  $sensitive = intval( $mw->isCaseSensitive() );
4849 
4850  foreach ( $synonyms as $syn ) {
4851  # Case
4852  if ( !$sensitive ) {
4853  $syn = $wgContLang->lc( $syn );
4854  }
4855  # Add leading hash
4856  if ( !( $flags & self::SFH_NO_HASH ) ) {
4857  $syn = '#' . $syn;
4858  }
4859  # Remove trailing colon
4860  if ( substr( $syn, -1, 1 ) === ':' ) {
4861  $syn = substr( $syn, 0, -1 );
4862  }
4863  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4864  }
4865  return $oldVal;
4866  }
4867 
4873  public function getFunctionHooks() {
4874  return array_keys( $this->mFunctionHooks );
4875  }
4876 
4887  public function setFunctionTagHook( $tag, $callback, $flags ) {
4888  $tag = strtolower( $tag );
4889  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4890  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4891  }
4892  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4893  $this->mFunctionTagHooks[$tag] : null;
4894  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4895 
4896  if ( !in_array( $tag, $this->mStripList ) ) {
4897  $this->mStripList[] = $tag;
4898  }
4899 
4900  return $old;
4901  }
4902 
4910  public function replaceLinkHolders( &$text, $options = 0 ) {
4911  $this->mLinkHolders->replace( $text );
4912  }
4913 
4921  public function replaceLinkHoldersText( $text ) {
4922  return $this->mLinkHolders->replaceText( $text );
4923  }
4924 
4938  public function renderImageGallery( $text, $params ) {
4939 
4940  $mode = false;
4941  if ( isset( $params['mode'] ) ) {
4942  $mode = $params['mode'];
4943  }
4944 
4945  try {
4946  $ig = ImageGalleryBase::factory( $mode );
4947  } catch ( Exception $e ) {
4948  // If invalid type set, fallback to default.
4949  $ig = ImageGalleryBase::factory( false );
4950  }
4951 
4952  $ig->setContextTitle( $this->mTitle );
4953  $ig->setShowBytes( false );
4954  $ig->setShowFilename( false );
4955  $ig->setParser( $this );
4956  $ig->setHideBadImages();
4957  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
4958 
4959  if ( isset( $params['showfilename'] ) ) {
4960  $ig->setShowFilename( true );
4961  } else {
4962  $ig->setShowFilename( false );
4963  }
4964  if ( isset( $params['caption'] ) ) {
4965  $caption = $params['caption'];
4966  $caption = htmlspecialchars( $caption );
4967  $caption = $this->replaceInternalLinks( $caption );
4968  $ig->setCaptionHtml( $caption );
4969  }
4970  if ( isset( $params['perrow'] ) ) {
4971  $ig->setPerRow( $params['perrow'] );
4972  }
4973  if ( isset( $params['widths'] ) ) {
4974  $ig->setWidths( $params['widths'] );
4975  }
4976  if ( isset( $params['heights'] ) ) {
4977  $ig->setHeights( $params['heights'] );
4978  }
4979  $ig->setAdditionalOptions( $params );
4980 
4981  // Avoid PHP 7.1 warning from passing $this by reference
4982  $parser = $this;
4983  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
4984 
4985  $lines = StringUtils::explode( "\n", $text );
4986  foreach ( $lines as $line ) {
4987  # match lines like these:
4988  # Image:someimage.jpg|This is some image
4989  $matches = [];
4990  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4991  # Skip empty lines
4992  if ( count( $matches ) == 0 ) {
4993  continue;
4994  }
4995 
4996  if ( strpos( $matches[0], '%' ) !== false ) {
4997  $matches[1] = rawurldecode( $matches[1] );
4998  }
5000  if ( is_null( $title ) ) {
5001  # Bogus title. Ignore these so we don't bomb out later.
5002  continue;
5003  }
5004 
5005  # We need to get what handler the file uses, to figure out parameters.
5006  # Note, a hook can overide the file name, and chose an entirely different
5007  # file (which potentially could be of a different type and have different handler).
5008  $options = [];
5009  $descQuery = false;
5010  Hooks::run( 'BeforeParserFetchFileAndTitle',
5011  [ $this, $title, &$options, &$descQuery ] );
5012  # Don't register it now, as ImageGallery does that later.
5013  $file = $this->fetchFileNoRegister( $title, $options );
5014  $handler = $file ? $file->getHandler() : false;
5015 
5016  $paramMap = [
5017  'img_alt' => 'gallery-internal-alt',
5018  'img_link' => 'gallery-internal-link',
5019  ];
5020  if ( $handler ) {
5021  $paramMap = $paramMap + $handler->getParamMap();
5022  // We don't want people to specify per-image widths.
5023  // Additionally the width parameter would need special casing anyhow.
5024  unset( $paramMap['img_width'] );
5025  }
5026 
5027  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5028 
5029  $label = '';
5030  $alt = '';
5031  $link = '';
5032  $handlerOptions = [];
5033  if ( isset( $matches[3] ) ) {
5034  // look for an |alt= definition while trying not to break existing
5035  // captions with multiple pipes (|) in it, until a more sensible grammar
5036  // is defined for images in galleries
5037 
5038  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5039  // splitting on '|' is a bit odd, and different from makeImage.
5040  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5041  $parameterMatches = StringUtils::explode( '|', $matches[3] );
5042 
5043  foreach ( $parameterMatches as $parameterMatch ) {
5044  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5045  if ( $magicName ) {
5046  $paramName = $paramMap[$magicName];
5047 
5048  switch ( $paramName ) {
5049  case 'gallery-internal-alt':
5050  $alt = $this->stripAltText( $match, false );
5051  break;
5052  case 'gallery-internal-link':
5053  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5054  $chars = self::EXT_LINK_URL_CLASS;
5055  $addr = self::EXT_LINK_ADDR;
5056  $prots = $this->mUrlProtocols;
5057  // check to see if link matches an absolute url, if not then it must be a wiki link.
5058  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5059  $link = $linkValue;
5060  $this->mOutput->addExternalLink( $link );
5061  } else {
5062  $localLinkTitle = Title::newFromText( $linkValue );
5063  if ( $localLinkTitle !== null ) {
5064  $this->mOutput->addLink( $localLinkTitle );
5065  $link = $localLinkTitle->getLinkURL();
5066  }
5067  }
5068  break;
5069  default:
5070  // Must be a handler specific parameter.
5071  if ( $handler->validateParam( $paramName, $match ) ) {
5072  $handlerOptions[$paramName] = $match;
5073  } else {
5074  // Guess not, consider it as caption.
5075  wfDebug( "$parameterMatch failed parameter validation\n" );
5076  $label = '|' . $parameterMatch;
5077  }
5078  }
5079 
5080  } else {
5081  // Last pipe wins.
5082  $label = '|' . $parameterMatch;
5083  }
5084  }
5085  // Remove the pipe.
5086  $label = substr( $label, 1 );
5087  }
5088 
5089  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5090  }
5091  $html = $ig->toHTML();
5092  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5093  return $html;
5094  }
5095 
5100  public function getImageParams( $handler ) {
5101  if ( $handler ) {
5102  $handlerClass = get_class( $handler );
5103  } else {
5104  $handlerClass = '';
5105  }
5106  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5107  # Initialise static lists
5108  static $internalParamNames = [
5109  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5110  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5111  'bottom', 'text-bottom' ],
5112  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5113  'upright', 'border', 'link', 'alt', 'class' ],
5114  ];
5115  static $internalParamMap;
5116  if ( !$internalParamMap ) {
5117  $internalParamMap = [];
5118  foreach ( $internalParamNames as $type => $names ) {
5119  foreach ( $names as $name ) {
5120  $magicName = str_replace( '-', '_', "img_$name" );
5121  $internalParamMap[$magicName] = [ $type, $name ];
5122  }
5123  }
5124  }
5125 
5126  # Add handler params
5127  $paramMap = $internalParamMap;
5128  if ( $handler ) {
5129  $handlerParamMap = $handler->getParamMap();
5130  foreach ( $handlerParamMap as $magic => $paramName ) {
5131  $paramMap[$magic] = [ 'handler', $paramName ];
5132  }
5133  }
5134  $this->mImageParams[$handlerClass] = $paramMap;
5135  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5136  }
5137  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5138  }
5139 
5148  public function makeImage( $title, $options, $holders = false ) {
5149  # Check if the options text is of the form "options|alt text"
5150  # Options are:
5151  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5152  # * left no resizing, just left align. label is used for alt= only
5153  # * right same, but right aligned
5154  # * none same, but not aligned
5155  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5156  # * center center the image
5157  # * frame Keep original image size, no magnify-button.
5158  # * framed Same as "frame"
5159  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5160  # * upright reduce width for upright images, rounded to full __0 px
5161  # * border draw a 1px border around the image
5162  # * alt Text for HTML alt attribute (defaults to empty)
5163  # * class Set a class for img node
5164  # * link Set the target of the image link. Can be external, interwiki, or local
5165  # vertical-align values (no % or length right now):
5166  # * baseline
5167  # * sub
5168  # * super
5169  # * top
5170  # * text-top
5171  # * middle
5172  # * bottom
5173  # * text-bottom
5174 
5175  $parts = StringUtils::explode( "|", $options );
5176 
5177  # Give extensions a chance to select the file revision for us
5178  $options = [];
5179  $descQuery = false;
5180  Hooks::run( 'BeforeParserFetchFileAndTitle',
5181  [ $this, $title, &$options, &$descQuery ] );
5182  # Fetch and register the file (file title may be different via hooks)
5183  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5184 
5185  # Get parameter map
5186  $handler = $file ? $file->getHandler() : false;
5187 
5188  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5189 
5190  if ( !$file ) {
5191  $this->addTrackingCategory( 'broken-file-category' );
5192  }
5193 
5194  # Process the input parameters
5195  $caption = '';
5196  $params = [ 'frame' => [], 'handler' => [],
5197  'horizAlign' => [], 'vertAlign' => [] ];
5198  $seenformat = false;
5199  foreach ( $parts as $part ) {
5200  $part = trim( $part );
5201  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5202  $validated = false;
5203  if ( isset( $paramMap[$magicName] ) ) {
5204  list( $type, $paramName ) = $paramMap[$magicName];
5205 
5206  # Special case; width and height come in one variable together
5207  if ( $type === 'handler' && $paramName === 'width' ) {
5208  $parsedWidthParam = $this->parseWidthParam( $value );
5209  if ( isset( $parsedWidthParam['width'] ) ) {
5210  $width = $parsedWidthParam['width'];
5211  if ( $handler->validateParam( 'width', $width ) ) {
5212  $params[$type]['width'] = $width;
5213  $validated = true;
5214  }
5215  }
5216  if ( isset( $parsedWidthParam['height'] ) ) {
5217  $height = $parsedWidthParam['height'];
5218  if ( $handler->validateParam( 'height', $height ) ) {
5219  $params[$type]['height'] = $height;
5220  $validated = true;
5221  }
5222  }
5223  # else no validation -- bug 13436
5224  } else {
5225  if ( $type === 'handler' ) {
5226  # Validate handler parameter
5227  $validated = $handler->validateParam( $paramName, $value );
5228  } else {
5229  # Validate internal parameters
5230  switch ( $paramName ) {
5231  case 'manualthumb':
5232  case 'alt':
5233  case 'class':
5234  # @todo FIXME: Possibly check validity here for
5235  # manualthumb? downstream behavior seems odd with
5236  # missing manual thumbs.
5237  $validated = true;
5238  $value = $this->stripAltText( $value, $holders );
5239  break;
5240  case 'link':
5241  $chars = self::EXT_LINK_URL_CLASS;
5242  $addr = self::EXT_LINK_ADDR;
5243  $prots = $this->mUrlProtocols;
5244  if ( $value === '' ) {
5245  $paramName = 'no-link';
5246  $value = true;
5247  $validated = true;
5248  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5249  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5250  $paramName = 'link-url';
5251  $this->mOutput->addExternalLink( $value );
5252  if ( $this->mOptions->getExternalLinkTarget() ) {
5253  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5254  }
5255  $validated = true;
5256  }
5257  } else {
5258  $linkTitle = Title::newFromText( $value );
5259  if ( $linkTitle ) {
5260  $paramName = 'link-title';
5261  $value = $linkTitle;
5262  $this->mOutput->addLink( $linkTitle );
5263  $validated = true;
5264  }
5265  }
5266  break;
5267  case 'frameless':
5268  case 'framed':
5269  case 'thumbnail':
5270  // use first appearing option, discard others.
5271  $validated = ! $seenformat;
5272  $seenformat = true;
5273  break;
5274  default:
5275  # Most other things appear to be empty or numeric...
5276  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5277  }
5278  }
5279 
5280  if ( $validated ) {
5281  $params[$type][$paramName] = $value;
5282  }
5283  }
5284  }
5285  if ( !$validated ) {
5286  $caption = $part;
5287  }
5288  }
5289 
5290  # Process alignment parameters
5291  if ( $params['horizAlign'] ) {
5292  $params['frame']['align'] = key( $params['horizAlign'] );
5293  }
5294  if ( $params['vertAlign'] ) {
5295  $params['frame']['valign'] = key( $params['vertAlign'] );
5296  }
5297 
5298  $params['frame']['caption'] = $caption;
5299 
5300  # Will the image be presented in a frame, with the caption below?
5301  $imageIsFramed = isset( $params['frame']['frame'] )
5302  || isset( $params['frame']['framed'] )
5303  || isset( $params['frame']['thumbnail'] )
5304  || isset( $params['frame']['manualthumb'] );
5305 
5306  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5307  # came to also set the caption, ordinary text after the image -- which
5308  # makes no sense, because that just repeats the text multiple times in
5309  # screen readers. It *also* came to set the title attribute.
5310  # Now that we have an alt attribute, we should not set the alt text to
5311  # equal the caption: that's worse than useless, it just repeats the
5312  # text. This is the framed/thumbnail case. If there's no caption, we
5313  # use the unnamed parameter for alt text as well, just for the time be-
5314  # ing, if the unnamed param is set and the alt param is not.
5315  # For the future, we need to figure out if we want to tweak this more,
5316  # e.g., introducing a title= parameter for the title; ignoring the un-
5317  # named parameter entirely for images without a caption; adding an ex-
5318  # plicit caption= parameter and preserving the old magic unnamed para-
5319  # meter for BC; ...
5320  if ( $imageIsFramed ) { # Framed image
5321  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5322  # No caption or alt text, add the filename as the alt text so
5323  # that screen readers at least get some description of the image
5324  $params['frame']['alt'] = $title->getText();
5325  }
5326  # Do not set $params['frame']['title'] because tooltips don't make sense
5327  # for framed images
5328  } else { # Inline image
5329  if ( !isset( $params['frame']['alt'] ) ) {
5330  # No alt text, use the "caption" for the alt text
5331  if ( $caption !== '' ) {
5332  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5333  } else {
5334  # No caption, fall back to using the filename for the
5335  # alt text
5336  $params['frame']['alt'] = $title->getText();
5337  }
5338  }
5339  # Use the "caption" for the tooltip text
5340  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5341  }
5342 
5343  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5344 
5345  # Linker does the rest
5346  $time = isset( $options['time'] ) ? $options['time'] : false;
5347  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5348  $time, $descQuery, $this->mOptions->getThumbSize() );
5349 
5350  # Give the handler a chance to modify the parser object
5351  if ( $handler ) {
5352  $handler->parserTransformHook( $this, $file );
5353  }
5354 
5355  return $ret;
5356  }
5357 
5363  protected function stripAltText( $caption, $holders ) {
5364  # Strip bad stuff out of the title (tooltip). We can't just use
5365  # replaceLinkHoldersText() here, because if this function is called
5366  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5367  if ( $holders ) {
5368  $tooltip = $holders->replaceText( $caption );
5369  } else {
5370  $tooltip = $this->replaceLinkHoldersText( $caption );
5371  }
5372 
5373  # make sure there are no placeholders in thumbnail attributes
5374  # that are later expanded to html- so expand them now and
5375  # remove the tags
5376  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5377  $tooltip = Sanitizer::stripAllTags( $tooltip );
5378 
5379  return $tooltip;
5380  }
5381 
5387  public function disableCache() {
5388  wfDebug( "Parser output marked as uncacheable.\n" );
5389  if ( !$this->mOutput ) {
5390  throw new MWException( __METHOD__ .
5391  " can only be called when actually parsing something" );
5392  }
5393  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5394  }
5395 
5404  public function attributeStripCallback( &$text, $frame = false ) {
5405  $text = $this->replaceVariables( $text, $frame );
5406  $text = $this->mStripState->unstripBoth( $text );
5407  return $text;
5408  }
5409 
5415  public function getTags() {
5416  return array_merge(
5417  array_keys( $this->mTransparentTagHooks ),
5418  array_keys( $this->mTagHooks ),
5419  array_keys( $this->mFunctionTagHooks )
5420  );
5421  }
5422 
5433  public function replaceTransparentTags( $text ) {
5434  $matches = [];
5435  $elements = array_keys( $this->mTransparentTagHooks );
5436  $text = self::extractTagsAndParams( $elements, $text, $matches );
5437  $replacements = [];
5438 
5439  foreach ( $matches as $marker => $data ) {
5440  list( $element, $content, $params, $tag ) = $data;
5441  $tagName = strtolower( $element );
5442  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5443  $output = call_user_func_array(
5444  $this->mTransparentTagHooks[$tagName],
5445  [ $content, $params, $this ]
5446  );
5447  } else {
5448  $output = $tag;
5449  }
5450  $replacements[$marker] = $output;
5451  }
5452  return strtr( $text, $replacements );
5453  }
5454 
5484  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5485  global $wgTitle; # not generally used but removes an ugly failure mode
5486 
5487  $magicScopeVariable = $this->lock();
5488  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5489  $outText = '';
5490  $frame = $this->getPreprocessor()->newFrame();
5491 
5492  # Process section extraction flags
5493  $flags = 0;
5494  $sectionParts = explode( '-', $sectionId );
5495  $sectionIndex = array_pop( $sectionParts );
5496  foreach ( $sectionParts as $part ) {
5497  if ( $part === 'T' ) {
5498  $flags |= self::PTD_FOR_INCLUSION;
5499  }
5500  }
5501 
5502  # Check for empty input
5503  if ( strval( $text ) === '' ) {
5504  # Only sections 0 and T-0 exist in an empty document
5505  if ( $sectionIndex == 0 ) {
5506  if ( $mode === 'get' ) {
5507  return '';
5508  } else {
5509  return $newText;
5510  }
5511  } else {
5512  if ( $mode === 'get' ) {
5513  return $newText;
5514  } else {
5515  return $text;
5516  }
5517  }
5518  }
5519 
5520  # Preprocess the text
5521  $root = $this->preprocessToDom( $text, $flags );
5522 
5523  # <h> nodes indicate section breaks
5524  # They can only occur at the top level, so we can find them by iterating the root's children
5525  $node = $root->getFirstChild();
5526 
5527  # Find the target section
5528  if ( $sectionIndex == 0 ) {
5529  # Section zero doesn't nest, level=big
5530  $targetLevel = 1000;
5531  } else {
5532  while ( $node ) {
5533  if ( $node->getName() === 'h' ) {
5534  $bits = $node->splitHeading();
5535  if ( $bits['i'] == $sectionIndex ) {
5536  $targetLevel = $bits['level'];
5537  break;
5538  }
5539  }
5540  if ( $mode === 'replace' ) {
5541  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5542  }
5543  $node = $node->getNextSibling();
5544  }
5545  }
5546 
5547  if ( !$node ) {
5548  # Not found
5549  if ( $mode === 'get' ) {
5550  return $newText;
5551  } else {
5552  return $text;
5553  }
5554  }
5555 
5556  # Find the end of the section, including nested sections
5557  do {
5558  if ( $node->getName() === 'h' ) {
5559  $bits = $node->splitHeading();
5560  $curLevel = $bits['level'];
5561  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5562  break;
5563  }
5564  }
5565  if ( $mode === 'get' ) {
5566  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5567  }
5568  $node = $node->getNextSibling();
5569  } while ( $node );
5570 
5571  # Write out the remainder (in replace mode only)
5572  if ( $mode === 'replace' ) {
5573  # Output the replacement text
5574  # Add two newlines on -- trailing whitespace in $newText is conventionally
5575  # stripped by the editor, so we need both newlines to restore the paragraph gap
5576  # Only add trailing whitespace if there is newText
5577  if ( $newText != "" ) {
5578  $outText .= $newText . "\n\n";
5579  }
5580 
5581  while ( $node ) {
5582  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5583  $node = $node->getNextSibling();
5584  }
5585  }
5586 
5587  if ( is_string( $outText ) ) {
5588  # Re-insert stripped tags
5589  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5590  }
5591 
5592  return $outText;
5593  }
5594 
5609  public function getSection( $text, $sectionId, $defaultText = '' ) {
5610  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5611  }
5612 
5625  public function replaceSection( $oldText, $sectionId, $newText ) {
5626  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5627  }
5628 
5634  public function getRevisionId() {
5635  return $this->mRevisionId;
5636  }
5637 
5644  public function getRevisionObject() {
5645  if ( !is_null( $this->mRevisionObject ) ) {
5646  return $this->mRevisionObject;
5647  }
5648  if ( is_null( $this->mRevisionId ) ) {
5649  return null;
5650  }
5651 
5652  $rev = call_user_func(
5653  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5654  );
5655 
5656  # If the parse is for a new revision, then the callback should have
5657  # already been set to force the object and should match mRevisionId.
5658  # If not, try to fetch by mRevisionId for sanity.
5659  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5660  $rev = Revision::newFromId( $this->mRevisionId );
5661  }
5662 
5663  $this->mRevisionObject = $rev;
5664 
5665  return $this->mRevisionObject;
5666  }
5667 
5673  public function getRevisionTimestamp() {
5674  if ( is_null( $this->mRevisionTimestamp ) ) {
5676 
5677  $revObject = $this->getRevisionObject();
5678  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5679 
5680  # The cryptic '' timezone parameter tells to use the site-default
5681  # timezone offset instead of the user settings.
5682  # Since this value will be saved into the parser cache, served
5683  # to other users, and potentially even used inside links and such,
5684  # it needs to be consistent for all visitors.
5685  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5686 
5687  }
5688  return $this->mRevisionTimestamp;
5689  }
5690 
5696  public function getRevisionUser() {
5697  if ( is_null( $this->mRevisionUser ) ) {
5698  $revObject = $this->getRevisionObject();
5699 
5700  # if this template is subst: the revision id will be blank,
5701  # so just use the current user's name
5702  if ( $revObject ) {
5703  $this->mRevisionUser = $revObject->getUserText();
5704  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5705  $this->mRevisionUser = $this->getUser()->getName();
5706  }
5707  }
5708  return $this->mRevisionUser;
5709  }
5710 
5716  public function getRevisionSize() {
5717  if ( is_null( $this->mRevisionSize ) ) {
5718  $revObject = $this->getRevisionObject();
5719 
5720  # if this variable is subst: the revision id will be blank,
5721  # so just use the parser input size, because the own substituation
5722  # will change the size.
5723  if ( $revObject ) {
5724  $this->mRevisionSize = $revObject->getSize();
5725  } else {
5726  $this->mRevisionSize = $this->mInputSize;
5727  }
5728  }
5729  return $this->mRevisionSize;
5730  }
5731 
5737  public function setDefaultSort( $sort ) {
5738  $this->mDefaultSort = $sort;
5739  $this->mOutput->setProperty( 'defaultsort', $sort );
5740  }
5741 
5752  public function getDefaultSort() {
5753  if ( $this->mDefaultSort !== false ) {
5754  return $this->mDefaultSort;
5755  } else {
5756  return '';
5757  }
5758  }
5759 
5766  public function getCustomDefaultSort() {
5767  return $this->mDefaultSort;
5768  }
5769 
5779  public function guessSectionNameFromWikiText( $text ) {
5780  # Strip out wikitext links(they break the anchor)
5781  $text = $this->stripSectionName( $text );
5783  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5784  }
5785 
5794  public function guessLegacySectionNameFromWikiText( $text ) {
5795  # Strip out wikitext links(they break the anchor)
5796  $text = $this->stripSectionName( $text );
5798  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5799  }
5800 
5815  public function stripSectionName( $text ) {
5816  # Strip internal link markup
5817  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5818  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5819 
5820  # Strip external link markup
5821  # @todo FIXME: Not tolerant to blank link text
5822  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5823  # on how many empty links there are on the page - need to figure that out.
5824  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5825 
5826  # Parse wikitext quotes (italics & bold)
5827  $text = $this->doQuotes( $text );
5828 
5829  # Strip HTML tags
5830  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5831  return $text;
5832  }
5833 
5844  public function testSrvus( $text, Title $title, ParserOptions $options,
5845  $outputType = self::OT_HTML
5846  ) {
5847  $magicScopeVariable = $this->lock();
5848  $this->startParse( $title, $options, $outputType, true );
5849 
5850  $text = $this->replaceVariables( $text );
5851  $text = $this->mStripState->unstripBoth( $text );
5852  $text = Sanitizer::removeHTMLtags( $text );
5853  return $text;
5854  }
5855 
5862  public function testPst( $text, Title $title, ParserOptions $options ) {
5863  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5864  }
5865 
5872  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5873  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5874  }
5875 
5892  public function markerSkipCallback( $s, $callback ) {
5893  $i = 0;
5894  $out = '';
5895  while ( $i < strlen( $s ) ) {
5896  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5897  if ( $markerStart === false ) {
5898  $out .= call_user_func( $callback, substr( $s, $i ) );
5899  break;
5900  } else {
5901  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5902  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5903  if ( $markerEnd === false ) {
5904  $out .= substr( $s, $markerStart );
5905  break;
5906  } else {
5907  $markerEnd += strlen( self::MARKER_SUFFIX );
5908  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5909  $i = $markerEnd;
5910  }
5911  }
5912  }
5913  return $out;
5914  }
5915 
5922  public function killMarkers( $text ) {
5923  return $this->mStripState->killMarkers( $text );
5924  }
5925 
5942  public function serializeHalfParsedText( $text ) {
5943  $data = [
5944  'text' => $text,
5945  'version' => self::HALF_PARSED_VERSION,
5946  'stripState' => $this->mStripState->getSubState( $text ),
5947  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5948  ];
5949  return $data;
5950  }
5951 
5967  public function unserializeHalfParsedText( $data ) {
5968  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5969  throw new MWException( __METHOD__ . ': invalid version' );
5970  }
5971 
5972  # First, extract the strip state.
5973  $texts = [ $data['text'] ];
5974  $texts = $this->mStripState->merge( $data['stripState'], $texts );
5975 
5976  # Now renumber links
5977  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
5978 
5979  # Should be good to go.
5980  return $texts[0];
5981  }
5982 
5992  public function isValidHalfParsedText( $data ) {
5993  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
5994  }
5995 
6004  public function parseWidthParam( $value ) {
6005  $parsedWidthParam = [];
6006  if ( $value === '' ) {
6007  return $parsedWidthParam;
6008  }
6009  $m = [];
6010  # (bug 13500) In both cases (width/height and width only),
6011  # permit trailing "px" for backward compatibility.
6012  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6013  $width = intval( $m[1] );
6014  $height = intval( $m[2] );
6015  $parsedWidthParam['width'] = $width;
6016  $parsedWidthParam['height'] = $height;
6017  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6018  $width = intval( $value );
6019  $parsedWidthParam['width'] = $width;
6020  }
6021  return $parsedWidthParam;
6022  }
6023 
6033  protected function lock() {
6034  if ( $this->mInParse ) {
6035  throw new MWException( "Parser state cleared while parsing. "
6036  . "Did you call Parser::parse recursively?" );
6037  }
6038  $this->mInParse = true;
6039 
6040  $recursiveCheck = new ScopedCallback( function() {
6041  $this->mInParse = false;
6042  } );
6043 
6044  return $recursiveCheck;
6045  }
6046 
6057  public static function stripOuterParagraph( $html ) {
6058  $m = [];
6059  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6060  if ( strpos( $m[1], '</p>' ) === false ) {
6061  $html = $m[1];
6062  }
6063  }
6064 
6065  return $html;
6066  }
6067 
6078  public function getFreshParser() {
6079  global $wgParserConf;
6080  if ( $this->mInParse ) {
6081  return new $wgParserConf['class']( $wgParserConf );
6082  } else {
6083  return $this;
6084  }
6085  }
6086 
6093  public function enableOOUI() {
6095  $this->mOutput->setEnableOOUI( true );
6096  }
6097 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5644
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:525
setTitle($t)
Set the context title.
Definition: Parser.php:749
$mAutonumber
Definition: Parser.php:177
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3298
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5892
#define the
table suitable for use with IDatabase::select()
$mPPNodeCount
Definition: Parser.php:191
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2098
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:271
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:117
getExternalLinkAttribs($url)
Get an associative array of additional HTML attributes appropriate for a particular external link...
Definition: Parser.php:1925
const MARKER_PREFIX
Definition: Parser.php:134
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:5992
null means default in associative array form
Definition: hooks.txt:1940
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:1940
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1633
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:5609
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1287
$mTplRedirCache
Definition: Parser.php:193
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5922
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:1645
LinkRenderer $mLinkRenderer
Definition: Parser.php:257
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3524
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:806
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5696
setFunctionTagHook($tag, $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4887
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:5815
const OT_PREPROCESS
Definition: Defines.php:190
either a plain
Definition: hooks.txt:1991
$mDoubleUnderscores
Definition: Parser.php:193
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:697
$context
Definition: load.php:50
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4601
MapCacheLRU null $currentRevisionCache
Definition: Parser.php:243
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3209
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4938
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:678
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1828
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
nextLinkID()
Definition: Parser.php:838
const SPACE_NOT_NL
Definition: Parser.php:103
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:1940
static replaceUnusualEscapes($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1953
getImageParams($handler)
Definition: Parser.php:5100
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:114
getTags()
Accessor.
Definition: Parser.php:5415
static isWellFormedXmlFragment($text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:735
const OT_WIKI
Definition: Parser.php:111
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2106
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3666
User $mUser
Definition: Parser.php:200
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:2839
static isEnabled()
Definition: MWTidy.php:79
Set options of the Parser.
static tidy($text)
Interface with html tidy.
Definition: MWTidy.php:46
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:4873
static fixTagAttributes($text, $element, $sorted=false)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML...
Definition: Sanitizer.php:1071
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition: globals.txt:10
wfHostname()
Fetch server name for use in error reporting etc.
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:853
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3769
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5844
uniqPrefix()
Accessor for mUniqPrefix.
Definition: Parser.php:739
const TOC_START
Definition: Parser.php:137
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:777
SectionProfiler $mProfiler
Definition: Parser.php:252
$sort
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3691
null for the local wiki Added in
Definition: hooks.txt:1559
There are three types of nodes:
$mHeadings
Definition: Parser.php:193
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4786
static makeSelfLinkObj($nt, $html= '', $query= '', $trail= '', $prefix= '')
Make appropriate markup for a link to the current article.
Definition: Linker.php:277
const NS_SPECIAL
Definition: Defines.php:45
clearState()
Clear Parser state.
Definition: Parser.php:343
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition: hooks.txt:1050
__construct($conf=[])
Definition: Parser.php:262
const EXT_LINK_ADDR
Definition: Parser.php:95
$mFirstCall
Definition: Parser.php:152
interwikiTransclude($title, $action)
Transclude an interwiki link.
Definition: Parser.php:3710
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4476
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead.
Definition: Parser.php:5794
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:831
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2707
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5942
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4910
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3507
static activeUsers()
Definition: SiteStats.php:165
$mLinkID
Definition: Parser.php:190
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:2869
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:2991
static cleanUrl($url)
Definition: Sanitizer.php:1856
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:262
$mGeneratedPPNodeCount
Definition: Parser.php:191
static getRandomString()
Get a random string.
Definition: Parser.php:718
$mRevisionId
Definition: Parser.php:217
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:1823
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:2469
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:803
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:920
const NS_TEMPLATE
Definition: Defines.php:66
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:128
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2484
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:609
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2491
MagicWordArray $mVariables
Definition: Parser.php:159
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor'$rcid is used in generating this variable which contains information about the new revision
Definition: hooks.txt:1160
static validateTagAttributes($attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
Definition: Sanitizer.php:748
const SFH_NO_HASH
Definition: Parser.php:85
const DB_MASTER
Definition: defines.php:23
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:25
wfRandomString($length=32)
Get a random string containing a number of pseudo-random hex characters.
$mForceTocPosition
Definition: Parser.php:195
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:652
static getCacheTTL($id)
Allow external reads of TTL array.
Definition: MagicWord.php:294
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5634
const OT_PREPROCESS
Definition: Parser.php:112
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2457
$mFunctionSynonyms
Definition: Parser.php:144
If you want to remove the page from your watchlist later
replaceLinkHoldersText($text)
Replace "" link placeholders with plain text of links (not HTML-formatted).
Definition: Parser.php:4921
setLinkID($id)
Definition: Parser.php:845
$mOutputType
Definition: Parser.php:214
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:147
static createAssocArgs($args)
Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
Definition: Parser.php:2943
$mExtLinkBracketedRegex
Definition: Parser.php:166
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:Associative array mapping language codes to prefixed links of the form"language:title".&$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:1938
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition: defines.php:6
if($line===false) $args
Definition: cdb.php:64
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:307
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:1722
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3441
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2893
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1500
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4615
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static factory($mode=false, IContextSource $context=null)
Get a new image gallery.
$wgLanguageCode
Site language code.
Custom PHP profiler for parser/DB type section names that xhprof/xdebug can't handle.
static getPage($name)
Find the object with a given name and return it (or NULL)
static edits()
Definition: SiteStats.php:133
$wgExtraInterlanguageLinkPrefixes
List of additional interwiki prefixes that should be treated as interlanguage links (i...
startExternalParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Set up some variables which are usually set up in parse() so that an external function can call some ...
Definition: Parser.php:4666
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:4013
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2085
$mVarCache
Definition: Parser.php:148
$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:5387
$mRevisionObject
Definition: Parser.php:216
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:1381
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:213
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:288
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:5673
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6057
static register($parser)
$mRevIdForTs
Definition: Parser.php:221
static singleton()
Get an instance of this class.
Definition: LinkCache.php:64
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
static normalizeSubpageLink($contextTitle, $target, &$text)
Definition: Linker.php:1439
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:6004
$mStripList
Definition: Parser.php:146
$mFunctionTagHooks
Definition: Parser.php:145
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3729
const OT_PLAIN
Definition: Defines.php:192
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3484
$mRevisionTimestamp
Definition: Parser.php:218
$mImageParams
Definition: Parser.php:149
stripAltText($caption, $holders)
Definition: Parser.php:5363
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:2003
if($limit) $timestamp
const VERSION
Update this version number when the ParserOutput format changes in an incompatible way...
Definition: Parser.php:76
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1050
setHook($tag, $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4741
const OT_WIKI
Definition: Defines.php:189
Preprocessor $mPreprocessor
Definition: Parser.php:170
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:906
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:38
const NS_MEDIA
Definition: Defines.php:44
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:2914
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:182
$mDefaultSort
Definition: Parser.php:192
getUser()
Get a User object either from $this->mUser, if set, or from the ParserOptions object otherwise...
Definition: Parser.php:894
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3931
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1023
const EXT_IMAGE_REGEX
Definition: Parser.php:98
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4678
$params
const NS_CATEGORY
Definition: Defines.php:70
static makeHeadline($level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1701
static extractTagsAndParams($elements, $text, &$matches, $uniq_prefix=null)
Replaces all occurrences of HTML-style comments and the given tags in the text with a random marker a...
Definition: Parser.php:953
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
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:5716
$mImageParamsMagicArray
Definition: Parser.php:150
LinkHolderArray $mLinkHolders
Definition: Parser.php:188
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: defines.php:11
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:1940
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:4444
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:866
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:957
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:1905
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:4033
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:884
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1600
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static tocLine($anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1615
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:222
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
Definition: magicword.txt:4
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:1011
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:4552
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:62
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:323
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:415
const PTD_FOR_INCLUSION
Definition: Parser.php:106
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:1940
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:2435
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:1725
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2881
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:786
$mTagHooks
Definition: Parser.php:141
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:64
static & get($id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:257
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6093
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:246
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor'$rcid is used in generating this variable which contains information about the new such as the revision s whether the revision was marked as a minor edit or not
Definition: hooks.txt:1160
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3552
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:2026
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2444
const OT_HTML
Definition: Defines.php:188
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1170
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1050
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:284
static images()
Definition: SiteStats.php:173
$mTransparentTagHooks
Definition: Parser.php:142
$mExpensiveFunctionCount
Definition: Parser.php:194
$mUrlProtocols
Definition: Parser.php:166
$mConf
Definition: Parser.php:166
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4697
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:110
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:934
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:300
static getExternalLinkRel($url=false, $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:1904
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:1011
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:806
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:1725
array $mLangLinkLanguages
Array with the language name of each language link (i.e.
Definition: Parser.php:235
const OT_MSG
Definition: Parser.php:113
replaceTransparentTags($text)
Replace transparent tags in $text with the values given by the callbacks.
Definition: Parser.php:5433
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:5625
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3958
$mFunctionHooks
Definition: Parser.php:143
static removeHTMLtags($text, $processCallback=null, $args=[], $extratags=[], $removetags=[], $warnCallback=null)
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
Definition: Sanitizer.php:462
$lines
Definition: router.php:67
testPreprocess($text, Title $title, ParserOptions $options)
Definition: Parser.php:5872
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:164
const TOC_END
Definition: Parser.php:138
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1400
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3342
$wgScriptPath
The path we should point to.
Variant of the Message class.
Definition: Message.php:1260
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6078
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
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1050
static articles()
Definition: SiteStats.php:141
$mRevisionUser
Definition: Parser.php:219
lock()
Lock the current instance of the parser.
Definition: Parser.php:6033
static pages()
Definition: SiteStats.php:149
$line
Definition: cdb.php:59
const SFH_OBJECT_ARGS
Definition: Parser.php:86
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:2411
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3565
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
Definition: design.txt:79
setFunctionHook($id, $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4835
static setupOOUI($skinName= '', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php:874
$mIncludeCount
Definition: Parser.php:184
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:2893
$mMarkerIndex
Definition: Parser.php:151
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1050
getTitle()
Accessor for the Title object.
Definition: Parser.php:767
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:5484
ParserOutput $mOutput
Definition: Parser.php:176
getOutput()
Get the ParserOutput object.
Definition: Parser.php:812
$wgExperimentalHtmlIds
Should we allow a broader set of characters in id attributes, per HTML5? If not, use only HTML 4-comp...
doMagicLinks($text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
Definition: Parser.php:1430
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
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1050
static cleanSigInSig($text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4652
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5737
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3655
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1589
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:593
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all.It could be easily changed to send incrementally if that becomes useful
static doBlockLevels($text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
$wgServer
URL of the server.
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:86
incrementExpensiveFunctionCount()
Increment the expensive function count.
Definition: Parser.php:3945
$mShowToc
Definition: Parser.php:195
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1967
const DB_REPLICA
Definition: defines.php:22
magicLinkCallback($m)
Definition: Parser.php:1460
const EXT_LINK_URL_CLASS
Definition: Parser.php:92
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:5862
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.
if(!$wgRequest->checkUrlExtension()) if(isset($_SERVER['PATH_INFO'])&&$_SERVER['PATH_INFO']!= '') if(!$wgEnableAPI) $wgTitle
Definition: api.php:68
static explode($separator, $subject)
Workalike for explode() with limited memory usage.
ParserOptions $mOptions
Definition: Parser.php:208
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:403
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:806
static numberingroup($group)
Find the number of users in a given user group.
Definition: SiteStats.php:183
=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()(which returns text) is superceded by Article::getContentObject().However, both methods should be avoided since they do not provide clean access to the page's actual content.For instance, they may return a system message for non-existing pages.Use WikiPage::getContent() instead.Code that relies on a textual representation of the page content should eventually be rewritten.However, ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page.Its behavior is controlled by $wgContentHandlerTextFallback it
const STRIP_COMMENTS
static getVersion($flags= '', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
braceSubstitution($piece, $frame)
Return the text of a template, after recursively replacing any variables or templates within the temp...
Definition: Parser.php:3013
setUser($user)
Set the current user.
Definition: Parser.php:729
$mHighestExpansionDepth
Definition: Parser.php:191
makeImage($title, $options, $holders=false)
Parse image options text and use it to make an image.
Definition: Parser.php:5148
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5404
static cascadingsources($parser, $title= '')
Returns the sources of any cascading protection acting on a specified page.
getCustomDefaultSort()
Accessor for $mDefaultSort Unlike getDefaultSort(), will return false if none is set.
Definition: Parser.php:5766
extensionSubstitution($params, $frame)
Return the text to be used for a given extension tag.
Definition: Parser.php:3822
static normalizeLineEndings($text)
Do a "\\r\\n" -> "\\n" and "\\r" -> "\\n" transformation as well as trim trailing whitespace...
static makeExternalImage($url, $alt= '')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage()...
Definition: Linker.php:362
recursiveTagParseFully($text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition: Parser.php:635
setTransparentTagHook($tag, $callback)
As setHook(), but letting the contents be parsed.
Definition: Parser.php:4772
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:229
wfFindFile($title, $options=[])
Find a file.
$mRevisionSize
Definition: Parser.php:220
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk page
Definition: hooks.txt:2495
static users()
Definition: SiteStats.php:157
unserializeHalfParsedText($data)
Load the parser state given in the $data array, which is assumed to have been generated by serializeH...
Definition: Parser.php:5967
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2495
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:511
guessSectionNameFromWikiText($text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition: Parser.php:5779
const SFH_OBJECT_ARGS
Definition: Defines.php:202
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1753
$wgServerName
Server name.
internalParseHalfParsed($text, $isMain=true, $linestart=true)
Helper function for parse() that transforms half-parsed HTML into fully parsed HTML.
Definition: Parser.php:1326
const OT_HTML
Definition: Parser.php:110
$mIncludeSizes
Definition: Parser.php:191
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1614
controlled by $wgMainCacheType controlled by $wgParserCacheType controlled by $wgMessageCacheType If you set CACHE_NONE to one of the three control variable
Definition: memcached.txt:78
getOptions()
Get the ParserOptions object.
Definition: Parser.php:821
getDefaultSort()
Accessor for $mDefaultSort Will use the empty string if none is set.
Definition: Parser.php:5752
For a write use something like
Definition: database.txt:26
const SFH_NO_HASH
Definition: Defines.php:201
makeFreeExternalLink($url, $numPostProto)
Make a free external link, given a user-supplied URL.
Definition: Parser.php:1532
$matches
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
$mTplDomCache
Definition: Parser.php:193