MediaWiki  master
Parser.php
Go to the documentation of this file.
1 <?php
26 
70 class Parser {
76  const VERSION = '1.6.4';
77 
83 
84  # Flags for Parser::setFunctionHook
85  const SFH_NO_HASH = 1;
86  const SFH_OBJECT_ARGS = 2;
87 
88  # Constants needed for external link processing
89  # Everything except bracket, space, or control characters
90  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
91  # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
92  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 (T15770)
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  // T58226: 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  Hooks::run( 'ParserFirstCallInit', [ &$this ] );
334  }
335 
341  public function clearState() {
342  if ( $this->mFirstCall ) {
343  $this->firstCallInit();
344  }
345  $this->mOutput = new ParserOutput;
346  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
347  $this->mAutonumber = 0;
348  $this->mIncludeCount = [];
349  $this->mLinkHolders = new LinkHolderArray( $this );
350  $this->mLinkID = 0;
351  $this->mRevisionObject = $this->mRevisionTimestamp =
352  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
353  $this->mVarCache = [];
354  $this->mUser = null;
355  $this->mLangLinkLanguages = [];
356  $this->currentRevisionCache = null;
357 
358  $this->mStripState = new StripState;
359 
360  # Clear these on every parse, T6549
361  $this->mTplRedirCache = $this->mTplDomCache = [];
362 
363  $this->mShowToc = true;
364  $this->mForceTocPosition = false;
365  $this->mIncludeSizes = [
366  'post-expand' => 0,
367  'arg' => 0,
368  ];
369  $this->mPPNodeCount = 0;
370  $this->mGeneratedPPNodeCount = 0;
371  $this->mHighestExpansionDepth = 0;
372  $this->mDefaultSort = false;
373  $this->mHeadings = [];
374  $this->mDoubleUnderscores = [];
375  $this->mExpensiveFunctionCount = 0;
376 
377  # Fix cloning
378  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
379  $this->mPreprocessor = null;
380  }
381 
382  $this->mProfiler = new SectionProfiler();
383 
384  Hooks::run( 'ParserClearState', [ &$this ] );
385  }
386 
399  public function parse(
401  $linestart = true, $clearState = true, $revid = null
402  ) {
408  global $wgShowHostnames;
409 
410  if ( $clearState ) {
411  // We use U+007F DELETE to construct strip markers, so we have to make
412  // sure that this character does not occur in the input text.
413  $text = strtr( $text, "\x7f", "?" );
414  $magicScopeVariable = $this->lock();
415  }
416 
417  $this->startParse( $title, $options, self::OT_HTML, $clearState );
418 
419  $this->currentRevisionCache = null;
420  $this->mInputSize = strlen( $text );
421  if ( $this->mOptions->getEnableLimitReport() ) {
422  $this->mOutput->resetParseStartTime();
423  }
424 
425  $oldRevisionId = $this->mRevisionId;
426  $oldRevisionObject = $this->mRevisionObject;
427  $oldRevisionTimestamp = $this->mRevisionTimestamp;
428  $oldRevisionUser = $this->mRevisionUser;
429  $oldRevisionSize = $this->mRevisionSize;
430  if ( $revid !== null ) {
431  $this->mRevisionId = $revid;
432  $this->mRevisionObject = null;
433  $this->mRevisionTimestamp = null;
434  $this->mRevisionUser = null;
435  $this->mRevisionSize = null;
436  }
437 
438  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
439  # No more strip!
440  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
441  $text = $this->internalParse( $text );
442  Hooks::run( 'ParserAfterParse', [ &$this, &$text, &$this->mStripState ] );
443 
444  $text = $this->internalParseHalfParsed( $text, true, $linestart );
445 
453  if ( !( $options->getDisableTitleConversion()
454  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
455  || isset( $this->mDoubleUnderscores['notitleconvert'] )
456  || $this->mOutput->getDisplayTitle() !== false )
457  ) {
458  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
459  if ( $convruletitle ) {
460  $this->mOutput->setTitleText( $convruletitle );
461  } else {
462  $titleText = $this->getConverterLanguage()->convertTitle( $title );
463  $this->mOutput->setTitleText( $titleText );
464  }
465  }
466 
467  # Done parsing! Compute runtime adaptive expiry if set
468  $this->mOutput->finalizeAdaptiveCacheExpiry();
469 
470  # Warn if too many heavyweight parser functions were used
471  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
472  $this->limitationWarn( 'expensive-parserfunction',
473  $this->mExpensiveFunctionCount,
474  $this->mOptions->getExpensiveParserFunctionLimit()
475  );
476  }
477 
478  # Information on include size limits, for the benefit of users who try to skirt them
479  if ( $this->mOptions->getEnableLimitReport() ) {
480  $max = $this->mOptions->getMaxIncludeSize();
481 
482  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
483  if ( $cpuTime !== null ) {
484  $this->mOutput->setLimitReportData( 'limitreport-cputime',
485  sprintf( "%.3f", $cpuTime )
486  );
487  }
488 
489  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
490  $this->mOutput->setLimitReportData( 'limitreport-walltime',
491  sprintf( "%.3f", $wallTime )
492  );
493 
494  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
495  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
496  );
497  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
498  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
499  );
500  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
501  [ $this->mIncludeSizes['post-expand'], $max ]
502  );
503  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
504  [ $this->mIncludeSizes['arg'], $max ]
505  );
506  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
507  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
508  );
509  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
510  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
511  );
512  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
513 
514  $limitReport = "NewPP limit report\n";
515  if ( $wgShowHostnames ) {
516  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
517  }
518  $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
519  $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
520  $limitReport .= 'Dynamic content: ' .
521  ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
522  "\n";
523 
524  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
525  if ( Hooks::run( 'ParserLimitReportFormat',
526  [ $key, &$value, &$limitReport, false, false ]
527  ) ) {
528  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
529  $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
530  ->inLanguage( 'en' )->useDatabase( false );
531  if ( !$valueMsg->exists() ) {
532  $valueMsg = new RawMessage( '$1' );
533  }
534  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
535  $valueMsg->params( $value );
536  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
537  }
538  }
539  }
540  // Since we're not really outputting HTML, decode the entities and
541  // then re-encode the things that need hiding inside HTML comments.
542  $limitReport = htmlspecialchars_decode( $limitReport );
543  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
544 
545  // Sanitize for comment. Note '‐' in the replacement is U+2010,
546  // which looks much like the problematic '-'.
547  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
548  $text .= "\n<!-- \n$limitReport-->\n";
549 
550  // Add on template profiling data in human/machine readable way
551  $dataByFunc = $this->mProfiler->getFunctionStats();
552  uasort( $dataByFunc, function ( $a, $b ) {
553  return $a['real'] < $b['real']; // descending order
554  } );
555  $profileReport = [];
556  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
557  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
558  $item['%real'], $item['real'], $item['calls'],
559  htmlspecialchars( $item['name'] ) );
560  }
561  $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
562  $text .= implode( "\n", $profileReport ) . "\n-->\n";
563 
564  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
565 
566  // Add other cache related metadata
567  if ( $wgShowHostnames ) {
568  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
569  }
570  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
571  $this->mOutput->getCacheTime() );
572  $this->mOutput->setLimitReportData( 'cachereport-ttl',
573  $this->mOutput->getCacheExpiry() );
574  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
575  $this->mOutput->hasDynamicContent() );
576 
577  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
578  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
579  $this->mTitle->getPrefixedDBkey() );
580  }
581  }
582  $this->mOutput->setText( $text );
583 
584  $this->mRevisionId = $oldRevisionId;
585  $this->mRevisionObject = $oldRevisionObject;
586  $this->mRevisionTimestamp = $oldRevisionTimestamp;
587  $this->mRevisionUser = $oldRevisionUser;
588  $this->mRevisionSize = $oldRevisionSize;
589  $this->mInputSize = false;
590  $this->currentRevisionCache = null;
591 
592  return $this->mOutput;
593  }
594 
617  public function recursiveTagParse( $text, $frame = false ) {
618  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
619  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
620  $text = $this->internalParse( $text, false, $frame );
621  return $text;
622  }
623 
641  public function recursiveTagParseFully( $text, $frame = false ) {
642  $text = $this->recursiveTagParse( $text, $frame );
643  $text = $this->internalParseHalfParsed( $text, false );
644  return $text;
645  }
646 
658  public function preprocess( $text, Title $title = null,
659  ParserOptions $options, $revid = null, $frame = false
660  ) {
661  $magicScopeVariable = $this->lock();
662  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
663  if ( $revid !== null ) {
664  $this->mRevisionId = $revid;
665  }
666  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
667  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
668  $text = $this->replaceVariables( $text, $frame );
669  $text = $this->mStripState->unstripBoth( $text );
670  return $text;
671  }
672 
682  public function recursivePreprocess( $text, $frame = false ) {
683  $text = $this->replaceVariables( $text, $frame );
684  $text = $this->mStripState->unstripBoth( $text );
685  return $text;
686  }
687 
701  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
702  $msg = new RawMessage( $text );
703  $text = $msg->params( $params )->plain();
704 
705  # Parser (re)initialisation
706  $magicScopeVariable = $this->lock();
707  $this->startParse( $title, $options, self::OT_PLAIN, true );
708 
710  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
711  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
712  $text = $this->mStripState->unstripBoth( $text );
713  return $text;
714  }
715 
722  public static function getRandomString() {
723  wfDeprecated( __METHOD__, '1.26' );
724  return wfRandomString( 16 );
725  }
726 
733  public function setUser( $user ) {
734  $this->mUser = $user;
735  }
736 
743  public function uniqPrefix() {
744  wfDeprecated( __METHOD__, '1.26' );
745  return self::MARKER_PREFIX;
746  }
747 
753  public function setTitle( $t ) {
754  if ( !$t ) {
755  $t = Title::newFromText( 'NO TITLE' );
756  }
757 
758  if ( $t->hasFragment() ) {
759  # Strip the fragment to avoid various odd effects
760  $this->mTitle = $t->createFragmentTarget( '' );
761  } else {
762  $this->mTitle = $t;
763  }
764  }
765 
771  public function getTitle() {
772  return $this->mTitle;
773  }
774 
781  public function Title( $x = null ) {
782  return wfSetVar( $this->mTitle, $x );
783  }
784 
790  public function setOutputType( $ot ) {
791  $this->mOutputType = $ot;
792  # Shortcut alias
793  $this->ot = [
794  'html' => $ot == self::OT_HTML,
795  'wiki' => $ot == self::OT_WIKI,
796  'pre' => $ot == self::OT_PREPROCESS,
797  'plain' => $ot == self::OT_PLAIN,
798  ];
799  }
800 
807  public function OutputType( $x = null ) {
808  return wfSetVar( $this->mOutputType, $x );
809  }
810 
816  public function getOutput() {
817  return $this->mOutput;
818  }
819 
825  public function getOptions() {
826  return $this->mOptions;
827  }
828 
835  public function Options( $x = null ) {
836  return wfSetVar( $this->mOptions, $x );
837  }
838 
842  public function nextLinkID() {
843  return $this->mLinkID++;
844  }
845 
849  public function setLinkID( $id ) {
850  $this->mLinkID = $id;
851  }
852 
857  public function getFunctionLang() {
858  return $this->getTargetLanguage();
859  }
860 
870  public function getTargetLanguage() {
871  $target = $this->mOptions->getTargetLanguage();
872 
873  if ( $target !== null ) {
874  return $target;
875  } elseif ( $this->mOptions->getInterfaceMessage() ) {
876  return $this->mOptions->getUserLangObj();
877  } elseif ( is_null( $this->mTitle ) ) {
878  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
879  }
880 
881  return $this->mTitle->getPageLanguage();
882  }
883 
888  public function getConverterLanguage() {
889  return $this->getTargetLanguage();
890  }
891 
898  public function getUser() {
899  if ( !is_null( $this->mUser ) ) {
900  return $this->mUser;
901  }
902  return $this->mOptions->getUser();
903  }
904 
910  public function getPreprocessor() {
911  if ( !isset( $this->mPreprocessor ) ) {
912  $class = $this->mPreprocessorClass;
913  $this->mPreprocessor = new $class( $this );
914  }
915  return $this->mPreprocessor;
916  }
917 
924  public function getLinkRenderer() {
925  if ( !$this->mLinkRenderer ) {
926  $this->mLinkRenderer = MediaWikiServices::getInstance()
927  ->getLinkRendererFactory()->create();
928  $this->mLinkRenderer->setStubThreshold(
929  $this->getOptions()->getStubThreshold()
930  );
931  }
932 
933  return $this->mLinkRenderer;
934  }
935 
957  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
958  if ( $uniq_prefix !== null ) {
959  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
960  }
961  static $n = 1;
962  $stripped = '';
963  $matches = [];
964 
965  $taglist = implode( '|', $elements );
966  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
967 
968  while ( $text != '' ) {
969  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
970  $stripped .= $p[0];
971  if ( count( $p ) < 5 ) {
972  break;
973  }
974  if ( count( $p ) > 5 ) {
975  # comment
976  $element = $p[4];
977  $attributes = '';
978  $close = '';
979  $inside = $p[5];
980  } else {
981  # tag
982  $element = $p[1];
983  $attributes = $p[2];
984  $close = $p[3];
985  $inside = $p[4];
986  }
987 
988  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
989  $stripped .= $marker;
990 
991  if ( $close === '/>' ) {
992  # Empty element tag, <tag />
993  $content = null;
994  $text = $inside;
995  $tail = null;
996  } else {
997  if ( $element === '!--' ) {
998  $end = '/(-->)/';
999  } else {
1000  $end = "/(<\\/$element\\s*>)/i";
1001  }
1002  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1003  $content = $q[0];
1004  if ( count( $q ) < 3 ) {
1005  # No end tag -- let it run out to the end of the text.
1006  $tail = '';
1007  $text = '';
1008  } else {
1009  $tail = $q[1];
1010  $text = $q[2];
1011  }
1012  }
1013 
1014  $matches[$marker] = [ $element,
1015  $content,
1016  Sanitizer::decodeTagAttributes( $attributes ),
1017  "<$element$attributes$close$content$tail" ];
1018  }
1019  return $stripped;
1020  }
1021 
1027  public function getStripList() {
1028  return $this->mStripList;
1029  }
1030 
1040  public function insertStripItem( $text ) {
1041  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1042  $this->mMarkerIndex++;
1043  $this->mStripState->addGeneral( $marker, $text );
1044  return $marker;
1045  }
1046 
1054  public function doTableStuff( $text ) {
1055 
1056  $lines = StringUtils::explode( "\n", $text );
1057  $out = '';
1058  $td_history = []; # Is currently a td tag open?
1059  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1060  $tr_history = []; # Is currently a tr tag open?
1061  $tr_attributes = []; # history of tr attributes
1062  $has_opened_tr = []; # Did this table open a <tr> element?
1063  $indent_level = 0; # indent level of the table
1064 
1065  foreach ( $lines as $outLine ) {
1066  $line = trim( $outLine );
1067 
1068  if ( $line === '' ) { # empty line, go to next line
1069  $out .= $outLine . "\n";
1070  continue;
1071  }
1072 
1073  $first_character = $line[0];
1074  $first_two = substr( $line, 0, 2 );
1075  $matches = [];
1076 
1077  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1078  # First check if we are starting a new table
1079  $indent_level = strlen( $matches[1] );
1080 
1081  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1082  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1083 
1084  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1085  array_push( $td_history, false );
1086  array_push( $last_tag_history, '' );
1087  array_push( $tr_history, false );
1088  array_push( $tr_attributes, '' );
1089  array_push( $has_opened_tr, false );
1090  } elseif ( count( $td_history ) == 0 ) {
1091  # Don't do any of the following
1092  $out .= $outLine . "\n";
1093  continue;
1094  } elseif ( $first_two === '|}' ) {
1095  # We are ending a table
1096  $line = '</table>' . substr( $line, 2 );
1097  $last_tag = array_pop( $last_tag_history );
1098 
1099  if ( !array_pop( $has_opened_tr ) ) {
1100  $line = "<tr><td></td></tr>{$line}";
1101  }
1102 
1103  if ( array_pop( $tr_history ) ) {
1104  $line = "</tr>{$line}";
1105  }
1106 
1107  if ( array_pop( $td_history ) ) {
1108  $line = "</{$last_tag}>{$line}";
1109  }
1110  array_pop( $tr_attributes );
1111  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1112  } elseif ( $first_two === '|-' ) {
1113  # Now we have a table row
1114  $line = preg_replace( '#^\|-+#', '', $line );
1115 
1116  # Whats after the tag is now only attributes
1117  $attributes = $this->mStripState->unstripBoth( $line );
1118  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1119  array_pop( $tr_attributes );
1120  array_push( $tr_attributes, $attributes );
1121 
1122  $line = '';
1123  $last_tag = array_pop( $last_tag_history );
1124  array_pop( $has_opened_tr );
1125  array_push( $has_opened_tr, true );
1126 
1127  if ( array_pop( $tr_history ) ) {
1128  $line = '</tr>';
1129  }
1130 
1131  if ( array_pop( $td_history ) ) {
1132  $line = "</{$last_tag}>{$line}";
1133  }
1134 
1135  $outLine = $line;
1136  array_push( $tr_history, false );
1137  array_push( $td_history, false );
1138  array_push( $last_tag_history, '' );
1139  } elseif ( $first_character === '|'
1140  || $first_character === '!'
1141  || $first_two === '|+'
1142  ) {
1143  # This might be cell elements, td, th or captions
1144  if ( $first_two === '|+' ) {
1145  $first_character = '+';
1146  $line = substr( $line, 2 );
1147  } else {
1148  $line = substr( $line, 1 );
1149  }
1150 
1151  // Implies both are valid for table headings.
1152  if ( $first_character === '!' ) {
1153  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1154  }
1155 
1156  # Split up multiple cells on the same line.
1157  # FIXME : This can result in improper nesting of tags processed
1158  # by earlier parser steps.
1159  $cells = explode( '||', $line );
1160 
1161  $outLine = '';
1162 
1163  # Loop through each table cell
1164  foreach ( $cells as $cell ) {
1165  $previous = '';
1166  if ( $first_character !== '+' ) {
1167  $tr_after = array_pop( $tr_attributes );
1168  if ( !array_pop( $tr_history ) ) {
1169  $previous = "<tr{$tr_after}>\n";
1170  }
1171  array_push( $tr_history, true );
1172  array_push( $tr_attributes, '' );
1173  array_pop( $has_opened_tr );
1174  array_push( $has_opened_tr, true );
1175  }
1176 
1177  $last_tag = array_pop( $last_tag_history );
1178 
1179  if ( array_pop( $td_history ) ) {
1180  $previous = "</{$last_tag}>\n{$previous}";
1181  }
1182 
1183  if ( $first_character === '|' ) {
1184  $last_tag = 'td';
1185  } elseif ( $first_character === '!' ) {
1186  $last_tag = 'th';
1187  } elseif ( $first_character === '+' ) {
1188  $last_tag = 'caption';
1189  } else {
1190  $last_tag = '';
1191  }
1192 
1193  array_push( $last_tag_history, $last_tag );
1194 
1195  # A cell could contain both parameters and data
1196  $cell_data = explode( '|', $cell, 2 );
1197 
1198  # T2553: Note that a '|' inside an invalid link should not
1199  # be mistaken as delimiting cell parameters
1200  # Bug T153140: Neither should language converter markup.
1201  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1202  $cell = "{$previous}<{$last_tag}>{$cell}";
1203  } elseif ( count( $cell_data ) == 1 ) {
1204  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1205  } else {
1206  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1207  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1208  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1209  }
1210 
1211  $outLine .= $cell;
1212  array_push( $td_history, true );
1213  }
1214  }
1215  $out .= $outLine . "\n";
1216  }
1217 
1218  # Closing open td, tr && table
1219  while ( count( $td_history ) > 0 ) {
1220  if ( array_pop( $td_history ) ) {
1221  $out .= "</td>\n";
1222  }
1223  if ( array_pop( $tr_history ) ) {
1224  $out .= "</tr>\n";
1225  }
1226  if ( !array_pop( $has_opened_tr ) ) {
1227  $out .= "<tr><td></td></tr>\n";
1228  }
1229 
1230  $out .= "</table>\n";
1231  }
1232 
1233  # Remove trailing line-ending (b/c)
1234  if ( substr( $out, -1 ) === "\n" ) {
1235  $out = substr( $out, 0, -1 );
1236  }
1237 
1238  # special case: don't return empty table
1239  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1240  $out = '';
1241  }
1242 
1243  return $out;
1244  }
1245 
1258  public function internalParse( $text, $isMain = true, $frame = false ) {
1259 
1260  $origText = $text;
1261 
1262  # Hook to suspend the parser in this state
1263  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$this, &$text, &$this->mStripState ] ) ) {
1264  return $text;
1265  }
1266 
1267  # if $frame is provided, then use $frame for replacing any variables
1268  if ( $frame ) {
1269  # use frame depth to infer how include/noinclude tags should be handled
1270  # depth=0 means this is the top-level document; otherwise it's an included document
1271  if ( !$frame->depth ) {
1272  $flag = 0;
1273  } else {
1274  $flag = Parser::PTD_FOR_INCLUSION;
1275  }
1276  $dom = $this->preprocessToDom( $text, $flag );
1277  $text = $frame->expand( $dom );
1278  } else {
1279  # if $frame is not provided, then use old-style replaceVariables
1280  $text = $this->replaceVariables( $text );
1281  }
1282 
1283  Hooks::run( 'InternalParseBeforeSanitize', [ &$this, &$text, &$this->mStripState ] );
1284  $text = Sanitizer::removeHTMLtags(
1285  $text,
1286  [ &$this, 'attributeStripCallback' ],
1287  false,
1288  array_keys( $this->mTransparentTagHooks ),
1289  [],
1290  [ &$this, 'addTrackingCategory' ]
1291  );
1292  Hooks::run( 'InternalParseBeforeLinks', [ &$this, &$text, &$this->mStripState ] );
1293 
1294  # Tables need to come after variable replacement for things to work
1295  # properly; putting them before other transformations should keep
1296  # exciting things like link expansions from showing up in surprising
1297  # places.
1298  $text = $this->doTableStuff( $text );
1299 
1300  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1301 
1302  $text = $this->doDoubleUnderscore( $text );
1303 
1304  $text = $this->doHeadings( $text );
1305  $text = $this->replaceInternalLinks( $text );
1306  $text = $this->doAllQuotes( $text );
1307  $text = $this->replaceExternalLinks( $text );
1308 
1309  # replaceInternalLinks may sometimes leave behind
1310  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1311  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1312 
1313  $text = $this->doMagicLinks( $text );
1314  $text = $this->formatHeadings( $text, $origText, $isMain );
1315 
1316  return $text;
1317  }
1318 
1328  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1329  $text = $this->mStripState->unstripGeneral( $text );
1330 
1331  if ( $isMain ) {
1332  Hooks::run( 'ParserAfterUnstrip', [ &$this, &$text ] );
1333  }
1334 
1335  # Clean up special characters, only run once, next-to-last before doBlockLevels
1336  $fixtags = [
1337  # French spaces, last one Guillemet-left
1338  # only if there is something before the space
1339  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1340  # french spaces, Guillemet-right
1341  '/(\\302\\253) /' => '\\1&#160;',
1342  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1343  ];
1344  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1345 
1346  $text = $this->doBlockLevels( $text, $linestart );
1347 
1348  $this->replaceLinkHolders( $text );
1349 
1357  if ( !( $this->mOptions->getDisableContentConversion()
1358  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1359  ) {
1360  if ( !$this->mOptions->getInterfaceMessage() ) {
1361  # The position of the convert() call should not be changed. it
1362  # assumes that the links are all replaced and the only thing left
1363  # is the <nowiki> mark.
1364  $text = $this->getConverterLanguage()->convert( $text );
1365  }
1366  }
1367 
1368  $text = $this->mStripState->unstripNoWiki( $text );
1369 
1370  if ( $isMain ) {
1371  Hooks::run( 'ParserBeforeTidy', [ &$this, &$text ] );
1372  }
1373 
1374  $text = $this->replaceTransparentTags( $text );
1375  $text = $this->mStripState->unstripGeneral( $text );
1376 
1377  $text = Sanitizer::normalizeCharReferences( $text );
1378 
1379  if ( MWTidy::isEnabled() ) {
1380  if ( $this->mOptions->getTidy() ) {
1381  $text = MWTidy::tidy( $text );
1382  }
1383  } else {
1384  # attempt to sanitize at least some nesting problems
1385  # (T4702 and quite a few others)
1386  $tidyregs = [
1387  # ''Something [http://www.cool.com cool''] -->
1388  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1389  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1390  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1391  # fix up an anchor inside another anchor, only
1392  # at least for a single single nested link (T5695)
1393  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1394  '\\1\\2</a>\\3</a>\\1\\4</a>',
1395  # fix div inside inline elements- doBlockLevels won't wrap a line which
1396  # contains a div, so fix it up here; replace
1397  # div with escaped text
1398  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1399  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1400  # remove empty italic or bold tag pairs, some
1401  # introduced by rules above
1402  '/<([bi])><\/\\1>/' => '',
1403  ];
1404 
1405  $text = preg_replace(
1406  array_keys( $tidyregs ),
1407  array_values( $tidyregs ),
1408  $text );
1409  }
1410 
1411  if ( $isMain ) {
1412  Hooks::run( 'ParserAfterTidy', [ &$this, &$text ] );
1413  }
1414 
1415  return $text;
1416  }
1417 
1429  public function doMagicLinks( $text ) {
1430  $prots = wfUrlProtocolsWithoutProtRel();
1431  $urlChar = self::EXT_LINK_URL_CLASS;
1432  $addr = self::EXT_LINK_ADDR;
1433  $space = self::SPACE_NOT_NL; # non-newline space
1434  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1435  $spaces = "$space++"; # possessive match of 1 or more spaces
1436  $text = preg_replace_callback(
1437  '!(?: # Start cases
1438  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1439  (<.*?>) | # m[2]: Skip stuff inside
1440  # HTML elements' . "
1441  (\b(?i:$prots)($addr$urlChar*)) | # m[3]: Free external links
1442  # m[4]: Post-protocol path
1443  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1444  ([0-9]+)\b |
1445  \bISBN $spaces ( # m[6]: ISBN, capture number
1446  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1447  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1448  [0-9Xx] # check digit
1449  )\b
1450  )!xu", [ &$this, 'magicLinkCallback' ], $text );
1451  return $text;
1452  }
1453 
1459  public function magicLinkCallback( $m ) {
1460  if ( isset( $m[1] ) && $m[1] !== '' ) {
1461  # Skip anchor
1462  return $m[0];
1463  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1464  # Skip HTML element
1465  return $m[0];
1466  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1467  # Free external link
1468  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1469  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1470  # RFC or PMID
1471  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1472  if ( !$this->mOptions->getMagicRFCLinks() ) {
1473  return $m[0];
1474  }
1475  $keyword = 'RFC';
1476  $urlmsg = 'rfcurl';
1477  $cssClass = 'mw-magiclink-rfc';
1478  $trackingCat = 'magiclink-tracking-rfc';
1479  $id = $m[5];
1480  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1481  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1482  return $m[0];
1483  }
1484  $keyword = 'PMID';
1485  $urlmsg = 'pubmedurl';
1486  $cssClass = 'mw-magiclink-pmid';
1487  $trackingCat = 'magiclink-tracking-pmid';
1488  $id = $m[5];
1489  } else {
1490  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1491  substr( $m[0], 0, 20 ) . '"' );
1492  }
1493  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1494  $this->addTrackingCategory( $trackingCat );
1495  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1496  } elseif ( isset( $m[6] ) && $m[6] !== ''
1497  && $this->mOptions->getMagicISBNLinks()
1498  ) {
1499  # ISBN
1500  $isbn = $m[6];
1501  $space = self::SPACE_NOT_NL; # non-newline space
1502  $isbn = preg_replace( "/$space/", ' ', $isbn );
1503  $num = strtr( $isbn, [
1504  '-' => '',
1505  ' ' => '',
1506  'x' => 'X',
1507  ] );
1508  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1509  return $this->getLinkRenderer()->makeKnownLink(
1510  SpecialPage::getTitleFor( 'Booksources', $num ),
1511  "ISBN $isbn",
1512  [
1513  'class' => 'internal mw-magiclink-isbn',
1514  'title' => false // suppress title attribute
1515  ]
1516  );
1517  } else {
1518  return $m[0];
1519  }
1520  }
1521 
1531  public function makeFreeExternalLink( $url, $numPostProto ) {
1532  $trail = '';
1533 
1534  # The characters '<' and '>' (which were escaped by
1535  # removeHTMLtags()) should not be included in
1536  # URLs, per RFC 2396.
1537  # Make &nbsp; terminate a URL as well (bug T84937)
1538  $m2 = [];
1539  if ( preg_match(
1540  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1541  $url,
1542  $m2,
1543  PREG_OFFSET_CAPTURE
1544  ) ) {
1545  $trail = substr( $url, $m2[0][1] ) . $trail;
1546  $url = substr( $url, 0, $m2[0][1] );
1547  }
1548 
1549  # Move trailing punctuation to $trail
1550  $sep = ',;\.:!?';
1551  # If there is no left bracket, then consider right brackets fair game too
1552  if ( strpos( $url, '(' ) === false ) {
1553  $sep .= ')';
1554  }
1555 
1556  $urlRev = strrev( $url );
1557  $numSepChars = strspn( $urlRev, $sep );
1558  # Don't break a trailing HTML entity by moving the ; into $trail
1559  # This is in hot code, so use substr_compare to avoid having to
1560  # create a new string object for the comparison
1561  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1562  # more optimization: instead of running preg_match with a $
1563  # anchor, which can be slow, do the match on the reversed
1564  # string starting at the desired offset.
1565  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1566  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1567  $numSepChars--;
1568  }
1569  }
1570  if ( $numSepChars ) {
1571  $trail = substr( $url, -$numSepChars ) . $trail;
1572  $url = substr( $url, 0, -$numSepChars );
1573  }
1574 
1575  # Verify that we still have a real URL after trail removal, and
1576  # not just lone protocol
1577  if ( strlen( $trail ) >= $numPostProto ) {
1578  return $url . $trail;
1579  }
1580 
1581  $url = Sanitizer::cleanUrl( $url );
1582 
1583  # Is this an external image?
1584  $text = $this->maybeMakeExternalImage( $url );
1585  if ( $text === false ) {
1586  # Not an image, make a link
1587  $text = Linker::makeExternalLink( $url,
1588  $this->getConverterLanguage()->markNoConversion( $url, true ),
1589  true, 'free',
1590  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1591  # Register it in the output object...
1592  # Replace unnecessary URL escape codes with their equivalent characters
1593  $pasteurized = self::normalizeLinkUrl( $url );
1594  $this->mOutput->addExternalLink( $pasteurized );
1595  }
1596  return $text . $trail;
1597  }
1598 
1608  public function doHeadings( $text ) {
1609  for ( $i = 6; $i >= 1; --$i ) {
1610  $h = str_repeat( '=', $i );
1611  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1612  }
1613  return $text;
1614  }
1615 
1624  public function doAllQuotes( $text ) {
1625  $outtext = '';
1626  $lines = StringUtils::explode( "\n", $text );
1627  foreach ( $lines as $line ) {
1628  $outtext .= $this->doQuotes( $line ) . "\n";
1629  }
1630  $outtext = substr( $outtext, 0, -1 );
1631  return $outtext;
1632  }
1633 
1641  public function doQuotes( $text ) {
1642  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1643  $countarr = count( $arr );
1644  if ( $countarr == 1 ) {
1645  return $text;
1646  }
1647 
1648  // First, do some preliminary work. This may shift some apostrophes from
1649  // being mark-up to being text. It also counts the number of occurrences
1650  // of bold and italics mark-ups.
1651  $numbold = 0;
1652  $numitalics = 0;
1653  for ( $i = 1; $i < $countarr; $i += 2 ) {
1654  $thislen = strlen( $arr[$i] );
1655  // If there are ever four apostrophes, assume the first is supposed to
1656  // be text, and the remaining three constitute mark-up for bold text.
1657  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1658  if ( $thislen == 4 ) {
1659  $arr[$i - 1] .= "'";
1660  $arr[$i] = "'''";
1661  $thislen = 3;
1662  } elseif ( $thislen > 5 ) {
1663  // If there are more than 5 apostrophes in a row, assume they're all
1664  // text except for the last 5.
1665  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1666  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1667  $arr[$i] = "'''''";
1668  $thislen = 5;
1669  }
1670  // Count the number of occurrences of bold and italics mark-ups.
1671  if ( $thislen == 2 ) {
1672  $numitalics++;
1673  } elseif ( $thislen == 3 ) {
1674  $numbold++;
1675  } elseif ( $thislen == 5 ) {
1676  $numitalics++;
1677  $numbold++;
1678  }
1679  }
1680 
1681  // If there is an odd number of both bold and italics, it is likely
1682  // that one of the bold ones was meant to be an apostrophe followed
1683  // by italics. Which one we cannot know for certain, but it is more
1684  // likely to be one that has a single-letter word before it.
1685  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1686  $firstsingleletterword = -1;
1687  $firstmultiletterword = -1;
1688  $firstspace = -1;
1689  for ( $i = 1; $i < $countarr; $i += 2 ) {
1690  if ( strlen( $arr[$i] ) == 3 ) {
1691  $x1 = substr( $arr[$i - 1], -1 );
1692  $x2 = substr( $arr[$i - 1], -2, 1 );
1693  if ( $x1 === ' ' ) {
1694  if ( $firstspace == -1 ) {
1695  $firstspace = $i;
1696  }
1697  } elseif ( $x2 === ' ' ) {
1698  $firstsingleletterword = $i;
1699  // if $firstsingleletterword is set, we don't
1700  // look at the other options, so we can bail early.
1701  break;
1702  } else {
1703  if ( $firstmultiletterword == -1 ) {
1704  $firstmultiletterword = $i;
1705  }
1706  }
1707  }
1708  }
1709 
1710  // If there is a single-letter word, use it!
1711  if ( $firstsingleletterword > -1 ) {
1712  $arr[$firstsingleletterword] = "''";
1713  $arr[$firstsingleletterword - 1] .= "'";
1714  } elseif ( $firstmultiletterword > -1 ) {
1715  // If not, but there's a multi-letter word, use that one.
1716  $arr[$firstmultiletterword] = "''";
1717  $arr[$firstmultiletterword - 1] .= "'";
1718  } elseif ( $firstspace > -1 ) {
1719  // ... otherwise use the first one that has neither.
1720  // (notice that it is possible for all three to be -1 if, for example,
1721  // there is only one pentuple-apostrophe in the line)
1722  $arr[$firstspace] = "''";
1723  $arr[$firstspace - 1] .= "'";
1724  }
1725  }
1726 
1727  // Now let's actually convert our apostrophic mush to HTML!
1728  $output = '';
1729  $buffer = '';
1730  $state = '';
1731  $i = 0;
1732  foreach ( $arr as $r ) {
1733  if ( ( $i % 2 ) == 0 ) {
1734  if ( $state === 'both' ) {
1735  $buffer .= $r;
1736  } else {
1737  $output .= $r;
1738  }
1739  } else {
1740  $thislen = strlen( $r );
1741  if ( $thislen == 2 ) {
1742  if ( $state === 'i' ) {
1743  $output .= '</i>';
1744  $state = '';
1745  } elseif ( $state === 'bi' ) {
1746  $output .= '</i>';
1747  $state = 'b';
1748  } elseif ( $state === 'ib' ) {
1749  $output .= '</b></i><b>';
1750  $state = 'b';
1751  } elseif ( $state === 'both' ) {
1752  $output .= '<b><i>' . $buffer . '</i>';
1753  $state = 'b';
1754  } else { // $state can be 'b' or ''
1755  $output .= '<i>';
1756  $state .= 'i';
1757  }
1758  } elseif ( $thislen == 3 ) {
1759  if ( $state === 'b' ) {
1760  $output .= '</b>';
1761  $state = '';
1762  } elseif ( $state === 'bi' ) {
1763  $output .= '</i></b><i>';
1764  $state = 'i';
1765  } elseif ( $state === 'ib' ) {
1766  $output .= '</b>';
1767  $state = 'i';
1768  } elseif ( $state === 'both' ) {
1769  $output .= '<i><b>' . $buffer . '</b>';
1770  $state = 'i';
1771  } else { // $state can be 'i' or ''
1772  $output .= '<b>';
1773  $state .= 'b';
1774  }
1775  } elseif ( $thislen == 5 ) {
1776  if ( $state === 'b' ) {
1777  $output .= '</b><i>';
1778  $state = 'i';
1779  } elseif ( $state === 'i' ) {
1780  $output .= '</i><b>';
1781  $state = 'b';
1782  } elseif ( $state === 'bi' ) {
1783  $output .= '</i></b>';
1784  $state = '';
1785  } elseif ( $state === 'ib' ) {
1786  $output .= '</b></i>';
1787  $state = '';
1788  } elseif ( $state === 'both' ) {
1789  $output .= '<i><b>' . $buffer . '</b></i>';
1790  $state = '';
1791  } else { // ($state == '')
1792  $buffer = '';
1793  $state = 'both';
1794  }
1795  }
1796  }
1797  $i++;
1798  }
1799  // Now close all remaining tags. Notice that the order is important.
1800  if ( $state === 'b' || $state === 'ib' ) {
1801  $output .= '</b>';
1802  }
1803  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1804  $output .= '</i>';
1805  }
1806  if ( $state === 'bi' ) {
1807  $output .= '</b>';
1808  }
1809  // There might be lonely ''''', so make sure we have a buffer
1810  if ( $state === 'both' && $buffer ) {
1811  $output .= '<b><i>' . $buffer . '</i></b>';
1812  }
1813  return $output;
1814  }
1815 
1829  public function replaceExternalLinks( $text ) {
1830 
1831  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1832  if ( $bits === false ) {
1833  throw new MWException( "PCRE needs to be compiled with "
1834  . "--enable-unicode-properties in order for MediaWiki to function" );
1835  }
1836  $s = array_shift( $bits );
1837 
1838  $i = 0;
1839  while ( $i < count( $bits ) ) {
1840  $url = $bits[$i++];
1841  $i++; // protocol
1842  $text = $bits[$i++];
1843  $trail = $bits[$i++];
1844 
1845  # The characters '<' and '>' (which were escaped by
1846  # removeHTMLtags()) should not be included in
1847  # URLs, per RFC 2396.
1848  $m2 = [];
1849  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1850  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1851  $url = substr( $url, 0, $m2[0][1] );
1852  }
1853 
1854  # If the link text is an image URL, replace it with an <img> tag
1855  # This happened by accident in the original parser, but some people used it extensively
1856  $img = $this->maybeMakeExternalImage( $text );
1857  if ( $img !== false ) {
1858  $text = $img;
1859  }
1860 
1861  $dtrail = '';
1862 
1863  # Set linktype for CSS - if URL==text, link is essentially free
1864  $linktype = ( $text === $url ) ? 'free' : 'text';
1865 
1866  # No link text, e.g. [http://domain.tld/some.link]
1867  if ( $text == '' ) {
1868  # Autonumber
1869  $langObj = $this->getTargetLanguage();
1870  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1871  $linktype = 'autonumber';
1872  } else {
1873  # Have link text, e.g. [http://domain.tld/some.link text]s
1874  # Check for trail
1875  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1876  }
1877 
1878  $text = $this->getConverterLanguage()->markNoConversion( $text );
1879 
1880  $url = Sanitizer::cleanUrl( $url );
1881 
1882  # Use the encoded URL
1883  # This means that users can paste URLs directly into the text
1884  # Funny characters like ö aren't valid in URLs anyway
1885  # This was changed in August 2004
1886  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1887  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1888 
1889  # Register link in the output object.
1890  # Replace unnecessary URL escape codes with the referenced character
1891  # This prevents spammers from hiding links from the filters
1892  $pasteurized = self::normalizeLinkUrl( $url );
1893  $this->mOutput->addExternalLink( $pasteurized );
1894  }
1895 
1896  return $s;
1897  }
1898 
1908  public static function getExternalLinkRel( $url = false, $title = null ) {
1909  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1910  $ns = $title ? $title->getNamespace() : false;
1911  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1912  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1913  ) {
1914  return 'nofollow';
1915  }
1916  return null;
1917  }
1918 
1929  public function getExternalLinkAttribs( $url ) {
1930  $attribs = [];
1931  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1932 
1933  $target = $this->mOptions->getExternalLinkTarget();
1934  if ( $target ) {
1935  $attribs['target'] = $target;
1936  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1937  // T133507. New windows can navigate parent cross-origin.
1938  // Including noreferrer due to lacking browser
1939  // support of noopener. Eventually noreferrer should be removed.
1940  if ( $rel !== '' ) {
1941  $rel .= ' ';
1942  }
1943  $rel .= 'noreferrer noopener';
1944  }
1945  }
1946  $attribs['rel'] = $rel;
1947  return $attribs;
1948  }
1949 
1959  public static function normalizeLinkUrl( $url ) {
1960  # First, make sure unsafe characters are encoded
1961  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1962  function ( $m ) {
1963  return rawurlencode( $m[0] );
1964  },
1965  $url
1966  );
1967 
1968  $ret = '';
1969  $end = strlen( $url );
1970 
1971  # Fragment part - 'fragment'
1972  $start = strpos( $url, '#' );
1973  if ( $start !== false && $start < $end ) {
1974  $ret = self::normalizeUrlComponent(
1975  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1976  $end = $start;
1977  }
1978 
1979  # Query part - 'query' minus &=+;
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  # Scheme and path part - 'pchar'
1988  # (we assume no userinfo or encoded colons in the host)
1989  $ret = self::normalizeUrlComponent(
1990  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
1991 
1992  return $ret;
1993  }
1994 
1995  private static function normalizeUrlComponent( $component, $unsafe ) {
1996  $callback = function ( $matches ) use ( $unsafe ) {
1997  $char = urldecode( $matches[0] );
1998  $ord = ord( $char );
1999  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2000  # Unescape it
2001  return $char;
2002  } else {
2003  # Leave it escaped, but use uppercase for a-f
2004  return strtoupper( $matches[0] );
2005  }
2006  };
2007  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2008  }
2009 
2018  private function maybeMakeExternalImage( $url ) {
2019  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2020  $imagesexception = !empty( $imagesfrom );
2021  $text = false;
2022  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2023  if ( $imagesexception && is_array( $imagesfrom ) ) {
2024  $imagematch = false;
2025  foreach ( $imagesfrom as $match ) {
2026  if ( strpos( $url, $match ) === 0 ) {
2027  $imagematch = true;
2028  break;
2029  }
2030  }
2031  } elseif ( $imagesexception ) {
2032  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2033  } else {
2034  $imagematch = false;
2035  }
2036 
2037  if ( $this->mOptions->getAllowExternalImages()
2038  || ( $imagesexception && $imagematch )
2039  ) {
2040  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2041  # Image found
2042  $text = Linker::makeExternalImage( $url );
2043  }
2044  }
2045  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2046  && preg_match( self::EXT_IMAGE_REGEX, $url )
2047  ) {
2048  $whitelist = explode(
2049  "\n",
2050  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2051  );
2052 
2053  foreach ( $whitelist as $entry ) {
2054  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2055  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2056  continue;
2057  }
2058  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2059  # Image matches a whitelist entry
2060  $text = Linker::makeExternalImage( $url );
2061  break;
2062  }
2063  }
2064  }
2065  return $text;
2066  }
2067 
2077  public function replaceInternalLinks( $s ) {
2078  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2079  return $s;
2080  }
2081 
2090  public function replaceInternalLinks2( &$s ) {
2092 
2093  static $tc = false, $e1, $e1_img;
2094  # the % is needed to support urlencoded titles as well
2095  if ( !$tc ) {
2096  $tc = Title::legalChars() . '#%';
2097  # Match a link having the form [[namespace:link|alternate]]trail
2098  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2099  # Match cases where there is no "]]", which might still be images
2100  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2101  }
2102 
2103  $holders = new LinkHolderArray( $this );
2104 
2105  # split the entire text string on occurrences of [[
2106  $a = StringUtils::explode( '[[', ' ' . $s );
2107  # get the first element (all text up to first [[), and remove the space we added
2108  $s = $a->current();
2109  $a->next();
2110  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2111  $s = substr( $s, 1 );
2112 
2113  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2114  $e2 = null;
2115  if ( $useLinkPrefixExtension ) {
2116  # Match the end of a line for a word that's not followed by whitespace,
2117  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2119  $charset = $wgContLang->linkPrefixCharset();
2120  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2121  }
2122 
2123  if ( is_null( $this->mTitle ) ) {
2124  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2125  }
2126  $nottalk = !$this->mTitle->isTalkPage();
2127 
2128  if ( $useLinkPrefixExtension ) {
2129  $m = [];
2130  if ( preg_match( $e2, $s, $m ) ) {
2131  $first_prefix = $m[2];
2132  } else {
2133  $first_prefix = false;
2134  }
2135  } else {
2136  $prefix = '';
2137  }
2138 
2139  $useSubpages = $this->areSubpagesAllowed();
2140 
2141  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2142  # Loop for each link
2143  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2144  // @codingStandardsIgnoreEnd
2145 
2146  # Check for excessive memory usage
2147  if ( $holders->isBig() ) {
2148  # Too big
2149  # Do the existence check, replace the link holders and clear the array
2150  $holders->replace( $s );
2151  $holders->clear();
2152  }
2153 
2154  if ( $useLinkPrefixExtension ) {
2155  if ( preg_match( $e2, $s, $m ) ) {
2156  $prefix = $m[2];
2157  $s = $m[1];
2158  } else {
2159  $prefix = '';
2160  }
2161  # first link
2162  if ( $first_prefix ) {
2163  $prefix = $first_prefix;
2164  $first_prefix = false;
2165  }
2166  }
2167 
2168  $might_be_img = false;
2169 
2170  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2171  $text = $m[2];
2172  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2173  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2174  # the real problem is with the $e1 regex
2175  # See T1500.
2176  # Still some problems for cases where the ] is meant to be outside punctuation,
2177  # and no image is in sight. See T4095.
2178  if ( $text !== ''
2179  && substr( $m[3], 0, 1 ) === ']'
2180  && strpos( $text, '[' ) !== false
2181  ) {
2182  $text .= ']'; # so that replaceExternalLinks($text) works later
2183  $m[3] = substr( $m[3], 1 );
2184  }
2185  # fix up urlencoded title texts
2186  if ( strpos( $m[1], '%' ) !== false ) {
2187  # Should anchors '#' also be rejected?
2188  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2189  }
2190  $trail = $m[3];
2191  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2192  # Invalid, but might be an image with a link in its caption
2193  $might_be_img = true;
2194  $text = $m[2];
2195  if ( strpos( $m[1], '%' ) !== false ) {
2196  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2197  }
2198  $trail = "";
2199  } else { # Invalid form; output directly
2200  $s .= $prefix . '[[' . $line;
2201  continue;
2202  }
2203 
2204  $origLink = ltrim( $m[1], ' ' );
2205 
2206  # Don't allow internal links to pages containing
2207  # PROTO: where PROTO is a valid URL protocol; these
2208  # should be external links.
2209  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2210  $s .= $prefix . '[[' . $line;
2211  continue;
2212  }
2213 
2214  # Make subpage if necessary
2215  if ( $useSubpages ) {
2216  $link = $this->maybeDoSubpageLink( $origLink, $text );
2217  } else {
2218  $link = $origLink;
2219  }
2220 
2221  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2222  if ( !$noforce ) {
2223  # Strip off leading ':'
2224  $link = substr( $link, 1 );
2225  }
2226 
2227  $unstrip = $this->mStripState->unstripNoWiki( $link );
2228  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2229  if ( $nt === null ) {
2230  $s .= $prefix . '[[' . $line;
2231  continue;
2232  }
2233 
2234  $ns = $nt->getNamespace();
2235  $iw = $nt->getInterwiki();
2236 
2237  if ( $might_be_img ) { # if this is actually an invalid link
2238  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2239  $found = false;
2240  while ( true ) {
2241  # look at the next 'line' to see if we can close it there
2242  $a->next();
2243  $next_line = $a->current();
2244  if ( $next_line === false || $next_line === null ) {
2245  break;
2246  }
2247  $m = explode( ']]', $next_line, 3 );
2248  if ( count( $m ) == 3 ) {
2249  # the first ]] closes the inner link, the second the image
2250  $found = true;
2251  $text .= "[[{$m[0]}]]{$m[1]}";
2252  $trail = $m[2];
2253  break;
2254  } elseif ( count( $m ) == 2 ) {
2255  # if there's exactly one ]] that's fine, we'll keep looking
2256  $text .= "[[{$m[0]}]]{$m[1]}";
2257  } else {
2258  # if $next_line is invalid too, we need look no further
2259  $text .= '[[' . $next_line;
2260  break;
2261  }
2262  }
2263  if ( !$found ) {
2264  # we couldn't find the end of this imageLink, so output it raw
2265  # but don't ignore what might be perfectly normal links in the text we've examined
2266  $holders->merge( $this->replaceInternalLinks2( $text ) );
2267  $s .= "{$prefix}[[$link|$text";
2268  # note: no $trail, because without an end, there *is* no trail
2269  continue;
2270  }
2271  } else { # it's not an image, so output it raw
2272  $s .= "{$prefix}[[$link|$text";
2273  # note: no $trail, because without an end, there *is* no trail
2274  continue;
2275  }
2276  }
2277 
2278  $wasblank = ( $text == '' );
2279  if ( $wasblank ) {
2280  $text = $link;
2281  } else {
2282  # T6598 madness. Handle the quotes only if they come from the alternate part
2283  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2284  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2285  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2286  $text = $this->doQuotes( $text );
2287  }
2288 
2289  # Link not escaped by : , create the various objects
2290  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2291  # Interwikis
2292  if (
2293  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2294  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2295  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2296  )
2297  ) {
2298  # T26502: filter duplicates
2299  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2300  $this->mLangLinkLanguages[$iw] = true;
2301  $this->mOutput->addLanguageLink( $nt->getFullText() );
2302  }
2303 
2304  $s = rtrim( $s . $prefix );
2305  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2306  continue;
2307  }
2308 
2309  if ( $ns == NS_FILE ) {
2310  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2311  if ( $wasblank ) {
2312  # if no parameters were passed, $text
2313  # becomes something like "File:Foo.png",
2314  # which we don't want to pass on to the
2315  # image generator
2316  $text = '';
2317  } else {
2318  # recursively parse links inside the image caption
2319  # actually, this will parse them in any other parameters, too,
2320  # but it might be hard to fix that, and it doesn't matter ATM
2321  $text = $this->replaceExternalLinks( $text );
2322  $holders->merge( $this->replaceInternalLinks2( $text ) );
2323  }
2324  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2325  $s .= $prefix . $this->armorLinks(
2326  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2327  continue;
2328  }
2329  } elseif ( $ns == NS_CATEGORY ) {
2330  $s = rtrim( $s . "\n" ); # T2087
2331 
2332  if ( $wasblank ) {
2333  $sortkey = $this->getDefaultSort();
2334  } else {
2335  $sortkey = $text;
2336  }
2337  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2338  $sortkey = str_replace( "\n", '', $sortkey );
2339  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2340  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2341 
2345  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2346 
2347  continue;
2348  }
2349  }
2350 
2351  # Self-link checking. For some languages, variants of the title are checked in
2352  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2353  # for linking to a different variant.
2354  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2355  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2356  continue;
2357  }
2358 
2359  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2360  # @todo FIXME: Should do batch file existence checks, see comment below
2361  if ( $ns == NS_MEDIA ) {
2362  # Give extensions a chance to select the file revision for us
2363  $options = [];
2364  $descQuery = false;
2365  Hooks::run( 'BeforeParserFetchFileAndTitle',
2366  [ $this, $nt, &$options, &$descQuery ] );
2367  # Fetch and register the file (file title may be different via hooks)
2368  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2369  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2370  $s .= $prefix . $this->armorLinks(
2371  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2372  continue;
2373  }
2374 
2375  # Some titles, such as valid special pages or files in foreign repos, should
2376  # be shown as bluelinks even though they're not included in the page table
2377  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2378  # batch file existence checks for NS_FILE and NS_MEDIA
2379  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2380  $this->mOutput->addLink( $nt );
2381  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2382  } else {
2383  # Links will be added to the output link list after checking
2384  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2385  }
2386  }
2387  return $holders;
2388  }
2389 
2403  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2404  list( $inside, $trail ) = Linker::splitTrail( $trail );
2405 
2406  if ( $text == '' ) {
2407  $text = htmlspecialchars( $nt->getPrefixedText() );
2408  }
2409 
2410  $link = $this->getLinkRenderer()->makeKnownLink(
2411  $nt, new HtmlArmor( "$prefix$text$inside" )
2412  );
2413 
2414  return $this->armorLinks( $link ) . $trail;
2415  }
2416 
2427  public function armorLinks( $text ) {
2428  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2429  self::MARKER_PREFIX . "NOPARSE$1", $text );
2430  }
2431 
2436  public function areSubpagesAllowed() {
2437  # Some namespaces don't allow subpages
2438  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2439  }
2440 
2449  public function maybeDoSubpageLink( $target, &$text ) {
2450  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2451  }
2452 
2461  public function doBlockLevels( $text, $linestart ) {
2462  return BlockLevelPass::doBlockLevels( $text, $linestart );
2463  }
2464 
2476  public function getVariableValue( $index, $frame = false ) {
2479 
2480  if ( is_null( $this->mTitle ) ) {
2481  // If no title set, bad things are going to happen
2482  // later. Title should always be set since this
2483  // should only be called in the middle of a parse
2484  // operation (but the unit-tests do funky stuff)
2485  throw new MWException( __METHOD__ . ' Should only be '
2486  . ' called while parsing (no title set)' );
2487  }
2488 
2493  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$this, &$this->mVarCache ] ) ) {
2494  if ( isset( $this->mVarCache[$index] ) ) {
2495  return $this->mVarCache[$index];
2496  }
2497  }
2498 
2499  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2500  Hooks::run( 'ParserGetVariableValueTs', [ &$this, &$ts ] );
2501 
2502  $pageLang = $this->getFunctionLang();
2503 
2504  switch ( $index ) {
2505  case '!':
2506  $value = '|';
2507  break;
2508  case 'currentmonth':
2509  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2510  break;
2511  case 'currentmonth1':
2512  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2513  break;
2514  case 'currentmonthname':
2515  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2516  break;
2517  case 'currentmonthnamegen':
2518  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2519  break;
2520  case 'currentmonthabbrev':
2521  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2522  break;
2523  case 'currentday':
2524  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2525  break;
2526  case 'currentday2':
2527  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2528  break;
2529  case 'localmonth':
2530  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2531  break;
2532  case 'localmonth1':
2533  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2534  break;
2535  case 'localmonthname':
2536  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2537  break;
2538  case 'localmonthnamegen':
2539  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2540  break;
2541  case 'localmonthabbrev':
2542  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2543  break;
2544  case 'localday':
2545  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2546  break;
2547  case 'localday2':
2548  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2549  break;
2550  case 'pagename':
2551  $value = wfEscapeWikiText( $this->mTitle->getText() );
2552  break;
2553  case 'pagenamee':
2554  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2555  break;
2556  case 'fullpagename':
2557  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2558  break;
2559  case 'fullpagenamee':
2560  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2561  break;
2562  case 'subpagename':
2563  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2564  break;
2565  case 'subpagenamee':
2566  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2567  break;
2568  case 'rootpagename':
2569  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2570  break;
2571  case 'rootpagenamee':
2572  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2573  ' ',
2574  '_',
2575  $this->mTitle->getRootText()
2576  ) ) );
2577  break;
2578  case 'basepagename':
2579  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2580  break;
2581  case 'basepagenamee':
2582  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2583  ' ',
2584  '_',
2585  $this->mTitle->getBaseText()
2586  ) ) );
2587  break;
2588  case 'talkpagename':
2589  if ( $this->mTitle->canTalk() ) {
2590  $talkPage = $this->mTitle->getTalkPage();
2591  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2592  } else {
2593  $value = '';
2594  }
2595  break;
2596  case 'talkpagenamee':
2597  if ( $this->mTitle->canTalk() ) {
2598  $talkPage = $this->mTitle->getTalkPage();
2599  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2600  } else {
2601  $value = '';
2602  }
2603  break;
2604  case 'subjectpagename':
2605  $subjPage = $this->mTitle->getSubjectPage();
2606  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2607  break;
2608  case 'subjectpagenamee':
2609  $subjPage = $this->mTitle->getSubjectPage();
2610  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2611  break;
2612  case 'pageid': // requested in T25427
2613  $pageid = $this->getTitle()->getArticleID();
2614  if ( $pageid == 0 ) {
2615  # 0 means the page doesn't exist in the database,
2616  # which means the user is previewing a new page.
2617  # The vary-revision flag must be set, because the magic word
2618  # will have a different value once the page is saved.
2619  $this->mOutput->setFlag( 'vary-revision' );
2620  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2621  }
2622  $value = $pageid ? $pageid : null;
2623  break;
2624  case 'revisionid':
2625  # Let the edit saving system know we should parse the page
2626  # *after* a revision ID has been assigned.
2627  $this->mOutput->setFlag( 'vary-revision-id' );
2628  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2629  $value = $this->mRevisionId;
2630  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2631  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2632  $this->mOutput->setSpeculativeRevIdUsed( $value );
2633  }
2634  break;
2635  case 'revisionday':
2636  # Let the edit saving system know we should parse the page
2637  # *after* a revision ID has been assigned. This is for null edits.
2638  $this->mOutput->setFlag( 'vary-revision' );
2639  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2640  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2641  break;
2642  case 'revisionday2':
2643  # Let the edit saving system know we should parse the page
2644  # *after* a revision ID has been assigned. This is for null edits.
2645  $this->mOutput->setFlag( 'vary-revision' );
2646  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2647  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2648  break;
2649  case 'revisionmonth':
2650  # Let the edit saving system know we should parse the page
2651  # *after* a revision ID has been assigned. This is for null edits.
2652  $this->mOutput->setFlag( 'vary-revision' );
2653  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2654  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2655  break;
2656  case 'revisionmonth1':
2657  # Let the edit saving system know we should parse the page
2658  # *after* a revision ID has been assigned. This is for null edits.
2659  $this->mOutput->setFlag( 'vary-revision' );
2660  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2661  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2662  break;
2663  case 'revisionyear':
2664  # Let the edit saving system know we should parse the page
2665  # *after* a revision ID has been assigned. This is for null edits.
2666  $this->mOutput->setFlag( 'vary-revision' );
2667  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2668  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2669  break;
2670  case 'revisiontimestamp':
2671  # Let the edit saving system know we should parse the page
2672  # *after* a revision ID has been assigned. This is for null edits.
2673  $this->mOutput->setFlag( 'vary-revision' );
2674  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2675  $value = $this->getRevisionTimestamp();
2676  break;
2677  case 'revisionuser':
2678  # Let the edit saving system know we should parse the page
2679  # *after* a revision ID has been assigned for null edits.
2680  $this->mOutput->setFlag( 'vary-user' );
2681  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2682  $value = $this->getRevisionUser();
2683  break;
2684  case 'revisionsize':
2685  $value = $this->getRevisionSize();
2686  break;
2687  case 'namespace':
2688  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2689  break;
2690  case 'namespacee':
2691  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2692  break;
2693  case 'namespacenumber':
2694  $value = $this->mTitle->getNamespace();
2695  break;
2696  case 'talkspace':
2697  $value = $this->mTitle->canTalk()
2698  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2699  : '';
2700  break;
2701  case 'talkspacee':
2702  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2703  break;
2704  case 'subjectspace':
2705  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2706  break;
2707  case 'subjectspacee':
2708  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2709  break;
2710  case 'currentdayname':
2711  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2712  break;
2713  case 'currentyear':
2714  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2715  break;
2716  case 'currenttime':
2717  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2718  break;
2719  case 'currenthour':
2720  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2721  break;
2722  case 'currentweek':
2723  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2724  # int to remove the padding
2725  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2726  break;
2727  case 'currentdow':
2728  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2729  break;
2730  case 'localdayname':
2731  $value = $pageLang->getWeekdayName(
2732  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2733  );
2734  break;
2735  case 'localyear':
2736  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2737  break;
2738  case 'localtime':
2739  $value = $pageLang->time(
2740  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2741  false,
2742  false
2743  );
2744  break;
2745  case 'localhour':
2746  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2747  break;
2748  case 'localweek':
2749  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2750  # int to remove the padding
2751  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2752  break;
2753  case 'localdow':
2754  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2755  break;
2756  case 'numberofarticles':
2757  $value = $pageLang->formatNum( SiteStats::articles() );
2758  break;
2759  case 'numberoffiles':
2760  $value = $pageLang->formatNum( SiteStats::images() );
2761  break;
2762  case 'numberofusers':
2763  $value = $pageLang->formatNum( SiteStats::users() );
2764  break;
2765  case 'numberofactiveusers':
2766  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2767  break;
2768  case 'numberofpages':
2769  $value = $pageLang->formatNum( SiteStats::pages() );
2770  break;
2771  case 'numberofadmins':
2772  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2773  break;
2774  case 'numberofedits':
2775  $value = $pageLang->formatNum( SiteStats::edits() );
2776  break;
2777  case 'currenttimestamp':
2778  $value = wfTimestamp( TS_MW, $ts );
2779  break;
2780  case 'localtimestamp':
2781  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2782  break;
2783  case 'currentversion':
2785  break;
2786  case 'articlepath':
2787  return $wgArticlePath;
2788  case 'sitename':
2789  return $wgSitename;
2790  case 'server':
2791  return $wgServer;
2792  case 'servername':
2793  return $wgServerName;
2794  case 'scriptpath':
2795  return $wgScriptPath;
2796  case 'stylepath':
2797  return $wgStylePath;
2798  case 'directionmark':
2799  return $pageLang->getDirMark();
2800  case 'contentlanguage':
2802  return $wgLanguageCode;
2803  case 'pagelanguage':
2804  $value = $pageLang->getCode();
2805  break;
2806  case 'cascadingsources':
2808  break;
2809  default:
2810  $ret = null;
2811  Hooks::run(
2812  'ParserGetVariableValueSwitch',
2813  [ &$this, &$this->mVarCache, &$index, &$ret, &$frame ]
2814  );
2815 
2816  return $ret;
2817  }
2818 
2819  if ( $index ) {
2820  $this->mVarCache[$index] = $value;
2821  }
2822 
2823  return $value;
2824  }
2825 
2831  public function initialiseVariables() {
2832  $variableIDs = MagicWord::getVariableIDs();
2833  $substIDs = MagicWord::getSubstIDs();
2834 
2835  $this->mVariables = new MagicWordArray( $variableIDs );
2836  $this->mSubstWords = new MagicWordArray( $substIDs );
2837  }
2838 
2861  public function preprocessToDom( $text, $flags = 0 ) {
2862  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2863  return $dom;
2864  }
2865 
2873  public static function splitWhitespace( $s ) {
2874  $ltrimmed = ltrim( $s );
2875  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2876  $trimmed = rtrim( $ltrimmed );
2877  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2878  if ( $diff > 0 ) {
2879  $w2 = substr( $ltrimmed, -$diff );
2880  } else {
2881  $w2 = '';
2882  }
2883  return [ $w1, $trimmed, $w2 ];
2884  }
2885 
2906  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2907  # Is there any text? Also, Prevent too big inclusions!
2908  $textSize = strlen( $text );
2909  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2910  return $text;
2911  }
2912 
2913  if ( $frame === false ) {
2914  $frame = $this->getPreprocessor()->newFrame();
2915  } elseif ( !( $frame instanceof PPFrame ) ) {
2916  wfDebug( __METHOD__ . " called using plain parameters instead of "
2917  . "a PPFrame instance. Creating custom frame.\n" );
2918  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2919  }
2920 
2921  $dom = $this->preprocessToDom( $text );
2922  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2923  $text = $frame->expand( $dom, $flags );
2924 
2925  return $text;
2926  }
2927 
2935  public static function createAssocArgs( $args ) {
2936  $assocArgs = [];
2937  $index = 1;
2938  foreach ( $args as $arg ) {
2939  $eqpos = strpos( $arg, '=' );
2940  if ( $eqpos === false ) {
2941  $assocArgs[$index++] = $arg;
2942  } else {
2943  $name = trim( substr( $arg, 0, $eqpos ) );
2944  $value = trim( substr( $arg, $eqpos + 1 ) );
2945  if ( $value === false ) {
2946  $value = '';
2947  }
2948  if ( $name !== false ) {
2949  $assocArgs[$name] = $value;
2950  }
2951  }
2952  }
2953 
2954  return $assocArgs;
2955  }
2956 
2983  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2984  # does no harm if $current and $max are present but are unnecessary for the message
2985  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2986  # only during preview, and that would split the parser cache unnecessarily.
2987  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
2988  ->text();
2989  $this->mOutput->addWarning( $warning );
2990  $this->addTrackingCategory( "$limitationType-category" );
2991  }
2992 
3005  public function braceSubstitution( $piece, $frame ) {
3006 
3007  // Flags
3008 
3009  // $text has been filled
3010  $found = false;
3011  // wiki markup in $text should be escaped
3012  $nowiki = false;
3013  // $text is HTML, armour it against wikitext transformation
3014  $isHTML = false;
3015  // Force interwiki transclusion to be done in raw mode not rendered
3016  $forceRawInterwiki = false;
3017  // $text is a DOM node needing expansion in a child frame
3018  $isChildObj = false;
3019  // $text is a DOM node needing expansion in the current frame
3020  $isLocalObj = false;
3021 
3022  # Title object, where $text came from
3023  $title = false;
3024 
3025  # $part1 is the bit before the first |, and must contain only title characters.
3026  # Various prefixes will be stripped from it later.
3027  $titleWithSpaces = $frame->expand( $piece['title'] );
3028  $part1 = trim( $titleWithSpaces );
3029  $titleText = false;
3030 
3031  # Original title text preserved for various purposes
3032  $originalTitle = $part1;
3033 
3034  # $args is a list of argument nodes, starting from index 0, not including $part1
3035  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3036  # below won't work b/c this $args isn't an object
3037  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3038 
3039  $profileSection = null; // profile templates
3040 
3041  # SUBST
3042  if ( !$found ) {
3043  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3044 
3045  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3046  # Decide whether to expand template or keep wikitext as-is.
3047  if ( $this->ot['wiki'] ) {
3048  if ( $substMatch === false ) {
3049  $literal = true; # literal when in PST with no prefix
3050  } else {
3051  $literal = false; # expand when in PST with subst: or safesubst:
3052  }
3053  } else {
3054  if ( $substMatch == 'subst' ) {
3055  $literal = true; # literal when not in PST with plain subst:
3056  } else {
3057  $literal = false; # expand when not in PST with safesubst: or no prefix
3058  }
3059  }
3060  if ( $literal ) {
3061  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3062  $isLocalObj = true;
3063  $found = true;
3064  }
3065  }
3066 
3067  # Variables
3068  if ( !$found && $args->getLength() == 0 ) {
3069  $id = $this->mVariables->matchStartToEnd( $part1 );
3070  if ( $id !== false ) {
3071  $text = $this->getVariableValue( $id, $frame );
3072  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3073  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3074  }
3075  $found = true;
3076  }
3077  }
3078 
3079  # MSG, MSGNW and RAW
3080  if ( !$found ) {
3081  # Check for MSGNW:
3082  $mwMsgnw = MagicWord::get( 'msgnw' );
3083  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3084  $nowiki = true;
3085  } else {
3086  # Remove obsolete MSG:
3087  $mwMsg = MagicWord::get( 'msg' );
3088  $mwMsg->matchStartAndRemove( $part1 );
3089  }
3090 
3091  # Check for RAW:
3092  $mwRaw = MagicWord::get( 'raw' );
3093  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3094  $forceRawInterwiki = true;
3095  }
3096  }
3097 
3098  # Parser functions
3099  if ( !$found ) {
3100  $colonPos = strpos( $part1, ':' );
3101  if ( $colonPos !== false ) {
3102  $func = substr( $part1, 0, $colonPos );
3103  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3104  $argsLength = $args->getLength();
3105  for ( $i = 0; $i < $argsLength; $i++ ) {
3106  $funcArgs[] = $args->item( $i );
3107  }
3108  try {
3109  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3110  } catch ( Exception $ex ) {
3111  throw $ex;
3112  }
3113 
3114  # The interface for parser functions allows for extracting
3115  # flags into the local scope. Extract any forwarded flags
3116  # here.
3117  extract( $result );
3118  }
3119  }
3120 
3121  # Finish mangling title and then check for loops.
3122  # Set $title to a Title object and $titleText to the PDBK
3123  if ( !$found ) {
3124  $ns = NS_TEMPLATE;
3125  # Split the title into page and subpage
3126  $subpage = '';
3127  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3128  if ( $part1 !== $relative ) {
3129  $part1 = $relative;
3130  $ns = $this->mTitle->getNamespace();
3131  }
3132  $title = Title::newFromText( $part1, $ns );
3133  if ( $title ) {
3134  $titleText = $title->getPrefixedText();
3135  # Check for language variants if the template is not found
3136  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3137  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3138  }
3139  # Do recursion depth check
3140  $limit = $this->mOptions->getMaxTemplateDepth();
3141  if ( $frame->depth >= $limit ) {
3142  $found = true;
3143  $text = '<span class="error">'
3144  . wfMessage( 'parser-template-recursion-depth-warning' )
3145  ->numParams( $limit )->inContentLanguage()->text()
3146  . '</span>';
3147  }
3148  }
3149  }
3150 
3151  # Load from database
3152  if ( !$found && $title ) {
3153  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3154  if ( !$title->isExternal() ) {
3155  if ( $title->isSpecialPage()
3156  && $this->mOptions->getAllowSpecialInclusion()
3157  && $this->ot['html']
3158  ) {
3159  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3160  // Pass the template arguments as URL parameters.
3161  // "uselang" will have no effect since the Language object
3162  // is forced to the one defined in ParserOptions.
3163  $pageArgs = [];
3164  $argsLength = $args->getLength();
3165  for ( $i = 0; $i < $argsLength; $i++ ) {
3166  $bits = $args->item( $i )->splitArg();
3167  if ( strval( $bits['index'] ) === '' ) {
3168  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3169  $value = trim( $frame->expand( $bits['value'] ) );
3170  $pageArgs[$name] = $value;
3171  }
3172  }
3173 
3174  // Create a new context to execute the special page
3175  $context = new RequestContext;
3176  $context->setTitle( $title );
3177  $context->setRequest( new FauxRequest( $pageArgs ) );
3178  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3179  $context->setUser( $this->getUser() );
3180  } else {
3181  // If this page is cached, then we better not be per user.
3182  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3183  }
3184  $context->setLanguage( $this->mOptions->getUserLangObj() );
3186  $title, $context, $this->getLinkRenderer() );
3187  if ( $ret ) {
3188  $text = $context->getOutput()->getHTML();
3189  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3190  $found = true;
3191  $isHTML = true;
3192  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3193  $this->mOutput->updateRuntimeAdaptiveExpiry(
3194  $specialPage->maxIncludeCacheTime()
3195  );
3196  }
3197  }
3198  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3199  $found = false; # access denied
3200  wfDebug( __METHOD__ . ": template inclusion denied for " .
3201  $title->getPrefixedDBkey() . "\n" );
3202  } else {
3203  list( $text, $title ) = $this->getTemplateDom( $title );
3204  if ( $text !== false ) {
3205  $found = true;
3206  $isChildObj = true;
3207  }
3208  }
3209 
3210  # If the title is valid but undisplayable, make a link to it
3211  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3212  $text = "[[:$titleText]]";
3213  $found = true;
3214  }
3215  } elseif ( $title->isTrans() ) {
3216  # Interwiki transclusion
3217  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3218  $text = $this->interwikiTransclude( $title, 'render' );
3219  $isHTML = true;
3220  } else {
3221  $text = $this->interwikiTransclude( $title, 'raw' );
3222  # Preprocess it like a template
3223  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3224  $isChildObj = true;
3225  }
3226  $found = true;
3227  }
3228 
3229  # Do infinite loop check
3230  # This has to be done after redirect resolution to avoid infinite loops via redirects
3231  if ( !$frame->loopCheck( $title ) ) {
3232  $found = true;
3233  $text = '<span class="error">'
3234  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3235  . '</span>';
3236  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3237  }
3238  }
3239 
3240  # If we haven't found text to substitute by now, we're done
3241  # Recover the source wikitext and return it
3242  if ( !$found ) {
3243  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3244  if ( $profileSection ) {
3245  $this->mProfiler->scopedProfileOut( $profileSection );
3246  }
3247  return [ 'object' => $text ];
3248  }
3249 
3250  # Expand DOM-style return values in a child frame
3251  if ( $isChildObj ) {
3252  # Clean up argument array
3253  $newFrame = $frame->newChild( $args, $title );
3254 
3255  if ( $nowiki ) {
3256  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3257  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3258  # Expansion is eligible for the empty-frame cache
3259  $text = $newFrame->cachedExpand( $titleText, $text );
3260  } else {
3261  # Uncached expansion
3262  $text = $newFrame->expand( $text );
3263  }
3264  }
3265  if ( $isLocalObj && $nowiki ) {
3266  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3267  $isLocalObj = false;
3268  }
3269 
3270  if ( $profileSection ) {
3271  $this->mProfiler->scopedProfileOut( $profileSection );
3272  }
3273 
3274  # Replace raw HTML by a placeholder
3275  if ( $isHTML ) {
3276  $text = $this->insertStripItem( $text );
3277  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3278  # Escape nowiki-style return values
3279  $text = wfEscapeWikiText( $text );
3280  } elseif ( is_string( $text )
3281  && !$piece['lineStart']
3282  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3283  ) {
3284  # T2529: if the template begins with a table or block-level
3285  # element, it should be treated as beginning a new line.
3286  # This behavior is somewhat controversial.
3287  $text = "\n" . $text;
3288  }
3289 
3290  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3291  # Error, oversize inclusion
3292  if ( $titleText !== false ) {
3293  # Make a working, properly escaped link if possible (T25588)
3294  $text = "[[:$titleText]]";
3295  } else {
3296  # This will probably not be a working link, but at least it may
3297  # provide some hint of where the problem is
3298  preg_replace( '/^:/', '', $originalTitle );
3299  $text = "[[:$originalTitle]]";
3300  }
3301  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3302  . 'post-expand include size too large -->' );
3303  $this->limitationWarn( 'post-expand-template-inclusion' );
3304  }
3305 
3306  if ( $isLocalObj ) {
3307  $ret = [ 'object' => $text ];
3308  } else {
3309  $ret = [ 'text' => $text ];
3310  }
3311 
3312  return $ret;
3313  }
3314 
3334  public function callParserFunction( $frame, $function, array $args = [] ) {
3336 
3337  # Case sensitive functions
3338  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3339  $function = $this->mFunctionSynonyms[1][$function];
3340  } else {
3341  # Case insensitive functions
3342  $function = $wgContLang->lc( $function );
3343  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3344  $function = $this->mFunctionSynonyms[0][$function];
3345  } else {
3346  return [ 'found' => false ];
3347  }
3348  }
3349 
3350  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3351 
3352  # Workaround for PHP bug 35229 and similar
3353  if ( !is_callable( $callback ) ) {
3354  throw new MWException( "Tag hook for $function is not callable\n" );
3355  }
3356 
3357  $allArgs = [ &$this ];
3358  if ( $flags & self::SFH_OBJECT_ARGS ) {
3359  # Convert arguments to PPNodes and collect for appending to $allArgs
3360  $funcArgs = [];
3361  foreach ( $args as $k => $v ) {
3362  if ( $v instanceof PPNode || $k === 0 ) {
3363  $funcArgs[] = $v;
3364  } else {
3365  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3366  }
3367  }
3368 
3369  # Add a frame parameter, and pass the arguments as an array
3370  $allArgs[] = $frame;
3371  $allArgs[] = $funcArgs;
3372  } else {
3373  # Convert arguments to plain text and append to $allArgs
3374  foreach ( $args as $k => $v ) {
3375  if ( $v instanceof PPNode ) {
3376  $allArgs[] = trim( $frame->expand( $v ) );
3377  } elseif ( is_int( $k ) && $k >= 0 ) {
3378  $allArgs[] = trim( $v );
3379  } else {
3380  $allArgs[] = trim( "$k=$v" );
3381  }
3382  }
3383  }
3384 
3385  $result = call_user_func_array( $callback, $allArgs );
3386 
3387  # The interface for function hooks allows them to return a wikitext
3388  # string or an array containing the string and any flags. This mungs
3389  # things around to match what this method should return.
3390  if ( !is_array( $result ) ) {
3391  $result =[
3392  'found' => true,
3393  'text' => $result,
3394  ];
3395  } else {
3396  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3397  $result['text'] = $result[0];
3398  }
3399  unset( $result[0] );
3400  $result += [
3401  'found' => true,
3402  ];
3403  }
3404 
3405  $noparse = true;
3406  $preprocessFlags = 0;
3407  if ( isset( $result['noparse'] ) ) {
3408  $noparse = $result['noparse'];
3409  }
3410  if ( isset( $result['preprocessFlags'] ) ) {
3411  $preprocessFlags = $result['preprocessFlags'];
3412  }
3413 
3414  if ( !$noparse ) {
3415  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3416  $result['isChildObj'] = true;
3417  }
3418 
3419  return $result;
3420  }
3421 
3430  public function getTemplateDom( $title ) {
3431  $cacheTitle = $title;
3432  $titleText = $title->getPrefixedDBkey();
3433 
3434  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3435  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3436  $title = Title::makeTitle( $ns, $dbk );
3437  $titleText = $title->getPrefixedDBkey();
3438  }
3439  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3440  return [ $this->mTplDomCache[$titleText], $title ];
3441  }
3442 
3443  # Cache miss, go to the database
3444  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3445 
3446  if ( $text === false ) {
3447  $this->mTplDomCache[$titleText] = false;
3448  return [ false, $title ];
3449  }
3450 
3451  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3452  $this->mTplDomCache[$titleText] = $dom;
3453 
3454  if ( !$title->equals( $cacheTitle ) ) {
3455  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3456  [ $title->getNamespace(), $cdb = $title->getDBkey() ];
3457  }
3458 
3459  return [ $dom, $title ];
3460  }
3461 
3474  $cacheKey = $title->getPrefixedDBkey();
3475  if ( !$this->currentRevisionCache ) {
3476  $this->currentRevisionCache = new MapCacheLRU( 100 );
3477  }
3478  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3479  $this->currentRevisionCache->set( $cacheKey,
3480  // Defaults to Parser::statelessFetchRevision()
3481  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3482  );
3483  }
3484  return $this->currentRevisionCache->get( $cacheKey );
3485  }
3486 
3496  public static function statelessFetchRevision( Title $title, $parser = false ) {
3497  $pageId = $title->getArticleID();
3498  $revId = $title->getLatestRevID();
3499 
3501  if ( $rev ) {
3502  $rev->setTitle( $title );
3503  }
3504 
3505  return $rev;
3506  }
3507 
3513  public function fetchTemplateAndTitle( $title ) {
3514  // Defaults to Parser::statelessFetchTemplate()
3515  $templateCb = $this->mOptions->getTemplateCallback();
3516  $stuff = call_user_func( $templateCb, $title, $this );
3517  // We use U+007F DELETE to distinguish strip markers from regular text.
3518  $text = $stuff['text'];
3519  if ( is_string( $stuff['text'] ) ) {
3520  $text = strtr( $text, "\x7f", "?" );
3521  }
3522  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3523  if ( isset( $stuff['deps'] ) ) {
3524  foreach ( $stuff['deps'] as $dep ) {
3525  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3526  if ( $dep['title']->equals( $this->getTitle() ) ) {
3527  // If we transclude ourselves, the final result
3528  // will change based on the new version of the page
3529  $this->mOutput->setFlag( 'vary-revision' );
3530  }
3531  }
3532  }
3533  return [ $text, $finalTitle ];
3534  }
3535 
3541  public function fetchTemplate( $title ) {
3542  return $this->fetchTemplateAndTitle( $title )[0];
3543  }
3544 
3554  public static function statelessFetchTemplate( $title, $parser = false ) {
3555  $text = $skip = false;
3556  $finalTitle = $title;
3557  $deps = [];
3558 
3559  # Loop to fetch the article, with up to 1 redirect
3560  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3561  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3562  // @codingStandardsIgnoreEnd
3563  # Give extensions a chance to select the revision instead
3564  $id = false; # Assume current
3565  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3566  [ $parser, $title, &$skip, &$id ] );
3567 
3568  if ( $skip ) {
3569  $text = false;
3570  $deps[] = [
3571  'title' => $title,
3572  'page_id' => $title->getArticleID(),
3573  'rev_id' => null
3574  ];
3575  break;
3576  }
3577  # Get the revision
3578  if ( $id ) {
3579  $rev = Revision::newFromId( $id );
3580  } elseif ( $parser ) {
3581  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3582  } else {
3584  }
3585  $rev_id = $rev ? $rev->getId() : 0;
3586  # If there is no current revision, there is no page
3587  if ( $id === false && !$rev ) {
3588  $linkCache = LinkCache::singleton();
3589  $linkCache->addBadLinkObj( $title );
3590  }
3591 
3592  $deps[] = [
3593  'title' => $title,
3594  'page_id' => $title->getArticleID(),
3595  'rev_id' => $rev_id ];
3596  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3597  # We fetched a rev from a different title; register it too...
3598  $deps[] = [
3599  'title' => $rev->getTitle(),
3600  'page_id' => $rev->getPage(),
3601  'rev_id' => $rev_id ];
3602  }
3603 
3604  if ( $rev ) {
3605  $content = $rev->getContent();
3606  $text = $content ? $content->getWikitextForTransclusion() : null;
3607 
3608  Hooks::run( 'ParserFetchTemplate',
3609  [ $parser, $title, $rev, &$text, &$deps ] );
3610 
3611  if ( $text === false || $text === null ) {
3612  $text = false;
3613  break;
3614  }
3615  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3617  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3618  if ( !$message->exists() ) {
3619  $text = false;
3620  break;
3621  }
3622  $content = $message->content();
3623  $text = $message->plain();
3624  } else {
3625  break;
3626  }
3627  if ( !$content ) {
3628  break;
3629  }
3630  # Redirect?
3631  $finalTitle = $title;
3632  $title = $content->getRedirectTarget();
3633  }
3634  return [
3635  'text' => $text,
3636  'finalTitle' => $finalTitle,
3637  'deps' => $deps ];
3638  }
3639 
3647  public function fetchFile( $title, $options = [] ) {
3648  return $this->fetchFileAndTitle( $title, $options )[0];
3649  }
3650 
3658  public function fetchFileAndTitle( $title, $options = [] ) {
3659  $file = $this->fetchFileNoRegister( $title, $options );
3660 
3661  $time = $file ? $file->getTimestamp() : false;
3662  $sha1 = $file ? $file->getSha1() : false;
3663  # Register the file as a dependency...
3664  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3665  if ( $file && !$title->equals( $file->getTitle() ) ) {
3666  # Update fetched file title
3667  $title = $file->getTitle();
3668  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3669  }
3670  return [ $file, $title ];
3671  }
3672 
3683  protected function fetchFileNoRegister( $title, $options = [] ) {
3684  if ( isset( $options['broken'] ) ) {
3685  $file = false; // broken thumbnail forced by hook
3686  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3687  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3688  } else { // get by (name,timestamp)
3689  $file = wfFindFile( $title, $options );
3690  }
3691  return $file;
3692  }
3693 
3702  public function interwikiTransclude( $title, $action ) {
3703  global $wgEnableScaryTranscluding;
3704 
3705  if ( !$wgEnableScaryTranscluding ) {
3706  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3707  }
3708 
3709  $url = $title->getFullURL( [ 'action' => $action ] );
3710 
3711  if ( strlen( $url ) > 255 ) {
3712  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3713  }
3714  return $this->fetchScaryTemplateMaybeFromCache( $url );
3715  }
3716 
3721  public function fetchScaryTemplateMaybeFromCache( $url ) {
3722  global $wgTranscludeCacheExpiry;
3723  $dbr = wfGetDB( DB_REPLICA );
3724  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3725  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3726  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3727  if ( $obj ) {
3728  return $obj->tc_contents;
3729  }
3730 
3731  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3732  $status = $req->execute(); // Status object
3733  if ( $status->isOK() ) {
3734  $text = $req->getContent();
3735  } elseif ( $req->getStatus() != 200 ) {
3736  // Though we failed to fetch the content, this status is useless.
3737  return wfMessage( 'scarytranscludefailed-httpstatus' )
3738  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3739  } else {
3740  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3741  }
3742 
3743  $dbw = wfGetDB( DB_MASTER );
3744  $dbw->replace( 'transcache', [ 'tc_url' ], [
3745  'tc_url' => $url,
3746  'tc_time' => $dbw->timestamp( time() ),
3747  'tc_contents' => $text
3748  ] );
3749  return $text;
3750  }
3751 
3761  public function argSubstitution( $piece, $frame ) {
3762 
3763  $error = false;
3764  $parts = $piece['parts'];
3765  $nameWithSpaces = $frame->expand( $piece['title'] );
3766  $argName = trim( $nameWithSpaces );
3767  $object = false;
3768  $text = $frame->getArgument( $argName );
3769  if ( $text === false && $parts->getLength() > 0
3770  && ( $this->ot['html']
3771  || $this->ot['pre']
3772  || ( $this->ot['wiki'] && $frame->isTemplate() )
3773  )
3774  ) {
3775  # No match in frame, use the supplied default
3776  $object = $parts->item( 0 )->getChildren();
3777  }
3778  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3779  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3780  $this->limitationWarn( 'post-expand-template-argument' );
3781  }
3782 
3783  if ( $text === false && $object === false ) {
3784  # No match anywhere
3785  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3786  }
3787  if ( $error !== false ) {
3788  $text .= $error;
3789  }
3790  if ( $object !== false ) {
3791  $ret = [ 'object' => $object ];
3792  } else {
3793  $ret = [ 'text' => $text ];
3794  }
3795 
3796  return $ret;
3797  }
3798 
3814  public function extensionSubstitution( $params, $frame ) {
3815  static $errorStr = '<span class="error">';
3816  static $errorLen = 20;
3817 
3818  $name = $frame->expand( $params['name'] );
3819  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3820  // Probably expansion depth or node count exceeded. Just punt the
3821  // error up.
3822  return $name;
3823  }
3824 
3825  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3826  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3827  // See above
3828  return $attrText;
3829  }
3830 
3831  // We can't safely check if the expansion for $content resulted in an
3832  // error, because the content could happen to be the error string
3833  // (T149622).
3834  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3835 
3836  $marker = self::MARKER_PREFIX . "-$name-"
3837  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3838 
3839  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3840  ( $this->ot['html'] || $this->ot['pre'] );
3841  if ( $isFunctionTag ) {
3842  $markerType = 'none';
3843  } else {
3844  $markerType = 'general';
3845  }
3846  if ( $this->ot['html'] || $isFunctionTag ) {
3847  $name = strtolower( $name );
3848  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3849  if ( isset( $params['attributes'] ) ) {
3850  $attributes = $attributes + $params['attributes'];
3851  }
3852 
3853  if ( isset( $this->mTagHooks[$name] ) ) {
3854  # Workaround for PHP bug 35229 and similar
3855  if ( !is_callable( $this->mTagHooks[$name] ) ) {
3856  throw new MWException( "Tag hook for $name is not callable\n" );
3857  }
3858  $output = call_user_func_array( $this->mTagHooks[$name],
3859  [ $content, $attributes, $this, $frame ] );
3860  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3861  list( $callback, ) = $this->mFunctionTagHooks[$name];
3862  if ( !is_callable( $callback ) ) {
3863  throw new MWException( "Tag hook for $name is not callable\n" );
3864  }
3865 
3866  $output = call_user_func_array( $callback, [ &$this, $frame, $content, $attributes ] );
3867  } else {
3868  $output = '<span class="error">Invalid tag extension name: ' .
3869  htmlspecialchars( $name ) . '</span>';
3870  }
3871 
3872  if ( is_array( $output ) ) {
3873  # Extract flags to local scope (to override $markerType)
3874  $flags = $output;
3875  $output = $flags[0];
3876  unset( $flags[0] );
3877  extract( $flags );
3878  }
3879  } else {
3880  if ( is_null( $attrText ) ) {
3881  $attrText = '';
3882  }
3883  if ( isset( $params['attributes'] ) ) {
3884  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3885  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3886  htmlspecialchars( $attrValue ) . '"';
3887  }
3888  }
3889  if ( $content === null ) {
3890  $output = "<$name$attrText/>";
3891  } else {
3892  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3893  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3894  // See above
3895  return $close;
3896  }
3897  $output = "<$name$attrText>$content$close";
3898  }
3899  }
3900 
3901  if ( $markerType === 'none' ) {
3902  return $output;
3903  } elseif ( $markerType === 'nowiki' ) {
3904  $this->mStripState->addNoWiki( $marker, $output );
3905  } elseif ( $markerType === 'general' ) {
3906  $this->mStripState->addGeneral( $marker, $output );
3907  } else {
3908  throw new MWException( __METHOD__ . ': invalid marker type' );
3909  }
3910  return $marker;
3911  }
3912 
3920  public function incrementIncludeSize( $type, $size ) {
3921  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3922  return false;
3923  } else {
3924  $this->mIncludeSizes[$type] += $size;
3925  return true;
3926  }
3927  }
3928 
3935  $this->mExpensiveFunctionCount++;
3936  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3937  }
3938 
3947  public function doDoubleUnderscore( $text ) {
3948 
3949  # The position of __TOC__ needs to be recorded
3950  $mw = MagicWord::get( 'toc' );
3951  if ( $mw->match( $text ) ) {
3952  $this->mShowToc = true;
3953  $this->mForceTocPosition = true;
3954 
3955  # Set a placeholder. At the end we'll fill it in with the TOC.
3956  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3957 
3958  # Only keep the first one.
3959  $text = $mw->replace( '', $text );
3960  }
3961 
3962  # Now match and remove the rest of them
3964  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3965 
3966  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3967  $this->mOutput->mNoGallery = true;
3968  }
3969  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3970  $this->mShowToc = false;
3971  }
3972  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3973  && $this->mTitle->getNamespace() == NS_CATEGORY
3974  ) {
3975  $this->addTrackingCategory( 'hidden-category-category' );
3976  }
3977  # (T10068) Allow control over whether robots index a page.
3978  # __INDEX__ always overrides __NOINDEX__, see T16899
3979  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3980  $this->mOutput->setIndexPolicy( 'noindex' );
3981  $this->addTrackingCategory( 'noindex-category' );
3982  }
3983  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
3984  $this->mOutput->setIndexPolicy( 'index' );
3985  $this->addTrackingCategory( 'index-category' );
3986  }
3987 
3988  # Cache all double underscores in the database
3989  foreach ( $this->mDoubleUnderscores as $key => $val ) {
3990  $this->mOutput->setProperty( $key, '' );
3991  }
3992 
3993  return $text;
3994  }
3995 
4001  public function addTrackingCategory( $msg ) {
4002  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4003  }
4004 
4021  public function formatHeadings( $text, $origText, $isMain = true ) {
4022  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4023 
4024  # Inhibit editsection links if requested in the page
4025  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4026  $maybeShowEditLink = $showEditLink = false;
4027  } else {
4028  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4029  $showEditLink = $this->mOptions->getEditSection();
4030  }
4031  if ( $showEditLink ) {
4032  $this->mOutput->setEditSectionTokens( true );
4033  }
4034 
4035  # Get all headlines for numbering them and adding funky stuff like [edit]
4036  # links - this is for later, but we need the number of headlines right now
4037  $matches = [];
4038  $numMatches = preg_match_all(
4039  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4040  $text,
4041  $matches
4042  );
4043 
4044  # if there are fewer than 4 headlines in the article, do not show TOC
4045  # unless it's been explicitly enabled.
4046  $enoughToc = $this->mShowToc &&
4047  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4048 
4049  # Allow user to stipulate that a page should have a "new section"
4050  # link added via __NEWSECTIONLINK__
4051  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4052  $this->mOutput->setNewSection( true );
4053  }
4054 
4055  # Allow user to remove the "new section"
4056  # link via __NONEWSECTIONLINK__
4057  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4058  $this->mOutput->hideNewSection( true );
4059  }
4060 
4061  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4062  # override above conditions and always show TOC above first header
4063  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4064  $this->mShowToc = true;
4065  $enoughToc = true;
4066  }
4067 
4068  # headline counter
4069  $headlineCount = 0;
4070  $numVisible = 0;
4071 
4072  # Ugh .. the TOC should have neat indentation levels which can be
4073  # passed to the skin functions. These are determined here
4074  $toc = '';
4075  $full = '';
4076  $head = [];
4077  $sublevelCount = [];
4078  $levelCount = [];
4079  $level = 0;
4080  $prevlevel = 0;
4081  $toclevel = 0;
4082  $prevtoclevel = 0;
4083  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4084  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4085  $oldType = $this->mOutputType;
4086  $this->setOutputType( self::OT_WIKI );
4087  $frame = $this->getPreprocessor()->newFrame();
4088  $root = $this->preprocessToDom( $origText );
4089  $node = $root->getFirstChild();
4090  $byteOffset = 0;
4091  $tocraw = [];
4092  $refers = [];
4093 
4094  $headlines = $numMatches !== false ? $matches[3] : [];
4095 
4096  foreach ( $headlines as $headline ) {
4097  $isTemplate = false;
4098  $titleText = false;
4099  $sectionIndex = false;
4100  $numbering = '';
4101  $markerMatches = [];
4102  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4103  $serial = $markerMatches[1];
4104  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4105  $isTemplate = ( $titleText != $baseTitleText );
4106  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4107  }
4108 
4109  if ( $toclevel ) {
4110  $prevlevel = $level;
4111  }
4112  $level = $matches[1][$headlineCount];
4113 
4114  if ( $level > $prevlevel ) {
4115  # Increase TOC level
4116  $toclevel++;
4117  $sublevelCount[$toclevel] = 0;
4118  if ( $toclevel < $wgMaxTocLevel ) {
4119  $prevtoclevel = $toclevel;
4120  $toc .= Linker::tocIndent();
4121  $numVisible++;
4122  }
4123  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4124  # Decrease TOC level, find level to jump to
4125 
4126  for ( $i = $toclevel; $i > 0; $i-- ) {
4127  if ( $levelCount[$i] == $level ) {
4128  # Found last matching level
4129  $toclevel = $i;
4130  break;
4131  } elseif ( $levelCount[$i] < $level ) {
4132  # Found first matching level below current level
4133  $toclevel = $i + 1;
4134  break;
4135  }
4136  }
4137  if ( $i == 0 ) {
4138  $toclevel = 1;
4139  }
4140  if ( $toclevel < $wgMaxTocLevel ) {
4141  if ( $prevtoclevel < $wgMaxTocLevel ) {
4142  # Unindent only if the previous toc level was shown :p
4143  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4144  $prevtoclevel = $toclevel;
4145  } else {
4146  $toc .= Linker::tocLineEnd();
4147  }
4148  }
4149  } else {
4150  # No change in level, end TOC line
4151  if ( $toclevel < $wgMaxTocLevel ) {
4152  $toc .= Linker::tocLineEnd();
4153  }
4154  }
4155 
4156  $levelCount[$toclevel] = $level;
4157 
4158  # count number of headlines for each level
4159  $sublevelCount[$toclevel]++;
4160  $dot = 0;
4161  for ( $i = 1; $i <= $toclevel; $i++ ) {
4162  if ( !empty( $sublevelCount[$i] ) ) {
4163  if ( $dot ) {
4164  $numbering .= '.';
4165  }
4166  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4167  $dot = 1;
4168  }
4169  }
4170 
4171  # The safe header is a version of the header text safe to use for links
4172 
4173  # Remove link placeholders by the link text.
4174  # <!--LINK number-->
4175  # turns into
4176  # link text with suffix
4177  # Do this before unstrip since link text can contain strip markers
4178  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4179 
4180  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4181  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4182 
4183  # Strip out HTML (first regex removes any tag not allowed)
4184  # Allowed tags are:
4185  # * <sup> and <sub> (T10393)
4186  # * <i> (T28375)
4187  # * <b> (r105284)
4188  # * <bdi> (T74884)
4189  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4190  # * <s> and <strike> (T35715)
4191  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4192  # to allow setting directionality in toc items.
4193  $tocline = preg_replace(
4194  [
4195  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4196  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4197  ],
4198  [ '', '<$1>' ],
4199  $safeHeadline
4200  );
4201 
4202  # Strip '<span></span>', which is the result from the above if
4203  # <span id="foo"></span> is used to produce an additional anchor
4204  # for a section.
4205  $tocline = str_replace( '<span></span>', '', $tocline );
4206 
4207  $tocline = trim( $tocline );
4208 
4209  # For the anchor, strip out HTML-y stuff period
4210  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4211  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4212 
4213  # Save headline for section edit hint before it's escaped
4214  $headlineHint = $safeHeadline;
4215 
4216  if ( $wgExperimentalHtmlIds ) {
4217  # For reverse compatibility, provide an id that's
4218  # HTML4-compatible, like we used to.
4219  # It may be worth noting, academically, that it's possible for
4220  # the legacy anchor to conflict with a non-legacy headline
4221  # anchor on the page. In this case likely the "correct" thing
4222  # would be to either drop the legacy anchors or make sure
4223  # they're numbered first. However, this would require people
4224  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4225  # manually, so let's not bother worrying about it.
4226  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4227  [ 'noninitial', 'legacy' ] );
4228  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4229 
4230  if ( $legacyHeadline == $safeHeadline ) {
4231  # No reason to have both (in fact, we can't)
4232  $legacyHeadline = false;
4233  }
4234  } else {
4235  $legacyHeadline = false;
4236  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4237  'noninitial' );
4238  }
4239 
4240  # HTML names must be case-insensitively unique (T12721).
4241  # This does not apply to Unicode characters per
4242  # https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4243  # @todo FIXME: We may be changing them depending on the current locale.
4244  $arrayKey = strtolower( $safeHeadline );
4245  if ( $legacyHeadline === false ) {
4246  $legacyArrayKey = false;
4247  } else {
4248  $legacyArrayKey = strtolower( $legacyHeadline );
4249  }
4250 
4251  # Create the anchor for linking from the TOC to the section
4252  $anchor = $safeHeadline;
4253  $legacyAnchor = $legacyHeadline;
4254  if ( isset( $refers[$arrayKey] ) ) {
4255  // @codingStandardsIgnoreStart
4256  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4257  // @codingStandardsIgnoreEnd
4258  $anchor .= "_$i";
4259  $refers["${arrayKey}_$i"] = true;
4260  } else {
4261  $refers[$arrayKey] = true;
4262  }
4263  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4264  // @codingStandardsIgnoreStart
4265  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4266  // @codingStandardsIgnoreEnd
4267  $legacyAnchor .= "_$i";
4268  $refers["${legacyArrayKey}_$i"] = true;
4269  } else {
4270  $refers[$legacyArrayKey] = true;
4271  }
4272 
4273  # Don't number the heading if it is the only one (looks silly)
4274  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4275  # the two are different if the line contains a link
4276  $headline = Html::element(
4277  'span',
4278  [ 'class' => 'mw-headline-number' ],
4279  $numbering
4280  ) . ' ' . $headline;
4281  }
4282 
4283  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4284  $toc .= Linker::tocLine( $anchor, $tocline,
4285  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4286  }
4287 
4288  # Add the section to the section tree
4289  # Find the DOM node for this header
4290  $noOffset = ( $isTemplate || $sectionIndex === false );
4291  while ( $node && !$noOffset ) {
4292  if ( $node->getName() === 'h' ) {
4293  $bits = $node->splitHeading();
4294  if ( $bits['i'] == $sectionIndex ) {
4295  break;
4296  }
4297  }
4298  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4299  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4300  $node = $node->getNextSibling();
4301  }
4302  $tocraw[] = [
4303  'toclevel' => $toclevel,
4304  'level' => $level,
4305  'line' => $tocline,
4306  'number' => $numbering,
4307  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4308  'fromtitle' => $titleText,
4309  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4310  'anchor' => $anchor,
4311  ];
4312 
4313  # give headline the correct <h#> tag
4314  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4315  // Output edit section links as markers with styles that can be customized by skins
4316  if ( $isTemplate ) {
4317  # Put a T flag in the section identifier, to indicate to extractSections()
4318  # that sections inside <includeonly> should be counted.
4319  $editsectionPage = $titleText;
4320  $editsectionSection = "T-$sectionIndex";
4321  $editsectionContent = null;
4322  } else {
4323  $editsectionPage = $this->mTitle->getPrefixedText();
4324  $editsectionSection = $sectionIndex;
4325  $editsectionContent = $headlineHint;
4326  }
4327  // We use a bit of pesudo-xml for editsection markers. The
4328  // language converter is run later on. Using a UNIQ style marker
4329  // leads to the converter screwing up the tokens when it
4330  // converts stuff. And trying to insert strip tags fails too. At
4331  // this point all real inputted tags have already been escaped,
4332  // so we don't have to worry about a user trying to input one of
4333  // these markers directly. We use a page and section attribute
4334  // to stop the language converter from converting these
4335  // important bits of data, but put the headline hint inside a
4336  // content block because the language converter is supposed to
4337  // be able to convert that piece of data.
4338  // Gets replaced with html in ParserOutput::getText
4339  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4340  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4341  if ( $editsectionContent !== null ) {
4342  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4343  } else {
4344  $editlink .= '/>';
4345  }
4346  } else {
4347  $editlink = '';
4348  }
4349  $head[$headlineCount] = Linker::makeHeadline( $level,
4350  $matches['attrib'][$headlineCount], $anchor, $headline,
4351  $editlink, $legacyAnchor );
4352 
4353  $headlineCount++;
4354  }
4355 
4356  $this->setOutputType( $oldType );
4357 
4358  # Never ever show TOC if no headers
4359  if ( $numVisible < 1 ) {
4360  $enoughToc = false;
4361  }
4362 
4363  if ( $enoughToc ) {
4364  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4365  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4366  }
4367  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4368  $this->mOutput->setTOCHTML( $toc );
4369  $toc = self::TOC_START . $toc . self::TOC_END;
4370  $this->mOutput->addModules( 'mediawiki.toc' );
4371  }
4372 
4373  if ( $isMain ) {
4374  $this->mOutput->setSections( $tocraw );
4375  }
4376 
4377  # split up and insert constructed headlines
4378  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4379  $i = 0;
4380 
4381  // build an array of document sections
4382  $sections = [];
4383  foreach ( $blocks as $block ) {
4384  // $head is zero-based, sections aren't.
4385  if ( empty( $head[$i - 1] ) ) {
4386  $sections[$i] = $block;
4387  } else {
4388  $sections[$i] = $head[$i - 1] . $block;
4389  }
4390 
4401  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4402 
4403  $i++;
4404  }
4405 
4406  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4407  // append the TOC at the beginning
4408  // Top anchor now in skin
4409  $sections[0] = $sections[0] . $toc . "\n";
4410  }
4411 
4412  $full .= implode( '', $sections );
4413 
4414  if ( $this->mForceTocPosition ) {
4415  return str_replace( '<!--MWTOC-->', $toc, $full );
4416  } else {
4417  return $full;
4418  }
4419  }
4420 
4432  public function preSaveTransform( $text, Title $title, User $user,
4433  ParserOptions $options, $clearState = true
4434  ) {
4435  if ( $clearState ) {
4436  $magicScopeVariable = $this->lock();
4437  }
4438  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4439  $this->setUser( $user );
4440 
4441  // We still normalize line endings for backwards-compatibility
4442  // with other code that just calls PST, but this should already
4443  // be handled in TextContent subclasses
4444  $text = TextContent::normalizeLineEndings( $text );
4445 
4446  if ( $options->getPreSaveTransform() ) {
4447  $text = $this->pstPass2( $text, $user );
4448  }
4449  $text = $this->mStripState->unstripBoth( $text );
4450 
4451  $this->setUser( null ); # Reset
4452 
4453  return $text;
4454  }
4455 
4464  private function pstPass2( $text, $user ) {
4466 
4467  # Note: This is the timestamp saved as hardcoded wikitext to
4468  # the database, we use $wgContLang here in order to give
4469  # everyone the same signature and use the default one rather
4470  # than the one selected in each user's preferences.
4471  # (see also T14815)
4472  $ts = $this->mOptions->getTimestamp();
4473  $timestamp = MWTimestamp::getLocalInstance( $ts );
4474  $ts = $timestamp->format( 'YmdHis' );
4475  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4476 
4477  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4478 
4479  # Variable replacement
4480  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4481  $text = $this->replaceVariables( $text );
4482 
4483  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4484  # which may corrupt this parser instance via its wfMessage()->text() call-
4485 
4486  # Signatures
4487  $sigText = $this->getUserSig( $user );
4488  $text = strtr( $text, [
4489  '~~~~~' => $d,
4490  '~~~~' => "$sigText $d",
4491  '~~~' => $sigText
4492  ] );
4493 
4494  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4495  $tc = '[' . Title::legalChars() . ']';
4496  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4497 
4498  // [[ns:page (context)|]]
4499  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4500  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4501  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4502  // [[ns:page (context), context|]] (using either single or double-width comma)
4503  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4504  // [[|page]] (reverse pipe trick: add context from page title)
4505  $p2 = "/\[\[\\|($tc+)]]/";
4506 
4507  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4508  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4509  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4510  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4511 
4512  $t = $this->mTitle->getText();
4513  $m = [];
4514  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4515  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4516  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4517  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4518  } else {
4519  # if there's no context, don't bother duplicating the title
4520  $text = preg_replace( $p2, '[[\\1]]', $text );
4521  }
4522 
4523  return $text;
4524  }
4525 
4540  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4541  global $wgMaxSigChars;
4542 
4543  $username = $user->getName();
4544 
4545  # If not given, retrieve from the user object.
4546  if ( $nickname === false ) {
4547  $nickname = $user->getOption( 'nickname' );
4548  }
4549 
4550  if ( is_null( $fancySig ) ) {
4551  $fancySig = $user->getBoolOption( 'fancysig' );
4552  }
4553 
4554  $nickname = $nickname == null ? $username : $nickname;
4555 
4556  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4557  $nickname = $username;
4558  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4559  } elseif ( $fancySig !== false ) {
4560  # Sig. might contain markup; validate this
4561  if ( $this->validateSig( $nickname ) !== false ) {
4562  # Validated; clean up (if needed) and return it
4563  return $this->cleanSig( $nickname, true );
4564  } else {
4565  # Failed to validate; fall back to the default
4566  $nickname = $username;
4567  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4568  }
4569  }
4570 
4571  # Make sure nickname doesnt get a sig in a sig
4572  $nickname = self::cleanSigInSig( $nickname );
4573 
4574  # If we're still here, make it a link to the user page
4575  $userText = wfEscapeWikiText( $username );
4576  $nickText = wfEscapeWikiText( $nickname );
4577  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4578 
4579  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4580  ->title( $this->getTitle() )->text();
4581  }
4582 
4589  public function validateSig( $text ) {
4590  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4591  }
4592 
4603  public function cleanSig( $text, $parsing = false ) {
4604  if ( !$parsing ) {
4605  global $wgTitle;
4606  $magicScopeVariable = $this->lock();
4607  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4608  }
4609 
4610  # Option to disable this feature
4611  if ( !$this->mOptions->getCleanSignatures() ) {
4612  return $text;
4613  }
4614 
4615  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4616  # => Move this logic to braceSubstitution()
4617  $substWord = MagicWord::get( 'subst' );
4618  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4619  $substText = '{{' . $substWord->getSynonym( 0 );
4620 
4621  $text = preg_replace( $substRegex, $substText, $text );
4622  $text = self::cleanSigInSig( $text );
4623  $dom = $this->preprocessToDom( $text );
4624  $frame = $this->getPreprocessor()->newFrame();
4625  $text = $frame->expand( $dom );
4626 
4627  if ( !$parsing ) {
4628  $text = $this->mStripState->unstripBoth( $text );
4629  }
4630 
4631  return $text;
4632  }
4633 
4640  public static function cleanSigInSig( $text ) {
4641  $text = preg_replace( '/~{3,5}/', '', $text );
4642  return $text;
4643  }
4644 
4655  $outputType, $clearState = true
4656  ) {
4657  $this->startParse( $title, $options, $outputType, $clearState );
4658  }
4659 
4666  private function startParse( Title $title = null, ParserOptions $options,
4667  $outputType, $clearState = true
4668  ) {
4669  $this->setTitle( $title );
4670  $this->mOptions = $options;
4671  $this->setOutputType( $outputType );
4672  if ( $clearState ) {
4673  $this->clearState();
4674  }
4675  }
4676 
4685  public function transformMsg( $text, $options, $title = null ) {
4686  static $executing = false;
4687 
4688  # Guard against infinite recursion
4689  if ( $executing ) {
4690  return $text;
4691  }
4692  $executing = true;
4693 
4694  if ( !$title ) {
4695  global $wgTitle;
4696  $title = $wgTitle;
4697  }
4698 
4699  $text = $this->preprocess( $text, $title, $options );
4700 
4701  $executing = false;
4702  return $text;
4703  }
4704 
4729  public function setHook( $tag, $callback ) {
4730  $tag = strtolower( $tag );
4731  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4732  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4733  }
4734  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4735  $this->mTagHooks[$tag] = $callback;
4736  if ( !in_array( $tag, $this->mStripList ) ) {
4737  $this->mStripList[] = $tag;
4738  }
4739 
4740  return $oldVal;
4741  }
4742 
4760  public function setTransparentTagHook( $tag, $callback ) {
4761  $tag = strtolower( $tag );
4762  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4763  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4764  }
4765  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4766  $this->mTransparentTagHooks[$tag] = $callback;
4767 
4768  return $oldVal;
4769  }
4770 
4774  public function clearTagHooks() {
4775  $this->mTagHooks = [];
4776  $this->mFunctionTagHooks = [];
4777  $this->mStripList = $this->mDefaultStripList;
4778  }
4779 
4823  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4825 
4826  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4827  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4828 
4829  # Add to function cache
4830  $mw = MagicWord::get( $id );
4831  if ( !$mw ) {
4832  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4833  }
4834 
4835  $synonyms = $mw->getSynonyms();
4836  $sensitive = intval( $mw->isCaseSensitive() );
4837 
4838  foreach ( $synonyms as $syn ) {
4839  # Case
4840  if ( !$sensitive ) {
4841  $syn = $wgContLang->lc( $syn );
4842  }
4843  # Add leading hash
4844  if ( !( $flags & self::SFH_NO_HASH ) ) {
4845  $syn = '#' . $syn;
4846  }
4847  # Remove trailing colon
4848  if ( substr( $syn, -1, 1 ) === ':' ) {
4849  $syn = substr( $syn, 0, -1 );
4850  }
4851  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4852  }
4853  return $oldVal;
4854  }
4855 
4861  public function getFunctionHooks() {
4862  return array_keys( $this->mFunctionHooks );
4863  }
4864 
4875  public function setFunctionTagHook( $tag, $callback, $flags ) {
4876  $tag = strtolower( $tag );
4877  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4878  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4879  }
4880  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4881  $this->mFunctionTagHooks[$tag] : null;
4882  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4883 
4884  if ( !in_array( $tag, $this->mStripList ) ) {
4885  $this->mStripList[] = $tag;
4886  }
4887 
4888  return $old;
4889  }
4890 
4898  public function replaceLinkHolders( &$text, $options = 0 ) {
4899  $this->mLinkHolders->replace( $text );
4900  }
4901 
4909  public function replaceLinkHoldersText( $text ) {
4910  return $this->mLinkHolders->replaceText( $text );
4911  }
4912 
4926  public function renderImageGallery( $text, $params ) {
4927 
4928  $mode = false;
4929  if ( isset( $params['mode'] ) ) {
4930  $mode = $params['mode'];
4931  }
4932 
4933  try {
4934  $ig = ImageGalleryBase::factory( $mode );
4935  } catch ( Exception $e ) {
4936  // If invalid type set, fallback to default.
4937  $ig = ImageGalleryBase::factory( false );
4938  }
4939 
4940  $ig->setContextTitle( $this->mTitle );
4941  $ig->setShowBytes( false );
4942  $ig->setShowFilename( false );
4943  $ig->setParser( $this );
4944  $ig->setHideBadImages();
4945  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
4946 
4947  if ( isset( $params['showfilename'] ) ) {
4948  $ig->setShowFilename( true );
4949  } else {
4950  $ig->setShowFilename( false );
4951  }
4952  if ( isset( $params['caption'] ) ) {
4953  $caption = $params['caption'];
4954  $caption = htmlspecialchars( $caption );
4955  $caption = $this->replaceInternalLinks( $caption );
4956  $ig->setCaptionHtml( $caption );
4957  }
4958  if ( isset( $params['perrow'] ) ) {
4959  $ig->setPerRow( $params['perrow'] );
4960  }
4961  if ( isset( $params['widths'] ) ) {
4962  $ig->setWidths( $params['widths'] );
4963  }
4964  if ( isset( $params['heights'] ) ) {
4965  $ig->setHeights( $params['heights'] );
4966  }
4967  $ig->setAdditionalOptions( $params );
4968 
4969  Hooks::run( 'BeforeParserrenderImageGallery', [ &$this, &$ig ] );
4970 
4971  $lines = StringUtils::explode( "\n", $text );
4972  foreach ( $lines as $line ) {
4973  # match lines like these:
4974  # Image:someimage.jpg|This is some image
4975  $matches = [];
4976  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4977  # Skip empty lines
4978  if ( count( $matches ) == 0 ) {
4979  continue;
4980  }
4981 
4982  if ( strpos( $matches[0], '%' ) !== false ) {
4983  $matches[1] = rawurldecode( $matches[1] );
4984  }
4986  if ( is_null( $title ) ) {
4987  # Bogus title. Ignore these so we don't bomb out later.
4988  continue;
4989  }
4990 
4991  # We need to get what handler the file uses, to figure out parameters.
4992  # Note, a hook can overide the file name, and chose an entirely different
4993  # file (which potentially could be of a different type and have different handler).
4994  $options = [];
4995  $descQuery = false;
4996  Hooks::run( 'BeforeParserFetchFileAndTitle',
4997  [ $this, $title, &$options, &$descQuery ] );
4998  # Don't register it now, as TraditionalImageGallery does that later.
4999  $file = $this->fetchFileNoRegister( $title, $options );
5000  $handler = $file ? $file->getHandler() : false;
5001 
5002  $paramMap = [
5003  'img_alt' => 'gallery-internal-alt',
5004  'img_link' => 'gallery-internal-link',
5005  ];
5006  if ( $handler ) {
5007  $paramMap = $paramMap + $handler->getParamMap();
5008  // We don't want people to specify per-image widths.
5009  // Additionally the width parameter would need special casing anyhow.
5010  unset( $paramMap['img_width'] );
5011  }
5012 
5013  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5014 
5015  $label = '';
5016  $alt = '';
5017  $link = '';
5018  $handlerOptions = [];
5019  if ( isset( $matches[3] ) ) {
5020  // look for an |alt= definition while trying not to break existing
5021  // captions with multiple pipes (|) in it, until a more sensible grammar
5022  // is defined for images in galleries
5023 
5024  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5025  // splitting on '|' is a bit odd, and different from makeImage.
5026  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5027  // Protect LanguageConverter markup
5028  $parameterMatches = StringUtils::delimiterExplode(
5029  '-{', '}-', '|', $matches[3], true /* nested */
5030  );
5031 
5032  foreach ( $parameterMatches as $parameterMatch ) {
5033  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5034  if ( $magicName ) {
5035  $paramName = $paramMap[$magicName];
5036 
5037  switch ( $paramName ) {
5038  case 'gallery-internal-alt':
5039  $alt = $this->stripAltText( $match, false );
5040  break;
5041  case 'gallery-internal-link':
5042  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5043  $chars = self::EXT_LINK_URL_CLASS;
5044  $addr = self::EXT_LINK_ADDR;
5045  $prots = $this->mUrlProtocols;
5046  // check to see if link matches an absolute url, if not then it must be a wiki link.
5047  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5048  // Result of LanguageConverter::markNoConversion
5049  // invoked on an external link.
5050  $linkValue = substr( $linkValue, 4, -2 );
5051  }
5052  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5053  $link = $linkValue;
5054  } else {
5055  $localLinkTitle = Title::newFromText( $linkValue );
5056  if ( $localLinkTitle !== null ) {
5057  $link = $localLinkTitle->getLinkURL();
5058  }
5059  }
5060  break;
5061  default:
5062  // Must be a handler specific parameter.
5063  if ( $handler->validateParam( $paramName, $match ) ) {
5064  $handlerOptions[$paramName] = $match;
5065  } else {
5066  // Guess not, consider it as caption.
5067  wfDebug( "$parameterMatch failed parameter validation\n" );
5068  $label = '|' . $parameterMatch;
5069  }
5070  }
5071 
5072  } else {
5073  // Last pipe wins.
5074  $label = '|' . $parameterMatch;
5075  }
5076  }
5077  // Remove the pipe.
5078  $label = substr( $label, 1 );
5079  }
5080 
5081  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5082  }
5083  $html = $ig->toHTML();
5084  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5085  return $html;
5086  }
5087 
5092  public function getImageParams( $handler ) {
5093  if ( $handler ) {
5094  $handlerClass = get_class( $handler );
5095  } else {
5096  $handlerClass = '';
5097  }
5098  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5099  # Initialise static lists
5100  static $internalParamNames = [
5101  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5102  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5103  'bottom', 'text-bottom' ],
5104  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5105  'upright', 'border', 'link', 'alt', 'class' ],
5106  ];
5107  static $internalParamMap;
5108  if ( !$internalParamMap ) {
5109  $internalParamMap = [];
5110  foreach ( $internalParamNames as $type => $names ) {
5111  foreach ( $names as $name ) {
5112  $magicName = str_replace( '-', '_', "img_$name" );
5113  $internalParamMap[$magicName] = [ $type, $name ];
5114  }
5115  }
5116  }
5117 
5118  # Add handler params
5119  $paramMap = $internalParamMap;
5120  if ( $handler ) {
5121  $handlerParamMap = $handler->getParamMap();
5122  foreach ( $handlerParamMap as $magic => $paramName ) {
5123  $paramMap[$magic] = [ 'handler', $paramName ];
5124  }
5125  }
5126  $this->mImageParams[$handlerClass] = $paramMap;
5127  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5128  }
5129  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5130  }
5131 
5140  public function makeImage( $title, $options, $holders = false ) {
5141  # Check if the options text is of the form "options|alt text"
5142  # Options are:
5143  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5144  # * left no resizing, just left align. label is used for alt= only
5145  # * right same, but right aligned
5146  # * none same, but not aligned
5147  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5148  # * center center the image
5149  # * frame Keep original image size, no magnify-button.
5150  # * framed Same as "frame"
5151  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5152  # * upright reduce width for upright images, rounded to full __0 px
5153  # * border draw a 1px border around the image
5154  # * alt Text for HTML alt attribute (defaults to empty)
5155  # * class Set a class for img node
5156  # * link Set the target of the image link. Can be external, interwiki, or local
5157  # vertical-align values (no % or length right now):
5158  # * baseline
5159  # * sub
5160  # * super
5161  # * top
5162  # * text-top
5163  # * middle
5164  # * bottom
5165  # * text-bottom
5166 
5167  # Protect LanguageConverter markup when splitting into parts
5169  '-{', '}-', '|', $options, true /* allow nesting */
5170  );
5171 
5172  # Give extensions a chance to select the file revision for us
5173  $options = [];
5174  $descQuery = false;
5175  Hooks::run( 'BeforeParserFetchFileAndTitle',
5176  [ $this, $title, &$options, &$descQuery ] );
5177  # Fetch and register the file (file title may be different via hooks)
5178  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5179 
5180  # Get parameter map
5181  $handler = $file ? $file->getHandler() : false;
5182 
5183  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5184 
5185  if ( !$file ) {
5186  $this->addTrackingCategory( 'broken-file-category' );
5187  }
5188 
5189  # Process the input parameters
5190  $caption = '';
5191  $params = [ 'frame' => [], 'handler' => [],
5192  'horizAlign' => [], 'vertAlign' => [] ];
5193  $seenformat = false;
5194  foreach ( $parts as $part ) {
5195  $part = trim( $part );
5196  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5197  $validated = false;
5198  if ( isset( $paramMap[$magicName] ) ) {
5199  list( $type, $paramName ) = $paramMap[$magicName];
5200 
5201  # Special case; width and height come in one variable together
5202  if ( $type === 'handler' && $paramName === 'width' ) {
5203  $parsedWidthParam = $this->parseWidthParam( $value );
5204  if ( isset( $parsedWidthParam['width'] ) ) {
5205  $width = $parsedWidthParam['width'];
5206  if ( $handler->validateParam( 'width', $width ) ) {
5207  $params[$type]['width'] = $width;
5208  $validated = true;
5209  }
5210  }
5211  if ( isset( $parsedWidthParam['height'] ) ) {
5212  $height = $parsedWidthParam['height'];
5213  if ( $handler->validateParam( 'height', $height ) ) {
5214  $params[$type]['height'] = $height;
5215  $validated = true;
5216  }
5217  }
5218  # else no validation -- T15436
5219  } else {
5220  if ( $type === 'handler' ) {
5221  # Validate handler parameter
5222  $validated = $handler->validateParam( $paramName, $value );
5223  } else {
5224  # Validate internal parameters
5225  switch ( $paramName ) {
5226  case 'manualthumb':
5227  case 'alt':
5228  case 'class':
5229  # @todo FIXME: Possibly check validity here for
5230  # manualthumb? downstream behavior seems odd with
5231  # missing manual thumbs.
5232  $validated = true;
5233  $value = $this->stripAltText( $value, $holders );
5234  break;
5235  case 'link':
5236  $chars = self::EXT_LINK_URL_CLASS;
5237  $addr = self::EXT_LINK_ADDR;
5238  $prots = $this->mUrlProtocols;
5239  if ( $value === '' ) {
5240  $paramName = 'no-link';
5241  $value = true;
5242  $validated = true;
5243  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5244  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5245  $paramName = 'link-url';
5246  $this->mOutput->addExternalLink( $value );
5247  if ( $this->mOptions->getExternalLinkTarget() ) {
5248  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5249  }
5250  $validated = true;
5251  }
5252  } else {
5253  $linkTitle = Title::newFromText( $value );
5254  if ( $linkTitle ) {
5255  $paramName = 'link-title';
5256  $value = $linkTitle;
5257  $this->mOutput->addLink( $linkTitle );
5258  $validated = true;
5259  }
5260  }
5261  break;
5262  case 'frameless':
5263  case 'framed':
5264  case 'thumbnail':
5265  // use first appearing option, discard others.
5266  $validated = !$seenformat;
5267  $seenformat = true;
5268  break;
5269  default:
5270  # Most other things appear to be empty or numeric...
5271  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5272  }
5273  }
5274 
5275  if ( $validated ) {
5276  $params[$type][$paramName] = $value;
5277  }
5278  }
5279  }
5280  if ( !$validated ) {
5281  $caption = $part;
5282  }
5283  }
5284 
5285  # Process alignment parameters
5286  if ( $params['horizAlign'] ) {
5287  $params['frame']['align'] = key( $params['horizAlign'] );
5288  }
5289  if ( $params['vertAlign'] ) {
5290  $params['frame']['valign'] = key( $params['vertAlign'] );
5291  }
5292 
5293  $params['frame']['caption'] = $caption;
5294 
5295  # Will the image be presented in a frame, with the caption below?
5296  $imageIsFramed = isset( $params['frame']['frame'] )
5297  || isset( $params['frame']['framed'] )
5298  || isset( $params['frame']['thumbnail'] )
5299  || isset( $params['frame']['manualthumb'] );
5300 
5301  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5302  # came to also set the caption, ordinary text after the image -- which
5303  # makes no sense, because that just repeats the text multiple times in
5304  # screen readers. It *also* came to set the title attribute.
5305  # Now that we have an alt attribute, we should not set the alt text to
5306  # equal the caption: that's worse than useless, it just repeats the
5307  # text. This is the framed/thumbnail case. If there's no caption, we
5308  # use the unnamed parameter for alt text as well, just for the time be-
5309  # ing, if the unnamed param is set and the alt param is not.
5310  # For the future, we need to figure out if we want to tweak this more,
5311  # e.g., introducing a title= parameter for the title; ignoring the un-
5312  # named parameter entirely for images without a caption; adding an ex-
5313  # plicit caption= parameter and preserving the old magic unnamed para-
5314  # meter for BC; ...
5315  if ( $imageIsFramed ) { # Framed image
5316  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5317  # No caption or alt text, add the filename as the alt text so
5318  # that screen readers at least get some description of the image
5319  $params['frame']['alt'] = $title->getText();
5320  }
5321  # Do not set $params['frame']['title'] because tooltips don't make sense
5322  # for framed images
5323  } else { # Inline image
5324  if ( !isset( $params['frame']['alt'] ) ) {
5325  # No alt text, use the "caption" for the alt text
5326  if ( $caption !== '' ) {
5327  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5328  } else {
5329  # No caption, fall back to using the filename for the
5330  # alt text
5331  $params['frame']['alt'] = $title->getText();
5332  }
5333  }
5334  # Use the "caption" for the tooltip text
5335  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5336  }
5337 
5338  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5339 
5340  # Linker does the rest
5341  $time = isset( $options['time'] ) ? $options['time'] : false;
5342  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5343  $time, $descQuery, $this->mOptions->getThumbSize() );
5344 
5345  # Give the handler a chance to modify the parser object
5346  if ( $handler ) {
5347  $handler->parserTransformHook( $this, $file );
5348  }
5349 
5350  return $ret;
5351  }
5352 
5358  protected function stripAltText( $caption, $holders ) {
5359  # Strip bad stuff out of the title (tooltip). We can't just use
5360  # replaceLinkHoldersText() here, because if this function is called
5361  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5362  if ( $holders ) {
5363  $tooltip = $holders->replaceText( $caption );
5364  } else {
5365  $tooltip = $this->replaceLinkHoldersText( $caption );
5366  }
5367 
5368  # make sure there are no placeholders in thumbnail attributes
5369  # that are later expanded to html- so expand them now and
5370  # remove the tags
5371  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5372  $tooltip = Sanitizer::stripAllTags( $tooltip );
5373 
5374  return $tooltip;
5375  }
5376 
5382  public function disableCache() {
5383  wfDebug( "Parser output marked as uncacheable.\n" );
5384  if ( !$this->mOutput ) {
5385  throw new MWException( __METHOD__ .
5386  " can only be called when actually parsing something" );
5387  }
5388  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5389  }
5390 
5399  public function attributeStripCallback( &$text, $frame = false ) {
5400  $text = $this->replaceVariables( $text, $frame );
5401  $text = $this->mStripState->unstripBoth( $text );
5402  return $text;
5403  }
5404 
5410  public function getTags() {
5411  return array_merge(
5412  array_keys( $this->mTransparentTagHooks ),
5413  array_keys( $this->mTagHooks ),
5414  array_keys( $this->mFunctionTagHooks )
5415  );
5416  }
5417 
5428  public function replaceTransparentTags( $text ) {
5429  $matches = [];
5430  $elements = array_keys( $this->mTransparentTagHooks );
5431  $text = self::extractTagsAndParams( $elements, $text, $matches );
5432  $replacements = [];
5433 
5434  foreach ( $matches as $marker => $data ) {
5435  list( $element, $content, $params, $tag ) = $data;
5436  $tagName = strtolower( $element );
5437  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5438  $output = call_user_func_array(
5439  $this->mTransparentTagHooks[$tagName],
5440  [ $content, $params, $this ]
5441  );
5442  } else {
5443  $output = $tag;
5444  }
5445  $replacements[$marker] = $output;
5446  }
5447  return strtr( $text, $replacements );
5448  }
5449 
5479  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5480  global $wgTitle; # not generally used but removes an ugly failure mode
5481 
5482  $magicScopeVariable = $this->lock();
5483  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5484  $outText = '';
5485  $frame = $this->getPreprocessor()->newFrame();
5486 
5487  # Process section extraction flags
5488  $flags = 0;
5489  $sectionParts = explode( '-', $sectionId );
5490  $sectionIndex = array_pop( $sectionParts );
5491  foreach ( $sectionParts as $part ) {
5492  if ( $part === 'T' ) {
5493  $flags |= self::PTD_FOR_INCLUSION;
5494  }
5495  }
5496 
5497  # Check for empty input
5498  if ( strval( $text ) === '' ) {
5499  # Only sections 0 and T-0 exist in an empty document
5500  if ( $sectionIndex == 0 ) {
5501  if ( $mode === 'get' ) {
5502  return '';
5503  } else {
5504  return $newText;
5505  }
5506  } else {
5507  if ( $mode === 'get' ) {
5508  return $newText;
5509  } else {
5510  return $text;
5511  }
5512  }
5513  }
5514 
5515  # Preprocess the text
5516  $root = $this->preprocessToDom( $text, $flags );
5517 
5518  # <h> nodes indicate section breaks
5519  # They can only occur at the top level, so we can find them by iterating the root's children
5520  $node = $root->getFirstChild();
5521 
5522  # Find the target section
5523  if ( $sectionIndex == 0 ) {
5524  # Section zero doesn't nest, level=big
5525  $targetLevel = 1000;
5526  } else {
5527  while ( $node ) {
5528  if ( $node->getName() === 'h' ) {
5529  $bits = $node->splitHeading();
5530  if ( $bits['i'] == $sectionIndex ) {
5531  $targetLevel = $bits['level'];
5532  break;
5533  }
5534  }
5535  if ( $mode === 'replace' ) {
5536  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5537  }
5538  $node = $node->getNextSibling();
5539  }
5540  }
5541 
5542  if ( !$node ) {
5543  # Not found
5544  if ( $mode === 'get' ) {
5545  return $newText;
5546  } else {
5547  return $text;
5548  }
5549  }
5550 
5551  # Find the end of the section, including nested sections
5552  do {
5553  if ( $node->getName() === 'h' ) {
5554  $bits = $node->splitHeading();
5555  $curLevel = $bits['level'];
5556  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5557  break;
5558  }
5559  }
5560  if ( $mode === 'get' ) {
5561  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5562  }
5563  $node = $node->getNextSibling();
5564  } while ( $node );
5565 
5566  # Write out the remainder (in replace mode only)
5567  if ( $mode === 'replace' ) {
5568  # Output the replacement text
5569  # Add two newlines on -- trailing whitespace in $newText is conventionally
5570  # stripped by the editor, so we need both newlines to restore the paragraph gap
5571  # Only add trailing whitespace if there is newText
5572  if ( $newText != "" ) {
5573  $outText .= $newText . "\n\n";
5574  }
5575 
5576  while ( $node ) {
5577  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5578  $node = $node->getNextSibling();
5579  }
5580  }
5581 
5582  if ( is_string( $outText ) ) {
5583  # Re-insert stripped tags
5584  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5585  }
5586 
5587  return $outText;
5588  }
5589 
5604  public function getSection( $text, $sectionId, $defaultText = '' ) {
5605  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5606  }
5607 
5620  public function replaceSection( $oldText, $sectionId, $newText ) {
5621  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5622  }
5623 
5629  public function getRevisionId() {
5630  return $this->mRevisionId;
5631  }
5632 
5639  public function getRevisionObject() {
5640  if ( !is_null( $this->mRevisionObject ) ) {
5641  return $this->mRevisionObject;
5642  }
5643  if ( is_null( $this->mRevisionId ) ) {
5644  return null;
5645  }
5646 
5647  $rev = call_user_func(
5648  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5649  );
5650 
5651  # If the parse is for a new revision, then the callback should have
5652  # already been set to force the object and should match mRevisionId.
5653  # If not, try to fetch by mRevisionId for sanity.
5654  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5655  $rev = Revision::newFromId( $this->mRevisionId );
5656  }
5657 
5658  $this->mRevisionObject = $rev;
5659 
5660  return $this->mRevisionObject;
5661  }
5662 
5668  public function getRevisionTimestamp() {
5669  if ( is_null( $this->mRevisionTimestamp ) ) {
5671 
5672  $revObject = $this->getRevisionObject();
5673  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5674 
5675  # The cryptic '' timezone parameter tells to use the site-default
5676  # timezone offset instead of the user settings.
5677  # Since this value will be saved into the parser cache, served
5678  # to other users, and potentially even used inside links and such,
5679  # it needs to be consistent for all visitors.
5680  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5681 
5682  }
5683  return $this->mRevisionTimestamp;
5684  }
5685 
5691  public function getRevisionUser() {
5692  if ( is_null( $this->mRevisionUser ) ) {
5693  $revObject = $this->getRevisionObject();
5694 
5695  # if this template is subst: the revision id will be blank,
5696  # so just use the current user's name
5697  if ( $revObject ) {
5698  $this->mRevisionUser = $revObject->getUserText();
5699  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5700  $this->mRevisionUser = $this->getUser()->getName();
5701  }
5702  }
5703  return $this->mRevisionUser;
5704  }
5705 
5711  public function getRevisionSize() {
5712  if ( is_null( $this->mRevisionSize ) ) {
5713  $revObject = $this->getRevisionObject();
5714 
5715  # if this variable is subst: the revision id will be blank,
5716  # so just use the parser input size, because the own substituation
5717  # will change the size.
5718  if ( $revObject ) {
5719  $this->mRevisionSize = $revObject->getSize();
5720  } else {
5721  $this->mRevisionSize = $this->mInputSize;
5722  }
5723  }
5724  return $this->mRevisionSize;
5725  }
5726 
5732  public function setDefaultSort( $sort ) {
5733  $this->mDefaultSort = $sort;
5734  $this->mOutput->setProperty( 'defaultsort', $sort );
5735  }
5736 
5747  public function getDefaultSort() {
5748  if ( $this->mDefaultSort !== false ) {
5749  return $this->mDefaultSort;
5750  } else {
5751  return '';
5752  }
5753  }
5754 
5761  public function getCustomDefaultSort() {
5762  return $this->mDefaultSort;
5763  }
5764 
5774  public function guessSectionNameFromWikiText( $text ) {
5775  # Strip out wikitext links(they break the anchor)
5776  $text = $this->stripSectionName( $text );
5778  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5779  }
5780 
5789  public function guessLegacySectionNameFromWikiText( $text ) {
5790  # Strip out wikitext links(they break the anchor)
5791  $text = $this->stripSectionName( $text );
5793  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5794  }
5795 
5810  public function stripSectionName( $text ) {
5811  # Strip internal link markup
5812  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5813  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5814 
5815  # Strip external link markup
5816  # @todo FIXME: Not tolerant to blank link text
5817  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5818  # on how many empty links there are on the page - need to figure that out.
5819  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5820 
5821  # Parse wikitext quotes (italics & bold)
5822  $text = $this->doQuotes( $text );
5823 
5824  # Strip HTML tags
5825  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5826  return $text;
5827  }
5828 
5839  public function testSrvus( $text, Title $title, ParserOptions $options,
5840  $outputType = self::OT_HTML
5841  ) {
5842  $magicScopeVariable = $this->lock();
5843  $this->startParse( $title, $options, $outputType, true );
5844 
5845  $text = $this->replaceVariables( $text );
5846  $text = $this->mStripState->unstripBoth( $text );
5847  $text = Sanitizer::removeHTMLtags( $text );
5848  return $text;
5849  }
5850 
5857  public function testPst( $text, Title $title, ParserOptions $options ) {
5858  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5859  }
5860 
5867  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5868  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5869  }
5870 
5887  public function markerSkipCallback( $s, $callback ) {
5888  $i = 0;
5889  $out = '';
5890  while ( $i < strlen( $s ) ) {
5891  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5892  if ( $markerStart === false ) {
5893  $out .= call_user_func( $callback, substr( $s, $i ) );
5894  break;
5895  } else {
5896  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5897  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5898  if ( $markerEnd === false ) {
5899  $out .= substr( $s, $markerStart );
5900  break;
5901  } else {
5902  $markerEnd += strlen( self::MARKER_SUFFIX );
5903  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5904  $i = $markerEnd;
5905  }
5906  }
5907  }
5908  return $out;
5909  }
5910 
5917  public function killMarkers( $text ) {
5918  return $this->mStripState->killMarkers( $text );
5919  }
5920 
5937  public function serializeHalfParsedText( $text ) {
5938  $data = [
5939  'text' => $text,
5940  'version' => self::HALF_PARSED_VERSION,
5941  'stripState' => $this->mStripState->getSubState( $text ),
5942  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5943  ];
5944  return $data;
5945  }
5946 
5962  public function unserializeHalfParsedText( $data ) {
5963  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5964  throw new MWException( __METHOD__ . ': invalid version' );
5965  }
5966 
5967  # First, extract the strip state.
5968  $texts = [ $data['text'] ];
5969  $texts = $this->mStripState->merge( $data['stripState'], $texts );
5970 
5971  # Now renumber links
5972  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
5973 
5974  # Should be good to go.
5975  return $texts[0];
5976  }
5977 
5987  public function isValidHalfParsedText( $data ) {
5988  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
5989  }
5990 
5999  public function parseWidthParam( $value ) {
6000  $parsedWidthParam = [];
6001  if ( $value === '' ) {
6002  return $parsedWidthParam;
6003  }
6004  $m = [];
6005  # (T15500) In both cases (width/height and width only),
6006  # permit trailing "px" for backward compatibility.
6007  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6008  $width = intval( $m[1] );
6009  $height = intval( $m[2] );
6010  $parsedWidthParam['width'] = $width;
6011  $parsedWidthParam['height'] = $height;
6012  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6013  $width = intval( $value );
6014  $parsedWidthParam['width'] = $width;
6015  }
6016  return $parsedWidthParam;
6017  }
6018 
6028  protected function lock() {
6029  if ( $this->mInParse ) {
6030  throw new MWException( "Parser state cleared while parsing. "
6031  . "Did you call Parser::parse recursively?" );
6032  }
6033  $this->mInParse = true;
6034 
6035  $recursiveCheck = new ScopedCallback( function() {
6036  $this->mInParse = false;
6037  } );
6038 
6039  return $recursiveCheck;
6040  }
6041 
6052  public static function stripOuterParagraph( $html ) {
6053  $m = [];
6054  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6055  if ( strpos( $m[1], '</p>' ) === false ) {
6056  $html = $m[1];
6057  }
6058  }
6059 
6060  return $html;
6061  }
6062 
6073  public function getFreshParser() {
6074  global $wgParserConf;
6075  if ( $this->mInParse ) {
6076  return new $wgParserConf['class']( $wgParserConf );
6077  } else {
6078  return $this;
6079  }
6080  }
6081 
6088  public function enableOOUI() {
6090  $this->mOutput->setEnableOOUI( true );
6091  }
6092 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5639
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:530
setTitle($t)
Set the context title.
Definition: Parser.php:753
$mAutonumber
Definition: Parser.php:177
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3266
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5887
$mPPNodeCount
Definition: Parser.php:191
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2090
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:272
getExternalLinkAttribs($url)
Get an associative array of additional HTML attributes appropriate for a particular external link...
Definition: Parser.php:1929
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:5987
null means default in associative array form
Definition: hooks.txt:1909
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:1909
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1537
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:5604
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1288
$mTplRedirCache
Definition: Parser.php:193
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5917
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:1549
LinkRenderer $mLinkRenderer
Definition: Parser.php:257
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3513
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:772
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5691
setFunctionTagHook($tag, $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4875
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:5810
const OT_PREPROCESS
Definition: Defines.php:183
either a plain
Definition: hooks.txt:1960
$mDoubleUnderscores
Definition: Parser.php:193
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2468
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:701
$context
Definition: load.php:50
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4589
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:3177
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4926
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:682
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1829
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
nextLinkID()
Definition: Parser.php:842
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:1909
getImageParams($handler)
Definition: Parser.php:5092
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:1608
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:5410
static isWellFormedXmlFragment($text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:728
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:2075
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3658
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:2831
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:4861
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:857
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3761
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5839
uniqPrefix()
Accessor for mUniqPrefix.
Definition: Parser.php:743
const TOC_START
Definition: Parser.php:137
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:781
SectionProfiler $mProfiler
Definition: Parser.php:252
$sort
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3683
null for the local wiki Added in
Definition: hooks.txt:1528
There are three types of nodes:
$mHeadings
Definition: Parser.php:193
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4774
static makeSelfLinkObj($nt, $html= '', $query= '', $trail= '', $prefix= '')
Make appropriate markup for a link to the current article.
Definition: Linker.php:181
const NS_SPECIAL
Definition: Defines.php:50
clearState()
Clear Parser state.
Definition: Parser.php:341
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:1016
__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:3702
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4464
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead.
Definition: Parser.php:5789
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:835
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2691
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5937
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4898
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3496
static activeUsers()
Definition: SiteStats.php:165
$mLinkID
Definition: Parser.php:190
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1641
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2861
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:2983
static cleanUrl($url)
Definition: Sanitizer.php:1857
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:722
$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:1824
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:2461
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:807
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:924
const NS_TEMPLATE
Definition: Defines.php:71
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:129
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2476
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:617
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2468
MagicWordArray $mVariables
Definition: Parser.php:159
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:658
static getCacheTTL($id)
Allow external reads of TTL array.
Definition: MagicWord.php:295
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5629
const OT_PREPROCESS
Definition: Parser.php:112
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2449
$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:4909
setLinkID($id)
Definition: Parser.php:849
$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:2935
$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:Array with elements of the form"language:title"in the order that they will be output.&$linkFlags:Associative array mapping prefixed links to arrays of flags.Currently unused, but planned to provide support for marking individual language links in the UI, e.g.for featured articles. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED!Use HtmlPageLinkRendererBegin instead.Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1907
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:63
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:308
static splitTrail($trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition: Linker.php:1624
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3430
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2871
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1501
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4603
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:4654
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:4001
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2077
$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:5382
$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:1382
internalParse($text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1258
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:5668
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6052
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:1343
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:5999
$mStripList
Definition: Parser.php:146
$mFunctionTagHooks
Definition: Parser.php:145
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3721
const OT_PLAIN
Definition: Defines.php:185
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3473
$mRevisionTimestamp
Definition: Parser.php:218
$mImageParams
Definition: Parser.php:149
stripAltText($caption, $holders)
Definition: Parser.php:5358
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1624
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:1995
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:1016
setHook($tag, $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4729
const OT_WIKI
Definition: Defines.php:182
Preprocessor $mPreprocessor
Definition: Parser.php:170
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:910
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:49
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:2906
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:898
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3920
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1027
const EXT_IMAGE_REGEX
Definition: Parser.php:98
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4666
$params
const NS_CATEGORY
Definition: Defines.php:75
static makeHeadline($level, $attribs, $anchor, $html, $link, $legacyAnchor=false)
Create a headline for content.
Definition: Linker.php:1605
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:957
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:1054
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:5711
$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:1909
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to and or sell copies of the and to permit persons to whom the Software is furnished to do so
Definition: LICENSE.txt:10
Some information about database access in MediaWiki By Tim January Database layout For information about the MediaWiki database such as a description of the tables and their please see
Definition: database.txt:2
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:117
preSaveTransform($text, Title $title, User $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
Definition: Parser.php:4432
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:870
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:923
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:1886
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:4021
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:888
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1504
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:1519
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:977
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:4540
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:67
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:319
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:1909
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:2427
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:1694
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2873
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:790
$mTagHooks
Definition: Parser.php:141
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:69
static & get($id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:258
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6088
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:242
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3541
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:2018
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2436
const OT_HTML
Definition: Defines.php:181
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:1016
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:285
static images()
Definition: SiteStats.php:173
$mTransparentTagHooks
Definition: Parser.php:142
$mExpensiveFunctionCount
Definition: Parser.php:194
$mUrlProtocols
Definition: Parser.php:166
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2485
$mConf
Definition: Parser.php:166
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4685
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:111
wfUrlProtocols($includeProtocolRelative=true)
Returns a regular expression of url protocols.
static makeExternalLink($url, $text, $escape=true, $linktype= '', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:838
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:300
static getExternalLinkRel($url=false, $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:1908
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:977
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:772
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:1694
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:5428
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:5620
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3947
$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:5867
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:1401
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3334
$wgScriptPath
The path we should point to.
Variant of the Message class.
Definition: Message.php:1361
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6073
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:1016
static articles()
Definition: SiteStats.php:141
$mRevisionUser
Definition: Parser.php:219
lock()
Lock the current instance of the parser.
Definition: Parser.php:6028
static pages()
Definition: SiteStats.php:149
$line
Definition: cdb.php:58
const SFH_OBJECT_ARGS
Definition: Parser.php:86
static delimiterExplode($startDelim, $endDelim, $separator, $subject, $nested=false)
Explode a string, but ignore any instances of the separator inside the given start and end delimiters...
Definition: StringUtils.php:68
makeKnownLinkHolder($nt, $text= '', $trail= '', $prefix= '')
Render a forced-blue link inline; protect against double expansion of URLs if we're in a mode that pr...
Definition: Parser.php:2403
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3554
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:4823
static setupOOUI($skinName= '', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php:778
$mIncludeCount
Definition: Parser.php: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:2871
$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:1016
getTitle()
Accessor for the Title object.
Definition: Parser.php:771
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:5479
ParserOutput $mOutput
Definition: Parser.php:176
getOutput()
Get the ParserOutput object.
Definition: Parser.php:816
$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:1429
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this submitted means any form of or written communication sent to the Licensor or its including but not limited to communication on electronic mailing source code control and issue tracking systems that are managed or on behalf the Licensor for the purpose of discussing and improving the but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work Grant of Copyright License Subject to the terms and conditions of this each Contributor hereby grants to You a non no royalty irrevocable copyright license to prepare Derivative Works publicly display
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:1016
static cleanSigInSig($text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4640
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5732
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3647
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1493
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:3934
$mShowToc
Definition: Parser.php:195
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1959
const DB_REPLICA
Definition: defines.php:22
magicLinkCallback($m)
Definition: Parser.php:1459
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:1040
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5857
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.
if(!$wgRequest->checkUrlExtension()) if(!$wgEnableAPI) $wgTitle
Definition: api.php:57
static explode($separator, $subject)
Workalike for explode() with limited memory usage.
ParserOptions $mOptions
Definition: Parser.php: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:399
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:772
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:3005
setUser($user)
Set the current user.
Definition: Parser.php:733
$mHighestExpansionDepth
Definition: Parser.php:191
makeImage($title, $options, $holders=false)
Parse image options text and use it to make an image.
Definition: Parser.php:5140
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5399
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:5761
extensionSubstitution($params, $frame)
Return the text to be used for a given extension tag.
Definition: Parser.php:3814
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: