MediaWiki  1.28.0
Parser.php
Go to the documentation of this file.
1 <?php
26 
70 class Parser {
76  const VERSION = '1.6.4';
77 
83 
84  # Flags for Parser::setFunctionHook
85  const SFH_NO_HASH = 1;
86  const SFH_OBJECT_ARGS = 2;
87 
88  # Constants needed for external link processing
89  # Everything except bracket, space, or control characters
90  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
91  # as well as U+3000 is IDEOGRAPHIC SPACE for bug 19052
92  const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}]';
93  # Simplified expression to match an IPv4 or IPv6 address, or
94  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
95  const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}])';
96  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
97  // @codingStandardsIgnoreStart Generic.Files.LineLength
98  const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}]+)
99  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
100  // @codingStandardsIgnoreEnd
101 
102  # Regular expression for a non-newline space
103  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
104 
105  # Flags for preprocessToDom
106  const PTD_FOR_INCLUSION = 1;
107 
108  # Allowed values for $this->mOutputType
109  # Parameter to startExternalParse().
110  const OT_HTML = 1; # like parse()
111  const OT_WIKI = 2; # like preSaveTransform()
113  const OT_MSG = 3;
114  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
115 
133  const MARKER_SUFFIX = "-QINU`\"'\x7f";
134  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
135 
136  # Markers used for wrapping the table of contents
137  const TOC_START = '<mw:toc>';
138  const TOC_END = '</mw:toc>';
139 
140  # Persistent:
141  public $mTagHooks = [];
143  public $mFunctionHooks = [];
144  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
145  public $mFunctionTagHooks = [];
146  public $mStripList = [];
147  public $mDefaultStripList = [];
148  public $mVarCache = [];
149  public $mImageParams = [];
151  public $mMarkerIndex = 0;
152  public $mFirstCall = true;
153 
154  # Initialised by initialiseVariables()
155 
159  public $mVariables;
160 
164  public $mSubstWords;
165  # Initialised in constructor
167 
168  # Initialized in getPreprocessor()
169 
171 
172  # Cleared with clearState():
173 
176  public $mOutput;
177  public $mAutonumber;
178 
182  public $mStripState;
183 
189 
190  public $mLinkID;
194  public $mExpensiveFunctionCount; # number of expensive parser function calls
196 
200  public $mUser; # User object; only used when doing pre-save transform
201 
202  # Temporary
203  # These are variables reset at least once per parse regardless of $clearState
204 
208  public $mOptions;
209 
213  public $mTitle; # Title context, used for self-link rendering and similar things
214  public $mOutputType; # Output type, one of the OT_xxx constants
215  public $ot; # Shortcut alias, see setOutputType()
216  public $mRevisionObject; # The revision object of the specified revision ID
217  public $mRevisionId; # ID to display in {{REVISIONID}} tags
218  public $mRevisionTimestamp; # The timestamp of the specified revision ID
219  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
220  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
221  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
222  public $mInputSize = false; # For {{PAGESIZE}} on current page.
223 
228  public $mUniqPrefix = Parser::MARKER_PREFIX;
229 
236 
244 
249  public $mInParse = false;
250 
252  protected $mProfiler;
253 
257  protected $mLinkRenderer;
258 
262  public function __construct( $conf = [] ) {
263  $this->mConf = $conf;
264  $this->mUrlProtocols = wfUrlProtocols();
265  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
266  self::EXT_LINK_ADDR .
267  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
268  if ( isset( $conf['preprocessorClass'] ) ) {
269  $this->mPreprocessorClass = $conf['preprocessorClass'];
270  } elseif ( defined( 'HPHP_VERSION' ) ) {
271  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
272  $this->mPreprocessorClass = 'Preprocessor_Hash';
273  } elseif ( extension_loaded( 'domxml' ) ) {
274  # PECL extension that conflicts with the core DOM extension (bug 13770)
275  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
276  $this->mPreprocessorClass = 'Preprocessor_Hash';
277  } elseif ( extension_loaded( 'dom' ) ) {
278  $this->mPreprocessorClass = 'Preprocessor_DOM';
279  } else {
280  $this->mPreprocessorClass = 'Preprocessor_Hash';
281  }
282  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
283  }
284 
288  public function __destruct() {
289  if ( isset( $this->mLinkHolders ) ) {
290  unset( $this->mLinkHolders );
291  }
292  foreach ( $this as $name => $value ) {
293  unset( $this->$name );
294  }
295  }
296 
300  public function __clone() {
301  $this->mInParse = false;
302 
303  // Bug 56226: When you create a reference "to" an object field, that
304  // makes the object field itself be a reference too (until the other
305  // reference goes out of scope). When cloning, any field that's a
306  // reference is copied as a reference in the new object. Both of these
307  // are defined PHP5 behaviors, as inconvenient as it is for us when old
308  // hooks from PHP4 days are passing fields by reference.
309  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
310  // Make a non-reference copy of the field, then rebind the field to
311  // reference the new copy.
312  $tmp = $this->$k;
313  $this->$k =& $tmp;
314  unset( $tmp );
315  }
316 
317  Hooks::run( 'ParserCloned', [ $this ] );
318  }
319 
323  public function firstCallInit() {
324  if ( !$this->mFirstCall ) {
325  return;
326  }
327  $this->mFirstCall = false;
328 
330  CoreTagHooks::register( $this );
331  $this->initialiseVariables();
332 
333  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, bug 4549
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
551  $dataByFunc = $this->mProfiler->getFunctionStats();
552  uasort( $dataByFunc, function ( $a, $b ) {
553  return $a['real'] < $b['real']; // descending order
554  } );
555  $profileReport = "Transclusion expansion time report (%,ms,calls,template)\n";
556  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
557  $profileReport .= sprintf( "%6.2f%% %8.3f %6d - %s\n",
558  $item['%real'], $item['real'], $item['calls'],
559  htmlspecialchars( $item['name'] ) );
560  }
561  $text .= "\n<!-- \n$profileReport-->\n";
562 
563  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
564  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
565  $this->mTitle->getPrefixedDBkey() );
566  }
567  }
568  $this->mOutput->setText( $text );
569 
570  $this->mRevisionId = $oldRevisionId;
571  $this->mRevisionObject = $oldRevisionObject;
572  $this->mRevisionTimestamp = $oldRevisionTimestamp;
573  $this->mRevisionUser = $oldRevisionUser;
574  $this->mRevisionSize = $oldRevisionSize;
575  $this->mInputSize = false;
576  $this->currentRevisionCache = null;
577 
578  return $this->mOutput;
579  }
580 
603  public function recursiveTagParse( $text, $frame = false ) {
604  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
605  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
606  $text = $this->internalParse( $text, false, $frame );
607  return $text;
608  }
609 
627  public function recursiveTagParseFully( $text, $frame = false ) {
628  $text = $this->recursiveTagParse( $text, $frame );
629  $text = $this->internalParseHalfParsed( $text, false );
630  return $text;
631  }
632 
644  public function preprocess( $text, Title $title = null,
645  ParserOptions $options, $revid = null, $frame = false
646  ) {
647  $magicScopeVariable = $this->lock();
648  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
649  if ( $revid !== null ) {
650  $this->mRevisionId = $revid;
651  }
652  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
653  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
654  $text = $this->replaceVariables( $text, $frame );
655  $text = $this->mStripState->unstripBoth( $text );
656  return $text;
657  }
658 
668  public function recursivePreprocess( $text, $frame = false ) {
669  $text = $this->replaceVariables( $text, $frame );
670  $text = $this->mStripState->unstripBoth( $text );
671  return $text;
672  }
673 
687  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
688  $msg = new RawMessage( $text );
689  $text = $msg->params( $params )->plain();
690 
691  # Parser (re)initialisation
692  $magicScopeVariable = $this->lock();
693  $this->startParse( $title, $options, self::OT_PLAIN, true );
694 
696  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
697  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
698  $text = $this->mStripState->unstripBoth( $text );
699  return $text;
700  }
701 
708  public static function getRandomString() {
709  wfDeprecated( __METHOD__, '1.26' );
710  return wfRandomString( 16 );
711  }
712 
719  public function setUser( $user ) {
720  $this->mUser = $user;
721  }
722 
729  public function uniqPrefix() {
730  wfDeprecated( __METHOD__, '1.26' );
731  return self::MARKER_PREFIX;
732  }
733 
739  public function setTitle( $t ) {
740  if ( !$t ) {
741  $t = Title::newFromText( 'NO TITLE' );
742  }
743 
744  if ( $t->hasFragment() ) {
745  # Strip the fragment to avoid various odd effects
746  $this->mTitle = $t->createFragmentTarget( '' );
747  } else {
748  $this->mTitle = $t;
749  }
750  }
751 
757  public function getTitle() {
758  return $this->mTitle;
759  }
760 
767  public function Title( $x = null ) {
768  return wfSetVar( $this->mTitle, $x );
769  }
770 
776  public function setOutputType( $ot ) {
777  $this->mOutputType = $ot;
778  # Shortcut alias
779  $this->ot = [
780  'html' => $ot == self::OT_HTML,
781  'wiki' => $ot == self::OT_WIKI,
782  'pre' => $ot == self::OT_PREPROCESS,
783  'plain' => $ot == self::OT_PLAIN,
784  ];
785  }
786 
793  public function OutputType( $x = null ) {
794  return wfSetVar( $this->mOutputType, $x );
795  }
796 
802  public function getOutput() {
803  return $this->mOutput;
804  }
805 
811  public function getOptions() {
812  return $this->mOptions;
813  }
814 
821  public function Options( $x = null ) {
822  return wfSetVar( $this->mOptions, $x );
823  }
824 
828  public function nextLinkID() {
829  return $this->mLinkID++;
830  }
831 
835  public function setLinkID( $id ) {
836  $this->mLinkID = $id;
837  }
838 
843  public function getFunctionLang() {
844  return $this->getTargetLanguage();
845  }
846 
856  public function getTargetLanguage() {
857  $target = $this->mOptions->getTargetLanguage();
858 
859  if ( $target !== null ) {
860  return $target;
861  } elseif ( $this->mOptions->getInterfaceMessage() ) {
862  return $this->mOptions->getUserLangObj();
863  } elseif ( is_null( $this->mTitle ) ) {
864  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
865  }
866 
867  return $this->mTitle->getPageLanguage();
868  }
869 
874  public function getConverterLanguage() {
875  return $this->getTargetLanguage();
876  }
877 
884  public function getUser() {
885  if ( !is_null( $this->mUser ) ) {
886  return $this->mUser;
887  }
888  return $this->mOptions->getUser();
889  }
890 
896  public function getPreprocessor() {
897  if ( !isset( $this->mPreprocessor ) ) {
898  $class = $this->mPreprocessorClass;
899  $this->mPreprocessor = new $class( $this );
900  }
901  return $this->mPreprocessor;
902  }
903 
910  public function getLinkRenderer() {
911  if ( !$this->mLinkRenderer ) {
912  $this->mLinkRenderer = MediaWikiServices::getInstance()
913  ->getLinkRendererFactory()->create();
914  $this->mLinkRenderer->setStubThreshold(
915  $this->getOptions()->getStubThreshold()
916  );
917  }
918 
919  return $this->mLinkRenderer;
920  }
921 
943  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
944  if ( $uniq_prefix !== null ) {
945  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
946  }
947  static $n = 1;
948  $stripped = '';
949  $matches = [];
950 
951  $taglist = implode( '|', $elements );
952  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
953 
954  while ( $text != '' ) {
955  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
956  $stripped .= $p[0];
957  if ( count( $p ) < 5 ) {
958  break;
959  }
960  if ( count( $p ) > 5 ) {
961  # comment
962  $element = $p[4];
963  $attributes = '';
964  $close = '';
965  $inside = $p[5];
966  } else {
967  # tag
968  $element = $p[1];
969  $attributes = $p[2];
970  $close = $p[3];
971  $inside = $p[4];
972  }
973 
974  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
975  $stripped .= $marker;
976 
977  if ( $close === '/>' ) {
978  # Empty element tag, <tag />
979  $content = null;
980  $text = $inside;
981  $tail = null;
982  } else {
983  if ( $element === '!--' ) {
984  $end = '/(-->)/';
985  } else {
986  $end = "/(<\\/$element\\s*>)/i";
987  }
988  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
989  $content = $q[0];
990  if ( count( $q ) < 3 ) {
991  # No end tag -- let it run out to the end of the text.
992  $tail = '';
993  $text = '';
994  } else {
995  $tail = $q[1];
996  $text = $q[2];
997  }
998  }
999 
1000  $matches[$marker] = [ $element,
1001  $content,
1002  Sanitizer::decodeTagAttributes( $attributes ),
1003  "<$element$attributes$close$content$tail" ];
1004  }
1005  return $stripped;
1006  }
1007 
1013  public function getStripList() {
1014  return $this->mStripList;
1015  }
1016 
1026  public function insertStripItem( $text ) {
1027  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1028  $this->mMarkerIndex++;
1029  $this->mStripState->addGeneral( $marker, $text );
1030  return $marker;
1031  }
1032 
1040  public function doTableStuff( $text ) {
1041 
1042  $lines = StringUtils::explode( "\n", $text );
1043  $out = '';
1044  $td_history = []; # Is currently a td tag open?
1045  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1046  $tr_history = []; # Is currently a tr tag open?
1047  $tr_attributes = []; # history of tr attributes
1048  $has_opened_tr = []; # Did this table open a <tr> element?
1049  $indent_level = 0; # indent level of the table
1050 
1051  foreach ( $lines as $outLine ) {
1052  $line = trim( $outLine );
1053 
1054  if ( $line === '' ) { # empty line, go to next line
1055  $out .= $outLine . "\n";
1056  continue;
1057  }
1058 
1059  $first_character = $line[0];
1060  $first_two = substr( $line, 0, 2 );
1061  $matches = [];
1062 
1063  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1064  # First check if we are starting a new table
1065  $indent_level = strlen( $matches[1] );
1066 
1067  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1068  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1069 
1070  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1071  array_push( $td_history, false );
1072  array_push( $last_tag_history, '' );
1073  array_push( $tr_history, false );
1074  array_push( $tr_attributes, '' );
1075  array_push( $has_opened_tr, false );
1076  } elseif ( count( $td_history ) == 0 ) {
1077  # Don't do any of the following
1078  $out .= $outLine . "\n";
1079  continue;
1080  } elseif ( $first_two === '|}' ) {
1081  # We are ending a table
1082  $line = '</table>' . substr( $line, 2 );
1083  $last_tag = array_pop( $last_tag_history );
1084 
1085  if ( !array_pop( $has_opened_tr ) ) {
1086  $line = "<tr><td></td></tr>{$line}";
1087  }
1088 
1089  if ( array_pop( $tr_history ) ) {
1090  $line = "</tr>{$line}";
1091  }
1092 
1093  if ( array_pop( $td_history ) ) {
1094  $line = "</{$last_tag}>{$line}";
1095  }
1096  array_pop( $tr_attributes );
1097  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1098  } elseif ( $first_two === '|-' ) {
1099  # Now we have a table row
1100  $line = preg_replace( '#^\|-+#', '', $line );
1101 
1102  # Whats after the tag is now only attributes
1103  $attributes = $this->mStripState->unstripBoth( $line );
1104  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1105  array_pop( $tr_attributes );
1106  array_push( $tr_attributes, $attributes );
1107 
1108  $line = '';
1109  $last_tag = array_pop( $last_tag_history );
1110  array_pop( $has_opened_tr );
1111  array_push( $has_opened_tr, true );
1112 
1113  if ( array_pop( $tr_history ) ) {
1114  $line = '</tr>';
1115  }
1116 
1117  if ( array_pop( $td_history ) ) {
1118  $line = "</{$last_tag}>{$line}";
1119  }
1120 
1121  $outLine = $line;
1122  array_push( $tr_history, false );
1123  array_push( $td_history, false );
1124  array_push( $last_tag_history, '' );
1125  } elseif ( $first_character === '|'
1126  || $first_character === '!'
1127  || $first_two === '|+'
1128  ) {
1129  # This might be cell elements, td, th or captions
1130  if ( $first_two === '|+' ) {
1131  $first_character = '+';
1132  $line = substr( $line, 2 );
1133  } else {
1134  $line = substr( $line, 1 );
1135  }
1136 
1137  // Implies both are valid for table headings.
1138  if ( $first_character === '!' ) {
1139  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1140  }
1141 
1142  # Split up multiple cells on the same line.
1143  # FIXME : This can result in improper nesting of tags processed
1144  # by earlier parser steps.
1145  $cells = explode( '||', $line );
1146 
1147  $outLine = '';
1148 
1149  # Loop through each table cell
1150  foreach ( $cells as $cell ) {
1151  $previous = '';
1152  if ( $first_character !== '+' ) {
1153  $tr_after = array_pop( $tr_attributes );
1154  if ( !array_pop( $tr_history ) ) {
1155  $previous = "<tr{$tr_after}>\n";
1156  }
1157  array_push( $tr_history, true );
1158  array_push( $tr_attributes, '' );
1159  array_pop( $has_opened_tr );
1160  array_push( $has_opened_tr, true );
1161  }
1162 
1163  $last_tag = array_pop( $last_tag_history );
1164 
1165  if ( array_pop( $td_history ) ) {
1166  $previous = "</{$last_tag}>\n{$previous}";
1167  }
1168 
1169  if ( $first_character === '|' ) {
1170  $last_tag = 'td';
1171  } elseif ( $first_character === '!' ) {
1172  $last_tag = 'th';
1173  } elseif ( $first_character === '+' ) {
1174  $last_tag = 'caption';
1175  } else {
1176  $last_tag = '';
1177  }
1178 
1179  array_push( $last_tag_history, $last_tag );
1180 
1181  # A cell could contain both parameters and data
1182  $cell_data = explode( '|', $cell, 2 );
1183 
1184  # Bug 553: Note that a '|' inside an invalid link should not
1185  # be mistaken as delimiting cell parameters
1186  if ( strpos( $cell_data[0], '[[' ) !== false ) {
1187  $cell = "{$previous}<{$last_tag}>{$cell}";
1188  } elseif ( count( $cell_data ) == 1 ) {
1189  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1190  } else {
1191  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1192  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1193  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1194  }
1195 
1196  $outLine .= $cell;
1197  array_push( $td_history, true );
1198  }
1199  }
1200  $out .= $outLine . "\n";
1201  }
1202 
1203  # Closing open td, tr && table
1204  while ( count( $td_history ) > 0 ) {
1205  if ( array_pop( $td_history ) ) {
1206  $out .= "</td>\n";
1207  }
1208  if ( array_pop( $tr_history ) ) {
1209  $out .= "</tr>\n";
1210  }
1211  if ( !array_pop( $has_opened_tr ) ) {
1212  $out .= "<tr><td></td></tr>\n";
1213  }
1214 
1215  $out .= "</table>\n";
1216  }
1217 
1218  # Remove trailing line-ending (b/c)
1219  if ( substr( $out, -1 ) === "\n" ) {
1220  $out = substr( $out, 0, -1 );
1221  }
1222 
1223  # special case: don't return empty table
1224  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1225  $out = '';
1226  }
1227 
1228  return $out;
1229  }
1230 
1243  public function internalParse( $text, $isMain = true, $frame = false ) {
1244 
1245  $origText = $text;
1246 
1247  # Hook to suspend the parser in this state
1248  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$this, &$text, &$this->mStripState ] ) ) {
1249  return $text;
1250  }
1251 
1252  # if $frame is provided, then use $frame for replacing any variables
1253  if ( $frame ) {
1254  # use frame depth to infer how include/noinclude tags should be handled
1255  # depth=0 means this is the top-level document; otherwise it's an included document
1256  if ( !$frame->depth ) {
1257  $flag = 0;
1258  } else {
1259  $flag = Parser::PTD_FOR_INCLUSION;
1260  }
1261  $dom = $this->preprocessToDom( $text, $flag );
1262  $text = $frame->expand( $dom );
1263  } else {
1264  # if $frame is not provided, then use old-style replaceVariables
1265  $text = $this->replaceVariables( $text );
1266  }
1267 
1268  Hooks::run( 'InternalParseBeforeSanitize', [ &$this, &$text, &$this->mStripState ] );
1269  $text = Sanitizer::removeHTMLtags(
1270  $text,
1271  [ &$this, 'attributeStripCallback' ],
1272  false,
1273  array_keys( $this->mTransparentTagHooks ),
1274  [],
1275  [ &$this, 'addTrackingCategory' ]
1276  );
1277  Hooks::run( 'InternalParseBeforeLinks', [ &$this, &$text, &$this->mStripState ] );
1278 
1279  # Tables need to come after variable replacement for things to work
1280  # properly; putting them before other transformations should keep
1281  # exciting things like link expansions from showing up in surprising
1282  # places.
1283  $text = $this->doTableStuff( $text );
1284 
1285  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1286 
1287  $text = $this->doDoubleUnderscore( $text );
1288 
1289  $text = $this->doHeadings( $text );
1290  $text = $this->replaceInternalLinks( $text );
1291  $text = $this->doAllQuotes( $text );
1292  $text = $this->replaceExternalLinks( $text );
1293 
1294  # replaceInternalLinks may sometimes leave behind
1295  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1296  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1297 
1298  $text = $this->doMagicLinks( $text );
1299  $text = $this->formatHeadings( $text, $origText, $isMain );
1300 
1301  return $text;
1302  }
1303 
1313  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1314  $text = $this->mStripState->unstripGeneral( $text );
1315 
1316  if ( $isMain ) {
1317  Hooks::run( 'ParserAfterUnstrip', [ &$this, &$text ] );
1318  }
1319 
1320  # Clean up special characters, only run once, next-to-last before doBlockLevels
1321  $fixtags = [
1322  # french spaces, last one Guillemet-left
1323  # only if there is something before the space
1324  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1325  # french spaces, Guillemet-right
1326  '/(\\302\\253) /' => '\\1&#160;',
1327  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
1328  ];
1329  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1330 
1331  $text = $this->doBlockLevels( $text, $linestart );
1332 
1333  $this->replaceLinkHolders( $text );
1334 
1342  if ( !( $this->mOptions->getDisableContentConversion()
1343  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1344  ) {
1345  if ( !$this->mOptions->getInterfaceMessage() ) {
1346  # The position of the convert() call should not be changed. it
1347  # assumes that the links are all replaced and the only thing left
1348  # is the <nowiki> mark.
1349  $text = $this->getConverterLanguage()->convert( $text );
1350  }
1351  }
1352 
1353  $text = $this->mStripState->unstripNoWiki( $text );
1354 
1355  if ( $isMain ) {
1356  Hooks::run( 'ParserBeforeTidy', [ &$this, &$text ] );
1357  }
1358 
1359  $text = $this->replaceTransparentTags( $text );
1360  $text = $this->mStripState->unstripGeneral( $text );
1361 
1362  $text = Sanitizer::normalizeCharReferences( $text );
1363 
1364  if ( MWTidy::isEnabled() ) {
1365  if ( $this->mOptions->getTidy() ) {
1366  $text = MWTidy::tidy( $text );
1367  }
1368  } else {
1369  # attempt to sanitize at least some nesting problems
1370  # (bug #2702 and quite a few others)
1371  $tidyregs = [
1372  # ''Something [http://www.cool.com cool''] -->
1373  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1374  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1375  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1376  # fix up an anchor inside another anchor, only
1377  # at least for a single single nested link (bug 3695)
1378  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1379  '\\1\\2</a>\\3</a>\\1\\4</a>',
1380  # fix div inside inline elements- doBlockLevels won't wrap a line which
1381  # contains a div, so fix it up here; replace
1382  # div with escaped text
1383  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1384  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1385  # remove empty italic or bold tag pairs, some
1386  # introduced by rules above
1387  '/<([bi])><\/\\1>/' => '',
1388  ];
1389 
1390  $text = preg_replace(
1391  array_keys( $tidyregs ),
1392  array_values( $tidyregs ),
1393  $text );
1394  }
1395 
1396  if ( $isMain ) {
1397  Hooks::run( 'ParserAfterTidy', [ &$this, &$text ] );
1398  }
1399 
1400  return $text;
1401  }
1402 
1414  public function doMagicLinks( $text ) {
1415  $prots = wfUrlProtocolsWithoutProtRel();
1416  $urlChar = self::EXT_LINK_URL_CLASS;
1417  $addr = self::EXT_LINK_ADDR;
1418  $space = self::SPACE_NOT_NL; # non-newline space
1419  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1420  $spaces = "$space++"; # possessive match of 1 or more spaces
1421  $text = preg_replace_callback(
1422  '!(?: # Start cases
1423  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1424  (<.*?>) | # m[2]: Skip stuff inside
1425  # HTML elements' . "
1426  (\b(?i:$prots)($addr$urlChar*)) | # m[3]: Free external links
1427  # m[4]: Post-protocol path
1428  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1429  ([0-9]+)\b |
1430  \bISBN $spaces ( # m[6]: ISBN, capture number
1431  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1432  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1433  [0-9Xx] # check digit
1434  )\b
1435  )!xu", [ &$this, 'magicLinkCallback' ], $text );
1436  return $text;
1437  }
1438 
1444  public function magicLinkCallback( $m ) {
1445  if ( isset( $m[1] ) && $m[1] !== '' ) {
1446  # Skip anchor
1447  return $m[0];
1448  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1449  # Skip HTML element
1450  return $m[0];
1451  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1452  # Free external link
1453  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1454  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1455  # RFC or PMID
1456  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1457  if ( !$this->mOptions->getMagicRFCLinks() ) {
1458  return $m[0];
1459  }
1460  $keyword = 'RFC';
1461  $urlmsg = 'rfcurl';
1462  $cssClass = 'mw-magiclink-rfc';
1463  $trackingCat = 'magiclink-tracking-rfc';
1464  $id = $m[5];
1465  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1466  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1467  return $m[0];
1468  }
1469  $keyword = 'PMID';
1470  $urlmsg = 'pubmedurl';
1471  $cssClass = 'mw-magiclink-pmid';
1472  $trackingCat = 'magiclink-tracking-pmid';
1473  $id = $m[5];
1474  } else {
1475  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1476  substr( $m[0], 0, 20 ) . '"' );
1477  }
1478  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1479  $this->addTrackingCategory( $trackingCat );
1480  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1481  } elseif ( isset( $m[6] ) && $m[6] !== ''
1482  && $this->mOptions->getMagicISBNLinks()
1483  ) {
1484  # ISBN
1485  $isbn = $m[6];
1486  $space = self::SPACE_NOT_NL; # non-newline space
1487  $isbn = preg_replace( "/$space/", ' ', $isbn );
1488  $num = strtr( $isbn, [
1489  '-' => '',
1490  ' ' => '',
1491  'x' => 'X',
1492  ] );
1493  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1494  return $this->getLinkRenderer()->makeKnownLink(
1495  SpecialPage::getTitleFor( 'Booksources', $num ),
1496  "ISBN $isbn",
1497  [
1498  'class' => 'internal mw-magiclink-isbn',
1499  'title' => false // suppress title attribute
1500  ]
1501  );
1502  } else {
1503  return $m[0];
1504  }
1505  }
1506 
1516  public function makeFreeExternalLink( $url, $numPostProto ) {
1517  $trail = '';
1518 
1519  # The characters '<' and '>' (which were escaped by
1520  # removeHTMLtags()) should not be included in
1521  # URLs, per RFC 2396.
1522  # Make &nbsp; terminate a URL as well (bug T84937)
1523  $m2 = [];
1524  if ( preg_match(
1525  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1526  $url,
1527  $m2,
1528  PREG_OFFSET_CAPTURE
1529  ) ) {
1530  $trail = substr( $url, $m2[0][1] ) . $trail;
1531  $url = substr( $url, 0, $m2[0][1] );
1532  }
1533 
1534  # Move trailing punctuation to $trail
1535  $sep = ',;\.:!?';
1536  # If there is no left bracket, then consider right brackets fair game too
1537  if ( strpos( $url, '(' ) === false ) {
1538  $sep .= ')';
1539  }
1540 
1541  $urlRev = strrev( $url );
1542  $numSepChars = strspn( $urlRev, $sep );
1543  # Don't break a trailing HTML entity by moving the ; into $trail
1544  # This is in hot code, so use substr_compare to avoid having to
1545  # create a new string object for the comparison
1546  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1547  # more optimization: instead of running preg_match with a $
1548  # anchor, which can be slow, do the match on the reversed
1549  # string starting at the desired offset.
1550  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1551  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1552  $numSepChars--;
1553  }
1554  }
1555  if ( $numSepChars ) {
1556  $trail = substr( $url, -$numSepChars ) . $trail;
1557  $url = substr( $url, 0, -$numSepChars );
1558  }
1559 
1560  # Verify that we still have a real URL after trail removal, and
1561  # not just lone protocol
1562  if ( strlen( $trail ) >= $numPostProto ) {
1563  return $url . $trail;
1564  }
1565 
1566  $url = Sanitizer::cleanUrl( $url );
1567 
1568  # Is this an external image?
1569  $text = $this->maybeMakeExternalImage( $url );
1570  if ( $text === false ) {
1571  # Not an image, make a link
1572  $text = Linker::makeExternalLink( $url,
1573  $this->getConverterLanguage()->markNoConversion( $url, true ),
1574  true, 'free',
1575  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1576  # Register it in the output object...
1577  # Replace unnecessary URL escape codes with their equivalent characters
1578  $pasteurized = self::normalizeLinkUrl( $url );
1579  $this->mOutput->addExternalLink( $pasteurized );
1580  }
1581  return $text . $trail;
1582  }
1583 
1593  public function doHeadings( $text ) {
1594  for ( $i = 6; $i >= 1; --$i ) {
1595  $h = str_repeat( '=', $i );
1596  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1597  }
1598  return $text;
1599  }
1600 
1609  public function doAllQuotes( $text ) {
1610  $outtext = '';
1611  $lines = StringUtils::explode( "\n", $text );
1612  foreach ( $lines as $line ) {
1613  $outtext .= $this->doQuotes( $line ) . "\n";
1614  }
1615  $outtext = substr( $outtext, 0, -1 );
1616  return $outtext;
1617  }
1618 
1626  public function doQuotes( $text ) {
1627  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1628  $countarr = count( $arr );
1629  if ( $countarr == 1 ) {
1630  return $text;
1631  }
1632 
1633  // First, do some preliminary work. This may shift some apostrophes from
1634  // being mark-up to being text. It also counts the number of occurrences
1635  // of bold and italics mark-ups.
1636  $numbold = 0;
1637  $numitalics = 0;
1638  for ( $i = 1; $i < $countarr; $i += 2 ) {
1639  $thislen = strlen( $arr[$i] );
1640  // If there are ever four apostrophes, assume the first is supposed to
1641  // be text, and the remaining three constitute mark-up for bold text.
1642  // (bug 13227: ''''foo'''' turns into ' ''' foo ' ''')
1643  if ( $thislen == 4 ) {
1644  $arr[$i - 1] .= "'";
1645  $arr[$i] = "'''";
1646  $thislen = 3;
1647  } elseif ( $thislen > 5 ) {
1648  // If there are more than 5 apostrophes in a row, assume they're all
1649  // text except for the last 5.
1650  // (bug 13227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1651  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1652  $arr[$i] = "'''''";
1653  $thislen = 5;
1654  }
1655  // Count the number of occurrences of bold and italics mark-ups.
1656  if ( $thislen == 2 ) {
1657  $numitalics++;
1658  } elseif ( $thislen == 3 ) {
1659  $numbold++;
1660  } elseif ( $thislen == 5 ) {
1661  $numitalics++;
1662  $numbold++;
1663  }
1664  }
1665 
1666  // If there is an odd number of both bold and italics, it is likely
1667  // that one of the bold ones was meant to be an apostrophe followed
1668  // by italics. Which one we cannot know for certain, but it is more
1669  // likely to be one that has a single-letter word before it.
1670  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1671  $firstsingleletterword = -1;
1672  $firstmultiletterword = -1;
1673  $firstspace = -1;
1674  for ( $i = 1; $i < $countarr; $i += 2 ) {
1675  if ( strlen( $arr[$i] ) == 3 ) {
1676  $x1 = substr( $arr[$i - 1], -1 );
1677  $x2 = substr( $arr[$i - 1], -2, 1 );
1678  if ( $x1 === ' ' ) {
1679  if ( $firstspace == -1 ) {
1680  $firstspace = $i;
1681  }
1682  } elseif ( $x2 === ' ' ) {
1683  $firstsingleletterword = $i;
1684  // if $firstsingleletterword is set, we don't
1685  // look at the other options, so we can bail early.
1686  break;
1687  } else {
1688  if ( $firstmultiletterword == -1 ) {
1689  $firstmultiletterword = $i;
1690  }
1691  }
1692  }
1693  }
1694 
1695  // If there is a single-letter word, use it!
1696  if ( $firstsingleletterword > -1 ) {
1697  $arr[$firstsingleletterword] = "''";
1698  $arr[$firstsingleletterword - 1] .= "'";
1699  } elseif ( $firstmultiletterword > -1 ) {
1700  // If not, but there's a multi-letter word, use that one.
1701  $arr[$firstmultiletterword] = "''";
1702  $arr[$firstmultiletterword - 1] .= "'";
1703  } elseif ( $firstspace > -1 ) {
1704  // ... otherwise use the first one that has neither.
1705  // (notice that it is possible for all three to be -1 if, for example,
1706  // there is only one pentuple-apostrophe in the line)
1707  $arr[$firstspace] = "''";
1708  $arr[$firstspace - 1] .= "'";
1709  }
1710  }
1711 
1712  // Now let's actually convert our apostrophic mush to HTML!
1713  $output = '';
1714  $buffer = '';
1715  $state = '';
1716  $i = 0;
1717  foreach ( $arr as $r ) {
1718  if ( ( $i % 2 ) == 0 ) {
1719  if ( $state === 'both' ) {
1720  $buffer .= $r;
1721  } else {
1722  $output .= $r;
1723  }
1724  } else {
1725  $thislen = strlen( $r );
1726  if ( $thislen == 2 ) {
1727  if ( $state === 'i' ) {
1728  $output .= '</i>';
1729  $state = '';
1730  } elseif ( $state === 'bi' ) {
1731  $output .= '</i>';
1732  $state = 'b';
1733  } elseif ( $state === 'ib' ) {
1734  $output .= '</b></i><b>';
1735  $state = 'b';
1736  } elseif ( $state === 'both' ) {
1737  $output .= '<b><i>' . $buffer . '</i>';
1738  $state = 'b';
1739  } else { // $state can be 'b' or ''
1740  $output .= '<i>';
1741  $state .= 'i';
1742  }
1743  } elseif ( $thislen == 3 ) {
1744  if ( $state === 'b' ) {
1745  $output .= '</b>';
1746  $state = '';
1747  } elseif ( $state === 'bi' ) {
1748  $output .= '</i></b><i>';
1749  $state = 'i';
1750  } elseif ( $state === 'ib' ) {
1751  $output .= '</b>';
1752  $state = 'i';
1753  } elseif ( $state === 'both' ) {
1754  $output .= '<i><b>' . $buffer . '</b>';
1755  $state = 'i';
1756  } else { // $state can be 'i' or ''
1757  $output .= '<b>';
1758  $state .= 'b';
1759  }
1760  } elseif ( $thislen == 5 ) {
1761  if ( $state === 'b' ) {
1762  $output .= '</b><i>';
1763  $state = 'i';
1764  } elseif ( $state === 'i' ) {
1765  $output .= '</i><b>';
1766  $state = 'b';
1767  } elseif ( $state === 'bi' ) {
1768  $output .= '</i></b>';
1769  $state = '';
1770  } elseif ( $state === 'ib' ) {
1771  $output .= '</b></i>';
1772  $state = '';
1773  } elseif ( $state === 'both' ) {
1774  $output .= '<i><b>' . $buffer . '</b></i>';
1775  $state = '';
1776  } else { // ($state == '')
1777  $buffer = '';
1778  $state = 'both';
1779  }
1780  }
1781  }
1782  $i++;
1783  }
1784  // Now close all remaining tags. Notice that the order is important.
1785  if ( $state === 'b' || $state === 'ib' ) {
1786  $output .= '</b>';
1787  }
1788  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1789  $output .= '</i>';
1790  }
1791  if ( $state === 'bi' ) {
1792  $output .= '</b>';
1793  }
1794  // There might be lonely ''''', so make sure we have a buffer
1795  if ( $state === 'both' && $buffer ) {
1796  $output .= '<b><i>' . $buffer . '</i></b>';
1797  }
1798  return $output;
1799  }
1800 
1814  public function replaceExternalLinks( $text ) {
1815 
1816  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1817  if ( $bits === false ) {
1818  throw new MWException( "PCRE needs to be compiled with "
1819  . "--enable-unicode-properties in order for MediaWiki to function" );
1820  }
1821  $s = array_shift( $bits );
1822 
1823  $i = 0;
1824  while ( $i < count( $bits ) ) {
1825  $url = $bits[$i++];
1826  $i++; // protocol
1827  $text = $bits[$i++];
1828  $trail = $bits[$i++];
1829 
1830  # The characters '<' and '>' (which were escaped by
1831  # removeHTMLtags()) should not be included in
1832  # URLs, per RFC 2396.
1833  $m2 = [];
1834  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1835  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1836  $url = substr( $url, 0, $m2[0][1] );
1837  }
1838 
1839  # If the link text is an image URL, replace it with an <img> tag
1840  # This happened by accident in the original parser, but some people used it extensively
1841  $img = $this->maybeMakeExternalImage( $text );
1842  if ( $img !== false ) {
1843  $text = $img;
1844  }
1845 
1846  $dtrail = '';
1847 
1848  # Set linktype for CSS - if URL==text, link is essentially free
1849  $linktype = ( $text === $url ) ? 'free' : 'text';
1850 
1851  # No link text, e.g. [http://domain.tld/some.link]
1852  if ( $text == '' ) {
1853  # Autonumber
1854  $langObj = $this->getTargetLanguage();
1855  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1856  $linktype = 'autonumber';
1857  } else {
1858  # Have link text, e.g. [http://domain.tld/some.link text]s
1859  # Check for trail
1860  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1861  }
1862 
1863  $text = $this->getConverterLanguage()->markNoConversion( $text );
1864 
1865  $url = Sanitizer::cleanUrl( $url );
1866 
1867  # Use the encoded URL
1868  # This means that users can paste URLs directly into the text
1869  # Funny characters like ö aren't valid in URLs anyway
1870  # This was changed in August 2004
1871  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1872  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1873 
1874  # Register link in the output object.
1875  # Replace unnecessary URL escape codes with the referenced character
1876  # This prevents spammers from hiding links from the filters
1877  $pasteurized = self::normalizeLinkUrl( $url );
1878  $this->mOutput->addExternalLink( $pasteurized );
1879  }
1880 
1881  return $s;
1882  }
1883 
1893  public static function getExternalLinkRel( $url = false, $title = null ) {
1894  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1895  $ns = $title ? $title->getNamespace() : false;
1896  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1897  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1898  ) {
1899  return 'nofollow';
1900  }
1901  return null;
1902  }
1903 
1914  public function getExternalLinkAttribs( $url ) {
1915  $attribs = [];
1916  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1917 
1918  $target = $this->mOptions->getExternalLinkTarget();
1919  if ( $target ) {
1920  $attribs['target'] = $target;
1921  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1922  // T133507. New windows can navigate parent cross-origin.
1923  // Including noreferrer due to lacking browser
1924  // support of noopener. Eventually noreferrer should be removed.
1925  if ( $rel !== '' ) {
1926  $rel .= ' ';
1927  }
1928  $rel .= 'noreferrer noopener';
1929  }
1930  }
1931  $attribs['rel'] = $rel;
1932  return $attribs;
1933  }
1934 
1942  public static function replaceUnusualEscapes( $url ) {
1943  wfDeprecated( __METHOD__, '1.24' );
1944  return self::normalizeLinkUrl( $url );
1945  }
1946 
1956  public static function normalizeLinkUrl( $url ) {
1957  # First, make sure unsafe characters are encoded
1958  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1959  function ( $m ) {
1960  return rawurlencode( $m[0] );
1961  },
1962  $url
1963  );
1964 
1965  $ret = '';
1966  $end = strlen( $url );
1967 
1968  # Fragment part - 'fragment'
1969  $start = strpos( $url, '#' );
1970  if ( $start !== false && $start < $end ) {
1971  $ret = self::normalizeUrlComponent(
1972  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1973  $end = $start;
1974  }
1975 
1976  # Query part - 'query' minus &=+;
1977  $start = strpos( $url, '?' );
1978  if ( $start !== false && $start < $end ) {
1979  $ret = self::normalizeUrlComponent(
1980  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
1981  $end = $start;
1982  }
1983 
1984  # Scheme and path part - 'pchar'
1985  # (we assume no userinfo or encoded colons in the host)
1986  $ret = self::normalizeUrlComponent(
1987  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
1988 
1989  return $ret;
1990  }
1991 
1992  private static function normalizeUrlComponent( $component, $unsafe ) {
1993  $callback = function ( $matches ) use ( $unsafe ) {
1994  $char = urldecode( $matches[0] );
1995  $ord = ord( $char );
1996  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
1997  # Unescape it
1998  return $char;
1999  } else {
2000  # Leave it escaped, but use uppercase for a-f
2001  return strtoupper( $matches[0] );
2002  }
2003  };
2004  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2005  }
2006 
2015  private function maybeMakeExternalImage( $url ) {
2016  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2017  $imagesexception = !empty( $imagesfrom );
2018  $text = false;
2019  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2020  if ( $imagesexception && is_array( $imagesfrom ) ) {
2021  $imagematch = false;
2022  foreach ( $imagesfrom as $match ) {
2023  if ( strpos( $url, $match ) === 0 ) {
2024  $imagematch = true;
2025  break;
2026  }
2027  }
2028  } elseif ( $imagesexception ) {
2029  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2030  } else {
2031  $imagematch = false;
2032  }
2033 
2034  if ( $this->mOptions->getAllowExternalImages()
2035  || ( $imagesexception && $imagematch )
2036  ) {
2037  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2038  # Image found
2039  $text = Linker::makeExternalImage( $url );
2040  }
2041  }
2042  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2043  && preg_match( self::EXT_IMAGE_REGEX, $url )
2044  ) {
2045  $whitelist = explode(
2046  "\n",
2047  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2048  );
2049 
2050  foreach ( $whitelist as $entry ) {
2051  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2052  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2053  continue;
2054  }
2055  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2056  # Image matches a whitelist entry
2057  $text = Linker::makeExternalImage( $url );
2058  break;
2059  }
2060  }
2061  }
2062  return $text;
2063  }
2064 
2074  public function replaceInternalLinks( $s ) {
2075  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2076  return $s;
2077  }
2078 
2087  public function replaceInternalLinks2( &$s ) {
2089 
2090  static $tc = false, $e1, $e1_img;
2091  # the % is needed to support urlencoded titles as well
2092  if ( !$tc ) {
2093  $tc = Title::legalChars() . '#%';
2094  # Match a link having the form [[namespace:link|alternate]]trail
2095  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2096  # Match cases where there is no "]]", which might still be images
2097  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2098  }
2099 
2100  $holders = new LinkHolderArray( $this );
2101 
2102  # split the entire text string on occurrences of [[
2103  $a = StringUtils::explode( '[[', ' ' . $s );
2104  # get the first element (all text up to first [[), and remove the space we added
2105  $s = $a->current();
2106  $a->next();
2107  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2108  $s = substr( $s, 1 );
2109 
2110  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2111  $e2 = null;
2112  if ( $useLinkPrefixExtension ) {
2113  # Match the end of a line for a word that's not followed by whitespace,
2114  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2116  $charset = $wgContLang->linkPrefixCharset();
2117  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2118  }
2119 
2120  if ( is_null( $this->mTitle ) ) {
2121  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2122  }
2123  $nottalk = !$this->mTitle->isTalkPage();
2124 
2125  if ( $useLinkPrefixExtension ) {
2126  $m = [];
2127  if ( preg_match( $e2, $s, $m ) ) {
2128  $first_prefix = $m[2];
2129  } else {
2130  $first_prefix = false;
2131  }
2132  } else {
2133  $prefix = '';
2134  }
2135 
2136  $useSubpages = $this->areSubpagesAllowed();
2137 
2138  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2139  # Loop for each link
2140  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2141  // @codingStandardsIgnoreEnd
2142 
2143  # Check for excessive memory usage
2144  if ( $holders->isBig() ) {
2145  # Too big
2146  # Do the existence check, replace the link holders and clear the array
2147  $holders->replace( $s );
2148  $holders->clear();
2149  }
2150 
2151  if ( $useLinkPrefixExtension ) {
2152  if ( preg_match( $e2, $s, $m ) ) {
2153  $prefix = $m[2];
2154  $s = $m[1];
2155  } else {
2156  $prefix = '';
2157  }
2158  # first link
2159  if ( $first_prefix ) {
2160  $prefix = $first_prefix;
2161  $first_prefix = false;
2162  }
2163  }
2164 
2165  $might_be_img = false;
2166 
2167  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2168  $text = $m[2];
2169  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2170  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2171  # the real problem is with the $e1 regex
2172  # See bug 1300.
2173  # Still some problems for cases where the ] is meant to be outside punctuation,
2174  # and no image is in sight. See bug 2095.
2175  if ( $text !== ''
2176  && substr( $m[3], 0, 1 ) === ']'
2177  && strpos( $text, '[' ) !== false
2178  ) {
2179  $text .= ']'; # so that replaceExternalLinks($text) works later
2180  $m[3] = substr( $m[3], 1 );
2181  }
2182  # fix up urlencoded title texts
2183  if ( strpos( $m[1], '%' ) !== false ) {
2184  # Should anchors '#' also be rejected?
2185  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2186  }
2187  $trail = $m[3];
2188  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2189  # Invalid, but might be an image with a link in its caption
2190  $might_be_img = true;
2191  $text = $m[2];
2192  if ( strpos( $m[1], '%' ) !== false ) {
2193  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2194  }
2195  $trail = "";
2196  } else { # Invalid form; output directly
2197  $s .= $prefix . '[[' . $line;
2198  continue;
2199  }
2200 
2201  $origLink = $m[1];
2202 
2203  # Don't allow internal links to pages containing
2204  # PROTO: where PROTO is a valid URL protocol; these
2205  # should be external links.
2206  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2207  $s .= $prefix . '[[' . $line;
2208  continue;
2209  }
2210 
2211  # Make subpage if necessary
2212  if ( $useSubpages ) {
2213  $link = $this->maybeDoSubpageLink( $origLink, $text );
2214  } else {
2215  $link = $origLink;
2216  }
2217 
2218  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2219  if ( !$noforce ) {
2220  # Strip off leading ':'
2221  $link = substr( $link, 1 );
2222  }
2223 
2224  $unstrip = $this->mStripState->unstripNoWiki( $link );
2225  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2226  if ( $nt === null ) {
2227  $s .= $prefix . '[[' . $line;
2228  continue;
2229  }
2230 
2231  $ns = $nt->getNamespace();
2232  $iw = $nt->getInterwiki();
2233 
2234  if ( $might_be_img ) { # if this is actually an invalid link
2235  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2236  $found = false;
2237  while ( true ) {
2238  # look at the next 'line' to see if we can close it there
2239  $a->next();
2240  $next_line = $a->current();
2241  if ( $next_line === false || $next_line === null ) {
2242  break;
2243  }
2244  $m = explode( ']]', $next_line, 3 );
2245  if ( count( $m ) == 3 ) {
2246  # the first ]] closes the inner link, the second the image
2247  $found = true;
2248  $text .= "[[{$m[0]}]]{$m[1]}";
2249  $trail = $m[2];
2250  break;
2251  } elseif ( count( $m ) == 2 ) {
2252  # if there's exactly one ]] that's fine, we'll keep looking
2253  $text .= "[[{$m[0]}]]{$m[1]}";
2254  } else {
2255  # if $next_line is invalid too, we need look no further
2256  $text .= '[[' . $next_line;
2257  break;
2258  }
2259  }
2260  if ( !$found ) {
2261  # we couldn't find the end of this imageLink, so output it raw
2262  # but don't ignore what might be perfectly normal links in the text we've examined
2263  $holders->merge( $this->replaceInternalLinks2( $text ) );
2264  $s .= "{$prefix}[[$link|$text";
2265  # note: no $trail, because without an end, there *is* no trail
2266  continue;
2267  }
2268  } else { # it's not an image, so output it raw
2269  $s .= "{$prefix}[[$link|$text";
2270  # note: no $trail, because without an end, there *is* no trail
2271  continue;
2272  }
2273  }
2274 
2275  $wasblank = ( $text == '' );
2276  if ( $wasblank ) {
2277  $text = $link;
2278  } else {
2279  # Bug 4598 madness. Handle the quotes only if they come from the alternate part
2280  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2281  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2282  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2283  $text = $this->doQuotes( $text );
2284  }
2285 
2286  # Link not escaped by : , create the various objects
2287  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2288  # Interwikis
2289  if (
2290  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2291  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2292  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2293  )
2294  ) {
2295  # Bug 24502: filter duplicates
2296  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2297  $this->mLangLinkLanguages[$iw] = true;
2298  $this->mOutput->addLanguageLink( $nt->getFullText() );
2299  }
2300 
2301  $s = rtrim( $s . $prefix );
2302  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2303  continue;
2304  }
2305 
2306  if ( $ns == NS_FILE ) {
2307  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2308  if ( $wasblank ) {
2309  # if no parameters were passed, $text
2310  # becomes something like "File:Foo.png",
2311  # which we don't want to pass on to the
2312  # image generator
2313  $text = '';
2314  } else {
2315  # recursively parse links inside the image caption
2316  # actually, this will parse them in any other parameters, too,
2317  # but it might be hard to fix that, and it doesn't matter ATM
2318  $text = $this->replaceExternalLinks( $text );
2319  $holders->merge( $this->replaceInternalLinks2( $text ) );
2320  }
2321  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2322  $s .= $prefix . $this->armorLinks(
2323  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2324  continue;
2325  }
2326  } elseif ( $ns == NS_CATEGORY ) {
2327  $s = rtrim( $s . "\n" ); # bug 87
2328 
2329  if ( $wasblank ) {
2330  $sortkey = $this->getDefaultSort();
2331  } else {
2332  $sortkey = $text;
2333  }
2334  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2335  $sortkey = str_replace( "\n", '', $sortkey );
2336  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2337  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2338 
2342  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2343 
2344  continue;
2345  }
2346  }
2347 
2348  # Self-link checking. For some languages, variants of the title are checked in
2349  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2350  # for linking to a different variant.
2351  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2352  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2353  continue;
2354  }
2355 
2356  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2357  # @todo FIXME: Should do batch file existence checks, see comment below
2358  if ( $ns == NS_MEDIA ) {
2359  # Give extensions a chance to select the file revision for us
2360  $options = [];
2361  $descQuery = false;
2362  Hooks::run( 'BeforeParserFetchFileAndTitle',
2363  [ $this, $nt, &$options, &$descQuery ] );
2364  # Fetch and register the file (file title may be different via hooks)
2365  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2366  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2367  $s .= $prefix . $this->armorLinks(
2368  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2369  continue;
2370  }
2371 
2372  # Some titles, such as valid special pages or files in foreign repos, should
2373  # be shown as bluelinks even though they're not included in the page table
2374  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2375  # batch file existence checks for NS_FILE and NS_MEDIA
2376  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2377  $this->mOutput->addLink( $nt );
2378  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2379  } else {
2380  # Links will be added to the output link list after checking
2381  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2382  }
2383  }
2384  return $holders;
2385  }
2386 
2400  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2401  list( $inside, $trail ) = Linker::splitTrail( $trail );
2402 
2403  if ( $text == '' ) {
2404  $text = htmlspecialchars( $nt->getPrefixedText() );
2405  }
2406 
2407  $link = $this->getLinkRenderer()->makeKnownLink(
2408  $nt, new HtmlArmor( "$prefix$text$inside" )
2409  );
2410 
2411  return $this->armorLinks( $link ) . $trail;
2412  }
2413 
2424  public function armorLinks( $text ) {
2425  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2426  self::MARKER_PREFIX . "NOPARSE$1", $text );
2427  }
2428 
2433  public function areSubpagesAllowed() {
2434  # Some namespaces don't allow subpages
2435  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2436  }
2437 
2446  public function maybeDoSubpageLink( $target, &$text ) {
2447  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2448  }
2449 
2458  public function doBlockLevels( $text, $linestart ) {
2459  return BlockLevelPass::doBlockLevels( $text, $linestart );
2460  }
2461 
2473  public function getVariableValue( $index, $frame = false ) {
2476 
2477  if ( is_null( $this->mTitle ) ) {
2478  // If no title set, bad things are going to happen
2479  // later. Title should always be set since this
2480  // should only be called in the middle of a parse
2481  // operation (but the unit-tests do funky stuff)
2482  throw new MWException( __METHOD__ . ' Should only be '
2483  . ' called while parsing (no title set)' );
2484  }
2485 
2490  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$this, &$this->mVarCache ] ) ) {
2491  if ( isset( $this->mVarCache[$index] ) ) {
2492  return $this->mVarCache[$index];
2493  }
2494  }
2495 
2496  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2497  Hooks::run( 'ParserGetVariableValueTs', [ &$this, &$ts ] );
2498 
2499  $pageLang = $this->getFunctionLang();
2500 
2501  switch ( $index ) {
2502  case '!':
2503  $value = '|';
2504  break;
2505  case 'currentmonth':
2506  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2507  break;
2508  case 'currentmonth1':
2509  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2510  break;
2511  case 'currentmonthname':
2512  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2513  break;
2514  case 'currentmonthnamegen':
2515  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2516  break;
2517  case 'currentmonthabbrev':
2518  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2519  break;
2520  case 'currentday':
2521  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2522  break;
2523  case 'currentday2':
2524  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2525  break;
2526  case 'localmonth':
2527  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2528  break;
2529  case 'localmonth1':
2530  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2531  break;
2532  case 'localmonthname':
2533  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2534  break;
2535  case 'localmonthnamegen':
2536  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2537  break;
2538  case 'localmonthabbrev':
2539  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2540  break;
2541  case 'localday':
2542  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2543  break;
2544  case 'localday2':
2545  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2546  break;
2547  case 'pagename':
2548  $value = wfEscapeWikiText( $this->mTitle->getText() );
2549  break;
2550  case 'pagenamee':
2551  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2552  break;
2553  case 'fullpagename':
2554  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2555  break;
2556  case 'fullpagenamee':
2557  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2558  break;
2559  case 'subpagename':
2560  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2561  break;
2562  case 'subpagenamee':
2563  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2564  break;
2565  case 'rootpagename':
2566  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2567  break;
2568  case 'rootpagenamee':
2569  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2570  ' ',
2571  '_',
2572  $this->mTitle->getRootText()
2573  ) ) );
2574  break;
2575  case 'basepagename':
2576  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2577  break;
2578  case 'basepagenamee':
2579  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2580  ' ',
2581  '_',
2582  $this->mTitle->getBaseText()
2583  ) ) );
2584  break;
2585  case 'talkpagename':
2586  if ( $this->mTitle->canTalk() ) {
2587  $talkPage = $this->mTitle->getTalkPage();
2588  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2589  } else {
2590  $value = '';
2591  }
2592  break;
2593  case 'talkpagenamee':
2594  if ( $this->mTitle->canTalk() ) {
2595  $talkPage = $this->mTitle->getTalkPage();
2596  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2597  } else {
2598  $value = '';
2599  }
2600  break;
2601  case 'subjectpagename':
2602  $subjPage = $this->mTitle->getSubjectPage();
2603  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2604  break;
2605  case 'subjectpagenamee':
2606  $subjPage = $this->mTitle->getSubjectPage();
2607  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2608  break;
2609  case 'pageid': // requested in bug 23427
2610  $pageid = $this->getTitle()->getArticleID();
2611  if ( $pageid == 0 ) {
2612  # 0 means the page doesn't exist in the database,
2613  # which means the user is previewing a new page.
2614  # The vary-revision flag must be set, because the magic word
2615  # will have a different value once the page is saved.
2616  $this->mOutput->setFlag( 'vary-revision' );
2617  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2618  }
2619  $value = $pageid ? $pageid : null;
2620  break;
2621  case 'revisionid':
2622  # Let the edit saving system know we should parse the page
2623  # *after* a revision ID has been assigned.
2624  $this->mOutput->setFlag( 'vary-revision-id' );
2625  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2626  $value = $this->mRevisionId;
2627  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2628  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2629  $this->mOutput->setSpeculativeRevIdUsed( $value );
2630  }
2631  break;
2632  case 'revisionday':
2633  # Let the edit saving system know we should parse the page
2634  # *after* a revision ID has been assigned. This is for null edits.
2635  $this->mOutput->setFlag( 'vary-revision' );
2636  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2637  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2638  break;
2639  case 'revisionday2':
2640  # Let the edit saving system know we should parse the page
2641  # *after* a revision ID has been assigned. This is for null edits.
2642  $this->mOutput->setFlag( 'vary-revision' );
2643  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2644  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2645  break;
2646  case 'revisionmonth':
2647  # Let the edit saving system know we should parse the page
2648  # *after* a revision ID has been assigned. This is for null edits.
2649  $this->mOutput->setFlag( 'vary-revision' );
2650  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2651  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2652  break;
2653  case 'revisionmonth1':
2654  # Let the edit saving system know we should parse the page
2655  # *after* a revision ID has been assigned. This is for null edits.
2656  $this->mOutput->setFlag( 'vary-revision' );
2657  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2658  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2659  break;
2660  case 'revisionyear':
2661  # Let the edit saving system know we should parse the page
2662  # *after* a revision ID has been assigned. This is for null edits.
2663  $this->mOutput->setFlag( 'vary-revision' );
2664  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2665  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2666  break;
2667  case 'revisiontimestamp':
2668  # Let the edit saving system know we should parse the page
2669  # *after* a revision ID has been assigned. This is for null edits.
2670  $this->mOutput->setFlag( 'vary-revision' );
2671  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2672  $value = $this->getRevisionTimestamp();
2673  break;
2674  case 'revisionuser':
2675  # Let the edit saving system know we should parse the page
2676  # *after* a revision ID has been assigned for null edits.
2677  $this->mOutput->setFlag( 'vary-user' );
2678  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2679  $value = $this->getRevisionUser();
2680  break;
2681  case 'revisionsize':
2682  $value = $this->getRevisionSize();
2683  break;
2684  case 'namespace':
2685  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2686  break;
2687  case 'namespacee':
2688  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2689  break;
2690  case 'namespacenumber':
2691  $value = $this->mTitle->getNamespace();
2692  break;
2693  case 'talkspace':
2694  $value = $this->mTitle->canTalk()
2695  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2696  : '';
2697  break;
2698  case 'talkspacee':
2699  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2700  break;
2701  case 'subjectspace':
2702  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2703  break;
2704  case 'subjectspacee':
2705  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2706  break;
2707  case 'currentdayname':
2708  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2709  break;
2710  case 'currentyear':
2711  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2712  break;
2713  case 'currenttime':
2714  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2715  break;
2716  case 'currenthour':
2717  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2718  break;
2719  case 'currentweek':
2720  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2721  # int to remove the padding
2722  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2723  break;
2724  case 'currentdow':
2725  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2726  break;
2727  case 'localdayname':
2728  $value = $pageLang->getWeekdayName(
2729  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2730  );
2731  break;
2732  case 'localyear':
2733  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2734  break;
2735  case 'localtime':
2736  $value = $pageLang->time(
2737  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2738  false,
2739  false
2740  );
2741  break;
2742  case 'localhour':
2743  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2744  break;
2745  case 'localweek':
2746  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2747  # int to remove the padding
2748  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2749  break;
2750  case 'localdow':
2751  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2752  break;
2753  case 'numberofarticles':
2754  $value = $pageLang->formatNum( SiteStats::articles() );
2755  break;
2756  case 'numberoffiles':
2757  $value = $pageLang->formatNum( SiteStats::images() );
2758  break;
2759  case 'numberofusers':
2760  $value = $pageLang->formatNum( SiteStats::users() );
2761  break;
2762  case 'numberofactiveusers':
2763  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2764  break;
2765  case 'numberofpages':
2766  $value = $pageLang->formatNum( SiteStats::pages() );
2767  break;
2768  case 'numberofadmins':
2769  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2770  break;
2771  case 'numberofedits':
2772  $value = $pageLang->formatNum( SiteStats::edits() );
2773  break;
2774  case 'currenttimestamp':
2775  $value = wfTimestamp( TS_MW, $ts );
2776  break;
2777  case 'localtimestamp':
2778  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2779  break;
2780  case 'currentversion':
2782  break;
2783  case 'articlepath':
2784  return $wgArticlePath;
2785  case 'sitename':
2786  return $wgSitename;
2787  case 'server':
2788  return $wgServer;
2789  case 'servername':
2790  return $wgServerName;
2791  case 'scriptpath':
2792  return $wgScriptPath;
2793  case 'stylepath':
2794  return $wgStylePath;
2795  case 'directionmark':
2796  return $pageLang->getDirMark();
2797  case 'contentlanguage':
2799  return $wgLanguageCode;
2800  case 'cascadingsources':
2802  break;
2803  default:
2804  $ret = null;
2805  Hooks::run(
2806  'ParserGetVariableValueSwitch',
2807  [ &$this, &$this->mVarCache, &$index, &$ret, &$frame ]
2808  );
2809 
2810  return $ret;
2811  }
2812 
2813  if ( $index ) {
2814  $this->mVarCache[$index] = $value;
2815  }
2816 
2817  return $value;
2818  }
2819 
2825  public function initialiseVariables() {
2826  $variableIDs = MagicWord::getVariableIDs();
2827  $substIDs = MagicWord::getSubstIDs();
2828 
2829  $this->mVariables = new MagicWordArray( $variableIDs );
2830  $this->mSubstWords = new MagicWordArray( $substIDs );
2831  }
2832 
2855  public function preprocessToDom( $text, $flags = 0 ) {
2856  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2857  return $dom;
2858  }
2859 
2867  public static function splitWhitespace( $s ) {
2868  $ltrimmed = ltrim( $s );
2869  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2870  $trimmed = rtrim( $ltrimmed );
2871  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2872  if ( $diff > 0 ) {
2873  $w2 = substr( $ltrimmed, -$diff );
2874  } else {
2875  $w2 = '';
2876  }
2877  return [ $w1, $trimmed, $w2 ];
2878  }
2879 
2900  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2901  # Is there any text? Also, Prevent too big inclusions!
2902  $textSize = strlen( $text );
2903  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2904  return $text;
2905  }
2906 
2907  if ( $frame === false ) {
2908  $frame = $this->getPreprocessor()->newFrame();
2909  } elseif ( !( $frame instanceof PPFrame ) ) {
2910  wfDebug( __METHOD__ . " called using plain parameters instead of "
2911  . "a PPFrame instance. Creating custom frame.\n" );
2912  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2913  }
2914 
2915  $dom = $this->preprocessToDom( $text );
2916  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2917  $text = $frame->expand( $dom, $flags );
2918 
2919  return $text;
2920  }
2921 
2929  public static function createAssocArgs( $args ) {
2930  $assocArgs = [];
2931  $index = 1;
2932  foreach ( $args as $arg ) {
2933  $eqpos = strpos( $arg, '=' );
2934  if ( $eqpos === false ) {
2935  $assocArgs[$index++] = $arg;
2936  } else {
2937  $name = trim( substr( $arg, 0, $eqpos ) );
2938  $value = trim( substr( $arg, $eqpos + 1 ) );
2939  if ( $value === false ) {
2940  $value = '';
2941  }
2942  if ( $name !== false ) {
2943  $assocArgs[$name] = $value;
2944  }
2945  }
2946  }
2947 
2948  return $assocArgs;
2949  }
2950 
2977  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2978  # does no harm if $current and $max are present but are unnecessary for the message
2979  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2980  # only during preview, and that would split the parser cache unnecessarily.
2981  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
2982  ->text();
2983  $this->mOutput->addWarning( $warning );
2984  $this->addTrackingCategory( "$limitationType-category" );
2985  }
2986 
2999  public function braceSubstitution( $piece, $frame ) {
3000 
3001  // Flags
3002 
3003  // $text has been filled
3004  $found = false;
3005  // wiki markup in $text should be escaped
3006  $nowiki = false;
3007  // $text is HTML, armour it against wikitext transformation
3008  $isHTML = false;
3009  // Force interwiki transclusion to be done in raw mode not rendered
3010  $forceRawInterwiki = false;
3011  // $text is a DOM node needing expansion in a child frame
3012  $isChildObj = false;
3013  // $text is a DOM node needing expansion in the current frame
3014  $isLocalObj = false;
3015 
3016  # Title object, where $text came from
3017  $title = false;
3018 
3019  # $part1 is the bit before the first |, and must contain only title characters.
3020  # Various prefixes will be stripped from it later.
3021  $titleWithSpaces = $frame->expand( $piece['title'] );
3022  $part1 = trim( $titleWithSpaces );
3023  $titleText = false;
3024 
3025  # Original title text preserved for various purposes
3026  $originalTitle = $part1;
3027 
3028  # $args is a list of argument nodes, starting from index 0, not including $part1
3029  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3030  # below won't work b/c this $args isn't an object
3031  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3032 
3033  $profileSection = null; // profile templates
3034 
3035  # SUBST
3036  if ( !$found ) {
3037  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3038 
3039  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3040  # Decide whether to expand template or keep wikitext as-is.
3041  if ( $this->ot['wiki'] ) {
3042  if ( $substMatch === false ) {
3043  $literal = true; # literal when in PST with no prefix
3044  } else {
3045  $literal = false; # expand when in PST with subst: or safesubst:
3046  }
3047  } else {
3048  if ( $substMatch == 'subst' ) {
3049  $literal = true; # literal when not in PST with plain subst:
3050  } else {
3051  $literal = false; # expand when not in PST with safesubst: or no prefix
3052  }
3053  }
3054  if ( $literal ) {
3055  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3056  $isLocalObj = true;
3057  $found = true;
3058  }
3059  }
3060 
3061  # Variables
3062  if ( !$found && $args->getLength() == 0 ) {
3063  $id = $this->mVariables->matchStartToEnd( $part1 );
3064  if ( $id !== false ) {
3065  $text = $this->getVariableValue( $id, $frame );
3066  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3067  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3068  }
3069  $found = true;
3070  }
3071  }
3072 
3073  # MSG, MSGNW and RAW
3074  if ( !$found ) {
3075  # Check for MSGNW:
3076  $mwMsgnw = MagicWord::get( 'msgnw' );
3077  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3078  $nowiki = true;
3079  } else {
3080  # Remove obsolete MSG:
3081  $mwMsg = MagicWord::get( 'msg' );
3082  $mwMsg->matchStartAndRemove( $part1 );
3083  }
3084 
3085  # Check for RAW:
3086  $mwRaw = MagicWord::get( 'raw' );
3087  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3088  $forceRawInterwiki = true;
3089  }
3090  }
3091 
3092  # Parser functions
3093  if ( !$found ) {
3094  $colonPos = strpos( $part1, ':' );
3095  if ( $colonPos !== false ) {
3096  $func = substr( $part1, 0, $colonPos );
3097  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3098  $argsLength = $args->getLength();
3099  for ( $i = 0; $i < $argsLength; $i++ ) {
3100  $funcArgs[] = $args->item( $i );
3101  }
3102  try {
3103  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3104  } catch ( Exception $ex ) {
3105  throw $ex;
3106  }
3107 
3108  # The interface for parser functions allows for extracting
3109  # flags into the local scope. Extract any forwarded flags
3110  # here.
3111  extract( $result );
3112  }
3113  }
3114 
3115  # Finish mangling title and then check for loops.
3116  # Set $title to a Title object and $titleText to the PDBK
3117  if ( !$found ) {
3118  $ns = NS_TEMPLATE;
3119  # Split the title into page and subpage
3120  $subpage = '';
3121  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3122  if ( $part1 !== $relative ) {
3123  $part1 = $relative;
3124  $ns = $this->mTitle->getNamespace();
3125  }
3126  $title = Title::newFromText( $part1, $ns );
3127  if ( $title ) {
3128  $titleText = $title->getPrefixedText();
3129  # Check for language variants if the template is not found
3130  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3131  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3132  }
3133  # Do recursion depth check
3134  $limit = $this->mOptions->getMaxTemplateDepth();
3135  if ( $frame->depth >= $limit ) {
3136  $found = true;
3137  $text = '<span class="error">'
3138  . wfMessage( 'parser-template-recursion-depth-warning' )
3139  ->numParams( $limit )->inContentLanguage()->text()
3140  . '</span>';
3141  }
3142  }
3143  }
3144 
3145  # Load from database
3146  if ( !$found && $title ) {
3147  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3148  if ( !$title->isExternal() ) {
3149  if ( $title->isSpecialPage()
3150  && $this->mOptions->getAllowSpecialInclusion()
3151  && $this->ot['html']
3152  ) {
3153  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3154  // Pass the template arguments as URL parameters.
3155  // "uselang" will have no effect since the Language object
3156  // is forced to the one defined in ParserOptions.
3157  $pageArgs = [];
3158  $argsLength = $args->getLength();
3159  for ( $i = 0; $i < $argsLength; $i++ ) {
3160  $bits = $args->item( $i )->splitArg();
3161  if ( strval( $bits['index'] ) === '' ) {
3162  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3163  $value = trim( $frame->expand( $bits['value'] ) );
3164  $pageArgs[$name] = $value;
3165  }
3166  }
3167 
3168  // Create a new context to execute the special page
3169  $context = new RequestContext;
3170  $context->setTitle( $title );
3171  $context->setRequest( new FauxRequest( $pageArgs ) );
3172  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3173  $context->setUser( $this->getUser() );
3174  } else {
3175  // If this page is cached, then we better not be per user.
3176  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3177  }
3178  $context->setLanguage( $this->mOptions->getUserLangObj() );
3180  $title, $context, $this->getLinkRenderer() );
3181  if ( $ret ) {
3182  $text = $context->getOutput()->getHTML();
3183  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3184  $found = true;
3185  $isHTML = true;
3186  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3187  $this->mOutput->updateRuntimeAdaptiveExpiry(
3188  $specialPage->maxIncludeCacheTime()
3189  );
3190  }
3191  }
3192  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3193  $found = false; # access denied
3194  wfDebug( __METHOD__ . ": template inclusion denied for " .
3195  $title->getPrefixedDBkey() . "\n" );
3196  } else {
3197  list( $text, $title ) = $this->getTemplateDom( $title );
3198  if ( $text !== false ) {
3199  $found = true;
3200  $isChildObj = true;
3201  }
3202  }
3203 
3204  # If the title is valid but undisplayable, make a link to it
3205  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3206  $text = "[[:$titleText]]";
3207  $found = true;
3208  }
3209  } elseif ( $title->isTrans() ) {
3210  # Interwiki transclusion
3211  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3212  $text = $this->interwikiTransclude( $title, 'render' );
3213  $isHTML = true;
3214  } else {
3215  $text = $this->interwikiTransclude( $title, 'raw' );
3216  # Preprocess it like a template
3217  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3218  $isChildObj = true;
3219  }
3220  $found = true;
3221  }
3222 
3223  # Do infinite loop check
3224  # This has to be done after redirect resolution to avoid infinite loops via redirects
3225  if ( !$frame->loopCheck( $title ) ) {
3226  $found = true;
3227  $text = '<span class="error">'
3228  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3229  . '</span>';
3230  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3231  }
3232  }
3233 
3234  # If we haven't found text to substitute by now, we're done
3235  # Recover the source wikitext and return it
3236  if ( !$found ) {
3237  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3238  if ( $profileSection ) {
3239  $this->mProfiler->scopedProfileOut( $profileSection );
3240  }
3241  return [ 'object' => $text ];
3242  }
3243 
3244  # Expand DOM-style return values in a child frame
3245  if ( $isChildObj ) {
3246  # Clean up argument array
3247  $newFrame = $frame->newChild( $args, $title );
3248 
3249  if ( $nowiki ) {
3250  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3251  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3252  # Expansion is eligible for the empty-frame cache
3253  $text = $newFrame->cachedExpand( $titleText, $text );
3254  } else {
3255  # Uncached expansion
3256  $text = $newFrame->expand( $text );
3257  }
3258  }
3259  if ( $isLocalObj && $nowiki ) {
3260  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3261  $isLocalObj = false;
3262  }
3263 
3264  if ( $profileSection ) {
3265  $this->mProfiler->scopedProfileOut( $profileSection );
3266  }
3267 
3268  # Replace raw HTML by a placeholder
3269  if ( $isHTML ) {
3270  $text = $this->insertStripItem( $text );
3271  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3272  # Escape nowiki-style return values
3273  $text = wfEscapeWikiText( $text );
3274  } elseif ( is_string( $text )
3275  && !$piece['lineStart']
3276  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3277  ) {
3278  # Bug 529: if the template begins with a table or block-level
3279  # element, it should be treated as beginning a new line.
3280  # This behavior is somewhat controversial.
3281  $text = "\n" . $text;
3282  }
3283 
3284  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3285  # Error, oversize inclusion
3286  if ( $titleText !== false ) {
3287  # Make a working, properly escaped link if possible (bug 23588)
3288  $text = "[[:$titleText]]";
3289  } else {
3290  # This will probably not be a working link, but at least it may
3291  # provide some hint of where the problem is
3292  preg_replace( '/^:/', '', $originalTitle );
3293  $text = "[[:$originalTitle]]";
3294  }
3295  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3296  . 'post-expand include size too large -->' );
3297  $this->limitationWarn( 'post-expand-template-inclusion' );
3298  }
3299 
3300  if ( $isLocalObj ) {
3301  $ret = [ 'object' => $text ];
3302  } else {
3303  $ret = [ 'text' => $text ];
3304  }
3305 
3306  return $ret;
3307  }
3308 
3328  public function callParserFunction( $frame, $function, array $args = [] ) {
3330 
3331  # Case sensitive functions
3332  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3333  $function = $this->mFunctionSynonyms[1][$function];
3334  } else {
3335  # Case insensitive functions
3336  $function = $wgContLang->lc( $function );
3337  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3338  $function = $this->mFunctionSynonyms[0][$function];
3339  } else {
3340  return [ 'found' => false ];
3341  }
3342  }
3343 
3344  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3345 
3346  # Workaround for PHP bug 35229 and similar
3347  if ( !is_callable( $callback ) ) {
3348  throw new MWException( "Tag hook for $function is not callable\n" );
3349  }
3350 
3351  $allArgs = [ &$this ];
3352  if ( $flags & self::SFH_OBJECT_ARGS ) {
3353  # Convert arguments to PPNodes and collect for appending to $allArgs
3354  $funcArgs = [];
3355  foreach ( $args as $k => $v ) {
3356  if ( $v instanceof PPNode || $k === 0 ) {
3357  $funcArgs[] = $v;
3358  } else {
3359  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3360  }
3361  }
3362 
3363  # Add a frame parameter, and pass the arguments as an array
3364  $allArgs[] = $frame;
3365  $allArgs[] = $funcArgs;
3366  } else {
3367  # Convert arguments to plain text and append to $allArgs
3368  foreach ( $args as $k => $v ) {
3369  if ( $v instanceof PPNode ) {
3370  $allArgs[] = trim( $frame->expand( $v ) );
3371  } elseif ( is_int( $k ) && $k >= 0 ) {
3372  $allArgs[] = trim( $v );
3373  } else {
3374  $allArgs[] = trim( "$k=$v" );
3375  }
3376  }
3377  }
3378 
3379  $result = call_user_func_array( $callback, $allArgs );
3380 
3381  # The interface for function hooks allows them to return a wikitext
3382  # string or an array containing the string and any flags. This mungs
3383  # things around to match what this method should return.
3384  if ( !is_array( $result ) ) {
3385  $result =[
3386  'found' => true,
3387  'text' => $result,
3388  ];
3389  } else {
3390  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3391  $result['text'] = $result[0];
3392  }
3393  unset( $result[0] );
3394  $result += [
3395  'found' => true,
3396  ];
3397  }
3398 
3399  $noparse = true;
3400  $preprocessFlags = 0;
3401  if ( isset( $result['noparse'] ) ) {
3402  $noparse = $result['noparse'];
3403  }
3404  if ( isset( $result['preprocessFlags'] ) ) {
3405  $preprocessFlags = $result['preprocessFlags'];
3406  }
3407 
3408  if ( !$noparse ) {
3409  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3410  $result['isChildObj'] = true;
3411  }
3412 
3413  return $result;
3414  }
3415 
3424  public function getTemplateDom( $title ) {
3425  $cacheTitle = $title;
3426  $titleText = $title->getPrefixedDBkey();
3427 
3428  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3429  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3430  $title = Title::makeTitle( $ns, $dbk );
3431  $titleText = $title->getPrefixedDBkey();
3432  }
3433  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3434  return [ $this->mTplDomCache[$titleText], $title ];
3435  }
3436 
3437  # Cache miss, go to the database
3438  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3439 
3440  if ( $text === false ) {
3441  $this->mTplDomCache[$titleText] = false;
3442  return [ false, $title ];
3443  }
3444 
3445  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3446  $this->mTplDomCache[$titleText] = $dom;
3447 
3448  if ( !$title->equals( $cacheTitle ) ) {
3449  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3450  [ $title->getNamespace(), $cdb = $title->getDBkey() ];
3451  }
3452 
3453  return [ $dom, $title ];
3454  }
3455 
3468  $cacheKey = $title->getPrefixedDBkey();
3469  if ( !$this->currentRevisionCache ) {
3470  $this->currentRevisionCache = new MapCacheLRU( 100 );
3471  }
3472  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3473  $this->currentRevisionCache->set( $cacheKey,
3474  // Defaults to Parser::statelessFetchRevision()
3475  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3476  );
3477  }
3478  return $this->currentRevisionCache->get( $cacheKey );
3479  }
3480 
3490  public static function statelessFetchRevision( Title $title, $parser = false ) {
3491  $pageId = $title->getArticleID();
3492  $revId = $title->getLatestRevID();
3493 
3495  if ( $rev ) {
3496  $rev->setTitle( $title );
3497  }
3498 
3499  return $rev;
3500  }
3501 
3507  public function fetchTemplateAndTitle( $title ) {
3508  // Defaults to Parser::statelessFetchTemplate()
3509  $templateCb = $this->mOptions->getTemplateCallback();
3510  $stuff = call_user_func( $templateCb, $title, $this );
3511  // We use U+007F DELETE to distinguish strip markers from regular text.
3512  $text = $stuff['text'];
3513  if ( is_string( $stuff['text'] ) ) {
3514  $text = strtr( $text, "\x7f", "?" );
3515  }
3516  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3517  if ( isset( $stuff['deps'] ) ) {
3518  foreach ( $stuff['deps'] as $dep ) {
3519  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3520  if ( $dep['title']->equals( $this->getTitle() ) ) {
3521  // If we transclude ourselves, the final result
3522  // will change based on the new version of the page
3523  $this->mOutput->setFlag( 'vary-revision' );
3524  }
3525  }
3526  }
3527  return [ $text, $finalTitle ];
3528  }
3529 
3535  public function fetchTemplate( $title ) {
3536  return $this->fetchTemplateAndTitle( $title )[0];
3537  }
3538 
3548  public static function statelessFetchTemplate( $title, $parser = false ) {
3549  $text = $skip = false;
3550  $finalTitle = $title;
3551  $deps = [];
3552 
3553  # Loop to fetch the article, with up to 1 redirect
3554  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3555  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3556  // @codingStandardsIgnoreEnd
3557  # Give extensions a chance to select the revision instead
3558  $id = false; # Assume current
3559  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3560  [ $parser, $title, &$skip, &$id ] );
3561 
3562  if ( $skip ) {
3563  $text = false;
3564  $deps[] = [
3565  'title' => $title,
3566  'page_id' => $title->getArticleID(),
3567  'rev_id' => null
3568  ];
3569  break;
3570  }
3571  # Get the revision
3572  if ( $id ) {
3573  $rev = Revision::newFromId( $id );
3574  } elseif ( $parser ) {
3575  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3576  } else {
3578  }
3579  $rev_id = $rev ? $rev->getId() : 0;
3580  # If there is no current revision, there is no page
3581  if ( $id === false && !$rev ) {
3582  $linkCache = LinkCache::singleton();
3583  $linkCache->addBadLinkObj( $title );
3584  }
3585 
3586  $deps[] = [
3587  'title' => $title,
3588  'page_id' => $title->getArticleID(),
3589  'rev_id' => $rev_id ];
3590  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3591  # We fetched a rev from a different title; register it too...
3592  $deps[] = [
3593  'title' => $rev->getTitle(),
3594  'page_id' => $rev->getPage(),
3595  'rev_id' => $rev_id ];
3596  }
3597 
3598  if ( $rev ) {
3599  $content = $rev->getContent();
3600  $text = $content ? $content->getWikitextForTransclusion() : null;
3601 
3602  if ( $text === false || $text === null ) {
3603  $text = false;
3604  break;
3605  }
3606  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3608  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3609  if ( !$message->exists() ) {
3610  $text = false;
3611  break;
3612  }
3613  $content = $message->content();
3614  $text = $message->plain();
3615  } else {
3616  break;
3617  }
3618  if ( !$content ) {
3619  break;
3620  }
3621  # Redirect?
3622  $finalTitle = $title;
3623  $title = $content->getRedirectTarget();
3624  }
3625  return [
3626  'text' => $text,
3627  'finalTitle' => $finalTitle,
3628  'deps' => $deps ];
3629  }
3630 
3638  public function fetchFile( $title, $options = [] ) {
3639  return $this->fetchFileAndTitle( $title, $options )[0];
3640  }
3641 
3649  public function fetchFileAndTitle( $title, $options = [] ) {
3650  $file = $this->fetchFileNoRegister( $title, $options );
3651 
3652  $time = $file ? $file->getTimestamp() : false;
3653  $sha1 = $file ? $file->getSha1() : false;
3654  # Register the file as a dependency...
3655  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3656  if ( $file && !$title->equals( $file->getTitle() ) ) {
3657  # Update fetched file title
3658  $title = $file->getTitle();
3659  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3660  }
3661  return [ $file, $title ];
3662  }
3663 
3674  protected function fetchFileNoRegister( $title, $options = [] ) {
3675  if ( isset( $options['broken'] ) ) {
3676  $file = false; // broken thumbnail forced by hook
3677  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3678  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3679  } else { // get by (name,timestamp)
3680  $file = wfFindFile( $title, $options );
3681  }
3682  return $file;
3683  }
3684 
3693  public function interwikiTransclude( $title, $action ) {
3694  global $wgEnableScaryTranscluding;
3695 
3696  if ( !$wgEnableScaryTranscluding ) {
3697  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3698  }
3699 
3700  $url = $title->getFullURL( [ 'action' => $action ] );
3701 
3702  if ( strlen( $url ) > 255 ) {
3703  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3704  }
3705  return $this->fetchScaryTemplateMaybeFromCache( $url );
3706  }
3707 
3712  public function fetchScaryTemplateMaybeFromCache( $url ) {
3713  global $wgTranscludeCacheExpiry;
3714  $dbr = wfGetDB( DB_REPLICA );
3715  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3716  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3717  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3718  if ( $obj ) {
3719  return $obj->tc_contents;
3720  }
3721 
3722  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3723  $status = $req->execute(); // Status object
3724  if ( $status->isOK() ) {
3725  $text = $req->getContent();
3726  } elseif ( $req->getStatus() != 200 ) {
3727  // Though we failed to fetch the content, this status is useless.
3728  return wfMessage( 'scarytranscludefailed-httpstatus' )
3729  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3730  } else {
3731  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3732  }
3733 
3734  $dbw = wfGetDB( DB_MASTER );
3735  $dbw->replace( 'transcache', [ 'tc_url' ], [
3736  'tc_url' => $url,
3737  'tc_time' => $dbw->timestamp( time() ),
3738  'tc_contents' => $text
3739  ] );
3740  return $text;
3741  }
3742 
3752  public function argSubstitution( $piece, $frame ) {
3753 
3754  $error = false;
3755  $parts = $piece['parts'];
3756  $nameWithSpaces = $frame->expand( $piece['title'] );
3757  $argName = trim( $nameWithSpaces );
3758  $object = false;
3759  $text = $frame->getArgument( $argName );
3760  if ( $text === false && $parts->getLength() > 0
3761  && ( $this->ot['html']
3762  || $this->ot['pre']
3763  || ( $this->ot['wiki'] && $frame->isTemplate() )
3764  )
3765  ) {
3766  # No match in frame, use the supplied default
3767  $object = $parts->item( 0 )->getChildren();
3768  }
3769  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3770  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3771  $this->limitationWarn( 'post-expand-template-argument' );
3772  }
3773 
3774  if ( $text === false && $object === false ) {
3775  # No match anywhere
3776  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3777  }
3778  if ( $error !== false ) {
3779  $text .= $error;
3780  }
3781  if ( $object !== false ) {
3782  $ret = [ 'object' => $object ];
3783  } else {
3784  $ret = [ 'text' => $text ];
3785  }
3786 
3787  return $ret;
3788  }
3789 
3805  public function extensionSubstitution( $params, $frame ) {
3806  static $errorStr = '<span class="error">';
3807  static $errorLen = 20;
3808 
3809  $name = $frame->expand( $params['name'] );
3810  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3811  // Probably expansion depth or node count exceeded. Just punt the
3812  // error up.
3813  return $name;
3814  }
3815 
3816  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3817  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3818  // See above
3819  return $attrText;
3820  }
3821 
3822  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3823  if ( substr( $content, 0, $errorLen ) === $errorStr ) {
3824  // See above
3825  return $content;
3826  }
3827 
3828  $marker = self::MARKER_PREFIX . "-$name-"
3829  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3830 
3831  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3832  ( $this->ot['html'] || $this->ot['pre'] );
3833  if ( $isFunctionTag ) {
3834  $markerType = 'none';
3835  } else {
3836  $markerType = 'general';
3837  }
3838  if ( $this->ot['html'] || $isFunctionTag ) {
3839  $name = strtolower( $name );
3840  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3841  if ( isset( $params['attributes'] ) ) {
3842  $attributes = $attributes + $params['attributes'];
3843  }
3844 
3845  if ( isset( $this->mTagHooks[$name] ) ) {
3846  # Workaround for PHP bug 35229 and similar
3847  if ( !is_callable( $this->mTagHooks[$name] ) ) {
3848  throw new MWException( "Tag hook for $name is not callable\n" );
3849  }
3850  $output = call_user_func_array( $this->mTagHooks[$name],
3851  [ $content, $attributes, $this, $frame ] );
3852  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3853  list( $callback, ) = $this->mFunctionTagHooks[$name];
3854  if ( !is_callable( $callback ) ) {
3855  throw new MWException( "Tag hook for $name is not callable\n" );
3856  }
3857 
3858  $output = call_user_func_array( $callback, [ &$this, $frame, $content, $attributes ] );
3859  } else {
3860  $output = '<span class="error">Invalid tag extension name: ' .
3861  htmlspecialchars( $name ) . '</span>';
3862  }
3863 
3864  if ( is_array( $output ) ) {
3865  # Extract flags to local scope (to override $markerType)
3866  $flags = $output;
3867  $output = $flags[0];
3868  unset( $flags[0] );
3869  extract( $flags );
3870  }
3871  } else {
3872  if ( is_null( $attrText ) ) {
3873  $attrText = '';
3874  }
3875  if ( isset( $params['attributes'] ) ) {
3876  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3877  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3878  htmlspecialchars( $attrValue ) . '"';
3879  }
3880  }
3881  if ( $content === null ) {
3882  $output = "<$name$attrText/>";
3883  } else {
3884  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3885  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3886  // See above
3887  return $close;
3888  }
3889  $output = "<$name$attrText>$content$close";
3890  }
3891  }
3892 
3893  if ( $markerType === 'none' ) {
3894  return $output;
3895  } elseif ( $markerType === 'nowiki' ) {
3896  $this->mStripState->addNoWiki( $marker, $output );
3897  } elseif ( $markerType === 'general' ) {
3898  $this->mStripState->addGeneral( $marker, $output );
3899  } else {
3900  throw new MWException( __METHOD__ . ': invalid marker type' );
3901  }
3902  return $marker;
3903  }
3904 
3912  public function incrementIncludeSize( $type, $size ) {
3913  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3914  return false;
3915  } else {
3916  $this->mIncludeSizes[$type] += $size;
3917  return true;
3918  }
3919  }
3920 
3927  $this->mExpensiveFunctionCount++;
3928  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3929  }
3930 
3939  public function doDoubleUnderscore( $text ) {
3940 
3941  # The position of __TOC__ needs to be recorded
3942  $mw = MagicWord::get( 'toc' );
3943  if ( $mw->match( $text ) ) {
3944  $this->mShowToc = true;
3945  $this->mForceTocPosition = true;
3946 
3947  # Set a placeholder. At the end we'll fill it in with the TOC.
3948  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3949 
3950  # Only keep the first one.
3951  $text = $mw->replace( '', $text );
3952  }
3953 
3954  # Now match and remove the rest of them
3956  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3957 
3958  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3959  $this->mOutput->mNoGallery = true;
3960  }
3961  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3962  $this->mShowToc = false;
3963  }
3964  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3965  && $this->mTitle->getNamespace() == NS_CATEGORY
3966  ) {
3967  $this->addTrackingCategory( 'hidden-category-category' );
3968  }
3969  # (bug 8068) Allow control over whether robots index a page.
3970  # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here! This
3971  # is not desirable, the last one on the page should win.
3972  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3973  $this->mOutput->setIndexPolicy( 'noindex' );
3974  $this->addTrackingCategory( 'noindex-category' );
3975  }
3976  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
3977  $this->mOutput->setIndexPolicy( 'index' );
3978  $this->addTrackingCategory( 'index-category' );
3979  }
3980 
3981  # Cache all double underscores in the database
3982  foreach ( $this->mDoubleUnderscores as $key => $val ) {
3983  $this->mOutput->setProperty( $key, '' );
3984  }
3985 
3986  return $text;
3987  }
3988 
3994  public function addTrackingCategory( $msg ) {
3995  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
3996  }
3997 
4014  public function formatHeadings( $text, $origText, $isMain = true ) {
4015  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4016 
4017  # Inhibit editsection links if requested in the page
4018  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4019  $maybeShowEditLink = $showEditLink = false;
4020  } else {
4021  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4022  $showEditLink = $this->mOptions->getEditSection();
4023  }
4024  if ( $showEditLink ) {
4025  $this->mOutput->setEditSectionTokens( true );
4026  }
4027 
4028  # Get all headlines for numbering them and adding funky stuff like [edit]
4029  # links - this is for later, but we need the number of headlines right now
4030  $matches = [];
4031  $numMatches = preg_match_all(
4032  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4033  $text,
4034  $matches
4035  );
4036 
4037  # if there are fewer than 4 headlines in the article, do not show TOC
4038  # unless it's been explicitly enabled.
4039  $enoughToc = $this->mShowToc &&
4040  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4041 
4042  # Allow user to stipulate that a page should have a "new section"
4043  # link added via __NEWSECTIONLINK__
4044  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4045  $this->mOutput->setNewSection( true );
4046  }
4047 
4048  # Allow user to remove the "new section"
4049  # link via __NONEWSECTIONLINK__
4050  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4051  $this->mOutput->hideNewSection( true );
4052  }
4053 
4054  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4055  # override above conditions and always show TOC above first header
4056  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4057  $this->mShowToc = true;
4058  $enoughToc = true;
4059  }
4060 
4061  # headline counter
4062  $headlineCount = 0;
4063  $numVisible = 0;
4064 
4065  # Ugh .. the TOC should have neat indentation levels which can be
4066  # passed to the skin functions. These are determined here
4067  $toc = '';
4068  $full = '';
4069  $head = [];
4070  $sublevelCount = [];
4071  $levelCount = [];
4072  $level = 0;
4073  $prevlevel = 0;
4074  $toclevel = 0;
4075  $prevtoclevel = 0;
4076  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4077  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4078  $oldType = $this->mOutputType;
4079  $this->setOutputType( self::OT_WIKI );
4080  $frame = $this->getPreprocessor()->newFrame();
4081  $root = $this->preprocessToDom( $origText );
4082  $node = $root->getFirstChild();
4083  $byteOffset = 0;
4084  $tocraw = [];
4085  $refers = [];
4086 
4087  $headlines = $numMatches !== false ? $matches[3] : [];
4088 
4089  foreach ( $headlines as $headline ) {
4090  $isTemplate = false;
4091  $titleText = false;
4092  $sectionIndex = false;
4093  $numbering = '';
4094  $markerMatches = [];
4095  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4096  $serial = $markerMatches[1];
4097  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4098  $isTemplate = ( $titleText != $baseTitleText );
4099  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4100  }
4101 
4102  if ( $toclevel ) {
4103  $prevlevel = $level;
4104  }
4105  $level = $matches[1][$headlineCount];
4106 
4107  if ( $level > $prevlevel ) {
4108  # Increase TOC level
4109  $toclevel++;
4110  $sublevelCount[$toclevel] = 0;
4111  if ( $toclevel < $wgMaxTocLevel ) {
4112  $prevtoclevel = $toclevel;
4113  $toc .= Linker::tocIndent();
4114  $numVisible++;
4115  }
4116  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4117  # Decrease TOC level, find level to jump to
4118 
4119  for ( $i = $toclevel; $i > 0; $i-- ) {
4120  if ( $levelCount[$i] == $level ) {
4121  # Found last matching level
4122  $toclevel = $i;
4123  break;
4124  } elseif ( $levelCount[$i] < $level ) {
4125  # Found first matching level below current level
4126  $toclevel = $i + 1;
4127  break;
4128  }
4129  }
4130  if ( $i == 0 ) {
4131  $toclevel = 1;
4132  }
4133  if ( $toclevel < $wgMaxTocLevel ) {
4134  if ( $prevtoclevel < $wgMaxTocLevel ) {
4135  # Unindent only if the previous toc level was shown :p
4136  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4137  $prevtoclevel = $toclevel;
4138  } else {
4139  $toc .= Linker::tocLineEnd();
4140  }
4141  }
4142  } else {
4143  # No change in level, end TOC line
4144  if ( $toclevel < $wgMaxTocLevel ) {
4145  $toc .= Linker::tocLineEnd();
4146  }
4147  }
4148 
4149  $levelCount[$toclevel] = $level;
4150 
4151  # count number of headlines for each level
4152  $sublevelCount[$toclevel]++;
4153  $dot = 0;
4154  for ( $i = 1; $i <= $toclevel; $i++ ) {
4155  if ( !empty( $sublevelCount[$i] ) ) {
4156  if ( $dot ) {
4157  $numbering .= '.';
4158  }
4159  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4160  $dot = 1;
4161  }
4162  }
4163 
4164  # The safe header is a version of the header text safe to use for links
4165 
4166  # Remove link placeholders by the link text.
4167  # <!--LINK number-->
4168  # turns into
4169  # link text with suffix
4170  # Do this before unstrip since link text can contain strip markers
4171  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4172 
4173  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4174  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4175 
4176  # Strip out HTML (first regex removes any tag not allowed)
4177  # Allowed tags are:
4178  # * <sup> and <sub> (bug 8393)
4179  # * <i> (bug 26375)
4180  # * <b> (r105284)
4181  # * <bdi> (bug 72884)
4182  # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
4183  # * <s> and <strike> (T35715)
4184  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4185  # to allow setting directionality in toc items.
4186  $tocline = preg_replace(
4187  [
4188  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4189  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4190  ],
4191  [ '', '<$1>' ],
4192  $safeHeadline
4193  );
4194 
4195  # Strip '<span></span>', which is the result from the above if
4196  # <span id="foo"></span> is used to produce an additional anchor
4197  # for a section.
4198  $tocline = str_replace( '<span></span>', '', $tocline );
4199 
4200  $tocline = trim( $tocline );
4201 
4202  # For the anchor, strip out HTML-y stuff period
4203  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4204  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4205 
4206  # Save headline for section edit hint before it's escaped
4207  $headlineHint = $safeHeadline;
4208 
4209  if ( $wgExperimentalHtmlIds ) {
4210  # For reverse compatibility, provide an id that's
4211  # HTML4-compatible, like we used to.
4212  # It may be worth noting, academically, that it's possible for
4213  # the legacy anchor to conflict with a non-legacy headline
4214  # anchor on the page. In this case likely the "correct" thing
4215  # would be to either drop the legacy anchors or make sure
4216  # they're numbered first. However, this would require people
4217  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4218  # manually, so let's not bother worrying about it.
4219  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4220  [ 'noninitial', 'legacy' ] );
4221  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4222 
4223  if ( $legacyHeadline == $safeHeadline ) {
4224  # No reason to have both (in fact, we can't)
4225  $legacyHeadline = false;
4226  }
4227  } else {
4228  $legacyHeadline = false;
4229  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4230  'noninitial' );
4231  }
4232 
4233  # HTML names must be case-insensitively unique (bug 10721).
4234  # This does not apply to Unicode characters per
4235  # http://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4236  # @todo FIXME: We may be changing them depending on the current locale.
4237  $arrayKey = strtolower( $safeHeadline );
4238  if ( $legacyHeadline === false ) {
4239  $legacyArrayKey = false;
4240  } else {
4241  $legacyArrayKey = strtolower( $legacyHeadline );
4242  }
4243 
4244  # Create the anchor for linking from the TOC to the section
4245  $anchor = $safeHeadline;
4246  $legacyAnchor = $legacyHeadline;
4247  if ( isset( $refers[$arrayKey] ) ) {
4248  // @codingStandardsIgnoreStart
4249  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4250  // @codingStandardsIgnoreEnd
4251  $anchor .= "_$i";
4252  $refers["${arrayKey}_$i"] = true;
4253  } else {
4254  $refers[$arrayKey] = true;
4255  }
4256  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4257  // @codingStandardsIgnoreStart
4258  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4259  // @codingStandardsIgnoreEnd
4260  $legacyAnchor .= "_$i";
4261  $refers["${legacyArrayKey}_$i"] = true;
4262  } else {
4263  $refers[$legacyArrayKey] = true;
4264  }
4265 
4266  # Don't number the heading if it is the only one (looks silly)
4267  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4268  # the two are different if the line contains a link
4269  $headline = Html::element(
4270  'span',
4271  [ 'class' => 'mw-headline-number' ],
4272  $numbering
4273  ) . ' ' . $headline;
4274  }
4275 
4276  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4277  $toc .= Linker::tocLine( $anchor, $tocline,
4278  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4279  }
4280 
4281  # Add the section to the section tree
4282  # Find the DOM node for this header
4283  $noOffset = ( $isTemplate || $sectionIndex === false );
4284  while ( $node && !$noOffset ) {
4285  if ( $node->getName() === 'h' ) {
4286  $bits = $node->splitHeading();
4287  if ( $bits['i'] == $sectionIndex ) {
4288  break;
4289  }
4290  }
4291  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4292  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4293  $node = $node->getNextSibling();
4294  }
4295  $tocraw[] = [
4296  'toclevel' => $toclevel,
4297  'level' => $level,
4298  'line' => $tocline,
4299  'number' => $numbering,
4300  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4301  'fromtitle' => $titleText,
4302  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4303  'anchor' => $anchor,
4304  ];
4305 
4306  # give headline the correct <h#> tag
4307  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4308  // Output edit section links as markers with styles that can be customized by skins
4309  if ( $isTemplate ) {
4310  # Put a T flag in the section identifier, to indicate to extractSections()
4311  # that sections inside <includeonly> should be counted.
4312  $editsectionPage = $titleText;
4313  $editsectionSection = "T-$sectionIndex";
4314  $editsectionContent = null;
4315  } else {
4316  $editsectionPage = $this->mTitle->getPrefixedText();
4317  $editsectionSection = $sectionIndex;
4318  $editsectionContent = $headlineHint;
4319  }
4320  // We use a bit of pesudo-xml for editsection markers. The
4321  // language converter is run later on. Using a UNIQ style marker
4322  // leads to the converter screwing up the tokens when it
4323  // converts stuff. And trying to insert strip tags fails too. At
4324  // this point all real inputted tags have already been escaped,
4325  // so we don't have to worry about a user trying to input one of
4326  // these markers directly. We use a page and section attribute
4327  // to stop the language converter from converting these
4328  // important bits of data, but put the headline hint inside a
4329  // content block because the language converter is supposed to
4330  // be able to convert that piece of data.
4331  // Gets replaced with html in ParserOutput::getText
4332  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4333  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4334  if ( $editsectionContent !== null ) {
4335  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4336  } else {
4337  $editlink .= '/>';
4338  }
4339  } else {
4340  $editlink = '';
4341  }
4342  $head[$headlineCount] = Linker::makeHeadline( $level,
4343  $matches['attrib'][$headlineCount], $anchor, $headline,
4344  $editlink, $legacyAnchor );
4345 
4346  $headlineCount++;
4347  }
4348 
4349  $this->setOutputType( $oldType );
4350 
4351  # Never ever show TOC if no headers
4352  if ( $numVisible < 1 ) {
4353  $enoughToc = false;
4354  }
4355 
4356  if ( $enoughToc ) {
4357  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4358  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4359  }
4360  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4361  $this->mOutput->setTOCHTML( $toc );
4362  $toc = self::TOC_START . $toc . self::TOC_END;
4363  $this->mOutput->addModules( 'mediawiki.toc' );
4364  }
4365 
4366  if ( $isMain ) {
4367  $this->mOutput->setSections( $tocraw );
4368  }
4369 
4370  # split up and insert constructed headlines
4371  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4372  $i = 0;
4373 
4374  // build an array of document sections
4375  $sections = [];
4376  foreach ( $blocks as $block ) {
4377  // $head is zero-based, sections aren't.
4378  if ( empty( $head[$i - 1] ) ) {
4379  $sections[$i] = $block;
4380  } else {
4381  $sections[$i] = $head[$i - 1] . $block;
4382  }
4383 
4394  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4395 
4396  $i++;
4397  }
4398 
4399  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4400  // append the TOC at the beginning
4401  // Top anchor now in skin
4402  $sections[0] = $sections[0] . $toc . "\n";
4403  }
4404 
4405  $full .= implode( '', $sections );
4406 
4407  if ( $this->mForceTocPosition ) {
4408  return str_replace( '<!--MWTOC-->', $toc, $full );
4409  } else {
4410  return $full;
4411  }
4412  }
4413 
4425  public function preSaveTransform( $text, Title $title, User $user,
4426  ParserOptions $options, $clearState = true
4427  ) {
4428  if ( $clearState ) {
4429  $magicScopeVariable = $this->lock();
4430  }
4431  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4432  $this->setUser( $user );
4433 
4434  // We still normalize line endings for backwards-compatibility
4435  // with other code that just calls PST, but this should already
4436  // be handled in TextContent subclasses
4437  $text = TextContent::normalizeLineEndings( $text );
4438 
4439  if ( $options->getPreSaveTransform() ) {
4440  $text = $this->pstPass2( $text, $user );
4441  }
4442  $text = $this->mStripState->unstripBoth( $text );
4443 
4444  $this->setUser( null ); # Reset
4445 
4446  return $text;
4447  }
4448 
4457  private function pstPass2( $text, $user ) {
4459 
4460  # Note: This is the timestamp saved as hardcoded wikitext to
4461  # the database, we use $wgContLang here in order to give
4462  # everyone the same signature and use the default one rather
4463  # than the one selected in each user's preferences.
4464  # (see also bug 12815)
4465  $ts = $this->mOptions->getTimestamp();
4467  $ts = $timestamp->format( 'YmdHis' );
4468  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4469 
4470  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4471 
4472  # Variable replacement
4473  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4474  $text = $this->replaceVariables( $text );
4475 
4476  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4477  # which may corrupt this parser instance via its wfMessage()->text() call-
4478 
4479  # Signatures
4480  $sigText = $this->getUserSig( $user );
4481  $text = strtr( $text, [
4482  '~~~~~' => $d,
4483  '~~~~' => "$sigText $d",
4484  '~~~' => $sigText
4485  ] );
4486 
4487  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4488  $tc = '[' . Title::legalChars() . ']';
4489  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4490 
4491  // [[ns:page (context)|]]
4492  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4493  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4494  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4495  // [[ns:page (context), context|]] (using either single or double-width comma)
4496  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4497  // [[|page]] (reverse pipe trick: add context from page title)
4498  $p2 = "/\[\[\\|($tc+)]]/";
4499 
4500  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4501  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4502  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4503  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4504 
4505  $t = $this->mTitle->getText();
4506  $m = [];
4507  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4508  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4509  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4510  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4511  } else {
4512  # if there's no context, don't bother duplicating the title
4513  $text = preg_replace( $p2, '[[\\1]]', $text );
4514  }
4515 
4516  return $text;
4517  }
4518 
4533  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4534  global $wgMaxSigChars;
4535 
4536  $username = $user->getName();
4537 
4538  # If not given, retrieve from the user object.
4539  if ( $nickname === false ) {
4540  $nickname = $user->getOption( 'nickname' );
4541  }
4542 
4543  if ( is_null( $fancySig ) ) {
4544  $fancySig = $user->getBoolOption( 'fancysig' );
4545  }
4546 
4547  $nickname = $nickname == null ? $username : $nickname;
4548 
4549  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4550  $nickname = $username;
4551  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4552  } elseif ( $fancySig !== false ) {
4553  # Sig. might contain markup; validate this
4554  if ( $this->validateSig( $nickname ) !== false ) {
4555  # Validated; clean up (if needed) and return it
4556  return $this->cleanSig( $nickname, true );
4557  } else {
4558  # Failed to validate; fall back to the default
4559  $nickname = $username;
4560  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4561  }
4562  }
4563 
4564  # Make sure nickname doesnt get a sig in a sig
4565  $nickname = self::cleanSigInSig( $nickname );
4566 
4567  # If we're still here, make it a link to the user page
4568  $userText = wfEscapeWikiText( $username );
4569  $nickText = wfEscapeWikiText( $nickname );
4570  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4571 
4572  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4573  ->title( $this->getTitle() )->text();
4574  }
4575 
4582  public function validateSig( $text ) {
4583  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4584  }
4585 
4596  public function cleanSig( $text, $parsing = false ) {
4597  if ( !$parsing ) {
4598  global $wgTitle;
4599  $magicScopeVariable = $this->lock();
4600  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4601  }
4602 
4603  # Option to disable this feature
4604  if ( !$this->mOptions->getCleanSignatures() ) {
4605  return $text;
4606  }
4607 
4608  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4609  # => Move this logic to braceSubstitution()
4610  $substWord = MagicWord::get( 'subst' );
4611  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4612  $substText = '{{' . $substWord->getSynonym( 0 );
4613 
4614  $text = preg_replace( $substRegex, $substText, $text );
4615  $text = self::cleanSigInSig( $text );
4616  $dom = $this->preprocessToDom( $text );
4617  $frame = $this->getPreprocessor()->newFrame();
4618  $text = $frame->expand( $dom );
4619 
4620  if ( !$parsing ) {
4621  $text = $this->mStripState->unstripBoth( $text );
4622  }
4623 
4624  return $text;
4625  }
4626 
4633  public static function cleanSigInSig( $text ) {
4634  $text = preg_replace( '/~{3,5}/', '', $text );
4635  return $text;
4636  }
4637 
4648  $outputType, $clearState = true
4649  ) {
4650  $this->startParse( $title, $options, $outputType, $clearState );
4651  }
4652 
4659  private function startParse( Title $title = null, ParserOptions $options,
4660  $outputType, $clearState = true
4661  ) {
4662  $this->setTitle( $title );
4663  $this->mOptions = $options;
4664  $this->setOutputType( $outputType );
4665  if ( $clearState ) {
4666  $this->clearState();
4667  }
4668  }
4669 
4678  public function transformMsg( $text, $options, $title = null ) {
4679  static $executing = false;
4680 
4681  # Guard against infinite recursion
4682  if ( $executing ) {
4683  return $text;
4684  }
4685  $executing = true;
4686 
4687  if ( !$title ) {
4688  global $wgTitle;
4689  $title = $wgTitle;
4690  }
4691 
4692  $text = $this->preprocess( $text, $title, $options );
4693 
4694  $executing = false;
4695  return $text;
4696  }
4697 
4722  public function setHook( $tag, $callback ) {
4723  $tag = strtolower( $tag );
4724  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4725  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4726  }
4727  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4728  $this->mTagHooks[$tag] = $callback;
4729  if ( !in_array( $tag, $this->mStripList ) ) {
4730  $this->mStripList[] = $tag;
4731  }
4732 
4733  return $oldVal;
4734  }
4735 
4753  public function setTransparentTagHook( $tag, $callback ) {
4754  $tag = strtolower( $tag );
4755  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4756  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4757  }
4758  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4759  $this->mTransparentTagHooks[$tag] = $callback;
4760 
4761  return $oldVal;
4762  }
4763 
4767  public function clearTagHooks() {
4768  $this->mTagHooks = [];
4769  $this->mFunctionTagHooks = [];
4770  $this->mStripList = $this->mDefaultStripList;
4771  }
4772 
4816  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4818 
4819  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4820  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4821 
4822  # Add to function cache
4823  $mw = MagicWord::get( $id );
4824  if ( !$mw ) {
4825  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4826  }
4827 
4828  $synonyms = $mw->getSynonyms();
4829  $sensitive = intval( $mw->isCaseSensitive() );
4830 
4831  foreach ( $synonyms as $syn ) {
4832  # Case
4833  if ( !$sensitive ) {
4834  $syn = $wgContLang->lc( $syn );
4835  }
4836  # Add leading hash
4837  if ( !( $flags & self::SFH_NO_HASH ) ) {
4838  $syn = '#' . $syn;
4839  }
4840  # Remove trailing colon
4841  if ( substr( $syn, -1, 1 ) === ':' ) {
4842  $syn = substr( $syn, 0, -1 );
4843  }
4844  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4845  }
4846  return $oldVal;
4847  }
4848 
4854  public function getFunctionHooks() {
4855  return array_keys( $this->mFunctionHooks );
4856  }
4857 
4868  public function setFunctionTagHook( $tag, $callback, $flags ) {
4869  $tag = strtolower( $tag );
4870  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4871  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4872  }
4873  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4874  $this->mFunctionTagHooks[$tag] : null;
4875  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4876 
4877  if ( !in_array( $tag, $this->mStripList ) ) {
4878  $this->mStripList[] = $tag;
4879  }
4880 
4881  return $old;
4882  }
4883 
4891  public function replaceLinkHolders( &$text, $options = 0 ) {
4892  $this->mLinkHolders->replace( $text );
4893  }
4894 
4902  public function replaceLinkHoldersText( $text ) {
4903  return $this->mLinkHolders->replaceText( $text );
4904  }
4905 
4919  public function renderImageGallery( $text, $params ) {
4920 
4921  $mode = false;
4922  if ( isset( $params['mode'] ) ) {
4923  $mode = $params['mode'];
4924  }
4925 
4926  try {
4927  $ig = ImageGalleryBase::factory( $mode );
4928  } catch ( Exception $e ) {
4929  // If invalid type set, fallback to default.
4930  $ig = ImageGalleryBase::factory( false );
4931  }
4932 
4933  $ig->setContextTitle( $this->mTitle );
4934  $ig->setShowBytes( false );
4935  $ig->setShowFilename( false );
4936  $ig->setParser( $this );
4937  $ig->setHideBadImages();
4938  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
4939 
4940  if ( isset( $params['showfilename'] ) ) {
4941  $ig->setShowFilename( true );
4942  } else {
4943  $ig->setShowFilename( false );
4944  }
4945  if ( isset( $params['caption'] ) ) {
4946  $caption = $params['caption'];
4947  $caption = htmlspecialchars( $caption );
4948  $caption = $this->replaceInternalLinks( $caption );
4949  $ig->setCaptionHtml( $caption );
4950  }
4951  if ( isset( $params['perrow'] ) ) {
4952  $ig->setPerRow( $params['perrow'] );
4953  }
4954  if ( isset( $params['widths'] ) ) {
4955  $ig->setWidths( $params['widths'] );
4956  }
4957  if ( isset( $params['heights'] ) ) {
4958  $ig->setHeights( $params['heights'] );
4959  }
4960  $ig->setAdditionalOptions( $params );
4961 
4962  Hooks::run( 'BeforeParserrenderImageGallery', [ &$this, &$ig ] );
4963 
4964  $lines = StringUtils::explode( "\n", $text );
4965  foreach ( $lines as $line ) {
4966  # match lines like these:
4967  # Image:someimage.jpg|This is some image
4968  $matches = [];
4969  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4970  # Skip empty lines
4971  if ( count( $matches ) == 0 ) {
4972  continue;
4973  }
4974 
4975  if ( strpos( $matches[0], '%' ) !== false ) {
4976  $matches[1] = rawurldecode( $matches[1] );
4977  }
4979  if ( is_null( $title ) ) {
4980  # Bogus title. Ignore these so we don't bomb out later.
4981  continue;
4982  }
4983 
4984  # We need to get what handler the file uses, to figure out parameters.
4985  # Note, a hook can overide the file name, and chose an entirely different
4986  # file (which potentially could be of a different type and have different handler).
4987  $options = [];
4988  $descQuery = false;
4989  Hooks::run( 'BeforeParserFetchFileAndTitle',
4990  [ $this, $title, &$options, &$descQuery ] );
4991  # Don't register it now, as ImageGallery does that later.
4992  $file = $this->fetchFileNoRegister( $title, $options );
4993  $handler = $file ? $file->getHandler() : false;
4994 
4995  $paramMap = [
4996  'img_alt' => 'gallery-internal-alt',
4997  'img_link' => 'gallery-internal-link',
4998  ];
4999  if ( $handler ) {
5000  $paramMap = $paramMap + $handler->getParamMap();
5001  // We don't want people to specify per-image widths.
5002  // Additionally the width parameter would need special casing anyhow.
5003  unset( $paramMap['img_width'] );
5004  }
5005 
5006  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5007 
5008  $label = '';
5009  $alt = '';
5010  $link = '';
5011  $handlerOptions = [];
5012  if ( isset( $matches[3] ) ) {
5013  // look for an |alt= definition while trying not to break existing
5014  // captions with multiple pipes (|) in it, until a more sensible grammar
5015  // is defined for images in galleries
5016 
5017  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5018  // splitting on '|' is a bit odd, and different from makeImage.
5019  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5020  $parameterMatches = StringUtils::explode( '|', $matches[3] );
5021 
5022  foreach ( $parameterMatches as $parameterMatch ) {
5023  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5024  if ( $magicName ) {
5025  $paramName = $paramMap[$magicName];
5026 
5027  switch ( $paramName ) {
5028  case 'gallery-internal-alt':
5029  $alt = $this->stripAltText( $match, false );
5030  break;
5031  case 'gallery-internal-link':
5032  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5033  $chars = self::EXT_LINK_URL_CLASS;
5034  $addr = self::EXT_LINK_ADDR;
5035  $prots = $this->mUrlProtocols;
5036  // check to see if link matches an absolute url, if not then it must be a wiki link.
5037  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5038  $link = $linkValue;
5039  } else {
5040  $localLinkTitle = Title::newFromText( $linkValue );
5041  if ( $localLinkTitle !== null ) {
5042  $link = $localLinkTitle->getLinkURL();
5043  }
5044  }
5045  break;
5046  default:
5047  // Must be a handler specific parameter.
5048  if ( $handler->validateParam( $paramName, $match ) ) {
5049  $handlerOptions[$paramName] = $match;
5050  } else {
5051  // Guess not, consider it as caption.
5052  wfDebug( "$parameterMatch failed parameter validation\n" );
5053  $label = '|' . $parameterMatch;
5054  }
5055  }
5056 
5057  } else {
5058  // Last pipe wins.
5059  $label = '|' . $parameterMatch;
5060  }
5061  }
5062  // Remove the pipe.
5063  $label = substr( $label, 1 );
5064  }
5065 
5066  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5067  }
5068  $html = $ig->toHTML();
5069  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5070  return $html;
5071  }
5072 
5077  public function getImageParams( $handler ) {
5078  if ( $handler ) {
5079  $handlerClass = get_class( $handler );
5080  } else {
5081  $handlerClass = '';
5082  }
5083  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5084  # Initialise static lists
5085  static $internalParamNames = [
5086  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5087  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5088  'bottom', 'text-bottom' ],
5089  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5090  'upright', 'border', 'link', 'alt', 'class' ],
5091  ];
5092  static $internalParamMap;
5093  if ( !$internalParamMap ) {
5094  $internalParamMap = [];
5095  foreach ( $internalParamNames as $type => $names ) {
5096  foreach ( $names as $name ) {
5097  $magicName = str_replace( '-', '_', "img_$name" );
5098  $internalParamMap[$magicName] = [ $type, $name ];
5099  }
5100  }
5101  }
5102 
5103  # Add handler params
5104  $paramMap = $internalParamMap;
5105  if ( $handler ) {
5106  $handlerParamMap = $handler->getParamMap();
5107  foreach ( $handlerParamMap as $magic => $paramName ) {
5108  $paramMap[$magic] = [ 'handler', $paramName ];
5109  }
5110  }
5111  $this->mImageParams[$handlerClass] = $paramMap;
5112  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5113  }
5114  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5115  }
5116 
5125  public function makeImage( $title, $options, $holders = false ) {
5126  # Check if the options text is of the form "options|alt text"
5127  # Options are:
5128  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5129  # * left no resizing, just left align. label is used for alt= only
5130  # * right same, but right aligned
5131  # * none same, but not aligned
5132  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5133  # * center center the image
5134  # * frame Keep original image size, no magnify-button.
5135  # * framed Same as "frame"
5136  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5137  # * upright reduce width for upright images, rounded to full __0 px
5138  # * border draw a 1px border around the image
5139  # * alt Text for HTML alt attribute (defaults to empty)
5140  # * class Set a class for img node
5141  # * link Set the target of the image link. Can be external, interwiki, or local
5142  # vertical-align values (no % or length right now):
5143  # * baseline
5144  # * sub
5145  # * super
5146  # * top
5147  # * text-top
5148  # * middle
5149  # * bottom
5150  # * text-bottom
5151 
5152  $parts = StringUtils::explode( "|", $options );
5153 
5154  # Give extensions a chance to select the file revision for us
5155  $options = [];
5156  $descQuery = false;
5157  Hooks::run( 'BeforeParserFetchFileAndTitle',
5158  [ $this, $title, &$options, &$descQuery ] );
5159  # Fetch and register the file (file title may be different via hooks)
5160  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5161 
5162  # Get parameter map
5163  $handler = $file ? $file->getHandler() : false;
5164 
5165  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5166 
5167  if ( !$file ) {
5168  $this->addTrackingCategory( 'broken-file-category' );
5169  }
5170 
5171  # Process the input parameters
5172  $caption = '';
5173  $params = [ 'frame' => [], 'handler' => [],
5174  'horizAlign' => [], 'vertAlign' => [] ];
5175  $seenformat = false;
5176  foreach ( $parts as $part ) {
5177  $part = trim( $part );
5178  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5179  $validated = false;
5180  if ( isset( $paramMap[$magicName] ) ) {
5181  list( $type, $paramName ) = $paramMap[$magicName];
5182 
5183  # Special case; width and height come in one variable together
5184  if ( $type === 'handler' && $paramName === 'width' ) {
5185  $parsedWidthParam = $this->parseWidthParam( $value );
5186  if ( isset( $parsedWidthParam['width'] ) ) {
5187  $width = $parsedWidthParam['width'];
5188  if ( $handler->validateParam( 'width', $width ) ) {
5189  $params[$type]['width'] = $width;
5190  $validated = true;
5191  }
5192  }
5193  if ( isset( $parsedWidthParam['height'] ) ) {
5194  $height = $parsedWidthParam['height'];
5195  if ( $handler->validateParam( 'height', $height ) ) {
5196  $params[$type]['height'] = $height;
5197  $validated = true;
5198  }
5199  }
5200  # else no validation -- bug 13436
5201  } else {
5202  if ( $type === 'handler' ) {
5203  # Validate handler parameter
5204  $validated = $handler->validateParam( $paramName, $value );
5205  } else {
5206  # Validate internal parameters
5207  switch ( $paramName ) {
5208  case 'manualthumb':
5209  case 'alt':
5210  case 'class':
5211  # @todo FIXME: Possibly check validity here for
5212  # manualthumb? downstream behavior seems odd with
5213  # missing manual thumbs.
5214  $validated = true;
5215  $value = $this->stripAltText( $value, $holders );
5216  break;
5217  case 'link':
5218  $chars = self::EXT_LINK_URL_CLASS;
5219  $addr = self::EXT_LINK_ADDR;
5220  $prots = $this->mUrlProtocols;
5221  if ( $value === '' ) {
5222  $paramName = 'no-link';
5223  $value = true;
5224  $validated = true;
5225  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5226  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5227  $paramName = 'link-url';
5228  $this->mOutput->addExternalLink( $value );
5229  if ( $this->mOptions->getExternalLinkTarget() ) {
5230  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5231  }
5232  $validated = true;
5233  }
5234  } else {
5235  $linkTitle = Title::newFromText( $value );
5236  if ( $linkTitle ) {
5237  $paramName = 'link-title';
5238  $value = $linkTitle;
5239  $this->mOutput->addLink( $linkTitle );
5240  $validated = true;
5241  }
5242  }
5243  break;
5244  case 'frameless':
5245  case 'framed':
5246  case 'thumbnail':
5247  // use first appearing option, discard others.
5248  $validated = ! $seenformat;
5249  $seenformat = true;
5250  break;
5251  default:
5252  # Most other things appear to be empty or numeric...
5253  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5254  }
5255  }
5256 
5257  if ( $validated ) {
5258  $params[$type][$paramName] = $value;
5259  }
5260  }
5261  }
5262  if ( !$validated ) {
5263  $caption = $part;
5264  }
5265  }
5266 
5267  # Process alignment parameters
5268  if ( $params['horizAlign'] ) {
5269  $params['frame']['align'] = key( $params['horizAlign'] );
5270  }
5271  if ( $params['vertAlign'] ) {
5272  $params['frame']['valign'] = key( $params['vertAlign'] );
5273  }
5274 
5275  $params['frame']['caption'] = $caption;
5276 
5277  # Will the image be presented in a frame, with the caption below?
5278  $imageIsFramed = isset( $params['frame']['frame'] )
5279  || isset( $params['frame']['framed'] )
5280  || isset( $params['frame']['thumbnail'] )
5281  || isset( $params['frame']['manualthumb'] );
5282 
5283  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5284  # came to also set the caption, ordinary text after the image -- which
5285  # makes no sense, because that just repeats the text multiple times in
5286  # screen readers. It *also* came to set the title attribute.
5287  # Now that we have an alt attribute, we should not set the alt text to
5288  # equal the caption: that's worse than useless, it just repeats the
5289  # text. This is the framed/thumbnail case. If there's no caption, we
5290  # use the unnamed parameter for alt text as well, just for the time be-
5291  # ing, if the unnamed param is set and the alt param is not.
5292  # For the future, we need to figure out if we want to tweak this more,
5293  # e.g., introducing a title= parameter for the title; ignoring the un-
5294  # named parameter entirely for images without a caption; adding an ex-
5295  # plicit caption= parameter and preserving the old magic unnamed para-
5296  # meter for BC; ...
5297  if ( $imageIsFramed ) { # Framed image
5298  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5299  # No caption or alt text, add the filename as the alt text so
5300  # that screen readers at least get some description of the image
5301  $params['frame']['alt'] = $title->getText();
5302  }
5303  # Do not set $params['frame']['title'] because tooltips don't make sense
5304  # for framed images
5305  } else { # Inline image
5306  if ( !isset( $params['frame']['alt'] ) ) {
5307  # No alt text, use the "caption" for the alt text
5308  if ( $caption !== '' ) {
5309  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5310  } else {
5311  # No caption, fall back to using the filename for the
5312  # alt text
5313  $params['frame']['alt'] = $title->getText();
5314  }
5315  }
5316  # Use the "caption" for the tooltip text
5317  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5318  }
5319 
5320  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5321 
5322  # Linker does the rest
5323  $time = isset( $options['time'] ) ? $options['time'] : false;
5324  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5325  $time, $descQuery, $this->mOptions->getThumbSize() );
5326 
5327  # Give the handler a chance to modify the parser object
5328  if ( $handler ) {
5329  $handler->parserTransformHook( $this, $file );
5330  }
5331 
5332  return $ret;
5333  }
5334 
5340  protected function stripAltText( $caption, $holders ) {
5341  # Strip bad stuff out of the title (tooltip). We can't just use
5342  # replaceLinkHoldersText() here, because if this function is called
5343  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5344  if ( $holders ) {
5345  $tooltip = $holders->replaceText( $caption );
5346  } else {
5347  $tooltip = $this->replaceLinkHoldersText( $caption );
5348  }
5349 
5350  # make sure there are no placeholders in thumbnail attributes
5351  # that are later expanded to html- so expand them now and
5352  # remove the tags
5353  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5354  $tooltip = Sanitizer::stripAllTags( $tooltip );
5355 
5356  return $tooltip;
5357  }
5358 
5364  public function disableCache() {
5365  wfDebug( "Parser output marked as uncacheable.\n" );
5366  if ( !$this->mOutput ) {
5367  throw new MWException( __METHOD__ .
5368  " can only be called when actually parsing something" );
5369  }
5370  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5371  }
5372 
5381  public function attributeStripCallback( &$text, $frame = false ) {
5382  $text = $this->replaceVariables( $text, $frame );
5383  $text = $this->mStripState->unstripBoth( $text );
5384  return $text;
5385  }
5386 
5392  public function getTags() {
5393  return array_merge(
5394  array_keys( $this->mTransparentTagHooks ),
5395  array_keys( $this->mTagHooks ),
5396  array_keys( $this->mFunctionTagHooks )
5397  );
5398  }
5399 
5410  public function replaceTransparentTags( $text ) {
5411  $matches = [];
5412  $elements = array_keys( $this->mTransparentTagHooks );
5413  $text = self::extractTagsAndParams( $elements, $text, $matches );
5414  $replacements = [];
5415 
5416  foreach ( $matches as $marker => $data ) {
5417  list( $element, $content, $params, $tag ) = $data;
5418  $tagName = strtolower( $element );
5419  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5420  $output = call_user_func_array(
5421  $this->mTransparentTagHooks[$tagName],
5422  [ $content, $params, $this ]
5423  );
5424  } else {
5425  $output = $tag;
5426  }
5427  $replacements[$marker] = $output;
5428  }
5429  return strtr( $text, $replacements );
5430  }
5431 
5461  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5462  global $wgTitle; # not generally used but removes an ugly failure mode
5463 
5464  $magicScopeVariable = $this->lock();
5465  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5466  $outText = '';
5467  $frame = $this->getPreprocessor()->newFrame();
5468 
5469  # Process section extraction flags
5470  $flags = 0;
5471  $sectionParts = explode( '-', $sectionId );
5472  $sectionIndex = array_pop( $sectionParts );
5473  foreach ( $sectionParts as $part ) {
5474  if ( $part === 'T' ) {
5475  $flags |= self::PTD_FOR_INCLUSION;
5476  }
5477  }
5478 
5479  # Check for empty input
5480  if ( strval( $text ) === '' ) {
5481  # Only sections 0 and T-0 exist in an empty document
5482  if ( $sectionIndex == 0 ) {
5483  if ( $mode === 'get' ) {
5484  return '';
5485  } else {
5486  return $newText;
5487  }
5488  } else {
5489  if ( $mode === 'get' ) {
5490  return $newText;
5491  } else {
5492  return $text;
5493  }
5494  }
5495  }
5496 
5497  # Preprocess the text
5498  $root = $this->preprocessToDom( $text, $flags );
5499 
5500  # <h> nodes indicate section breaks
5501  # They can only occur at the top level, so we can find them by iterating the root's children
5502  $node = $root->getFirstChild();
5503 
5504  # Find the target section
5505  if ( $sectionIndex == 0 ) {
5506  # Section zero doesn't nest, level=big
5507  $targetLevel = 1000;
5508  } else {
5509  while ( $node ) {
5510  if ( $node->getName() === 'h' ) {
5511  $bits = $node->splitHeading();
5512  if ( $bits['i'] == $sectionIndex ) {
5513  $targetLevel = $bits['level'];
5514  break;
5515  }
5516  }
5517  if ( $mode === 'replace' ) {
5518  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5519  }
5520  $node = $node->getNextSibling();
5521  }
5522  }
5523 
5524  if ( !$node ) {
5525  # Not found
5526  if ( $mode === 'get' ) {
5527  return $newText;
5528  } else {
5529  return $text;
5530  }
5531  }
5532 
5533  # Find the end of the section, including nested sections
5534  do {
5535  if ( $node->getName() === 'h' ) {
5536  $bits = $node->splitHeading();
5537  $curLevel = $bits['level'];
5538  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5539  break;
5540  }
5541  }
5542  if ( $mode === 'get' ) {
5543  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5544  }
5545  $node = $node->getNextSibling();
5546  } while ( $node );
5547 
5548  # Write out the remainder (in replace mode only)
5549  if ( $mode === 'replace' ) {
5550  # Output the replacement text
5551  # Add two newlines on -- trailing whitespace in $newText is conventionally
5552  # stripped by the editor, so we need both newlines to restore the paragraph gap
5553  # Only add trailing whitespace if there is newText
5554  if ( $newText != "" ) {
5555  $outText .= $newText . "\n\n";
5556  }
5557 
5558  while ( $node ) {
5559  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5560  $node = $node->getNextSibling();
5561  }
5562  }
5563 
5564  if ( is_string( $outText ) ) {
5565  # Re-insert stripped tags
5566  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5567  }
5568 
5569  return $outText;
5570  }
5571 
5586  public function getSection( $text, $sectionId, $defaultText = '' ) {
5587  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5588  }
5589 
5602  public function replaceSection( $oldText, $sectionId, $newText ) {
5603  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5604  }
5605 
5611  public function getRevisionId() {
5612  return $this->mRevisionId;
5613  }
5614 
5621  public function getRevisionObject() {
5622  if ( !is_null( $this->mRevisionObject ) ) {
5623  return $this->mRevisionObject;
5624  }
5625  if ( is_null( $this->mRevisionId ) ) {
5626  return null;
5627  }
5628 
5629  $rev = call_user_func(
5630  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5631  );
5632 
5633  # If the parse is for a new revision, then the callback should have
5634  # already been set to force the object and should match mRevisionId.
5635  # If not, try to fetch by mRevisionId for sanity.
5636  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5637  $rev = Revision::newFromId( $this->mRevisionId );
5638  }
5639 
5640  $this->mRevisionObject = $rev;
5641 
5642  return $this->mRevisionObject;
5643  }
5644 
5650  public function getRevisionTimestamp() {
5651  if ( is_null( $this->mRevisionTimestamp ) ) {
5653 
5654  $revObject = $this->getRevisionObject();
5655  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5656 
5657  # The cryptic '' timezone parameter tells to use the site-default
5658  # timezone offset instead of the user settings.
5659  # Since this value will be saved into the parser cache, served
5660  # to other users, and potentially even used inside links and such,
5661  # it needs to be consistent for all visitors.
5662  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5663 
5664  }
5665  return $this->mRevisionTimestamp;
5666  }
5667 
5673  public function getRevisionUser() {
5674  if ( is_null( $this->mRevisionUser ) ) {
5675  $revObject = $this->getRevisionObject();
5676 
5677  # if this template is subst: the revision id will be blank,
5678  # so just use the current user's name
5679  if ( $revObject ) {
5680  $this->mRevisionUser = $revObject->getUserText();
5681  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5682  $this->mRevisionUser = $this->getUser()->getName();
5683  }
5684  }
5685  return $this->mRevisionUser;
5686  }
5687 
5693  public function getRevisionSize() {
5694  if ( is_null( $this->mRevisionSize ) ) {
5695  $revObject = $this->getRevisionObject();
5696 
5697  # if this variable is subst: the revision id will be blank,
5698  # so just use the parser input size, because the own substituation
5699  # will change the size.
5700  if ( $revObject ) {
5701  $this->mRevisionSize = $revObject->getSize();
5702  } else {
5703  $this->mRevisionSize = $this->mInputSize;
5704  }
5705  }
5706  return $this->mRevisionSize;
5707  }
5708 
5714  public function setDefaultSort( $sort ) {
5715  $this->mDefaultSort = $sort;
5716  $this->mOutput->setProperty( 'defaultsort', $sort );
5717  }
5718 
5729  public function getDefaultSort() {
5730  if ( $this->mDefaultSort !== false ) {
5731  return $this->mDefaultSort;
5732  } else {
5733  return '';
5734  }
5735  }
5736 
5743  public function getCustomDefaultSort() {
5744  return $this->mDefaultSort;
5745  }
5746 
5756  public function guessSectionNameFromWikiText( $text ) {
5757  # Strip out wikitext links(they break the anchor)
5758  $text = $this->stripSectionName( $text );
5760  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5761  }
5762 
5771  public function guessLegacySectionNameFromWikiText( $text ) {
5772  # Strip out wikitext links(they break the anchor)
5773  $text = $this->stripSectionName( $text );
5775  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5776  }
5777 
5792  public function stripSectionName( $text ) {
5793  # Strip internal link markup
5794  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5795  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5796 
5797  # Strip external link markup
5798  # @todo FIXME: Not tolerant to blank link text
5799  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5800  # on how many empty links there are on the page - need to figure that out.
5801  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5802 
5803  # Parse wikitext quotes (italics & bold)
5804  $text = $this->doQuotes( $text );
5805 
5806  # Strip HTML tags
5807  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5808  return $text;
5809  }
5810 
5821  public function testSrvus( $text, Title $title, ParserOptions $options,
5822  $outputType = self::OT_HTML
5823  ) {
5824  $magicScopeVariable = $this->lock();
5825  $this->startParse( $title, $options, $outputType, true );
5826 
5827  $text = $this->replaceVariables( $text );
5828  $text = $this->mStripState->unstripBoth( $text );
5829  $text = Sanitizer::removeHTMLtags( $text );
5830  return $text;
5831  }
5832 
5839  public function testPst( $text, Title $title, ParserOptions $options ) {
5840  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5841  }
5842 
5849  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5850  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5851  }
5852 
5869  public function markerSkipCallback( $s, $callback ) {
5870  $i = 0;
5871  $out = '';
5872  while ( $i < strlen( $s ) ) {
5873  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5874  if ( $markerStart === false ) {
5875  $out .= call_user_func( $callback, substr( $s, $i ) );
5876  break;
5877  } else {
5878  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5879  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5880  if ( $markerEnd === false ) {
5881  $out .= substr( $s, $markerStart );
5882  break;
5883  } else {
5884  $markerEnd += strlen( self::MARKER_SUFFIX );
5885  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5886  $i = $markerEnd;
5887  }
5888  }
5889  }
5890  return $out;
5891  }
5892 
5899  public function killMarkers( $text ) {
5900  return $this->mStripState->killMarkers( $text );
5901  }
5902 
5919  public function serializeHalfParsedText( $text ) {
5920  $data = [
5921  'text' => $text,
5922  'version' => self::HALF_PARSED_VERSION,
5923  'stripState' => $this->mStripState->getSubState( $text ),
5924  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5925  ];
5926  return $data;
5927  }
5928 
5944  public function unserializeHalfParsedText( $data ) {
5945  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5946  throw new MWException( __METHOD__ . ': invalid version' );
5947  }
5948 
5949  # First, extract the strip state.
5950  $texts = [ $data['text'] ];
5951  $texts = $this->mStripState->merge( $data['stripState'], $texts );
5952 
5953  # Now renumber links
5954  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
5955 
5956  # Should be good to go.
5957  return $texts[0];
5958  }
5959 
5969  public function isValidHalfParsedText( $data ) {
5970  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
5971  }
5972 
5981  public function parseWidthParam( $value ) {
5982  $parsedWidthParam = [];
5983  if ( $value === '' ) {
5984  return $parsedWidthParam;
5985  }
5986  $m = [];
5987  # (bug 13500) In both cases (width/height and width only),
5988  # permit trailing "px" for backward compatibility.
5989  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
5990  $width = intval( $m[1] );
5991  $height = intval( $m[2] );
5992  $parsedWidthParam['width'] = $width;
5993  $parsedWidthParam['height'] = $height;
5994  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
5995  $width = intval( $value );
5996  $parsedWidthParam['width'] = $width;
5997  }
5998  return $parsedWidthParam;
5999  }
6000 
6010  protected function lock() {
6011  if ( $this->mInParse ) {
6012  throw new MWException( "Parser state cleared while parsing. "
6013  . "Did you call Parser::parse recursively?" );
6014  }
6015  $this->mInParse = true;
6016 
6017  $recursiveCheck = new ScopedCallback( function() {
6018  $this->mInParse = false;
6019  } );
6020 
6021  return $recursiveCheck;
6022  }
6023 
6034  public static function stripOuterParagraph( $html ) {
6035  $m = [];
6036  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6037  if ( strpos( $m[1], '</p>' ) === false ) {
6038  $html = $m[1];
6039  }
6040  }
6041 
6042  return $html;
6043  }
6044 
6055  public function getFreshParser() {
6056  global $wgParserConf;
6057  if ( $this->mInParse ) {
6058  return new $wgParserConf['class']( $wgParserConf );
6059  } else {
6060  return $this;
6061  }
6062  }
6063 
6070  public function enableOOUI() {
6072  $this->mOutput->setEnableOOUI( true );
6073  }
6074 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5621
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:525
setTitle($t)
Set the context title.
Definition: Parser.php:739
$mAutonumber
Definition: Parser.php:177
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3260
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5869
#define the
table suitable for use with IDatabase::select()
$mPPNodeCount
Definition: Parser.php:191
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2087
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:271
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:117
getExternalLinkAttribs($url)
Get an associative array of additional HTML attributes appropriate for a particular external link...
Definition: Parser.php:1914
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:5969
null means default in associative array form
Definition: hooks.txt:1936
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:1936
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1633
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
getSection($text, $sectionId, $defaultText= '')
This function returns the text of a section, specified by a number ($section).
Definition: Parser.php:5586
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1287
$mTplRedirCache
Definition: Parser.php:193
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5899
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
static tocList($toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
Definition: Linker.php:1645
LinkRenderer $mLinkRenderer
Definition: Parser.php:257
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3507
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:802
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5673
setFunctionTagHook($tag, $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4868
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:5792
const OT_PREPROCESS
Definition: Defines.php:190
either a plain
Definition: hooks.txt:1987
$mDoubleUnderscores
Definition: Parser.php:193
Group all the pieces relevant to the context of a request into one instance.
getPreloadText($text, Title $title, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:687
$context
Definition: load.php:50
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4582
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:3171
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4919
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:668
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1814
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
nextLinkID()
Definition: Parser.php:828
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:1936
static replaceUnusualEscapes($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1942
getImageParams($handler)
Definition: Parser.php:5077
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:1593
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:5392
static isWellFormedXmlFragment($text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:735
const OT_WIKI
Definition: Parser.php:111
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2102
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3649
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:2825
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:4854
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:843
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3752
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5821
uniqPrefix()
Accessor for mUniqPrefix.
Definition: Parser.php:729
const TOC_START
Definition: Parser.php:137
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:767
SectionProfiler $mProfiler
Definition: Parser.php:252
$sort
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3674
null for the local wiki Added in
Definition: hooks.txt:1555
There are three types of nodes:
$mHeadings
Definition: Parser.php:193
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4767
static makeSelfLinkObj($nt, $html= '', $query= '', $trail= '', $prefix= '')
Make appropriate markup for a link to the current article.
Definition: Linker.php:277
const NS_SPECIAL
Definition: Defines.php:45
clearState()
Clear Parser state.
Definition: Parser.php: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:1046
__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:3693
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4457
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead.
Definition: Parser.php:5771
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:821
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2703
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5919
replaceLinkHolders(&$text, $options=0)
Replace "" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4891
static statelessFetchRevision(Title $title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3490
static activeUsers()
Definition: SiteStats.php:165
$mLinkID
Definition: Parser.php:190
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1626
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2855
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:2977
static cleanUrl($url)
Definition: Sanitizer.php:1856
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:262
$mGeneratedPPNodeCount
Definition: Parser.php:191
static getRandomString()
Get a random string.
Definition: Parser.php:708
$mRevisionId
Definition: Parser.php:217
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
Definition: Sanitizer.php:1823
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
doBlockLevels($text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2458
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:793
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:910
const NS_TEMPLATE
Definition: Defines.php:66
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target...
Definition: Revision.php:128
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2473
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:603
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2487
MagicWordArray $mVariables
Definition: Parser.php:159
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor'$rcid is used in generating this variable which contains information about the new revision
Definition: hooks.txt:1156
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:644
static getCacheTTL($id)
Allow external reads of TTL array.
Definition: MagicWord.php:294
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5611
const OT_PREPROCESS
Definition: Parser.php:112
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2446
$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:4902
setLinkID($id)
Definition: Parser.php:835
$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:2929
$mExtLinkBracketedRegex
Definition: Parser.php:166
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message.Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload.Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports.&$fullInterwikiPrefix:Interwiki prefix, may contain colons.&$pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable.Can be used to lazy-load the import sources list.&$importSources:The value of $wgImportSources.Modify as necessary.See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page.$context:IContextSource object &$pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect.&$title:Title object for the current page &$request:WebRequest &$ignoreRedirect:boolean to skip redirect check &$target:Title/string of redirect target &$article:Article object 'InternalParseBeforeLinks':during Parser's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InternalParseBeforeSanitize':during Parser's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings.Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not.Return true without providing an interwiki to continue interwiki search.$prefix:interwiki prefix we are looking for.&$iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user's email has been invalidated successfully.$user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification.Callee may modify $url and $query, URL will be constructed as $url.$query &$url:URL to index.php &$query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) &$article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() &$ip:IP being check &$result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from &$allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn't match your organization.$addr:The e-mail address entered by the user &$result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user &$result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we're looking for a messages file for &$file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED!Use $magicWords in a file listed in $wgExtensionMessagesFiles instead.Use this to define synonyms of magic words depending of the language &$magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces.Do not use this hook to add namespaces.Use CanonicalNamespaces for that.&$namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED!Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead.Use to define aliases of special pages names depending of the language &$specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names.&$names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page's language links.This is called in various places to allow extensions to define the effective language links for a page.$title:The page's Title.&$links:Associative array mapping language codes to prefixed links of the form"language:title".&$linkFlags:Associative array mapping prefixed links to arrays of flags.Currently unused, but planned to provide support for marking individual language links in the UI, e.g.for featured articles. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED!Use HtmlPageLinkRendererBegin instead.Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1934
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition: defines.php:6
if($line===false) $args
Definition: cdb.php:64
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:307
static splitTrail($trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition: Linker.php:1720
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3424
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2889
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1500
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4596
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:4647
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:3994
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2074
$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:5364
$mRevisionObject
Definition: Parser.php:216
static normalizeSectionNameWhitespace($section)
Normalizes whitespace in a section name, such as might be returned by Parser::stripSectionName(), for use in the id's that are used for section links.
Definition: Sanitizer.php:1381
internalParse($text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1243
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:5650
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:6034
static register($parser)
$mRevIdForTs
Definition: Parser.php:221
static singleton()
Get an instance of this class.
Definition: LinkCache.php:64
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
static normalizeSubpageLink($contextTitle, $target, &$text)
Definition: Linker.php:1439
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:5981
$mStripList
Definition: Parser.php:146
$mFunctionTagHooks
Definition: Parser.php:145
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3712
const OT_PLAIN
Definition: Defines.php:192
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3467
$mRevisionTimestamp
Definition: Parser.php:218
$mImageParams
Definition: Parser.php:149
stripAltText($caption, $holders)
Definition: Parser.php:5340
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1609
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:1992
if($limit) $timestamp
const VERSION
Update this version number when the ParserOutput format changes in an incompatible way...
Definition: Parser.php:76
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
setHook($tag, $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4722
const OT_WIKI
Definition: Defines.php:189
Preprocessor $mPreprocessor
Definition: Parser.php:170
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:896
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of normal(non-web) applications--they might conflict with distributors'policies
static getInstance($ts=false)
Get a timestamp instance in GMT.
Definition: MWTimestamp.php:38
const NS_MEDIA
Definition: Defines.php:44
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
replaceVariables($text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2900
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:884
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3912
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:1013
const EXT_IMAGE_REGEX
Definition: Parser.php:98
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4659
$params
const NS_CATEGORY
Definition: Defines.php:70
static makeHeadline($level, $attribs, $anchor, $html, $link, $legacyAnchor=false)
Create a headline for content.
Definition: Linker.php:1701
static extractTagsAndParams($elements, $text, &$matches, $uniq_prefix=null)
Replaces all occurrences of HTML-style comments and the given tags in the text with a random marker a...
Definition: Parser.php:943
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:1040
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:5693
$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:1936
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to and or sell copies of the and to permit persons to whom the Software is furnished to do so
Definition: LICENSE.txt:10
Some information about database access in MediaWiki By Tim January Database layout For information about the MediaWiki database such as a description of the tables and their please see
Definition: database.txt:2
preSaveTransform($text, Title $title, User $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
Definition: Parser.php:4425
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:856
$buffer
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:953
static newKnownCurrent(IDatabase $db, $pageId, $revId)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1905
static hasSubpages($index)
Does the namespace allow subpages?
formatHeadings($text, $origText, $isMain=true)
This function accomplishes several tasks: 1) Auto-number headings if that option is enabled 2) Add an...
Definition: Parser.php:4014
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:874
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1600
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static tocLine($anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1615
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$mInputSize
Definition: Parser.php:222
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
Definition: magicword.txt:4
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:1007
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:4533
const HALF_PARSED_VERSION
Update this version number when the output of serialiseHalfParsedText() changes in an incompatible wa...
Definition: Parser.php:82
const NS_FILE
Definition: Defines.php:62
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:323
static makeImageLink(Parser $parser, Title $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
Definition: Linker.php:415
const PTD_FOR_INCLUSION
Definition: Parser.php:106
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped broken
Definition: hooks.txt:1936
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:2424
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:1721
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2867
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:776
$mTagHooks
Definition: Parser.php:141
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:64
static & get($id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:257
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6070
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
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor'$rcid is used in generating this variable which contains information about the new such as the revision s whether the revision was marked as a minor edit or not
Definition: hooks.txt:1156
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3535
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:2015
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2433
const OT_HTML
Definition: Defines.php:188
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1170
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1046
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:284
static images()
Definition: SiteStats.php:173
$mTransparentTagHooks
Definition: Parser.php:142
$mExpensiveFunctionCount
Definition: Parser.php:194
$mUrlProtocols
Definition: Parser.php:166
$mConf
Definition: Parser.php:166
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4678
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:110
wfUrlProtocols($includeProtocolRelative=true)
Returns a regular expression of url protocols.
static makeExternalLink($url, $text, $escape=true, $linktype= '', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:934
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:300
static getExternalLinkRel($url=false, $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:1893
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:1007
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:802
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:1721
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:5410
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:5602
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3939
$mFunctionHooks
Definition: Parser.php: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:5849
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if so it s not worth the trouble Since there is a job queue in the jobs table
Definition: deferred.txt:11
MagicWordArray $mSubstWords
Definition: Parser.php:164
const TOC_END
Definition: Parser.php:138
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1400
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3328
$wgScriptPath
The path we should point to.
Variant of the Message class.
Definition: Message.php:1242
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:6055
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:1046
static articles()
Definition: SiteStats.php:141
$mRevisionUser
Definition: Parser.php:219
lock()
Lock the current instance of the parser.
Definition: Parser.php:6010
static pages()
Definition: SiteStats.php:149
$line
Definition: cdb.php:59
const SFH_OBJECT_ARGS
Definition: Parser.php:86
makeKnownLinkHolder($nt, $text= '', $trail= '', $prefix= '')
Render a forced-blue link inline; protect against double expansion of URLs if we're in a mode that pr...
Definition: Parser.php:2400
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3548
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:4816
static setupOOUI($skinName= '', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php:874
$mIncludeCount
Definition: Parser.php:184
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template to be included in the link
Definition: hooks.txt:2889
$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:1046
getTitle()
Accessor for the Title object.
Definition: Parser.php:757
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:5461
ParserOutput $mOutput
Definition: Parser.php:176
getOutput()
Get the ParserOutput object.
Definition: Parser.php:802
$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:1414
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:1046
static cleanSigInSig($text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4633
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5714
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3638
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1589
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:593
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all.It could be easily changed to send incrementally if that becomes useful
static doBlockLevels($text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
$wgServer
URL of the server.
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:86
incrementExpensiveFunctionCount()
Increment the expensive function count.
Definition: Parser.php:3926
$mShowToc
Definition: Parser.php:195
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1956
const DB_REPLICA
Definition: defines.php:22
magicLinkCallback($m)
Definition: Parser.php:1444
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:1026
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5839
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:802
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:2999
setUser($user)
Set the current user.
Definition: Parser.php:719
$mHighestExpansionDepth
Definition: Parser.php:191
makeImage($title, $options, $holders=false)
Parse image options text and use it to make an image.
Definition: Parser.php:5125
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5381
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:5743
extensionSubstitution($params, $frame)
Return the text to be used for a given extension tag.
Definition: Parser.php:3805
static normalizeLineEndings($text)
Do a "\\r\\n" -> "\\n" and "\\r" -> "\\n" transformation as well as trim trailing whitespace...
static makeExternalImage($url, $alt= '')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage()...
Definition: Linker.php:362
recursiveTagParseFully($text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition: Parser.php:627
setTransparentTagHook($tag, $callback)
As setHook(), but letting the contents be parsed.
Definition: Parser.php:4753
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:229
wfFindFile($title, $options=[])
Find a file.
$mRevisionSize
Definition: Parser.php:220
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk page
Definition: hooks.txt:2491
static users()
Definition: SiteStats.php:157
unserializeHalfParsedText($data)
Load the parser state given in the $data array, which is assumed to have been generated by serializeH...
Definition: Parser.php:5944
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2491
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:511
guessSectionNameFromWikiText($text)
Try to guess the section anchor name based on a wikitext fragment presumably extracted from a heading...
Definition: Parser.php:5756
const SFH_OBJECT_ARGS
Definition: Defines.php:202
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1749
$wgServerName
Server name.
internalParseHalfParsed($text, $isMain=true, $linestart=true)
Helper function for parse() that transforms half-parsed HTML into fully parsed HTML.
Definition: Parser.php:1313
const OT_HTML
Definition: Parser.php:110
$mIncludeSizes
Definition: Parser.php:191
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1610
controlled by $wgMainCacheType controlled by $wgParserCacheType controlled by $wgMessageCacheType If you set CACHE_NONE to one of the three control variable
Definition: memcached.txt:78
getOptions()
Get the ParserOptions object.
Definition: Parser.php:811
getDefaultSort()
Accessor for $mDefaultSort Will use the empty string if none is set.
Definition: Parser.php:5729
For a write use something like
Definition: database.txt:26
const SFH_NO_HASH
Definition: Defines.php:201
makeFreeExternalLink($url, $numPostProto)
Make a free external link, given a user-supplied URL.
Definition: Parser.php:1516
$matches
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300
$mTplDomCache
Definition: Parser.php:193