MediaWiki  1.29.2
Parser.php
Go to the documentation of this file.
1 <?php
25 use Wikimedia\ScopedCallback;
26 
70 class Parser {
76  const VERSION = '1.6.4';
77 
82  const HALF_PARSED_VERSION = 2;
83 
84  # Flags for Parser::setFunctionHook
85  const SFH_NO_HASH = 1;
86  const SFH_OBJECT_ARGS = 2;
87 
88  # Constants needed for external link processing
89  # Everything except bracket, space, or control characters
90  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
91  # as well as U+3000 is IDEOGRAPHIC SPACE for T21052
92  # \x{FFFD} is the Unicode replacement character, which Preprocessor_DOM
93  # uses to replace invalid HTML characters.
94  const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]';
95  # Simplified expression to match an IPv4 or IPv6 address, or
96  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
97  const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])';
98  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
99  // @codingStandardsIgnoreStart Generic.Files.LineLength
100  const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+)
101  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
102  // @codingStandardsIgnoreEnd
103 
104  # Regular expression for a non-newline space
105  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
106 
107  # Flags for preprocessToDom
108  const PTD_FOR_INCLUSION = 1;
109 
110  # Allowed values for $this->mOutputType
111  # Parameter to startExternalParse().
112  const OT_HTML = 1; # like parse()
113  const OT_WIKI = 2; # like preSaveTransform()
114  const OT_PREPROCESS = 3; # like preprocess()
115  const OT_MSG = 3;
116  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
117 
135  const MARKER_SUFFIX = "-QINU`\"'\x7f";
136  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
137 
138  # Markers used for wrapping the table of contents
139  const TOC_START = '<mw:toc>';
140  const TOC_END = '</mw:toc>';
141 
142  # Persistent:
143  public $mTagHooks = [];
144  public $mTransparentTagHooks = [];
145  public $mFunctionHooks = [];
146  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
147  public $mFunctionTagHooks = [];
148  public $mStripList = [];
149  public $mDefaultStripList = [];
150  public $mVarCache = [];
151  public $mImageParams = [];
152  public $mImageParamsMagicArray = [];
153  public $mMarkerIndex = 0;
154  public $mFirstCall = true;
155 
156  # Initialised by initialiseVariables()
157 
161  public $mVariables;
162 
166  public $mSubstWords;
167  # Initialised in constructor
168  public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
169 
170  # Initialized in getPreprocessor()
171 
172  public $mPreprocessor;
173 
174  # Cleared with clearState():
175 
178  public $mOutput;
179  public $mAutonumber;
180 
184  public $mStripState;
185 
186  public $mIncludeCount;
190  public $mLinkHolders;
191 
192  public $mLinkID;
193  public $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth;
194  public $mDefaultSort;
195  public $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
196  public $mExpensiveFunctionCount; # number of expensive parser function calls
197  public $mShowToc, $mForceTocPosition;
198 
202  public $mUser; # User object; only used when doing pre-save transform
203 
204  # Temporary
205  # These are variables reset at least once per parse regardless of $clearState
206 
210  public $mOptions;
211 
215  public $mTitle; # Title context, used for self-link rendering and similar things
216  public $mOutputType; # Output type, one of the OT_xxx constants
217  public $ot; # Shortcut alias, see setOutputType()
218  public $mRevisionObject; # The revision object of the specified revision ID
219  public $mRevisionId; # ID to display in {{REVISIONID}} tags
220  public $mRevisionTimestamp; # The timestamp of the specified revision ID
221  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
222  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
223  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
224  public $mInputSize = false; # For {{PAGESIZE}} on current page.
225 
230  public $mUniqPrefix = Parser::MARKER_PREFIX;
231 
237  public $mLangLinkLanguages;
238 
245  public $currentRevisionCache;
246 
251  public $mInParse = false;
252 
254  protected $mProfiler;
255 
259  protected $mLinkRenderer;
260 
264  public function __construct( $conf = [] ) {
265  $this->mConf = $conf;
266  $this->mUrlProtocols = wfUrlProtocols();
267  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
268  self::EXT_LINK_ADDR .
269  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
270  if ( isset( $conf['preprocessorClass'] ) ) {
271  $this->mPreprocessorClass = $conf['preprocessorClass'];
272  } elseif ( defined( 'HPHP_VERSION' ) ) {
273  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
274  $this->mPreprocessorClass = 'Preprocessor_Hash';
275  } elseif ( extension_loaded( 'domxml' ) ) {
276  # PECL extension that conflicts with the core DOM extension (T15770)
277  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
278  $this->mPreprocessorClass = 'Preprocessor_Hash';
279  } elseif ( extension_loaded( 'dom' ) ) {
280  $this->mPreprocessorClass = 'Preprocessor_DOM';
281  } else {
282  $this->mPreprocessorClass = 'Preprocessor_Hash';
283  }
284  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
285  }
286 
290  public function __destruct() {
291  if ( isset( $this->mLinkHolders ) ) {
292  unset( $this->mLinkHolders );
293  }
294  foreach ( $this as $name => $value ) {
295  unset( $this->$name );
296  }
297  }
298 
302  public function __clone() {
303  $this->mInParse = false;
304 
305  // T58226: When you create a reference "to" an object field, that
306  // makes the object field itself be a reference too (until the other
307  // reference goes out of scope). When cloning, any field that's a
308  // reference is copied as a reference in the new object. Both of these
309  // are defined PHP5 behaviors, as inconvenient as it is for us when old
310  // hooks from PHP4 days are passing fields by reference.
311  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
312  // Make a non-reference copy of the field, then rebind the field to
313  // reference the new copy.
314  $tmp = $this->$k;
315  $this->$k =& $tmp;
316  unset( $tmp );
317  }
318 
319  Hooks::run( 'ParserCloned', [ $this ] );
320  }
321 
325  public function firstCallInit() {
326  if ( !$this->mFirstCall ) {
327  return;
328  }
329  $this->mFirstCall = false;
330 
332  CoreTagHooks::register( $this );
333  $this->initialiseVariables();
334 
335  // Avoid PHP 7.1 warning from passing $this by reference
336  $parser = $this;
337  Hooks::run( 'ParserFirstCallInit', [ &$parser ] );
338  }
339 
345  public function clearState() {
346  if ( $this->mFirstCall ) {
347  $this->firstCallInit();
348  }
349  $this->mOutput = new ParserOutput;
350  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
351  $this->mAutonumber = 0;
352  $this->mIncludeCount = [];
353  $this->mLinkHolders = new LinkHolderArray( $this );
354  $this->mLinkID = 0;
355  $this->mRevisionObject = $this->mRevisionTimestamp =
356  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
357  $this->mVarCache = [];
358  $this->mUser = null;
359  $this->mLangLinkLanguages = [];
360  $this->currentRevisionCache = null;
361 
362  $this->mStripState = new StripState;
363 
364  # Clear these on every parse, T6549
365  $this->mTplRedirCache = $this->mTplDomCache = [];
366 
367  $this->mShowToc = true;
368  $this->mForceTocPosition = false;
369  $this->mIncludeSizes = [
370  'post-expand' => 0,
371  'arg' => 0,
372  ];
373  $this->mPPNodeCount = 0;
374  $this->mGeneratedPPNodeCount = 0;
375  $this->mHighestExpansionDepth = 0;
376  $this->mDefaultSort = false;
377  $this->mHeadings = [];
378  $this->mDoubleUnderscores = [];
379  $this->mExpensiveFunctionCount = 0;
380 
381  # Fix cloning
382  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
383  $this->mPreprocessor = null;
384  }
385 
386  $this->mProfiler = new SectionProfiler();
387 
388  // Avoid PHP 7.1 warning from passing $this by reference
389  $parser = $this;
390  Hooks::run( 'ParserClearState', [ &$parser ] );
391  }
392 
405  public function parse(
407  $linestart = true, $clearState = true, $revid = null
408  ) {
414  global $wgShowHostnames;
415 
416  if ( $clearState ) {
417  // We use U+007F DELETE to construct strip markers, so we have to make
418  // sure that this character does not occur in the input text.
419  $text = strtr( $text, "\x7f", "?" );
420  $magicScopeVariable = $this->lock();
421  }
422  // Strip U+0000 NULL (T159174)
423  $text = str_replace( "\000", '', $text );
424 
425  $this->startParse( $title, $options, self::OT_HTML, $clearState );
426 
427  $this->currentRevisionCache = null;
428  $this->mInputSize = strlen( $text );
429  if ( $this->mOptions->getEnableLimitReport() ) {
430  $this->mOutput->resetParseStartTime();
431  }
432 
433  $oldRevisionId = $this->mRevisionId;
434  $oldRevisionObject = $this->mRevisionObject;
435  $oldRevisionTimestamp = $this->mRevisionTimestamp;
436  $oldRevisionUser = $this->mRevisionUser;
437  $oldRevisionSize = $this->mRevisionSize;
438  if ( $revid !== null ) {
439  $this->mRevisionId = $revid;
440  $this->mRevisionObject = null;
441  $this->mRevisionTimestamp = null;
442  $this->mRevisionUser = null;
443  $this->mRevisionSize = null;
444  }
445 
446  // Avoid PHP 7.1 warning from passing $this by reference
447  $parser = $this;
448  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
449  # No more strip!
450  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
451  $text = $this->internalParse( $text );
452  Hooks::run( 'ParserAfterParse', [ &$parser, &$text, &$this->mStripState ] );
453 
454  $text = $this->internalParseHalfParsed( $text, true, $linestart );
455 
463  if ( !( $options->getDisableTitleConversion()
464  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
465  || isset( $this->mDoubleUnderscores['notitleconvert'] )
466  || $this->mOutput->getDisplayTitle() !== false )
467  ) {
468  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
469  if ( $convruletitle ) {
470  $this->mOutput->setTitleText( $convruletitle );
471  } else {
472  $titleText = $this->getConverterLanguage()->convertTitle( $title );
473  $this->mOutput->setTitleText( $titleText );
474  }
475  }
476 
477  # Done parsing! Compute runtime adaptive expiry if set
478  $this->mOutput->finalizeAdaptiveCacheExpiry();
479 
480  # Warn if too many heavyweight parser functions were used
481  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
482  $this->limitationWarn( 'expensive-parserfunction',
483  $this->mExpensiveFunctionCount,
484  $this->mOptions->getExpensiveParserFunctionLimit()
485  );
486  }
487 
488  # Information on include size limits, for the benefit of users who try to skirt them
489  if ( $this->mOptions->getEnableLimitReport() ) {
490  $max = $this->mOptions->getMaxIncludeSize();
491 
492  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
493  if ( $cpuTime !== null ) {
494  $this->mOutput->setLimitReportData( 'limitreport-cputime',
495  sprintf( "%.3f", $cpuTime )
496  );
497  }
498 
499  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
500  $this->mOutput->setLimitReportData( 'limitreport-walltime',
501  sprintf( "%.3f", $wallTime )
502  );
503 
504  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
505  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
506  );
507  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
508  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
509  );
510  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
511  [ $this->mIncludeSizes['post-expand'], $max ]
512  );
513  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
514  [ $this->mIncludeSizes['arg'], $max ]
515  );
516  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
517  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
518  );
519  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
520  [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
521  );
522  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
523 
524  $limitReport = "NewPP limit report\n";
525  if ( $wgShowHostnames ) {
526  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
527  }
528  $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
529  $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
530  $limitReport .= 'Dynamic content: ' .
531  ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
532  "\n";
533 
534  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
535  if ( Hooks::run( 'ParserLimitReportFormat',
536  [ $key, &$value, &$limitReport, false, false ]
537  ) ) {
538  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
539  $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
540  ->inLanguage( 'en' )->useDatabase( false );
541  if ( !$valueMsg->exists() ) {
542  $valueMsg = new RawMessage( '$1' );
543  }
544  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
545  $valueMsg->params( $value );
546  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
547  }
548  }
549  }
550  // Since we're not really outputting HTML, decode the entities and
551  // then re-encode the things that need hiding inside HTML comments.
552  $limitReport = htmlspecialchars_decode( $limitReport );
553  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
554 
555  // Sanitize for comment. Note '‐' in the replacement is U+2010,
556  // which looks much like the problematic '-'.
557  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
558  $text .= "\n<!-- \n$limitReport-->\n";
559 
560  // Add on template profiling data in human/machine readable way
561  $dataByFunc = $this->mProfiler->getFunctionStats();
562  uasort( $dataByFunc, function ( $a, $b ) {
563  return $a['real'] < $b['real']; // descending order
564  } );
565  $profileReport = [];
566  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
567  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
568  $item['%real'], $item['real'], $item['calls'],
569  htmlspecialchars( $item['name'] ) );
570  }
571  $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
572  $text .= implode( "\n", $profileReport ) . "\n-->\n";
573 
574  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
575 
576  // Add other cache related metadata
577  if ( $wgShowHostnames ) {
578  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
579  }
580  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
581  $this->mOutput->getCacheTime() );
582  $this->mOutput->setLimitReportData( 'cachereport-ttl',
583  $this->mOutput->getCacheExpiry() );
584  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
585  $this->mOutput->hasDynamicContent() );
586 
587  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
588  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
589  $this->mTitle->getPrefixedDBkey() );
590  }
591  }
592  $this->mOutput->setText( $text );
593 
594  $this->mRevisionId = $oldRevisionId;
595  $this->mRevisionObject = $oldRevisionObject;
596  $this->mRevisionTimestamp = $oldRevisionTimestamp;
597  $this->mRevisionUser = $oldRevisionUser;
598  $this->mRevisionSize = $oldRevisionSize;
599  $this->mInputSize = false;
600  $this->currentRevisionCache = null;
601 
602  return $this->mOutput;
603  }
604 
627  public function recursiveTagParse( $text, $frame = false ) {
628  // Avoid PHP 7.1 warning from passing $this by reference
629  $parser = $this;
630  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
631  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
632  $text = $this->internalParse( $text, false, $frame );
633  return $text;
634  }
635 
653  public function recursiveTagParseFully( $text, $frame = false ) {
654  $text = $this->recursiveTagParse( $text, $frame );
655  $text = $this->internalParseHalfParsed( $text, false );
656  return $text;
657  }
658 
670  public function preprocess( $text, Title $title = null,
671  ParserOptions $options, $revid = null, $frame = false
672  ) {
673  $magicScopeVariable = $this->lock();
674  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
675  if ( $revid !== null ) {
676  $this->mRevisionId = $revid;
677  }
678  // Avoid PHP 7.1 warning from passing $this by reference
679  $parser = $this;
680  Hooks::run( 'ParserBeforeStrip', [ &$parser, &$text, &$this->mStripState ] );
681  Hooks::run( 'ParserAfterStrip', [ &$parser, &$text, &$this->mStripState ] );
682  $text = $this->replaceVariables( $text, $frame );
683  $text = $this->mStripState->unstripBoth( $text );
684  return $text;
685  }
686 
696  public function recursivePreprocess( $text, $frame = false ) {
697  $text = $this->replaceVariables( $text, $frame );
698  $text = $this->mStripState->unstripBoth( $text );
699  return $text;
700  }
701 
715  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
716  $msg = new RawMessage( $text );
717  $text = $msg->params( $params )->plain();
718 
719  # Parser (re)initialisation
720  $magicScopeVariable = $this->lock();
721  $this->startParse( $title, $options, self::OT_PLAIN, true );
722 
724  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
725  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
726  $text = $this->mStripState->unstripBoth( $text );
727  return $text;
728  }
729 
736  public static function getRandomString() {
737  wfDeprecated( __METHOD__, '1.26' );
738  return wfRandomString( 16 );
739  }
740 
747  public function setUser( $user ) {
748  $this->mUser = $user;
749  }
750 
757  public function uniqPrefix() {
758  wfDeprecated( __METHOD__, '1.26' );
759  return self::MARKER_PREFIX;
760  }
761 
767  public function setTitle( $t ) {
768  if ( !$t ) {
769  $t = Title::newFromText( 'NO TITLE' );
770  }
771 
772  if ( $t->hasFragment() ) {
773  # Strip the fragment to avoid various odd effects
774  $this->mTitle = $t->createFragmentTarget( '' );
775  } else {
776  $this->mTitle = $t;
777  }
778  }
779 
785  public function getTitle() {
786  return $this->mTitle;
787  }
788 
795  public function Title( $x = null ) {
796  return wfSetVar( $this->mTitle, $x );
797  }
798 
804  public function setOutputType( $ot ) {
805  $this->mOutputType = $ot;
806  # Shortcut alias
807  $this->ot = [
808  'html' => $ot == self::OT_HTML,
809  'wiki' => $ot == self::OT_WIKI,
810  'pre' => $ot == self::OT_PREPROCESS,
811  'plain' => $ot == self::OT_PLAIN,
812  ];
813  }
814 
821  public function OutputType( $x = null ) {
822  return wfSetVar( $this->mOutputType, $x );
823  }
824 
830  public function getOutput() {
831  return $this->mOutput;
832  }
833 
839  public function getOptions() {
840  return $this->mOptions;
841  }
842 
849  public function Options( $x = null ) {
850  return wfSetVar( $this->mOptions, $x );
851  }
852 
856  public function nextLinkID() {
857  return $this->mLinkID++;
858  }
859 
863  public function setLinkID( $id ) {
864  $this->mLinkID = $id;
865  }
866 
871  public function getFunctionLang() {
872  return $this->getTargetLanguage();
873  }
874 
884  public function getTargetLanguage() {
885  $target = $this->mOptions->getTargetLanguage();
886 
887  if ( $target !== null ) {
888  return $target;
889  } elseif ( $this->mOptions->getInterfaceMessage() ) {
890  return $this->mOptions->getUserLangObj();
891  } elseif ( is_null( $this->mTitle ) ) {
892  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
893  }
894 
895  return $this->mTitle->getPageLanguage();
896  }
897 
902  public function getConverterLanguage() {
903  return $this->getTargetLanguage();
904  }
905 
912  public function getUser() {
913  if ( !is_null( $this->mUser ) ) {
914  return $this->mUser;
915  }
916  return $this->mOptions->getUser();
917  }
918 
924  public function getPreprocessor() {
925  if ( !isset( $this->mPreprocessor ) ) {
926  $class = $this->mPreprocessorClass;
927  $this->mPreprocessor = new $class( $this );
928  }
929  return $this->mPreprocessor;
930  }
931 
938  public function getLinkRenderer() {
939  if ( !$this->mLinkRenderer ) {
940  $this->mLinkRenderer = MediaWikiServices::getInstance()
941  ->getLinkRendererFactory()->create();
942  $this->mLinkRenderer->setStubThreshold(
943  $this->getOptions()->getStubThreshold()
944  );
945  }
946 
947  return $this->mLinkRenderer;
948  }
949 
971  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
972  if ( $uniq_prefix !== null ) {
973  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
974  }
975  static $n = 1;
976  $stripped = '';
977  $matches = [];
978 
979  $taglist = implode( '|', $elements );
980  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
981 
982  while ( $text != '' ) {
983  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
984  $stripped .= $p[0];
985  if ( count( $p ) < 5 ) {
986  break;
987  }
988  if ( count( $p ) > 5 ) {
989  # comment
990  $element = $p[4];
991  $attributes = '';
992  $close = '';
993  $inside = $p[5];
994  } else {
995  # tag
996  $element = $p[1];
997  $attributes = $p[2];
998  $close = $p[3];
999  $inside = $p[4];
1000  }
1001 
1002  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
1003  $stripped .= $marker;
1004 
1005  if ( $close === '/>' ) {
1006  # Empty element tag, <tag />
1007  $content = null;
1008  $text = $inside;
1009  $tail = null;
1010  } else {
1011  if ( $element === '!--' ) {
1012  $end = '/(-->)/';
1013  } else {
1014  $end = "/(<\\/$element\\s*>)/i";
1015  }
1016  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
1017  $content = $q[0];
1018  if ( count( $q ) < 3 ) {
1019  # No end tag -- let it run out to the end of the text.
1020  $tail = '';
1021  $text = '';
1022  } else {
1023  $tail = $q[1];
1024  $text = $q[2];
1025  }
1026  }
1027 
1028  $matches[$marker] = [ $element,
1029  $content,
1030  Sanitizer::decodeTagAttributes( $attributes ),
1031  "<$element$attributes$close$content$tail" ];
1032  }
1033  return $stripped;
1034  }
1035 
1041  public function getStripList() {
1042  return $this->mStripList;
1043  }
1044 
1054  public function insertStripItem( $text ) {
1055  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1056  $this->mMarkerIndex++;
1057  $this->mStripState->addGeneral( $marker, $text );
1058  return $marker;
1059  }
1060 
1068  public function doTableStuff( $text ) {
1069 
1070  $lines = StringUtils::explode( "\n", $text );
1071  $out = '';
1072  $td_history = []; # Is currently a td tag open?
1073  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1074  $tr_history = []; # Is currently a tr tag open?
1075  $tr_attributes = []; # history of tr attributes
1076  $has_opened_tr = []; # Did this table open a <tr> element?
1077  $indent_level = 0; # indent level of the table
1078 
1079  foreach ( $lines as $outLine ) {
1080  $line = trim( $outLine );
1081 
1082  if ( $line === '' ) { # empty line, go to next line
1083  $out .= $outLine . "\n";
1084  continue;
1085  }
1086 
1087  $first_character = $line[0];
1088  $first_two = substr( $line, 0, 2 );
1089  $matches = [];
1090 
1091  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1092  # First check if we are starting a new table
1093  $indent_level = strlen( $matches[1] );
1094 
1095  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1096  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1097 
1098  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1099  array_push( $td_history, false );
1100  array_push( $last_tag_history, '' );
1101  array_push( $tr_history, false );
1102  array_push( $tr_attributes, '' );
1103  array_push( $has_opened_tr, false );
1104  } elseif ( count( $td_history ) == 0 ) {
1105  # Don't do any of the following
1106  $out .= $outLine . "\n";
1107  continue;
1108  } elseif ( $first_two === '|}' ) {
1109  # We are ending a table
1110  $line = '</table>' . substr( $line, 2 );
1111  $last_tag = array_pop( $last_tag_history );
1112 
1113  if ( !array_pop( $has_opened_tr ) ) {
1114  $line = "<tr><td></td></tr>{$line}";
1115  }
1116 
1117  if ( array_pop( $tr_history ) ) {
1118  $line = "</tr>{$line}";
1119  }
1120 
1121  if ( array_pop( $td_history ) ) {
1122  $line = "</{$last_tag}>{$line}";
1123  }
1124  array_pop( $tr_attributes );
1125  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1126  } elseif ( $first_two === '|-' ) {
1127  # Now we have a table row
1128  $line = preg_replace( '#^\|-+#', '', $line );
1129 
1130  # Whats after the tag is now only attributes
1131  $attributes = $this->mStripState->unstripBoth( $line );
1132  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1133  array_pop( $tr_attributes );
1134  array_push( $tr_attributes, $attributes );
1135 
1136  $line = '';
1137  $last_tag = array_pop( $last_tag_history );
1138  array_pop( $has_opened_tr );
1139  array_push( $has_opened_tr, true );
1140 
1141  if ( array_pop( $tr_history ) ) {
1142  $line = '</tr>';
1143  }
1144 
1145  if ( array_pop( $td_history ) ) {
1146  $line = "</{$last_tag}>{$line}";
1147  }
1148 
1149  $outLine = $line;
1150  array_push( $tr_history, false );
1151  array_push( $td_history, false );
1152  array_push( $last_tag_history, '' );
1153  } elseif ( $first_character === '|'
1154  || $first_character === '!'
1155  || $first_two === '|+'
1156  ) {
1157  # This might be cell elements, td, th or captions
1158  if ( $first_two === '|+' ) {
1159  $first_character = '+';
1160  $line = substr( $line, 2 );
1161  } else {
1162  $line = substr( $line, 1 );
1163  }
1164 
1165  // Implies both are valid for table headings.
1166  if ( $first_character === '!' ) {
1167  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1168  }
1169 
1170  # Split up multiple cells on the same line.
1171  # FIXME : This can result in improper nesting of tags processed
1172  # by earlier parser steps.
1173  $cells = explode( '||', $line );
1174 
1175  $outLine = '';
1176 
1177  # Loop through each table cell
1178  foreach ( $cells as $cell ) {
1179  $previous = '';
1180  if ( $first_character !== '+' ) {
1181  $tr_after = array_pop( $tr_attributes );
1182  if ( !array_pop( $tr_history ) ) {
1183  $previous = "<tr{$tr_after}>\n";
1184  }
1185  array_push( $tr_history, true );
1186  array_push( $tr_attributes, '' );
1187  array_pop( $has_opened_tr );
1188  array_push( $has_opened_tr, true );
1189  }
1190 
1191  $last_tag = array_pop( $last_tag_history );
1192 
1193  if ( array_pop( $td_history ) ) {
1194  $previous = "</{$last_tag}>\n{$previous}";
1195  }
1196 
1197  if ( $first_character === '|' ) {
1198  $last_tag = 'td';
1199  } elseif ( $first_character === '!' ) {
1200  $last_tag = 'th';
1201  } elseif ( $first_character === '+' ) {
1202  $last_tag = 'caption';
1203  } else {
1204  $last_tag = '';
1205  }
1206 
1207  array_push( $last_tag_history, $last_tag );
1208 
1209  # A cell could contain both parameters and data
1210  $cell_data = explode( '|', $cell, 2 );
1211 
1212  # T2553: Note that a '|' inside an invalid link should not
1213  # be mistaken as delimiting cell parameters
1214  # Bug T153140: Neither should language converter markup.
1215  if ( preg_match( '/\[\[|-\{/', $cell_data[0] ) === 1 ) {
1216  $cell = "{$previous}<{$last_tag}>{$cell}";
1217  } elseif ( count( $cell_data ) == 1 ) {
1218  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1219  } else {
1220  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1221  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1222  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1223  }
1224 
1225  $outLine .= $cell;
1226  array_push( $td_history, true );
1227  }
1228  }
1229  $out .= $outLine . "\n";
1230  }
1231 
1232  # Closing open td, tr && table
1233  while ( count( $td_history ) > 0 ) {
1234  if ( array_pop( $td_history ) ) {
1235  $out .= "</td>\n";
1236  }
1237  if ( array_pop( $tr_history ) ) {
1238  $out .= "</tr>\n";
1239  }
1240  if ( !array_pop( $has_opened_tr ) ) {
1241  $out .= "<tr><td></td></tr>\n";
1242  }
1243 
1244  $out .= "</table>\n";
1245  }
1246 
1247  # Remove trailing line-ending (b/c)
1248  if ( substr( $out, -1 ) === "\n" ) {
1249  $out = substr( $out, 0, -1 );
1250  }
1251 
1252  # special case: don't return empty table
1253  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1254  $out = '';
1255  }
1256 
1257  return $out;
1258  }
1259 
1272  public function internalParse( $text, $isMain = true, $frame = false ) {
1273 
1274  $origText = $text;
1275 
1276  // Avoid PHP 7.1 warning from passing $this by reference
1277  $parser = $this;
1278 
1279  # Hook to suspend the parser in this state
1280  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$parser, &$text, &$this->mStripState ] ) ) {
1281  return $text;
1282  }
1283 
1284  # if $frame is provided, then use $frame for replacing any variables
1285  if ( $frame ) {
1286  # use frame depth to infer how include/noinclude tags should be handled
1287  # depth=0 means this is the top-level document; otherwise it's an included document
1288  if ( !$frame->depth ) {
1289  $flag = 0;
1290  } else {
1291  $flag = Parser::PTD_FOR_INCLUSION;
1292  }
1293  $dom = $this->preprocessToDom( $text, $flag );
1294  $text = $frame->expand( $dom );
1295  } else {
1296  # if $frame is not provided, then use old-style replaceVariables
1297  $text = $this->replaceVariables( $text );
1298  }
1299 
1300  Hooks::run( 'InternalParseBeforeSanitize', [ &$parser, &$text, &$this->mStripState ] );
1301  $text = Sanitizer::removeHTMLtags(
1302  $text,
1303  [ $this, 'attributeStripCallback' ],
1304  false,
1305  array_keys( $this->mTransparentTagHooks ),
1306  [],
1307  [ $this, 'addTrackingCategory' ]
1308  );
1309  Hooks::run( 'InternalParseBeforeLinks', [ &$parser, &$text, &$this->mStripState ] );
1310 
1311  # Tables need to come after variable replacement for things to work
1312  # properly; putting them before other transformations should keep
1313  # exciting things like link expansions from showing up in surprising
1314  # places.
1315  $text = $this->doTableStuff( $text );
1316 
1317  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1318 
1319  $text = $this->doDoubleUnderscore( $text );
1320 
1321  $text = $this->doHeadings( $text );
1322  $text = $this->replaceInternalLinks( $text );
1323  $text = $this->doAllQuotes( $text );
1324  $text = $this->replaceExternalLinks( $text );
1325 
1326  # replaceInternalLinks may sometimes leave behind
1327  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1328  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1329 
1330  $text = $this->doMagicLinks( $text );
1331  $text = $this->formatHeadings( $text, $origText, $isMain );
1332 
1333  return $text;
1334  }
1335 
1345  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1346  $text = $this->mStripState->unstripGeneral( $text );
1347 
1348  // Avoid PHP 7.1 warning from passing $this by reference
1349  $parser = $this;
1350 
1351  if ( $isMain ) {
1352  Hooks::run( 'ParserAfterUnstrip', [ &$parser, &$text ] );
1353  }
1354 
1355  # Clean up special characters, only run once, next-to-last before doBlockLevels
1356  $fixtags = [
1357  # French spaces, last one Guillemet-left
1358  # only if there is something before the space
1359  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1360  # french spaces, Guillemet-right
1361  '/(\\302\\253) /' => '\\1&#160;',
1362  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, T13874.
1363  ];
1364  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1365 
1366  $text = $this->doBlockLevels( $text, $linestart );
1367 
1368  $this->replaceLinkHolders( $text );
1369 
1377  if ( !( $this->mOptions->getDisableContentConversion()
1378  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1379  ) {
1380  if ( !$this->mOptions->getInterfaceMessage() ) {
1381  # The position of the convert() call should not be changed. it
1382  # assumes that the links are all replaced and the only thing left
1383  # is the <nowiki> mark.
1384  $text = $this->getConverterLanguage()->convert( $text );
1385  }
1386  }
1387 
1388  $text = $this->mStripState->unstripNoWiki( $text );
1389 
1390  if ( $isMain ) {
1391  Hooks::run( 'ParserBeforeTidy', [ &$parser, &$text ] );
1392  }
1393 
1394  $text = $this->replaceTransparentTags( $text );
1395  $text = $this->mStripState->unstripGeneral( $text );
1396 
1397  $text = Sanitizer::normalizeCharReferences( $text );
1398 
1399  if ( MWTidy::isEnabled() ) {
1400  if ( $this->mOptions->getTidy() ) {
1401  $text = MWTidy::tidy( $text );
1402  }
1403  } else {
1404  # attempt to sanitize at least some nesting problems
1405  # (T4702 and quite a few others)
1406  $tidyregs = [
1407  # ''Something [http://www.cool.com cool''] -->
1408  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1409  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1410  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1411  # fix up an anchor inside another anchor, only
1412  # at least for a single single nested link (T5695)
1413  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1414  '\\1\\2</a>\\3</a>\\1\\4</a>',
1415  # fix div inside inline elements- doBlockLevels won't wrap a line which
1416  # contains a div, so fix it up here; replace
1417  # div with escaped text
1418  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1419  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1420  # remove empty italic or bold tag pairs, some
1421  # introduced by rules above
1422  '/<([bi])><\/\\1>/' => '',
1423  ];
1424 
1425  $text = preg_replace(
1426  array_keys( $tidyregs ),
1427  array_values( $tidyregs ),
1428  $text );
1429  }
1430 
1431  if ( $isMain ) {
1432  Hooks::run( 'ParserAfterTidy', [ &$parser, &$text ] );
1433  }
1434 
1435  return $text;
1436  }
1437 
1449  public function doMagicLinks( $text ) {
1450  $prots = wfUrlProtocolsWithoutProtRel();
1451  $urlChar = self::EXT_LINK_URL_CLASS;
1452  $addr = self::EXT_LINK_ADDR;
1453  $space = self::SPACE_NOT_NL; # non-newline space
1454  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1455  $spaces = "$space++"; # possessive match of 1 or more spaces
1456  $text = preg_replace_callback(
1457  '!(?: # Start cases
1458  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1459  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1460  (\b # m[3]: Free external links
1461  (?i:$prots)
1462  ($addr$urlChar*) # m[4]: Post-protocol path
1463  ) |
1464  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1465  ([0-9]+)\b |
1466  \bISBN $spaces ( # m[6]: ISBN, capture number
1467  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1468  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1469  [0-9Xx] # check digit
1470  )\b
1471  )!xu", [ $this, 'magicLinkCallback' ], $text );
1472  return $text;
1473  }
1474 
1480  public function magicLinkCallback( $m ) {
1481  if ( isset( $m[1] ) && $m[1] !== '' ) {
1482  # Skip anchor
1483  return $m[0];
1484  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1485  # Skip HTML element
1486  return $m[0];
1487  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1488  # Free external link
1489  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1490  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1491  # RFC or PMID
1492  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1493  if ( !$this->mOptions->getMagicRFCLinks() ) {
1494  return $m[0];
1495  }
1496  $keyword = 'RFC';
1497  $urlmsg = 'rfcurl';
1498  $cssClass = 'mw-magiclink-rfc';
1499  $trackingCat = 'magiclink-tracking-rfc';
1500  $id = $m[5];
1501  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1502  if ( !$this->mOptions->getMagicPMIDLinks() ) {
1503  return $m[0];
1504  }
1505  $keyword = 'PMID';
1506  $urlmsg = 'pubmedurl';
1507  $cssClass = 'mw-magiclink-pmid';
1508  $trackingCat = 'magiclink-tracking-pmid';
1509  $id = $m[5];
1510  } else {
1511  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1512  substr( $m[0], 0, 20 ) . '"' );
1513  }
1514  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1515  $this->addTrackingCategory( $trackingCat );
1516  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1517  } elseif ( isset( $m[6] ) && $m[6] !== ''
1518  && $this->mOptions->getMagicISBNLinks()
1519  ) {
1520  # ISBN
1521  $isbn = $m[6];
1522  $space = self::SPACE_NOT_NL; # non-newline space
1523  $isbn = preg_replace( "/$space/", ' ', $isbn );
1524  $num = strtr( $isbn, [
1525  '-' => '',
1526  ' ' => '',
1527  'x' => 'X',
1528  ] );
1529  $this->addTrackingCategory( 'magiclink-tracking-isbn' );
1530  return $this->getLinkRenderer()->makeKnownLink(
1531  SpecialPage::getTitleFor( 'Booksources', $num ),
1532  "ISBN $isbn",
1533  [
1534  'class' => 'internal mw-magiclink-isbn',
1535  'title' => false // suppress title attribute
1536  ]
1537  );
1538  } else {
1539  return $m[0];
1540  }
1541  }
1542 
1552  public function makeFreeExternalLink( $url, $numPostProto ) {
1553  $trail = '';
1554 
1555  # The characters '<' and '>' (which were escaped by
1556  # removeHTMLtags()) should not be included in
1557  # URLs, per RFC 2396.
1558  # Make &nbsp; terminate a URL as well (bug T84937)
1559  $m2 = [];
1560  if ( preg_match(
1561  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1562  $url,
1563  $m2,
1564  PREG_OFFSET_CAPTURE
1565  ) ) {
1566  $trail = substr( $url, $m2[0][1] ) . $trail;
1567  $url = substr( $url, 0, $m2[0][1] );
1568  }
1569 
1570  # Move trailing punctuation to $trail
1571  $sep = ',;\.:!?';
1572  # If there is no left bracket, then consider right brackets fair game too
1573  if ( strpos( $url, '(' ) === false ) {
1574  $sep .= ')';
1575  }
1576 
1577  $urlRev = strrev( $url );
1578  $numSepChars = strspn( $urlRev, $sep );
1579  # Don't break a trailing HTML entity by moving the ; into $trail
1580  # This is in hot code, so use substr_compare to avoid having to
1581  # create a new string object for the comparison
1582  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1583  # more optimization: instead of running preg_match with a $
1584  # anchor, which can be slow, do the match on the reversed
1585  # string starting at the desired offset.
1586  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1587  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1588  $numSepChars--;
1589  }
1590  }
1591  if ( $numSepChars ) {
1592  $trail = substr( $url, -$numSepChars ) . $trail;
1593  $url = substr( $url, 0, -$numSepChars );
1594  }
1595 
1596  # Verify that we still have a real URL after trail removal, and
1597  # not just lone protocol
1598  if ( strlen( $trail ) >= $numPostProto ) {
1599  return $url . $trail;
1600  }
1601 
1602  $url = Sanitizer::cleanUrl( $url );
1603 
1604  # Is this an external image?
1605  $text = $this->maybeMakeExternalImage( $url );
1606  if ( $text === false ) {
1607  # Not an image, make a link
1608  $text = Linker::makeExternalLink( $url,
1609  $this->getConverterLanguage()->markNoConversion( $url, true ),
1610  true, 'free',
1611  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1612  # Register it in the output object...
1613  $this->mOutput->addExternalLink( $url );
1614  }
1615  return $text . $trail;
1616  }
1617 
1627  public function doHeadings( $text ) {
1628  for ( $i = 6; $i >= 1; --$i ) {
1629  $h = str_repeat( '=', $i );
1630  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1631  }
1632  return $text;
1633  }
1634 
1643  public function doAllQuotes( $text ) {
1644  $outtext = '';
1645  $lines = StringUtils::explode( "\n", $text );
1646  foreach ( $lines as $line ) {
1647  $outtext .= $this->doQuotes( $line ) . "\n";
1648  }
1649  $outtext = substr( $outtext, 0, -1 );
1650  return $outtext;
1651  }
1652 
1660  public function doQuotes( $text ) {
1661  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1662  $countarr = count( $arr );
1663  if ( $countarr == 1 ) {
1664  return $text;
1665  }
1666 
1667  // First, do some preliminary work. This may shift some apostrophes from
1668  // being mark-up to being text. It also counts the number of occurrences
1669  // of bold and italics mark-ups.
1670  $numbold = 0;
1671  $numitalics = 0;
1672  for ( $i = 1; $i < $countarr; $i += 2 ) {
1673  $thislen = strlen( $arr[$i] );
1674  // If there are ever four apostrophes, assume the first is supposed to
1675  // be text, and the remaining three constitute mark-up for bold text.
1676  // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
1677  if ( $thislen == 4 ) {
1678  $arr[$i - 1] .= "'";
1679  $arr[$i] = "'''";
1680  $thislen = 3;
1681  } elseif ( $thislen > 5 ) {
1682  // If there are more than 5 apostrophes in a row, assume they're all
1683  // text except for the last 5.
1684  // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1685  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1686  $arr[$i] = "'''''";
1687  $thislen = 5;
1688  }
1689  // Count the number of occurrences of bold and italics mark-ups.
1690  if ( $thislen == 2 ) {
1691  $numitalics++;
1692  } elseif ( $thislen == 3 ) {
1693  $numbold++;
1694  } elseif ( $thislen == 5 ) {
1695  $numitalics++;
1696  $numbold++;
1697  }
1698  }
1699 
1700  // If there is an odd number of both bold and italics, it is likely
1701  // that one of the bold ones was meant to be an apostrophe followed
1702  // by italics. Which one we cannot know for certain, but it is more
1703  // likely to be one that has a single-letter word before it.
1704  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1705  $firstsingleletterword = -1;
1706  $firstmultiletterword = -1;
1707  $firstspace = -1;
1708  for ( $i = 1; $i < $countarr; $i += 2 ) {
1709  if ( strlen( $arr[$i] ) == 3 ) {
1710  $x1 = substr( $arr[$i - 1], -1 );
1711  $x2 = substr( $arr[$i - 1], -2, 1 );
1712  if ( $x1 === ' ' ) {
1713  if ( $firstspace == -1 ) {
1714  $firstspace = $i;
1715  }
1716  } elseif ( $x2 === ' ' ) {
1717  $firstsingleletterword = $i;
1718  // if $firstsingleletterword is set, we don't
1719  // look at the other options, so we can bail early.
1720  break;
1721  } else {
1722  if ( $firstmultiletterword == -1 ) {
1723  $firstmultiletterword = $i;
1724  }
1725  }
1726  }
1727  }
1728 
1729  // If there is a single-letter word, use it!
1730  if ( $firstsingleletterword > -1 ) {
1731  $arr[$firstsingleletterword] = "''";
1732  $arr[$firstsingleletterword - 1] .= "'";
1733  } elseif ( $firstmultiletterword > -1 ) {
1734  // If not, but there's a multi-letter word, use that one.
1735  $arr[$firstmultiletterword] = "''";
1736  $arr[$firstmultiletterword - 1] .= "'";
1737  } elseif ( $firstspace > -1 ) {
1738  // ... otherwise use the first one that has neither.
1739  // (notice that it is possible for all three to be -1 if, for example,
1740  // there is only one pentuple-apostrophe in the line)
1741  $arr[$firstspace] = "''";
1742  $arr[$firstspace - 1] .= "'";
1743  }
1744  }
1745 
1746  // Now let's actually convert our apostrophic mush to HTML!
1747  $output = '';
1748  $buffer = '';
1749  $state = '';
1750  $i = 0;
1751  foreach ( $arr as $r ) {
1752  if ( ( $i % 2 ) == 0 ) {
1753  if ( $state === 'both' ) {
1754  $buffer .= $r;
1755  } else {
1756  $output .= $r;
1757  }
1758  } else {
1759  $thislen = strlen( $r );
1760  if ( $thislen == 2 ) {
1761  if ( $state === 'i' ) {
1762  $output .= '</i>';
1763  $state = '';
1764  } elseif ( $state === 'bi' ) {
1765  $output .= '</i>';
1766  $state = 'b';
1767  } elseif ( $state === 'ib' ) {
1768  $output .= '</b></i><b>';
1769  $state = 'b';
1770  } elseif ( $state === 'both' ) {
1771  $output .= '<b><i>' . $buffer . '</i>';
1772  $state = 'b';
1773  } else { // $state can be 'b' or ''
1774  $output .= '<i>';
1775  $state .= 'i';
1776  }
1777  } elseif ( $thislen == 3 ) {
1778  if ( $state === 'b' ) {
1779  $output .= '</b>';
1780  $state = '';
1781  } elseif ( $state === 'bi' ) {
1782  $output .= '</i></b><i>';
1783  $state = 'i';
1784  } elseif ( $state === 'ib' ) {
1785  $output .= '</b>';
1786  $state = 'i';
1787  } elseif ( $state === 'both' ) {
1788  $output .= '<i><b>' . $buffer . '</b>';
1789  $state = 'i';
1790  } else { // $state can be 'i' or ''
1791  $output .= '<b>';
1792  $state .= 'b';
1793  }
1794  } elseif ( $thislen == 5 ) {
1795  if ( $state === 'b' ) {
1796  $output .= '</b><i>';
1797  $state = 'i';
1798  } elseif ( $state === 'i' ) {
1799  $output .= '</i><b>';
1800  $state = 'b';
1801  } elseif ( $state === 'bi' ) {
1802  $output .= '</i></b>';
1803  $state = '';
1804  } elseif ( $state === 'ib' ) {
1805  $output .= '</b></i>';
1806  $state = '';
1807  } elseif ( $state === 'both' ) {
1808  $output .= '<i><b>' . $buffer . '</b></i>';
1809  $state = '';
1810  } else { // ($state == '')
1811  $buffer = '';
1812  $state = 'both';
1813  }
1814  }
1815  }
1816  $i++;
1817  }
1818  // Now close all remaining tags. Notice that the order is important.
1819  if ( $state === 'b' || $state === 'ib' ) {
1820  $output .= '</b>';
1821  }
1822  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1823  $output .= '</i>';
1824  }
1825  if ( $state === 'bi' ) {
1826  $output .= '</b>';
1827  }
1828  // There might be lonely ''''', so make sure we have a buffer
1829  if ( $state === 'both' && $buffer ) {
1830  $output .= '<b><i>' . $buffer . '</i></b>';
1831  }
1832  return $output;
1833  }
1834 
1848  public function replaceExternalLinks( $text ) {
1849 
1850  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1851  if ( $bits === false ) {
1852  throw new MWException( "PCRE needs to be compiled with "
1853  . "--enable-unicode-properties in order for MediaWiki to function" );
1854  }
1855  $s = array_shift( $bits );
1856 
1857  $i = 0;
1858  while ( $i < count( $bits ) ) {
1859  $url = $bits[$i++];
1860  $i++; // protocol
1861  $text = $bits[$i++];
1862  $trail = $bits[$i++];
1863 
1864  # The characters '<' and '>' (which were escaped by
1865  # removeHTMLtags()) should not be included in
1866  # URLs, per RFC 2396.
1867  $m2 = [];
1868  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1869  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1870  $url = substr( $url, 0, $m2[0][1] );
1871  }
1872 
1873  # If the link text is an image URL, replace it with an <img> tag
1874  # This happened by accident in the original parser, but some people used it extensively
1875  $img = $this->maybeMakeExternalImage( $text );
1876  if ( $img !== false ) {
1877  $text = $img;
1878  }
1879 
1880  $dtrail = '';
1881 
1882  # Set linktype for CSS - if URL==text, link is essentially free
1883  $linktype = ( $text === $url ) ? 'free' : 'text';
1884 
1885  # No link text, e.g. [http://domain.tld/some.link]
1886  if ( $text == '' ) {
1887  # Autonumber
1888  $langObj = $this->getTargetLanguage();
1889  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1890  $linktype = 'autonumber';
1891  } else {
1892  # Have link text, e.g. [http://domain.tld/some.link text]s
1893  # Check for trail
1894  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1895  }
1896 
1897  $text = $this->getConverterLanguage()->markNoConversion( $text );
1898 
1899  $url = Sanitizer::cleanUrl( $url );
1900 
1901  # Use the encoded URL
1902  # This means that users can paste URLs directly into the text
1903  # Funny characters like ö aren't valid in URLs anyway
1904  # This was changed in August 2004
1905  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1906  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1907 
1908  # Register link in the output object.
1909  $this->mOutput->addExternalLink( $url );
1910  }
1911 
1912  return $s;
1913  }
1914 
1924  public static function getExternalLinkRel( $url = false, $title = null ) {
1925  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1926  $ns = $title ? $title->getNamespace() : false;
1927  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1928  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1929  ) {
1930  return 'nofollow';
1931  }
1932  return null;
1933  }
1934 
1945  public function getExternalLinkAttribs( $url ) {
1946  $attribs = [];
1947  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1948 
1949  $target = $this->mOptions->getExternalLinkTarget();
1950  if ( $target ) {
1951  $attribs['target'] = $target;
1952  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1953  // T133507. New windows can navigate parent cross-origin.
1954  // Including noreferrer due to lacking browser
1955  // support of noopener. Eventually noreferrer should be removed.
1956  if ( $rel !== '' ) {
1957  $rel .= ' ';
1958  }
1959  $rel .= 'noreferrer noopener';
1960  }
1961  }
1962  $attribs['rel'] = $rel;
1963  return $attribs;
1964  }
1965 
1975  public static function normalizeLinkUrl( $url ) {
1976  # First, make sure unsafe characters are encoded
1977  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1978  function ( $m ) {
1979  return rawurlencode( $m[0] );
1980  },
1981  $url
1982  );
1983 
1984  $ret = '';
1985  $end = strlen( $url );
1986 
1987  # Fragment part - 'fragment'
1988  $start = strpos( $url, '#' );
1989  if ( $start !== false && $start < $end ) {
1990  $ret = self::normalizeUrlComponent(
1991  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1992  $end = $start;
1993  }
1994 
1995  # Query part - 'query' minus &=+;
1996  $start = strpos( $url, '?' );
1997  if ( $start !== false && $start < $end ) {
1998  $ret = self::normalizeUrlComponent(
1999  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
2000  $end = $start;
2001  }
2002 
2003  # Scheme and path part - 'pchar'
2004  # (we assume no userinfo or encoded colons in the host)
2005  $ret = self::normalizeUrlComponent(
2006  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
2007 
2008  return $ret;
2009  }
2010 
2011  private static function normalizeUrlComponent( $component, $unsafe ) {
2012  $callback = function ( $matches ) use ( $unsafe ) {
2013  $char = urldecode( $matches[0] );
2014  $ord = ord( $char );
2015  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
2016  # Unescape it
2017  return $char;
2018  } else {
2019  # Leave it escaped, but use uppercase for a-f
2020  return strtoupper( $matches[0] );
2021  }
2022  };
2023  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
2024  }
2025 
2034  private function maybeMakeExternalImage( $url ) {
2035  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
2036  $imagesexception = !empty( $imagesfrom );
2037  $text = false;
2038  # $imagesfrom could be either a single string or an array of strings, parse out the latter
2039  if ( $imagesexception && is_array( $imagesfrom ) ) {
2040  $imagematch = false;
2041  foreach ( $imagesfrom as $match ) {
2042  if ( strpos( $url, $match ) === 0 ) {
2043  $imagematch = true;
2044  break;
2045  }
2046  }
2047  } elseif ( $imagesexception ) {
2048  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
2049  } else {
2050  $imagematch = false;
2051  }
2052 
2053  if ( $this->mOptions->getAllowExternalImages()
2054  || ( $imagesexception && $imagematch )
2055  ) {
2056  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2057  # Image found
2058  $text = Linker::makeExternalImage( $url );
2059  }
2060  }
2061  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2062  && preg_match( self::EXT_IMAGE_REGEX, $url )
2063  ) {
2064  $whitelist = explode(
2065  "\n",
2066  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2067  );
2068 
2069  foreach ( $whitelist as $entry ) {
2070  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2071  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2072  continue;
2073  }
2074  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2075  # Image matches a whitelist entry
2076  $text = Linker::makeExternalImage( $url );
2077  break;
2078  }
2079  }
2080  }
2081  return $text;
2082  }
2083 
2093  public function replaceInternalLinks( $s ) {
2094  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2095  return $s;
2096  }
2097 
2106  public function replaceInternalLinks2( &$s ) {
2108 
2109  static $tc = false, $e1, $e1_img;
2110  # the % is needed to support urlencoded titles as well
2111  if ( !$tc ) {
2112  $tc = Title::legalChars() . '#%';
2113  # Match a link having the form [[namespace:link|alternate]]trail
2114  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2115  # Match cases where there is no "]]", which might still be images
2116  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2117  }
2118 
2119  $holders = new LinkHolderArray( $this );
2120 
2121  # split the entire text string on occurrences of [[
2122  $a = StringUtils::explode( '[[', ' ' . $s );
2123  # get the first element (all text up to first [[), and remove the space we added
2124  $s = $a->current();
2125  $a->next();
2126  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2127  $s = substr( $s, 1 );
2128 
2129  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2130  $e2 = null;
2131  if ( $useLinkPrefixExtension ) {
2132  # Match the end of a line for a word that's not followed by whitespace,
2133  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2135  $charset = $wgContLang->linkPrefixCharset();
2136  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2137  }
2138 
2139  if ( is_null( $this->mTitle ) ) {
2140  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2141  }
2142  $nottalk = !$this->mTitle->isTalkPage();
2143 
2144  if ( $useLinkPrefixExtension ) {
2145  $m = [];
2146  if ( preg_match( $e2, $s, $m ) ) {
2147  $first_prefix = $m[2];
2148  } else {
2149  $first_prefix = false;
2150  }
2151  } else {
2152  $prefix = '';
2153  }
2154 
2155  $useSubpages = $this->areSubpagesAllowed();
2156 
2157  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2158  # Loop for each link
2159  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2160  // @codingStandardsIgnoreEnd
2161 
2162  # Check for excessive memory usage
2163  if ( $holders->isBig() ) {
2164  # Too big
2165  # Do the existence check, replace the link holders and clear the array
2166  $holders->replace( $s );
2167  $holders->clear();
2168  }
2169 
2170  if ( $useLinkPrefixExtension ) {
2171  if ( preg_match( $e2, $s, $m ) ) {
2172  $prefix = $m[2];
2173  $s = $m[1];
2174  } else {
2175  $prefix = '';
2176  }
2177  # first link
2178  if ( $first_prefix ) {
2179  $prefix = $first_prefix;
2180  $first_prefix = false;
2181  }
2182  }
2183 
2184  $might_be_img = false;
2185 
2186  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2187  $text = $m[2];
2188  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2189  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2190  # the real problem is with the $e1 regex
2191  # See T1500.
2192  # Still some problems for cases where the ] is meant to be outside punctuation,
2193  # and no image is in sight. See T4095.
2194  if ( $text !== ''
2195  && substr( $m[3], 0, 1 ) === ']'
2196  && strpos( $text, '[' ) !== false
2197  ) {
2198  $text .= ']'; # so that replaceExternalLinks($text) works later
2199  $m[3] = substr( $m[3], 1 );
2200  }
2201  # fix up urlencoded title texts
2202  if ( strpos( $m[1], '%' ) !== false ) {
2203  # Should anchors '#' also be rejected?
2204  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2205  }
2206  $trail = $m[3];
2207  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2208  # Invalid, but might be an image with a link in its caption
2209  $might_be_img = true;
2210  $text = $m[2];
2211  if ( strpos( $m[1], '%' ) !== false ) {
2212  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2213  }
2214  $trail = "";
2215  } else { # Invalid form; output directly
2216  $s .= $prefix . '[[' . $line;
2217  continue;
2218  }
2219 
2220  $origLink = ltrim( $m[1], ' ' );
2221 
2222  # Don't allow internal links to pages containing
2223  # PROTO: where PROTO is a valid URL protocol; these
2224  # should be external links.
2225  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2226  $s .= $prefix . '[[' . $line;
2227  continue;
2228  }
2229 
2230  # Make subpage if necessary
2231  if ( $useSubpages ) {
2232  $link = $this->maybeDoSubpageLink( $origLink, $text );
2233  } else {
2234  $link = $origLink;
2235  }
2236 
2237  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2238  if ( !$noforce ) {
2239  # Strip off leading ':'
2240  $link = substr( $link, 1 );
2241  }
2242 
2243  $unstrip = $this->mStripState->unstripNoWiki( $link );
2244  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2245  if ( $nt === null ) {
2246  $s .= $prefix . '[[' . $line;
2247  continue;
2248  }
2249 
2250  $ns = $nt->getNamespace();
2251  $iw = $nt->getInterwiki();
2252 
2253  if ( $might_be_img ) { # if this is actually an invalid link
2254  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2255  $found = false;
2256  while ( true ) {
2257  # look at the next 'line' to see if we can close it there
2258  $a->next();
2259  $next_line = $a->current();
2260  if ( $next_line === false || $next_line === null ) {
2261  break;
2262  }
2263  $m = explode( ']]', $next_line, 3 );
2264  if ( count( $m ) == 3 ) {
2265  # the first ]] closes the inner link, the second the image
2266  $found = true;
2267  $text .= "[[{$m[0]}]]{$m[1]}";
2268  $trail = $m[2];
2269  break;
2270  } elseif ( count( $m ) == 2 ) {
2271  # if there's exactly one ]] that's fine, we'll keep looking
2272  $text .= "[[{$m[0]}]]{$m[1]}";
2273  } else {
2274  # if $next_line is invalid too, we need look no further
2275  $text .= '[[' . $next_line;
2276  break;
2277  }
2278  }
2279  if ( !$found ) {
2280  # we couldn't find the end of this imageLink, so output it raw
2281  # but don't ignore what might be perfectly normal links in the text we've examined
2282  $holders->merge( $this->replaceInternalLinks2( $text ) );
2283  $s .= "{$prefix}[[$link|$text";
2284  # note: no $trail, because without an end, there *is* no trail
2285  continue;
2286  }
2287  } else { # it's not an image, so output it raw
2288  $s .= "{$prefix}[[$link|$text";
2289  # note: no $trail, because without an end, there *is* no trail
2290  continue;
2291  }
2292  }
2293 
2294  $wasblank = ( $text == '' );
2295  if ( $wasblank ) {
2296  $text = $link;
2297  } else {
2298  # T6598 madness. Handle the quotes only if they come from the alternate part
2299  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2300  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2301  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2302  $text = $this->doQuotes( $text );
2303  }
2304 
2305  # Link not escaped by : , create the various objects
2306  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2307  # Interwikis
2308  if (
2309  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2310  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2311  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2312  )
2313  ) {
2314  # T26502: filter duplicates
2315  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2316  $this->mLangLinkLanguages[$iw] = true;
2317  $this->mOutput->addLanguageLink( $nt->getFullText() );
2318  }
2319 
2320  $s = rtrim( $s . $prefix );
2321  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2322  continue;
2323  }
2324 
2325  if ( $ns == NS_FILE ) {
2326  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2327  if ( $wasblank ) {
2328  # if no parameters were passed, $text
2329  # becomes something like "File:Foo.png",
2330  # which we don't want to pass on to the
2331  # image generator
2332  $text = '';
2333  } else {
2334  # recursively parse links inside the image caption
2335  # actually, this will parse them in any other parameters, too,
2336  # but it might be hard to fix that, and it doesn't matter ATM
2337  $text = $this->replaceExternalLinks( $text );
2338  $holders->merge( $this->replaceInternalLinks2( $text ) );
2339  }
2340  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2341  $s .= $prefix . $this->armorLinks(
2342  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2343  continue;
2344  }
2345  } elseif ( $ns == NS_CATEGORY ) {
2346  $s = rtrim( $s . "\n" ); # T2087
2347 
2348  if ( $wasblank ) {
2349  $sortkey = $this->getDefaultSort();
2350  } else {
2351  $sortkey = $text;
2352  }
2353  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2354  $sortkey = str_replace( "\n", '', $sortkey );
2355  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2356  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2357 
2361  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2362 
2363  continue;
2364  }
2365  }
2366 
2367  # Self-link checking. For some languages, variants of the title are checked in
2368  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2369  # for linking to a different variant.
2370  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2371  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2372  continue;
2373  }
2374 
2375  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2376  # @todo FIXME: Should do batch file existence checks, see comment below
2377  if ( $ns == NS_MEDIA ) {
2378  # Give extensions a chance to select the file revision for us
2379  $options = [];
2380  $descQuery = false;
2381  Hooks::run( 'BeforeParserFetchFileAndTitle',
2382  [ $this, $nt, &$options, &$descQuery ] );
2383  # Fetch and register the file (file title may be different via hooks)
2384  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2385  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2386  $s .= $prefix . $this->armorLinks(
2387  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2388  continue;
2389  }
2390 
2391  # Some titles, such as valid special pages or files in foreign repos, should
2392  # be shown as bluelinks even though they're not included in the page table
2393  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2394  # batch file existence checks for NS_FILE and NS_MEDIA
2395  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2396  $this->mOutput->addLink( $nt );
2397  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2398  } else {
2399  # Links will be added to the output link list after checking
2400  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2401  }
2402  }
2403  return $holders;
2404  }
2405 
2419  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2420  list( $inside, $trail ) = Linker::splitTrail( $trail );
2421 
2422  if ( $text == '' ) {
2423  $text = htmlspecialchars( $nt->getPrefixedText() );
2424  }
2425 
2426  $link = $this->getLinkRenderer()->makeKnownLink(
2427  $nt, new HtmlArmor( "$prefix$text$inside" )
2428  );
2429 
2430  return $this->armorLinks( $link ) . $trail;
2431  }
2432 
2443  public function armorLinks( $text ) {
2444  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2445  self::MARKER_PREFIX . "NOPARSE$1", $text );
2446  }
2447 
2452  public function areSubpagesAllowed() {
2453  # Some namespaces don't allow subpages
2454  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2455  }
2456 
2465  public function maybeDoSubpageLink( $target, &$text ) {
2466  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2467  }
2468 
2477  public function doBlockLevels( $text, $linestart ) {
2478  return BlockLevelPass::doBlockLevels( $text, $linestart );
2479  }
2480 
2492  public function getVariableValue( $index, $frame = false ) {
2495 
2496  if ( is_null( $this->mTitle ) ) {
2497  // If no title set, bad things are going to happen
2498  // later. Title should always be set since this
2499  // should only be called in the middle of a parse
2500  // operation (but the unit-tests do funky stuff)
2501  throw new MWException( __METHOD__ . ' Should only be '
2502  . ' called while parsing (no title set)' );
2503  }
2504 
2505  // Avoid PHP 7.1 warning from passing $this by reference
2506  $parser = $this;
2507 
2512  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) ) {
2513  if ( isset( $this->mVarCache[$index] ) ) {
2514  return $this->mVarCache[$index];
2515  }
2516  }
2517 
2518  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2519  Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
2520 
2521  $pageLang = $this->getFunctionLang();
2522 
2523  switch ( $index ) {
2524  case '!':
2525  $value = '|';
2526  break;
2527  case 'currentmonth':
2528  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2529  break;
2530  case 'currentmonth1':
2531  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2532  break;
2533  case 'currentmonthname':
2534  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2535  break;
2536  case 'currentmonthnamegen':
2537  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2538  break;
2539  case 'currentmonthabbrev':
2540  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2541  break;
2542  case 'currentday':
2543  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2544  break;
2545  case 'currentday2':
2546  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2547  break;
2548  case 'localmonth':
2549  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2550  break;
2551  case 'localmonth1':
2552  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2553  break;
2554  case 'localmonthname':
2555  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2556  break;
2557  case 'localmonthnamegen':
2558  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2559  break;
2560  case 'localmonthabbrev':
2561  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2562  break;
2563  case 'localday':
2564  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2565  break;
2566  case 'localday2':
2567  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2568  break;
2569  case 'pagename':
2570  $value = wfEscapeWikiText( $this->mTitle->getText() );
2571  break;
2572  case 'pagenamee':
2573  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2574  break;
2575  case 'fullpagename':
2576  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2577  break;
2578  case 'fullpagenamee':
2579  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2580  break;
2581  case 'subpagename':
2582  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2583  break;
2584  case 'subpagenamee':
2585  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2586  break;
2587  case 'rootpagename':
2588  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2589  break;
2590  case 'rootpagenamee':
2591  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2592  ' ',
2593  '_',
2594  $this->mTitle->getRootText()
2595  ) ) );
2596  break;
2597  case 'basepagename':
2598  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2599  break;
2600  case 'basepagenamee':
2601  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2602  ' ',
2603  '_',
2604  $this->mTitle->getBaseText()
2605  ) ) );
2606  break;
2607  case 'talkpagename':
2608  if ( $this->mTitle->canTalk() ) {
2609  $talkPage = $this->mTitle->getTalkPage();
2610  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2611  } else {
2612  $value = '';
2613  }
2614  break;
2615  case 'talkpagenamee':
2616  if ( $this->mTitle->canTalk() ) {
2617  $talkPage = $this->mTitle->getTalkPage();
2618  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2619  } else {
2620  $value = '';
2621  }
2622  break;
2623  case 'subjectpagename':
2624  $subjPage = $this->mTitle->getSubjectPage();
2625  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2626  break;
2627  case 'subjectpagenamee':
2628  $subjPage = $this->mTitle->getSubjectPage();
2629  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2630  break;
2631  case 'pageid': // requested in T25427
2632  $pageid = $this->getTitle()->getArticleID();
2633  if ( $pageid == 0 ) {
2634  # 0 means the page doesn't exist in the database,
2635  # which means the user is previewing a new page.
2636  # The vary-revision flag must be set, because the magic word
2637  # will have a different value once the page is saved.
2638  $this->mOutput->setFlag( 'vary-revision' );
2639  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2640  }
2641  $value = $pageid ? $pageid : null;
2642  break;
2643  case 'revisionid':
2644  # Let the edit saving system know we should parse the page
2645  # *after* a revision ID has been assigned.
2646  $this->mOutput->setFlag( 'vary-revision-id' );
2647  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2648  $value = $this->mRevisionId;
2649  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2650  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2651  $this->mOutput->setSpeculativeRevIdUsed( $value );
2652  }
2653  break;
2654  case 'revisionday':
2655  # Let the edit saving system know we should parse the page
2656  # *after* a revision ID has been assigned. This is for null edits.
2657  $this->mOutput->setFlag( 'vary-revision' );
2658  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2659  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2660  break;
2661  case 'revisionday2':
2662  # Let the edit saving system know we should parse the page
2663  # *after* a revision ID has been assigned. This is for null edits.
2664  $this->mOutput->setFlag( 'vary-revision' );
2665  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2666  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2667  break;
2668  case 'revisionmonth':
2669  # Let the edit saving system know we should parse the page
2670  # *after* a revision ID has been assigned. This is for null edits.
2671  $this->mOutput->setFlag( 'vary-revision' );
2672  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2673  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2674  break;
2675  case 'revisionmonth1':
2676  # Let the edit saving system know we should parse the page
2677  # *after* a revision ID has been assigned. This is for null edits.
2678  $this->mOutput->setFlag( 'vary-revision' );
2679  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2680  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2681  break;
2682  case 'revisionyear':
2683  # Let the edit saving system know we should parse the page
2684  # *after* a revision ID has been assigned. This is for null edits.
2685  $this->mOutput->setFlag( 'vary-revision' );
2686  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2687  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2688  break;
2689  case 'revisiontimestamp':
2690  # Let the edit saving system know we should parse the page
2691  # *after* a revision ID has been assigned. This is for null edits.
2692  $this->mOutput->setFlag( 'vary-revision' );
2693  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2694  $value = $this->getRevisionTimestamp();
2695  break;
2696  case 'revisionuser':
2697  # Let the edit saving system know we should parse the page
2698  # *after* a revision ID has been assigned for null edits.
2699  $this->mOutput->setFlag( 'vary-user' );
2700  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2701  $value = $this->getRevisionUser();
2702  break;
2703  case 'revisionsize':
2704  $value = $this->getRevisionSize();
2705  break;
2706  case 'namespace':
2707  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2708  break;
2709  case 'namespacee':
2710  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2711  break;
2712  case 'namespacenumber':
2713  $value = $this->mTitle->getNamespace();
2714  break;
2715  case 'talkspace':
2716  $value = $this->mTitle->canTalk()
2717  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2718  : '';
2719  break;
2720  case 'talkspacee':
2721  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2722  break;
2723  case 'subjectspace':
2724  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2725  break;
2726  case 'subjectspacee':
2727  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2728  break;
2729  case 'currentdayname':
2730  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2731  break;
2732  case 'currentyear':
2733  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2734  break;
2735  case 'currenttime':
2736  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2737  break;
2738  case 'currenthour':
2739  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2740  break;
2741  case 'currentweek':
2742  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2743  # int to remove the padding
2744  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2745  break;
2746  case 'currentdow':
2747  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2748  break;
2749  case 'localdayname':
2750  $value = $pageLang->getWeekdayName(
2751  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2752  );
2753  break;
2754  case 'localyear':
2755  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2756  break;
2757  case 'localtime':
2758  $value = $pageLang->time(
2759  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2760  false,
2761  false
2762  );
2763  break;
2764  case 'localhour':
2765  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2766  break;
2767  case 'localweek':
2768  # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
2769  # int to remove the padding
2770  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2771  break;
2772  case 'localdow':
2773  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2774  break;
2775  case 'numberofarticles':
2776  $value = $pageLang->formatNum( SiteStats::articles() );
2777  break;
2778  case 'numberoffiles':
2779  $value = $pageLang->formatNum( SiteStats::images() );
2780  break;
2781  case 'numberofusers':
2782  $value = $pageLang->formatNum( SiteStats::users() );
2783  break;
2784  case 'numberofactiveusers':
2785  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2786  break;
2787  case 'numberofpages':
2788  $value = $pageLang->formatNum( SiteStats::pages() );
2789  break;
2790  case 'numberofadmins':
2791  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2792  break;
2793  case 'numberofedits':
2794  $value = $pageLang->formatNum( SiteStats::edits() );
2795  break;
2796  case 'currenttimestamp':
2797  $value = wfTimestamp( TS_MW, $ts );
2798  break;
2799  case 'localtimestamp':
2800  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2801  break;
2802  case 'currentversion':
2804  break;
2805  case 'articlepath':
2806  return $wgArticlePath;
2807  case 'sitename':
2808  return $wgSitename;
2809  case 'server':
2810  return $wgServer;
2811  case 'servername':
2812  return $wgServerName;
2813  case 'scriptpath':
2814  return $wgScriptPath;
2815  case 'stylepath':
2816  return $wgStylePath;
2817  case 'directionmark':
2818  return $pageLang->getDirMark();
2819  case 'contentlanguage':
2821  return $wgLanguageCode;
2822  case 'pagelanguage':
2823  $value = $pageLang->getCode();
2824  break;
2825  case 'cascadingsources':
2827  break;
2828  default:
2829  $ret = null;
2830  Hooks::run(
2831  'ParserGetVariableValueSwitch',
2832  [ &$parser, &$this->mVarCache, &$index, &$ret, &$frame ]
2833  );
2834 
2835  return $ret;
2836  }
2837 
2838  if ( $index ) {
2839  $this->mVarCache[$index] = $value;
2840  }
2841 
2842  return $value;
2843  }
2844 
2850  public function initialiseVariables() {
2851  $variableIDs = MagicWord::getVariableIDs();
2852  $substIDs = MagicWord::getSubstIDs();
2853 
2854  $this->mVariables = new MagicWordArray( $variableIDs );
2855  $this->mSubstWords = new MagicWordArray( $substIDs );
2856  }
2857 
2880  public function preprocessToDom( $text, $flags = 0 ) {
2881  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2882  return $dom;
2883  }
2884 
2892  public static function splitWhitespace( $s ) {
2893  $ltrimmed = ltrim( $s );
2894  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2895  $trimmed = rtrim( $ltrimmed );
2896  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2897  if ( $diff > 0 ) {
2898  $w2 = substr( $ltrimmed, -$diff );
2899  } else {
2900  $w2 = '';
2901  }
2902  return [ $w1, $trimmed, $w2 ];
2903  }
2904 
2925  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2926  # Is there any text? Also, Prevent too big inclusions!
2927  $textSize = strlen( $text );
2928  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2929  return $text;
2930  }
2931 
2932  if ( $frame === false ) {
2933  $frame = $this->getPreprocessor()->newFrame();
2934  } elseif ( !( $frame instanceof PPFrame ) ) {
2935  wfDebug( __METHOD__ . " called using plain parameters instead of "
2936  . "a PPFrame instance. Creating custom frame.\n" );
2937  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2938  }
2939 
2940  $dom = $this->preprocessToDom( $text );
2941  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2942  $text = $frame->expand( $dom, $flags );
2943 
2944  return $text;
2945  }
2946 
2954  public static function createAssocArgs( $args ) {
2955  $assocArgs = [];
2956  $index = 1;
2957  foreach ( $args as $arg ) {
2958  $eqpos = strpos( $arg, '=' );
2959  if ( $eqpos === false ) {
2960  $assocArgs[$index++] = $arg;
2961  } else {
2962  $name = trim( substr( $arg, 0, $eqpos ) );
2963  $value = trim( substr( $arg, $eqpos + 1 ) );
2964  if ( $value === false ) {
2965  $value = '';
2966  }
2967  if ( $name !== false ) {
2968  $assocArgs[$name] = $value;
2969  }
2970  }
2971  }
2972 
2973  return $assocArgs;
2974  }
2975 
3002  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
3003  # does no harm if $current and $max are present but are unnecessary for the message
3004  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
3005  # only during preview, and that would split the parser cache unnecessarily.
3006  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
3007  ->text();
3008  $this->mOutput->addWarning( $warning );
3009  $this->addTrackingCategory( "$limitationType-category" );
3010  }
3011 
3024  public function braceSubstitution( $piece, $frame ) {
3025 
3026  // Flags
3027 
3028  // $text has been filled
3029  $found = false;
3030  // wiki markup in $text should be escaped
3031  $nowiki = false;
3032  // $text is HTML, armour it against wikitext transformation
3033  $isHTML = false;
3034  // Force interwiki transclusion to be done in raw mode not rendered
3035  $forceRawInterwiki = false;
3036  // $text is a DOM node needing expansion in a child frame
3037  $isChildObj = false;
3038  // $text is a DOM node needing expansion in the current frame
3039  $isLocalObj = false;
3040 
3041  # Title object, where $text came from
3042  $title = false;
3043 
3044  # $part1 is the bit before the first |, and must contain only title characters.
3045  # Various prefixes will be stripped from it later.
3046  $titleWithSpaces = $frame->expand( $piece['title'] );
3047  $part1 = trim( $titleWithSpaces );
3048  $titleText = false;
3049 
3050  # Original title text preserved for various purposes
3051  $originalTitle = $part1;
3052 
3053  # $args is a list of argument nodes, starting from index 0, not including $part1
3054  # @todo FIXME: If piece['parts'] is null then the call to getLength()
3055  # below won't work b/c this $args isn't an object
3056  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3057 
3058  $profileSection = null; // profile templates
3059 
3060  # SUBST
3061  if ( !$found ) {
3062  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3063 
3064  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3065  # Decide whether to expand template or keep wikitext as-is.
3066  if ( $this->ot['wiki'] ) {
3067  if ( $substMatch === false ) {
3068  $literal = true; # literal when in PST with no prefix
3069  } else {
3070  $literal = false; # expand when in PST with subst: or safesubst:
3071  }
3072  } else {
3073  if ( $substMatch == 'subst' ) {
3074  $literal = true; # literal when not in PST with plain subst:
3075  } else {
3076  $literal = false; # expand when not in PST with safesubst: or no prefix
3077  }
3078  }
3079  if ( $literal ) {
3080  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3081  $isLocalObj = true;
3082  $found = true;
3083  }
3084  }
3085 
3086  # Variables
3087  if ( !$found && $args->getLength() == 0 ) {
3088  $id = $this->mVariables->matchStartToEnd( $part1 );
3089  if ( $id !== false ) {
3090  $text = $this->getVariableValue( $id, $frame );
3091  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3092  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3093  }
3094  $found = true;
3095  }
3096  }
3097 
3098  # MSG, MSGNW and RAW
3099  if ( !$found ) {
3100  # Check for MSGNW:
3101  $mwMsgnw = MagicWord::get( 'msgnw' );
3102  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3103  $nowiki = true;
3104  } else {
3105  # Remove obsolete MSG:
3106  $mwMsg = MagicWord::get( 'msg' );
3107  $mwMsg->matchStartAndRemove( $part1 );
3108  }
3109 
3110  # Check for RAW:
3111  $mwRaw = MagicWord::get( 'raw' );
3112  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3113  $forceRawInterwiki = true;
3114  }
3115  }
3116 
3117  # Parser functions
3118  if ( !$found ) {
3119  $colonPos = strpos( $part1, ':' );
3120  if ( $colonPos !== false ) {
3121  $func = substr( $part1, 0, $colonPos );
3122  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3123  $argsLength = $args->getLength();
3124  for ( $i = 0; $i < $argsLength; $i++ ) {
3125  $funcArgs[] = $args->item( $i );
3126  }
3127  try {
3128  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3129  } catch ( Exception $ex ) {
3130  throw $ex;
3131  }
3132 
3133  # The interface for parser functions allows for extracting
3134  # flags into the local scope. Extract any forwarded flags
3135  # here.
3136  extract( $result );
3137  }
3138  }
3139 
3140  # Finish mangling title and then check for loops.
3141  # Set $title to a Title object and $titleText to the PDBK
3142  if ( !$found ) {
3143  $ns = NS_TEMPLATE;
3144  # Split the title into page and subpage
3145  $subpage = '';
3146  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3147  if ( $part1 !== $relative ) {
3148  $part1 = $relative;
3149  $ns = $this->mTitle->getNamespace();
3150  }
3151  $title = Title::newFromText( $part1, $ns );
3152  if ( $title ) {
3153  $titleText = $title->getPrefixedText();
3154  # Check for language variants if the template is not found
3155  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3156  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3157  }
3158  # Do recursion depth check
3159  $limit = $this->mOptions->getMaxTemplateDepth();
3160  if ( $frame->depth >= $limit ) {
3161  $found = true;
3162  $text = '<span class="error">'
3163  . wfMessage( 'parser-template-recursion-depth-warning' )
3164  ->numParams( $limit )->inContentLanguage()->text()
3165  . '</span>';
3166  }
3167  }
3168  }
3169 
3170  # Load from database
3171  if ( !$found && $title ) {
3172  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3173  if ( !$title->isExternal() ) {
3174  if ( $title->isSpecialPage()
3175  && $this->mOptions->getAllowSpecialInclusion()
3176  && $this->ot['html']
3177  ) {
3178  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3179  // Pass the template arguments as URL parameters.
3180  // "uselang" will have no effect since the Language object
3181  // is forced to the one defined in ParserOptions.
3182  $pageArgs = [];
3183  $argsLength = $args->getLength();
3184  for ( $i = 0; $i < $argsLength; $i++ ) {
3185  $bits = $args->item( $i )->splitArg();
3186  if ( strval( $bits['index'] ) === '' ) {
3187  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3188  $value = trim( $frame->expand( $bits['value'] ) );
3189  $pageArgs[$name] = $value;
3190  }
3191  }
3192 
3193  // Create a new context to execute the special page
3194  $context = new RequestContext;
3195  $context->setTitle( $title );
3196  $context->setRequest( new FauxRequest( $pageArgs ) );
3197  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3198  $context->setUser( $this->getUser() );
3199  } else {
3200  // If this page is cached, then we better not be per user.
3201  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3202  }
3203  $context->setLanguage( $this->mOptions->getUserLangObj() );
3205  $title, $context, $this->getLinkRenderer() );
3206  if ( $ret ) {
3207  $text = $context->getOutput()->getHTML();
3208  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3209  $found = true;
3210  $isHTML = true;
3211  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3212  $this->mOutput->updateRuntimeAdaptiveExpiry(
3213  $specialPage->maxIncludeCacheTime()
3214  );
3215  }
3216  }
3217  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3218  $found = false; # access denied
3219  wfDebug( __METHOD__ . ": template inclusion denied for " .
3220  $title->getPrefixedDBkey() . "\n" );
3221  } else {
3222  list( $text, $title ) = $this->getTemplateDom( $title );
3223  if ( $text !== false ) {
3224  $found = true;
3225  $isChildObj = true;
3226  }
3227  }
3228 
3229  # If the title is valid but undisplayable, make a link to it
3230  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3231  $text = "[[:$titleText]]";
3232  $found = true;
3233  }
3234  } elseif ( $title->isTrans() ) {
3235  # Interwiki transclusion
3236  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3237  $text = $this->interwikiTransclude( $title, 'render' );
3238  $isHTML = true;
3239  } else {
3240  $text = $this->interwikiTransclude( $title, 'raw' );
3241  # Preprocess it like a template
3242  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3243  $isChildObj = true;
3244  }
3245  $found = true;
3246  }
3247 
3248  # Do infinite loop check
3249  # This has to be done after redirect resolution to avoid infinite loops via redirects
3250  if ( !$frame->loopCheck( $title ) ) {
3251  $found = true;
3252  $text = '<span class="error">'
3253  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3254  . '</span>';
3255  $this->addTrackingCategory( 'template-loop-category' );
3256  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3257  }
3258  }
3259 
3260  # If we haven't found text to substitute by now, we're done
3261  # Recover the source wikitext and return it
3262  if ( !$found ) {
3263  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3264  if ( $profileSection ) {
3265  $this->mProfiler->scopedProfileOut( $profileSection );
3266  }
3267  return [ 'object' => $text ];
3268  }
3269 
3270  # Expand DOM-style return values in a child frame
3271  if ( $isChildObj ) {
3272  # Clean up argument array
3273  $newFrame = $frame->newChild( $args, $title );
3274 
3275  if ( $nowiki ) {
3276  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3277  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3278  # Expansion is eligible for the empty-frame cache
3279  $text = $newFrame->cachedExpand( $titleText, $text );
3280  } else {
3281  # Uncached expansion
3282  $text = $newFrame->expand( $text );
3283  }
3284  }
3285  if ( $isLocalObj && $nowiki ) {
3286  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3287  $isLocalObj = false;
3288  }
3289 
3290  if ( $profileSection ) {
3291  $this->mProfiler->scopedProfileOut( $profileSection );
3292  }
3293 
3294  # Replace raw HTML by a placeholder
3295  if ( $isHTML ) {
3296  $text = $this->insertStripItem( $text );
3297  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3298  # Escape nowiki-style return values
3299  $text = wfEscapeWikiText( $text );
3300  } elseif ( is_string( $text )
3301  && !$piece['lineStart']
3302  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3303  ) {
3304  # T2529: if the template begins with a table or block-level
3305  # element, it should be treated as beginning a new line.
3306  # This behavior is somewhat controversial.
3307  $text = "\n" . $text;
3308  }
3309 
3310  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3311  # Error, oversize inclusion
3312  if ( $titleText !== false ) {
3313  # Make a working, properly escaped link if possible (T25588)
3314  $text = "[[:$titleText]]";
3315  } else {
3316  # This will probably not be a working link, but at least it may
3317  # provide some hint of where the problem is
3318  preg_replace( '/^:/', '', $originalTitle );
3319  $text = "[[:$originalTitle]]";
3320  }
3321  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3322  . 'post-expand include size too large -->' );
3323  $this->limitationWarn( 'post-expand-template-inclusion' );
3324  }
3325 
3326  if ( $isLocalObj ) {
3327  $ret = [ 'object' => $text ];
3328  } else {
3329  $ret = [ 'text' => $text ];
3330  }
3331 
3332  return $ret;
3333  }
3334 
3354  public function callParserFunction( $frame, $function, array $args = [] ) {
3356 
3357  # Case sensitive functions
3358  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3359  $function = $this->mFunctionSynonyms[1][$function];
3360  } else {
3361  # Case insensitive functions
3362  $function = $wgContLang->lc( $function );
3363  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3364  $function = $this->mFunctionSynonyms[0][$function];
3365  } else {
3366  return [ 'found' => false ];
3367  }
3368  }
3369 
3370  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3371 
3372  # Workaround for PHP bug 35229 and similar
3373  if ( !is_callable( $callback ) ) {
3374  throw new MWException( "Tag hook for $function is not callable\n" );
3375  }
3376 
3377  // Avoid PHP 7.1 warning from passing $this by reference
3378  $parser = $this;
3379 
3380  $allArgs = [ &$parser ];
3381  if ( $flags & self::SFH_OBJECT_ARGS ) {
3382  # Convert arguments to PPNodes and collect for appending to $allArgs
3383  $funcArgs = [];
3384  foreach ( $args as $k => $v ) {
3385  if ( $v instanceof PPNode || $k === 0 ) {
3386  $funcArgs[] = $v;
3387  } else {
3388  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3389  }
3390  }
3391 
3392  # Add a frame parameter, and pass the arguments as an array
3393  $allArgs[] = $frame;
3394  $allArgs[] = $funcArgs;
3395  } else {
3396  # Convert arguments to plain text and append to $allArgs
3397  foreach ( $args as $k => $v ) {
3398  if ( $v instanceof PPNode ) {
3399  $allArgs[] = trim( $frame->expand( $v ) );
3400  } elseif ( is_int( $k ) && $k >= 0 ) {
3401  $allArgs[] = trim( $v );
3402  } else {
3403  $allArgs[] = trim( "$k=$v" );
3404  }
3405  }
3406  }
3407 
3408  $result = call_user_func_array( $callback, $allArgs );
3409 
3410  # The interface for function hooks allows them to return a wikitext
3411  # string or an array containing the string and any flags. This mungs
3412  # things around to match what this method should return.
3413  if ( !is_array( $result ) ) {
3414  $result =[
3415  'found' => true,
3416  'text' => $result,
3417  ];
3418  } else {
3419  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3420  $result['text'] = $result[0];
3421  }
3422  unset( $result[0] );
3423  $result += [
3424  'found' => true,
3425  ];
3426  }
3427 
3428  $noparse = true;
3429  $preprocessFlags = 0;
3430  if ( isset( $result['noparse'] ) ) {
3431  $noparse = $result['noparse'];
3432  }
3433  if ( isset( $result['preprocessFlags'] ) ) {
3434  $preprocessFlags = $result['preprocessFlags'];
3435  }
3436 
3437  if ( !$noparse ) {
3438  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3439  $result['isChildObj'] = true;
3440  }
3441 
3442  return $result;
3443  }
3444 
3453  public function getTemplateDom( $title ) {
3454  $cacheTitle = $title;
3455  $titleText = $title->getPrefixedDBkey();
3456 
3457  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3458  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3459  $title = Title::makeTitle( $ns, $dbk );
3460  $titleText = $title->getPrefixedDBkey();
3461  }
3462  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3463  return [ $this->mTplDomCache[$titleText], $title ];
3464  }
3465 
3466  # Cache miss, go to the database
3467  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3468 
3469  if ( $text === false ) {
3470  $this->mTplDomCache[$titleText] = false;
3471  return [ false, $title ];
3472  }
3473 
3474  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3475  $this->mTplDomCache[$titleText] = $dom;
3476 
3477  if ( !$title->equals( $cacheTitle ) ) {
3478  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3479  [ $title->getNamespace(), $cdb = $title->getDBkey() ];
3480  }
3481 
3482  return [ $dom, $title ];
3483  }
3484 
3496  public function fetchCurrentRevisionOfTitle( $title ) {
3497  $cacheKey = $title->getPrefixedDBkey();
3498  if ( !$this->currentRevisionCache ) {
3499  $this->currentRevisionCache = new MapCacheLRU( 100 );
3500  }
3501  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3502  $this->currentRevisionCache->set( $cacheKey,
3503  // Defaults to Parser::statelessFetchRevision()
3504  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3505  );
3506  }
3507  return $this->currentRevisionCache->get( $cacheKey );
3508  }
3509 
3519  public static function statelessFetchRevision( Title $title, $parser = false ) {
3520  $pageId = $title->getArticleID();
3521  $revId = $title->getLatestRevID();
3522 
3524  if ( $rev ) {
3525  $rev->setTitle( $title );
3526  }
3527 
3528  return $rev;
3529  }
3530 
3536  public function fetchTemplateAndTitle( $title ) {
3537  // Defaults to Parser::statelessFetchTemplate()
3538  $templateCb = $this->mOptions->getTemplateCallback();
3539  $stuff = call_user_func( $templateCb, $title, $this );
3540  // We use U+007F DELETE to distinguish strip markers from regular text.
3541  $text = $stuff['text'];
3542  if ( is_string( $stuff['text'] ) ) {
3543  $text = strtr( $text, "\x7f", "?" );
3544  }
3545  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3546  if ( isset( $stuff['deps'] ) ) {
3547  foreach ( $stuff['deps'] as $dep ) {
3548  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3549  if ( $dep['title']->equals( $this->getTitle() ) ) {
3550  // If we transclude ourselves, the final result
3551  // will change based on the new version of the page
3552  $this->mOutput->setFlag( 'vary-revision' );
3553  }
3554  }
3555  }
3556  return [ $text, $finalTitle ];
3557  }
3558 
3564  public function fetchTemplate( $title ) {
3565  return $this->fetchTemplateAndTitle( $title )[0];
3566  }
3567 
3577  public static function statelessFetchTemplate( $title, $parser = false ) {
3578  $text = $skip = false;
3579  $finalTitle = $title;
3580  $deps = [];
3581 
3582  # Loop to fetch the article, with up to 1 redirect
3583  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3584  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3585  // @codingStandardsIgnoreEnd
3586  # Give extensions a chance to select the revision instead
3587  $id = false; # Assume current
3588  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3589  [ $parser, $title, &$skip, &$id ] );
3590 
3591  if ( $skip ) {
3592  $text = false;
3593  $deps[] = [
3594  'title' => $title,
3595  'page_id' => $title->getArticleID(),
3596  'rev_id' => null
3597  ];
3598  break;
3599  }
3600  # Get the revision
3601  if ( $id ) {
3602  $rev = Revision::newFromId( $id );
3603  } elseif ( $parser ) {
3604  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3605  } else {
3607  }
3608  $rev_id = $rev ? $rev->getId() : 0;
3609  # If there is no current revision, there is no page
3610  if ( $id === false && !$rev ) {
3611  $linkCache = LinkCache::singleton();
3612  $linkCache->addBadLinkObj( $title );
3613  }
3614 
3615  $deps[] = [
3616  'title' => $title,
3617  'page_id' => $title->getArticleID(),
3618  'rev_id' => $rev_id ];
3619  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3620  # We fetched a rev from a different title; register it too...
3621  $deps[] = [
3622  'title' => $rev->getTitle(),
3623  'page_id' => $rev->getPage(),
3624  'rev_id' => $rev_id ];
3625  }
3626 
3627  if ( $rev ) {
3628  $content = $rev->getContent();
3629  $text = $content ? $content->getWikitextForTransclusion() : null;
3630 
3631  Hooks::run( 'ParserFetchTemplate',
3632  [ $parser, $title, $rev, &$text, &$deps ] );
3633 
3634  if ( $text === false || $text === null ) {
3635  $text = false;
3636  break;
3637  }
3638  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3640  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3641  if ( !$message->exists() ) {
3642  $text = false;
3643  break;
3644  }
3645  $content = $message->content();
3646  $text = $message->plain();
3647  } else {
3648  break;
3649  }
3650  if ( !$content ) {
3651  break;
3652  }
3653  # Redirect?
3654  $finalTitle = $title;
3655  $title = $content->getRedirectTarget();
3656  }
3657  return [
3658  'text' => $text,
3659  'finalTitle' => $finalTitle,
3660  'deps' => $deps ];
3661  }
3662 
3670  public function fetchFile( $title, $options = [] ) {
3671  return $this->fetchFileAndTitle( $title, $options )[0];
3672  }
3673 
3681  public function fetchFileAndTitle( $title, $options = [] ) {
3682  $file = $this->fetchFileNoRegister( $title, $options );
3683 
3684  $time = $file ? $file->getTimestamp() : false;
3685  $sha1 = $file ? $file->getSha1() : false;
3686  # Register the file as a dependency...
3687  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3688  if ( $file && !$title->equals( $file->getTitle() ) ) {
3689  # Update fetched file title
3690  $title = $file->getTitle();
3691  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3692  }
3693  return [ $file, $title ];
3694  }
3695 
3706  protected function fetchFileNoRegister( $title, $options = [] ) {
3707  if ( isset( $options['broken'] ) ) {
3708  $file = false; // broken thumbnail forced by hook
3709  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3710  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3711  } else { // get by (name,timestamp)
3712  $file = wfFindFile( $title, $options );
3713  }
3714  return $file;
3715  }
3716 
3725  public function interwikiTransclude( $title, $action ) {
3726  global $wgEnableScaryTranscluding;
3727 
3728  if ( !$wgEnableScaryTranscluding ) {
3729  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3730  }
3731 
3732  $url = $title->getFullURL( [ 'action' => $action ] );
3733 
3734  if ( strlen( $url ) > 255 ) {
3735  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3736  }
3737  return $this->fetchScaryTemplateMaybeFromCache( $url );
3738  }
3739 
3744  public function fetchScaryTemplateMaybeFromCache( $url ) {
3745  global $wgTranscludeCacheExpiry;
3746  $dbr = wfGetDB( DB_REPLICA );
3747  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3748  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3749  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3750  if ( $obj ) {
3751  return $obj->tc_contents;
3752  }
3753 
3754  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3755  $status = $req->execute(); // Status object
3756  if ( $status->isOK() ) {
3757  $text = $req->getContent();
3758  } elseif ( $req->getStatus() != 200 ) {
3759  // Though we failed to fetch the content, this status is useless.
3760  return wfMessage( 'scarytranscludefailed-httpstatus' )
3761  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3762  } else {
3763  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3764  }
3765 
3766  $dbw = wfGetDB( DB_MASTER );
3767  $dbw->replace( 'transcache', [ 'tc_url' ], [
3768  'tc_url' => $url,
3769  'tc_time' => $dbw->timestamp( time() ),
3770  'tc_contents' => $text
3771  ] );
3772  return $text;
3773  }
3774 
3784  public function argSubstitution( $piece, $frame ) {
3785 
3786  $error = false;
3787  $parts = $piece['parts'];
3788  $nameWithSpaces = $frame->expand( $piece['title'] );
3789  $argName = trim( $nameWithSpaces );
3790  $object = false;
3791  $text = $frame->getArgument( $argName );
3792  if ( $text === false && $parts->getLength() > 0
3793  && ( $this->ot['html']
3794  || $this->ot['pre']
3795  || ( $this->ot['wiki'] && $frame->isTemplate() )
3796  )
3797  ) {
3798  # No match in frame, use the supplied default
3799  $object = $parts->item( 0 )->getChildren();
3800  }
3801  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3802  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3803  $this->limitationWarn( 'post-expand-template-argument' );
3804  }
3805 
3806  if ( $text === false && $object === false ) {
3807  # No match anywhere
3808  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3809  }
3810  if ( $error !== false ) {
3811  $text .= $error;
3812  }
3813  if ( $object !== false ) {
3814  $ret = [ 'object' => $object ];
3815  } else {
3816  $ret = [ 'text' => $text ];
3817  }
3818 
3819  return $ret;
3820  }
3821 
3837  public function extensionSubstitution( $params, $frame ) {
3838  static $errorStr = '<span class="error">';
3839  static $errorLen = 20;
3840 
3841  $name = $frame->expand( $params['name'] );
3842  if ( substr( $name, 0, $errorLen ) === $errorStr ) {
3843  // Probably expansion depth or node count exceeded. Just punt the
3844  // error up.
3845  return $name;
3846  }
3847 
3848  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3849  if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
3850  // See above
3851  return $attrText;
3852  }
3853 
3854  // We can't safely check if the expansion for $content resulted in an
3855  // error, because the content could happen to be the error string
3856  // (T149622).
3857  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3858 
3859  $marker = self::MARKER_PREFIX . "-$name-"
3860  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3861 
3862  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3863  ( $this->ot['html'] || $this->ot['pre'] );
3864  if ( $isFunctionTag ) {
3865  $markerType = 'none';
3866  } else {
3867  $markerType = 'general';
3868  }
3869  if ( $this->ot['html'] || $isFunctionTag ) {
3870  $name = strtolower( $name );
3871  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3872  if ( isset( $params['attributes'] ) ) {
3873  $attributes = $attributes + $params['attributes'];
3874  }
3875 
3876  if ( isset( $this->mTagHooks[$name] ) ) {
3877  # Workaround for PHP bug 35229 and similar
3878  if ( !is_callable( $this->mTagHooks[$name] ) ) {
3879  throw new MWException( "Tag hook for $name is not callable\n" );
3880  }
3881  $output = call_user_func_array( $this->mTagHooks[$name],
3882  [ $content, $attributes, $this, $frame ] );
3883  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3884  list( $callback, ) = $this->mFunctionTagHooks[$name];
3885  if ( !is_callable( $callback ) ) {
3886  throw new MWException( "Tag hook for $name is not callable\n" );
3887  }
3888 
3889  // Avoid PHP 7.1 warning from passing $this by reference
3890  $parser = $this;
3891  $output = call_user_func_array( $callback, [ &$parser, $frame, $content, $attributes ] );
3892  } else {
3893  $output = '<span class="error">Invalid tag extension name: ' .
3894  htmlspecialchars( $name ) . '</span>';
3895  }
3896 
3897  if ( is_array( $output ) ) {
3898  # Extract flags to local scope (to override $markerType)
3899  $flags = $output;
3900  $output = $flags[0];
3901  unset( $flags[0] );
3902  extract( $flags );
3903  }
3904  } else {
3905  if ( is_null( $attrText ) ) {
3906  $attrText = '';
3907  }
3908  if ( isset( $params['attributes'] ) ) {
3909  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3910  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3911  htmlspecialchars( $attrValue ) . '"';
3912  }
3913  }
3914  if ( $content === null ) {
3915  $output = "<$name$attrText/>";
3916  } else {
3917  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3918  if ( substr( $close, 0, $errorLen ) === $errorStr ) {
3919  // See above
3920  return $close;
3921  }
3922  $output = "<$name$attrText>$content$close";
3923  }
3924  }
3925 
3926  if ( $markerType === 'none' ) {
3927  return $output;
3928  } elseif ( $markerType === 'nowiki' ) {
3929  $this->mStripState->addNoWiki( $marker, $output );
3930  } elseif ( $markerType === 'general' ) {
3931  $this->mStripState->addGeneral( $marker, $output );
3932  } else {
3933  throw new MWException( __METHOD__ . ': invalid marker type' );
3934  }
3935  return $marker;
3936  }
3937 
3945  public function incrementIncludeSize( $type, $size ) {
3946  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3947  return false;
3948  } else {
3949  $this->mIncludeSizes[$type] += $size;
3950  return true;
3951  }
3952  }
3953 
3959  public function incrementExpensiveFunctionCount() {
3960  $this->mExpensiveFunctionCount++;
3961  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3962  }
3963 
3972  public function doDoubleUnderscore( $text ) {
3973 
3974  # The position of __TOC__ needs to be recorded
3975  $mw = MagicWord::get( 'toc' );
3976  if ( $mw->match( $text ) ) {
3977  $this->mShowToc = true;
3978  $this->mForceTocPosition = true;
3979 
3980  # Set a placeholder. At the end we'll fill it in with the TOC.
3981  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3982 
3983  # Only keep the first one.
3984  $text = $mw->replace( '', $text );
3985  }
3986 
3987  # Now match and remove the rest of them
3989  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3990 
3991  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3992  $this->mOutput->mNoGallery = true;
3993  }
3994  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3995  $this->mShowToc = false;
3996  }
3997  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3998  && $this->mTitle->getNamespace() == NS_CATEGORY
3999  ) {
4000  $this->addTrackingCategory( 'hidden-category-category' );
4001  }
4002  # (T10068) Allow control over whether robots index a page.
4003  # __INDEX__ always overrides __NOINDEX__, see T16899
4004  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
4005  $this->mOutput->setIndexPolicy( 'noindex' );
4006  $this->addTrackingCategory( 'noindex-category' );
4007  }
4008  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4009  $this->mOutput->setIndexPolicy( 'index' );
4010  $this->addTrackingCategory( 'index-category' );
4011  }
4012 
4013  # Cache all double underscores in the database
4014  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4015  $this->mOutput->setProperty( $key, '' );
4016  }
4017 
4018  return $text;
4019  }
4020 
4026  public function addTrackingCategory( $msg ) {
4027  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
4028  }
4029 
4046  public function formatHeadings( $text, $origText, $isMain = true ) {
4047  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4048 
4049  # Inhibit editsection links if requested in the page
4050  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4051  $maybeShowEditLink = $showEditLink = false;
4052  } else {
4053  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4054  $showEditLink = $this->mOptions->getEditSection();
4055  }
4056  if ( $showEditLink ) {
4057  $this->mOutput->setEditSectionTokens( true );
4058  }
4059 
4060  # Get all headlines for numbering them and adding funky stuff like [edit]
4061  # links - this is for later, but we need the number of headlines right now
4062  $matches = [];
4063  $numMatches = preg_match_all(
4064  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
4065  $text,
4066  $matches
4067  );
4068 
4069  # if there are fewer than 4 headlines in the article, do not show TOC
4070  # unless it's been explicitly enabled.
4071  $enoughToc = $this->mShowToc &&
4072  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4073 
4074  # Allow user to stipulate that a page should have a "new section"
4075  # link added via __NEWSECTIONLINK__
4076  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4077  $this->mOutput->setNewSection( true );
4078  }
4079 
4080  # Allow user to remove the "new section"
4081  # link via __NONEWSECTIONLINK__
4082  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4083  $this->mOutput->hideNewSection( true );
4084  }
4085 
4086  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4087  # override above conditions and always show TOC above first header
4088  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4089  $this->mShowToc = true;
4090  $enoughToc = true;
4091  }
4092 
4093  # headline counter
4094  $headlineCount = 0;
4095  $numVisible = 0;
4096 
4097  # Ugh .. the TOC should have neat indentation levels which can be
4098  # passed to the skin functions. These are determined here
4099  $toc = '';
4100  $full = '';
4101  $head = [];
4102  $sublevelCount = [];
4103  $levelCount = [];
4104  $level = 0;
4105  $prevlevel = 0;
4106  $toclevel = 0;
4107  $prevtoclevel = 0;
4108  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4109  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4110  $oldType = $this->mOutputType;
4111  $this->setOutputType( self::OT_WIKI );
4112  $frame = $this->getPreprocessor()->newFrame();
4113  $root = $this->preprocessToDom( $origText );
4114  $node = $root->getFirstChild();
4115  $byteOffset = 0;
4116  $tocraw = [];
4117  $refers = [];
4118 
4119  $headlines = $numMatches !== false ? $matches[3] : [];
4120 
4121  foreach ( $headlines as $headline ) {
4122  $isTemplate = false;
4123  $titleText = false;
4124  $sectionIndex = false;
4125  $numbering = '';
4126  $markerMatches = [];
4127  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4128  $serial = $markerMatches[1];
4129  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4130  $isTemplate = ( $titleText != $baseTitleText );
4131  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4132  }
4133 
4134  if ( $toclevel ) {
4135  $prevlevel = $level;
4136  }
4137  $level = $matches[1][$headlineCount];
4138 
4139  if ( $level > $prevlevel ) {
4140  # Increase TOC level
4141  $toclevel++;
4142  $sublevelCount[$toclevel] = 0;
4143  if ( $toclevel < $wgMaxTocLevel ) {
4144  $prevtoclevel = $toclevel;
4145  $toc .= Linker::tocIndent();
4146  $numVisible++;
4147  }
4148  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4149  # Decrease TOC level, find level to jump to
4150 
4151  for ( $i = $toclevel; $i > 0; $i-- ) {
4152  if ( $levelCount[$i] == $level ) {
4153  # Found last matching level
4154  $toclevel = $i;
4155  break;
4156  } elseif ( $levelCount[$i] < $level ) {
4157  # Found first matching level below current level
4158  $toclevel = $i + 1;
4159  break;
4160  }
4161  }
4162  if ( $i == 0 ) {
4163  $toclevel = 1;
4164  }
4165  if ( $toclevel < $wgMaxTocLevel ) {
4166  if ( $prevtoclevel < $wgMaxTocLevel ) {
4167  # Unindent only if the previous toc level was shown :p
4168  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4169  $prevtoclevel = $toclevel;
4170  } else {
4171  $toc .= Linker::tocLineEnd();
4172  }
4173  }
4174  } else {
4175  # No change in level, end TOC line
4176  if ( $toclevel < $wgMaxTocLevel ) {
4177  $toc .= Linker::tocLineEnd();
4178  }
4179  }
4180 
4181  $levelCount[$toclevel] = $level;
4182 
4183  # count number of headlines for each level
4184  $sublevelCount[$toclevel]++;
4185  $dot = 0;
4186  for ( $i = 1; $i <= $toclevel; $i++ ) {
4187  if ( !empty( $sublevelCount[$i] ) ) {
4188  if ( $dot ) {
4189  $numbering .= '.';
4190  }
4191  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4192  $dot = 1;
4193  }
4194  }
4195 
4196  # The safe header is a version of the header text safe to use for links
4197 
4198  # Remove link placeholders by the link text.
4199  # <!--LINK number-->
4200  # turns into
4201  # link text with suffix
4202  # Do this before unstrip since link text can contain strip markers
4203  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4204 
4205  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4206  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4207 
4208  # Strip out HTML (first regex removes any tag not allowed)
4209  # Allowed tags are:
4210  # * <sup> and <sub> (T10393)
4211  # * <i> (T28375)
4212  # * <b> (r105284)
4213  # * <bdi> (T74884)
4214  # * <span dir="rtl"> and <span dir="ltr"> (T37167)
4215  # * <s> and <strike> (T35715)
4216  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4217  # to allow setting directionality in toc items.
4218  $tocline = preg_replace(
4219  [
4220  '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
4221  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
4222  ],
4223  [ '', '<$1>' ],
4224  $safeHeadline
4225  );
4226 
4227  # Strip '<span></span>', which is the result from the above if
4228  # <span id="foo"></span> is used to produce an additional anchor
4229  # for a section.
4230  $tocline = str_replace( '<span></span>', '', $tocline );
4231 
4232  $tocline = trim( $tocline );
4233 
4234  # For the anchor, strip out HTML-y stuff period
4235  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4236  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4237 
4238  # Save headline for section edit hint before it's escaped
4239  $headlineHint = $safeHeadline;
4240 
4241  if ( $wgExperimentalHtmlIds ) {
4242  # For reverse compatibility, provide an id that's
4243  # HTML4-compatible, like we used to.
4244  # It may be worth noting, academically, that it's possible for
4245  # the legacy anchor to conflict with a non-legacy headline
4246  # anchor on the page. In this case likely the "correct" thing
4247  # would be to either drop the legacy anchors or make sure
4248  # they're numbered first. However, this would require people
4249  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4250  # manually, so let's not bother worrying about it.
4251  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4252  [ 'noninitial', 'legacy' ] );
4253  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4254 
4255  if ( $legacyHeadline == $safeHeadline ) {
4256  # No reason to have both (in fact, we can't)
4257  $legacyHeadline = false;
4258  }
4259  } else {
4260  $legacyHeadline = false;
4261  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4262  'noninitial' );
4263  }
4264 
4265  # HTML names must be case-insensitively unique (T12721).
4266  # This does not apply to Unicode characters per
4267  # https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4268  # @todo FIXME: We may be changing them depending on the current locale.
4269  $arrayKey = strtolower( $safeHeadline );
4270  if ( $legacyHeadline === false ) {
4271  $legacyArrayKey = false;
4272  } else {
4273  $legacyArrayKey = strtolower( $legacyHeadline );
4274  }
4275 
4276  # Create the anchor for linking from the TOC to the section
4277  $anchor = $safeHeadline;
4278  $legacyAnchor = $legacyHeadline;
4279  if ( isset( $refers[$arrayKey] ) ) {
4280  // @codingStandardsIgnoreStart
4281  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4282  // @codingStandardsIgnoreEnd
4283  $anchor .= "_$i";
4284  $refers["${arrayKey}_$i"] = true;
4285  } else {
4286  $refers[$arrayKey] = true;
4287  }
4288  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4289  // @codingStandardsIgnoreStart
4290  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4291  // @codingStandardsIgnoreEnd
4292  $legacyAnchor .= "_$i";
4293  $refers["${legacyArrayKey}_$i"] = true;
4294  } else {
4295  $refers[$legacyArrayKey] = true;
4296  }
4297 
4298  # Don't number the heading if it is the only one (looks silly)
4299  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4300  # the two are different if the line contains a link
4301  $headline = Html::element(
4302  'span',
4303  [ 'class' => 'mw-headline-number' ],
4304  $numbering
4305  ) . ' ' . $headline;
4306  }
4307 
4308  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4309  $toc .= Linker::tocLine( $anchor, $tocline,
4310  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4311  }
4312 
4313  # Add the section to the section tree
4314  # Find the DOM node for this header
4315  $noOffset = ( $isTemplate || $sectionIndex === false );
4316  while ( $node && !$noOffset ) {
4317  if ( $node->getName() === 'h' ) {
4318  $bits = $node->splitHeading();
4319  if ( $bits['i'] == $sectionIndex ) {
4320  break;
4321  }
4322  }
4323  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4324  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4325  $node = $node->getNextSibling();
4326  }
4327  $tocraw[] = [
4328  'toclevel' => $toclevel,
4329  'level' => $level,
4330  'line' => $tocline,
4331  'number' => $numbering,
4332  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4333  'fromtitle' => $titleText,
4334  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4335  'anchor' => $anchor,
4336  ];
4337 
4338  # give headline the correct <h#> tag
4339  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4340  // Output edit section links as markers with styles that can be customized by skins
4341  if ( $isTemplate ) {
4342  # Put a T flag in the section identifier, to indicate to extractSections()
4343  # that sections inside <includeonly> should be counted.
4344  $editsectionPage = $titleText;
4345  $editsectionSection = "T-$sectionIndex";
4346  $editsectionContent = null;
4347  } else {
4348  $editsectionPage = $this->mTitle->getPrefixedText();
4349  $editsectionSection = $sectionIndex;
4350  $editsectionContent = $headlineHint;
4351  }
4352  // We use a bit of pesudo-xml for editsection markers. The
4353  // language converter is run later on. Using a UNIQ style marker
4354  // leads to the converter screwing up the tokens when it
4355  // converts stuff. And trying to insert strip tags fails too. At
4356  // this point all real inputted tags have already been escaped,
4357  // so we don't have to worry about a user trying to input one of
4358  // these markers directly. We use a page and section attribute
4359  // to stop the language converter from converting these
4360  // important bits of data, but put the headline hint inside a
4361  // content block because the language converter is supposed to
4362  // be able to convert that piece of data.
4363  // Gets replaced with html in ParserOutput::getText
4364  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4365  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4366  if ( $editsectionContent !== null ) {
4367  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4368  } else {
4369  $editlink .= '/>';
4370  }
4371  } else {
4372  $editlink = '';
4373  }
4374  $head[$headlineCount] = Linker::makeHeadline( $level,
4375  $matches['attrib'][$headlineCount], $anchor, $headline,
4376  $editlink, $legacyAnchor );
4377 
4378  $headlineCount++;
4379  }
4380 
4381  $this->setOutputType( $oldType );
4382 
4383  # Never ever show TOC if no headers
4384  if ( $numVisible < 1 ) {
4385  $enoughToc = false;
4386  }
4387 
4388  if ( $enoughToc ) {
4389  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4390  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4391  }
4392  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4393  $this->mOutput->setTOCHTML( $toc );
4394  $toc = self::TOC_START . $toc . self::TOC_END;
4395  $this->mOutput->addModules( 'mediawiki.toc' );
4396  }
4397 
4398  if ( $isMain ) {
4399  $this->mOutput->setSections( $tocraw );
4400  }
4401 
4402  # split up and insert constructed headlines
4403  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4404  $i = 0;
4405 
4406  // build an array of document sections
4407  $sections = [];
4408  foreach ( $blocks as $block ) {
4409  // $head is zero-based, sections aren't.
4410  if ( empty( $head[$i - 1] ) ) {
4411  $sections[$i] = $block;
4412  } else {
4413  $sections[$i] = $head[$i - 1] . $block;
4414  }
4415 
4426  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4427 
4428  $i++;
4429  }
4430 
4431  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4432  // append the TOC at the beginning
4433  // Top anchor now in skin
4434  $sections[0] = $sections[0] . $toc . "\n";
4435  }
4436 
4437  $full .= implode( '', $sections );
4438 
4439  if ( $this->mForceTocPosition ) {
4440  return str_replace( '<!--MWTOC-->', $toc, $full );
4441  } else {
4442  return $full;
4443  }
4444  }
4445 
4457  public function preSaveTransform( $text, Title $title, User $user,
4458  ParserOptions $options, $clearState = true
4459  ) {
4460  if ( $clearState ) {
4461  $magicScopeVariable = $this->lock();
4462  }
4463  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4464  $this->setUser( $user );
4465 
4466  // Strip U+0000 NULL (T159174)
4467  $text = str_replace( "\000", '', $text );
4468 
4469  // We still normalize line endings for backwards-compatibility
4470  // with other code that just calls PST, but this should already
4471  // be handled in TextContent subclasses
4472  $text = TextContent::normalizeLineEndings( $text );
4473 
4474  if ( $options->getPreSaveTransform() ) {
4475  $text = $this->pstPass2( $text, $user );
4476  }
4477  $text = $this->mStripState->unstripBoth( $text );
4478 
4479  $this->setUser( null ); # Reset
4480 
4481  return $text;
4482  }
4483 
4492  private function pstPass2( $text, $user ) {
4494 
4495  # Note: This is the timestamp saved as hardcoded wikitext to
4496  # the database, we use $wgContLang here in order to give
4497  # everyone the same signature and use the default one rather
4498  # than the one selected in each user's preferences.
4499  # (see also T14815)
4500  $ts = $this->mOptions->getTimestamp();
4501  $timestamp = MWTimestamp::getLocalInstance( $ts );
4502  $ts = $timestamp->format( 'YmdHis' );
4503  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4504 
4505  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4506 
4507  # Variable replacement
4508  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4509  $text = $this->replaceVariables( $text );
4510 
4511  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4512  # which may corrupt this parser instance via its wfMessage()->text() call-
4513 
4514  # Signatures
4515  $sigText = $this->getUserSig( $user );
4516  $text = strtr( $text, [
4517  '~~~~~' => $d,
4518  '~~~~' => "$sigText $d",
4519  '~~~' => $sigText
4520  ] );
4521 
4522  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4523  $tc = '[' . Title::legalChars() . ']';
4524  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4525 
4526  // [[ns:page (context)|]]
4527  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4528  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4529  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4530  // [[ns:page (context), context|]] (using either single or double-width comma)
4531  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4532  // [[|page]] (reverse pipe trick: add context from page title)
4533  $p2 = "/\[\[\\|($tc+)]]/";
4534 
4535  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4536  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4537  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4538  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4539 
4540  $t = $this->mTitle->getText();
4541  $m = [];
4542  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4543  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4544  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4545  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4546  } else {
4547  # if there's no context, don't bother duplicating the title
4548  $text = preg_replace( $p2, '[[\\1]]', $text );
4549  }
4550 
4551  return $text;
4552  }
4553 
4568  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4569  global $wgMaxSigChars;
4570 
4571  $username = $user->getName();
4572 
4573  # If not given, retrieve from the user object.
4574  if ( $nickname === false ) {
4575  $nickname = $user->getOption( 'nickname' );
4576  }
4577 
4578  if ( is_null( $fancySig ) ) {
4579  $fancySig = $user->getBoolOption( 'fancysig' );
4580  }
4581 
4582  $nickname = $nickname == null ? $username : $nickname;
4583 
4584  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4585  $nickname = $username;
4586  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4587  } elseif ( $fancySig !== false ) {
4588  # Sig. might contain markup; validate this
4589  if ( $this->validateSig( $nickname ) !== false ) {
4590  # Validated; clean up (if needed) and return it
4591  return $this->cleanSig( $nickname, true );
4592  } else {
4593  # Failed to validate; fall back to the default
4594  $nickname = $username;
4595  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4596  }
4597  }
4598 
4599  # Make sure nickname doesnt get a sig in a sig
4600  $nickname = self::cleanSigInSig( $nickname );
4601 
4602  # If we're still here, make it a link to the user page
4603  $userText = wfEscapeWikiText( $username );
4604  $nickText = wfEscapeWikiText( $nickname );
4605  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4606 
4607  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4608  ->title( $this->getTitle() )->text();
4609  }
4610 
4617  public function validateSig( $text ) {
4618  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4619  }
4620 
4631  public function cleanSig( $text, $parsing = false ) {
4632  if ( !$parsing ) {
4633  global $wgTitle;
4634  $magicScopeVariable = $this->lock();
4635  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4636  }
4637 
4638  # Option to disable this feature
4639  if ( !$this->mOptions->getCleanSignatures() ) {
4640  return $text;
4641  }
4642 
4643  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4644  # => Move this logic to braceSubstitution()
4645  $substWord = MagicWord::get( 'subst' );
4646  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4647  $substText = '{{' . $substWord->getSynonym( 0 );
4648 
4649  $text = preg_replace( $substRegex, $substText, $text );
4650  $text = self::cleanSigInSig( $text );
4651  $dom = $this->preprocessToDom( $text );
4652  $frame = $this->getPreprocessor()->newFrame();
4653  $text = $frame->expand( $dom );
4654 
4655  if ( !$parsing ) {
4656  $text = $this->mStripState->unstripBoth( $text );
4657  }
4658 
4659  return $text;
4660  }
4661 
4668  public static function cleanSigInSig( $text ) {
4669  $text = preg_replace( '/~{3,5}/', '', $text );
4670  return $text;
4671  }
4672 
4682  public function startExternalParse( Title $title = null, ParserOptions $options,
4683  $outputType, $clearState = true
4684  ) {
4685  $this->startParse( $title, $options, $outputType, $clearState );
4686  }
4687 
4694  private function startParse( Title $title = null, ParserOptions $options,
4695  $outputType, $clearState = true
4696  ) {
4697  $this->setTitle( $title );
4698  $this->mOptions = $options;
4699  $this->setOutputType( $outputType );
4700  if ( $clearState ) {
4701  $this->clearState();
4702  }
4703  }
4704 
4713  public function transformMsg( $text, $options, $title = null ) {
4714  static $executing = false;
4715 
4716  # Guard against infinite recursion
4717  if ( $executing ) {
4718  return $text;
4719  }
4720  $executing = true;
4721 
4722  if ( !$title ) {
4723  global $wgTitle;
4724  $title = $wgTitle;
4725  }
4726 
4727  $text = $this->preprocess( $text, $title, $options );
4728 
4729  $executing = false;
4730  return $text;
4731  }
4732 
4757  public function setHook( $tag, $callback ) {
4758  $tag = strtolower( $tag );
4759  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4760  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4761  }
4762  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4763  $this->mTagHooks[$tag] = $callback;
4764  if ( !in_array( $tag, $this->mStripList ) ) {
4765  $this->mStripList[] = $tag;
4766  }
4767 
4768  return $oldVal;
4769  }
4770 
4788  public function setTransparentTagHook( $tag, $callback ) {
4789  $tag = strtolower( $tag );
4790  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4791  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4792  }
4793  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4794  $this->mTransparentTagHooks[$tag] = $callback;
4795 
4796  return $oldVal;
4797  }
4798 
4802  public function clearTagHooks() {
4803  $this->mTagHooks = [];
4804  $this->mFunctionTagHooks = [];
4805  $this->mStripList = $this->mDefaultStripList;
4806  }
4807 
4851  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4853 
4854  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4855  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4856 
4857  # Add to function cache
4858  $mw = MagicWord::get( $id );
4859  if ( !$mw ) {
4860  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4861  }
4862 
4863  $synonyms = $mw->getSynonyms();
4864  $sensitive = intval( $mw->isCaseSensitive() );
4865 
4866  foreach ( $synonyms as $syn ) {
4867  # Case
4868  if ( !$sensitive ) {
4869  $syn = $wgContLang->lc( $syn );
4870  }
4871  # Add leading hash
4872  if ( !( $flags & self::SFH_NO_HASH ) ) {
4873  $syn = '#' . $syn;
4874  }
4875  # Remove trailing colon
4876  if ( substr( $syn, -1, 1 ) === ':' ) {
4877  $syn = substr( $syn, 0, -1 );
4878  }
4879  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4880  }
4881  return $oldVal;
4882  }
4883 
4889  public function getFunctionHooks() {
4890  return array_keys( $this->mFunctionHooks );
4891  }
4892 
4903  public function setFunctionTagHook( $tag, $callback, $flags ) {
4904  $tag = strtolower( $tag );
4905  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4906  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4907  }
4908  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4909  $this->mFunctionTagHooks[$tag] : null;
4910  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4911 
4912  if ( !in_array( $tag, $this->mStripList ) ) {
4913  $this->mStripList[] = $tag;
4914  }
4915 
4916  return $old;
4917  }
4918 
4926  public function replaceLinkHolders( &$text, $options = 0 ) {
4927  $this->mLinkHolders->replace( $text );
4928  }
4929 
4937  public function replaceLinkHoldersText( $text ) {
4938  return $this->mLinkHolders->replaceText( $text );
4939  }
4940 
4954  public function renderImageGallery( $text, $params ) {
4955 
4956  $mode = false;
4957  if ( isset( $params['mode'] ) ) {
4958  $mode = $params['mode'];
4959  }
4960 
4961  try {
4962  $ig = ImageGalleryBase::factory( $mode );
4963  } catch ( Exception $e ) {
4964  // If invalid type set, fallback to default.
4965  $ig = ImageGalleryBase::factory( false );
4966  }
4967 
4968  $ig->setContextTitle( $this->mTitle );
4969  $ig->setShowBytes( false );
4970  $ig->setShowFilename( false );
4971  $ig->setParser( $this );
4972  $ig->setHideBadImages();
4973  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'ul' ) );
4974 
4975  if ( isset( $params['showfilename'] ) ) {
4976  $ig->setShowFilename( true );
4977  } else {
4978  $ig->setShowFilename( false );
4979  }
4980  if ( isset( $params['caption'] ) ) {
4981  $caption = $params['caption'];
4982  $caption = htmlspecialchars( $caption );
4983  $caption = $this->replaceInternalLinks( $caption );
4984  $ig->setCaptionHtml( $caption );
4985  }
4986  if ( isset( $params['perrow'] ) ) {
4987  $ig->setPerRow( $params['perrow'] );
4988  }
4989  if ( isset( $params['widths'] ) ) {
4990  $ig->setWidths( $params['widths'] );
4991  }
4992  if ( isset( $params['heights'] ) ) {
4993  $ig->setHeights( $params['heights'] );
4994  }
4995  $ig->setAdditionalOptions( $params );
4996 
4997  // Avoid PHP 7.1 warning from passing $this by reference
4998  $parser = $this;
4999  Hooks::run( 'BeforeParserrenderImageGallery', [ &$parser, &$ig ] );
5000 
5001  $lines = StringUtils::explode( "\n", $text );
5002  foreach ( $lines as $line ) {
5003  # match lines like these:
5004  # Image:someimage.jpg|This is some image
5005  $matches = [];
5006  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5007  # Skip empty lines
5008  if ( count( $matches ) == 0 ) {
5009  continue;
5010  }
5011 
5012  if ( strpos( $matches[0], '%' ) !== false ) {
5013  $matches[1] = rawurldecode( $matches[1] );
5014  }
5016  if ( is_null( $title ) ) {
5017  # Bogus title. Ignore these so we don't bomb out later.
5018  continue;
5019  }
5020 
5021  # We need to get what handler the file uses, to figure out parameters.
5022  # Note, a hook can overide the file name, and chose an entirely different
5023  # file (which potentially could be of a different type and have different handler).
5024  $options = [];
5025  $descQuery = false;
5026  Hooks::run( 'BeforeParserFetchFileAndTitle',
5027  [ $this, $title, &$options, &$descQuery ] );
5028  # Don't register it now, as TraditionalImageGallery does that later.
5029  $file = $this->fetchFileNoRegister( $title, $options );
5030  $handler = $file ? $file->getHandler() : false;
5031 
5032  $paramMap = [
5033  'img_alt' => 'gallery-internal-alt',
5034  'img_link' => 'gallery-internal-link',
5035  ];
5036  if ( $handler ) {
5037  $paramMap = $paramMap + $handler->getParamMap();
5038  // We don't want people to specify per-image widths.
5039  // Additionally the width parameter would need special casing anyhow.
5040  unset( $paramMap['img_width'] );
5041  }
5042 
5043  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5044 
5045  $label = '';
5046  $alt = '';
5047  $link = '';
5048  $handlerOptions = [];
5049  if ( isset( $matches[3] ) ) {
5050  // look for an |alt= definition while trying not to break existing
5051  // captions with multiple pipes (|) in it, until a more sensible grammar
5052  // is defined for images in galleries
5053 
5054  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5055  // splitting on '|' is a bit odd, and different from makeImage.
5056  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5057  // Protect LanguageConverter markup
5058  $parameterMatches = StringUtils::delimiterExplode(
5059  '-{', '}-', '|', $matches[3], true /* nested */
5060  );
5061 
5062  foreach ( $parameterMatches as $parameterMatch ) {
5063  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5064  if ( $magicName ) {
5065  $paramName = $paramMap[$magicName];
5066 
5067  switch ( $paramName ) {
5068  case 'gallery-internal-alt':
5069  $alt = $this->stripAltText( $match, false );
5070  break;
5071  case 'gallery-internal-link':
5072  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5073  $chars = self::EXT_LINK_URL_CLASS;
5074  $addr = self::EXT_LINK_ADDR;
5075  $prots = $this->mUrlProtocols;
5076  // check to see if link matches an absolute url, if not then it must be a wiki link.
5077  if ( preg_match( '/^-{R|(.*)}-$/', $linkValue ) ) {
5078  // Result of LanguageConverter::markNoConversion
5079  // invoked on an external link.
5080  $linkValue = substr( $linkValue, 4, -2 );
5081  }
5082  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
5083  $link = $linkValue;
5084  $this->mOutput->addExternalLink( $link );
5085  } else {
5086  $localLinkTitle = Title::newFromText( $linkValue );
5087  if ( $localLinkTitle !== null ) {
5088  $this->mOutput->addLink( $localLinkTitle );
5089  $link = $localLinkTitle->getLinkURL();
5090  }
5091  }
5092  break;
5093  default:
5094  // Must be a handler specific parameter.
5095  if ( $handler->validateParam( $paramName, $match ) ) {
5096  $handlerOptions[$paramName] = $match;
5097  } else {
5098  // Guess not, consider it as caption.
5099  wfDebug( "$parameterMatch failed parameter validation\n" );
5100  $label = '|' . $parameterMatch;
5101  }
5102  }
5103 
5104  } else {
5105  // Last pipe wins.
5106  $label = '|' . $parameterMatch;
5107  }
5108  }
5109  // Remove the pipe.
5110  $label = substr( $label, 1 );
5111  }
5112 
5113  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5114  }
5115  $html = $ig->toHTML();
5116  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5117  return $html;
5118  }
5119 
5124  public function getImageParams( $handler ) {
5125  if ( $handler ) {
5126  $handlerClass = get_class( $handler );
5127  } else {
5128  $handlerClass = '';
5129  }
5130  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5131  # Initialise static lists
5132  static $internalParamNames = [
5133  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5134  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5135  'bottom', 'text-bottom' ],
5136  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5137  'upright', 'border', 'link', 'alt', 'class' ],
5138  ];
5139  static $internalParamMap;
5140  if ( !$internalParamMap ) {
5141  $internalParamMap = [];
5142  foreach ( $internalParamNames as $type => $names ) {
5143  foreach ( $names as $name ) {
5144  // For grep: img_left, img_right, img_center, img_none,
5145  // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle,
5146  // img_bottom, img_text_bottom,
5147  // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright,
5148  // img_border, img_link, img_alt, img_class
5149  $magicName = str_replace( '-', '_', "img_$name" );
5150  $internalParamMap[$magicName] = [ $type, $name ];
5151  }
5152  }
5153  }
5154 
5155  # Add handler params
5156  $paramMap = $internalParamMap;
5157  if ( $handler ) {
5158  $handlerParamMap = $handler->getParamMap();
5159  foreach ( $handlerParamMap as $magic => $paramName ) {
5160  $paramMap[$magic] = [ 'handler', $paramName ];
5161  }
5162  }
5163  $this->mImageParams[$handlerClass] = $paramMap;
5164  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5165  }
5166  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5167  }
5168 
5177  public function makeImage( $title, $options, $holders = false ) {
5178  # Check if the options text is of the form "options|alt text"
5179  # Options are:
5180  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5181  # * left no resizing, just left align. label is used for alt= only
5182  # * right same, but right aligned
5183  # * none same, but not aligned
5184  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5185  # * center center the image
5186  # * frame Keep original image size, no magnify-button.
5187  # * framed Same as "frame"
5188  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5189  # * upright reduce width for upright images, rounded to full __0 px
5190  # * border draw a 1px border around the image
5191  # * alt Text for HTML alt attribute (defaults to empty)
5192  # * class Set a class for img node
5193  # * link Set the target of the image link. Can be external, interwiki, or local
5194  # vertical-align values (no % or length right now):
5195  # * baseline
5196  # * sub
5197  # * super
5198  # * top
5199  # * text-top
5200  # * middle
5201  # * bottom
5202  # * text-bottom
5203 
5204  # Protect LanguageConverter markup when splitting into parts
5206  '-{', '}-', '|', $options, true /* allow nesting */
5207  );
5208 
5209  # Give extensions a chance to select the file revision for us
5210  $options = [];
5211  $descQuery = false;
5212  Hooks::run( 'BeforeParserFetchFileAndTitle',
5213  [ $this, $title, &$options, &$descQuery ] );
5214  # Fetch and register the file (file title may be different via hooks)
5215  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5216 
5217  # Get parameter map
5218  $handler = $file ? $file->getHandler() : false;
5219 
5220  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5221 
5222  if ( !$file ) {
5223  $this->addTrackingCategory( 'broken-file-category' );
5224  }
5225 
5226  # Process the input parameters
5227  $caption = '';
5228  $params = [ 'frame' => [], 'handler' => [],
5229  'horizAlign' => [], 'vertAlign' => [] ];
5230  $seenformat = false;
5231  foreach ( $parts as $part ) {
5232  $part = trim( $part );
5233  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5234  $validated = false;
5235  if ( isset( $paramMap[$magicName] ) ) {
5236  list( $type, $paramName ) = $paramMap[$magicName];
5237 
5238  # Special case; width and height come in one variable together
5239  if ( $type === 'handler' && $paramName === 'width' ) {
5240  $parsedWidthParam = $this->parseWidthParam( $value );
5241  if ( isset( $parsedWidthParam['width'] ) ) {
5242  $width = $parsedWidthParam['width'];
5243  if ( $handler->validateParam( 'width', $width ) ) {
5244  $params[$type]['width'] = $width;
5245  $validated = true;
5246  }
5247  }
5248  if ( isset( $parsedWidthParam['height'] ) ) {
5249  $height = $parsedWidthParam['height'];
5250  if ( $handler->validateParam( 'height', $height ) ) {
5251  $params[$type]['height'] = $height;
5252  $validated = true;
5253  }
5254  }
5255  # else no validation -- T15436
5256  } else {
5257  if ( $type === 'handler' ) {
5258  # Validate handler parameter
5259  $validated = $handler->validateParam( $paramName, $value );
5260  } else {
5261  # Validate internal parameters
5262  switch ( $paramName ) {
5263  case 'manualthumb':
5264  case 'alt':
5265  case 'class':
5266  # @todo FIXME: Possibly check validity here for
5267  # manualthumb? downstream behavior seems odd with
5268  # missing manual thumbs.
5269  $validated = true;
5270  $value = $this->stripAltText( $value, $holders );
5271  break;
5272  case 'link':
5273  $chars = self::EXT_LINK_URL_CLASS;
5274  $addr = self::EXT_LINK_ADDR;
5275  $prots = $this->mUrlProtocols;
5276  if ( $value === '' ) {
5277  $paramName = 'no-link';
5278  $value = true;
5279  $validated = true;
5280  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5281  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5282  $paramName = 'link-url';
5283  $this->mOutput->addExternalLink( $value );
5284  if ( $this->mOptions->getExternalLinkTarget() ) {
5285  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5286  }
5287  $validated = true;
5288  }
5289  } else {
5290  $linkTitle = Title::newFromText( $value );
5291  if ( $linkTitle ) {
5292  $paramName = 'link-title';
5293  $value = $linkTitle;
5294  $this->mOutput->addLink( $linkTitle );
5295  $validated = true;
5296  }
5297  }
5298  break;
5299  case 'frameless':
5300  case 'framed':
5301  case 'thumbnail':
5302  // use first appearing option, discard others.
5303  $validated = !$seenformat;
5304  $seenformat = true;
5305  break;
5306  default:
5307  # Most other things appear to be empty or numeric...
5308  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5309  }
5310  }
5311 
5312  if ( $validated ) {
5313  $params[$type][$paramName] = $value;
5314  }
5315  }
5316  }
5317  if ( !$validated ) {
5318  $caption = $part;
5319  }
5320  }
5321 
5322  # Process alignment parameters
5323  if ( $params['horizAlign'] ) {
5324  $params['frame']['align'] = key( $params['horizAlign'] );
5325  }
5326  if ( $params['vertAlign'] ) {
5327  $params['frame']['valign'] = key( $params['vertAlign'] );
5328  }
5329 
5330  $params['frame']['caption'] = $caption;
5331 
5332  # Will the image be presented in a frame, with the caption below?
5333  $imageIsFramed = isset( $params['frame']['frame'] )
5334  || isset( $params['frame']['framed'] )
5335  || isset( $params['frame']['thumbnail'] )
5336  || isset( $params['frame']['manualthumb'] );
5337 
5338  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5339  # came to also set the caption, ordinary text after the image -- which
5340  # makes no sense, because that just repeats the text multiple times in
5341  # screen readers. It *also* came to set the title attribute.
5342  # Now that we have an alt attribute, we should not set the alt text to
5343  # equal the caption: that's worse than useless, it just repeats the
5344  # text. This is the framed/thumbnail case. If there's no caption, we
5345  # use the unnamed parameter for alt text as well, just for the time be-
5346  # ing, if the unnamed param is set and the alt param is not.
5347  # For the future, we need to figure out if we want to tweak this more,
5348  # e.g., introducing a title= parameter for the title; ignoring the un-
5349  # named parameter entirely for images without a caption; adding an ex-
5350  # plicit caption= parameter and preserving the old magic unnamed para-
5351  # meter for BC; ...
5352  if ( $imageIsFramed ) { # Framed image
5353  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5354  # No caption or alt text, add the filename as the alt text so
5355  # that screen readers at least get some description of the image
5356  $params['frame']['alt'] = $title->getText();
5357  }
5358  # Do not set $params['frame']['title'] because tooltips don't make sense
5359  # for framed images
5360  } else { # Inline image
5361  if ( !isset( $params['frame']['alt'] ) ) {
5362  # No alt text, use the "caption" for the alt text
5363  if ( $caption !== '' ) {
5364  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5365  } else {
5366  # No caption, fall back to using the filename for the
5367  # alt text
5368  $params['frame']['alt'] = $title->getText();
5369  }
5370  }
5371  # Use the "caption" for the tooltip text
5372  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5373  }
5374 
5375  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5376 
5377  # Linker does the rest
5378  $time = isset( $options['time'] ) ? $options['time'] : false;
5379  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5380  $time, $descQuery, $this->mOptions->getThumbSize() );
5381 
5382  # Give the handler a chance to modify the parser object
5383  if ( $handler ) {
5384  $handler->parserTransformHook( $this, $file );
5385  }
5386 
5387  return $ret;
5388  }
5389 
5395  protected function stripAltText( $caption, $holders ) {
5396  # Strip bad stuff out of the title (tooltip). We can't just use
5397  # replaceLinkHoldersText() here, because if this function is called
5398  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5399  if ( $holders ) {
5400  $tooltip = $holders->replaceText( $caption );
5401  } else {
5402  $tooltip = $this->replaceLinkHoldersText( $caption );
5403  }
5404 
5405  # make sure there are no placeholders in thumbnail attributes
5406  # that are later expanded to html- so expand them now and
5407  # remove the tags
5408  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5409  $tooltip = Sanitizer::stripAllTags( $tooltip );
5410 
5411  return $tooltip;
5412  }
5413 
5419  public function disableCache() {
5420  wfDebug( "Parser output marked as uncacheable.\n" );
5421  if ( !$this->mOutput ) {
5422  throw new MWException( __METHOD__ .
5423  " can only be called when actually parsing something" );
5424  }
5425  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5426  }
5427 
5436  public function attributeStripCallback( &$text, $frame = false ) {
5437  $text = $this->replaceVariables( $text, $frame );
5438  $text = $this->mStripState->unstripBoth( $text );
5439  return $text;
5440  }
5441 
5447  public function getTags() {
5448  return array_merge(
5449  array_keys( $this->mTransparentTagHooks ),
5450  array_keys( $this->mTagHooks ),
5451  array_keys( $this->mFunctionTagHooks )
5452  );
5453  }
5454 
5465  public function replaceTransparentTags( $text ) {
5466  $matches = [];
5467  $elements = array_keys( $this->mTransparentTagHooks );
5468  $text = self::extractTagsAndParams( $elements, $text, $matches );
5469  $replacements = [];
5470 
5471  foreach ( $matches as $marker => $data ) {
5472  list( $element, $content, $params, $tag ) = $data;
5473  $tagName = strtolower( $element );
5474  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5475  $output = call_user_func_array(
5476  $this->mTransparentTagHooks[$tagName],
5477  [ $content, $params, $this ]
5478  );
5479  } else {
5480  $output = $tag;
5481  }
5482  $replacements[$marker] = $output;
5483  }
5484  return strtr( $text, $replacements );
5485  }
5486 
5516  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5517  global $wgTitle; # not generally used but removes an ugly failure mode
5518 
5519  $magicScopeVariable = $this->lock();
5520  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5521  $outText = '';
5522  $frame = $this->getPreprocessor()->newFrame();
5523 
5524  # Process section extraction flags
5525  $flags = 0;
5526  $sectionParts = explode( '-', $sectionId );
5527  $sectionIndex = array_pop( $sectionParts );
5528  foreach ( $sectionParts as $part ) {
5529  if ( $part === 'T' ) {
5530  $flags |= self::PTD_FOR_INCLUSION;
5531  }
5532  }
5533 
5534  # Check for empty input
5535  if ( strval( $text ) === '' ) {
5536  # Only sections 0 and T-0 exist in an empty document
5537  if ( $sectionIndex == 0 ) {
5538  if ( $mode === 'get' ) {
5539  return '';
5540  } else {
5541  return $newText;
5542  }
5543  } else {
5544  if ( $mode === 'get' ) {
5545  return $newText;
5546  } else {
5547  return $text;
5548  }
5549  }
5550  }
5551 
5552  # Preprocess the text
5553  $root = $this->preprocessToDom( $text, $flags );
5554 
5555  # <h> nodes indicate section breaks
5556  # They can only occur at the top level, so we can find them by iterating the root's children
5557  $node = $root->getFirstChild();
5558 
5559  # Find the target section
5560  if ( $sectionIndex == 0 ) {
5561  # Section zero doesn't nest, level=big
5562  $targetLevel = 1000;
5563  } else {
5564  while ( $node ) {
5565  if ( $node->getName() === 'h' ) {
5566  $bits = $node->splitHeading();
5567  if ( $bits['i'] == $sectionIndex ) {
5568  $targetLevel = $bits['level'];
5569  break;
5570  }
5571  }
5572  if ( $mode === 'replace' ) {
5573  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5574  }
5575  $node = $node->getNextSibling();
5576  }
5577  }
5578 
5579  if ( !$node ) {
5580  # Not found
5581  if ( $mode === 'get' ) {
5582  return $newText;
5583  } else {
5584  return $text;
5585  }
5586  }
5587 
5588  # Find the end of the section, including nested sections
5589  do {
5590  if ( $node->getName() === 'h' ) {
5591  $bits = $node->splitHeading();
5592  $curLevel = $bits['level'];
5593  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5594  break;
5595  }
5596  }
5597  if ( $mode === 'get' ) {
5598  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5599  }
5600  $node = $node->getNextSibling();
5601  } while ( $node );
5602 
5603  # Write out the remainder (in replace mode only)
5604  if ( $mode === 'replace' ) {
5605  # Output the replacement text
5606  # Add two newlines on -- trailing whitespace in $newText is conventionally
5607  # stripped by the editor, so we need both newlines to restore the paragraph gap
5608  # Only add trailing whitespace if there is newText
5609  if ( $newText != "" ) {
5610  $outText .= $newText . "\n\n";
5611  }
5612 
5613  while ( $node ) {
5614  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5615  $node = $node->getNextSibling();
5616  }
5617  }
5618 
5619  if ( is_string( $outText ) ) {
5620  # Re-insert stripped tags
5621  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5622  }
5623 
5624  return $outText;
5625  }
5626 
5641  public function getSection( $text, $sectionId, $defaultText = '' ) {
5642  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5643  }
5644 
5657  public function replaceSection( $oldText, $sectionId, $newText ) {
5658  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5659  }
5660 
5666  public function getRevisionId() {
5667  return $this->mRevisionId;
5668  }
5669 
5676  public function getRevisionObject() {
5677  if ( !is_null( $this->mRevisionObject ) ) {
5678  return $this->mRevisionObject;
5679  }
5680  if ( is_null( $this->mRevisionId ) ) {
5681  return null;
5682  }
5683 
5684  $rev = call_user_func(
5685  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5686  );
5687 
5688  # If the parse is for a new revision, then the callback should have
5689  # already been set to force the object and should match mRevisionId.
5690  # If not, try to fetch by mRevisionId for sanity.
5691  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5692  $rev = Revision::newFromId( $this->mRevisionId );
5693  }
5694 
5695  $this->mRevisionObject = $rev;
5696 
5697  return $this->mRevisionObject;
5698  }
5699 
5705  public function getRevisionTimestamp() {
5706  if ( is_null( $this->mRevisionTimestamp ) ) {
5708 
5709  $revObject = $this->getRevisionObject();
5710  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5711 
5712  # The cryptic '' timezone parameter tells to use the site-default
5713  # timezone offset instead of the user settings.
5714  # Since this value will be saved into the parser cache, served
5715  # to other users, and potentially even used inside links and such,
5716  # it needs to be consistent for all visitors.
5717  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5718 
5719  }
5720  return $this->mRevisionTimestamp;
5721  }
5722 
5728  public function getRevisionUser() {
5729  if ( is_null( $this->mRevisionUser ) ) {
5730  $revObject = $this->getRevisionObject();
5731 
5732  # if this template is subst: the revision id will be blank,
5733  # so just use the current user's name
5734  if ( $revObject ) {
5735  $this->mRevisionUser = $revObject->getUserText();
5736  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5737  $this->mRevisionUser = $this->getUser()->getName();
5738  }
5739  }
5740  return $this->mRevisionUser;
5741  }
5742 
5748  public function getRevisionSize() {
5749  if ( is_null( $this->mRevisionSize ) ) {
5750  $revObject = $this->getRevisionObject();
5751 
5752  # if this variable is subst: the revision id will be blank,
5753  # so just use the parser input size, because the own substituation
5754  # will change the size.
5755  if ( $revObject ) {
5756  $this->mRevisionSize = $revObject->getSize();
5757  } else {
5758  $this->mRevisionSize = $this->mInputSize;
5759  }
5760  }
5761  return $this->mRevisionSize;
5762  }
5763 
5769  public function setDefaultSort( $sort ) {
5770  $this->mDefaultSort = $sort;
5771  $this->mOutput->setProperty( 'defaultsort', $sort );
5772  }
5773 
5784  public function getDefaultSort() {
5785  if ( $this->mDefaultSort !== false ) {
5786  return $this->mDefaultSort;
5787  } else {
5788  return '';
5789  }
5790  }
5791 
5798  public function getCustomDefaultSort() {
5799  return $this->mDefaultSort;
5800  }
5801 
5811  public function guessSectionNameFromWikiText( $text ) {
5812  # Strip out wikitext links(they break the anchor)
5813  $text = $this->stripSectionName( $text );
5814  $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5815  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5816  }
5817 
5826  public function guessLegacySectionNameFromWikiText( $text ) {
5827  # Strip out wikitext links(they break the anchor)
5828  $text = $this->stripSectionName( $text );
5829  $text = Sanitizer::normalizeSectionNameWhitespace( $text );
5830  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5831  }
5832 
5847  public function stripSectionName( $text ) {
5848  # Strip internal link markup
5849  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5850  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5851 
5852  # Strip external link markup
5853  # @todo FIXME: Not tolerant to blank link text
5854  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5855  # on how many empty links there are on the page - need to figure that out.
5856  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5857 
5858  # Parse wikitext quotes (italics & bold)
5859  $text = $this->doQuotes( $text );
5860 
5861  # Strip HTML tags
5862  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5863  return $text;
5864  }
5865 
5876  public function testSrvus( $text, Title $title, ParserOptions $options,
5877  $outputType = self::OT_HTML
5878  ) {
5879  $magicScopeVariable = $this->lock();
5880  $this->startParse( $title, $options, $outputType, true );
5881 
5882  $text = $this->replaceVariables( $text );
5883  $text = $this->mStripState->unstripBoth( $text );
5884  $text = Sanitizer::removeHTMLtags( $text );
5885  return $text;
5886  }
5887 
5894  public function testPst( $text, Title $title, ParserOptions $options ) {
5895  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5896  }
5897 
5904  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5905  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5906  }
5907 
5924  public function markerSkipCallback( $s, $callback ) {
5925  $i = 0;
5926  $out = '';
5927  while ( $i < strlen( $s ) ) {
5928  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5929  if ( $markerStart === false ) {
5930  $out .= call_user_func( $callback, substr( $s, $i ) );
5931  break;
5932  } else {
5933  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5934  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5935  if ( $markerEnd === false ) {
5936  $out .= substr( $s, $markerStart );
5937  break;
5938  } else {
5939  $markerEnd += strlen( self::MARKER_SUFFIX );
5940  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5941  $i = $markerEnd;
5942  }
5943  }
5944  }
5945  return $out;
5946  }
5947 
5954  public function killMarkers( $text ) {
5955  return $this->mStripState->killMarkers( $text );
5956  }
5957 
5974  public function serializeHalfParsedText( $text ) {
5975  $data = [
5976  'text' => $text,
5977  'version' => self::HALF_PARSED_VERSION,
5978  'stripState' => $this->mStripState->getSubState( $text ),
5979  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5980  ];
5981  return $data;
5982  }
5983 
5999  public function unserializeHalfParsedText( $data ) {
6000  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
6001  throw new MWException( __METHOD__ . ': invalid version' );
6002  }
6003 
6004  # First, extract the strip state.
6005  $texts = [ $data['text'] ];
6006  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6007 
6008  # Now renumber links
6009  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6010 
6011  # Should be good to go.
6012  return $texts[0];
6013  }
6014 
6024  public function isValidHalfParsedText( $data ) {
6025  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6026  }
6027 
6036  public function parseWidthParam( $value ) {
6037  $parsedWidthParam = [];
6038  if ( $value === '' ) {
6039  return $parsedWidthParam;
6040  }
6041  $m = [];
6042  # (T15500) In both cases (width/height and width only),
6043  # permit trailing "px" for backward compatibility.
6044  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6045  $width = intval( $m[1] );
6046  $height = intval( $m[2] );
6047  $parsedWidthParam['width'] = $width;
6048  $parsedWidthParam['height'] = $height;
6049  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6050  $width = intval( $value );
6051  $parsedWidthParam['width'] = $width;
6052  }
6053  return $parsedWidthParam;
6054  }
6055 
6065  protected function lock() {
6066  if ( $this->mInParse ) {
6067  throw new MWException( "Parser state cleared while parsing. "
6068  . "Did you call Parser::parse recursively?" );
6069  }
6070  $this->mInParse = true;
6071 
6072  $recursiveCheck = new ScopedCallback( function() {
6073  $this->mInParse = false;
6074  } );
6075 
6076  return $recursiveCheck;
6077  }
6078 
6089  public static function stripOuterParagraph( $html ) {
6090  $m = [];
6091  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
6092  if ( strpos( $m[1], '</p>' ) === false ) {
6093  $html = $m[1];
6094  }
6095  }
6096 
6097  return $html;
6098  }
6099 
6110  public function getFreshParser() {
6111  global $wgParserConf;
6112  if ( $this->mInParse ) {
6113  return new $wgParserConf['class']( $wgParserConf );
6114  } else {
6115  return $this;
6116  }
6117  }
6118 
6125  public function enableOOUI() {
6127  $this->mOutput->setEnableOOUI( true );
6128  }
6129 }
OT_MSG
const OT_MSG
Definition: Defines.php:185
SiteStats\articles
static articles()
Definition: SiteStats.php:144
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:33
save
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
MagicWordArray
Class for handling an array of magic words.
Definition: MagicWordArray.php:31
broken
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:1956
if
if($IP===false)
Definition: cleanupArchiveUserText.php:4
MWHttpRequest\factory
static factory( $url, $options=null, $caller=__METHOD__)
Generate a new request object.
Definition: MWHttpRequest.php:180
object
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
$context
error also a ContextSource you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2612
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:265
PPFrame\STRIP_COMMENTS
const STRIP_COMMENTS
Definition: Preprocessor.php:168
MWNamespace\isNonincludable
static isNonincludable( $index)
It is not possible to use pages from this namespace as template?
Definition: MWNamespace.php:422
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
ParserOutput
Definition: ParserOutput.php:24
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
SiteStats\users
static users()
Definition: SiteStats.php:160
SiteStats\activeUsers
static activeUsers()
Definition: SiteStats.php:168
is
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
Linker\makeSelfLinkObj
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition: Linker.php:181
PPFrame\NO_ARGS
const NO_ARGS
Definition: Preprocessor.php:166
wfSetVar
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...
Definition: GlobalFunctions.php:1713
captcha-old.count
count
Definition: captcha-old.py:225
Linker\tocIndent
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1503
text
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
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:42
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1954
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1994
SiteStats\pages
static pages()
Definition: SiteStats.php:152
OutputPage\setupOOUI
static setupOOUI( $skinName='', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
Definition: OutputPage.php:3971
$status
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
Revision\newKnownCurrent
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:1921
captcha-old.word
def word
Definition: captcha-old.py:242
use
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 use
Definition: MIT-LICENSE.txt:10
wfUrlencode
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
Definition: GlobalFunctions.php:371
SiteStats\numberingroup
static numberingroup( $group)
Find the number of users in a given user group.
Definition: SiteStats.php:186
$user
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 account $user
Definition: hooks.txt:246
$req
this hook is for auditing only $req
Definition: hooks.txt:990
it
=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
Definition: contenthandler.txt:104
normal
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
SFH_OBJECT_ARGS
const SFH_OBJECT_ARGS
Definition: Defines.php:196
MagicWord\get
static & get( $id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:258
OT_PREPROCESS
const OT_PREPROCESS
Definition: Defines.php:184
NS_FILE
const NS_FILE
Definition: Defines.php:68
OT_PLAIN
const OT_PLAIN
Definition: Defines.php:186
$params
$params
Definition: styleTest.css.php:40
wfHostname
wfHostname()
Fetch server name for use in error reporting etc.
Definition: GlobalFunctions.php:1435
NS_TEMPLATE
const NS_TEMPLATE
Definition: Defines.php:72
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:556
link
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:2929
SpecialPageFactory\capturePath
static capturePath(Title $title, IContextSource $context, LinkRenderer $linkRenderer=null)
Just like executePath() but will override global variables and execute the page in "inclusion" mode.
Definition: SpecialPageFactory.php:598
$s
$s
Definition: mergeMessageFileList.php:188
SpecialPage\getTitleFor
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
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') if(! $wgEnableAPI) $wgTitle
Definition: api.php:68
MWTidy\isEnabled
static isEnabled()
Definition: MWTidy.php:79
so
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is and will automatically terminate your rights under this License parties who have received or from you under this License will not have their licenses terminated so long as such parties remain in full compliance You are not required to accept this since you have not signed it nothing else grants you permission to modify or distribute the Program or its derivative works These actions are prohibited by law if you do not accept this License by modifying or distributing the you indicate your acceptance of this License to do so
Definition: COPYING.txt:185
StripState
Definition: StripState.php:28
Makefile.open
open
Definition: Makefile.py:18
$type
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2536
Linker\tocLine
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1529
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1092
$wgStylePath
$wgStylePath
The URL path of the skins directory.
Definition: DefaultSettings.php:217
php
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
PPFrame\NO_TEMPLATES
const NO_TEMPLATES
Definition: Preprocessor.php:167
Preprocessor
Definition: Preprocessor.php:29
later
If you want to remove the page from your watchlist later
Definition: All_system_messages.txt:361
SiteStats\images
static images()
Definition: SiteStats.php:176
StringUtils\replaceMarkup
static replaceMarkup( $search, $replace, $text)
More or less "markup-safe" str_replace() Ignores any instances of the separator inside <....
Definition: StringUtils.php:298
key
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
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:134
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:51
MagicWord\getVariableIDs
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:272
used
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:117
$html
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:1956
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
MagicWord\getCacheTTL
static getCacheTTL( $id)
Allow external reads of TTL array.
Definition: MagicWord.php:295
a
</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre >< span ></span >< span class="kd"> var</span >< span class="nx"> a</span >< span class="p"></span ></pre ></div > ! end ! test Multiline< source/> in lists !input *< source > a b</source > *foo< source > a b</source > ! html< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! html tidy< ul >< li >< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul >< ul >< li > foo< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > a b</pre ></div ></li ></ul > ! end ! test Custom attributes !input< source lang="javascript" id="foo" class="bar" dir="rtl" style="font-size: larger;"> var a
Definition: parserTests.txt:89
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1128
table
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
BlockLevelPass\doBlockLevels
static doBlockLevels( $text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: BlockLevelPass.php:50
$content
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3060
wfUrlProtocolsWithoutProtRel
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
Definition: GlobalFunctions.php:803
Linker\tocList
static tocList( $toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
Definition: Linker.php:1559
$tag
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1028
ContextSource\getOutput
getOutput()
Get the OutputPage object.
Definition: ContextSource.php:123
$matches
$matches
Definition: NoLocalSettings.php:24
in
null for the wiki Added in
Definition: hooks.txt:1572
CoreTagHooks\register
static register( $parser)
Definition: CoreTagHooks.php:33
mode
if write to the Free Software Franklin Fifth MA USA Also add information on how to contact you by electronic and paper mail If the program is make it output a short notice like this when it starts in an interactive mode
Definition: COPYING.txt:307
directly
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
StringUtils\explode
static explode( $separator, $subject)
Workalike for explode() with limited memory usage.
Definition: StringUtils.php:335
PPNode
There are three types of nodes:
Definition: Preprocessor.php:358
LinkHolderArray
Definition: LinkHolderArray.php:27
PPFrame\RECOVER_ORIG
const RECOVER_ORIG
Definition: Preprocessor.php:173
$attribs
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:1956
Linker\makeHeadline
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1615
not
if not
Definition: COPYING.txt:307
$limit
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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 please use GetContentModels hook to make them known to core 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:1049
Linker\tocLineEnd
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1547
MapCacheLRU
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:34
MWNamespace\hasSubpages
static hasSubpages( $index)
Does the namespace allow subpages?
Definition: MWNamespace.php:330
form
null means default in associative array form
Definition: hooks.txt:1956
$lines
$lines
Definition: router.php:67
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2536
MWTimestamp\getInstance
static getInstance( $ts=false)
Get a timestamp instance in GMT.
Definition: MWTimestamp.php:39
$output
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:838
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1769
OT_WIKI
const OT_WIKI
Definition: Defines.php:183
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:514
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
SectionProfiler
Custom PHP profiler for parser/DB type section names that xhprof/xdebug can't handle.
Definition: SectionProfiler.php:31
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2023
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:76
RequestContext
Group all the pieces relevant to the context of a request into one instance.
Definition: RequestContext.php:33
DB_MASTER
const DB_MASTER
Definition: defines.php:26
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:999
list
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
specified
! hooks source ! endhooks ! test Non existent language !input< source lang="doesnotexist"> foobar</source > ! result< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > foobar</pre ></div > ! end ! test No language specified ! wikitext< source > foo</source > ! html< div class="mw-highlight mw-content-ltr" dir="ltr">< pre > foo</pre ></div > ! end ! test No language specified(no wellformed xml) !! config !! wikitext< source > bar</source > !! html< div class
$sort
$sort
Definition: profileinfo.php:323
Linker\splitTrail
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:1636
or
or
Definition: COPYING.txt:140
wfUrlProtocols
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
Definition: GlobalFunctions.php:758
SpecialVersion\getVersion
static getVersion( $flags='', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
Definition: SpecialVersion.php:268
$line
$line
Definition: cdb.php:58
CoreParserFunctions\register
static register( $parser)
Definition: CoreParserFunctions.php:34
see
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
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
$value
$value
Definition: styleTest.css.php:45
on
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
$wgServerName
$wgServerName
Server name.
Definition: DefaultSettings.php:125
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:50
variable
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
$wgServer
$wgServer
URL of the server.
Definition: DefaultSettings.php:109
PPFrame
Definition: Preprocessor.php:165
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:2839
$wgSitename
$wgSitename
Name of the site.
Definition: DefaultSettings.php:83
StringUtils\delimiterExplode
static delimiterExplode( $startDelim, $endDelim, $separator, $subject, $nested=false)
Explode a string, but ignore any instances of the separator inside the given start and end delimiters...
Definition: StringUtils.php:68
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1657
$ret
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:1956
display
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
Definition: APACHE-LICENSE-2.0.txt:49
Linker\makeImageLink
static makeImageLink(Parser $parser, Title $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
Definition: Linker.php:319
$handler
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:783
format
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1630
tag
</code > tag
Definition: citeParserTests.txt:219
plain
either a plain
Definition: hooks.txt:2007
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:3101
SFH_NO_HASH
const SFH_NO_HASH
Definition: Defines.php:195
$wgExtraInterlanguageLinkPrefixes
$wgExtraInterlanguageLinkPrefixes
List of additional interwiki prefixes that should be treated as interlanguage links (i....
Definition: DefaultSettings.php:2881
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:45
$args
if( $line===false) $args
Definition: cdb.php:63
page
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk page
Definition: hooks.txt:2536
OT_HTML
const OT_HTML
Definition: Defines.php:182
Title
Represents a title within MediaWiki.
Definition: Title.php:39
CoreParserFunctions\cascadingsources
static cascadingsources( $parser, $title='')
Returns the sources of any cascading protection acting on a specified page.
Definition: CoreParserFunctions.php:1329
and
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
like
For a write use something like
Definition: database.txt:26
type
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
MagicWord\getSubstIDs
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:285
wfMatchesDomainList
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
Definition: GlobalFunctions.php:965
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
output
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
Xml\isWellFormedXmlFragment
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:698
history
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:1741
things
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
Definition: magicword.txt:4
LinkCache\singleton
static singleton()
Get an instance of this class.
Definition: LinkCache.php:67
$rev
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:1741
as
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
Linker\tocUnindent
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1514
public
we sometimes make exceptions for this Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally NO WARRANTY BECAUSE THE PROGRAM IS LICENSED FREE OF THERE IS NO WARRANTY FOR THE TO THE EXTENT PERMITTED BY APPLICABLE LAW EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND OR OTHER PARTIES PROVIDE THE PROGRAM AS IS WITHOUT WARRANTY OF ANY EITHER EXPRESSED OR BUT NOT LIMITED THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU SHOULD THE PROGRAM PROVE YOU ASSUME THE COST OF ALL NECESSARY REPAIR OR CORRECTION IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT OR ANY OTHER PARTY WHO MAY MODIFY AND OR REDISTRIBUTE THE PROGRAM AS PERMITTED BE LIABLE TO YOU FOR INCLUDING ANY INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new and you want it to be of the greatest possible use to the public
Definition: COPYING.txt:284
Linker\makeMediaLinkFile
static makeMediaLinkFile(Title $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition: Linker.php:778
StringUtils\delimiterReplace
static delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags='')
Perform an operation equivalent to preg_replace() with flags.
Definition: StringUtils.php:257
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2929
MagicWord\getDoubleUnderscoreArray
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:308
TextContent\normalizeLineEndings
static normalizeLineEndings( $text)
Do a "\\r\\n" -> "\\n" and "\\r" -> "\\n" transformation as well as trim trailing whitespace.
Definition: TextContent.php:163
$revId
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
captcha-old.parser
parser
Definition: captcha-old.py:187
Linker\normalizeSubpageLink
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition: Linker.php:1353
of
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
Definition: globals.txt:10
wfMessage
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 unset offset - wrap String Wrap the message in html(usually something like "&lt
ImageGalleryBase\factory
static factory( $mode=false, IContextSource $context=null)
Get a new image gallery.
Definition: ImageGalleryBase.php:87
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:70
that
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
SpecialPageFactory\getPage
static getPage( $name)
Find the object with a given name and return it (or NULL)
Definition: SpecialPageFactory.php:379
$t
$t
Definition: testCompression.php:67
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:596
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
MediaWikiServices
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
$wgScriptPath
$wgScriptPath
The path we should point to.
Definition: DefaultSettings.php:141
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:50
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:783
MWTimestamp\getLocalInstance
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Definition: MWTimestamp.php:204
Linker\makeExternalImage
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition: Linker.php:266
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
SiteStats\edits
static edits()
Definition: SiteStats.php:136
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2749
$buffer
$buffer
Definition: mwdoc-filter.php:48
line
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
array
the array() calling protocol came about after MediaWiki 1.4rc1.
$wgContLang
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 content language as $wgContLang
Definition: design.txt:56
wfRandomString
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
Definition: GlobalFunctions.php:336
$out
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:783
MWTidy\tidy
static tidy( $text)
Interface with html tidy.
Definition: MWTidy.php:46